Loading core/java/com/android/internal/app/ChooserActivity.java +50 −28 Original line number Diff line number Diff line Loading @@ -1594,7 +1594,7 @@ public class ChooserActivity extends ResolverActivity { if (info == null) return null; // Now fetch app icon and raster with no badging even in work profile Bitmap appIcon = (new ActivityInfoPresentationGetter(info)).getIconBitmap(); Bitmap appIcon = makePresentationGetter(info).getIconBitmap(); // Raster target drawable with appIcon as a badge SimpleIconFactory sif = SimpleIconFactory.obtain(ChooserActivity.this); Loading Loading @@ -1865,8 +1865,9 @@ public class ChooserActivity extends ResolverActivity { ri.noResourceId = true; ri.icon = 0; } ResolveInfoPresentationGetter getter = makePresentationGetter(ri); mCallerTargets.add(new DisplayResolveInfo(ii, ri, ri.loadLabel(pm), null, ii)); getter.getLabel(), getter.getSubLabel(), ii)); } } } Loading @@ -1878,12 +1879,6 @@ public class ChooserActivity extends ResolverActivity { } } @Override public boolean showsExtendedInfo(TargetInfo info) { // We have badges so we don't need this text shown. return false; } @Override public View onCreateView(ViewGroup parent) { return mInflater.inflate( Loading Loading @@ -2301,8 +2296,10 @@ public class ChooserActivity extends ResolverActivity { final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); int columnCount = holder.getColumnCount(); final boolean isDirectShare = holder instanceof DirectShareViewHolder; for (int i = 0; i < columnCount; i++) { final View v = mChooserListAdapter.createView(holder.getRow(i)); final View v = mChooserListAdapter.createView(holder.getRowByIndex(i)); final int column = i; v.setOnClickListener(new OnClickListener() { @Override Loading @@ -2321,27 +2318,31 @@ public class ChooserActivity extends ResolverActivity { }); ViewGroup row = holder.addView(i, v); // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll = // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be // done before measuring. if (isDirectShare) { final ViewHolder vh = (ViewHolder) v.getTag(); vh.text.setLines(2); vh.text.setHorizontallyScrolling(false); vh.text2.setVisibility(View.GONE); } // Force height to be a given so we don't have visual disruption during scaling. LayoutParams lp = v.getLayoutParams(); v.measure(spec, spec); if (lp == null) { lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight()); row.setLayoutParams(lp); } else { lp.height = v.getMeasuredHeight(); } setViewHeight(v, v.getMeasuredHeight()); } final ViewGroup viewGroup = holder.getViewGroup(); // Pre-measure so we can scale later. // Pre-measure and fix height so we can scale later. holder.measure(); LayoutParams lp = viewGroup.getLayoutParams(); if (lp == null) { lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight()); viewGroup.setLayoutParams(lp); } else { lp.height = holder.getMeasuredRowHeight(); setViewHeight(viewGroup, holder.getMeasuredRowHeight()); if (isDirectShare) { DirectShareViewHolder dsvh = (DirectShareViewHolder) holder; setViewHeight(dsvh.getRow(0), holder.getMeasuredRowHeight()); setViewHeight(dsvh.getRow(1), holder.getMeasuredRowHeight()); } viewGroup.setTag(holder); Loading @@ -2349,6 +2350,16 @@ public class ChooserActivity extends ResolverActivity { return holder; } private void setViewHeight(View view, int heightPx) { LayoutParams lp = view.getLayoutParams(); if (lp == null) { lp = new LayoutParams(LayoutParams.MATCH_PARENT, heightPx); view.setLayoutParams(lp); } else { lp.height = heightPx; } } RowViewHolder createViewHolder(int viewType, ViewGroup parent) { if (viewType == VIEW_TYPE_DIRECT_SHARE) { ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate( Loading Loading @@ -2386,7 +2397,7 @@ public class ChooserActivity extends ResolverActivity { if (startType != lastStartType || rowPosition == getContentPreviewRowCount()) { row.setBackground(mChooserRowLayer); setVertPadding(row, mChooserRowServiceSpacing, 0); setVertPadding(row, 0, 0); } else { row.setBackground(null); setVertPadding(row, 0, 0); Loading Loading @@ -2483,7 +2494,9 @@ public class ChooserActivity extends ResolverActivity { abstract ViewGroup getViewGroup(); abstract ViewGroup getRow(int index); abstract ViewGroup getRowByIndex(int index); abstract ViewGroup getRow(int rowNumber); abstract void setViewVisibility(int i, int visibility); Loading Loading @@ -2532,10 +2545,15 @@ public class ChooserActivity extends ResolverActivity { return mRow; } public ViewGroup getRow(int index) { public ViewGroup getRowByIndex(int index) { return mRow; } public ViewGroup getRow(int rowNumber) { if (rowNumber == 0) return mRow; return null; } public ViewGroup addView(int index, View v) { mRow.addView(v); mCells[index] = v; Loading Loading @@ -2574,7 +2592,7 @@ public class ChooserActivity extends ResolverActivity { } public ViewGroup addView(int index, View v) { ViewGroup row = getRow(index); ViewGroup row = getRowByIndex(index); row.addView(v); mCells[index] = v; Loading @@ -2589,10 +2607,14 @@ public class ChooserActivity extends ResolverActivity { return mParent; } public ViewGroup getRow(int index) { public ViewGroup getRowByIndex(int index) { return mRows.get(index / mCellCountPerRow); } public ViewGroup getRow(int rowNumber) { return mRows.get(rowNumber); } public void measure() { final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); getRow(0).measure(spec, spec); Loading core/java/com/android/internal/app/ResolverActivity.java +76 −116 Original line number Diff line number Diff line Loading @@ -83,7 +83,6 @@ import com.android.internal.widget.ResolverDrawerLayout; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; Loading Loading @@ -499,33 +498,40 @@ public class ResolverActivity extends Activity { /** * Loads the icon for the provided ApplicationInfo. Defaults to using the application icon over * any IntentFilter or Activity icon to increase user understanding, with an exception for * applications that hold the right permission. Always attempts to use icon resources over * PackageManager loading mechanisms so badging can be done by iconloader. * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application * icon and label over any IntentFilter or Activity icon to increase user understanding, with an * exception for applications that hold the right permission. Always attempts to use available * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses * Strings to strip creative formatting. */ private abstract class TargetPresentationGetter { @Nullable abstract Drawable getIconSubstitute(); @Nullable abstract String getAppSubLabel(); private abstract static class TargetPresentationGetter { @Nullable abstract Drawable getIconSubstituteInternal(); @Nullable abstract String getAppSubLabelInternal(); private Context mCtx; protected PackageManager mPm; private final ApplicationInfo mAi; private final int mIconDpi; private final boolean mHasSubstitutePermission; TargetPresentationGetter(ApplicationInfo ai) { TargetPresentationGetter(Context ctx, int iconDpi, ApplicationInfo ai) { mCtx = ctx; mPm = ctx.getPackageManager(); mAi = ai; mIconDpi = iconDpi; mHasSubstitutePermission = PackageManager.PERMISSION_GRANTED == mPm.checkPermission( android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON, mAi.packageName); } Drawable getIcon() { return new BitmapDrawable(getResources(), getIconBitmap()); public Drawable getIcon() { return new BitmapDrawable(mCtx.getResources(), getIconBitmap()); } Bitmap getIconBitmap() { public Bitmap getIconBitmap() { Drawable dr = null; if (mHasSubstitutePermission) { dr = getIconSubstitute(); dr = getIconSubstituteInternal(); } if (dr == null) { Loading @@ -542,18 +548,18 @@ public class ResolverActivity extends Activity { dr = mAi.loadIcon(mPm); } SimpleIconFactory sif = SimpleIconFactory.obtain(ResolverActivity.this); SimpleIconFactory sif = SimpleIconFactory.obtain(mCtx); Bitmap icon = sif.createUserBadgedIconBitmap(dr, Process.myUserHandle()); sif.recycle(); return icon; } String getLabel() { public String getLabel() { String label = null; // Apps with the substitute permission will always show the sublabel as their label if (mHasSubstitutePermission) { label = getAppSubLabel(); label = getAppSubLabelInternal(); } if (label == null) { Loading @@ -563,10 +569,14 @@ public class ResolverActivity extends Activity { return label; } String getSubLabel() { public String getSubLabel() { // Apps with the substitute permission will never have a sublabel if (mHasSubstitutePermission) return null; return getAppSubLabel(); return getAppSubLabelInternal(); } protected String loadLabelFromResource(Resources res, int resId) { return res.getString(resId); } @Nullable Loading @@ -576,17 +586,19 @@ public class ResolverActivity extends Activity { } protected class ResolveInfoPresentationGetter extends TargetPresentationGetter { /** * Loads the icon and label for the provided ResolveInfo. */ @VisibleForTesting public static class ResolveInfoPresentationGetter extends TargetPresentationGetter { private final ResolveInfo mRi; ResolveInfoPresentationGetter(ResolveInfo ri) { super(ri.activityInfo.applicationInfo); public ResolveInfoPresentationGetter(Context ctx, int iconDpi, ResolveInfo ri) { super(ctx, iconDpi, ri.activityInfo.applicationInfo); mRi = ri; } @Override Drawable getIconSubstitute() { Drawable getIconSubstituteInternal() { Drawable dr = null; try { // Do not use ResolveInfo#getIconResource() as it defaults to the app Loading @@ -603,20 +615,31 @@ public class ResolverActivity extends Activity { } @Override String getAppSubLabel() { String getAppSubLabelInternal() { // Will default to app name if no intent filter or activity label set, make sure to // check if subLabel matches label before final display return (String) mRi.loadLabel(mPm); } } protected class ActivityInfoPresentationGetter extends TargetPresentationGetter { ResolveInfoPresentationGetter makePresentationGetter(ResolveInfo ri) { return new ResolveInfoPresentationGetter(this, mIconDpi, ri); } /** * Loads the icon and label for the provided ActivityInfo. */ @VisibleForTesting public static class ActivityInfoPresentationGetter extends TargetPresentationGetter { private final ActivityInfo mActivityInfo; protected ActivityInfoPresentationGetter(ActivityInfo activityInfo) { super(activityInfo.applicationInfo); public ActivityInfoPresentationGetter(Context ctx, int iconDpi, ActivityInfo activityInfo) { super(ctx, iconDpi, activityInfo.applicationInfo); mActivityInfo = activityInfo; } @Override Drawable getIconSubstitute() { Drawable getIconSubstituteInternal() { Drawable dr = null; try { // Do not use ActivityInfo#getIconResource() as it defaults to the app Loading @@ -634,13 +657,19 @@ public class ResolverActivity extends Activity { } @Override String getAppSubLabel() { String getAppSubLabelInternal() { // Will default to app name if no activity label set, make sure to check if subLabel // matches label before final display return (String) mActivityInfo.loadLabel(mPm); } } protected ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo ai) { return new ActivityInfoPresentationGetter(this, mIconDpi, ai); } Drawable loadIconForResolveInfo(ResolveInfo ri) { return (new ResolveInfoPresentationGetter(ri)).getIcon(); return makePresentationGetter(ri).getIcon(); } @Override Loading Loading @@ -1713,34 +1742,13 @@ public class ResolverActivity extends Activity { } } // Check for applications with same name and use application name or // package name if necessary ResolvedComponentInfo rci0 = sortedComponents.get(0); ResolveInfo r0 = rci0.getResolveInfoAt(0); int start = 0; CharSequence r0Label = r0.loadLabel(mPm); mHasExtendedInfo = false; for (int i = 1; i < N; i++) { if (r0Label == null) { r0Label = r0.activityInfo.packageName; for (ResolvedComponentInfo rci : sortedComponents) { final ResolveInfo ri = rci.getResolveInfoAt(0); if (ri != null) { ResolveInfoPresentationGetter pg = makePresentationGetter(ri); addResolveInfoWithAlternates(rci, pg.getSubLabel(), pg.getLabel()); } ResolvedComponentInfo rci = sortedComponents.get(i); ResolveInfo ri = rci.getResolveInfoAt(0); CharSequence riLabel = ri.loadLabel(mPm); if (riLabel == null) { riLabel = ri.activityInfo.packageName; } if (riLabel.equals(r0Label)) { continue; } processGroup(sortedComponents, start, (i - 1), rci0, r0Label); rci0 = rci; r0 = ri; r0Label = riLabel; start = i; } // Process last group processGroup(sortedComponents, start, (N - 1), rci0, r0Label); } postListReadyRunnable(); Loading Loading @@ -1782,55 +1790,6 @@ public class ResolverActivity extends Activity { return mFilterLastUsed; } private void processGroup(List<ResolvedComponentInfo> rList, int start, int end, ResolvedComponentInfo ro, CharSequence roLabel) { // Process labels from start to i int num = end - start+1; if (num == 1) { // No duplicate labels. Use label for entry at start addResolveInfoWithAlternates(ro, null, roLabel); } else { mHasExtendedInfo = true; boolean usePkg = false; final ApplicationInfo ai = ro.getResolveInfoAt(0).activityInfo.applicationInfo; final CharSequence startApp = ai.loadLabel(mPm); if (startApp == null) { usePkg = true; } if (!usePkg) { // Use HashSet to track duplicates HashSet<CharSequence> duplicates = new HashSet<CharSequence>(); duplicates.add(startApp); for (int j = start+1; j <= end ; j++) { ResolveInfo jRi = rList.get(j).getResolveInfoAt(0); CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm); if ( (jApp == null) || (duplicates.contains(jApp))) { usePkg = true; break; } else { duplicates.add(jApp); } } // Clear HashSet for later use duplicates.clear(); } for (int k = start; k <= end; k++) { final ResolvedComponentInfo rci = rList.get(k); final ResolveInfo add = rci.getResolveInfoAt(0); final CharSequence extraInfo; if (usePkg) { // Use package name for all entries from start to end-1 extraInfo = add.activityInfo.packageName; } else { // Use application name for all entries from start to end-1 extraInfo = add.activityInfo.applicationInfo.loadLabel(mPm); } addResolveInfoWithAlternates(rci, extraInfo, roLabel); } } } private void addResolveInfoWithAlternates(ResolvedComponentInfo rci, CharSequence extraInfo, CharSequence roLabel) { final int count = rci.getCount(); Loading Loading @@ -1979,31 +1938,32 @@ public class ResolverActivity extends Activity { com.android.internal.R.layout.resolve_list_item, parent, false); } public boolean showsExtendedInfo(TargetInfo info) { return !TextUtils.isEmpty(info.getExtendedInfo()); } public final void bindView(int position, View view) { onBindView(view, getItem(position)); } private void onBindView(View view, TargetInfo info) { protected void onBindView(View view, TargetInfo info) { final ViewHolder holder = (ViewHolder) view.getTag(); if (info == null) { holder.icon.setImageDrawable( getDrawable(R.drawable.resolver_icon_placeholder)); return; } final CharSequence label = info.getDisplayLabel(); if (!TextUtils.equals(holder.text.getText(), label)) { holder.text.setText(info.getDisplayLabel()); } if (showsExtendedInfo(info)) { holder.text2.setVisibility(View.VISIBLE); holder.text2.setText(info.getExtendedInfo()); } else { holder.text2.setVisibility(View.GONE); // Always show a subLabel for visual consistency across list items. Show an empty // subLabel if the subLabel is the same as the label CharSequence subLabel = info.getExtendedInfo(); if (TextUtils.equals(label, subLabel)) subLabel = null; if (!TextUtils.equals(holder.text2.getText(), subLabel)) { holder.text2.setText(subLabel); } if (info instanceof DisplayResolveInfo && !((DisplayResolveInfo) info).hasDisplayIcon()) { new LoadAdapterIconTask((DisplayResolveInfo) info).execute(); Loading core/res/res/layout/resolve_grid_item.xml +16 −19 Original line number Diff line number Diff line Loading @@ -22,46 +22,43 @@ android:layout_height="wrap_content" android:minHeight="100dp" android:gravity="center" android:paddingTop="8dp" android:paddingTop="24dp" android:paddingBottom="8dp" android:paddingLeft="2dp" android:paddingRight="2dp" android:focusable="true" android:background="?attr/selectableItemBackgroundBorderless"> <ImageView android:id="@+id/icon" android:layout_width="@dimen/resolver_icon_size" android:layout_height="@dimen/resolver_icon_size" android:layout_marginLeft="3dp" android:layout_marginRight="3dp" android:layout_marginBottom="3dp" android:scaleType="fitCenter" /> <!-- Activity name --> <!-- Size manually tuned to match specs --> <Space android:layout_width="1dp" android:layout_height="7dp"/> <!-- App name or Direct Share target name, DS set to 2 lines --> <TextView android:id="@android:id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:textAppearance="?attr/textAppearanceSmall" android:textColor="?attr/textColorPrimary" android:textSize="12sp" android:textSize="14sp" android:fontFamily="sans-serif-condensed" android:gravity="top|center_horizontal" android:minLines="2" android:maxLines="2" android:ellipsize="marquee" /> <!-- Extended activity info to distinguish between duplicate activity names --> android:lines="1" android:ellipsize="end" /> <!-- Activity name if set, gone for Direct Share targets --> <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:lines="1" android:gravity="top|center_horizontal" android:ellipsize="marquee" android:visibility="gone" /> android:ellipsize="end"/> </LinearLayout> core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +49 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo; import static com.android.internal.app.ResolverWrapperActivity.sOverrides; import static org.hamcrest.CoreMatchers.is; Loading @@ -32,6 +33,7 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.content.pm.ResolveInfo; import android.text.TextUtils; import android.view.View; import android.widget.RelativeLayout; Loading @@ -40,7 +42,10 @@ import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.app.ResolverActivity.ActivityInfoPresentationGetter; import com.android.internal.app.ResolverActivity.ResolveInfoPresentationGetter; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; import com.android.internal.app.ResolverDataProvider.PackageManagerMockedInfo; import com.android.internal.widget.ResolverDrawerLayout; import org.junit.Before; Loading Loading @@ -319,6 +324,50 @@ public class ResolverActivityTest { assertThat(chosen[0], is(toChoose)); } @Test public void getActivityLabelAndSubLabel() throws Exception { ActivityInfoPresentationGetter pg; PackageManagerMockedInfo info; info = createPackageManagerMockedInfo(false); pg = new ActivityInfoPresentationGetter( info.ctx, 0, info.activityInfo); assertThat("Label should match app label", pg.getLabel().equals( info.setAppLabel)); assertThat("Sublabel should match activity label if set", pg.getSubLabel().equals(info.setActivityLabel)); info = createPackageManagerMockedInfo(true); pg = new ActivityInfoPresentationGetter( info.ctx, 0, info.activityInfo); assertThat("With override permission label should match activity label if set", pg.getLabel().equals(info.setActivityLabel)); assertThat("With override permission sublabel should be empty", TextUtils.isEmpty(pg.getSubLabel())); } @Test public void getResolveInfoLabelAndSubLabel() throws Exception { ResolveInfoPresentationGetter pg; PackageManagerMockedInfo info; info = createPackageManagerMockedInfo(false); pg = new ResolveInfoPresentationGetter( info.ctx, 0, info.resolveInfo); assertThat("Label should match app label", pg.getLabel().equals( info.setAppLabel)); assertThat("Sublabel should match resolve info label if set", pg.getSubLabel().equals(info.setResolveInfoLabel)); info = createPackageManagerMockedInfo(true); pg = new ResolveInfoPresentationGetter( info.ctx, 0, info.resolveInfo); assertThat("With override permission label should match resolve info label if set", pg.getLabel().equals(info.setResolveInfoLabel)); assertThat("With override permission sublabel should be empty", TextUtils.isEmpty(pg.getSubLabel())); } private Intent createSendImageIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); Loading core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java +86 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/com/android/internal/app/ChooserActivity.java +50 −28 Original line number Diff line number Diff line Loading @@ -1594,7 +1594,7 @@ public class ChooserActivity extends ResolverActivity { if (info == null) return null; // Now fetch app icon and raster with no badging even in work profile Bitmap appIcon = (new ActivityInfoPresentationGetter(info)).getIconBitmap(); Bitmap appIcon = makePresentationGetter(info).getIconBitmap(); // Raster target drawable with appIcon as a badge SimpleIconFactory sif = SimpleIconFactory.obtain(ChooserActivity.this); Loading Loading @@ -1865,8 +1865,9 @@ public class ChooserActivity extends ResolverActivity { ri.noResourceId = true; ri.icon = 0; } ResolveInfoPresentationGetter getter = makePresentationGetter(ri); mCallerTargets.add(new DisplayResolveInfo(ii, ri, ri.loadLabel(pm), null, ii)); getter.getLabel(), getter.getSubLabel(), ii)); } } } Loading @@ -1878,12 +1879,6 @@ public class ChooserActivity extends ResolverActivity { } } @Override public boolean showsExtendedInfo(TargetInfo info) { // We have badges so we don't need this text shown. return false; } @Override public View onCreateView(ViewGroup parent) { return mInflater.inflate( Loading Loading @@ -2301,8 +2296,10 @@ public class ChooserActivity extends ResolverActivity { final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); int columnCount = holder.getColumnCount(); final boolean isDirectShare = holder instanceof DirectShareViewHolder; for (int i = 0; i < columnCount; i++) { final View v = mChooserListAdapter.createView(holder.getRow(i)); final View v = mChooserListAdapter.createView(holder.getRowByIndex(i)); final int column = i; v.setOnClickListener(new OnClickListener() { @Override Loading @@ -2321,27 +2318,31 @@ public class ChooserActivity extends ResolverActivity { }); ViewGroup row = holder.addView(i, v); // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll = // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be // done before measuring. if (isDirectShare) { final ViewHolder vh = (ViewHolder) v.getTag(); vh.text.setLines(2); vh.text.setHorizontallyScrolling(false); vh.text2.setVisibility(View.GONE); } // Force height to be a given so we don't have visual disruption during scaling. LayoutParams lp = v.getLayoutParams(); v.measure(spec, spec); if (lp == null) { lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight()); row.setLayoutParams(lp); } else { lp.height = v.getMeasuredHeight(); } setViewHeight(v, v.getMeasuredHeight()); } final ViewGroup viewGroup = holder.getViewGroup(); // Pre-measure so we can scale later. // Pre-measure and fix height so we can scale later. holder.measure(); LayoutParams lp = viewGroup.getLayoutParams(); if (lp == null) { lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight()); viewGroup.setLayoutParams(lp); } else { lp.height = holder.getMeasuredRowHeight(); setViewHeight(viewGroup, holder.getMeasuredRowHeight()); if (isDirectShare) { DirectShareViewHolder dsvh = (DirectShareViewHolder) holder; setViewHeight(dsvh.getRow(0), holder.getMeasuredRowHeight()); setViewHeight(dsvh.getRow(1), holder.getMeasuredRowHeight()); } viewGroup.setTag(holder); Loading @@ -2349,6 +2350,16 @@ public class ChooserActivity extends ResolverActivity { return holder; } private void setViewHeight(View view, int heightPx) { LayoutParams lp = view.getLayoutParams(); if (lp == null) { lp = new LayoutParams(LayoutParams.MATCH_PARENT, heightPx); view.setLayoutParams(lp); } else { lp.height = heightPx; } } RowViewHolder createViewHolder(int viewType, ViewGroup parent) { if (viewType == VIEW_TYPE_DIRECT_SHARE) { ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate( Loading Loading @@ -2386,7 +2397,7 @@ public class ChooserActivity extends ResolverActivity { if (startType != lastStartType || rowPosition == getContentPreviewRowCount()) { row.setBackground(mChooserRowLayer); setVertPadding(row, mChooserRowServiceSpacing, 0); setVertPadding(row, 0, 0); } else { row.setBackground(null); setVertPadding(row, 0, 0); Loading Loading @@ -2483,7 +2494,9 @@ public class ChooserActivity extends ResolverActivity { abstract ViewGroup getViewGroup(); abstract ViewGroup getRow(int index); abstract ViewGroup getRowByIndex(int index); abstract ViewGroup getRow(int rowNumber); abstract void setViewVisibility(int i, int visibility); Loading Loading @@ -2532,10 +2545,15 @@ public class ChooserActivity extends ResolverActivity { return mRow; } public ViewGroup getRow(int index) { public ViewGroup getRowByIndex(int index) { return mRow; } public ViewGroup getRow(int rowNumber) { if (rowNumber == 0) return mRow; return null; } public ViewGroup addView(int index, View v) { mRow.addView(v); mCells[index] = v; Loading Loading @@ -2574,7 +2592,7 @@ public class ChooserActivity extends ResolverActivity { } public ViewGroup addView(int index, View v) { ViewGroup row = getRow(index); ViewGroup row = getRowByIndex(index); row.addView(v); mCells[index] = v; Loading @@ -2589,10 +2607,14 @@ public class ChooserActivity extends ResolverActivity { return mParent; } public ViewGroup getRow(int index) { public ViewGroup getRowByIndex(int index) { return mRows.get(index / mCellCountPerRow); } public ViewGroup getRow(int rowNumber) { return mRows.get(rowNumber); } public void measure() { final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); getRow(0).measure(spec, spec); Loading
core/java/com/android/internal/app/ResolverActivity.java +76 −116 Original line number Diff line number Diff line Loading @@ -83,7 +83,6 @@ import com.android.internal.widget.ResolverDrawerLayout; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; Loading Loading @@ -499,33 +498,40 @@ public class ResolverActivity extends Activity { /** * Loads the icon for the provided ApplicationInfo. Defaults to using the application icon over * any IntentFilter or Activity icon to increase user understanding, with an exception for * applications that hold the right permission. Always attempts to use icon resources over * PackageManager loading mechanisms so badging can be done by iconloader. * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application * icon and label over any IntentFilter or Activity icon to increase user understanding, with an * exception for applications that hold the right permission. Always attempts to use available * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses * Strings to strip creative formatting. */ private abstract class TargetPresentationGetter { @Nullable abstract Drawable getIconSubstitute(); @Nullable abstract String getAppSubLabel(); private abstract static class TargetPresentationGetter { @Nullable abstract Drawable getIconSubstituteInternal(); @Nullable abstract String getAppSubLabelInternal(); private Context mCtx; protected PackageManager mPm; private final ApplicationInfo mAi; private final int mIconDpi; private final boolean mHasSubstitutePermission; TargetPresentationGetter(ApplicationInfo ai) { TargetPresentationGetter(Context ctx, int iconDpi, ApplicationInfo ai) { mCtx = ctx; mPm = ctx.getPackageManager(); mAi = ai; mIconDpi = iconDpi; mHasSubstitutePermission = PackageManager.PERMISSION_GRANTED == mPm.checkPermission( android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON, mAi.packageName); } Drawable getIcon() { return new BitmapDrawable(getResources(), getIconBitmap()); public Drawable getIcon() { return new BitmapDrawable(mCtx.getResources(), getIconBitmap()); } Bitmap getIconBitmap() { public Bitmap getIconBitmap() { Drawable dr = null; if (mHasSubstitutePermission) { dr = getIconSubstitute(); dr = getIconSubstituteInternal(); } if (dr == null) { Loading @@ -542,18 +548,18 @@ public class ResolverActivity extends Activity { dr = mAi.loadIcon(mPm); } SimpleIconFactory sif = SimpleIconFactory.obtain(ResolverActivity.this); SimpleIconFactory sif = SimpleIconFactory.obtain(mCtx); Bitmap icon = sif.createUserBadgedIconBitmap(dr, Process.myUserHandle()); sif.recycle(); return icon; } String getLabel() { public String getLabel() { String label = null; // Apps with the substitute permission will always show the sublabel as their label if (mHasSubstitutePermission) { label = getAppSubLabel(); label = getAppSubLabelInternal(); } if (label == null) { Loading @@ -563,10 +569,14 @@ public class ResolverActivity extends Activity { return label; } String getSubLabel() { public String getSubLabel() { // Apps with the substitute permission will never have a sublabel if (mHasSubstitutePermission) return null; return getAppSubLabel(); return getAppSubLabelInternal(); } protected String loadLabelFromResource(Resources res, int resId) { return res.getString(resId); } @Nullable Loading @@ -576,17 +586,19 @@ public class ResolverActivity extends Activity { } protected class ResolveInfoPresentationGetter extends TargetPresentationGetter { /** * Loads the icon and label for the provided ResolveInfo. */ @VisibleForTesting public static class ResolveInfoPresentationGetter extends TargetPresentationGetter { private final ResolveInfo mRi; ResolveInfoPresentationGetter(ResolveInfo ri) { super(ri.activityInfo.applicationInfo); public ResolveInfoPresentationGetter(Context ctx, int iconDpi, ResolveInfo ri) { super(ctx, iconDpi, ri.activityInfo.applicationInfo); mRi = ri; } @Override Drawable getIconSubstitute() { Drawable getIconSubstituteInternal() { Drawable dr = null; try { // Do not use ResolveInfo#getIconResource() as it defaults to the app Loading @@ -603,20 +615,31 @@ public class ResolverActivity extends Activity { } @Override String getAppSubLabel() { String getAppSubLabelInternal() { // Will default to app name if no intent filter or activity label set, make sure to // check if subLabel matches label before final display return (String) mRi.loadLabel(mPm); } } protected class ActivityInfoPresentationGetter extends TargetPresentationGetter { ResolveInfoPresentationGetter makePresentationGetter(ResolveInfo ri) { return new ResolveInfoPresentationGetter(this, mIconDpi, ri); } /** * Loads the icon and label for the provided ActivityInfo. */ @VisibleForTesting public static class ActivityInfoPresentationGetter extends TargetPresentationGetter { private final ActivityInfo mActivityInfo; protected ActivityInfoPresentationGetter(ActivityInfo activityInfo) { super(activityInfo.applicationInfo); public ActivityInfoPresentationGetter(Context ctx, int iconDpi, ActivityInfo activityInfo) { super(ctx, iconDpi, activityInfo.applicationInfo); mActivityInfo = activityInfo; } @Override Drawable getIconSubstitute() { Drawable getIconSubstituteInternal() { Drawable dr = null; try { // Do not use ActivityInfo#getIconResource() as it defaults to the app Loading @@ -634,13 +657,19 @@ public class ResolverActivity extends Activity { } @Override String getAppSubLabel() { String getAppSubLabelInternal() { // Will default to app name if no activity label set, make sure to check if subLabel // matches label before final display return (String) mActivityInfo.loadLabel(mPm); } } protected ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo ai) { return new ActivityInfoPresentationGetter(this, mIconDpi, ai); } Drawable loadIconForResolveInfo(ResolveInfo ri) { return (new ResolveInfoPresentationGetter(ri)).getIcon(); return makePresentationGetter(ri).getIcon(); } @Override Loading Loading @@ -1713,34 +1742,13 @@ public class ResolverActivity extends Activity { } } // Check for applications with same name and use application name or // package name if necessary ResolvedComponentInfo rci0 = sortedComponents.get(0); ResolveInfo r0 = rci0.getResolveInfoAt(0); int start = 0; CharSequence r0Label = r0.loadLabel(mPm); mHasExtendedInfo = false; for (int i = 1; i < N; i++) { if (r0Label == null) { r0Label = r0.activityInfo.packageName; for (ResolvedComponentInfo rci : sortedComponents) { final ResolveInfo ri = rci.getResolveInfoAt(0); if (ri != null) { ResolveInfoPresentationGetter pg = makePresentationGetter(ri); addResolveInfoWithAlternates(rci, pg.getSubLabel(), pg.getLabel()); } ResolvedComponentInfo rci = sortedComponents.get(i); ResolveInfo ri = rci.getResolveInfoAt(0); CharSequence riLabel = ri.loadLabel(mPm); if (riLabel == null) { riLabel = ri.activityInfo.packageName; } if (riLabel.equals(r0Label)) { continue; } processGroup(sortedComponents, start, (i - 1), rci0, r0Label); rci0 = rci; r0 = ri; r0Label = riLabel; start = i; } // Process last group processGroup(sortedComponents, start, (N - 1), rci0, r0Label); } postListReadyRunnable(); Loading Loading @@ -1782,55 +1790,6 @@ public class ResolverActivity extends Activity { return mFilterLastUsed; } private void processGroup(List<ResolvedComponentInfo> rList, int start, int end, ResolvedComponentInfo ro, CharSequence roLabel) { // Process labels from start to i int num = end - start+1; if (num == 1) { // No duplicate labels. Use label for entry at start addResolveInfoWithAlternates(ro, null, roLabel); } else { mHasExtendedInfo = true; boolean usePkg = false; final ApplicationInfo ai = ro.getResolveInfoAt(0).activityInfo.applicationInfo; final CharSequence startApp = ai.loadLabel(mPm); if (startApp == null) { usePkg = true; } if (!usePkg) { // Use HashSet to track duplicates HashSet<CharSequence> duplicates = new HashSet<CharSequence>(); duplicates.add(startApp); for (int j = start+1; j <= end ; j++) { ResolveInfo jRi = rList.get(j).getResolveInfoAt(0); CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm); if ( (jApp == null) || (duplicates.contains(jApp))) { usePkg = true; break; } else { duplicates.add(jApp); } } // Clear HashSet for later use duplicates.clear(); } for (int k = start; k <= end; k++) { final ResolvedComponentInfo rci = rList.get(k); final ResolveInfo add = rci.getResolveInfoAt(0); final CharSequence extraInfo; if (usePkg) { // Use package name for all entries from start to end-1 extraInfo = add.activityInfo.packageName; } else { // Use application name for all entries from start to end-1 extraInfo = add.activityInfo.applicationInfo.loadLabel(mPm); } addResolveInfoWithAlternates(rci, extraInfo, roLabel); } } } private void addResolveInfoWithAlternates(ResolvedComponentInfo rci, CharSequence extraInfo, CharSequence roLabel) { final int count = rci.getCount(); Loading Loading @@ -1979,31 +1938,32 @@ public class ResolverActivity extends Activity { com.android.internal.R.layout.resolve_list_item, parent, false); } public boolean showsExtendedInfo(TargetInfo info) { return !TextUtils.isEmpty(info.getExtendedInfo()); } public final void bindView(int position, View view) { onBindView(view, getItem(position)); } private void onBindView(View view, TargetInfo info) { protected void onBindView(View view, TargetInfo info) { final ViewHolder holder = (ViewHolder) view.getTag(); if (info == null) { holder.icon.setImageDrawable( getDrawable(R.drawable.resolver_icon_placeholder)); return; } final CharSequence label = info.getDisplayLabel(); if (!TextUtils.equals(holder.text.getText(), label)) { holder.text.setText(info.getDisplayLabel()); } if (showsExtendedInfo(info)) { holder.text2.setVisibility(View.VISIBLE); holder.text2.setText(info.getExtendedInfo()); } else { holder.text2.setVisibility(View.GONE); // Always show a subLabel for visual consistency across list items. Show an empty // subLabel if the subLabel is the same as the label CharSequence subLabel = info.getExtendedInfo(); if (TextUtils.equals(label, subLabel)) subLabel = null; if (!TextUtils.equals(holder.text2.getText(), subLabel)) { holder.text2.setText(subLabel); } if (info instanceof DisplayResolveInfo && !((DisplayResolveInfo) info).hasDisplayIcon()) { new LoadAdapterIconTask((DisplayResolveInfo) info).execute(); Loading
core/res/res/layout/resolve_grid_item.xml +16 −19 Original line number Diff line number Diff line Loading @@ -22,46 +22,43 @@ android:layout_height="wrap_content" android:minHeight="100dp" android:gravity="center" android:paddingTop="8dp" android:paddingTop="24dp" android:paddingBottom="8dp" android:paddingLeft="2dp" android:paddingRight="2dp" android:focusable="true" android:background="?attr/selectableItemBackgroundBorderless"> <ImageView android:id="@+id/icon" android:layout_width="@dimen/resolver_icon_size" android:layout_height="@dimen/resolver_icon_size" android:layout_marginLeft="3dp" android:layout_marginRight="3dp" android:layout_marginBottom="3dp" android:scaleType="fitCenter" /> <!-- Activity name --> <!-- Size manually tuned to match specs --> <Space android:layout_width="1dp" android:layout_height="7dp"/> <!-- App name or Direct Share target name, DS set to 2 lines --> <TextView android:id="@android:id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:textAppearance="?attr/textAppearanceSmall" android:textColor="?attr/textColorPrimary" android:textSize="12sp" android:textSize="14sp" android:fontFamily="sans-serif-condensed" android:gravity="top|center_horizontal" android:minLines="2" android:maxLines="2" android:ellipsize="marquee" /> <!-- Extended activity info to distinguish between duplicate activity names --> android:lines="1" android:ellipsize="end" /> <!-- Activity name if set, gone for Direct Share targets --> <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:lines="1" android:gravity="top|center_horizontal" android:ellipsize="marquee" android:visibility="gone" /> android:ellipsize="end"/> </LinearLayout>
core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +49 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo; import static com.android.internal.app.ResolverWrapperActivity.sOverrides; import static org.hamcrest.CoreMatchers.is; Loading @@ -32,6 +33,7 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.content.pm.ResolveInfo; import android.text.TextUtils; import android.view.View; import android.widget.RelativeLayout; Loading @@ -40,7 +42,10 @@ import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.app.ResolverActivity.ActivityInfoPresentationGetter; import com.android.internal.app.ResolverActivity.ResolveInfoPresentationGetter; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; import com.android.internal.app.ResolverDataProvider.PackageManagerMockedInfo; import com.android.internal.widget.ResolverDrawerLayout; import org.junit.Before; Loading Loading @@ -319,6 +324,50 @@ public class ResolverActivityTest { assertThat(chosen[0], is(toChoose)); } @Test public void getActivityLabelAndSubLabel() throws Exception { ActivityInfoPresentationGetter pg; PackageManagerMockedInfo info; info = createPackageManagerMockedInfo(false); pg = new ActivityInfoPresentationGetter( info.ctx, 0, info.activityInfo); assertThat("Label should match app label", pg.getLabel().equals( info.setAppLabel)); assertThat("Sublabel should match activity label if set", pg.getSubLabel().equals(info.setActivityLabel)); info = createPackageManagerMockedInfo(true); pg = new ActivityInfoPresentationGetter( info.ctx, 0, info.activityInfo); assertThat("With override permission label should match activity label if set", pg.getLabel().equals(info.setActivityLabel)); assertThat("With override permission sublabel should be empty", TextUtils.isEmpty(pg.getSubLabel())); } @Test public void getResolveInfoLabelAndSubLabel() throws Exception { ResolveInfoPresentationGetter pg; PackageManagerMockedInfo info; info = createPackageManagerMockedInfo(false); pg = new ResolveInfoPresentationGetter( info.ctx, 0, info.resolveInfo); assertThat("Label should match app label", pg.getLabel().equals( info.setAppLabel)); assertThat("Sublabel should match resolve info label if set", pg.getSubLabel().equals(info.setResolveInfoLabel)); info = createPackageManagerMockedInfo(true); pg = new ResolveInfoPresentationGetter( info.ctx, 0, info.resolveInfo); assertThat("With override permission label should match resolve info label if set", pg.getLabel().equals(info.setResolveInfoLabel)); assertThat("With override permission sublabel should be empty", TextUtils.isEmpty(pg.getSubLabel())); } private Intent createSendImageIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); Loading
core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java +86 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes