diff options
| -rw-r--r-- | api/current.txt | 3 | ||||
| -rw-r--r-- | api/system-current.txt | 3 | ||||
| -rw-r--r-- | core/java/android/content/Intent.java | 8 | ||||
| -rw-r--r-- | core/java/android/service/chooser/ChooserTarget.java | 149 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/ChooserActivity.java | 298 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/ResolverActivity.java | 239 | ||||
| -rw-r--r-- | core/res/res/layout/chooser_grid.xml | 20 | ||||
| -rw-r--r-- | core/res/res/layout/chooser_row.xml | 31 | ||||
| -rw-r--r-- | core/res/res/layout/resolve_grid_item.xml | 33 | ||||
| -rw-r--r-- | core/res/res/values/colors.xml | 2 | ||||
| -rwxr-xr-x | core/res/res/values/symbols.xml | 4 |
11 files changed, 467 insertions, 323 deletions
diff --git a/api/current.txt b/api/current.txt index ef2c4dae5209..6c9dd35c95f2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8294,7 +8294,6 @@ package android.content { 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_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER"; - 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"; @@ -28730,10 +28729,8 @@ 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(); diff --git a/api/system-current.txt b/api/system-current.txt index 4508b20e3810..6b1f809ad47b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -8520,7 +8520,6 @@ package android.content { 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_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER"; - 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"; @@ -30744,10 +30743,8 @@ 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(); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 6f543a80564a..7d767602541a 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3365,14 +3365,6 @@ 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 f0ca276f11f2..4c94ee7ca23c 100644 --- a/core/java/android/service/chooser/ChooserTarget.java +++ b/core/java/android/service/chooser/ChooserTarget.java @@ -58,12 +58,6 @@ 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; @@ -146,43 +140,6 @@ public final class ChooserTarget implements Parcelable { mIntentSender = intentSender; } - /** - * Construct a deep link target for presentation by a chooser UI. - * - * <p>A target is composed of a title and an icon for presentation to the user. - * The UI presenting this target may truncate the title if it is too long to be presented - * in the available space, as well as crop, resize or overlay the supplied icon.</p> - * - * <p>The creator of a target may supply a ranking score. This score is assumed to be relative - * to the other targets supplied by the same - * {@link ChooserTargetService#onGetChooserTargets(ComponentName, IntentFilter) query}. - * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match). - * Scores for a set of targets do not need to sum to 1.</p> - * - * <p>Before being sent, the Intent supplied will be - * {@link Intent#fillIn(Intent, int) filled in} by the Intent originally supplied - * to the chooser.</p> - * - * <p>Take care not to place custom {@link android.os.Parcelable} types into - * the Intent as extras, as the system will not be able to unparcel it to merge - * additional extras.</p> - * - * @param title title of this target that will be shown to a user - * @param icon icon to represent this target - * @param score ranking score for this target between 0.0f and 1.0f, inclusive - * @param intent Intent to fill in and send if the user chooses this target - */ - 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) { @@ -192,9 +149,6 @@ public final class ChooserTarget implements Parcelable { } mScore = in.readFloat(); mIntentSender = IntentSender.readIntentSenderOrNullFromParcel(in); - if (in.readInt() != 0) { - mIntent = Intent.CREATOR.createFromParcel(in); - } } /** @@ -241,18 +195,6 @@ 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 @@ -272,91 +214,8 @@ public final class ChooserTarget implements Parcelable { 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"); + Log.e(TAG, "sendIntent " + this + " failed - no IntentSender to send"); return false; } } @@ -364,7 +223,7 @@ public final class ChooserTarget implements Parcelable { @Override public String toString() { return "ChooserTarget{" - + (mIntentSender != null ? mIntentSender.getCreatorPackage() : mIntent) + + (mIntentSender != null ? mIntentSender.getCreatorPackage() : null) + ", " + "'" + mTitle + "', " + mScore + "}"; @@ -386,10 +245,6 @@ public final class ChooserTarget implements Parcelable { } dest.writeFloat(mScore); IntentSender.writeIntentSenderOrNullToParcel(mIntentSender, dest); - dest.writeInt(mIntent != null ? 1 : 0); - if (mIntent != null) { - mIntent.writeToParcel(dest, 0); - } } public static final Creator<ChooserTarget> CREATOR diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 62ca1f0f3935..83fa967e5ca8 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -24,9 +24,11 @@ import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; +import android.content.pm.LabeledIntent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; +import android.database.DataSetObserver; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -37,6 +39,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; +import android.os.UserManager; import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; import android.service.chooser.IChooserTargetResult; @@ -44,8 +47,16 @@ import android.service.chooser.IChooserTargetService; import android.text.TextUtils; import android.util.Log; import android.util.Slog; +import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.widget.AbsListView; +import android.widget.BaseAdapter; +import android.widget.LinearLayout; +import android.widget.ListView; +import com.android.internal.R; import java.util.ArrayList; import java.util.List; @@ -63,7 +74,7 @@ public class ChooserActivity extends ResolverActivity { private IntentSender mRefinementIntentSender; private RefinementResultReceiver mRefinementResultReceiver; - private ChooserTarget[] mCallerChooserTargets; + private ChooserListAdapter mChooserListAdapter; private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>(); @@ -84,8 +95,7 @@ public class ChooserActivity extends ResolverActivity { + " Have you considered returning results faster?"); break; } - final ChooserListAdapter cla = (ChooserListAdapter) getAdapter(); - cla.addServiceResults(sri.originalTarget, sri.resultTargets); + mChooserListAdapter.addServiceResults(sri.originalTarget, sri.resultTargets); unbindService(sri.connection); mServiceConnections.remove(sri.connection); break; @@ -166,20 +176,6 @@ public class ChooserActivity extends ResolverActivity { } } - 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(TAG, "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); mRefinementIntentSender = intent.getParcelableExtra( @@ -233,8 +229,19 @@ public class ChooserActivity extends ResolverActivity { } @Override + void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter, + boolean alwaysUseOption) { + final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null; + mChooserListAdapter = (ChooserListAdapter) adapter; + adapterView.setAdapter(new ChooserRowAdapter(mChooserListAdapter)); + if (listView != null) { + listView.setItemsCanFocus(true); + } + } + + @Override int getLayoutResource() { - return com.android.internal.R.layout.chooser_grid; + return R.layout.chooser_grid; } @Override @@ -413,10 +420,11 @@ public class ChooserActivity extends ResolverActivity { } @Override - ResolveListAdapter createAdapter(Context context, Intent[] initialIntents, - List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { - final ChooserListAdapter adapter = new ChooserListAdapter(context, initialIntents, rList, - launchedFromUid, filterLastUsed, mCallerChooserTargets); + ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents, + Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, + boolean filterLastUsed) { + final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents, + initialIntents, rList, launchedFromUid, filterLastUsed); if (DEBUG) Log.d(TAG, "Adapter created; querying services"); queryTargetServices(adapter); return adapter; @@ -426,17 +434,23 @@ public class ChooserActivity extends ResolverActivity { private final DisplayResolveInfo mSourceInfo; private final ResolveInfo mBackupResolveInfo; private final ChooserTarget mChooserTarget; + private Drawable mBadgeIcon = null; private final Drawable mDisplayIcon; private final Intent mFillInIntent; private final int mFillInFlags; - public ChooserTargetInfo(ChooserTarget target) { - this(null, target); - } - public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget) { mSourceInfo = sourceInfo; mChooserTarget = chooserTarget; + if (sourceInfo != null) { + final ResolveInfo ri = sourceInfo.getResolveInfo(); + if (ri != null) { + final ActivityInfo ai = ri.activityInfo; + if (ai != null && ai.applicationInfo != null) { + mBadgeIcon = getPackageManager().getApplicationIcon(ai.applicationInfo); + } + } + } mDisplayIcon = new BitmapDrawable(getResources(), chooserTarget.getIcon()); if (sourceInfo != null) { @@ -453,6 +467,7 @@ public class ChooserActivity extends ResolverActivity { mSourceInfo = other.mSourceInfo; mBackupResolveInfo = other.mBackupResolveInfo; mChooserTarget = other.mChooserTarget; + mBadgeIcon = other.mBadgeIcon; mDisplayIcon = other.mDisplayIcon; mFillInIntent = fillInIntent; mFillInFlags = flags; @@ -460,10 +475,7 @@ public class ChooserActivity extends ResolverActivity { @Override public Intent getResolvedIntent() { - final Intent targetIntent = mChooserTarget.getIntent(); - if (targetIntent != null) { - return targetIntent; - } else if (mSourceInfo != null) { + if (mSourceInfo != null) { return mSourceInfo.getResolvedIntent(); } return getTargetIntent(); @@ -507,7 +519,8 @@ public class ChooserActivity extends ResolverActivity { if (intent == null) { return false; } - return mChooserTarget.sendIntentAsCaller(activity, intent, userId); + // ChooserTargets will launch with their IntentSender's identity + return mChooserTarget.sendIntent(activity, intent); } @Override @@ -516,7 +529,8 @@ public class ChooserActivity extends ResolverActivity { if (intent == null) { return false; } - return mChooserTarget.sendIntentAsUser(activity, intent, user); + // ChooserTargets will launch with their IntentSender's identity + return mChooserTarget.sendIntent(activity, intent); } @Override @@ -540,6 +554,11 @@ public class ChooserActivity extends ResolverActivity { } @Override + public Drawable getBadgeIcon() { + return mBadgeIcon; + } + + @Override public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { return new ChooserTargetInfo(this, fillInIntent, flags); } @@ -556,16 +575,49 @@ public class ChooserActivity extends ResolverActivity { } public class ChooserListAdapter extends ResolveListAdapter { - private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>(); - private final List<ChooserTargetInfo> mCallerTargets = new ArrayList<>(); - - public ChooserListAdapter(Context context, Intent[] initialIntents, List<ResolveInfo> rList, - int launchedFromUid, boolean filterLastUsed, ChooserTarget[] callerChooserTargets) { - super(context, initialIntents, rList, launchedFromUid, filterLastUsed); + public static final int TARGET_BAD = -1; + public static final int TARGET_CALLER = 0; + public static final int TARGET_SERVICE = 1; + public static final int TARGET_STANDARD = 2; - if (callerChooserTargets != null) { - for (ChooserTarget target : callerChooserTargets) { - mCallerTargets.add(new ChooserTargetInfo(target)); + private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>(); + private final List<TargetInfo> mCallerTargets = new ArrayList<>(); + + public ChooserListAdapter(Context context, List<Intent> payloadIntents, + Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, + boolean filterLastUsed) { + // Don't send the initial intents through the shared ResolverActivity path, + // we want to separate them into a different section. + super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed); + + if (initialIntents != null) { + final PackageManager pm = getPackageManager(); + for (int i = 0; i < initialIntents.length; i++) { + final Intent ii = initialIntents[i]; + if (ii == null) { + continue; + } + final ActivityInfo ai = ii.resolveActivityInfo(pm, 0); + if (ai == null) { + Log.w(TAG, "No activity found for " + ii); + continue; + } + ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = ai; + UserManager userManager = + (UserManager) getSystemService(Context.USER_SERVICE); + if (userManager.isManagedProfile()) { + ri.noResourceId = true; + } + if (ii instanceof LabeledIntent) { + LabeledIntent li = (LabeledIntent)ii; + ri.resolvePackageName = li.getSourcePackage(); + ri.labelRes = li.getLabelResource(); + ri.nonLocalizedLabel = li.getNonLocalizedLabel(); + ri.icon = li.getIconResource(); + } + mCallerTargets.add(new DisplayResolveInfo(ii, ri, + ri.loadLabel(pm), null, ii)); } } } @@ -578,7 +630,7 @@ public class ChooserActivity extends ResolverActivity { } @Override - public View createView(ViewGroup parent) { + public View onCreateView(ViewGroup parent) { return mInflater.inflate( com.android.internal.R.layout.resolve_grid_item, parent, false); } @@ -600,6 +652,41 @@ public class ChooserActivity extends ResolverActivity { return super.getCount() + mServiceTargets.size() + mCallerTargets.size(); } + public int getCallerTargetsCount() { + return mCallerTargets.size(); + } + + public int getServiceTargetsCount() { + return mServiceTargets.size(); + } + + public int getStandardTargetCount() { + return super.getCount(); + } + + public int getPositionTargetType(int position) { + int offset = 0; + + final int callerTargetCount = mCallerTargets.size(); + if (position < callerTargetCount) { + return TARGET_CALLER; + } + offset += callerTargetCount; + + final int serviceTargetCount = mServiceTargets.size(); + if (position - offset < serviceTargetCount) { + return TARGET_SERVICE; + } + offset += serviceTargetCount; + + final int standardTargetCount = super.getCount(); + if (position - offset < standardTargetCount) { + return TARGET_STANDARD; + } + + return TARGET_BAD; + } + @Override public TargetInfo getItem(int position) { int offset = 0; @@ -643,6 +730,133 @@ public class ChooserActivity extends ResolverActivity { } } + class ChooserRowAdapter extends BaseAdapter { + private ChooserListAdapter mChooserListAdapter; + private final LayoutInflater mLayoutInflater; + private final int mColumnCount = 4; + + public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) { + mChooserListAdapter = wrappedAdapter; + mLayoutInflater = LayoutInflater.from(ChooserActivity.this); + + wrappedAdapter.registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + super.onChanged(); + notifyDataSetChanged(); + } + + @Override + public void onInvalidated() { + super.onInvalidated(); + notifyDataSetInvalidated(); + } + }); + } + + @Override + public int getCount() { + return (int) ( + Math.ceil((float) mChooserListAdapter.getCallerTargetsCount() / mColumnCount) + + Math.ceil((float) mChooserListAdapter.getServiceTargetsCount() / mColumnCount) + + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount) + ); + } + + @Override + public Object getItem(int position) { + // We have nothing useful to return here. + return position; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final View[] holder; + if (convertView == null) { + holder = createViewHolder(parent); + } else { + holder = (View[]) convertView.getTag(); + } + bindViewHolder(position, holder); + + // We keep the actual list item view as the last item in the holder array + return holder[mColumnCount]; + } + + View[] createViewHolder(ViewGroup parent) { + final View[] holder = new View[mColumnCount + 1]; + + final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, + parent, false); + for (int i = 0; i < mColumnCount; i++) { + holder[i] = mChooserListAdapter.createView(row); + row.addView(holder[i]); + } + row.setTag(holder); + holder[mColumnCount] = row; + return holder; + } + + void bindViewHolder(int rowPosition, View[] holder) { + final int start = getFirstRowPosition(rowPosition); + final int startType = mChooserListAdapter.getPositionTargetType(start); + + int end = start + mColumnCount - 1; + while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) { + end--; + } + + final ViewGroup row = (ViewGroup) holder[mColumnCount]; + + if (startType == ChooserListAdapter.TARGET_SERVICE) { + row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color)); + } else { + row.setBackground(null); + } + + for (int i = 0; i < mColumnCount; i++) { + final View v = holder[i]; + if (start + i <= end) { + v.setVisibility(View.VISIBLE); + final int itemIndex = start + i; + mChooserListAdapter.bindView(itemIndex, v); + v.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + startSelected(itemIndex, false, true); + } + }); + } else { + v.setVisibility(View.GONE); + } + } + } + + int getFirstRowPosition(int row) { + final int callerCount = mChooserListAdapter.getCallerTargetsCount(); + final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount); + + if (row < callerRows) { + return row * mColumnCount; + } + + final int serviceCount = mChooserListAdapter.getServiceTargetsCount(); + final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount); + + if (row < callerRows + serviceRows) { + return callerCount + (row - callerRows) * mColumnCount; + } + + return callerCount + serviceCount + + (row - callerRows - serviceRows) * mColumnCount; + } + } + class ChooserTargetServiceConnection implements ServiceConnection { private final DisplayResolveInfo mOriginalTarget; diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 204866432000..26cdf6b6eaa2 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -25,7 +25,6 @@ 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; @@ -83,7 +82,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; * which there is more than one matching activity, allowing the user to decide * which to go to. It is not normally used directly by application developers. */ -public class ResolverActivity extends Activity implements AdapterView.OnItemClickListener { +public class ResolverActivity extends Activity { private static final String TAG = "ResolverActivity"; private static final boolean DEBUG = false; @@ -93,8 +92,6 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic private boolean mSafeForwardingMode; private boolean mAlwaysUseOption; private AbsListView mAdapterView; - private ListView mListView; - private GridView mGridView; private Button mAlwaysButton; private Button mOnceButton; private View mProfileView; @@ -217,6 +214,13 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } catch (RemoteException e) { mLaunchedFromUid = -1; } + + if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) { + // Gulp! + finish(); + return; + } + mPm = getPackageManager(); mUsm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); @@ -229,67 +233,11 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); mIconDpi = am.getLauncherLargeIconDensity(); + // Add our initial intent as the first item, regardless of what else has already been added. mIntents.add(0, new Intent(intent)); - mAdapter = createAdapter(this, initialIntents, rList, mLaunchedFromUid, alwaysUseOption); - - final int layoutId; - final boolean useHeader; - if (mAdapter.hasFilteredItem()) { - layoutId = R.layout.resolver_list_with_default; - alwaysUseOption = false; - useHeader = true; - } else { - useHeader = false; - layoutId = getLayoutResource(); - } - mAlwaysUseOption = alwaysUseOption; - - if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) { - // Gulp! - finish(); - return; - } - - int count = mAdapter.mDisplayList.size(); - if (count > 1 || (count == 1 && mAdapter.getOtherProfile() != null)) { - setContentView(layoutId); - 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) { - mAdapterView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); - } - if (useHeader && mListView != null) { - mListView.addHeaderView(LayoutInflater.from(this).inflate( - R.layout.resolver_different_item_header, mListView, false)); - } - } else if (count == 1) { - safelyStartActivity(mAdapter.targetInfoForPosition(0, false)); - mPackageMonitor.unregister(); - mRegistered = false; - finish(); - return; - } else { - setContentView(R.layout.resolver_list); - - final TextView empty = (TextView) findViewById(R.id.empty); - empty.setVisibility(View.VISIBLE); + configureContentView(mIntents, initialIntents, rList, alwaysUseOption); - 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. getWindow().clearFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR); @@ -548,29 +496,6 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - if (mListView != null) { - position -= mListView.getHeaderViewsCount(); - } - if (position < 0) { - // Header views don't count. - return; - } - 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) { - mAdapterView.smoothScrollToPosition(checkedPos); - } - mLastSelected = checkedPos; - } else { - startSelected(position, false, true); - } - } - private boolean hasManagedProfile() { UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); if (userManager == null) { @@ -831,14 +756,68 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic startActivity(in); } - ResolveListAdapter createAdapter(Context context, Intent[] initialIntents, - List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { - return new ResolveListAdapter(context, initialIntents, rList, launchedFromUid, - filterLastUsed); + ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents, + Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, + boolean filterLastUsed) { + return new ResolveListAdapter(context, payloadIntents, initialIntents, rList, + launchedFromUid, filterLastUsed); } - ResolveListAdapter getAdapter() { - return mAdapter; + void configureContentView(List<Intent> payloadIntents, Intent[] initialIntents, + List<ResolveInfo> rList, boolean alwaysUseOption) { + mAdapter = createAdapter(this, payloadIntents, initialIntents, rList, + mLaunchedFromUid, alwaysUseOption); + + final int layoutId; + if (mAdapter.hasFilteredItem()) { + layoutId = R.layout.resolver_list_with_default; + alwaysUseOption = false; + } else { + layoutId = getLayoutResource(); + } + mAlwaysUseOption = alwaysUseOption; + + int count = mAdapter.mDisplayList.size(); + if (count > 1 || (count == 1 && mAdapter.getOtherProfile() != null)) { + setContentView(layoutId); + mAdapterView = (AbsListView) findViewById(R.id.resolver_list); + onPrepareAdapterView(mAdapterView, mAdapter, alwaysUseOption); + } else if (count == 1) { + safelyStartActivity(mAdapter.targetInfoForPosition(0, false)); + mPackageMonitor.unregister(); + mRegistered = false; + finish(); + return; + } else { + setContentView(R.layout.resolver_list); + + final TextView empty = (TextView) findViewById(R.id.empty); + empty.setVisibility(View.VISIBLE); + + mAdapterView = (AbsListView) findViewById(R.id.resolver_list); + mAdapterView.setVisibility(View.GONE); + } + } + + void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter, + boolean alwaysUseOption) { + final boolean useHeader = adapter.hasFilteredItem(); + final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null; + + adapterView.setAdapter(mAdapter); + + final ItemClickListener listener = new ItemClickListener(); + adapterView.setOnItemClickListener(listener); + adapterView.setOnItemLongClickListener(listener); + + if (alwaysUseOption) { + listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); + } + + if (useHeader && listView != null) { + listView.addHeaderView(LayoutInflater.from(this).inflate( + R.layout.resolver_different_item_header, listView, false)); + } } final class DisplayResolveInfo implements TargetInfo { @@ -888,6 +867,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return mDisplayIcon; } + public Drawable getBadgeIcon() { + return null; + } + @Override public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { return new DisplayResolveInfo(this, fillInIntent, flags); @@ -1024,6 +1007,11 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic public Drawable getDisplayIcon(); /** + * @return The (small) icon to badge the target with + */ + public Drawable getBadgeIcon(); + + /** * Clone this target with the given fill-in information. */ public TargetInfo cloneFilledIn(Intent fillInIntent, int flags); @@ -1035,6 +1023,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } class ResolveListAdapter extends BaseAdapter { + private final List<Intent> mIntents; private final Intent[] mInitialIntents; private final List<ResolveInfo> mBaseResolveList; private ResolveInfo mLastChosen; @@ -1050,8 +1039,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic private int mLastChosenPosition = -1; private boolean mFilterLastUsed; - public ResolveListAdapter(Context context, Intent[] initialIntents, - List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { + public ResolveListAdapter(Context context, List<Intent> payloadIntents, + Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, + boolean filterLastUsed) { + mIntents = payloadIntents; mInitialIntents = initialIntents; mBaseResolveList = rList; mLaunchedFromUid = launchedFromUid; @@ -1430,15 +1421,19 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic View view = convertView; if (view == null) { view = createView(parent); - - final ViewHolder holder = new ViewHolder(view); - view.setTag(holder); } - bindView(view, getItem(position)); + onBindView(view, getItem(position)); return view; } - public View createView(ViewGroup parent) { + public final View createView(ViewGroup parent) { + final View view = onCreateView(parent); + final ViewHolder holder = new ViewHolder(view); + view.setTag(holder); + return view; + } + + public View onCreateView(ViewGroup parent) { return mInflater.inflate( com.android.internal.R.layout.resolve_list_item, parent, false); } @@ -1447,7 +1442,11 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return !TextUtils.isEmpty(info.getExtendedInfo()); } - private final void bindView(View view, TargetInfo info) { + public final void bindView(int position, View view) { + onBindView(view, getItem(position)); + } + + private void onBindView(View view, TargetInfo info) { final ViewHolder holder = (ViewHolder) view.getTag(); holder.text.setText(info.getDisplayLabel()); if (showsExtendedInfo(info)) { @@ -1461,6 +1460,15 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic new LoadAdapterIconTask((DisplayResolveInfo) info).execute(); } holder.icon.setImageDrawable(info.getDisplayIcon()); + if (holder.badge != null) { + final Drawable badge = info.getBadgeIcon(); + if (badge != null) { + holder.badge.setImageDrawable(badge); + holder.badge.setVisibility(View.VISIBLE); + } else { + holder.badge.setVisibility(View.GONE); + } + } } } @@ -1514,20 +1522,47 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic public TextView text; public TextView text2; public ImageView icon; + public ImageView badge; public ViewHolder(View view) { text = (TextView) view.findViewById(com.android.internal.R.id.text1); text2 = (TextView) view.findViewById(com.android.internal.R.id.text2); icon = (ImageView) view.findViewById(R.id.icon); + badge = (ImageView) view.findViewById(R.id.target_badge); } } - class ItemLongClickListener implements AdapterView.OnItemLongClickListener { + class ItemClickListener implements AdapterView.OnItemClickListener, + AdapterView.OnItemLongClickListener { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + final ListView listView = parent instanceof ListView ? (ListView) parent : null; + if (listView != null) { + position -= listView.getHeaderViewsCount(); + } + if (position < 0) { + // Header views don't count. + return; + } + 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) { + mAdapterView.smoothScrollToPosition(checkedPos); + } + mLastSelected = checkedPos; + } else { + startSelected(position, false, true); + } + } @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { - if (mListView != null) { - position -= mListView.getHeaderViewsCount(); + final ListView listView = parent instanceof ListView ? (ListView) parent : null; + if (listView != null) { + position -= listView.getHeaderViewsCount(); } if (position < 0) { // Header views don't count. diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml index 0fa82ebcb9d6..dcdfb6c318d4 100644 --- a/core/res/res/layout/chooser_grid.xml +++ b/core/res/res/layout/chooser_grid.xml @@ -21,7 +21,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:maxWidth="@dimen/resolver_max_width" - android:maxCollapsedHeight="256dp" + android:maxCollapsedHeight="288dp" android:maxCollapsedHeightSmall="56dp" android:id="@id/contentPanel"> @@ -30,24 +30,25 @@ android:layout_height="wrap_content" android:layout_alwaysShow="true" android:elevation="8dp" - android:paddingStart="?attr/dialogPreferredPadding" + android:paddingStart="16dp" 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:visibility="gone" 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:textSize="14sp" android:gravity="start|center_vertical" android:paddingEnd="?attr/dialogPreferredPadding" - android:paddingTop="8dp" - android:paddingBottom="8dp" /> + android:paddingTop="12dp" + android:paddingBottom="12dp" /> <LinearLayout android:id="@+id/profile_button" android:layout_width="wrap_content" android:layout_height="48dp" @@ -82,23 +83,24 @@ </LinearLayout> </LinearLayout> - <GridView + <ListView 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:listSelector="@color/transparent" + android:divider="@null" + android:scrollIndicators="top" android:nestedScrollingEnabled="true" /> <TextView android:id="@+id/empty" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alwaysShow="true" + android:background="@color/white" android:text="@string/noApplications" android:padding="32dp" android:gravity="center" diff --git a/core/res/res/layout/chooser_row.xml b/core/res/res/layout/chooser_row.xml new file mode 100644 index 000000000000..9baa32c67f3f --- /dev/null +++ b/core/res/res/layout/chooser_row.xml @@ -0,0 +1,31 @@ +<?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. +*/ +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" android:layout_height="wrap_content" + android:minHeight="80dp" + android:gravity="start|top" + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:paddingStart="@dimen/chooser_grid_padding" + android:paddingEnd="@dimen/chooser_grid_padding" + android:weightSum="4"> + +</LinearLayout> + diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml index 664b02fdbb60..1c496f6435d6 100644 --- a/core/res/res/layout/resolve_grid_item.xml +++ b/core/res/res/layout/resolve_grid_item.xml @@ -18,18 +18,31 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" - android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" android:minWidth="80dp" android:gravity="center" android:paddingTop="8dp" android:paddingBottom="8dp" - android:background="?attr/activatedBackgroundIndicator"> + android:background="?attr/selectableItemBackgroundBorderless"> - <!-- Activity icon when presenting dialog --> - <ImageView android:id="@+id/icon" - android:layout_width="48dp" - android:layout_height="48dp" - android:scaleType="fitCenter" /> + <FrameLayout android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <ImageView android:id="@+id/icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginLeft="3dp" + android:layout_marginRight="3dp" + android:layout_marginBottom="3dp" + android:scaleType="fitCenter" /> + <ImageView android:id="@+id/target_badge" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_gravity="end|bottom" + android:visibility="gone" + android:scaleType="fitCenter" /> + </FrameLayout> <!-- Activity name --> <TextView android:id="@android:id/text1" @@ -40,21 +53,23 @@ android:layout_marginRight="4dp" android:textAppearance="?attr/textAppearanceSmall" android:textColor="?attr/textColorPrimary" + android:textSize="12sp" android:fontFamily="sans-serif-condensed" - android:gravity="center" + android:gravity="top|center_horizontal" 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:textSize="12sp" 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:gravity="top|center_horizontal" android:ellipsize="marquee" /> </LinearLayout> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index b9825c534705..7f8c460bda4c 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -174,4 +174,6 @@ <color name="Pink_800">#ffad1457</color> <color name="Red_700">#ffc53929</color> <color name="Red_800">#ffb93221</color> + + <color name="chooser_service_row_background_color">#fff5f5f5</color> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 801a98a679d0..4e6c057680ed 100755 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2257,4 +2257,8 @@ <java-symbol type="id" name="day_picker_view_pager" /> <java-symbol type="layout" name="day_picker_content_material" /> <java-symbol type="drawable" name="scroll_indicator_material" /> + + <java-symbol type="layout" name="chooser_row" /> + <java-symbol type="color" name="chooser_service_row_background_color" /> + <java-symbol type="id" name="target_badge" /> </resources> |