summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Matt Pietal <mpietal@google.com> 2019-01-08 07:29:34 -0500
committer Matt Pietal <mpietal@google.com> 2019-01-29 14:04:23 -0500
commit26038402bd9d0c9eaba08ef1fba4b61531994781 (patch)
treec991e8af1586dd8d3104af3b6dde3640544b385f
parentf5e95eff500a03e52c165130ab5f41623d32fd8b (diff)
Sharesheet content preview - adding text support only
Adding initial support for a content preview section within the ChooserActivity. File/Image support is pending. Bug: 120419296 Test: 'atest ChooserActivityTest' as well as visual inspection of sharing from multiple google apps like Chrome Change-Id: Iee4746940fb8ddd4f0a54a0bf7ef485be2eab30d
-rw-r--r--config/hiddenapi-greylist.txt1
-rw-r--r--core/java/android/content/Intent.java6
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java193
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java24
-rw-r--r--core/res/res/layout/chooser_grid.xml67
-rw-r--r--core/res/res/values/dimens.xml3
-rw-r--r--core/res/res/values/strings.xml2
-rw-r--r--core/res/res/values/symbols.xml6
-rw-r--r--core/tests/coretests/Android.mk2
-rw-r--r--core/tests/coretests/AndroidManifest.xml1
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java167
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java13
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 22f73dbd4644..93c56fc8c228 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, &gt; 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 aefa9dfd1056..aacb9c56b1ba 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 86818c611c1f..86fb65a882bf 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);
}
}