diff options
| -rw-r--r-- | config/hiddenapi-greylist.txt | 1 | ||||
| -rw-r--r-- | core/java/android/content/Intent.java | 6 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/ChooserActivity.java | 193 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/ResolverActivity.java | 24 | ||||
| -rw-r--r-- | core/res/res/layout/chooser_grid.xml | 67 | ||||
| -rw-r--r-- | core/res/res/values/dimens.xml | 3 | ||||
| -rw-r--r-- | core/res/res/values/strings.xml | 2 | ||||
| -rw-r--r-- | core/res/res/values/symbols.xml | 6 | ||||
| -rw-r--r-- | core/tests/coretests/Android.mk | 2 | ||||
| -rw-r--r-- | core/tests/coretests/AndroidManifest.xml | 1 | ||||
| -rw-r--r-- | core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java | 167 | ||||
| -rw-r--r-- | core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java | 13 |
12 files changed, 413 insertions, 72 deletions
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index 8e7a58b40d0c..a74895947450 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -1942,7 +1942,6 @@ Lcom/android/internal/R$id;->title:I Lcom/android/internal/R$id;->titleDivider:I Lcom/android/internal/R$id;->titleDividerTop:I Lcom/android/internal/R$id;->title_container:I -Lcom/android/internal/R$id;->title_icon:I Lcom/android/internal/R$id;->title_template:I Lcom/android/internal/R$id;->topPanel:I Lcom/android/internal/R$id;->up:I diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 65ea6356d160..a3021f371e19 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -971,7 +971,8 @@ public class Intent implements Parcelable, Cloneable { * * @param target The Intent that the user will be selecting an activity * to perform. - * @param title Optional title that will be displayed in the chooser. + * @param title Optional title that will be displayed in the chooser, + * only when the target action is not ACTION_SEND or ACTION_SEND_MULTIPLE. * @return Return a new Intent object that you can hand to * {@link Context#startActivity(Intent) Context.startActivity()} and * related methods. @@ -998,7 +999,8 @@ public class Intent implements Parcelable, Cloneable { * * @param target The Intent that the user will be selecting an activity * to perform. - * @param title Optional title that will be displayed in the chooser. + * @param title Optional title that will be displayed in the chooser, + * only when the target action is not ACTION_SEND or ACTION_SEND_MULTIPLE. * @param sender Optional IntentSender to be called when a choice is made. * @return Return a new Intent object that you can hand to * {@link Context#startActivity(Intent) Context.startActivity()} and diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 299801a1b8fc..46f42f7864c5 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -24,6 +24,7 @@ import android.app.prediction.AppPredictor; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.prediction.AppTargetId; +import android.content.ClipData; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -41,9 +42,13 @@ import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.database.DataSetObserver; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Path; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; @@ -62,7 +67,9 @@ import android.service.chooser.ChooserTargetService; import android.service.chooser.IChooserTargetResult; import android.service.chooser.IChooserTargetService; import android.text.TextUtils; +import android.util.AttributeSet; import android.util.Log; +import android.util.Size; import android.util.Slog; import android.view.LayoutInflater; import android.view.View; @@ -73,9 +80,11 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.AbsListView; import android.widget.BaseAdapter; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.Space; +import android.widget.TextView; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -85,6 +94,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.google.android.collect.Lists; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -262,16 +272,31 @@ public class ChooserActivity extends ResolverActivity { } mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS); - CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE); + + // Do not allow the title to be changed when sharing content + CharSequence title = null; + if (target != null) { + String targetAction = target.getAction(); + if (!(Intent.ACTION_SEND.equals(targetAction) || Intent.ACTION_SEND_MULTIPLE.equals( + targetAction))) { + title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE); + } else { + Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a" + + " preview title by using EXTRA_TITLE property of the wrapped" + + " EXTRA_INTENT."); + } + } + int defaultTitleRes = 0; if (title == null) { defaultTitleRes = com.android.internal.R.string.chooseActivity; } + Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS); Intent[] initialIntents = null; if (pa != null) { initialIntents = new Intent[pa.length]; - for (int i=0; i<pa.length; i++) { + for (int i = 0; i < pa.length; i++) { if (!(pa[i] instanceof Intent)) { Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]); finish(); @@ -364,6 +389,68 @@ public class ChooserActivity extends ResolverActivity { } } + /** + * Override method to add content preview area, specific to the chooser activity. + */ + @Override + public void setHeader() { + super.setHeader(); + + Intent targetIntent = getTargetIntent(); + if (targetIntent == null) { + return; + } + + ViewGroup contentPreviewLayout = findViewById(R.id.content_preview); + String action = targetIntent.getAction(); + if (!(Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action))) { + contentPreviewLayout.setVisibility(View.GONE); + return; + } + + showDefaultContentPreview(contentPreviewLayout, targetIntent); + } + + private void showDefaultContentPreview(final ViewGroup parentLayout, + final Intent targetIntent) { + CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT); + TextView previewTextView = findViewById(R.id.content_preview_text); + if (sharingText == null) { + previewTextView.setVisibility(View.GONE); + } else { + previewTextView.setText(sharingText); + } + + String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE); + TextView previewTitleView = findViewById(R.id.content_preview_title); + if (previewTitle == null) { + previewTitleView.setVisibility(View.GONE); + } else { + previewTitleView.setText(previewTitle); + } + + ClipData previewData = targetIntent.getClipData(); + Uri previewThumbnail = null; + if (previewData != null) { + if (previewData.getItemCount() > 0) { + ClipData.Item previewDataItem = previewData.getItemAt(0); + previewThumbnail = previewDataItem.getUri(); + } + } + + ImageView previewThumbnailView = findViewById(R.id.content_preview_thumbnail); + if (previewThumbnail == null) { + previewThumbnailView.setVisibility(View.GONE); + } else { + Bitmap bmp = loadThumbnail(previewThumbnail, new Size(200, 200)); + if (bmp == null) { + previewThumbnailView.setVisibility(View.GONE); + } else { + previewThumbnailView.setImageBitmap(bmp); + } + } + } + static SharedPreferences getPinnedSharedPrefs(Context context) { // The code below is because in the android:ui process, no one can hear you scream. // The package info in the context isn't initialized in the way it is for normal apps, @@ -630,15 +717,19 @@ public class ChooserActivity extends ResolverActivity { } } if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) { - if (DEBUG) Log.d(TAG, "queryTargets hit query target limit " - + QUERY_TARGET_SERVICE_LIMIT); + if (DEBUG) { + Log.d(TAG, "queryTargets hit query target limit " + + QUERY_TARGET_SERVICE_LIMIT); + } break; } } if (!mServiceConnections.isEmpty()) { - if (DEBUG) Log.d(TAG, "queryTargets setting watchdog timer for " - + WATCHDOG_TIMEOUT_MILLIS + "ms"); + if (DEBUG) { + Log.d(TAG, "queryTargets setting watchdog timer for " + + WATCHDOG_TIMEOUT_MILLIS + "ms"); + } mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT, WATCHDOG_TIMEOUT_MILLIS); } else { @@ -969,6 +1060,20 @@ public class ChooserActivity extends ResolverActivity { mLaunchedFromUid); } + @VisibleForTesting + protected Bitmap loadThumbnail(Uri uri, Size size) { + if (uri == null || size == null) { + return null; + } + + try { + return getContentResolver().loadThumbnail(uri, size, null); + } catch (IOException | NullPointerException ex) { + Log.w(TAG, "Error loading preview thumbnail for uri: " + uri.toString(), ex); + } + return null; + } + final class ChooserTargetInfo implements TargetInfo { private final DisplayResolveInfo mSourceInfo; private final ResolveInfo mBackupResolveInfo; @@ -1247,7 +1352,7 @@ public class ChooserActivity extends ResolverActivity { UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); if (ii instanceof LabeledIntent) { - LabeledIntent li = (LabeledIntent)ii; + LabeledIntent li = (LabeledIntent) ii; ri.resolvePackageName = li.getSourcePackage(); ri.labelRes = li.getLabelResource(); ri.nonLocalizedLabel = li.getNonLocalizedLabel(); @@ -1391,8 +1496,10 @@ public class ChooserActivity extends ResolverActivity { } public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) { - if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size() - + " targets"); + if (DEBUG) { + Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size() + + " targets"); + } if (mTargetsNeedPruning && targets.size() > 0) { // First proper update since we got an onListRebuilt() with (transient) 0 items. @@ -1492,8 +1599,9 @@ public class ChooserActivity extends ResolverActivity { public int getCount() { return (int) ( getCallerTargetRowCount() - + getServiceTargetRowCount() - + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount) + + getServiceTargetRowCount() + + Math.ceil( + (float) mChooserListAdapter.getStandardTargetCount() / mColumnCount) ); } @@ -1860,7 +1968,7 @@ public class ChooserActivity extends ResolverActivity { final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount(); int offset = 0; - for (int i = 0; i < chooserTargetRows; i++) { + for (int i = 0; i < chooserTargetRows; i++) { final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i; final int vt = mChooserRowAdapter.getItemViewType(pos); if (vt != mCachedViewType) { @@ -1882,4 +1990,65 @@ public class ChooserActivity extends ResolverActivity { mResolverDrawerLayout.setCollapsibleHeightReserved(offset); } } + + + /** + * Used internally to round image corners while obeying view padding. + */ + public static class RoundedRectImageView extends ImageView { + private int mRadius = 0; + private Path mPath = new Path(); + + public RoundedRectImageView(Context context) { + super(context); + } + + public RoundedRectImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius); + } + + private void updatePath(int width, int height) { + mPath.reset(); + + int imageWidth = width - getPaddingLeft() - getPaddingRight(); + int imageHeight = height - getPaddingTop() - getPaddingBottom(); + mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius, + mRadius, Path.Direction.CW); + } + + /** + * Sets the corner radius on all corners + * + * param radius 0 for no radius, > 0 for a visible corner radius + */ + public void setRadius(int radius) { + mRadius = radius; + updatePath(getWidth(), getHeight()); + } + + @Override + protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { + super.onSizeChanged(width, height, oldWidth, oldHeight); + updatePath(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (mRadius != 0) { + canvas.clipPath(mPath); + } + + super.onDraw(canvas); + } + } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index a50c736e195e..091991130391 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1044,7 +1044,10 @@ public class ResolverActivity extends Activity { } } - public void setTitleAndIcon() { + /** + * Configure the area above the app selection list (title, content preview, etc). + */ + public void setHeader() { if (mAdapter.getCount() == 0 && mAdapter.mPlaceholderCount == 0) { final TextView titleView = findViewById(R.id.title); if (titleView != null) { @@ -1062,23 +1065,6 @@ public class ResolverActivity extends Activity { titleView.setText(title); } setTitle(title); - - // Try to initialize the title icon if we have a view for it and a title to match - final ImageView titleIcon = findViewById(R.id.title_icon); - if (titleIcon != null) { - ApplicationInfo ai = null; - try { - if (!TextUtils.isEmpty(mReferrerPackage)) { - ai = mPm.getApplicationInfo(mReferrerPackage, 0); - } - } catch (NameNotFoundException e) { - Log.e(TAG, "Could not find referrer package " + mReferrerPackage); - } - - if (ai != null) { - titleIcon.setImageDrawable(ai.loadIcon(mPm)); - } - } } final ImageView iconView = findViewById(R.id.icon); @@ -1692,7 +1678,7 @@ public class ResolverActivity extends Activity { mPostListReadyRunnable = new Runnable() { @Override public void run() { - setTitleAndIcon(); + setHeader(); resetButtonBar(); onListRebuilt(); mPostListReadyRunnable = null; diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml index ded2b3569952..c42f43ac0df5 100644 --- a/core/res/res/layout/chooser_grid.xml +++ b/core/res/res/layout/chooser_grid.xml @@ -29,8 +29,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alwaysShow="true" - android:elevation="8dp" - android:paddingStart="16dp" android:background="?attr/colorBackgroundFloating" > <TextView android:id="@+id/profile_button" android:layout_width="wrap_content" @@ -46,25 +44,66 @@ android:layout_alignParentTop="true" android:layout_alignParentRight="true" android:singleLine="true"/> - <ImageView android:id="@+id/title_icon" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_marginEnd="16dp" - android:visibility="gone" - android:scaleType="fitCenter" - android:layout_below="@id/profile_button" - android:layout_alignParentLeft="true"/> + <TextView android:id="@+id/title" android:layout_height="wrap_content" android:layout_width="wrap_content" android:textAppearance="?attr/textAppearanceMedium" - android:textSize="14sp" - android:gravity="start|center_vertical" + android:textSize="20sp" + android:gravity="center" android:paddingEnd="?attr/dialogPreferredPadding" android:paddingTop="12dp" - android:paddingBottom="12dp" + android:paddingBottom="6dp" android:layout_below="@id/profile_button" - android:layout_toRightOf="@id/title_icon"/> + android:layout_centerHorizontal="true"/> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/content_preview" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/colorBackgroundFloating"> + + <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView" + android:id="@+id/content_preview_thumbnail" + android:layout_alignParentTop="true" + android:layout_width="100dp" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:gravity="center" + android:adjustViewBounds="true" + android:maxWidth="90dp" + android:maxHeight="90dp" + android:scaleType="fitCenter" + android:padding="5dp"/> + + <TextView + android:id="@+id/content_preview_title" + android:layout_alignParentTop="true" + android:layout_toEndOf="@id/content_preview_thumbnail" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start|top" + android:textAppearance="?attr/textAppearanceMedium" + android:maxLines="2" + android:ellipsize="end" + android:paddingStart="15dp" + android:paddingEnd="15dp" + android:paddingTop="10dp" /> + + <TextView + android:id="@+id/content_preview_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/content_preview_title" + android:layout_toEndOf="@id/content_preview_thumbnail" + android:gravity="start|top" + android:maxLines="2" + android:ellipsize="end" + android:paddingStart="15dp" + android:paddingEnd="15dp" + android:paddingTop="10dp" + android:paddingBottom="5dp"/> </RelativeLayout> <ListView diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index c8706838df57..ef3834cccc15 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -714,4 +714,7 @@ <dimen name="harmful_app_message_padding_bottom">24dp</dimen> <!-- Line spacing modifier for the message field of the harmful app dialog --> <item name="harmful_app_message_line_spacing_modifier" type="dimen">1.22</item> + + <!-- chooser corner radius --> + <dimen name="chooser_corner_radius">4dp</dimen> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a761baf95b0d..6adb4a711857 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3082,7 +3082,7 @@ <!-- Label for a link to a intent resolver dialog when selecting an editor application --> <string name="whichEditApplicationLabel">Edit</string> <!-- Title of intent resolver dialog when selecting a sharing application to run. --> - <string name="whichSendApplication">Share with</string> + <string name="whichSendApplication">Share</string> <!-- Title of intent resolver dialog when selecting a sharing application to run and a previously used application is known. --> <string name="whichSendApplicationNamed">Share with %1$s</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ca8e226a9911..54a3243dd562 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -50,6 +50,10 @@ <java-symbol type="id" name="characterPicker" /> <java-symbol type="id" name="clearDefaultHint" /> <java-symbol type="id" name="contentPanel" /> + <java-symbol type="id" name="content_preview" /> + <java-symbol type="id" name="content_preview_thumbnail" /> + <java-symbol type="id" name="content_preview_text" /> + <java-symbol type="id" name="content_preview_title" /> <java-symbol type="id" name="current_scene" /> <java-symbol type="id" name="scene_layoutid_cache" /> <java-symbol type="id" name="customPanel" /> @@ -2703,9 +2707,9 @@ <java-symbol type="layout" name="date_picker_month_item_material" /> <java-symbol type="id" name="month_view" /> <java-symbol type="integer" name="config_zen_repeat_callers_threshold" /> + <java-symbol type="dimen" name="chooser_corner_radius" /> <java-symbol type="layout" name="chooser_grid" /> <java-symbol type="layout" name="resolve_grid_item" /> - <java-symbol type="id" name="title_icon" /> <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" /> diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index 9d04e6392fd0..6a8105088b88 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -37,7 +37,9 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ mockwebserver \ guava \ androidx.test.runner \ + androidx.test.ext.junit \ androidx.test.rules \ + androidx.test.espresso.core \ mockito-target-minus-junit4 \ espresso-core \ ub-uiautomator \ diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index fffee903b3f0..268bb81e8736 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -62,6 +62,7 @@ <uses-permission android:name="android.permission.READ_LOGS"/> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_SMS"/> + <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/> <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index aaa624e6b25e..21fcae780b80 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -16,12 +16,12 @@ package com.android.internal.app; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; import static com.android.internal.app.ChooserWrapperActivity.sOverrides; @@ -33,12 +33,18 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.usage.UsageStatsManager; +import android.content.ClipData; import android.content.Intent; import android.content.pm.ResolveInfo; - -import androidx.test.InstrumentationRegistry; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.net.Uri; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; @@ -69,20 +75,34 @@ public class ChooserActivityTest { @Test public void customTitle() throws InterruptedException { - Intent sendIntent = createSendImageIntent(); + Intent viewIntent = createViewTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test")); + mActivityRule.launchActivity(Intent.createChooser(viewIntent, "chooser test")); + waitForIdle(); onView(withId(R.id.title)).check(matches(withText("chooser test"))); } @Test + public void customTitleIgnoredForSendIntents() throws InterruptedException { + Intent sendIntent = createSendTextIntent(); + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test")); + waitForIdle(); + onView(withId(R.id.title)).check(matches(withText(R.string.whichSendApplication))); + } + + @Test public void emptyTitle() throws InterruptedException { - Intent sendIntent = createSendImageIntent(); + Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), @@ -95,8 +115,72 @@ public class ChooserActivityTest { } @Test + public void emptyPreviewTitleAndThumbnail() throws InterruptedException { + Intent sendIntent = createSendTextIntentWithPreview(null, null); + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + onView(withId(R.id.content_preview_title)).check(matches(not(isDisplayed()))); + onView(withId(R.id.content_preview_thumbnail)).check(matches(not(isDisplayed()))); + } + + @Test + public void visiblePreviewTitleWithoutThumbnail() throws InterruptedException { + String previewTitle = "My Content Preview Title"; + Intent sendIntent = createSendTextIntentWithPreview(previewTitle, null); + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + onView(withId(R.id.content_preview_title)).check(matches(isDisplayed())); + onView(withId(R.id.content_preview_title)).check(matches(withText(previewTitle))); + onView(withId(R.id.content_preview_thumbnail)).check(matches(not(isDisplayed()))); + } + + @Test + public void visiblePreviewTitleWithInvalidThumbnail() throws InterruptedException { + String previewTitle = "My Content Preview Title"; + Intent sendIntent = createSendTextIntentWithPreview(previewTitle, + Uri.parse("tel:(+49)12345789")); + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + onView(withId(R.id.content_preview_title)).check(matches(isDisplayed())); + onView(withId(R.id.content_preview_thumbnail)).check(matches(not(isDisplayed()))); + } + + @Test + public void visiblePreviewTitleAndThumbnail() throws InterruptedException { + String previewTitle = "My Content Preview Title"; + Intent sendIntent = createSendTextIntentWithPreview(previewTitle, + Uri.parse("android.resource://com.android.frameworks.coretests/" + + com.android.frameworks.coretests.R.drawable.test320x240)); + sOverrides.previewThumbnail = createBitmap(); + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + onView(withId(R.id.content_preview_title)).check(matches(isDisplayed())); + onView(withId(R.id.content_preview_thumbnail)).check(matches(isDisplayed())); + } + + @Test public void twoOptionsAndUserSelectsOne() throws InterruptedException { - Intent sendIntent = createSendImageIntent(); + Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), @@ -125,7 +209,7 @@ public class ChooserActivityTest { @Test public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException { - Intent sendIntent = createSendImageIntent(); + Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), @@ -158,7 +242,7 @@ public class ChooserActivityTest { when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(null); - Intent sendIntent = createSendImageIntent(); + Intent sendIntent = createSendTextIntent(); final ChooserWrapperActivity activity = mActivityRule .launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); @@ -186,7 +270,7 @@ public class ChooserActivityTest { Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); + Intent sendIntent = createSendTextIntent(); final ChooserWrapperActivity activity = mActivityRule .launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); @@ -197,7 +281,7 @@ public class ChooserActivityTest { @Test public void hasOtherProfileOneOption() throws Exception { - Intent sendIntent = createSendImageIntent(); + Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(2); ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); @@ -234,7 +318,7 @@ public class ChooserActivityTest { @Test public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception { - Intent sendIntent = createSendImageIntent(); + Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3); ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); @@ -273,7 +357,7 @@ public class ChooserActivityTest { @Test public void hasLastChosenActivityAndOtherProfile() throws Exception { - Intent sendIntent = createSendImageIntent(); + Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3); ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); @@ -308,14 +392,33 @@ public class ChooserActivityTest { assertThat(chosen[0], is(toChoose)); } - private Intent createSendImageIntent() { + private Intent createSendTextIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); - sendIntent.setType("image/jpeg"); return sendIntent; } + private Intent createSendTextIntentWithPreview(String title, Uri imageThumbnail) { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); + sendIntent.putExtra(Intent.EXTRA_TITLE, title); + if (imageThumbnail != null) { + ClipData.Item clipItem = new ClipData.Item(imageThumbnail); + sendIntent.setClipData(new ClipData("Clip Label", new String[]{"image/png"}, clipItem)); + } + + return sendIntent; + } + + private Intent createViewTextIntent() { + Intent viewIntent = new Intent(); + viewIntent.setAction(Intent.ACTION_VIEW); + viewIntent.putExtra(Intent.EXTRA_TEXT, "testing intent viewing"); + return viewIntent; + } + private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) { List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); for (int i = 0; i < numberOfResults; i++) { @@ -340,4 +443,24 @@ public class ChooserActivityTest { private void waitForIdle() { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } -}
\ No newline at end of file + + private Bitmap createBitmap() { + int width = 200; + int height = 200; + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + Paint paint = new Paint(); + paint.setColor(Color.RED); + paint.setStyle(Paint.Style.FILL); + canvas.drawPaint(paint); + + paint.setColor(Color.WHITE); + paint.setAntiAlias(true); + paint.setTextSize(14.f); + paint.setTextAlign(Paint.Align.CENTER); + canvas.drawText("Hi!", (width / 2.f), (height / 2.f), paint); + + return bitmap; + } +} diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index 60529f6d4071..637d2eafc7af 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -21,6 +21,9 @@ import static org.mockito.Mockito.mock; import android.app.usage.UsageStatsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.net.Uri; +import android.util.Size; import java.util.function.Function; @@ -74,6 +77,14 @@ public class ChooserWrapperActivity extends ChooserActivity { return super.getPackageManager(); } + @Override + protected Bitmap loadThumbnail(Uri uri, Size size) { + if (sOverrides.previewThumbnail != null) { + return sOverrides.previewThumbnail; + } + return super.loadThumbnail(uri, size); + } + /** * We cannot directly mock the activity created since instrumentation creates it. * <p> @@ -85,11 +96,13 @@ public class ChooserWrapperActivity extends ChooserActivity { public Function<TargetInfo, Boolean> onSafelyStartCallback; public ResolverListController resolverListController; public Boolean isVoiceInteraction; + public Bitmap previewThumbnail; public void reset() { onSafelyStartCallback = null; isVoiceInteraction = null; createPackageManager = null; + previewThumbnail = null; resolverListController = mock(ResolverListController.class); } } |