Plugin subsystem. Shown as a last option in song context menu. Part of #407 (#442)

This commit is contained in:
Antic1tizen One 2017-01-08 22:52:07 +04:00 committed by Adrian Ulrich
parent 091d711ff9
commit 58b40d862c
3 changed files with 175 additions and 2 deletions

View File

@ -315,4 +315,7 @@ THE SOFTWARE.
<string name="media_stats_progress">Scan progress</string>
<string name="media_library_scan_running">Scanning media library…</string>
<!-- Plugin system -->
<string name="plugins">Plugins</string>
</resources>

View File

@ -26,9 +26,14 @@ package ch.blinkenlights.android.vanilla;
import ch.blinkenlights.android.medialibrary.MediaLibrary;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
@ -38,7 +43,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.support.iosched.tabs.VanillaTabLayout;
import android.support.v4.view.ViewPager;
import android.text.TextUtils;
@ -56,6 +60,11 @@ import android.widget.TextView;
import android.widget.SearchView;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import junit.framework.Assert;
/**
@ -139,6 +148,19 @@ public class LibraryActivity
* open the playback activity when an item is pressed twice.
*/
private long mLastActedId;
/**
* Holds last intent that was passed to the context menu
*/
private Intent mLastRequestedCtx;
/**
* Plugin descriptions and packages.
* Keys are plugin names, values are their packages
*/
private Map<String, ApplicationInfo> mPlugins = new HashMap<>();
/**
* Broadcast receiver for plugin collecting
*/
private BroadcastReceiver mPluginInfoReceiver;
/**
* The pager adapter that manages each media ListView.
*/
@ -172,6 +194,7 @@ public class LibraryActivity
mViewPager = pager;
SharedPreferences settings = PlaybackService.getSettings(this);
mPluginInfoReceiver = new PluginBroadcastReceiver();
mBottomBarControls = (BottomBarControls)findViewById(R.id.bottombar_controls);
mBottomBarControls.setOnClickListener(this);
@ -195,6 +218,43 @@ public class LibraryActivity
bindControlButtons();
}
/**
* Shows plugin selection dialog. Be sure that {@link #mLastRequestedCtx} is initialized
* and {@link #mPlugins} are populated before calling this.
*
* @see PluginBroadcastReceiver
*/
private void showPluginMenu() {
Set<String> pluginNames = mPlugins.keySet();
final String[] pNamesArr = pluginNames.toArray(new String[pluginNames.size()]);
new AlertDialog.Builder(this)
.setItems(pNamesArr, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
long id = mLastRequestedCtx.getLongExtra("id", LibraryAdapter.INVALID_ID);
Song resolved = MediaUtils.getSongByTypeId(LibraryActivity.this, MediaUtils.TYPE_SONG, id);
if (resolved != null) {
ApplicationInfo selected = mPlugins.get(pNamesArr[which]);
Intent request = new Intent(PluginUtils.ACTION_LAUNCH_PLUGIN);
request.setPackage(selected.packageName);
request.putExtra(PluginUtils.EXTRA_PARAM_URI, Uri.fromFile(new File(resolved.path)));
request.putExtra(PluginUtils.EXTRA_PARAM_SONG_TITLE, resolved.title);
request.putExtra(PluginUtils.EXTRA_PARAM_SONG_ARTIST, resolved.artist);
request.putExtra(PluginUtils.EXTRA_PARAM_SONG_ALBUM, resolved.album);
startService(request);
}
mLastRequestedCtx = null;
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
mLastRequestedCtx = null;
}
}).create().show();
}
@Override
public void onRestart()
{
@ -213,6 +273,18 @@ public class LibraryActivity
updateHeaders();
}
@Override
public void onResume() {
super.onResume();
registerReceiver(mPluginInfoReceiver, new IntentFilter(PluginUtils.ACTION_HANDLE_PLUGIN_PARAMS));
}
@Override
public void onPause() {
super.onPause();
unregisterReceiver(mPluginInfoReceiver);
}
/**
* Load the tab order and update the tab bars if needed.
*/
@ -593,6 +665,7 @@ public class LibraryActivity
private static final int CTX_MENU_MORE_FROM_ALBUM = 8;
private static final int CTX_MENU_MORE_FROM_ARTIST = 9;
private static final int CTX_MENU_OPEN_EXTERNAL = 10;
private static final int CTX_MENU_PLUGINS = 11;
/**
* Creates a context menu for an adapter row.
@ -627,8 +700,11 @@ public class LibraryActivity
}
if (type == MediaUtils.TYPE_ALBUM || type == MediaUtils.TYPE_SONG)
menu.add(0, CTX_MENU_MORE_FROM_ARTIST, 0, R.string.more_from_artist).setIntent(rowData);
if (type == MediaUtils.TYPE_SONG)
if (type == MediaUtils.TYPE_SONG) {
menu.add(0, CTX_MENU_MORE_FROM_ALBUM, 0, R.string.more_from_album).setIntent(rowData);
if (PluginUtils.checkPlugins(this))
menu.add(0, CTX_MENU_PLUGINS, 1, R.string.plugins).setIntent(rowData); // last in order
}
menu.addSubMenu(0, CTX_MENU_ADD_TO_PLAYLIST, 0, R.string.add_to_playlist).getItem().setIntent(rowData);
menu.add(0, CTX_MENU_DELETE, 0, R.string.delete).setIntent(rowData);
}
@ -709,6 +785,15 @@ public class LibraryActivity
FileUtils.dispatchIntent(this, intent);
break;
}
case CTX_MENU_PLUGINS: {
// obtain list of plugins anew - some plugins may be installed/deleted
mPlugins.clear();
mLastRequestedCtx = intent;
Intent requestPlugins = new Intent(PluginUtils.ACTION_REQUEST_PLUGIN_PARAMS);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
sendBroadcast(requestPlugins);
break;
}
case CTX_MENU_MORE_FROM_ARTIST: {
String selection;
if (intent.getIntExtra(LibraryAdapter.DATA_TYPE, -1) == MediaUtils.TYPE_ALBUM) {
@ -923,4 +1008,20 @@ public class LibraryActivity
}
}
private class PluginBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
List<ResolveInfo> resolved = getPackageManager().queryBroadcastReceivers(new Intent(PluginUtils.ACTION_REQUEST_PLUGIN_PARAMS), 0);
if (PluginUtils.ACTION_HANDLE_PLUGIN_PARAMS.equals(intent.getAction())) {
// plugin answered, store it in the map
String pluginName = intent.getStringExtra(PluginUtils.EXTRA_PARAM_PLUGIN_NAME);
ApplicationInfo info = intent.getParcelableExtra(PluginUtils.EXTRA_PARAM_PLUGIN_APP);
mPlugins.put(pluginName, info);
if (mPlugins.size() == resolved.size()) { // got all plugins
showPluginMenu();
}
}
}
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (C) 2016 Oleg Chernovskiy <adonai@xaker.ru>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ch.blinkenlights.android.vanilla;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import java.util.List;
/**
* Common plugin utilities and constants reside here.
* Note that public `action` and `extra` strings are common over all plugins, so please keep them in sync.
*/
public class PluginUtils {
private PluginUtils() {
}
// these actions are for passing between main player and plugins
public static final String ACTION_REQUEST_PLUGIN_PARAMS = "ch.blinkenlights.android.vanilla.action.REQUEST_PLUGIN_PARAMS"; // broadcast
public static final String ACTION_HANDLE_PLUGIN_PARAMS = "ch.blinkenlights.android.vanilla.action.HANDLE_PLUGIN_PARAMS"; // answer
public static final String ACTION_WAKE_PLUGIN = "ch.blinkenlights.android.vanilla.action.WAKE_PLUGIN"; // targeted at each found
public static final String ACTION_LAUNCH_PLUGIN = "ch.blinkenlights.android.vanilla.action.LAUNCH_PLUGIN"; // targeted at selected by user
// these are used by plugins to describe themselves
public static final String EXTRA_PARAM_PLUGIN_NAME = "ch.blinkenlights.android.vanilla.extra.PLUGIN_NAME";
public static final String EXTRA_PARAM_PLUGIN_APP = "ch.blinkenlights.android.vanilla.extra.PLUGIN_APP";
public static final String EXTRA_PARAM_PLUGIN_DESC = "ch.blinkenlights.android.vanilla.extra.PLUGIN_DESC";
// this is passed to plugin when it is selected by user
public static final String EXTRA_PARAM_URI = "ch.blinkenlights.android.vanilla.extra.URI";
public static final String EXTRA_PARAM_SONG_TITLE = "ch.blinkenlights.android.vanilla.extra.SONG_TITLE";
public static final String EXTRA_PARAM_SONG_ALBUM = "ch.blinkenlights.android.vanilla.extra.SONG_ALBUM";
public static final String EXTRA_PARAM_SONG_ARTIST = "ch.blinkenlights.android.vanilla.extra.SONG_ARTIST";
static final String EXTRA_PLUGIN_MAP = "ch.blinkenlights.android.vanilla.internal.extra.PLUGIN_MAP";
public static boolean checkPlugins(Context ctx) {
List<ResolveInfo> resolved = ctx.getPackageManager().queryBroadcastReceivers(new Intent(ACTION_REQUEST_PLUGIN_PARAMS), 0);
if(!resolved.isEmpty()) {
// If plugin is just installed, Android will not deliver intents to its receiver
// until it's started at least one time
for (ResolveInfo ri : resolved) {
Intent awaker = new Intent(ACTION_WAKE_PLUGIN);
awaker.setPackage(ri.activityInfo.packageName);
ctx.startService(awaker);
}
return true;
}
return false;
}
}