diff options
author | Adam Powell <adamp@google.com> | 2015-04-14 00:01:45 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-04-14 00:01:47 +0000 |
commit | cc8b2afbaaabb3d120c1edc5ead9a67c5361762a (patch) | |
tree | dd1b23f09f3045e3fb10654c91714a182df74161 | |
parent | 7d8f2c25df8d017c5fb57cfe844500e3aeb8f321 (diff) | |
parent | 2442841819f9554f9b5c8b9c147a51b04e50de4d (diff) | |
download | frameworks_base-cc8b2afbaaabb3d120c1edc5ead9a67c5361762a.zip frameworks_base-cc8b2afbaaabb3d120c1edc5ead9a67c5361762a.tar.gz frameworks_base-cc8b2afbaaabb3d120c1edc5ead9a67c5361762a.tar.bz2 |
Merge "Implement ChooserTargetService querying for ChooserActivity"
-rw-r--r-- | api/current.txt | 5 | ||||
-rw-r--r-- | api/system-current.txt | 5 | ||||
-rw-r--r-- | core/java/android/content/Intent.java | 8 | ||||
-rw-r--r-- | core/java/android/service/chooser/ChooserTarget.java | 139 | ||||
-rw-r--r-- | core/java/android/service/chooser/ChooserTargetService.java | 25 | ||||
-rw-r--r-- | core/java/com/android/internal/app/ChooserActivity.java | 392 | ||||
-rw-r--r-- | core/java/com/android/internal/app/ResolverActivity.java | 469 | ||||
-rw-r--r-- | core/res/res/layout/chooser_grid.xml | 107 | ||||
-rw-r--r-- | core/res/res/layout/resolve_grid_item.xml | 60 | ||||
-rw-r--r-- | core/res/res/values-sw360dp/dimens.xml | 21 | ||||
-rw-r--r-- | core/res/res/values/dimens.xml | 1 | ||||
-rwxr-xr-x | core/res/res/values/symbols.xml | 3 | ||||
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java | 5 |
13 files changed, 1113 insertions, 127 deletions
diff --git a/api/current.txt b/api/current.txt index a96c3a2..87cd2d6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8158,6 +8158,7 @@ package android.content { field public static final java.lang.String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list"; field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list"; field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list"; + field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER"; field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED"; @@ -28384,8 +28385,10 @@ package android.service.chooser { public final class ChooserTarget implements android.os.Parcelable { ctor public ChooserTarget(java.lang.CharSequence, android.graphics.Bitmap, float, android.app.PendingIntent); ctor public ChooserTarget(java.lang.CharSequence, android.graphics.Bitmap, float, android.content.IntentSender); + ctor public ChooserTarget(java.lang.CharSequence, android.graphics.Bitmap, float, android.content.Intent); method public int describeContents(); method public android.graphics.Bitmap getIcon(); + method public android.content.Intent getIntent(); method public android.content.IntentSender getIntentSender(); method public float getScore(); method public java.lang.CharSequence getTitle(); @@ -28398,6 +28401,8 @@ package android.service.chooser { ctor public ChooserTargetService(); method public android.os.IBinder onBind(android.content.Intent); method public abstract java.util.List<android.service.chooser.ChooserTarget> onGetChooserTargets(android.content.ComponentName, android.content.IntentFilter); + field public static final java.lang.String BIND_PERMISSION = "android.permission.BIND_CHOOSER_TARGET_SERVICE"; + field public static final java.lang.String META_DATA_NAME = "android.service.chooser.chooser_target_service"; field public static final java.lang.String SERVICE_INTERFACE = "android.service.chooser.ChooserTargetService"; } diff --git a/api/system-current.txt b/api/system-current.txt index 9021029..9e635bf 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -8384,6 +8384,7 @@ package android.content { field public static final java.lang.String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list"; field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list"; field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list"; + field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER"; field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED"; @@ -30383,8 +30384,10 @@ package android.service.chooser { public final class ChooserTarget implements android.os.Parcelable { ctor public ChooserTarget(java.lang.CharSequence, android.graphics.Bitmap, float, android.app.PendingIntent); ctor public ChooserTarget(java.lang.CharSequence, android.graphics.Bitmap, float, android.content.IntentSender); + ctor public ChooserTarget(java.lang.CharSequence, android.graphics.Bitmap, float, android.content.Intent); method public int describeContents(); method public android.graphics.Bitmap getIcon(); + method public android.content.Intent getIntent(); method public android.content.IntentSender getIntentSender(); method public float getScore(); method public java.lang.CharSequence getTitle(); @@ -30397,6 +30400,8 @@ package android.service.chooser { ctor public ChooserTargetService(); method public android.os.IBinder onBind(android.content.Intent); method public abstract java.util.List<android.service.chooser.ChooserTarget> onGetChooserTargets(android.content.ComponentName, android.content.IntentFilter); + field public static final java.lang.String BIND_PERMISSION = "android.permission.BIND_CHOOSER_TARGET_SERVICE"; + field public static final java.lang.String META_DATA_NAME = "android.service.chooser.chooser_target_service"; field public static final java.lang.String SERVICE_INTERFACE = "android.service.chooser.ChooserTargetService"; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index eea47b7..54fe786 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3311,6 +3311,14 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS"; /** + * A Parcelable[] of {@link android.service.chooser.ChooserTarget ChooserTarget} objects + * as set with {@link #putExtra(String, Parcelable[])} representing additional app-specific + * targets to place at the front of the list of choices. Shown to the user with + * {@link #ACTION_CHOOSER}. + */ + public static final String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; + + /** * A Bundle forming a mapping of potential target package names to different extras Bundles * to add to the default intent extras in {@link #EXTRA_INTENT} when used with * {@link #ACTION_CHOOSER}. Each key should be a package name. The package need not diff --git a/core/java/android/service/chooser/ChooserTarget.java b/core/java/android/service/chooser/ChooserTarget.java index 7fd1d10..d21cc3c 100644 --- a/core/java/android/service/chooser/ChooserTarget.java +++ b/core/java/android/service/chooser/ChooserTarget.java @@ -17,6 +17,7 @@ package android.service.chooser; +import android.app.Activity; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -24,8 +25,10 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.graphics.Bitmap; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.util.Log; /** @@ -55,6 +58,12 @@ public final class ChooserTarget implements Parcelable { private IntentSender mIntentSender; /** + * A raw intent provided in lieu of an IntentSender. Will be filled in and sent + * by {@link #sendIntent(Context, Intent)}. + */ + private Intent mIntent; + + /** * The score given to this item. It can be normalized. */ private float mScore; @@ -135,6 +144,17 @@ public final class ChooserTarget implements Parcelable { mIntentSender = intentSender; } + public ChooserTarget(CharSequence title, Bitmap icon, float score, Intent intent) { + mTitle = title; + mIcon = icon; + if (score > 1.f || score < 0.f) { + throw new IllegalArgumentException("Score " + score + " out of range; " + + "must be between 0.0f and 1.0f"); + } + mScore = score; + mIntent = intent; + } + ChooserTarget(Parcel in) { mTitle = in.readCharSequence(); if (in.readInt() != 0) { @@ -144,6 +164,9 @@ public final class ChooserTarget implements Parcelable { } mScore = in.readFloat(); mIntentSender = IntentSender.readIntentSenderOrNullFromParcel(in); + if (in.readInt() != 0) { + mIntent = Intent.CREATOR.createFromParcel(in); + } } /** @@ -179,6 +202,7 @@ public final class ChooserTarget implements Parcelable { /** * Returns the raw IntentSender supplied by the ChooserTarget's creator. + * This may be null if the creator specified a regular Intent instead. * * <p>To fill in and send the intent, see {@link #sendIntent(Context, Intent)}.</p> * @@ -189,6 +213,18 @@ public final class ChooserTarget implements Parcelable { } /** + * Returns the Intent supplied by the ChooserTarget's creator. + * This may be null if the creator specified an IntentSender or PendingIntent instead. + * + * <p>To fill in and send the intent, see {@link #sendIntent(Context, Intent)}.</p> + * + * @return the Intent supplied by the ChooserTarget's creator + */ + public Intent getIntent() { + return mIntent; + } + + /** * Fill in the IntentSender supplied by the ChooserTarget's creator and send it. * * @param context the sending Context; generally the Activity presenting the chooser UI @@ -200,18 +236,109 @@ public final class ChooserTarget implements Parcelable { fillInIntent.migrateExtraStreamToClipData(); fillInIntent.prepareToLeaveProcess(); } - try { - mIntentSender.sendIntent(context, 0, fillInIntent, null, null); - return true; - } catch (IntentSender.SendIntentException e) { - Log.e(TAG, "sendIntent " + this + " failed", e); + if (mIntentSender != null) { + try { + mIntentSender.sendIntent(context, 0, fillInIntent, null, null); + return true; + } catch (IntentSender.SendIntentException e) { + Log.e(TAG, "sendIntent " + this + " failed", e); + return false; + } + } else if (mIntent != null) { + try { + final Intent toSend = new Intent(mIntent); + toSend.fillIn(fillInIntent, 0); + context.startActivity(toSend); + return true; + } catch (Exception e) { + Log.e(TAG, "sendIntent " + this + " failed", e); + return false; + } + } else { + Log.e(TAG, "sendIntent " + this + " failed - no IntentSender or Intent to send"); + return false; + } + } + + /** + * Same as {@link #sendIntent(Context, Intent)}, but offers a userId field to use + * for launching the {@link #getIntent() intent} using + * {@link Activity#startActivityAsCaller(Intent, Bundle, int)} if the + * {@link #getIntentSender() IntentSender} is not present. If the IntentSender is present, + * it will be invoked as usual with its own calling identity. + * + * @hide internal use only. + */ + public boolean sendIntentAsCaller(Activity context, Intent fillInIntent, int userId) { + if (fillInIntent != null) { + fillInIntent.migrateExtraStreamToClipData(); + fillInIntent.prepareToLeaveProcess(); + } + if (mIntentSender != null) { + try { + mIntentSender.sendIntent(context, 0, fillInIntent, null, null); + return true; + } catch (IntentSender.SendIntentException e) { + Log.e(TAG, "sendIntent " + this + " failed", e); + return false; + } + } else if (mIntent != null) { + try { + final Intent toSend = new Intent(mIntent); + toSend.fillIn(fillInIntent, 0); + context.startActivityAsCaller(toSend, null, userId); + return true; + } catch (Exception e) { + Log.e(TAG, "sendIntent " + this + " failed", e); + return false; + } + } else { + Log.e(TAG, "sendIntent " + this + " failed - no IntentSender or Intent to send"); + return false; + } + } + + /** + * The UserHandle is only used if we're launching a raw intent. The IntentSender will be + * launched with its associated identity. + * + * @hide Internal use only + */ + public boolean sendIntentAsUser(Activity context, Intent fillInIntent, UserHandle user) { + if (fillInIntent != null) { + fillInIntent.migrateExtraStreamToClipData(); + fillInIntent.prepareToLeaveProcess(); + } + if (mIntentSender != null) { + try { + mIntentSender.sendIntent(context, 0, fillInIntent, null, null); + return true; + } catch (IntentSender.SendIntentException e) { + Log.e(TAG, "sendIntent " + this + " failed", e); + return false; + } + } else if (mIntent != null) { + try { + final Intent toSend = new Intent(mIntent); + toSend.fillIn(fillInIntent, 0); + context.startActivityAsUser(toSend, user); + return true; + } catch (Exception e) { + Log.e(TAG, "sendIntent " + this + " failed", e); + return false; + } + } else { + Log.e(TAG, "sendIntent " + this + " failed - no IntentSender or Intent to send"); return false; } } @Override public String toString() { - return "ChooserTarget{" + mIntentSender.getCreatorPackage() + "'" + mTitle + return "ChooserTarget{" + + (mIntentSender != null ? mIntentSender.getCreatorPackage() : mIntent) + + ", " + + "'" + mTitle + "', " + mScore + "}"; } diff --git a/core/java/android/service/chooser/ChooserTargetService.java b/core/java/android/service/chooser/ChooserTargetService.java index 9188806..699bd0a 100644 --- a/core/java/android/service/chooser/ChooserTargetService.java +++ b/core/java/android/service/chooser/ChooserTargetService.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.IBinder; import android.os.RemoteException; +import android.util.Log; import java.util.List; @@ -77,9 +78,26 @@ public abstract class ChooserTargetService extends Service { private final String TAG = ChooserTargetService.class.getSimpleName() + '[' + getClass().getSimpleName() + ']'; + private static final boolean DEBUG = false; + + /** + * The Intent action that a ChooserTargetService must respond to + */ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.service.chooser.ChooserTargetService"; + /** + * The name of the <code>meta-data</code> element that must be present on an + * <code>activity</code> element in a manifest to link it to a ChooserTargetService + */ + public static final String META_DATA_NAME = "android.service.chooser.chooser_target_service"; + + /** + * The permission that a ChooserTargetService must require in order to bind to it. + * If this permission is not enforced the system will skip that ChooserTargetService. + */ + public static final String BIND_PERMISSION = "android.permission.BIND_CHOOSER_TARGET_SERVICE"; + private IChooserTargetServiceWrapper mWrapper = null; /** @@ -105,7 +123,9 @@ public abstract class ChooserTargetService extends Service { @Override public IBinder onBind(Intent intent) { + if (DEBUG) Log.d(TAG, "onBind " + intent); if (!SERVICE_INTERFACE.equals(intent.getAction())) { + if (DEBUG) Log.d(TAG, "bad intent action " + intent.getAction() + "; returning null"); return null; } @@ -121,9 +141,14 @@ public abstract class ChooserTargetService extends Service { IntentFilter matchedFilter, IChooserTargetResult result) throws RemoteException { List<ChooserTarget> targets = null; try { + if (DEBUG) { + Log.d(TAG, "getChooserTargets calling onGetChooserTargets; " + + targetComponentName + " filter: " + matchedFilter); + } targets = onGetChooserTargets(targetComponentName, matchedFilter); } finally { result.sendResult(targets); + if (DEBUG) Log.d(TAG, "Sent results"); } } } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 64bd6b6..8403e77 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -18,20 +18,87 @@ package com.android.internal.app; import android.app.Activity; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.ServiceConnection; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; import android.os.Parcelable; +import android.os.RemoteException; +import android.os.UserHandle; +import android.service.chooser.ChooserTarget; +import android.service.chooser.ChooserTargetService; +import android.service.chooser.IChooserTargetResult; +import android.service.chooser.IChooserTargetService; +import android.text.TextUtils; import android.util.Log; import android.util.Slog; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.List; public class ChooserActivity extends ResolverActivity { private static final String TAG = "ChooserActivity"; + private static final boolean DEBUG = false; + + private static final int QUERY_TARGET_LIMIT = 5; + private static final int WATCHDOG_TIMEOUT_MILLIS = 5000; + private Bundle mReplacementExtras; private IntentSender mChosenComponentSender; + private ChooserTarget[] mCallerChooserTargets; + + private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>(); + + private static final int CHOOSER_TARGET_SERVICE_RESULT = 1; + private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2; + + private Handler mTargetResultHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case CHOOSER_TARGET_SERVICE_RESULT: + if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT"); + if (isDestroyed()) break; + final ServiceResultInfo sri = (ServiceResultInfo) msg.obj; + if (!mServiceConnections.contains(sri.connection)) { + Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection + + " returned after being removed from active connections." + + " Have you considered returning results faster?"); + break; + } + final ChooserListAdapter cla = (ChooserListAdapter) getAdapter(); + cla.addServiceResults(sri.originalTarget, sri.resultTargets); + unbindService(sri.connection); + mServiceConnections.remove(sri.connection); + break; + + case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT: + if (DEBUG) { + Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services"); + } + unbindRemainingServices(); + break; + + default: + super.handleMessage(msg); + } + } + }; + @Override protected void onCreate(Bundle savedInstanceState) { Intent intent = getIntent(); @@ -42,7 +109,7 @@ public class ChooserActivity extends ResolverActivity { super.onCreate(null); return; } - Intent target = (Intent)targetParcelable; + Intent target = (Intent) targetParcelable; if (target != null) { modifyTargetIntent(target); } @@ -68,6 +135,22 @@ public class ChooserActivity extends ResolverActivity { initialIntents[i] = in; } } + + pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS); + if (pa != null) { + final ChooserTarget[] targets = new ChooserTarget[pa.length]; + for (int i = 0; i < pa.length; i++) { + if (!(pa[i] instanceof ChooserTarget)) { + Log.w("ChooserActivity", "Chooser target #" + i + " is not a ChooserTarget: " + + pa[i]); + finish(); + super.onCreate(null); + return; + } + targets[i] = (ChooserTarget) pa[i]; + } + mCallerChooserTargets = targets; + } mChosenComponentSender = intent.getParcelableExtra( Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER); setSafeForwardingMode(true); @@ -94,9 +177,9 @@ public class ChooserActivity extends ResolverActivity { } @Override - public void onActivityStarted(Intent intent) { + void onActivityStarted(TargetInfo cti) { if (mChosenComponentSender != null) { - final ComponentName target = intent.getComponent(); + final ComponentName target = cti.getResolvedComponentName(); if (target != null) { final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target); try { @@ -109,6 +192,16 @@ public class ChooserActivity extends ResolverActivity { } } + @Override + int getLayoutResource() { + return com.android.internal.R.layout.chooser_grid; + } + + @Override + boolean shouldGetActivityMetadata() { + return true; + } + private void modifyTargetIntent(Intent in) { final String action = in.getAction(); if (Intent.ACTION_SEND.equals(action) || @@ -117,4 +210,297 @@ public class ChooserActivity extends ResolverActivity { Intent.FLAG_ACTIVITY_MULTIPLE_TASK); } } + + void queryTargetServices(ChooserListAdapter adapter) { + final PackageManager pm = getPackageManager(); + int targetsToQuery = 0; + for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) { + final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i); + final ActivityInfo ai = dri.getResolveInfo().activityInfo; + final Bundle md = ai.metaData; + final String serviceName = md != null ? convertServiceName(ai.packageName, + md.getString(ChooserTargetService.META_DATA_NAME)) : null; + if (serviceName != null) { + final ComponentName serviceComponent = new ComponentName( + ai.packageName, serviceName); + final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE) + .setComponent(serviceComponent); + + if (DEBUG) { + Log.d(TAG, "queryTargets found target with service " + serviceComponent); + } + + try { + final String perm = pm.getServiceInfo(serviceComponent, 0).permission; + if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) { + Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require" + + " permission " + ChooserTargetService.BIND_PERMISSION + + " - this service will not be queried for ChooserTargets." + + " add android:permission=\"" + + ChooserTargetService.BIND_PERMISSION + "\"" + + " to the <service> tag for " + serviceComponent + + " in the manifest."); + continue; + } + } catch (NameNotFoundException e) { + Log.e(TAG, "Could not look up service " + serviceComponent, e); + continue; + } + + final ChooserTargetServiceConnection conn = new ChooserTargetServiceConnection(dri); + if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND, + UserHandle.CURRENT)) { + if (DEBUG) { + Log.d(TAG, "Binding service connection for target " + dri + + " intent " + serviceIntent); + } + mServiceConnections.add(conn); + targetsToQuery++; + } + } + if (targetsToQuery >= QUERY_TARGET_LIMIT) { + if (DEBUG) Log.d(TAG, "queryTargets hit query target limit " + QUERY_TARGET_LIMIT); + break; + } + } + + if (!mServiceConnections.isEmpty()) { + if (DEBUG) Log.d(TAG, "queryTargets setting watchdog timer for " + + WATCHDOG_TIMEOUT_MILLIS + "ms"); + mTargetResultHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT, + WATCHDOG_TIMEOUT_MILLIS); + } + } + + private String convertServiceName(String packageName, String serviceName) { + if (TextUtils.isEmpty(serviceName)) { + return null; + } + + final String fullName; + if (serviceName.startsWith(".")) { + // Relative to the app package. Prepend the app package name. + fullName = packageName + serviceName; + } else if (serviceName.indexOf('.') >= 0) { + // Fully qualified package name. + fullName = serviceName; + } else { + fullName = null; + } + return fullName; + } + + void unbindRemainingServices() { + if (DEBUG) { + Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left"); + } + for (int i = 0, N = mServiceConnections.size(); i < N; i++) { + final ChooserTargetServiceConnection conn = mServiceConnections.get(i); + if (DEBUG) Log.d(TAG, "unbinding " + conn); + unbindService(conn); + } + mServiceConnections.clear(); + mTargetResultHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); + } + + @Override + ResolveListAdapter createAdapter(Context context, Intent[] initialIntents, + List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { + final ChooserListAdapter adapter = new ChooserListAdapter(context, initialIntents, rList, + launchedFromUid, filterLastUsed); + if (DEBUG) Log.d(TAG, "Adapter created; querying services"); + queryTargetServices(adapter); + return adapter; + } + + class ChooserTargetInfo implements TargetInfo { + private final TargetInfo mSourceInfo; + private final ChooserTarget mChooserTarget; + private final Drawable mDisplayIcon; + + public ChooserTargetInfo(TargetInfo sourceInfo, ChooserTarget chooserTarget) { + mSourceInfo = sourceInfo; + mChooserTarget = chooserTarget; + mDisplayIcon = new BitmapDrawable(getResources(), chooserTarget.getIcon()); + } + + @Override + public Intent getResolvedIntent() { + final Intent targetIntent = mChooserTarget.getIntent(); + return targetIntent != null ? targetIntent : mSourceInfo.getResolvedIntent(); + } + + @Override + public ComponentName getResolvedComponentName() { + return mSourceInfo.getResolvedComponentName(); + } + + @Override + public boolean start(Activity activity, Bundle options) { + return mChooserTarget.sendIntent(activity, mSourceInfo.getResolvedIntent()); + } + + @Override + public boolean startAsCaller(Activity activity, Bundle options, int userId) { + return mChooserTarget.sendIntentAsCaller(activity, mSourceInfo.getResolvedIntent(), + userId); + } + + @Override + public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { + return mChooserTarget.sendIntentAsUser(activity, mSourceInfo.getResolvedIntent(), user); + } + + @Override + public ResolveInfo getResolveInfo() { + return mSourceInfo.getResolveInfo(); + } + + @Override + public CharSequence getDisplayLabel() { + return mChooserTarget.getTitle(); + } + + @Override + public CharSequence getExtendedInfo() { + return mSourceInfo.getExtendedInfo(); + } + + @Override + public Drawable getDisplayIcon() { + return mDisplayIcon; + } + } + + public class ChooserListAdapter extends ResolveListAdapter { + private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>(); + + public ChooserListAdapter(Context context, Intent[] initialIntents, List<ResolveInfo> rList, + int launchedFromUid, boolean filterLastUsed) { + super(context, initialIntents, rList, launchedFromUid, filterLastUsed); + } + + @Override + public boolean showsExtendedInfo(TargetInfo info) { + // Reserve space to show extended info if any one of the items in the adapter has + // extended info. This keeps grid item sizes uniform. + return hasExtendedInfo(); + } + + @Override + public View createView(ViewGroup parent) { + return mInflater.inflate( + com.android.internal.R.layout.resolve_grid_item, parent, false); + } + + @Override + public void onListRebuilt() { + if (mServiceTargets != null) { + pruneServiceTargets(); + } + } + + @Override + public int getCount() { + int count = super.getCount(); + if (mServiceTargets != null) { + count += mServiceTargets.size(); + } + return count; + } + + @Override + public TargetInfo getItem(int position) { + int offset = 0; + if (mServiceTargets != null) { + final int serviceTargetCount = mServiceTargets.size(); + if (position < serviceTargetCount) { + return mServiceTargets.get(position); + } + offset += serviceTargetCount; + } + return super.getItem(position - offset); + } + + public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) { + if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size() + + " targets"); + for (int i = 0, N = targets.size(); i < N; i++) { + mServiceTargets.add(new ChooserTargetInfo(origTarget, targets.get(i))); + } + + // TODO: Maintain sort by ranking scores. + + notifyDataSetChanged(); + } + + private void pruneServiceTargets() { + if (DEBUG) Log.d(TAG, "pruneServiceTargets"); + for (int i = mServiceTargets.size() - 1; i >= 0; i--) { + final ChooserTargetInfo cti = mServiceTargets.get(i); + if (!hasResolvedTarget(cti.getResolveInfo())) { + if (DEBUG) Log.d(TAG, " => " + i + " " + cti); + mServiceTargets.remove(i); + } + } + } + } + + class ChooserTargetServiceConnection implements ServiceConnection { + private final DisplayResolveInfo mOriginalTarget; + + private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() { + @Override + public void sendResult(List<ChooserTarget> targets) throws RemoteException { + final Message msg = Message.obtain(); + msg.what = CHOOSER_TARGET_SERVICE_RESULT; + msg.obj = new ServiceResultInfo(mOriginalTarget, targets, + ChooserTargetServiceConnection.this); + mTargetResultHandler.sendMessage(msg); + } + }; + + public ChooserTargetServiceConnection(DisplayResolveInfo dri) { + mOriginalTarget = dri; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (DEBUG) Log.d(TAG, "onServiceConnected: " + name); + final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service); + try { + icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(), + mOriginalTarget.getResolveInfo().filter, mChooserTargetResult); + } catch (RemoteException e) { + Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e); + unbindService(this); + mServiceConnections.remove(this); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name); + unbindService(this); + mServiceConnections.remove(this); + } + + @Override + public String toString() { + return mOriginalTarget.getResolveInfo().activityInfo.toString(); + } + } + + static class ServiceResultInfo { + public final DisplayResolveInfo originalTarget; + public final List<ChooserTarget> resultTargets; + public final ChooserTargetServiceConnection connection; + + public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt, + ChooserTargetServiceConnection c) { + originalTarget = ot; + resultTargets = rt; + connection = c; + } + } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 6b35f3f..3cd69a1 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -25,6 +25,7 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; import android.widget.AbsListView; +import android.widget.GridView; import com.android.internal.R; import com.android.internal.content.PackageMonitor; @@ -91,15 +92,14 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic private PackageManager mPm; private boolean mSafeForwardingMode; private boolean mAlwaysUseOption; - private boolean mShowExtended; + private AbsListView mAdapterView; private ListView mListView; + private GridView mGridView; private Button mAlwaysButton; private Button mOnceButton; private View mProfileView; private int mIconDpi; - private int mIconSize; - private int mMaxColumns; - private int mLastSelected = ListView.INVALID_POSITION; + private int mLastSelected = AbsListView.INVALID_POSITION; private boolean mResolvingHome = false; private int mProfileSwitchMessageId = -1; private Intent mIntent; @@ -192,7 +192,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } /** - * Compatibility version for other bundled services that use this ocerload without + * Compatibility version for other bundled services that use this overload without * a default title resource */ protected void onCreate(Bundle savedInstanceState, Intent intent, @@ -223,18 +223,14 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic final long sinceTime = System.currentTimeMillis() - USAGE_STATS_PERIOD; mStats = mUsm.queryAndAggregateUsageStats(sinceTime, System.currentTimeMillis()); - mMaxColumns = getResources().getInteger(R.integer.config_maxResolverActivityColumns); - mPackageMonitor.register(this, getMainLooper(), false); mRegistered = true; final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); mIconDpi = am.getLauncherLargeIconDensity(); - mIconSize = am.getLauncherLargeIconSize(); mIntent = new Intent(intent); - mAdapter = new ResolveListAdapter(this, initialIntents, rList, - mLaunchedFromUid, alwaysUseOption); + mAdapter = createAdapter(this, initialIntents, rList, mLaunchedFromUid, alwaysUseOption); final int layoutId; final boolean useHeader; @@ -244,7 +240,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic useHeader = true; } else { useHeader = false; - layoutId = R.layout.resolver_list; + layoutId = getLayoutResource(); } mAlwaysUseOption = alwaysUseOption; @@ -257,21 +253,30 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic int count = mAdapter.mList.size(); if (count > 1 || (count == 1 && mAdapter.getOtherProfile() != null)) { setContentView(layoutId); - mListView = (ListView) findViewById(R.id.resolver_list); - mListView.setAdapter(mAdapter); - mListView.setOnItemClickListener(this); - mListView.setOnItemLongClickListener(new ItemLongClickListener()); + mAdapterView = (AbsListView) findViewById(R.id.resolver_list); + mAdapterView.setAdapter(mAdapter); + mAdapterView.setOnItemClickListener(this); + mAdapterView.setOnItemLongClickListener(new ItemLongClickListener()); + + // Initialize the different types of collection views we may have. Depending + // on which ones are initialized later we'll configure different properties. + if (mAdapterView instanceof ListView) { + mListView = (ListView) mAdapterView; + } + if (mAdapterView instanceof GridView) { + mGridView = (GridView) mAdapterView; + } if (alwaysUseOption) { - mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + mAdapterView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); } - if (useHeader) { + if (useHeader && mListView != null) { mListView.addHeaderView(LayoutInflater.from(this).inflate( R.layout.resolver_different_item_header, mListView, false)); } } else if (count == 1) { - safelyStartActivity(mAdapter.intentForPosition(0, false)); + safelyStartActivity(mAdapter.targetInfoForPosition(0, false)); mPackageMonitor.unregister(); mRegistered = false; finish(); @@ -282,8 +287,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic final TextView empty = (TextView) findViewById(R.id.empty); empty.setVisibility(View.VISIBLE); - mListView = (ListView) findViewById(R.id.resolver_list); - mListView.setVisibility(View.GONE); + mAdapterView = (AbsListView) findViewById(R.id.resolver_list); + mAdapterView.setVisibility(View.GONE); } // Prevent the Resolver window from becoming the top fullscreen window and thus from taking // control of the system bars. @@ -308,12 +313,30 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic titleView.setText(title); } setTitle(title); + + // Try to initialize the title icon if we have a view for it and a title to match + final ImageView titleIcon = (ImageView) findViewById(R.id.title_icon); + if (titleIcon != null) { + final String referrerPackage = getReferrerPackageName(); + ApplicationInfo ai = null; + try { + if (!TextUtils.isEmpty(referrerPackage)) { + ai = mPm.getApplicationInfo(referrerPackage, 0); + } + } catch (NameNotFoundException e) { + Log.e(TAG, "Could not find referrer package " + referrerPackage); + } + + if (ai != null) { + titleIcon.setImageDrawable(ai.loadIcon(mPm)); + } + } } final ImageView iconView = (ImageView) findViewById(R.id.icon); final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem(); if (iconView != null && iconInfo != null) { - new LoadIconIntoViewTask(iconView).execute(iconInfo); + new LoadIconIntoViewTask(iconInfo, iconView).execute(); } if (alwaysUseOption || mAdapter.hasFilteredItem()) { @@ -345,8 +368,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic // Do not show the profile switch message anymore. mProfileSwitchMessageId = -1; - final Intent intent = intentForDisplayResolveInfo(dri); - onIntentSelected(dri.ri, intent, false); + onTargetSelected(dri, false); finish(); } }); @@ -354,17 +376,29 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } + private String getReferrerPackageName() { + final Uri referrer = getReferrer(); + if (referrer != null && "android-app".equals(referrer.getScheme())) { + return referrer.getHost(); + } + return null; + } + + int getLayoutResource() { + return R.layout.resolver_list; + } + void bindProfileView() { final DisplayResolveInfo dri = mAdapter.getOtherProfile(); if (dri != null) { mProfileView.setVisibility(View.VISIBLE); final ImageView icon = (ImageView) mProfileView.findViewById(R.id.icon); final TextView text = (TextView) mProfileView.findViewById(R.id.text1); - if (dri.displayIcon == null) { - new LoadIconTask().execute(dri); + if (!dri.hasDisplayIcon()) { + new LoadIconIntoViewTask(dri, icon).execute(); } - icon.setImageDrawable(dri.displayIcon); - text.setText(dri.displayLabel); + icon.setImageDrawable(dri.getDisplayIcon()); + text.setText(dri.getDisplayLabel()); } else { mProfileView.setVisibility(View.GONE); } @@ -408,8 +442,9 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) { return getString(defaultTitleRes); } else { - return named ? getString(title.namedTitleRes, mAdapter.getFilteredItem().displayLabel) : - getString(title.titleRes); + return named + ? getString(title.namedTitleRes, mAdapter.getFilteredItem().getDisplayLabel()) + : getString(title.titleRes); } } @@ -490,31 +525,33 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (mAlwaysUseOption) { - final int checkedPos = mListView.getCheckedItemPosition(); + final int checkedPos = mAdapterView.getCheckedItemPosition(); final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; mLastSelected = checkedPos; setAlwaysButtonEnabled(hasValidSelection, checkedPos, true); mOnceButton.setEnabled(hasValidSelection); if (hasValidSelection) { - mListView.setSelection(checkedPos); + mAdapterView.setSelection(checkedPos); } } } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - position -= mListView.getHeaderViewsCount(); + if (mListView != null) { + position -= mListView.getHeaderViewsCount(); + } if (position < 0) { // Header views don't count. return; } - final int checkedPos = mListView.getCheckedItemPosition(); + final int checkedPos = mAdapterView.getCheckedItemPosition(); final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) { setAlwaysButtonEnabled(hasValidSelection, checkedPos, true); mOnceButton.setEnabled(hasValidSelection); if (hasValidSelection) { - mListView.smoothScrollToPosition(checkedPos); + mAdapterView.smoothScrollToPosition(checkedPos); } mLastSelected = checkedPos; } else { @@ -570,7 +607,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic public void onButtonClick(View v) { final int id = v.getId(); startSelected(mAlwaysUseOption ? - mListView.getCheckedItemPosition() : mAdapter.getFilteredPosition(), + mAdapterView.getCheckedItemPosition() : mAdapter.getFilteredPosition(), id == R.id.button_always, mAlwaysUseOption); } @@ -588,8 +625,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return; } - Intent intent = mAdapter.intentForPosition(which, filtered); - onIntentSelected(ri, intent, always); + TargetInfo target = mAdapter.targetInfoForPosition(which, filtered); + onTargetSelected(target, always); finish(); } @@ -600,8 +637,12 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return defIntent; } - protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) { - if ((mAlwaysUseOption || mAdapter.hasFilteredItem()) && mAdapter.mOrigResolveList != null) { + protected void onTargetSelected(TargetInfo target, boolean alwaysCheck) { + final ResolveInfo ri = target.getResolveInfo(); + final Intent intent = target != null ? target.getResolvedIntent() : null; + + if (intent != null && (mAlwaysUseOption || mAdapter.hasFilteredItem()) + && mAdapter.mOrigResolveList != null) { // Build a reasonable intent filter, based on what matched. IntentFilter filter = new IntentFilter(); String action = intent.getAction(); @@ -617,7 +658,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } filter.addCategory(Intent.CATEGORY_DEFAULT); - int cat = ri.match&IntentFilter.MATCH_CATEGORY_MASK; + int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK; Uri data = intent.getData(); if (cat == IntentFilter.MATCH_CATEGORY_TYPE) { String mimeType = intent.resolveType(this); @@ -726,25 +767,27 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } - if (intent != null) { - safelyStartActivity(intent); + if (target != null) { + safelyStartActivity(target); } } - public void safelyStartActivity(Intent intent) { + void safelyStartActivity(TargetInfo cti) { // If needed, show that intent is forwarded // from managed profile to owner or other way around. if (mProfileSwitchMessageId != -1) { Toast.makeText(this, getString(mProfileSwitchMessageId), Toast.LENGTH_LONG).show(); } if (!mSafeForwardingMode) { - startActivity(intent); - onActivityStarted(intent); + if (cti.start(this, null)) { + onActivityStarted(cti); + } return; } try { - startActivityAsCaller(intent, null, UserHandle.USER_NULL); - onActivityStarted(intent); + if (cti.startAsCaller(this, null, UserHandle.USER_NULL)) { + onActivityStarted(cti); + } } catch (RuntimeException e) { String launchedFromPackage; try { @@ -759,51 +802,197 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } - public void onActivityStarted(Intent intent) { + void onActivityStarted(TargetInfo cti) { // Do nothing } + boolean shouldGetActivityMetadata() { + return false; + } + void showAppDetails(ResolveInfo ri) { Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) .setData(Uri.fromParts("package", ri.activityInfo.packageName, null)) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); startActivity(in); } - Intent intentForDisplayResolveInfo(DisplayResolveInfo dri) { - Intent intent = new Intent(dri.origIntent != null ? dri.origIntent : - getReplacementIntent(dri.ri.activityInfo, mIntent)); - intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT - |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); - ActivityInfo ai = dri.ri.activityInfo; - intent.setComponent(new ComponentName( - ai.applicationInfo.packageName, ai.name)); - return intent; + ResolveListAdapter createAdapter(Context context, Intent[] initialIntents, + List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { + return new ResolveListAdapter(context, initialIntents, rList, launchedFromUid, + filterLastUsed); + } + + ResolveListAdapter getAdapter() { + return mAdapter; } - private final class DisplayResolveInfo { - ResolveInfo ri; - CharSequence displayLabel; - Drawable displayIcon; - CharSequence extendedInfo; - Intent origIntent; + final class DisplayResolveInfo implements TargetInfo { + private final ResolveInfo mResolveInfo; + private final CharSequence mDisplayLabel; + private Drawable mDisplayIcon; + private final CharSequence mExtendedInfo; + private final Intent mResolvedIntent; DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel, CharSequence pInfo, Intent pOrigIntent) { - ri = pri; - displayLabel = pLabel; - extendedInfo = pInfo; - origIntent = pOrigIntent; + mResolveInfo = pri; + mDisplayLabel = pLabel; + mExtendedInfo = pInfo; + + final Intent intent = new Intent(pOrigIntent != null ? pOrigIntent : + getReplacementIntent(pri.activityInfo, mIntent)); + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT + | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); + final ActivityInfo ai = mResolveInfo.activityInfo; + intent.setComponent(new ComponentName(ai.applicationInfo.packageName, ai.name)); + + mResolvedIntent = intent; + } + + public ResolveInfo getResolveInfo() { + return mResolveInfo; + } + + public CharSequence getDisplayLabel() { + return mDisplayLabel; + } + + public Drawable getDisplayIcon() { + return mDisplayIcon; + } + + public void setDisplayIcon(Drawable icon) { + mDisplayIcon = icon; + } + + public boolean hasDisplayIcon() { + return mDisplayIcon != null; + } + + public CharSequence getExtendedInfo() { + return mExtendedInfo; + } + + public Intent getResolvedIntent() { + return mResolvedIntent; + } + + @Override + public ComponentName getResolvedComponentName() { + return new ComponentName(mResolveInfo.activityInfo.packageName, + mResolveInfo.activityInfo.name); + } + + @Override + public boolean start(Activity activity, Bundle options) { + activity.startActivity(mResolvedIntent, options); + return true; } + + @Override + public boolean startAsCaller(Activity activity, Bundle options, int userId) { + activity.startActivityAsCaller(mResolvedIntent, options, userId); + return true; + } + + @Override + public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { + activity.startActivityAsUser(mResolvedIntent, options, user); + return false; + } + } + + /** + * A single target as represented in the chooser. + */ + public interface TargetInfo { + /** + * Get the resolved intent that represents this target. Note that this may not be the + * intent that will be launched by calling one of the <code>start</code> methods provided; + * this is the intent that will be credited with the launch. + * + * @return the resolved intent for this target + */ + public Intent getResolvedIntent(); + + /** + * Get the resolved component name that represents this target. Note that this may not + * be the component that will be directly launched by calling one of the <code>start</code> + * methods provided; this is the component that will be credited with the launch. + * + * @return the resolved ComponentName for this target + */ + public ComponentName getResolvedComponentName(); + + /** + * Start the activity referenced by this target. + * + * @param activity calling Activity performing the launch + * @param options ActivityOptions bundle + * @return true if the start completed successfully + */ + public boolean start(Activity activity, Bundle options); + + /** + * Start the activity referenced by this target as if the ResolverActivity's caller + * was performing the start operation. + * + * @param activity calling Activity (actually) performing the launch + * @param options ActivityOptions bundle + * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller + * @return true if the start completed successfully + */ + public boolean startAsCaller(Activity activity, Bundle options, int userId); + + /** + * Start the activity referenced by this target as a given user. + * + * @param activity calling activity performing the launch + * @param options ActivityOptions bundle + * @param user handle for the user to start the activity as + * @return true if the start completed successfully + */ + public boolean startAsUser(Activity activity, Bundle options, UserHandle user); + + /** + * Return the ResolveInfo about how and why this target matched the original query + * for available targets. + * + * @return ResolveInfo representing this target's match + */ + public ResolveInfo getResolveInfo(); + + /** + * Return the human-readable text label for this target. + * + * @return user-visible target label + */ + public CharSequence getDisplayLabel(); + + /** + * Return any extended info for this target. This may be used to disambiguate + * otherwise identical targets. + * + * @return human-readable disambig string or null if none present + */ + public CharSequence getExtendedInfo(); + + /** + * @return The drawable that should be used to represent this target + */ + public Drawable getDisplayIcon(); } - private final class ResolveListAdapter extends BaseAdapter { + class ResolveListAdapter extends BaseAdapter { private final Intent[] mInitialIntents; private final List<ResolveInfo> mBaseResolveList; private ResolveInfo mLastChosen; private DisplayResolveInfo mOtherProfile; private final int mLaunchedFromUid; - private final LayoutInflater mInflater; + private boolean mHasExtendedInfo; + + protected final LayoutInflater mInflater; List<DisplayResolveInfo> mList; List<ResolveInfo> mOrigResolveList; @@ -817,7 +1006,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic mBaseResolveList = rList; mLaunchedFromUid = launchedFromUid; mInflater = LayoutInflater.from(context); - mList = new ArrayList<DisplayResolveInfo>(); + mList = new ArrayList<>(); mFilterLastUsed = filterLastUsed; rebuildList(); } @@ -871,9 +1060,11 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (mBaseResolveList != null) { currentResolveList = mOrigResolveList = mBaseResolveList; } else { - currentResolveList = mOrigResolveList = mPm.queryIntentActivities( - mIntent, PackageManager.MATCH_DEFAULT_ONLY - | (mFilterLastUsed ? PackageManager.GET_RESOLVED_FILTER : 0)); + currentResolveList = mOrigResolveList = mPm.queryIntentActivities(mIntent, + PackageManager.MATCH_DEFAULT_ONLY + | (mFilterLastUsed ? PackageManager.GET_RESOLVED_FILTER : 0) + | (shouldGetActivityMetadata() ? PackageManager.GET_META_DATA : 0) + ); // Filter out any activities that the launched uid does not // have permission for. We don't do this when we have an explicit // list of resolved activities, because that only happens when @@ -961,7 +1152,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic r0 = currentResolveList.get(0); int start = 0; CharSequence r0Label = r0.loadLabel(mPm); - mShowExtended = false; + mHasExtendedInfo = false; for (int i = 1; i < N; i++) { if (r0Label == null) { r0Label = r0.activityInfo.packageName; @@ -989,6 +1180,12 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic mLastChosenPosition = -1; mFilterLastUsed = false; } + + onListRebuilt(); + } + + public void onListRebuilt() { + // This space for rent } private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro, @@ -1000,7 +1197,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic addResolveInfo(new DisplayResolveInfo(ro, roLabel, null, null)); updateLastChosenPosition(ro); } else { - mShowExtended = true; + mHasExtendedInfo = true; boolean usePkg = false; CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm); if (startApp == null) { @@ -1049,7 +1246,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } private void addResolveInfo(DisplayResolveInfo dri) { - if (dri.ri.targetUserId != UserHandle.USER_CURRENT && mOtherProfile == null) { + if (dri.mResolveInfo.targetUserId != UserHandle.USER_CURRENT && mOtherProfile == null) { // So far we only support a single other profile at a time. // The first one we see gets special treatment. mOtherProfile = dri; @@ -1059,12 +1256,11 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } public ResolveInfo resolveInfoForPosition(int position, boolean filtered) { - return (filtered ? getItem(position) : mList.get(position)).ri; + return (filtered ? getItem(position) : mList.get(position)).getResolveInfo(); } - public Intent intentForPosition(int position, boolean filtered) { - DisplayResolveInfo dri = filtered ? getItem(position) : mList.get(position); - return intentForDisplayResolveInfo(dri); + public TargetInfo targetInfoForPosition(int position, boolean filtered) { + return filtered ? getItem(position) : mList.get(position); } public int getCount() { @@ -1075,7 +1271,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return result; } - public DisplayResolveInfo getItem(int position) { + public TargetInfo getItem(int position) { if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) { position++; } @@ -1086,11 +1282,31 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return position; } - public View getView(int position, View convertView, ViewGroup parent) { + public boolean hasExtendedInfo() { + return mHasExtendedInfo; + } + + public boolean hasResolvedTarget(ResolveInfo info) { + for (int i = 0, N = mList.size(); i < N; i++) { + if (info.equals(mList.get(i).getResolveInfo())) { + return true; + } + } + return false; + } + + protected int getDisplayResolveInfoCount() { + return mList.size(); + } + + protected DisplayResolveInfo getDisplayResolveInfo(int index) { + return mList.get(index); + } + + public final View getView(int position, View convertView, ViewGroup parent) { View view = convertView; if (view == null) { - view = mInflater.inflate( - com.android.internal.R.layout.resolve_list_item, parent, false); + view = createView(parent); final ViewHolder holder = new ViewHolder(view); view.setTag(holder); @@ -1099,19 +1315,29 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return view; } - private final void bindView(View view, DisplayResolveInfo info) { + public View createView(ViewGroup parent) { + return mInflater.inflate( + com.android.internal.R.layout.resolve_list_item, parent, false); + } + + public boolean showsExtendedInfo(TargetInfo info) { + return !TextUtils.isEmpty(info.getExtendedInfo()); + } + + private final void bindView(View view, TargetInfo info) { final ViewHolder holder = (ViewHolder) view.getTag(); - holder.text.setText(info.displayLabel); - if (mShowExtended) { + holder.text.setText(info.getDisplayLabel()); + if (showsExtendedInfo(info)) { holder.text2.setVisibility(View.VISIBLE); - holder.text2.setText(info.extendedInfo); + holder.text2.setText(info.getExtendedInfo()); } else { holder.text2.setVisibility(View.GONE); } - if (info.displayIcon == null) { - new LoadIconTask().execute(info); + if (info instanceof DisplayResolveInfo + && !((DisplayResolveInfo) info).hasDisplayIcon()) { + new LoadAdapterIconTask((DisplayResolveInfo) info).execute(); } - holder.icon.setImageDrawable(info.displayIcon); + holder.icon.setImageDrawable(info.getDisplayIcon()); } } @@ -1131,7 +1357,9 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { - position -= mListView.getHeaderViewsCount(); + if (mListView != null) { + position -= mListView.getHeaderViewsCount(); + } if (position < 0) { // Header views don't count. return false; @@ -1143,44 +1371,53 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } - class LoadIconTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> { + abstract class LoadIconTask extends AsyncTask<Void, Void, Drawable> { + protected final DisplayResolveInfo mDisplayResolveInfo; + private final ResolveInfo mResolveInfo; + + public LoadIconTask(DisplayResolveInfo dri) { + mDisplayResolveInfo = dri; + mResolveInfo = dri.getResolveInfo(); + } + @Override - protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) { - final DisplayResolveInfo info = params[0]; - if (info.displayIcon == null) { - info.displayIcon = loadIconForResolveInfo(info.ri); - } - return info; + protected Drawable doInBackground(Void... params) { + return loadIconForResolveInfo(mResolveInfo); + } + + @Override + protected void onPostExecute(Drawable d) { + mDisplayResolveInfo.setDisplayIcon(d); + } + } + + class LoadAdapterIconTask extends LoadIconTask { + public LoadAdapterIconTask(DisplayResolveInfo dri) { + super(dri); } @Override - protected void onPostExecute(DisplayResolveInfo info) { - if (mProfileView != null && mAdapter.getOtherProfile() == info) { + protected void onPostExecute(Drawable d) { + super.onPostExecute(d); + if (mProfileView != null && mAdapter.getOtherProfile() == mDisplayResolveInfo) { bindProfileView(); } mAdapter.notifyDataSetChanged(); } } - class LoadIconIntoViewTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> { - final ImageView mTargetView; + class LoadIconIntoViewTask extends LoadIconTask { + private final ImageView mTargetView; - public LoadIconIntoViewTask(ImageView target) { + public LoadIconIntoViewTask(DisplayResolveInfo dri, ImageView target) { + super(dri); mTargetView = target; } @Override - protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) { - final DisplayResolveInfo info = params[0]; - if (info.displayIcon == null) { - info.displayIcon = loadIconForResolveInfo(info.ri); - } - return info; - } - - @Override - protected void onPostExecute(DisplayResolveInfo info) { - mTargetView.setImageDrawable(info.displayIcon); + protected void onPostExecute(Drawable d) { + super.onPostExecute(d); + mTargetView.setImageDrawable(d); } } diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml new file mode 100644 index 0000000..0fa82eb --- /dev/null +++ b/core/res/res/layout/chooser_grid.xml @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +* Copyright 2015, The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +--> +<com.android.internal.widget.ResolverDrawerLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:maxWidth="@dimen/resolver_max_width" + android:maxCollapsedHeight="256dp" + android:maxCollapsedHeightSmall="56dp" + android:id="@id/contentPanel"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alwaysShow="true" + android:elevation="8dp" + android:paddingStart="?attr/dialogPreferredPadding" + android:background="@color/white" > + <ImageView android:id="@+id/title_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="start|center_vertical" + android:layout_marginEnd="16dp" + android:scaleType="fitCenter" /> + <TextView android:id="@+id/title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:minHeight="56dp" + android:textAppearance="?attr/textAppearanceMedium" + android:gravity="start|center_vertical" + android:paddingEnd="?attr/dialogPreferredPadding" + android:paddingTop="8dp" + android:paddingBottom="8dp" /> + <LinearLayout android:id="@+id/profile_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_marginTop="4dp" + android:layout_marginEnd="4dp" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:paddingTop="4dp" + android:paddingBottom="4dp" + android:focusable="true" + android:visibility="gone" + style="?attr/borderlessButtonStyle"> + <ImageView android:id="@+id/icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="start|center_vertical" + android:layout_marginStart="4dp" + android:layout_marginEnd="16dp" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:scaleType="fitCenter" /> + <TextView android:id="@id/text1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start|center_vertical" + android:layout_marginEnd="16dp" + android:textAppearance="?attr/textAppearanceButton" + android:textColor="?attr/textColorPrimary" + android:minLines="1" + android:maxLines="1" + android:ellipsize="marquee" /> + </LinearLayout> + </LinearLayout> + + <GridView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/resolver_list" + android:clipToPadding="false" + android:paddingStart="@dimen/chooser_grid_padding" + android:paddingEnd="@dimen/chooser_grid_padding" + android:scrollbarStyle="outsideOverlay" + android:background="@color/white" + android:elevation="8dp" + android:numColumns="4" + android:nestedScrollingEnabled="true" /> + + <TextView android:id="@+id/empty" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alwaysShow="true" + android:text="@string/noApplications" + android:padding="32dp" + android:gravity="center" + android:visibility="gone" /> + +</com.android.internal.widget.ResolverDrawerLayout> diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml new file mode 100644 index 0000000..664b02f --- /dev/null +++ b/core/res/res/layout/resolve_grid_item.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" android:layout_height="wrap_content" + android:minWidth="80dp" + android:gravity="center" + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:background="?attr/activatedBackgroundIndicator"> + + <!-- Activity icon when presenting dialog --> + <ImageView android:id="@+id/icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:scaleType="fitCenter" /> + + <!-- Activity name --> + <TextView android:id="@android:id/text1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginLeft="4dp" + android:layout_marginRight="4dp" + android:textAppearance="?attr/textAppearanceSmall" + android:textColor="?attr/textColorPrimary" + android:fontFamily="sans-serif-condensed" + android:gravity="center" + android:minLines="2" + android:maxLines="2" + android:ellipsize="marquee" /> + <!-- Extended activity info to distinguish between duplicate activity names --> + <TextView android:id="@android:id/text2" + android:textAppearance="?android:attr/textAppearanceSmall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="4dp" + android:layout_marginRight="4dp" + android:minLines="2" + android:maxLines="2" + android:gravity="center" + android:ellipsize="marquee" /> +</LinearLayout> + diff --git a/core/res/res/values-sw360dp/dimens.xml b/core/res/res/values-sw360dp/dimens.xml new file mode 100644 index 0000000..4c74264 --- /dev/null +++ b/core/res/res/values-sw360dp/dimens.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources> + <dimen name="chooser_grid_padding">16dp</dimen> +</resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 2654a25..100b161 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -394,4 +394,5 @@ <dimen name="floating_toolbar_minimum_overflow_height">192dp</dimen> <dimen name="floating_toolbar_overflow_width">130dp</dimen> <dimen name="floating_toolbar_margin">2dp</dimen> + <dimen name="chooser_grid_padding">0dp</dimen> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 42d187d..36a736a 100755 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2232,4 +2232,7 @@ <java-symbol type="layout" name="date_picker_month_item_material" /> <java-symbol type="id" name="month_view" /> <java-symbol type="integer" name="config_zen_repeat_callers_threshold" /> + <java-symbol type="layout" name="chooser_grid" /> + <java-symbol type="layout" name="resolve_grid_item" /> + <java-symbol type="id" name="title_icon" /> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java index 9928f7f..23a65e8 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java @@ -93,7 +93,8 @@ public class UsbResolverActivity extends ResolverActivity { } @Override - protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) { + protected void onTargetSelected(TargetInfo target, boolean alwaysCheck) { + final ResolveInfo ri = target.getResolveInfo(); try { IBinder b = ServiceManager.getService(USB_SERVICE); IUsbManager service = IUsbManager.Stub.asInterface(b); @@ -121,7 +122,7 @@ public class UsbResolverActivity extends ResolverActivity { } try { - startActivityAsUser(intent, new UserHandle(userId)); + target.startAsUser(this, null, new UserHandle(userId)); } catch (ActivityNotFoundException e) { Log.e(TAG, "startActivity failed", e); } |