Bringing new Chooser UI closer to spec

Separate the chooser targets into rows by type. Remove some API that
was redundant with LabeledIntent, simplifying ChooserTarget.

Change-Id: I90de471825f05d85e6ffbe72a32fb597be824a30
This commit is contained in:
Adam Powell
2015-05-06 17:49:36 -07:00
parent 1c86159142
commit 7d7580019e
11 changed files with 465 additions and 321 deletions

View File

@@ -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();

View File

@@ -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();

View File

@@ -3364,14 +3364,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

View File

@@ -57,12 +57,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.
*/
@@ -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);
}
}
/**
@@ -240,18 +194,6 @@ public final class ChooserTarget implements Parcelable {
return mIntentSender;
}
/**
* 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.
*
@@ -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

View File

@@ -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(
@@ -232,9 +228,20 @@ 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
@@ -539,6 +553,11 @@ public class ChooserActivity extends ResolverActivity {
return mDisplayIcon;
}
@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 {
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;
private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
private final List<ChooserTargetInfo> mCallerTargets = new ArrayList<>();
private final List<TargetInfo> 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 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 (callerChooserTargets != null) {
for (ChooserTarget target : callerChooserTargets) {
mCallerTargets.add(new ChooserTargetInfo(target));
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;

View File

@@ -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;
configureContentView(mIntents, initialIntents, rList, 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);
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);
@@ -1023,6 +1006,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.
*/
@@ -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.

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>