blob: 80c821b0339371b676b898a955c02d4b4d1e95a9 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Adam Powelle7c74cc2016-01-28 16:42:27 +000017package com.android.internal.app;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080018
Matt Pietal0ea391b2019-01-30 10:44:15 -050019import static java.lang.annotation.RetentionPolicy.SOURCE;
20
Matt Pietalcdfcd9a2019-03-05 08:31:47 -050021import android.animation.Animator;
22import android.animation.AnimatorListenerAdapter;
23import android.animation.AnimatorSet;
24import android.animation.ObjectAnimator;
25import android.animation.ValueAnimator;
Matt Pietal0ea391b2019-01-30 10:44:15 -050026import android.annotation.IntDef;
George Hodulik145b3a52019-03-27 11:18:43 -070027import android.annotation.Nullable;
Artur Satayevfc46be72019-11-04 17:50:59 +000028import android.annotation.UnsupportedAppUsage;
Adam Powell0b3c1122014-10-09 12:50:14 -070029import android.app.Activity;
Ng Zhi And3ec5fc2018-05-10 09:13:00 -070030import android.app.ActivityManager;
George Hodulik69d4a082019-01-18 11:27:03 -080031import android.app.prediction.AppPredictionContext;
32import android.app.prediction.AppPredictionManager;
33import android.app.prediction.AppPredictor;
34import android.app.prediction.AppTarget;
George Hodulikf2b0d342019-01-25 12:43:54 -080035import android.app.prediction.AppTargetEvent;
Matt Pietal26038402019-01-08 07:29:34 -050036import android.content.ClipData;
Matt Pietal1fa7d802019-01-30 10:44:15 -050037import android.content.ClipboardManager;
Adam Powell0b3c1122014-10-09 12:50:14 -070038import android.content.ComponentName;
Matt Pietal0ea391b2019-01-30 10:44:15 -050039import android.content.ContentResolver;
Adam Powell24428412015-04-01 17:19:56 -070040import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.content.Intent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080042import android.content.IntentFilter;
Adam Powell0b3c1122014-10-09 12:50:14 -070043import android.content.IntentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -070044import android.content.IntentSender.SendIntentException;
Adam Powell24428412015-04-01 17:19:56 -070045import android.content.ServiceConnection;
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +000046import android.content.pm.ActivityInfo;
Matt Pietala4b30072019-04-04 13:44:36 -040047import android.content.pm.ApplicationInfo;
Adam Powell7d758002015-05-06 17:49:36 -070048import android.content.pm.LabeledIntent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080049import android.content.pm.LauncherApps;
Adam Powell24428412015-04-01 17:19:56 -070050import android.content.pm.PackageManager;
51import android.content.pm.PackageManager.NameNotFoundException;
52import android.content.pm.ResolveInfo;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080053import android.content.pm.ShortcutInfo;
54import android.content.pm.ShortcutManager;
Matt Pietal18bbd822019-02-12 15:21:36 -050055import android.content.res.Configuration;
Matt Pietal46d828c2019-02-05 08:07:07 -050056import android.database.Cursor;
Adam Powell7d758002015-05-06 17:49:36 -070057import android.database.DataSetObserver;
Matt Pietal26038402019-01-08 07:29:34 -050058import android.graphics.Bitmap;
59import android.graphics.Canvas;
Adam Powell63b31692015-09-28 10:45:00 -070060import android.graphics.Color;
Matt Pietal0ea391b2019-01-30 10:44:15 -050061import android.graphics.Paint;
Matt Pietal26038402019-01-08 07:29:34 -050062import android.graphics.Path;
Mike Digmanac1d88c2019-04-18 15:15:55 -070063import android.graphics.drawable.AnimatedVectorDrawable;
Mike Digman9c4ae502019-03-19 17:02:25 -070064import android.graphics.drawable.BitmapDrawable;
Adam Powell24428412015-04-01 17:19:56 -070065import android.graphics.drawable.Drawable;
Adam Powell13036be2015-05-12 14:43:56 -070066import android.graphics.drawable.Icon;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -050067import android.metrics.LogMaker;
Matt Pietal26038402019-01-08 07:29:34 -050068import android.net.Uri;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080069import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070import android.os.Bundle;
Adam Powell24428412015-04-01 17:19:56 -070071import android.os.Handler;
72import android.os.IBinder;
73import android.os.Message;
Dianne Hackborneb034652009-09-07 00:49:58 -070074import android.os.Parcelable;
Adam Powell52c39212016-04-07 15:14:18 -070075import android.os.Process;
Adam Powell24428412015-04-01 17:19:56 -070076import android.os.RemoteException;
Adam Powell2ed547e2015-04-29 18:45:04 -070077import android.os.ResultReceiver;
Adam Powell24428412015-04-01 17:19:56 -070078import android.os.UserHandle;
Adam Powell7d758002015-05-06 17:49:36 -070079import android.os.UserManager;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -040080import android.provider.DeviceConfig;
Matt Pietal46d828c2019-02-05 08:07:07 -050081import android.provider.DocumentsContract;
Matt Pietalf38e9d22019-02-15 10:01:03 -050082import android.provider.Downloads;
Matt Pietal46d828c2019-02-05 08:07:07 -050083import android.provider.OpenableColumns;
Adam Powell24428412015-04-01 17:19:56 -070084import android.service.chooser.ChooserTarget;
85import android.service.chooser.ChooserTargetService;
86import android.service.chooser.IChooserTargetResult;
87import android.service.chooser.IChooserTargetService;
Matt Pietal9d501432019-04-12 10:05:29 -040088import android.text.SpannableStringBuilder;
Adam Powell24428412015-04-01 17:19:56 -070089import android.text.TextUtils;
Matt Pietal26038402019-01-08 07:29:34 -050090import android.util.AttributeSet;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -040091import android.util.HashedStringCache;
Dianne Hackborneb034652009-09-07 00:49:58 -070092import android.util.Log;
Matt Pietal26038402019-01-08 07:29:34 -050093import android.util.Size;
Adam Powell0b3c1122014-10-09 12:50:14 -070094import android.util.Slog;
Adam Powell7d758002015-05-06 17:49:36 -070095import android.view.LayoutInflater;
Adam Powell24428412015-04-01 17:19:56 -070096import android.view.View;
Adam Powell63b31692015-09-28 10:45:00 -070097import android.view.View.MeasureSpec;
Adam Powell7d758002015-05-06 17:49:36 -070098import android.view.View.OnClickListener;
Adam Powell98b7f892015-06-19 12:38:45 -070099import android.view.View.OnLongClickListener;
Adam Powell24428412015-04-01 17:19:56 -0700100import android.view.ViewGroup;
Adam Powell63b31692015-09-28 10:45:00 -0700101import android.view.ViewGroup.LayoutParams;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500102import android.view.animation.AccelerateInterpolator;
103import android.view.animation.DecelerateInterpolator;
Adam Powell7d758002015-05-06 17:49:36 -0700104import android.widget.AbsListView;
105import android.widget.BaseAdapter;
Matt Pietal26038402019-01-08 07:29:34 -0500106import android.widget.ImageView;
Adam Powell7d758002015-05-06 17:49:36 -0700107import android.widget.ListView;
Matt Pietal26038402019-01-08 07:29:34 -0500108import android.widget.TextView;
Matt Pietal1fa7d802019-01-30 10:44:15 -0500109import android.widget.Toast;
Jason Monk027dcfa2017-06-27 18:37:35 -0400110
Adam Powell7d758002015-05-06 17:49:36 -0700111import com.android.internal.R;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800112import com.android.internal.annotations.VisibleForTesting;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400113import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
Matt Pietalab73a882019-06-05 07:04:55 -0400114import com.android.internal.content.PackageMonitor;
Adam Powell98b7f892015-06-19 12:38:45 -0700115import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +0100116import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500117import com.android.internal.util.ImageUtils;
Mike Digman849a9d12019-04-29 11:20:48 -0700118import com.android.internal.widget.ResolverDrawerLayout;
Alison Cichowlas3e340502018-08-07 17:15:01 -0400119
Adam Powell52c39212016-04-07 15:14:18 -0700120import com.google.android.collect.Lists;
Adam Powell24428412015-04-01 17:19:56 -0700121
Matt Pietal26038402019-01-08 07:29:34 -0500122import java.io.IOException;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500123import java.lang.annotation.Retention;
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400124import java.text.Collator;
Adam Powell24428412015-04-01 17:19:56 -0700125import java.util.ArrayList;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800126import java.util.Arrays;
Adam Powella182e452015-07-06 16:57:56 -0700127import java.util.Collections;
128import java.util.Comparator;
George Hodulikaa5238c2019-04-18 14:17:51 -0700129import java.util.HashMap;
Matt Pietalab73a882019-06-05 07:04:55 -0400130import java.util.HashSet;
Adam Powell24428412015-04-01 17:19:56 -0700131import java.util.List;
George Hodulikaa5238c2019-04-18 14:17:51 -0700132import java.util.Map;
Matt Pietalab73a882019-06-05 07:04:55 -0400133import java.util.Set;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400135/**
136 * The Chooser Activity handles intent resolution specifically for sharing intents -
137 * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
138 *
139 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140public class ChooserActivity extends ResolverActivity {
Adam Powell0b3c1122014-10-09 12:50:14 -0700141 private static final String TAG = "ChooserActivity";
142
Artur Satayevfc46be72019-11-04 17:50:59 +0000143 @UnsupportedAppUsage
144 public ChooserActivity() {
145 }
George Hodulik145b3a52019-03-27 11:18:43 -0700146
Jorim Jaggif631ef72017-02-24 13:49:47 +0100147 /**
148 * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
149 * in onStop when launched in a new task. If this extra is set to true, we do not finish
150 * ourselves when onStop gets called.
151 */
152 public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
153 = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
154
Mike Digman849a9d12019-04-29 11:20:48 -0700155 private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
156
Mehdi Alizadeh3c335a22019-01-17 16:03:19 -0800157 private static final boolean DEBUG = false;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800158
George Hodulik69d4a082019-01-18 11:27:03 -0800159 /**
160 * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
161 * {@link AppPredictionManager} will be queried for direct share targets.
162 */
163 // TODO(b/123089490): Replace with system flag
George Hodulik1428beb2019-05-01 17:04:23 -0700164 private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = true;
165 private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
George Hodulik69d4a082019-01-18 11:27:03 -0800166 // TODO(b/123088566) Share these in a better way.
167 private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
George Hodulikf2b0d342019-01-25 12:43:54 -0800168 public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
George Hodulik69d4a082019-01-18 11:27:03 -0800169 private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
170 public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
171 private AppPredictor mAppPredictor;
172 private AppPredictor.Callback mAppPredictorCallback;
George Hodulikaa5238c2019-04-18 14:17:51 -0700173 private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
George Hodulik69d4a082019-01-18 11:27:03 -0800174
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800175 /**
176 * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
177 * binding to every ChooserTargetService implementation.
178 */
179 // TODO(b/121287573): Replace with a system flag (setprop?)
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800180 private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
181 private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
182
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500183 /**
184 * The transition time between placeholders for direct share to a message
185 * indicating that non are available.
186 */
187 private static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200;
188
Matt Pietal394ebd02019-05-03 07:36:21 -0400189 private static final float DIRECT_SHARE_EXPANSION_RATE = 0.78f;
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400190
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800191 // TODO(b/121287224): Re-evaluate this limit
192 private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
Adam Powell24428412015-04-01 17:19:56 -0700193
Adam Powell2ed547e2015-04-29 18:45:04 -0700194 private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
Adam Powell24428412015-04-01 17:19:56 -0700195
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400196 private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7;
197 private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
198 SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
199 DEFAULT_SALT_EXPIRATION_DAYS);
200
Adam Powelle49d9392014-07-17 18:45:19 -0700201 private Bundle mReplacementExtras;
Adam Powell0b3c1122014-10-09 12:50:14 -0700202 private IntentSender mChosenComponentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -0700203 private IntentSender mRefinementIntentSender;
204 private RefinementResultReceiver mRefinementResultReceiver;
Adam Powell52c39212016-04-07 15:14:18 -0700205 private ChooserTarget[] mCallerChooserTargets;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800206 private ComponentName[] mFilteredComponentNames;
Adam Powelle49d9392014-07-17 18:45:19 -0700207
Adam Powell13036be2015-05-12 14:43:56 -0700208 private Intent mReferrerFillInIntent;
209
Kang Li9082f5b2016-12-02 10:56:21 -0800210 private long mChooserShownTime;
Kang Li64b018e2017-01-05 17:30:06 -0800211 protected boolean mIsSuccessfullySelected;
Kang Li9082f5b2016-12-02 10:56:21 -0800212
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700213 private long mQueriedTargetServicesTimeMs;
214 private long mQueriedSharingShortcutsTimeMs;
215
Adam Powell7d758002015-05-06 17:49:36 -0700216 private ChooserListAdapter mChooserListAdapter;
Adam Powell63b31692015-09-28 10:45:00 -0700217 private ChooserRowAdapter mChooserRowAdapter;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500218 private int mChooserRowServiceSpacing;
Adam Powell24428412015-04-01 17:19:56 -0700219
Matt Pietalab73a882019-06-05 07:04:55 -0400220 private int mCurrAvailableWidth = 0;
221
Matt Pietalfbfa0492019-04-01 11:29:56 -0400222 /** {@link ChooserActivity#getBaseScore} */
Adam Powell52c39212016-04-07 15:14:18 -0700223 private static final float CALLER_TARGET_SCORE_BOOST = 900.f;
Matt Pietalfbfa0492019-04-01 11:29:56 -0400224 /** {@link ChooserActivity#getBaseScore} */
Matt Pietal39d181d2019-05-08 14:48:30 -0400225 private static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
Adam Powell23882512016-01-29 10:21:00 -0800226 private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400227 // TODO: Update to handle landscape instead of using static value
228 private static final int MAX_RANKED_TARGETS = 4;
Adam Powell23882512016-01-29 10:21:00 -0800229
Adam Powell24428412015-04-01 17:19:56 -0700230 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
Matt Pietalab73a882019-06-05 07:04:55 -0400231 private final Set<ComponentName> mServicesRequested = new HashSet<>();
Matt Pietalaf044ae2019-03-29 06:53:53 -0400232
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -0400233 private static final int MAX_LOG_RANK_POSITION = 12;
234
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400235 @VisibleForTesting
236 public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
Matt Pietalaf044ae2019-03-29 06:53:53 -0400237
Matt Pietal4e2e3632019-04-05 08:32:47 -0400238 private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400239 private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
Matt Pietal4e2e3632019-04-05 08:32:47 -0400240
Matt Pietalaf044ae2019-03-29 06:53:53 -0400241 private boolean mListViewDataChanged = false;
Adam Powell24428412015-04-01 17:19:56 -0700242
Matt Pietal0ea391b2019-01-30 10:44:15 -0500243 @Retention(SOURCE)
244 @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
245 private @interface ContentPreviewType {
246 }
247
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500248 // Starting at 1 since 0 is considered "undefined" for some of the database transformations
249 // of tron logs.
250 private static final int CONTENT_PREVIEW_IMAGE = 1;
251 private static final int CONTENT_PREVIEW_FILE = 2;
252 private static final int CONTENT_PREVIEW_TEXT = 3;
253 protected MetricsLogger mMetricsLogger;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500254
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400255 // Sorted list of DisplayResolveInfos for the alphabetical app section.
256 private List<ResolverActivity.DisplayResolveInfo> mSortedList = new ArrayList<>();
257
Matt Pietale7cacab2019-05-23 07:21:36 -0400258 private ContentPreviewCoordinator mPreviewCoord;
259
260 private class ContentPreviewCoordinator {
Matt Pietale7cacab2019-05-23 07:21:36 -0400261 private static final int IMAGE_FADE_IN_MILLIS = 150;
262 private static final int IMAGE_LOAD_TIMEOUT = 1;
263 private static final int IMAGE_LOAD_INTO_VIEW = 2;
264
Matt Pietalab73a882019-06-05 07:04:55 -0400265 private final int mImageLoadTimeoutMillis =
266 getResources().getInteger(R.integer.config_shortAnimTime);
267
Matt Pietale7cacab2019-05-23 07:21:36 -0400268 private final View mParentView;
269 private boolean mHideParentOnFail;
270 private boolean mAtLeastOneLoaded = false;
271
272 class LoadUriTask {
273 public final Uri mUri;
274 public final int mImageResourceId;
275 public final int mExtraCount;
276 public final Bitmap mBmp;
277
278 LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) {
279 this.mImageResourceId = imageResourceId;
280 this.mUri = uri;
281 this.mExtraCount = extraCount;
282 this.mBmp = bmp;
283 }
284 }
285
286 // If at least one image loads within the timeout period, allow other
287 // loads to continue. Otherwise terminate and optionally hide
288 // the parent area
289 private final Handler mHandler = new Handler() {
290 @Override
291 public void handleMessage(Message msg) {
292 switch (msg.what) {
293 case IMAGE_LOAD_TIMEOUT:
294 maybeHideContentPreview();
295 break;
296
297 case IMAGE_LOAD_INTO_VIEW:
298 if (isFinishing()) break;
299
300 LoadUriTask task = (LoadUriTask) msg.obj;
301 RoundedRectImageView imageView = mParentView.findViewById(
302 task.mImageResourceId);
303 if (task.mBmp == null) {
304 imageView.setVisibility(View.GONE);
305 maybeHideContentPreview();
306 return;
307 }
308
309 mAtLeastOneLoaded = true;
310 imageView.setVisibility(View.VISIBLE);
311 imageView.setAlpha(0.0f);
312 imageView.setImageBitmap(task.mBmp);
313
314 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f,
315 1.0f);
316 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
317 fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS);
318 fadeAnim.start();
319
320 if (task.mExtraCount > 0) {
321 imageView.setExtraImageCount(task.mExtraCount);
322 }
323 }
324 }
325 };
326
327 ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) {
328 super();
329
330 this.mParentView = parentView;
331 this.mHideParentOnFail = hideParentOnFail;
332 }
333
334 private void loadUriIntoView(final int imageResourceId, final Uri uri,
335 final int extraImages) {
Matt Pietalab73a882019-06-05 07:04:55 -0400336 mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, mImageLoadTimeoutMillis);
Matt Pietale7cacab2019-05-23 07:21:36 -0400337
338 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
339 final Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
340 final Message msg = Message.obtain();
341 msg.what = IMAGE_LOAD_INTO_VIEW;
342 msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
343 mHandler.sendMessage(msg);
344 });
345 }
346
347 private void cancelLoads() {
348 mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW);
349 mHandler.removeMessages(IMAGE_LOAD_TIMEOUT);
350 }
351
352 private void maybeHideContentPreview() {
353 if (!mAtLeastOneLoaded && mHideParentOnFail) {
354 Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
Matt Pietalab73a882019-06-05 07:04:55 -0400355 + " within " + mImageLoadTimeoutMillis + "ms.");
Matt Pietale7cacab2019-05-23 07:21:36 -0400356 collapseParentView();
357 if (mChooserRowAdapter != null) {
358 mChooserRowAdapter.hideContentPreview();
359 }
360 mHideParentOnFail = false;
361 }
362 }
363
364 private void collapseParentView() {
365 // This will effectively hide the content preview row by forcing the height
366 // to zero. It is faster than forcing a relayout of the listview
367 final View v = mParentView;
368 int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY);
369 int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
370 v.measure(widthSpec, heightSpec);
371 v.getLayoutParams().height = 0;
372 v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop());
373 v.invalidate();
374 }
375 }
376
Matt Pietalab73a882019-06-05 07:04:55 -0400377 private final ChooserHandler mChooserHandler = new ChooserHandler();
378
379 private class ChooserHandler extends Handler {
380 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
381 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT = 2;
382 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT = 3;
383 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
384 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
385 private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
386
387 private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
388 private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;
389
390 private boolean mMinTimeoutPassed = false;
391
392 private void removeAllMessages() {
393 removeMessages(LIST_VIEW_UPDATE_MESSAGE);
394 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
395 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
396 removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
397 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
398 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
399 }
400
401 private void restartServiceRequestTimer() {
402 mMinTimeoutPassed = false;
403 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
404 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
405
406 if (DEBUG) {
407 Log.d(TAG, "queryTargets setting watchdog timer for "
408 + WATCHDOG_TIMEOUT_MIN_MILLIS + "-"
409 + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
410 }
411
412 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
413 WATCHDOG_TIMEOUT_MIN_MILLIS);
414 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
415 WATCHDOG_TIMEOUT_MAX_MILLIS);
416 }
417
418 private void maybeStopServiceRequestTimer() {
419 // Set a minimum timeout threshold, to ensure both apis, sharing shortcuts
420 // and older-style direct share services, have had time to load, otherwise
421 // just checking mServiceConnections could force us to end prematurely
422 if (mMinTimeoutPassed && mServiceConnections.isEmpty()) {
423 logDirectShareTargetReceived(
424 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
425 sendVoiceChoicesIfNeeded();
426 mChooserListAdapter.completeServiceTargetLoading();
427 }
428 }
429
Adam Powell24428412015-04-01 17:19:56 -0700430 @Override
431 public void handleMessage(Message msg) {
Matt Pietalaf044ae2019-03-29 06:53:53 -0400432 if (mChooserListAdapter == null || isDestroyed()) {
433 return;
434 }
435
Adam Powell24428412015-04-01 17:19:56 -0700436 switch (msg.what) {
437 case CHOOSER_TARGET_SERVICE_RESULT:
438 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
Adam Powell24428412015-04-01 17:19:56 -0700439 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
440 if (!mServiceConnections.contains(sri.connection)) {
441 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
442 + " returned after being removed from active connections."
443 + " Have you considered returning results faster?");
444 break;
445 }
Adam Powella182e452015-07-06 16:57:56 -0700446 if (sri.resultTargets != null) {
447 mChooserListAdapter.addServiceResults(sri.originalTarget,
Matt Pietalfbfa0492019-04-01 11:29:56 -0400448 sri.resultTargets, false);
Adam Powella182e452015-07-06 16:57:56 -0700449 }
Adam Powell24428412015-04-01 17:19:56 -0700450 unbindService(sri.connection);
Adam Powell9761ab22015-09-08 17:01:49 -0700451 sri.connection.destroy();
Adam Powell24428412015-04-01 17:19:56 -0700452 mServiceConnections.remove(sri.connection);
Matt Pietalab73a882019-06-05 07:04:55 -0400453 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700454 break;
455
Matt Pietalab73a882019-06-05 07:04:55 -0400456 case CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT:
457 mMinTimeoutPassed = true;
458 maybeStopServiceRequestTimer();
459 break;
Matt Pietalaf044ae2019-03-29 06:53:53 -0400460
Matt Pietalab73a882019-06-05 07:04:55 -0400461 case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
Adam Powell24428412015-04-01 17:19:56 -0700462 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -0400463 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700464 break;
465
Matt Pietalaf044ae2019-03-29 06:53:53 -0400466 case LIST_VIEW_UPDATE_MESSAGE:
467 if (DEBUG) {
468 Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
469 }
470
471 mChooserListAdapter.refreshListView();
472 break;
473
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800474 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
475 if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800476 final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
477 if (resultInfo.resultTargets != null) {
478 mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
Matt Pietalfbfa0492019-04-01 11:29:56 -0400479 resultInfo.resultTargets, true);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800480 }
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800481 break;
482
483 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700484 logDirectShareTargetReceived(
485 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800486 sendVoiceChoicesIfNeeded();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800487 break;
488
Adam Powell24428412015-04-01 17:19:56 -0700489 default:
490 super.handleMessage(msg);
491 }
492 }
493 };
494
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 @Override
496 protected void onCreate(Bundle savedInstanceState) {
Kang Li9082f5b2016-12-02 10:56:21 -0800497 final long intentReceivedTime = System.currentTimeMillis();
498 mIsSuccessfullySelected = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499 Intent intent = getIntent();
Dianne Hackborneb034652009-09-07 00:49:58 -0700500 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
501 if (!(targetParcelable instanceof Intent)) {
Christopher Tate9d6376a2014-02-12 13:14:10 -0800502 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
Dianne Hackborneb034652009-09-07 00:49:58 -0700503 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800504 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700505 return;
506 }
Adam Powell24428412015-04-01 17:19:56 -0700507 Intent target = (Intent) targetParcelable;
Craig Mautner411d2aed2014-05-08 09:07:43 -0700508 if (target != null) {
Adam Powelle49d9392014-07-17 18:45:19 -0700509 modifyTargetIntent(target);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700510 }
Adam Powell2ed547e2015-04-29 18:45:04 -0700511 Parcelable[] targetsParcelable
512 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
513 if (targetsParcelable != null) {
514 final boolean offset = target == null;
515 Intent[] additionalTargets =
516 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
517 for (int i = 0; i < targetsParcelable.length; i++) {
518 if (!(targetsParcelable[i] instanceof Intent)) {
519 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
520 + targetsParcelable[i]);
521 finish();
522 super.onCreate(null);
523 return;
524 }
525 final Intent additionalTarget = (Intent) targetsParcelable[i];
526 if (i == 0 && target == null) {
527 target = additionalTarget;
528 modifyTargetIntent(target);
529 } else {
530 additionalTargets[offset ? i - 1 : i] = additionalTarget;
531 modifyTargetIntent(additionalTarget);
532 }
533 }
534 setAdditionalTargets(additionalTargets);
535 }
536
Adam Powelle49d9392014-07-17 18:45:19 -0700537 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
Matt Pietal26038402019-01-08 07:29:34 -0500538
539 // Do not allow the title to be changed when sharing content
540 CharSequence title = null;
541 if (target != null) {
Matt Pietal95574b02019-03-13 08:12:25 -0400542 if (!isSendAction(target)) {
Matt Pietal26038402019-01-08 07:29:34 -0500543 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
544 } else {
545 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
546 + " preview title by using EXTRA_TITLE property of the wrapped"
547 + " EXTRA_INTENT.");
548 }
549 }
550
Adam Powell278902c2014-07-12 18:33:22 -0700551 int defaultTitleRes = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800552 if (title == null) {
Adam Powell278902c2014-07-12 18:33:22 -0700553 defaultTitleRes = com.android.internal.R.string.chooseActivity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554 }
Matt Pietal26038402019-01-08 07:29:34 -0500555
Dianne Hackborneb034652009-09-07 00:49:58 -0700556 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
557 Intent[] initialIntents = null;
558 if (pa != null) {
Matt Pietal4e2e3632019-04-05 08:32:47 -0400559 int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS);
560 initialIntents = new Intent[count];
561 for (int i = 0; i < count; i++) {
Dianne Hackborneb034652009-09-07 00:49:58 -0700562 if (!(pa[i] instanceof Intent)) {
Adam Powell2ed547e2015-04-29 18:45:04 -0700563 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
Dianne Hackborneb034652009-09-07 00:49:58 -0700564 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800565 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700566 return;
567 }
Craig Mautner411d2aed2014-05-08 09:07:43 -0700568 final Intent in = (Intent) pa[i];
Adam Powelle49d9392014-07-17 18:45:19 -0700569 modifyTargetIntent(in);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700570 initialIntents[i] = in;
Dianne Hackborneb034652009-09-07 00:49:58 -0700571 }
572 }
Adam Powell24428412015-04-01 17:19:56 -0700573
Adam Powell13036be2015-05-12 14:43:56 -0700574 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
575
Adam Powell0b3c1122014-10-09 12:50:14 -0700576 mChosenComponentSender = intent.getParcelableExtra(
577 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
Adam Powell2ed547e2015-04-29 18:45:04 -0700578 mRefinementIntentSender = intent.getParcelableExtra(
579 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
Dianne Hackborn028ceeb2014-08-17 17:45:48 -0700580 setSafeForwardingMode(true);
Adam Powell23882512016-01-29 10:21:00 -0800581
Adam Powell52c39212016-04-07 15:14:18 -0700582 pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
583 if (pa != null) {
584 ComponentName[] names = new ComponentName[pa.length];
585 for (int i = 0; i < pa.length; i++) {
586 if (!(pa[i] instanceof ComponentName)) {
587 Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
588 names = null;
589 break;
590 }
591 names[i] = (ComponentName) pa[i];
592 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800593 mFilteredComponentNames = names;
Adam Powell52c39212016-04-07 15:14:18 -0700594 }
595
596 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
597 if (pa != null) {
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400598 int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS);
599 ChooserTarget[] targets = new ChooserTarget[count];
600 for (int i = 0; i < count; i++) {
Adam Powell52c39212016-04-07 15:14:18 -0700601 if (!(pa[i] instanceof ChooserTarget)) {
602 Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
603 targets = null;
604 break;
605 }
606 targets[i] = (ChooserTarget) pa[i];
607 }
608 mCallerChooserTargets = targets;
609 }
610
Jorim Jaggif631ef72017-02-24 13:49:47 +0100611 setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
Adam Powell278902c2014-07-12 18:33:22 -0700612 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
613 null, false);
Adam Powell98b7f892015-06-19 12:38:45 -0700614
Kang Li9082f5b2016-12-02 10:56:21 -0800615 mChooserShownTime = System.currentTimeMillis();
616 final long systemCost = mChooserShownTime - intentReceivedTime;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500617
618 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500619 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
620 MetricsEvent.PARENT_PROFILE)
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500621 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
622 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
George Hodulik69d4a082019-01-18 11:27:03 -0800623
George Hodulik145b3a52019-03-27 11:18:43 -0700624 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
625 if (appPredictor != null) {
George Hodulikaa5238c2019-04-18 14:17:51 -0700626 mDirectShareAppTargetCache = new HashMap<>();
George Hodulik69d4a082019-01-18 11:27:03 -0800627 mAppPredictorCallback = resultList -> {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500628 if (isFinishing() || isDestroyed()) {
629 return;
630 }
George Hodulik0dd5fbe2019-03-06 12:00:26 -0800631 // May be null if there are no apps to perform share/open action.
632 if (mChooserListAdapter == null) {
633 return;
634 }
George Hodulik3f399f22019-04-26 16:17:54 -0700635 if (resultList.isEmpty()) {
636 // APS may be disabled, so try querying targets ourselves.
637 queryDirectShareTargets(mChooserListAdapter, true);
638 return;
639 }
George Hodulik69d4a082019-01-18 11:27:03 -0800640 final List<DisplayResolveInfo> driList =
641 getDisplayResolveInfos(mChooserListAdapter);
642 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
643 new ArrayList<>();
644 for (AppTarget appTarget : resultList) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500645 if (appTarget.getShortcutInfo() == null) {
646 continue;
647 }
George Hodulik69d4a082019-01-18 11:27:03 -0800648 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
649 appTarget.getShortcutInfo(),
650 new ComponentName(
651 appTarget.getPackageName(), appTarget.getClassName())));
652 }
George Hodulikaa5238c2019-04-18 14:17:51 -0700653 sendShareShortcutInfoList(shareShortcutInfos, driList, resultList);
George Hodulik69d4a082019-01-18 11:27:03 -0800654 };
George Hodulik145b3a52019-03-27 11:18:43 -0700655 appPredictor
656 .registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
George Hodulik69d4a082019-01-18 11:27:03 -0800657 }
658
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500659 mChooserRowServiceSpacing = getResources()
660 .getDimensionPixelSize(R.dimen.chooser_service_spacing);
661
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400662 if (mResolverDrawerLayout != null) {
663 mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
664
665 // expand/shrink direct share 4 -> 8 viewgroup
666 if (isSendAction(target)) {
667 mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
668 }
Matt Pietalb1d629d2019-04-23 11:35:53 -0400669
670 final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
671 final float defaultElevation = chooserHeader.getElevation();
672 final float chooserHeaderScrollElevation =
673 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
674
675 mAdapterView.setOnScrollListener(new AbsListView.OnScrollListener() {
676 public void onScrollStateChanged(AbsListView view, int scrollState) {
677 }
678
679 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
680 int totalItemCount) {
681 if (view.getChildCount() > 0) {
682 if (firstVisibleItem > 0 || view.getChildAt(0).getTop() < 0) {
683 chooserHeader.setElevation(chooserHeaderScrollElevation);
684 return;
685 }
686 }
687
688 chooserHeader.setElevation(defaultElevation);
689 }
690 });
Mike Digman849a9d12019-04-29 11:20:48 -0700691
692 mResolverDrawerLayout.setOnCollapsedChangedListener(
693 new ResolverDrawerLayout.OnCollapsedChangedListener() {
694
695 // Only consider one expansion per activity creation
696 private boolean mWrittenOnce = false;
697
698 @Override
699 public void onCollapsedChanged(boolean isCollapsed) {
700 if (!isCollapsed && !mWrittenOnce) {
701 incrementNumSheetExpansions();
702 mWrittenOnce = true;
703 }
704 }
705 });
Matt Pietal5b648562019-03-12 07:40:26 -0400706 }
707
Kang Li9082f5b2016-12-02 10:56:21 -0800708 if (DEBUG) {
709 Log.d(TAG, "System Time Cost is " + systemCost);
710 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800711 }
Adam Powelle49d9392014-07-17 18:45:19 -0700712
Matt Pietal26038402019-01-08 07:29:34 -0500713 /**
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500714 * Check if the profile currently used is a work profile.
715 * @return true if it is work profile, false if it is parent profile (or no work profile is
716 * set up)
717 */
718 protected boolean isWorkProfile() {
719 return ((UserManager) getSystemService(Context.USER_SERVICE))
720 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
721 }
722
Matt Pietalab73a882019-06-05 07:04:55 -0400723 @Override
724 protected PackageMonitor createPackageMonitor() {
725 return new PackageMonitor() {
726 @Override
727 public void onSomePackagesChanged() {
728 mAdapter.handlePackagesChanged();
729 bindProfileView();
730 }
731 };
732 }
733
Matt Pietal46d828c2019-02-05 08:07:07 -0500734 private void onCopyButtonClicked(View v) {
735 Intent targetIntent = getTargetIntent();
736 if (targetIntent == null) {
737 finish();
738 } else {
739 final String action = targetIntent.getAction();
740
741 ClipData clipData = null;
742 if (Intent.ACTION_SEND.equals(action)) {
743 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
744 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
745
746 if (extraText != null) {
747 clipData = ClipData.newPlainText(null, extraText);
748 } else if (extraStream != null) {
749 clipData = ClipData.newUri(getContentResolver(), null, extraStream);
750 } else {
751 Log.w(TAG, "No data available to copy to clipboard");
752 return;
753 }
754 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
755 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
756 Intent.EXTRA_STREAM);
757 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
758 for (int i = 1; i < streams.size(); i++) {
759 clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
760 }
761 } else {
762 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
763 // so warn about unexpected action
764 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
765 return;
766 }
767
768 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
769 Context.CLIPBOARD_SERVICE);
770 clipboardManager.setPrimaryClip(clipData);
771 Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
772
773 finish();
774 }
775 }
776
Matt Pietal18bbd822019-02-12 15:21:36 -0500777 @Override
778 public void onConfigurationChanged(Configuration newConfig) {
779 super.onConfigurationChanged(newConfig);
780
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400781 adjustPreviewWidth(newConfig.orientation, null);
782 }
783
784 private boolean shouldDisplayLandscape(int orientation) {
785 // Sharesheet fixes the # of items per row and therefore can not correctly lay out
786 // when in the restricted size of multi-window mode. In the future, would be nice
787 // to use minimum dp size requirements instead
788 return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
789 }
790
791 private void adjustPreviewWidth(int orientation, View parent) {
Matt Pietal18bbd822019-02-12 15:21:36 -0500792 int width = -1;
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400793 if (shouldDisplayLandscape(orientation)) {
Matt Pietal18bbd822019-02-12 15:21:36 -0500794 width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
795 }
796
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400797 parent = parent == null ? getWindow().getDecorView() : parent;
798
799 updateLayoutWidth(R.id.content_preview_text_layout, width, parent);
800 updateLayoutWidth(R.id.content_preview_title_layout, width, parent);
801 updateLayoutWidth(R.id.content_preview_file_layout, width, parent);
Matt Pietal18bbd822019-02-12 15:21:36 -0500802 }
803
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400804 private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
805 View view = parent.findViewById(layoutResourceId);
Matt Pietal1ef88002019-03-13 10:43:18 -0400806 if (view != null && view.getLayoutParams() != null) {
807 LayoutParams params = view.getLayoutParams();
808 params.width = width;
809 view.setLayoutParams(params);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500810 }
811 }
812
Matt Pietal1ef88002019-03-13 10:43:18 -0400813 private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
814 Intent targetIntent, LayoutInflater layoutInflater, ViewGroup convertView,
815 ViewGroup parent) {
Matt Pietale7cacab2019-05-23 07:21:36 -0400816 if (convertView != null) return convertView;
817
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400818 ViewGroup layout = null;
819
Matt Pietal1ef88002019-03-13 10:43:18 -0400820 switch (previewType) {
821 case CONTENT_PREVIEW_TEXT:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400822 layout = displayTextContentPreview(targetIntent, layoutInflater, parent);
823 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400824 case CONTENT_PREVIEW_IMAGE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400825 layout = displayImageContentPreview(targetIntent, layoutInflater, parent);
826 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400827 case CONTENT_PREVIEW_FILE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400828 layout = displayFileContentPreview(targetIntent, layoutInflater, parent);
829 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400830 default:
831 Log.e(TAG, "Unexpected content preview type: " + previewType);
832 }
Matt Pietal0ea391b2019-01-30 10:44:15 -0500833
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400834 if (layout != null) {
835 adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
836 }
837
838 return layout;
Matt Pietal1ef88002019-03-13 10:43:18 -0400839 }
840
841 private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -0400842 ViewGroup parent) {
843 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
844 R.layout.chooser_grid_preview_text, parent, false);
Matt Pietal1ef88002019-03-13 10:43:18 -0400845
846 contentPreviewLayout.findViewById(R.id.copy_button).setOnClickListener(
847 this::onCopyButtonClicked);
Matt Pietal46d828c2019-02-05 08:07:07 -0500848
Matt Pietal26038402019-01-08 07:29:34 -0500849 CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
Matt Pietal26038402019-01-08 07:29:34 -0500850 if (sharingText == null) {
Matt Pietal1ef88002019-03-13 10:43:18 -0400851 contentPreviewLayout.findViewById(R.id.content_preview_text_layout).setVisibility(
852 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500853 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -0400854 TextView textView = contentPreviewLayout.findViewById(R.id.content_preview_text);
Matt Pietal1fa7d802019-01-30 10:44:15 -0500855 textView.setText(sharingText);
Matt Pietal26038402019-01-08 07:29:34 -0500856 }
857
858 String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
Matt Pietal46d828c2019-02-05 08:07:07 -0500859 if (TextUtils.isEmpty(previewTitle)) {
Matt Pietal1ef88002019-03-13 10:43:18 -0400860 contentPreviewLayout.findViewById(R.id.content_preview_title_layout).setVisibility(
861 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500862 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -0400863 TextView previewTitleView = contentPreviewLayout.findViewById(
864 R.id.content_preview_title);
Matt Pietal26038402019-01-08 07:29:34 -0500865 previewTitleView.setText(previewTitle);
Matt Pietal26038402019-01-08 07:29:34 -0500866
Matt Pietal1fa7d802019-01-30 10:44:15 -0500867 ClipData previewData = targetIntent.getClipData();
868 Uri previewThumbnail = null;
869 if (previewData != null) {
870 if (previewData.getItemCount() > 0) {
871 ClipData.Item previewDataItem = previewData.getItemAt(0);
872 previewThumbnail = previewDataItem.getUri();
873 }
Matt Pietal26038402019-01-08 07:29:34 -0500874 }
Matt Pietal26038402019-01-08 07:29:34 -0500875
Matt Pietal1ef88002019-03-13 10:43:18 -0400876 ImageView previewThumbnailView = contentPreviewLayout.findViewById(
877 R.id.content_preview_thumbnail);
Matt Pietal1fa7d802019-01-30 10:44:15 -0500878 if (previewThumbnail == null) {
Matt Pietal26038402019-01-08 07:29:34 -0500879 previewThumbnailView.setVisibility(View.GONE);
880 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -0400881 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
882 mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
Matt Pietal26038402019-01-08 07:29:34 -0500883 }
884 }
Matt Pietal1ef88002019-03-13 10:43:18 -0400885
886 return contentPreviewLayout;
Matt Pietal26038402019-01-08 07:29:34 -0500887 }
888
Matt Pietal1ef88002019-03-13 10:43:18 -0400889 private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -0400890 ViewGroup parent) {
891 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
892 R.layout.chooser_grid_preview_image, parent, false);
893 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500894
895 String action = targetIntent.getAction();
896 if (Intent.ACTION_SEND.equals(action)) {
897 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietale7cacab2019-05-23 07:21:36 -0400898 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500899 } else {
900 ContentResolver resolver = getContentResolver();
901
902 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
903 List<Uri> imageUris = new ArrayList<>();
904 for (Uri uri : uris) {
905 if (isImageType(resolver.getType(uri))) {
906 imageUris.add(uri);
907 }
908 }
909
910 if (imageUris.size() == 0) {
911 Log.i(TAG, "Attempted to display image preview area with zero"
912 + " available images detected in EXTRA_STREAM list");
Matt Pietal46d828c2019-02-05 08:07:07 -0500913 contentPreviewLayout.setVisibility(View.GONE);
Matt Pietal1ef88002019-03-13 10:43:18 -0400914 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500915 }
916
Matt Pietale7cacab2019-05-23 07:21:36 -0400917 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500918
919 if (imageUris.size() == 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -0400920 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
921 imageUris.get(1), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500922 } else if (imageUris.size() > 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -0400923 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
924 imageUris.get(1), 0);
925 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
926 imageUris.get(2), imageUris.size() - 3);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500927 }
928 }
Matt Pietal1ef88002019-03-13 10:43:18 -0400929
930 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500931 }
932
Matt Pietal46d828c2019-02-05 08:07:07 -0500933 private static class FileInfo {
934 public final String name;
935 public final boolean hasThumbnail;
936
937 FileInfo(String name, boolean hasThumbnail) {
938 this.name = name;
939 this.hasThumbnail = hasThumbnail;
940 }
941 }
942
Matt Pietalf38e9d22019-02-15 10:01:03 -0500943 /**
944 * Wrapping the ContentResolver call to expose for easier mocking,
945 * and to avoid mocking Android core classes.
946 */
947 @VisibleForTesting
948 public Cursor queryResolver(ContentResolver resolver, Uri uri) {
949 return resolver.query(uri, null, null, null, null);
950 }
951
Matt Pietal46d828c2019-02-05 08:07:07 -0500952 private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
953 String fileName = null;
954 boolean hasThumbnail = false;
Matt Pietal3087bca2019-02-14 12:19:16 -0500955
Matt Pietalf38e9d22019-02-15 10:01:03 -0500956 try (Cursor cursor = queryResolver(resolver, uri)) {
957 if (cursor != null && cursor.getCount() > 0) {
958 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
959 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
960 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
961
962 cursor.moveToFirst();
963 if (nameIndex != -1) {
964 fileName = cursor.getString(nameIndex);
965 } else if (titleIndex != -1) {
966 fileName = cursor.getString(titleIndex);
967 }
968
969 if (flagsIndex != -1) {
970 hasThumbnail = (cursor.getInt(flagsIndex)
971 & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
972 }
973 }
Matt Pietal73a873f2019-03-15 08:46:20 -0400974 } catch (SecurityException | NullPointerException e) {
Matt Pietal62532e52019-05-07 09:51:37 -0400975 logContentPreviewWarning(uri);
Matt Pietal3087bca2019-02-14 12:19:16 -0500976 }
977
Matt Pietal46d828c2019-02-05 08:07:07 -0500978 if (TextUtils.isEmpty(fileName)) {
979 fileName = uri.getPath();
980 int index = fileName.lastIndexOf('/');
981 if (index != -1) {
982 fileName = fileName.substring(index + 1);
983 }
984 }
985
986 return new FileInfo(fileName, hasThumbnail);
987 }
988
Matt Pietal62532e52019-05-07 09:51:37 -0400989 private void logContentPreviewWarning(Uri uri) {
990 // The ContentResolver already logs the exception. Log something more informative.
991 Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
992 + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
993 + "and set your Intent's clipData and flags in accordance with that method's "
994 + "documentation");
995 }
996
Matt Pietal1ef88002019-03-13 10:43:18 -0400997 private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -0400998 ViewGroup parent) {
Matt Pietal1ef88002019-03-13 10:43:18 -0400999
Matt Pietale7cacab2019-05-23 07:21:36 -04001000 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1001 R.layout.chooser_grid_preview_file, parent, false);
Matt Pietal46d828c2019-02-05 08:07:07 -05001002
1003 // TODO(b/120417119): Disable file copy until after moving to sysui,
1004 // due to permissions issues
Matt Pietal1ef88002019-03-13 10:43:18 -04001005 contentPreviewLayout.findViewById(R.id.file_copy_button).setVisibility(View.GONE);
Matt Pietal46d828c2019-02-05 08:07:07 -05001006
Matt Pietal3087bca2019-02-14 12:19:16 -05001007 String action = targetIntent.getAction();
1008 if (Intent.ACTION_SEND.equals(action)) {
1009 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietal1ef88002019-03-13 10:43:18 -04001010 loadFileUriIntoView(uri, contentPreviewLayout);
Matt Pietal3087bca2019-02-14 12:19:16 -05001011 } else {
1012 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1013 int uriCount = uris.size();
Matt Pietal46d828c2019-02-05 08:07:07 -05001014
Matt Pietal3087bca2019-02-14 12:19:16 -05001015 if (uriCount == 0) {
1016 contentPreviewLayout.setVisibility(View.GONE);
1017 Log.i(TAG,
1018 "Appears to be no uris available in EXTRA_STREAM, removing "
1019 + "preview area");
Matt Pietal1ef88002019-03-13 10:43:18 -04001020 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001021 } else if (uriCount == 1) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001022 loadFileUriIntoView(uris.get(0), contentPreviewLayout);
Matt Pietal46d828c2019-02-05 08:07:07 -05001023 } else {
Matt Pietal3087bca2019-02-14 12:19:16 -05001024 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
1025 int remUriCount = uriCount - 1;
Matt Pietalacabc572019-02-14 11:02:05 -05001026 String fileName = getResources().getQuantityString(R.plurals.file_count,
Matt Pietal3087bca2019-02-14 12:19:16 -05001027 remUriCount, fileInfo.name, remUriCount);
Matt Pietalacabc572019-02-14 11:02:05 -05001028
Matt Pietal1ef88002019-03-13 10:43:18 -04001029 TextView fileNameView = contentPreviewLayout.findViewById(
1030 R.id.content_preview_filename);
Matt Pietalacabc572019-02-14 11:02:05 -05001031 fileNameView.setText(fileName);
Matt Pietal3087bca2019-02-14 12:19:16 -05001032
Matt Pietale7cacab2019-05-23 07:21:36 -04001033 View thumbnailView = contentPreviewLayout.findViewById(
1034 R.id.content_preview_file_thumbnail);
1035 thumbnailView.setVisibility(View.GONE);
1036
Matt Pietal1ef88002019-03-13 10:43:18 -04001037 ImageView fileIconView = contentPreviewLayout.findViewById(
1038 R.id.content_preview_file_icon);
Matt Pietal46d828c2019-02-05 08:07:07 -05001039 fileIconView.setVisibility(View.VISIBLE);
Matt Pietalacabc572019-02-14 11:02:05 -05001040 fileIconView.setImageResource(R.drawable.ic_file_copy);
Matt Pietal46d828c2019-02-05 08:07:07 -05001041 }
Matt Pietal3087bca2019-02-14 12:19:16 -05001042 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001043
1044 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001045 }
1046
Matt Pietale7cacab2019-05-23 07:21:36 -04001047 private void loadFileUriIntoView(final Uri uri, final View parent) {
Matt Pietal3087bca2019-02-14 12:19:16 -05001048 FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
1049
Matt Pietal1ef88002019-03-13 10:43:18 -04001050 TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
Matt Pietal3087bca2019-02-14 12:19:16 -05001051 fileNameView.setText(fileInfo.name);
1052
1053 if (fileInfo.hasThumbnail) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001054 mPreviewCoord = new ContentPreviewCoordinator(parent, false);
1055 mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
Matt Pietal3087bca2019-02-14 12:19:16 -05001056 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -04001057 View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
1058 thumbnailView.setVisibility(View.GONE);
1059
Matt Pietal1ef88002019-03-13 10:43:18 -04001060 ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
Matt Pietal3087bca2019-02-14 12:19:16 -05001061 fileIconView.setVisibility(View.VISIBLE);
Matt Pietal832cdbf2019-04-05 13:20:31 -04001062 fileIconView.setImageResource(R.drawable.chooser_file_generic);
Matt Pietal46d828c2019-02-05 08:07:07 -05001063 }
Matt Pietal0ea391b2019-01-30 10:44:15 -05001064 }
1065
Matt Pietal0ea391b2019-01-30 10:44:15 -05001066 @VisibleForTesting
1067 protected boolean isImageType(String mimeType) {
1068 return mimeType != null && mimeType.startsWith("image/");
1069 }
1070
1071 @ContentPreviewType
1072 private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
1073 if (uri == null) {
1074 return CONTENT_PREVIEW_TEXT;
1075 }
1076
1077 String mimeType = resolver.getType(uri);
1078 return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
1079 }
1080
1081 /**
1082 * In {@link android.content.Intent#getType}, the app may specify a very general
1083 * mime-type that broadly covers all data being shared, such as {@literal *}/*
1084 * when sending an image and text. We therefore should inspect each item for the
1085 * the preferred type, in order of IMAGE, FILE, TEXT.
1086 */
1087 @ContentPreviewType
1088 private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
1089 String action = targetIntent.getAction();
1090 if (Intent.ACTION_SEND.equals(action)) {
1091 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1092 return findPreferredContentPreview(uri, resolver);
1093 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1094 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1095 if (uris == null || uris.isEmpty()) {
1096 return CONTENT_PREVIEW_TEXT;
1097 }
1098
1099 for (Uri uri : uris) {
Matt Pietal832cdbf2019-04-05 13:20:31 -04001100 // Defaulting to file preview when there are mixed image/file types is
1101 // preferable, as it shows the user the correct number of items being shared
1102 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_FILE) {
1103 return CONTENT_PREVIEW_FILE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001104 }
1105 }
1106
Matt Pietal832cdbf2019-04-05 13:20:31 -04001107 return CONTENT_PREVIEW_IMAGE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001108 }
1109
1110 return CONTENT_PREVIEW_TEXT;
1111 }
1112
Mike Digman849a9d12019-04-29 11:20:48 -07001113 private int getNumSheetExpansions() {
1114 return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0);
1115 }
1116
1117 private void incrementNumSheetExpansions() {
1118 getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS,
1119 getNumSheetExpansions() + 1).apply();
1120 }
1121
Adam Powell0b3c1122014-10-09 12:50:14 -07001122 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07001123 protected void onDestroy() {
1124 super.onDestroy();
1125 if (mRefinementResultReceiver != null) {
1126 mRefinementResultReceiver.destroy();
1127 mRefinementResultReceiver = null;
1128 }
Adam Powell9761ab22015-09-08 17:01:49 -07001129 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -04001130 mChooserHandler.removeAllMessages();
Matt Pietale7cacab2019-05-23 07:21:36 -04001131
1132 if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
1133
George Hodulik145b3a52019-03-27 11:18:43 -07001134 if (mAppPredictor != null) {
George Hodulik69d4a082019-01-18 11:27:03 -08001135 mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
1136 mAppPredictor.destroy();
1137 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001138 }
1139
1140 @Override
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001141 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
1142 Intent result = defIntent;
Adam Powelle49d9392014-07-17 18:45:19 -07001143 if (mReplacementExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001144 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
Adam Powelle49d9392014-07-17 18:45:19 -07001145 if (replExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001146 result = new Intent(defIntent);
Adam Powelle49d9392014-07-17 18:45:19 -07001147 result.putExtras(replExtras);
Adam Powelle49d9392014-07-17 18:45:19 -07001148 }
1149 }
Nicolas Prevot741abfc2015-08-11 12:03:51 +01001150 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001151 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
1152 result = Intent.createChooser(result,
1153 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
Hakan Seyalioglu7317e8a2016-12-12 16:15:38 -08001154
1155 // Don't auto-launch single intents if the intent is being forwarded. This is done
1156 // because automatically launching a resolving application as a response to the user
1157 // action of switching accounts is pretty unexpected.
1158 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001159 }
1160 return result;
Adam Powelle49d9392014-07-17 18:45:19 -07001161 }
1162
Adam Powell0b3c1122014-10-09 12:50:14 -07001163 @Override
Adam Powell23882512016-01-29 10:21:00 -08001164 public void onActivityStarted(TargetInfo cti) {
Adam Powell0b3c1122014-10-09 12:50:14 -07001165 if (mChosenComponentSender != null) {
Adam Powell24428412015-04-01 17:19:56 -07001166 final ComponentName target = cti.getResolvedComponentName();
Adam Powell0b3c1122014-10-09 12:50:14 -07001167 if (target != null) {
1168 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
1169 try {
1170 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
1171 } catch (IntentSender.SendIntentException e) {
1172 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
1173 + "the chosen component: " + e);
1174 }
1175 }
1176 }
1177 }
1178
Adam Powell24428412015-04-01 17:19:56 -07001179 @Override
Hakan Seyalioglu13405c52017-01-31 19:01:31 -08001180 public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
Adam Powell7d758002015-05-06 17:49:36 -07001181 final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
1182 mChooserListAdapter = (ChooserListAdapter) adapter;
Adam Powell52c39212016-04-07 15:14:18 -07001183 if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
Matt Pietalfbfa0492019-04-01 11:29:56 -04001184 mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets),
1185 false);
Adam Powell52c39212016-04-07 15:14:18 -07001186 }
Adam Powell63b31692015-09-28 10:45:00 -07001187 mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
Adam Powell7d758002015-05-06 17:49:36 -07001188 if (listView != null) {
1189 listView.setItemsCanFocus(true);
1190 }
1191 }
1192
1193 @Override
Adam Powell23882512016-01-29 10:21:00 -08001194 public int getLayoutResource() {
Adam Powell7d758002015-05-06 17:49:36 -07001195 return R.layout.chooser_grid;
Adam Powell24428412015-04-01 17:19:56 -07001196 }
1197
1198 @Override
Adam Powell23882512016-01-29 10:21:00 -08001199 public boolean shouldGetActivityMetadata() {
Adam Powell24428412015-04-01 17:19:56 -07001200 return true;
1201 }
1202
Adam Powell9761ab22015-09-08 17:01:49 -07001203 @Override
Ben Lin145b0ca2016-10-14 14:23:40 -07001204 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
Hakan Seyalioglu13405c52017-01-31 19:01:31 -08001205 // Note that this is only safe because the Intent handled by the ChooserActivity is
1206 // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
1207 // method can not be replaced in the ResolverActivity whole hog.
Matt Pietala4b30072019-04-04 13:44:36 -04001208 if (!super.shouldAutoLaunchSingleChoice(target)) {
1209 return false;
1210 }
1211
1212 return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
Ben Lin145b0ca2016-10-14 14:23:40 -07001213 }
1214
1215 @Override
Adam Powell23882512016-01-29 10:21:00 -08001216 public void showTargetDetails(ResolveInfo ri) {
sanryhuang296ca9e2018-03-31 11:17:13 +08001217 if (ri == null) {
1218 return;
1219 }
1220
Adam Powell23882512016-01-29 10:21:00 -08001221 ComponentName name = ri.activityInfo.getComponentName();
Adam Powell23882512016-01-29 10:21:00 -08001222 ResolverTargetActionsDialogFragment f =
1223 new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
Matt Pietaldf634cc2019-03-13 09:55:28 -04001224 name);
Adam Powell23882512016-01-29 10:21:00 -08001225 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1226 }
1227
Adam Powelle49d9392014-07-17 18:45:19 -07001228 private void modifyTargetIntent(Intent in) {
Matt Pietal95574b02019-03-13 08:12:25 -04001229 if (isSendAction(in)) {
Adam Powelle49d9392014-07-17 18:45:19 -07001230 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
Dianne Hackborn13420f22014-07-18 15:43:56 -07001231 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Adam Powelle49d9392014-07-17 18:45:19 -07001232 }
1233 }
Adam Powell24428412015-04-01 17:19:56 -07001234
Adam Powell2ed547e2015-04-29 18:45:04 -07001235 @Override
1236 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
1237 if (mRefinementIntentSender != null) {
1238 final Intent fillIn = new Intent();
1239 final List<Intent> sourceIntents = target.getAllSourceIntents();
1240 if (!sourceIntents.isEmpty()) {
1241 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
1242 if (sourceIntents.size() > 1) {
1243 final Intent[] alts = new Intent[sourceIntents.size() - 1];
1244 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
1245 alts[i - 1] = sourceIntents.get(i);
1246 }
1247 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
1248 }
1249 if (mRefinementResultReceiver != null) {
1250 mRefinementResultReceiver.destroy();
1251 }
1252 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
1253 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
1254 mRefinementResultReceiver);
1255 try {
1256 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
1257 return false;
1258 } catch (SendIntentException e) {
1259 Log.e(TAG, "Refinement IntentSender failed to send", e);
1260 }
1261 }
1262 }
Kang Li9fa2a2c2017-01-06 13:33:24 -08001263 updateModelAndChooserCounts(target);
Adam Powell2ed547e2015-04-29 18:45:04 -07001264 return super.onTargetSelected(target, alwaysCheck);
1265 }
1266
Adam Powell98b7f892015-06-19 12:38:45 -07001267 @Override
Adam Powell23882512016-01-29 10:21:00 -08001268 public void startSelected(int which, boolean always, boolean filtered) {
Matt Pietala4b30072019-04-04 13:44:36 -04001269 TargetInfo targetInfo = mChooserListAdapter.targetInfoForPosition(which, filtered);
1270 if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
1271 return;
1272 }
1273
Kang Li9082f5b2016-12-02 10:56:21 -08001274 final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
Adam Powell98b7f892015-06-19 12:38:45 -07001275 super.startSelected(which, always, filtered);
1276
1277 if (mChooserListAdapter != null) {
1278 // Log the index of which type of target the user picked.
1279 // Lower values mean the ranking was better.
1280 int cat = 0;
1281 int value = which;
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001282 int directTargetAlsoRanked = -1;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001283 int numCallerProvided = 0;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001284 HashedStringCache.HashResult directTargetHashed = null;
Adam Powell98b7f892015-06-19 12:38:45 -07001285 switch (mChooserListAdapter.getPositionTargetType(which)) {
Adam Powell98b7f892015-06-19 12:38:45 -07001286 case ChooserListAdapter.TARGET_SERVICE:
Chris Wrenf6e9228b2016-01-26 18:04:35 -05001287 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001288 // Log the package name + target name to answer the question if most users
1289 // share to mostly the same person or to a bunch of different people.
1290 ChooserTarget target =
1291 mChooserListAdapter.mServiceTargets.get(value).getChooserTarget();
1292 directTargetHashed = HashedStringCache.getInstance().hashString(
1293 this,
1294 TAG,
1295 target.getComponentName().getPackageName()
1296 + target.getTitle().toString(),
1297 mMaxHashSaltDays);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001298 directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001299
1300 if (mCallerChooserTargets != null) {
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001301 numCallerProvided = mCallerChooserTargets.length;
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001302 }
Adam Powell98b7f892015-06-19 12:38:45 -07001303 break;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001304 case ChooserListAdapter.TARGET_CALLER:
Adam Powell98b7f892015-06-19 12:38:45 -07001305 case ChooserListAdapter.TARGET_STANDARD:
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001306 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
1307 value -= mChooserListAdapter.getSelectableServiceTargetCount();
1308 numCallerProvided = mChooserListAdapter.getCallerTargetCount();
Adam Powell98b7f892015-06-19 12:38:45 -07001309 break;
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04001310 case ChooserListAdapter.TARGET_STANDARD_AZ:
1311 // A-Z targets are unranked standard targets; we use -1 to mark that they
1312 // are from the alphabetical pool.
1313 value = -1;
1314 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
1315 break;
Adam Powell98b7f892015-06-19 12:38:45 -07001316 }
1317
1318 if (cat != 0) {
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001319 LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
1320 if (directTargetHashed != null) {
1321 targetLogMaker.addTaggedData(
1322 MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
1323 targetLogMaker.addTaggedData(
1324 MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
1325 directTargetHashed.saltGeneration);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001326 targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
1327 directTargetAlsoRanked);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001328 }
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001329 targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED,
1330 numCallerProvided);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001331 getMetricsLogger().write(targetLogMaker);
Adam Powell98b7f892015-06-19 12:38:45 -07001332 }
Kang Li9082f5b2016-12-02 10:56:21 -08001333
1334 if (mIsSuccessfullySelected) {
1335 if (DEBUG) {
1336 Log.d(TAG, "User Selection Time Cost is " + selectionCost);
1337 Log.d(TAG, "position of selected app/service/caller is " +
1338 Integer.toString(value));
1339 }
1340 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
1341 (int) selectionCost);
1342 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
1343 }
Adam Powell98b7f892015-06-19 12:38:45 -07001344 }
1345 }
1346
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001347 private int getRankedPosition(SelectableTargetInfo targetInfo) {
1348 String targetPackageName =
1349 targetInfo.getChooserTarget().getComponentName().getPackageName();
1350 int maxRankedResults = Math.min(mChooserListAdapter.mDisplayList.size(),
1351 MAX_LOG_RANK_POSITION);
1352
1353 for (int i = 0; i < maxRankedResults; i++) {
1354 if (mChooserListAdapter.mDisplayList.get(i)
1355 .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
1356 return i;
1357 }
1358 }
1359 return -1;
1360 }
1361
Adam Powell24428412015-04-01 17:19:56 -07001362 void queryTargetServices(ChooserListAdapter adapter) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001363 mQueriedTargetServicesTimeMs = System.currentTimeMillis();
1364
Adam Powell24428412015-04-01 17:19:56 -07001365 final PackageManager pm = getPackageManager();
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001366 ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
Adam Powell24428412015-04-01 17:19:56 -07001367 int targetsToQuery = 0;
Matt Pietalab73a882019-06-05 07:04:55 -04001368
Adam Powell24428412015-04-01 17:19:56 -07001369 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
1370 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
Adam Powell3a09c522015-10-21 13:21:28 -07001371 if (adapter.getScore(dri) == 0) {
1372 // A score of 0 means the app hasn't been used in some time;
1373 // don't query it as it's not likely to be relevant.
1374 continue;
1375 }
Adam Powell24428412015-04-01 17:19:56 -07001376 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001377 if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
1378 && sm.hasShareTargets(ai.packageName)) {
1379 // Share targets will be queried from ShortcutManager
1380 continue;
1381 }
Adam Powell24428412015-04-01 17:19:56 -07001382 final Bundle md = ai.metaData;
1383 final String serviceName = md != null ? convertServiceName(ai.packageName,
1384 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
1385 if (serviceName != null) {
1386 final ComponentName serviceComponent = new ComponentName(
1387 ai.packageName, serviceName);
Matt Pietalab73a882019-06-05 07:04:55 -04001388
1389 if (mServicesRequested.contains(serviceComponent)) {
1390 continue;
1391 }
1392 mServicesRequested.add(serviceComponent);
1393
Adam Powell24428412015-04-01 17:19:56 -07001394 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
1395 .setComponent(serviceComponent);
1396
1397 if (DEBUG) {
1398 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
1399 }
1400
1401 try {
1402 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
1403 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
1404 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
1405 + " permission " + ChooserTargetService.BIND_PERMISSION
1406 + " - this service will not be queried for ChooserTargets."
1407 + " add android:permission=\""
1408 + ChooserTargetService.BIND_PERMISSION + "\""
1409 + " to the <service> tag for " + serviceComponent
1410 + " in the manifest.");
1411 continue;
1412 }
1413 } catch (NameNotFoundException e) {
Adam Powell52c39212016-04-07 15:14:18 -07001414 Log.e(TAG, "Could not look up service " + serviceComponent
1415 + "; component name not found");
Adam Powell24428412015-04-01 17:19:56 -07001416 continue;
1417 }
1418
Adam Powell9761ab22015-09-08 17:01:49 -07001419 final ChooserTargetServiceConnection conn =
1420 new ChooserTargetServiceConnection(this, dri);
Adam Powell52c39212016-04-07 15:14:18 -07001421
1422 // Explicitly specify Process.myUserHandle instead of calling bindService
1423 // to avoid the warning from calling from the system process without an explicit
1424 // user handle
1425 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
1426 Process.myUserHandle())) {
Adam Powell24428412015-04-01 17:19:56 -07001427 if (DEBUG) {
1428 Log.d(TAG, "Binding service connection for target " + dri
1429 + " intent " + serviceIntent);
1430 }
1431 mServiceConnections.add(conn);
1432 targetsToQuery++;
1433 }
1434 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001435 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
Matt Pietal26038402019-01-08 07:29:34 -05001436 if (DEBUG) {
1437 Log.d(TAG, "queryTargets hit query target limit "
1438 + QUERY_TARGET_SERVICE_LIMIT);
1439 }
Adam Powell24428412015-04-01 17:19:56 -07001440 break;
1441 }
1442 }
1443
Matt Pietalab73a882019-06-05 07:04:55 -04001444 mChooserHandler.restartServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -07001445 }
1446
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001447 private IntentFilter getTargetIntentFilter() {
1448 try {
1449 final Intent intent = getTargetIntent();
1450 String dataString = intent.getDataString();
1451 if (TextUtils.isEmpty(dataString)) {
1452 dataString = intent.getType();
1453 }
1454 return new IntentFilter(intent.getAction(), dataString);
1455 } catch (Exception e) {
1456 Log.e(TAG, "failed to get target intent filter " + e);
1457 return null;
1458 }
1459 }
1460
George Hodulik69d4a082019-01-18 11:27:03 -08001461 private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001462 // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
1463 // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
George Hodulik69d4a082019-01-18 11:27:03 -08001464 List<DisplayResolveInfo> driList = new ArrayList<>();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001465 int targetsToQuery = 0;
1466 for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
1467 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
1468 if (adapter.getScore(dri) == 0) {
1469 // A score of 0 means the app hasn't been used in some time;
1470 // don't query it as it's not likely to be relevant.
1471 continue;
1472 }
1473 driList.add(dri);
1474 targetsToQuery++;
1475 // TODO(b/121287224): Do we need this here? (similar to queryTargetServices)
1476 if (targetsToQuery >= SHARE_TARGET_QUERY_PACKAGE_LIMIT) {
1477 if (DEBUG) {
1478 Log.d(TAG, "queryTargets hit query target limit "
1479 + SHARE_TARGET_QUERY_PACKAGE_LIMIT);
1480 }
1481 break;
1482 }
1483 }
George Hodulik69d4a082019-01-18 11:27:03 -08001484 return driList;
1485 }
1486
George Hodulik3f399f22019-04-26 16:17:54 -07001487 private void queryDirectShareTargets(
1488 ChooserListAdapter adapter, boolean skipAppPredictionService) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001489 mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
George Hodulik3f399f22019-04-26 16:17:54 -07001490 if (!skipAppPredictionService) {
1491 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
1492 if (appPredictor != null) {
1493 appPredictor.requestPredictionUpdate();
1494 return;
1495 }
George Hodulik69d4a082019-01-18 11:27:03 -08001496 }
George Hodulik145b3a52019-03-27 11:18:43 -07001497 // Default to just querying ShortcutManager if AppPredictor not present.
George Hodulik69d4a082019-01-18 11:27:03 -08001498 final IntentFilter filter = getTargetIntentFilter();
1499 if (filter == null) {
1500 return;
1501 }
1502 final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001503
1504 AsyncTask.execute(() -> {
1505 ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
1506 List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
George Hodulikaa5238c2019-04-18 14:17:51 -07001507 sendShareShortcutInfoList(resultList, driList, null);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001508 });
1509 }
1510
George Hodulik69d4a082019-01-18 11:27:03 -08001511 private void sendShareShortcutInfoList(
1512 List<ShortcutManager.ShareShortcutInfo> resultList,
George Hodulikaa5238c2019-04-18 14:17:51 -07001513 List<DisplayResolveInfo> driList,
1514 @Nullable List<AppTarget> appTargets) {
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001515 if (appTargets != null && appTargets.size() != resultList.size()) {
1516 throw new RuntimeException("resultList and appTargets must have the same size."
1517 + " resultList.size()=" + resultList.size()
1518 + " appTargets.size()=" + appTargets.size());
1519 }
1520
1521 for (int i = resultList.size() - 1; i >= 0; i--) {
1522 final String packageName = resultList.get(i).getTargetComponent().getPackageName();
1523 if (!isPackageEnabled(packageName)) {
1524 resultList.remove(i);
1525 if (appTargets != null) {
1526 appTargets.remove(i);
1527 }
1528 }
1529 }
1530
George Hodulik69d4a082019-01-18 11:27:03 -08001531 // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
1532 // for direct share targets. After ShareSheet is refactored we should use the
1533 // ShareShortcutInfos directly.
1534 boolean resultMessageSent = false;
1535 for (int i = 0; i < driList.size(); i++) {
1536 List<ChooserTarget> chooserTargets = new ArrayList<>();
1537 for (int j = 0; j < resultList.size(); j++) {
1538 if (driList.get(i).getResolvedComponentName().equals(
1539 resultList.get(j).getTargetComponent())) {
George Hodulikaa5238c2019-04-18 14:17:51 -07001540 ShortcutManager.ShareShortcutInfo shareShortcutInfo = resultList.get(j);
Matt Pietalbdbeea22019-07-01 13:06:21 -04001541 // Incoming results are ordered but without a score. Create a score
1542 // based on the index in order to be sorted appropriately when joined
1543 // with legacy direct share api results.
1544 float score = Math.max(1.0f - (0.05f * j), 0.0f);
1545 ChooserTarget chooserTarget = convertToChooserTarget(shareShortcutInfo, score);
George Hodulikaa5238c2019-04-18 14:17:51 -07001546 chooserTargets.add(chooserTarget);
1547 if (mDirectShareAppTargetCache != null && appTargets != null) {
George Hodulikaa5238c2019-04-18 14:17:51 -07001548 mDirectShareAppTargetCache.put(chooserTarget, appTargets.get(j));
1549 }
George Hodulik69d4a082019-01-18 11:27:03 -08001550 }
1551 }
1552 if (chooserTargets.isEmpty()) {
1553 continue;
1554 }
1555 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04001556 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
George Hodulik69d4a082019-01-18 11:27:03 -08001557 msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
1558 mChooserHandler.sendMessage(msg);
1559 resultMessageSent = true;
1560 }
1561
1562 if (resultMessageSent) {
George Hodulik145b3a52019-03-27 11:18:43 -07001563 sendShortcutManagerShareTargetResultCompleted();
George Hodulik69d4a082019-01-18 11:27:03 -08001564 }
1565 }
1566
George Hodulik145b3a52019-03-27 11:18:43 -07001567 private void sendShortcutManagerShareTargetResultCompleted() {
1568 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04001569 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
George Hodulik145b3a52019-03-27 11:18:43 -07001570 mChooserHandler.sendMessage(msg);
1571 }
1572
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001573 private boolean isPackageEnabled(String packageName) {
1574 if (TextUtils.isEmpty(packageName)) {
1575 return false;
1576 }
1577 ApplicationInfo appInfo;
1578 try {
1579 appInfo = getPackageManager().getApplicationInfo(packageName, 0);
1580 } catch (NameNotFoundException e) {
1581 return false;
1582 }
1583
1584 if (appInfo != null && appInfo.enabled
1585 && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
1586 return true;
1587 }
1588 return false;
1589 }
1590
Matt Pietalbdbeea22019-07-01 13:06:21 -04001591 private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut,
1592 float score) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001593 ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
1594 Bundle extras = new Bundle();
1595 extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
1596 return new ChooserTarget(
1597 // The name of this target.
1598 shortcutInfo.getShortLabel(),
1599 // Don't load the icon until it is selected to be shown
1600 null,
1601 // The ranking score for this target (0.0-1.0); the system will omit items with low
1602 // scores when there are too many Direct Share items.
Matt Pietalbdbeea22019-07-01 13:06:21 -04001603 score,
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001604 // The name of the component to be launched if this target is chosen.
1605 shareShortcut.getTargetComponent().clone(),
1606 // The extra values here will be merged into the Intent when this target is chosen.
1607 extras);
1608 }
1609
Adam Powell24428412015-04-01 17:19:56 -07001610 private String convertServiceName(String packageName, String serviceName) {
1611 if (TextUtils.isEmpty(serviceName)) {
1612 return null;
1613 }
1614
1615 final String fullName;
1616 if (serviceName.startsWith(".")) {
1617 // Relative to the app package. Prepend the app package name.
1618 fullName = packageName + serviceName;
1619 } else if (serviceName.indexOf('.') >= 0) {
1620 // Fully qualified package name.
1621 fullName = serviceName;
1622 } else {
1623 fullName = null;
1624 }
1625 return fullName;
1626 }
1627
1628 void unbindRemainingServices() {
1629 if (DEBUG) {
1630 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
1631 }
1632 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
1633 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
1634 if (DEBUG) Log.d(TAG, "unbinding " + conn);
1635 unbindService(conn);
Adam Powell9761ab22015-09-08 17:01:49 -07001636 conn.destroy();
Adam Powell24428412015-04-01 17:19:56 -07001637 }
Matt Pietalab73a882019-06-05 07:04:55 -04001638 mServicesRequested.clear();
Adam Powell24428412015-04-01 17:19:56 -07001639 mServiceConnections.clear();
Adam Powell24428412015-04-01 17:19:56 -07001640 }
1641
Adam Powell23882512016-01-29 10:21:00 -08001642 public void onSetupVoiceInteraction() {
Adam Powell4c470d62015-06-19 17:46:17 -07001643 // Do nothing. We'll send the voice stuff ourselves.
1644 }
1645
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001646 private void logDirectShareTargetReceived(int logCategory) {
1647 final long queryTime =
1648 logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
1649 ? mQueriedSharingShortcutsTimeMs : mQueriedTargetServicesTimeMs;
1650 final int apiLatency = (int) (System.currentTimeMillis() - queryTime);
1651 getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
1652 }
1653
Kang Li9fa2a2c2017-01-06 13:33:24 -08001654 void updateModelAndChooserCounts(TargetInfo info) {
Kang Li53b43142016-11-14 14:38:25 -08001655 if (info != null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001656 sendClickToAppPredictor(info);
Kang Li53b43142016-11-14 14:38:25 -08001657 final ResolveInfo ri = info.getResolveInfo();
Kang Li64b018e2017-01-05 17:30:06 -08001658 Intent targetIntent = getTargetIntent();
1659 if (ri != null && ri.activityInfo != null && targetIntent != null) {
Kang Li0cef910d2017-01-05 09:14:36 -08001660 if (mAdapter != null) {
1661 mAdapter.updateModel(info.getResolvedComponentName());
Kang Li9fa2a2c2017-01-06 13:33:24 -08001662 mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
1663 targetIntent.getAction());
Kang Li0cef910d2017-01-05 09:14:36 -08001664 }
Kang Li53b43142016-11-14 14:38:25 -08001665 if (DEBUG) {
Kang Li64b018e2017-01-05 17:30:06 -08001666 Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
Kang Li64b018e2017-01-05 17:30:06 -08001667 Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
Kang Li53b43142016-11-14 14:38:25 -08001668 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001669 } else if (DEBUG) {
Kang Li53b43142016-11-14 14:38:25 -08001670 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
1671 }
1672 }
Kang Li9082f5b2016-12-02 10:56:21 -08001673 mIsSuccessfullySelected = true;
Kang Li53b43142016-11-14 14:38:25 -08001674 }
1675
George Hodulikf2b0d342019-01-25 12:43:54 -08001676 private void sendClickToAppPredictor(TargetInfo targetInfo) {
George Hodulikaa5238c2019-04-18 14:17:51 -07001677 AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled();
1678 if (directShareAppPredictor == null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001679 return;
1680 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001681 if (!(targetInfo instanceof ChooserTargetInfo)) {
1682 return;
1683 }
1684 ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
George Hodulikaa5238c2019-04-18 14:17:51 -07001685 AppTarget appTarget = null;
1686 if (mDirectShareAppTargetCache != null) {
1687 appTarget = mDirectShareAppTargetCache.get(chooserTarget);
George Hodulikf2b0d342019-01-25 12:43:54 -08001688 }
George Hodulikaa5238c2019-04-18 14:17:51 -07001689 // This is a direct share click that was provided by the APS
1690 if (appTarget != null) {
1691 directShareAppPredictor.notifyAppTargetEvent(
1692 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
1693 .setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
1694 .build());
George Hodulikf2b0d342019-01-25 12:43:54 -08001695 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001696 }
1697
George Hodulik145b3a52019-03-27 11:18:43 -07001698 @Nullable
1699 private AppPredictor getAppPredictor() {
1700 if (mAppPredictor == null
1701 && getPackageManager().getAppPredictionServicePackageName() != null) {
1702 final IntentFilter filter = getTargetIntentFilter();
1703 Bundle extras = new Bundle();
1704 extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
1705 AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(this)
1706 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
1707 .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
1708 .setExtras(extras)
1709 .build();
1710 AppPredictionManager appPredictionManager
1711 = getSystemService(AppPredictionManager.class);
1712 mAppPredictor = appPredictionManager.createAppPredictionSession(appPredictionContext);
1713 }
1714 return mAppPredictor;
1715 }
1716
1717 /**
1718 * This will return an app predictor if it is enabled for direct share sorting
1719 * and if one exists. Otherwise, it returns null.
1720 */
1721 @Nullable
1722 private AppPredictor getAppPredictorForDirectShareIfEnabled() {
Matt Pietal030bd842019-05-29 07:14:14 -04001723 return USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS && !ActivityManager.isLowRamDeviceStatic()
1724 ? getAppPredictor() : null;
George Hodulik145b3a52019-03-27 11:18:43 -07001725 }
1726
George Hodulikc681ce42019-04-12 17:10:31 -07001727 /**
1728 * This will return an app predictor if it is enabled for share activity sorting
1729 * and if one exists. Otherwise, it returns null.
1730 */
1731 @Nullable
1732 private AppPredictor getAppPredictorForShareActivitesIfEnabled() {
1733 return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? getAppPredictor() : null;
1734 }
1735
Adam Powell2ed547e2015-04-29 18:45:04 -07001736 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
1737 if (mRefinementResultReceiver != null) {
1738 mRefinementResultReceiver.destroy();
1739 mRefinementResultReceiver = null;
1740 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001741 if (selectedTarget == null) {
1742 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
1743 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
1744 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
1745 + " cannot match refined source intent " + matchingIntent);
Kang Li53b43142016-11-14 14:38:25 -08001746 } else {
1747 TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
1748 if (super.onTargetSelected(clonedTarget, false)) {
Kang Li9fa2a2c2017-01-06 13:33:24 -08001749 updateModelAndChooserCounts(clonedTarget);
Kang Li53b43142016-11-14 14:38:25 -08001750 finish();
1751 return;
1752 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001753 }
1754 onRefinementCanceled();
1755 }
1756
1757 void onRefinementCanceled() {
1758 if (mRefinementResultReceiver != null) {
1759 mRefinementResultReceiver.destroy();
1760 mRefinementResultReceiver = null;
1761 }
1762 finish();
1763 }
1764
1765 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
1766 final List<Intent> targetIntents = target.getAllSourceIntents();
1767 for (int i = 0, N = targetIntents.size(); i < N; i++) {
1768 final Intent targetIntent = targetIntents.get(i);
1769 if (targetIntent.filterEquals(matchingIntent)) {
1770 return true;
1771 }
1772 }
1773 return false;
1774 }
1775
Adam Powell666d82a2015-07-15 20:14:57 -07001776 void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
1777 if (targets == null) {
1778 return;
1779 }
1780
1781 final PackageManager pm = getPackageManager();
1782 for (int i = targets.size() - 1; i >= 0; i--) {
1783 final ChooserTarget target = targets.get(i);
1784 final ComponentName targetName = target.getComponentName();
1785 if (packageName != null && packageName.equals(targetName.getPackageName())) {
1786 // Anything from the original target's package is fine.
1787 continue;
1788 }
1789
1790 boolean remove;
1791 try {
1792 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
1793 remove = !ai.exported || ai.permission != null;
1794 } catch (NameNotFoundException e) {
1795 Log.e(TAG, "Target " + target + " returned by " + packageName
1796 + " component not found");
1797 remove = true;
1798 }
1799
1800 if (remove) {
1801 targets.remove(i);
1802 }
1803 }
1804 }
1805
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04001806 private void updateAlphabeticalList() {
Alison Cichowlas13314612019-04-11 15:20:39 -04001807 mSortedList.clear();
1808 mSortedList.addAll(getDisplayList());
1809 Collections.sort(mSortedList, new AzInfoComparator(ChooserActivity.this));
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04001810 }
1811
1812 /**
1813 * Sort intents alphabetically based on display label.
1814 */
1815 class AzInfoComparator implements Comparator<ResolverActivity.DisplayResolveInfo> {
1816 Collator mCollator;
1817 AzInfoComparator(Context context) {
1818 mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
1819 }
1820
1821 @Override
1822 public int compare(ResolverActivity.DisplayResolveInfo lhsp,
1823 ResolverActivity.DisplayResolveInfo rhsp) {
1824 return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
1825 }
1826 }
1827
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -05001828 protected MetricsLogger getMetricsLogger() {
1829 if (mMetricsLogger == null) {
1830 mMetricsLogger = new MetricsLogger();
1831 }
1832 return mMetricsLogger;
1833 }
1834
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001835 public class ChooserListController extends ResolverListController {
1836 public ChooserListController(Context context,
1837 PackageManager pm,
1838 Intent targetIntent,
1839 String referrerPackageName,
George Hodulikc681ce42019-04-12 17:10:31 -07001840 int launchedFromUid,
1841 AbstractResolverComparator resolverComparator) {
1842 super(context, pm, targetIntent, referrerPackageName, launchedFromUid,
1843 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001844 }
1845
1846 @Override
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001847 boolean isComponentFiltered(ComponentName name) {
1848 if (mFilteredComponentNames == null) {
1849 return false;
1850 }
1851 for (ComponentName filteredComponentName : mFilteredComponentNames) {
1852 if (name.equals(filteredComponentName)) {
1853 return true;
1854 }
1855 }
1856 return false;
1857 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001858 }
1859
Adam Powell24428412015-04-01 17:19:56 -07001860 @Override
Adam Powell23882512016-01-29 10:21:00 -08001861 public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
Adam Powell7d758002015-05-06 17:49:36 -07001862 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
1863 boolean filterLastUsed) {
1864 final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001865 initialIntents, rList, launchedFromUid, filterLastUsed, createListController());
Adam Powell24428412015-04-01 17:19:56 -07001866 return adapter;
1867 }
1868
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001869 @VisibleForTesting
1870 protected ResolverListController createListController() {
George Hodulikc681ce42019-04-12 17:10:31 -07001871 AppPredictor appPredictor = getAppPredictorForShareActivitesIfEnabled();
1872 AbstractResolverComparator resolverComparator;
1873 if (appPredictor != null) {
1874 resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
George Hodulik3f399f22019-04-26 16:17:54 -07001875 getReferrerPackageName(), appPredictor, getUser());
George Hodulikc681ce42019-04-12 17:10:31 -07001876 } else {
1877 resolverComparator =
1878 new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
1879 getReferrerPackageName(), null);
1880 }
1881
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001882 return new ChooserListController(
1883 this,
1884 mPm,
1885 getTargetIntent(),
1886 getReferrerPackageName(),
George Hodulikc681ce42019-04-12 17:10:31 -07001887 mLaunchedFromUid,
1888 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001889 }
1890
Matt Pietal26038402019-01-08 07:29:34 -05001891 @VisibleForTesting
1892 protected Bitmap loadThumbnail(Uri uri, Size size) {
1893 if (uri == null || size == null) {
1894 return null;
1895 }
1896
1897 try {
Matt Pietal46d828c2019-02-05 08:07:07 -05001898 return ImageUtils.loadThumbnail(getContentResolver(), uri, size);
1899 } catch (IOException | NullPointerException | SecurityException ex) {
Matt Pietal62532e52019-05-07 09:51:37 -04001900 logContentPreviewWarning(uri);
Matt Pietal26038402019-01-08 07:29:34 -05001901 }
1902 return null;
1903 }
1904
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001905 interface ChooserTargetInfo extends TargetInfo {
1906 float getModifiedScore();
1907
1908 ChooserTarget getChooserTarget();
Matt Pietal9d501432019-04-12 10:05:29 -04001909
1910 /**
1911 * Do not label as 'equals', since this doesn't quite work
1912 * as intended with java 8.
1913 */
1914 default boolean isSimilar(ChooserTargetInfo other) {
1915 if (other == null) return false;
1916
1917 ChooserTarget ct1 = getChooserTarget();
1918 ChooserTarget ct2 = other.getChooserTarget();
1919
1920 // If either is null, there is not enough info to make an informed decision
1921 // about equality, so just exit
1922 if (ct1 == null || ct2 == null) return false;
1923
1924 if (ct1.getComponentName().equals(ct2.getComponentName())
1925 && TextUtils.equals(getDisplayLabel(), other.getDisplayLabel())
1926 && TextUtils.equals(getExtendedInfo(), other.getExtendedInfo())) {
1927 return true;
1928 }
1929
1930 return false;
1931 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001932 }
1933
1934 /**
1935 * Distinguish between targets that selectable by the user, vs those that are
1936 * placeholders for the system while information is loading in an async manner.
1937 */
1938 abstract class NotSelectableTargetInfo implements ChooserTargetInfo {
1939
1940 public Intent getResolvedIntent() {
1941 return null;
1942 }
1943
1944 public ComponentName getResolvedComponentName() {
1945 return null;
1946 }
1947
1948 public boolean start(Activity activity, Bundle options) {
1949 return false;
1950 }
1951
1952 public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
1953 return false;
1954 }
1955
1956 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
1957 return false;
1958 }
1959
1960 public ResolveInfo getResolveInfo() {
1961 return null;
1962 }
1963
1964 public CharSequence getDisplayLabel() {
1965 return null;
1966 }
1967
1968 public CharSequence getExtendedInfo() {
1969 return null;
1970 }
1971
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001972 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
1973 return null;
1974 }
1975
1976 public List<Intent> getAllSourceIntents() {
1977 return null;
1978 }
1979
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001980 public float getModifiedScore() {
Matt Pietalfbfa0492019-04-01 11:29:56 -04001981 return -0.1f;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001982 }
1983
1984 public ChooserTarget getChooserTarget() {
1985 return null;
1986 }
Matt Pietala4b30072019-04-04 13:44:36 -04001987
1988 public boolean isSuspended() {
1989 return false;
1990 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001991 }
1992
1993 final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
1994 public Drawable getDisplayIcon() {
Mike Digmanac1d88c2019-04-18 15:15:55 -07001995 AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
1996 getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
1997 avd.start(); // Start animation after generation
1998 return avd;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001999 }
2000 }
2001
2002
2003 final class EmptyTargetInfo extends NotSelectableTargetInfo {
2004 public Drawable getDisplayIcon() {
2005 return null;
2006 }
2007 }
2008
2009 final class SelectableTargetInfo implements ChooserTargetInfo {
Adam Powell2ed547e2015-04-29 18:45:04 -07002010 private final DisplayResolveInfo mSourceInfo;
Adam Powell0ccc0e92015-04-23 17:19:37 -07002011 private final ResolveInfo mBackupResolveInfo;
Adam Powell24428412015-04-01 17:19:56 -07002012 private final ChooserTarget mChooserTarget;
Matt Pietal9d501432019-04-12 10:05:29 -04002013 private final String mDisplayLabel;
Adam Powell7d758002015-05-06 17:49:36 -07002014 private Drawable mBadgeIcon = null;
Alan Viverettece5d92c2015-07-31 16:46:56 -04002015 private CharSequence mBadgeContentDescription;
Adam Powell13036be2015-05-12 14:43:56 -07002016 private Drawable mDisplayIcon;
Adam Powell2ed547e2015-04-29 18:45:04 -07002017 private final Intent mFillInIntent;
2018 private final int mFillInFlags;
Adam Powella182e452015-07-06 16:57:56 -07002019 private final float mModifiedScore;
Matt Pietalab986b52019-04-10 10:14:32 -04002020 private boolean mIsSuspended = false;
Adam Powell24428412015-04-01 17:19:56 -07002021
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002022 SelectableTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
Adam Powella182e452015-07-06 16:57:56 -07002023 float modifiedScore) {
Adam Powell24428412015-04-01 17:19:56 -07002024 mSourceInfo = sourceInfo;
2025 mChooserTarget = chooserTarget;
Adam Powella182e452015-07-06 16:57:56 -07002026 mModifiedScore = modifiedScore;
Adam Powell7d758002015-05-06 17:49:36 -07002027 if (sourceInfo != null) {
2028 final ResolveInfo ri = sourceInfo.getResolveInfo();
2029 if (ri != null) {
2030 final ActivityInfo ai = ri.activityInfo;
2031 if (ai != null && ai.applicationInfo != null) {
Alan Viverettece5d92c2015-07-31 16:46:56 -04002032 final PackageManager pm = getPackageManager();
2033 mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
2034 mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
Matt Pietalab986b52019-04-10 10:14:32 -04002035 mIsSuspended =
2036 (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
Adam Powell7d758002015-05-06 17:49:36 -07002037 }
2038 }
2039 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002040 // TODO(b/121287224): do this in the background thread, and only for selected targets
2041 mDisplayIcon = getChooserTargetIconDrawable(chooserTarget);
Adam Powell0ccc0e92015-04-23 17:19:37 -07002042
2043 if (sourceInfo != null) {
2044 mBackupResolveInfo = null;
2045 } else {
2046 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
2047 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002048
2049 mFillInIntent = null;
2050 mFillInFlags = 0;
Matt Pietal9d501432019-04-12 10:05:29 -04002051
2052 mDisplayLabel = sanitizeDisplayLabel(chooserTarget.getTitle());
Adam Powell2ed547e2015-04-29 18:45:04 -07002053 }
2054
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002055 private SelectableTargetInfo(SelectableTargetInfo other, Intent fillInIntent, int flags) {
Adam Powell2ed547e2015-04-29 18:45:04 -07002056 mSourceInfo = other.mSourceInfo;
2057 mBackupResolveInfo = other.mBackupResolveInfo;
2058 mChooserTarget = other.mChooserTarget;
Adam Powell7d758002015-05-06 17:49:36 -07002059 mBadgeIcon = other.mBadgeIcon;
Alan Viverettece5d92c2015-07-31 16:46:56 -04002060 mBadgeContentDescription = other.mBadgeContentDescription;
Adam Powell2ed547e2015-04-29 18:45:04 -07002061 mDisplayIcon = other.mDisplayIcon;
2062 mFillInIntent = fillInIntent;
2063 mFillInFlags = flags;
Adam Powella182e452015-07-06 16:57:56 -07002064 mModifiedScore = other.mModifiedScore;
Matt Pietal9d501432019-04-12 10:05:29 -04002065
2066 mDisplayLabel = sanitizeDisplayLabel(mChooserTarget.getTitle());
2067 }
2068
2069 private String sanitizeDisplayLabel(CharSequence label) {
2070 SpannableStringBuilder sb = new SpannableStringBuilder(label);
2071 sb.clearSpans();
2072 return sb.toString();
Adam Powella182e452015-07-06 16:57:56 -07002073 }
2074
Matt Pietala4b30072019-04-04 13:44:36 -04002075 public boolean isSuspended() {
2076 return mIsSuspended;
2077 }
2078
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002079 /**
2080 * Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
2081 * the call to LauncherApps#getShortcuts(ShortcutQuery).
2082 */
2083 // TODO(121287224): Refactor code to apply the suggestion above
2084 private Drawable getChooserTargetIconDrawable(ChooserTarget target) {
Mike Digman9c4ae502019-03-19 17:02:25 -07002085 Drawable directShareIcon = null;
2086
2087 // First get the target drawable and associated activity info
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002088 final Icon icon = target.getIcon();
2089 if (icon != null) {
Mike Digman9c4ae502019-03-19 17:02:25 -07002090 directShareIcon = icon.loadDrawable(ChooserActivity.this);
2091 } else if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
2092 Bundle extras = target.getIntentExtras();
2093 if (extras != null && extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
2094 CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
2095 LauncherApps launcherApps = (LauncherApps) getSystemService(
2096 Context.LAUNCHER_APPS_SERVICE);
2097 final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
2098 q.setPackage(target.getComponentName().getPackageName());
2099 q.setShortcutIds(Arrays.asList(shortcutId.toString()));
2100 q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
2101 final List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(q, getUser());
2102 if (shortcuts != null && shortcuts.size() > 0) {
2103 directShareIcon = launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
2104 }
2105 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002106 }
2107
Mike Digman9c4ae502019-03-19 17:02:25 -07002108 if (directShareIcon == null) return null;
2109
2110 ActivityInfo info = null;
2111 try {
2112 info = mPm.getActivityInfo(target.getComponentName(), 0);
2113 } catch (NameNotFoundException error) {
2114 Log.e(TAG, "Could not find activity associated with ChooserTarget");
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002115 }
2116
Mike Digman9c4ae502019-03-19 17:02:25 -07002117 if (info == null) return null;
2118
2119 // Now fetch app icon and raster with no badging even in work profile
Mike Digmanb2e5e712019-04-19 15:49:10 -07002120 Bitmap appIcon = makePresentationGetter(info).getIconBitmap(
2121 UserHandle.getUserHandleForUid(UserHandle.myUserId()));
Mike Digman9c4ae502019-03-19 17:02:25 -07002122
2123 // Raster target drawable with appIcon as a badge
2124 SimpleIconFactory sif = SimpleIconFactory.obtain(ChooserActivity.this);
2125 Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
2126 sif.recycle();
2127
2128 return new BitmapDrawable(getResources(), directShareBadgedIcon);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002129 }
2130
Adam Powella182e452015-07-06 16:57:56 -07002131 public float getModifiedScore() {
2132 return mModifiedScore;
Adam Powell24428412015-04-01 17:19:56 -07002133 }
2134
2135 @Override
2136 public Intent getResolvedIntent() {
Adam Powell7d758002015-05-06 17:49:36 -07002137 if (mSourceInfo != null) {
Adam Powell0ccc0e92015-04-23 17:19:37 -07002138 return mSourceInfo.getResolvedIntent();
2139 }
Adam Powell52c39212016-04-07 15:14:18 -07002140
2141 final Intent targetIntent = new Intent(getTargetIntent());
2142 targetIntent.setComponent(mChooserTarget.getComponentName());
2143 targetIntent.putExtras(mChooserTarget.getIntentExtras());
2144 return targetIntent;
Adam Powell24428412015-04-01 17:19:56 -07002145 }
2146
2147 @Override
2148 public ComponentName getResolvedComponentName() {
Adam Powell0ccc0e92015-04-23 17:19:37 -07002149 if (mSourceInfo != null) {
2150 return mSourceInfo.getResolvedComponentName();
2151 } else if (mBackupResolveInfo != null) {
2152 return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
2153 mBackupResolveInfo.activityInfo.name);
2154 }
2155 return null;
2156 }
2157
Adam Powell666d82a2015-07-15 20:14:57 -07002158 private Intent getBaseIntentToSend() {
Adam Powell52c39212016-04-07 15:14:18 -07002159 Intent result = getResolvedIntent();
Adam Powell2ed547e2015-04-29 18:45:04 -07002160 if (result == null) {
Adam Powell666d82a2015-07-15 20:14:57 -07002161 Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
Adam Powell13036be2015-05-12 14:43:56 -07002162 } else {
Adam Powell2ed547e2015-04-29 18:45:04 -07002163 result = new Intent(result);
Adam Powell13036be2015-05-12 14:43:56 -07002164 if (mFillInIntent != null) {
2165 result.fillIn(mFillInIntent, mFillInFlags);
2166 }
2167 result.fillIn(mReferrerFillInIntent, 0);
Adam Powell2ed547e2015-04-29 18:45:04 -07002168 }
2169 return result;
Adam Powell24428412015-04-01 17:19:56 -07002170 }
2171
2172 @Override
2173 public boolean start(Activity activity, Bundle options) {
Adam Powell666d82a2015-07-15 20:14:57 -07002174 throw new RuntimeException("ChooserTargets should be started as caller.");
Adam Powell24428412015-04-01 17:19:56 -07002175 }
2176
2177 @Override
Alison Cichowlas3e340502018-08-07 17:15:01 -04002178 public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
Adam Powell666d82a2015-07-15 20:14:57 -07002179 final Intent intent = getBaseIntentToSend();
Adam Powell2ed547e2015-04-29 18:45:04 -07002180 if (intent == null) {
2181 return false;
2182 }
Adam Powell666d82a2015-07-15 20:14:57 -07002183 intent.setComponent(mChooserTarget.getComponentName());
Makoto Onuki99302b52017-03-29 12:42:26 -07002184 intent.putExtras(mChooserTarget.getIntentExtras());
Adam Powell52c39212016-04-07 15:14:18 -07002185
2186 // Important: we will ignore the target security checks in ActivityManager
2187 // if and only if the ChooserTarget's target package is the same package
2188 // where we got the ChooserTargetService that provided it. This lets a
2189 // ChooserTargetService provide a non-exported or permission-guarded target
2190 // to the chooser for the user to pick.
2191 //
2192 // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
2193 // so we'll obey the caller's normal security checks.
2194 final boolean ignoreTargetSecurity = mSourceInfo != null
2195 && mSourceInfo.getResolvedComponentName().getPackageName()
2196 .equals(mChooserTarget.getComponentName().getPackageName());
Alison Cichowlas3e340502018-08-07 17:15:01 -04002197 return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
Adam Powell24428412015-04-01 17:19:56 -07002198 }
2199
2200 @Override
2201 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
Adam Powell666d82a2015-07-15 20:14:57 -07002202 throw new RuntimeException("ChooserTargets should be started as caller.");
Adam Powell24428412015-04-01 17:19:56 -07002203 }
2204
2205 @Override
2206 public ResolveInfo getResolveInfo() {
Adam Powell0ccc0e92015-04-23 17:19:37 -07002207 return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
Adam Powell24428412015-04-01 17:19:56 -07002208 }
2209
2210 @Override
2211 public CharSequence getDisplayLabel() {
Matt Pietal9d501432019-04-12 10:05:29 -04002212 return mDisplayLabel;
Adam Powell24428412015-04-01 17:19:56 -07002213 }
2214
2215 @Override
2216 public CharSequence getExtendedInfo() {
Adam Powell00f4aad2015-09-17 13:38:16 -07002217 // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
2218 return null;
Adam Powell24428412015-04-01 17:19:56 -07002219 }
2220
2221 @Override
2222 public Drawable getDisplayIcon() {
2223 return mDisplayIcon;
2224 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002225
George Hodulikf2b0d342019-01-25 12:43:54 -08002226 public ChooserTarget getChooserTarget() {
2227 return mChooserTarget;
2228 }
2229
Alan Viverettece5d92c2015-07-31 16:46:56 -04002230 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07002231 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002232 return new SelectableTargetInfo(this, fillInIntent, flags);
Adam Powell2ed547e2015-04-29 18:45:04 -07002233 }
2234
2235 @Override
2236 public List<Intent> getAllSourceIntents() {
2237 final List<Intent> results = new ArrayList<>();
2238 if (mSourceInfo != null) {
2239 // We only queried the service for the first one in our sourceinfo.
2240 results.add(mSourceInfo.getAllSourceIntents().get(0));
2241 }
2242 return results;
2243 }
Adam Powell24428412015-04-01 17:19:56 -07002244 }
2245
Matt Pietal5b648562019-03-12 07:40:26 -04002246 private void handleScroll(View view, int x, int y, int oldx, int oldy) {
2247 if (mChooserRowAdapter != null) {
2248 mChooserRowAdapter.handleScroll(view, y, oldy);
2249 }
2250 }
2251
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002252 /*
2253 * Need to dynamically adjust how many icons can fit per row before we add them,
2254 * which also means setting the correct offset to initially show the content
2255 * preview area + 2 rows of targets
2256 */
2257 private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
2258 int oldTop, int oldRight, int oldBottom) {
2259 if (mChooserRowAdapter == null || mAdapterView == null) {
2260 return;
2261 }
2262
Matt Pietalab73a882019-06-05 07:04:55 -04002263 final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
Matt Pietale7cacab2019-05-23 07:21:36 -04002264 if (mChooserRowAdapter.consumeLayoutRequest()
2265 || mChooserRowAdapter.calculateChooserTargetWidth(availableWidth)
Matt Pietalab73a882019-06-05 07:04:55 -04002266 || mAdapterView.getAdapter() == null
2267 || availableWidth != mCurrAvailableWidth) {
2268 mCurrAvailableWidth = availableWidth;
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002269 mAdapterView.setAdapter(mChooserRowAdapter);
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002270
2271 getMainThreadHandler().post(() -> {
2272 if (mResolverDrawerLayout == null || mChooserRowAdapter == null) {
2273 return;
2274 }
2275
Matt Pietal800136a2019-05-08 07:46:39 -04002276 final int bottomInset = mSystemWindowInsets != null
2277 ? mSystemWindowInsets.bottom : 0;
2278 int offset = bottomInset;
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002279 int rowsToShow = mChooserRowAdapter.getContentPreviewRowCount()
Matt Pietal74c6ed02019-04-18 13:38:46 -04002280 + mChooserRowAdapter.getProfileRowCount()
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002281 + mChooserRowAdapter.getServiceTargetRowCount()
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002282 + mChooserRowAdapter.getCallerAndRankedTargetRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002283
2284 // then this is most likely not a SEND_* action, so check
2285 // the app target count
2286 if (rowsToShow == 0) {
2287 rowsToShow = mChooserRowAdapter.getCount();
2288 }
2289
2290 // still zero? then use a default height and leave, which
2291 // can happen when there are no targets to show
2292 if (rowsToShow == 0) {
Matt Pietal800136a2019-05-08 07:46:39 -04002293 offset += getResources().getDimensionPixelSize(
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002294 R.dimen.chooser_max_collapsed_height);
2295 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2296 return;
2297 }
2298
Matt Pietal394ebd02019-05-03 07:36:21 -04002299 int directShareHeight = 0;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002300 rowsToShow = Math.min(4, rowsToShow);
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002301 for (int i = 0; i < Math.min(rowsToShow, mAdapterView.getChildCount()); i++) {
Matt Pietal394ebd02019-05-03 07:36:21 -04002302 View child = mAdapterView.getChildAt(i);
2303 int height = child.getHeight();
2304 offset += height;
2305
2306 if (child.getTag() != null
2307 && (child.getTag() instanceof DirectShareViewHolder)) {
2308 directShareHeight = height;
2309 }
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002310 }
2311
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002312 boolean isExpandable = getResources().getConfiguration().orientation
2313 == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
2314 if (directShareHeight != 0 && isSendAction(getTargetIntent()) && isExpandable) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002315 // make sure to leave room for direct share 4->8 expansion
Matt Pietal394ebd02019-05-03 07:36:21 -04002316 int requiredExpansionHeight =
2317 (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal800136a2019-05-08 07:46:39 -04002318 int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
Matt Pietal394ebd02019-05-03 07:36:21 -04002319 int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
Matt Pietal800136a2019-05-08 07:46:39 -04002320 - requiredExpansionHeight - topInset - bottomInset;
Matt Pietal394ebd02019-05-03 07:36:21 -04002321
2322 offset = Math.min(offset, minHeight);
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002323 }
2324
Matt Pietal399e8c72019-04-04 15:49:48 -04002325 mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top));
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002326 });
2327 }
2328 }
2329
Adam Powell24428412015-04-01 17:19:56 -07002330 public class ChooserListAdapter extends ResolveListAdapter {
Adam Powell7d758002015-05-06 17:49:36 -07002331 public static final int TARGET_BAD = -1;
2332 public static final int TARGET_CALLER = 0;
2333 public static final int TARGET_SERVICE = 1;
2334 public static final int TARGET_STANDARD = 2;
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002335 public static final int TARGET_STANDARD_AZ = 3;
Adam Powell7d758002015-05-06 17:49:36 -07002336
Matt Pietal5b648562019-03-12 07:40:26 -04002337 private static final int MAX_SUGGESTED_APP_TARGETS = 4;
Matt Pietal791b1c32019-04-30 13:36:49 -04002338 private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
Adam Powella182e452015-07-06 16:57:56 -07002339
Matt Pietal5b648562019-03-12 07:40:26 -04002340 private static final int MAX_SERVICE_TARGETS = 8;
2341
Matt Pietal3ed20a72019-06-24 12:14:52 -04002342 private final int mMaxShortcutTargetsPerApp =
2343 getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp);
2344
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002345 private int mNumShortcutResults = 0;
2346
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002347 // Reserve spots for incoming direct share targets by adding placeholders
2348 private ChooserTargetInfo mPlaceHolderTargetInfo = new PlaceHolderTargetInfo();
Matt Pietal5b648562019-03-12 07:40:26 -04002349 private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
Adam Powell7d758002015-05-06 17:49:36 -07002350 private final List<TargetInfo> mCallerTargets = new ArrayList<>();
Dan Sandlerf5e17692018-06-04 22:13:40 -04002351
Adam Powella182e452015-07-06 16:57:56 -07002352 private final BaseChooserTargetComparator mBaseTargetComparator
2353 = new BaseChooserTargetComparator();
2354
Adam Powell7d758002015-05-06 17:49:36 -07002355 public ChooserListAdapter(Context context, List<Intent> payloadIntents,
2356 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002357 boolean filterLastUsed, ResolverListController resolverListController) {
Adam Powell7d758002015-05-06 17:49:36 -07002358 // Don't send the initial intents through the shared ResolverActivity path,
2359 // we want to separate them into a different section.
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002360 super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed,
2361 resolverListController);
Adam Powell0ccc0e92015-04-23 17:19:37 -07002362
Matt Pietal5b648562019-03-12 07:40:26 -04002363 createPlaceHolders();
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002364
Adam Powell7d758002015-05-06 17:49:36 -07002365 if (initialIntents != null) {
2366 final PackageManager pm = getPackageManager();
2367 for (int i = 0; i < initialIntents.length; i++) {
2368 final Intent ii = initialIntents[i];
2369 if (ii == null) {
2370 continue;
2371 }
Adam Powell86100d12016-05-12 16:13:17 -07002372
2373 // We reimplement Intent#resolveActivityInfo here because if we have an
2374 // implicit intent, we want the ResolveInfo returned by PackageManager
2375 // instead of one we reconstruct ourselves. The ResolveInfo returned might
2376 // have extra metadata and resolvePackageName set and we want to respect that.
2377 ResolveInfo ri = null;
2378 ActivityInfo ai = null;
2379 final ComponentName cn = ii.getComponent();
2380 if (cn != null) {
2381 try {
2382 ai = pm.getActivityInfo(ii.getComponent(), 0);
2383 ri = new ResolveInfo();
2384 ri.activityInfo = ai;
2385 } catch (PackageManager.NameNotFoundException ignored) {
2386 // ai will == null below
2387 }
2388 }
2389 if (ai == null) {
2390 ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
2391 ai = ri != null ? ri.activityInfo : null;
2392 }
Adam Powell7d758002015-05-06 17:49:36 -07002393 if (ai == null) {
2394 Log.w(TAG, "No activity found for " + ii);
2395 continue;
2396 }
Adam Powell7d758002015-05-06 17:49:36 -07002397 UserManager userManager =
2398 (UserManager) getSystemService(Context.USER_SERVICE);
Adam Powell7d758002015-05-06 17:49:36 -07002399 if (ii instanceof LabeledIntent) {
Matt Pietal26038402019-01-08 07:29:34 -05002400 LabeledIntent li = (LabeledIntent) ii;
Adam Powell7d758002015-05-06 17:49:36 -07002401 ri.resolvePackageName = li.getSourcePackage();
2402 ri.labelRes = li.getLabelResource();
2403 ri.nonLocalizedLabel = li.getNonLocalizedLabel();
2404 ri.icon = li.getIconResource();
Sudheer Shanka9ded7602015-05-19 21:17:25 +01002405 ri.iconResourceId = ri.icon;
2406 }
2407 if (userManager.isManagedProfile()) {
2408 ri.noResourceId = true;
2409 ri.icon = 0;
Adam Powell7d758002015-05-06 17:49:36 -07002410 }
Mike Digmanba232682019-03-27 14:55:26 -07002411 ResolveInfoPresentationGetter getter = makePresentationGetter(ri);
Adam Powell7d758002015-05-06 17:49:36 -07002412 mCallerTargets.add(new DisplayResolveInfo(ii, ri,
Mike Digmanba232682019-03-27 14:55:26 -07002413 getter.getLabel(), getter.getSubLabel(), ii));
Adam Powelld974c7b2015-04-28 15:41:46 -07002414 }
Adam Powell0ccc0e92015-04-23 17:19:37 -07002415 }
Adam Powell24428412015-04-01 17:19:56 -07002416 }
2417
Matt Pietalaf044ae2019-03-29 06:53:53 -04002418 @Override
Matt Pietalab73a882019-06-05 07:04:55 -04002419 public void handlePackagesChanged() {
2420 if (DEBUG) {
2421 Log.d(TAG, "clearing queryTargets on package change");
2422 }
2423 createPlaceHolders();
2424 mServicesRequested.clear();
2425 notifyDataSetChanged();
2426
2427 super.handlePackagesChanged();
2428 }
2429
2430 @Override
Matt Pietalaf044ae2019-03-29 06:53:53 -04002431 public void notifyDataSetChanged() {
2432 if (!mListViewDataChanged) {
Matt Pietalab73a882019-06-05 07:04:55 -04002433 mChooserHandler.sendEmptyMessageDelayed(ChooserHandler.LIST_VIEW_UPDATE_MESSAGE,
Matt Pietalaf044ae2019-03-29 06:53:53 -04002434 LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
2435 mListViewDataChanged = true;
2436 }
2437 }
2438
2439 private void refreshListView() {
2440 if (mListViewDataChanged) {
2441 super.notifyDataSetChanged();
2442 }
2443 mListViewDataChanged = false;
2444 }
2445
2446
Matt Pietal5b648562019-03-12 07:40:26 -04002447 private void createPlaceHolders() {
Matt Pietalab73a882019-06-05 07:04:55 -04002448 mNumShortcutResults = 0;
Matt Pietal5b648562019-03-12 07:40:26 -04002449 mServiceTargets.clear();
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002450 for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
Matt Pietal5b648562019-03-12 07:40:26 -04002451 mServiceTargets.add(mPlaceHolderTargetInfo);
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002452 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002453 }
2454
Adam Powell24428412015-04-01 17:19:56 -07002455 @Override
Adam Powell7d758002015-05-06 17:49:36 -07002456 public View onCreateView(ViewGroup parent) {
Adam Powell24428412015-04-01 17:19:56 -07002457 return mInflater.inflate(
2458 com.android.internal.R.layout.resolve_grid_item, parent, false);
2459 }
2460
2461 @Override
Mike Digmanbd7e0df2019-04-23 14:52:45 -07002462 protected void onBindView(View view, TargetInfo info) {
2463 super.onBindView(view, info);
2464
Mike Digman4b83c212019-05-03 10:17:35 -07002465 // If target is loading, show a special placeholder shape in the label, make unclickable
Mike Digmanbd7e0df2019-04-23 14:52:45 -07002466 final ViewHolder holder = (ViewHolder) view.getTag();
2467 if (info instanceof PlaceHolderTargetInfo) {
2468 final int maxWidth = getResources().getDimensionPixelSize(
2469 R.dimen.chooser_direct_share_label_placeholder_max_width);
2470 holder.text.setMaxWidth(maxWidth);
2471 holder.text.setBackground(getResources().getDrawable(
2472 R.drawable.chooser_direct_share_label_placeholder, getTheme()));
Mike Digman4b83c212019-05-03 10:17:35 -07002473 // Prevent rippling by removing background containing ripple
2474 holder.itemView.setBackground(null);
Mike Digmanbd7e0df2019-04-23 14:52:45 -07002475 } else {
2476 holder.text.setMaxWidth(Integer.MAX_VALUE);
2477 holder.text.setBackground(null);
Mike Digman4b83c212019-05-03 10:17:35 -07002478 holder.itemView.setBackground(holder.defaultItemViewBackground);
Mike Digmanbd7e0df2019-04-23 14:52:45 -07002479 }
2480 }
2481
2482 @Override
Adam Powell24428412015-04-01 17:19:56 -07002483 public void onListRebuilt() {
Matt Pietal030bd842019-05-29 07:14:14 -04002484 updateAlphabeticalList();
2485
Ng Zhi And3ec5fc2018-05-10 09:13:00 -07002486 // don't support direct share on low ram devices
2487 if (ActivityManager.isLowRamDeviceStatic()) {
2488 return;
2489 }
2490
George Hodulik145b3a52019-03-27 11:18:43 -07002491 if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
2492 || USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002493 if (DEBUG) {
2494 Log.d(TAG, "querying direct share targets from ShortcutManager");
2495 }
Matt Pietalaf044ae2019-03-29 06:53:53 -04002496
George Hodulik3f399f22019-04-26 16:17:54 -07002497 queryDirectShareTargets(this, false);
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -08002498 }
2499 if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
2500 if (DEBUG) {
2501 Log.d(TAG, "List built querying services");
2502 }
Matt Pietalaf044ae2019-03-29 06:53:53 -04002503
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002504 queryTargetServices(this);
2505 }
Adam Powell24428412015-04-01 17:19:56 -07002506 }
2507
2508 @Override
Adam Powellc6d5e3a2015-04-23 12:22:20 -07002509 public boolean shouldGetResolvedFilter() {
2510 return true;
2511 }
2512
2513 @Override
Adam Powell24428412015-04-01 17:19:56 -07002514 public int getCount() {
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002515 return getRankedTargetCount() + getAlphaTargetCount()
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002516 + getSelectableServiceTargetCount() + getCallerTargetCount();
Adam Powell24428412015-04-01 17:19:56 -07002517 }
2518
Adam Powell50077352015-05-26 18:01:55 -07002519 @Override
2520 public int getUnfilteredCount() {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002521 int appTargets = super.getUnfilteredCount();
Alison Cichowlas13314612019-04-11 15:20:39 -04002522 if (appTargets > getMaxRankedTargets()) {
2523 appTargets = appTargets + getMaxRankedTargets();
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002524 }
2525 return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
Adam Powell50077352015-05-26 18:01:55 -07002526 }
2527
Alison Cichowlas13314612019-04-11 15:20:39 -04002528
Adam Powell50077352015-05-26 18:01:55 -07002529 public int getCallerTargetCount() {
Matt Pietal5b648562019-03-12 07:40:26 -04002530 return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS);
Adam Powell7d758002015-05-06 17:49:36 -07002531 }
2532
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002533 /**
2534 * Filter out placeholders and non-selectable service targets
2535 */
2536 public int getSelectableServiceTargetCount() {
2537 int count = 0;
2538 for (ChooserTargetInfo info : mServiceTargets) {
2539 if (info instanceof SelectableTargetInfo) {
2540 count++;
2541 }
Adam Powell565943f2016-02-11 10:29:05 -08002542 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002543 return count;
2544 }
2545
2546 public int getServiceTargetCount() {
Matt Pietal6e88b512019-06-10 10:20:15 -04002547 if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
Matt Pietal95574b02019-03-13 08:12:25 -04002548 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
2549 }
2550
2551 return 0;
Adam Powell7d758002015-05-06 17:49:36 -07002552 }
2553
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002554 int getAlphaTargetCount() {
2555 int standardCount = super.getCount();
Alison Cichowlas13314612019-04-11 15:20:39 -04002556 return standardCount > getMaxRankedTargets() ? standardCount : 0;
Adam Powell7d758002015-05-06 17:49:36 -07002557 }
2558
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002559 int getRankedTargetCount() {
Alison Cichowlas13314612019-04-11 15:20:39 -04002560 int spacesAvailable = getMaxRankedTargets() - getCallerTargetCount();
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002561 return Math.min(spacesAvailable, super.getCount());
2562 }
2563
Alison Cichowlas13314612019-04-11 15:20:39 -04002564 private int getMaxRankedTargets() {
2565 return mChooserRowAdapter == null ? 4 : mChooserRowAdapter.getMaxTargetsPerRow();
2566 }
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002567
Adam Powell7d758002015-05-06 17:49:36 -07002568 public int getPositionTargetType(int position) {
2569 int offset = 0;
2570
Adam Powella182e452015-07-06 16:57:56 -07002571 final int serviceTargetCount = getServiceTargetCount();
Matt Pietal5b648562019-03-12 07:40:26 -04002572 if (position < serviceTargetCount) {
Adam Powell7d758002015-05-06 17:49:36 -07002573 return TARGET_SERVICE;
2574 }
2575 offset += serviceTargetCount;
2576
Matt Pietal5b648562019-03-12 07:40:26 -04002577 final int callerTargetCount = getCallerTargetCount();
2578 if (position - offset < callerTargetCount) {
2579 return TARGET_CALLER;
2580 }
2581 offset += callerTargetCount;
2582
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002583 final int rankedTargetCount = getRankedTargetCount();
2584 if (position - offset < rankedTargetCount) {
Adam Powell7d758002015-05-06 17:49:36 -07002585 return TARGET_STANDARD;
2586 }
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002587 offset += rankedTargetCount;
2588
2589 final int standardTargetCount = getAlphaTargetCount();
2590 if (position - offset < standardTargetCount) {
2591 return TARGET_STANDARD_AZ;
2592 }
Adam Powell7d758002015-05-06 17:49:36 -07002593
2594 return TARGET_BAD;
2595 }
2596
Adam Powell24428412015-04-01 17:19:56 -07002597 @Override
2598 public TargetInfo getItem(int position) {
Adam Powell50077352015-05-26 18:01:55 -07002599 return targetInfoForPosition(position, true);
2600 }
2601
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002602
2603 /**
2604 * Find target info for a given position.
2605 * Since ChooserActivity displays several sections of content, determine which
2606 * section provides this item.
2607 */
Adam Powell50077352015-05-26 18:01:55 -07002608 @Override
2609 public TargetInfo targetInfoForPosition(int position, boolean filtered) {
Adam Powell24428412015-04-01 17:19:56 -07002610 int offset = 0;
Adam Powell0ccc0e92015-04-23 17:19:37 -07002611
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002612 // Direct share targets
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002613 final int serviceTargetCount = filtered ? getServiceTargetCount() :
2614 getSelectableServiceTargetCount();
Matt Pietal5b648562019-03-12 07:40:26 -04002615 if (position < serviceTargetCount) {
2616 return mServiceTargets.get(position);
Adam Powell0ccc0e92015-04-23 17:19:37 -07002617 }
2618 offset += serviceTargetCount;
2619
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002620 // Targets provided by calling app
Matt Pietal5b648562019-03-12 07:40:26 -04002621 final int callerTargetCount = getCallerTargetCount();
2622 if (position - offset < callerTargetCount) {
2623 return mCallerTargets.get(position - offset);
2624 }
2625 offset += callerTargetCount;
2626
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002627 // Ranked standard app targets
2628 final int rankedTargetCount = getRankedTargetCount();
2629 if (position - offset < rankedTargetCount) {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002630 return filtered ? super.getItem(position - offset)
2631 : getDisplayResolveInfo(position - offset);
2632 }
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002633 offset += rankedTargetCount;
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002634
2635 // Alphabetical complete app target list.
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002636 if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002637 return mSortedList.get(position - offset);
2638 }
2639
2640 return null;
Adam Powell24428412015-04-01 17:19:56 -07002641 }
2642
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002643
Matt Pietalfbfa0492019-04-01 11:29:56 -04002644 /**
2645 * Evaluate targets for inclusion in the direct share area. May not be included
2646 * if score is too low.
2647 */
2648 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
2649 boolean isShortcutResult) {
Matt Pietal26038402019-01-08 07:29:34 -05002650 if (DEBUG) {
2651 Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
2652 + " targets");
2653 }
Dan Sandlerf5e17692018-06-04 22:13:40 -04002654
Matt Pietalfbfa0492019-04-01 11:29:56 -04002655 if (targets.size() == 0) {
2656 return;
2657 }
2658
Matt Pietalfbfa0492019-04-01 11:29:56 -04002659 final float baseScore = getBaseScore(origTarget, isShortcutResult);
Adam Powella182e452015-07-06 16:57:56 -07002660 Collections.sort(targets, mBaseTargetComparator);
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002661
Matt Pietal3ed20a72019-06-24 12:14:52 -04002662 final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
Matt Pietal791b1c32019-04-30 13:36:49 -04002663 : MAX_CHOOSER_TARGETS_PER_APP;
Adam Powella182e452015-07-06 16:57:56 -07002664 float lastScore = 0;
Matt Pietalfbfa0492019-04-01 11:29:56 -04002665 boolean shouldNotify = false;
Matt Pietal791b1c32019-04-30 13:36:49 -04002666 for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
Adam Powella182e452015-07-06 16:57:56 -07002667 final ChooserTarget target = targets.get(i);
2668 float targetScore = target.getScore();
Matt Pietalfbfa0492019-04-01 11:29:56 -04002669 targetScore *= baseScore;
Adam Powella182e452015-07-06 16:57:56 -07002670 if (i > 0 && targetScore >= lastScore) {
2671 // Apply a decay so that the top app can't crowd out everything else.
2672 // This incents ChooserTargetServices to define what's truly better.
2673 targetScore = lastScore * 0.95f;
2674 }
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002675 boolean isInserted = insertServiceTarget(
Matt Pietalfbfa0492019-04-01 11:29:56 -04002676 new SelectableTargetInfo(origTarget, target, targetScore));
Adam Powella182e452015-07-06 16:57:56 -07002677
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002678 if (isInserted && isShortcutResult) {
2679 mNumShortcutResults++;
2680 }
2681
2682 shouldNotify |= isInserted;
2683
Adam Powella182e452015-07-06 16:57:56 -07002684 if (DEBUG) {
2685 Log.d(TAG, " => " + target.toString() + " score=" + targetScore
2686 + " base=" + target.getScore()
2687 + " lastScore=" + lastScore
Matt Pietalfbfa0492019-04-01 11:29:56 -04002688 + " baseScore=" + baseScore);
Adam Powella182e452015-07-06 16:57:56 -07002689 }
2690
2691 lastScore = targetScore;
Adam Powell24428412015-04-01 17:19:56 -07002692 }
2693
Matt Pietalfbfa0492019-04-01 11:29:56 -04002694 if (shouldNotify) {
2695 notifyDataSetChanged();
2696 }
2697 }
Adam Powell24428412015-04-01 17:19:56 -07002698
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002699 private int getNumShortcutResults() {
2700 return mNumShortcutResults;
2701 }
2702
Matt Pietalfbfa0492019-04-01 11:29:56 -04002703 /**
Matt Pietal39d181d2019-05-08 14:48:30 -04002704 * Use the scoring system along with artificial boosts to create up to 4 distinct buckets:
Matt Pietalfbfa0492019-04-01 11:29:56 -04002705 * <ol>
2706 * <li>App-supplied targets
Matt Pietal39d181d2019-05-08 14:48:30 -04002707 * <li>Shortcuts ranked via App Prediction Manager
2708 * <li>Shortcuts ranked via legacy heuristics
Matt Pietalfbfa0492019-04-01 11:29:56 -04002709 * <li>Legacy direct share targets
2710 * </ol>
2711 */
2712 private float getBaseScore(DisplayResolveInfo target, boolean isShortcutResult) {
2713 if (target == null) {
2714 return CALLER_TARGET_SCORE_BOOST;
2715 }
2716
Matt Pietal39d181d2019-05-08 14:48:30 -04002717 if (isShortcutResult && getAppPredictorForDirectShareIfEnabled() != null) {
Matt Pietalfbfa0492019-04-01 11:29:56 -04002718 return SHORTCUT_TARGET_SCORE_BOOST;
2719 }
2720
2721 float score = super.getScore(target);
2722 if (isShortcutResult) {
2723 return score * SHORTCUT_TARGET_SCORE_BOOST;
2724 }
2725
2726 return score;
Adam Powell24428412015-04-01 17:19:56 -07002727 }
2728
Adam Powell565943f2016-02-11 10:29:05 -08002729 /**
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002730 * Calling this marks service target loading complete, and will attempt to no longer
2731 * update the direct share area.
2732 */
2733 public void completeServiceTargetLoading() {
2734 mServiceTargets.removeIf(o -> o instanceof PlaceHolderTargetInfo);
2735
2736 if (mServiceTargets.isEmpty()) {
2737 mServiceTargets.add(new EmptyTargetInfo());
2738 }
2739 notifyDataSetChanged();
2740 }
2741
Matt Pietalfbfa0492019-04-01 11:29:56 -04002742 private boolean insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002743 // Avoid inserting any potentially late results
2744 if (mServiceTargets.size() == 1
2745 && mServiceTargets.get(0) instanceof EmptyTargetInfo) {
Matt Pietalfbfa0492019-04-01 11:29:56 -04002746 return false;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002747 }
2748
Matt Pietal9d501432019-04-12 10:05:29 -04002749 // Check for duplicates and abort if found
2750 for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
2751 if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
2752 return false;
2753 }
2754 }
2755
Matt Pietalfbfa0492019-04-01 11:29:56 -04002756 int currentSize = mServiceTargets.size();
Matt Pietal9d501432019-04-12 10:05:29 -04002757 final float newScore = chooserTargetInfo.getModifiedScore();
Matt Pietalfbfa0492019-04-01 11:29:56 -04002758 for (int i = 0; i < Math.min(currentSize, MAX_SERVICE_TARGETS); i++) {
Adam Powella182e452015-07-06 16:57:56 -07002759 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002760 if (serviceTarget == null) {
2761 mServiceTargets.set(i, chooserTargetInfo);
Matt Pietalfbfa0492019-04-01 11:29:56 -04002762 return true;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002763 } else if (newScore > serviceTarget.getModifiedScore()) {
Adam Powella182e452015-07-06 16:57:56 -07002764 mServiceTargets.add(i, chooserTargetInfo);
Matt Pietalfbfa0492019-04-01 11:29:56 -04002765 return true;
Adam Powella182e452015-07-06 16:57:56 -07002766 }
2767 }
Matt Pietalfbfa0492019-04-01 11:29:56 -04002768
2769 if (currentSize < MAX_SERVICE_TARGETS) {
2770 mServiceTargets.add(chooserTargetInfo);
2771 return true;
2772 }
2773
2774 return false;
Adam Powella182e452015-07-06 16:57:56 -07002775 }
Adam Powell24428412015-04-01 17:19:56 -07002776 }
2777
Adam Powella182e452015-07-06 16:57:56 -07002778 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
2779 @Override
2780 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
2781 // Descending order
Adam Powell77a533f2015-10-16 10:47:32 -07002782 return (int) Math.signum(rhs.getScore() - lhs.getScore());
Adam Powella182e452015-07-06 16:57:56 -07002783 }
2784 }
2785
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002786
Matt Pietal95574b02019-03-13 08:12:25 -04002787 private boolean isSendAction(Intent targetIntent) {
2788 if (targetIntent == null) {
2789 return false;
2790 }
2791
2792 String action = targetIntent.getAction();
2793 if (action == null) {
2794 return false;
2795 }
2796
2797 if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
2798 return true;
2799 }
2800
2801 return false;
2802 }
2803
Adam Powell7d758002015-05-06 17:49:36 -07002804 class ChooserRowAdapter extends BaseAdapter {
2805 private ChooserListAdapter mChooserListAdapter;
2806 private final LayoutInflater mLayoutInflater;
Adam Powell7d758002015-05-06 17:49:36 -07002807
Matt Pietal5b648562019-03-12 07:40:26 -04002808 private DirectShareViewHolder mDirectShareViewHolder;
Matt Pietalab986b52019-04-10 10:14:32 -04002809 private int mChooserTargetWidth = 0;
Mike Digman849a9d12019-04-29 11:20:48 -07002810 private boolean mShowAzLabelIfPoss;
Matt Pietal5b648562019-03-12 07:40:26 -04002811
Matt Pietale7cacab2019-05-23 07:21:36 -04002812 private boolean mHideContentPreview = false;
2813 private boolean mLayoutRequested = false;
2814
Matt Pietal5b648562019-03-12 07:40:26 -04002815 private static final int VIEW_TYPE_DIRECT_SHARE = 0;
2816 private static final int VIEW_TYPE_NORMAL = 1;
Matt Pietal1ef88002019-03-13 10:43:18 -04002817 private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002818 private static final int VIEW_TYPE_PROFILE = 3;
Mike Digmanae730b12019-04-25 11:10:31 -07002819 private static final int VIEW_TYPE_AZ_LABEL = 4;
Matt Pietal5b648562019-03-12 07:40:26 -04002820
Matt Pietalfaedea82019-03-21 10:36:54 -04002821 private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
2822 private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
2823
Mike Digman849a9d12019-04-29 11:20:48 -07002824 private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
2825
Adam Powell7d758002015-05-06 17:49:36 -07002826 public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
2827 mChooserListAdapter = wrappedAdapter;
2828 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
2829
Mike Digman849a9d12019-04-29 11:20:48 -07002830 mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
2831
Adam Powell7d758002015-05-06 17:49:36 -07002832 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
2833 @Override
2834 public void onChanged() {
2835 super.onChanged();
2836 notifyDataSetChanged();
2837 }
2838
2839 @Override
2840 public void onInvalidated() {
2841 super.onInvalidated();
2842 notifyDataSetInvalidated();
2843 }
2844 });
2845 }
2846
Matt Pietalfaedea82019-03-21 10:36:54 -04002847 /**
Matt Pietalab986b52019-04-10 10:14:32 -04002848 * Calculate the chooser target width to maximize space per item
Matt Pietalfaedea82019-03-21 10:36:54 -04002849 *
2850 * @param width The new row width to use for recalculation
Matt Pietalab986b52019-04-10 10:14:32 -04002851 * @return true if the view width has changed
Matt Pietalfaedea82019-03-21 10:36:54 -04002852 */
Matt Pietalab986b52019-04-10 10:14:32 -04002853 public boolean calculateChooserTargetWidth(int width) {
Matt Pietalab986b52019-04-10 10:14:32 -04002854 if (width == 0) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002855 return false;
2856 }
2857
Matt Pietal9d501432019-04-12 10:05:29 -04002858 int newWidth = width / getMaxTargetsPerRow();
Matt Pietalab986b52019-04-10 10:14:32 -04002859 if (newWidth != mChooserTargetWidth) {
2860 mChooserTargetWidth = newWidth;
Matt Pietalfaedea82019-03-21 10:36:54 -04002861 return true;
2862 }
2863
2864 return false;
2865 }
2866
Matt Pietal5b648562019-03-12 07:40:26 -04002867 private int getMaxTargetsPerRow() {
Matt Pietalfaedea82019-03-21 10:36:54 -04002868 int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002869 if (shouldDisplayLandscape(getResources().getConfiguration().orientation)) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002870 maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
2871 }
2872
Matt Pietalab986b52019-04-10 10:14:32 -04002873 return maxTargets;
Matt Pietal5b648562019-03-12 07:40:26 -04002874 }
2875
Matt Pietale7cacab2019-05-23 07:21:36 -04002876 public void hideContentPreview() {
2877 mHideContentPreview = true;
2878 mLayoutRequested = true;
2879 notifyDataSetChanged();
2880 }
2881
2882 public boolean consumeLayoutRequest() {
2883 boolean oldValue = mLayoutRequested;
2884 mLayoutRequested = false;
2885 return oldValue;
2886 }
2887
Adam Powell7d758002015-05-06 17:49:36 -07002888 @Override
Matt Pietal8a8cfc42019-04-30 07:59:14 -04002889 public boolean areAllItemsEnabled() {
2890 return false;
2891 }
2892
2893 @Override
2894 public boolean isEnabled(int position) {
2895 int viewType = getItemViewType(position);
Matt Pietal9462c0b2019-05-16 09:26:33 -04002896 if (viewType == VIEW_TYPE_CONTENT_PREVIEW || viewType == VIEW_TYPE_AZ_LABEL) {
Matt Pietal8a8cfc42019-04-30 07:59:14 -04002897 return false;
2898 }
2899 return true;
2900 }
2901
2902 @Override
Adam Powell7d758002015-05-06 17:49:36 -07002903 public int getCount() {
2904 return (int) (
Matt Pietal1ef88002019-03-13 10:43:18 -04002905 getContentPreviewRowCount()
Matt Pietal74c6ed02019-04-18 13:38:46 -04002906 + getProfileRowCount()
Matt Pietal26038402019-01-08 07:29:34 -05002907 + getServiceTargetRowCount()
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002908 + getCallerAndRankedTargetRowCount()
Mike Digmanae730b12019-04-25 11:10:31 -07002909 + getAzLabelRowCount()
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002910 + Math.ceil(
2911 (float) mChooserListAdapter.getAlphaTargetCount()
2912 / getMaxTargetsPerRow())
Adam Powell7d758002015-05-06 17:49:36 -07002913 );
2914 }
2915
Matt Pietal1ef88002019-03-13 10:43:18 -04002916 public int getContentPreviewRowCount() {
2917 if (!isSendAction(getTargetIntent())) {
2918 return 0;
2919 }
2920
Matt Pietale7cacab2019-05-23 07:21:36 -04002921 if (mHideContentPreview || mChooserListAdapter == null
2922 || mChooserListAdapter.getCount() == 0) {
Matt Pietal1ef88002019-03-13 10:43:18 -04002923 return 0;
2924 }
2925
2926 return 1;
2927 }
2928
Matt Pietal74c6ed02019-04-18 13:38:46 -04002929 public int getProfileRowCount() {
2930 return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
2931 }
2932
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002933 public int getCallerAndRankedTargetRowCount() {
Adam Powell63b31692015-09-28 10:45:00 -07002934 return (int) Math.ceil(
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002935 ((float) mChooserListAdapter.getCallerTargetCount()
2936 + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow());
Adam Powell63b31692015-09-28 10:45:00 -07002937 }
2938
Matt Pietal5b648562019-03-12 07:40:26 -04002939 // There can be at most one row in the listview, that is internally
2940 // a ViewGroup with 2 rows
Adam Powell63b31692015-09-28 10:45:00 -07002941 public int getServiceTargetRowCount() {
Matt Pietal030bd842019-05-29 07:14:14 -04002942 if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
Matt Pietal95574b02019-03-13 08:12:25 -04002943 return 1;
2944 }
2945 return 0;
Adam Powell63b31692015-09-28 10:45:00 -07002946 }
2947
Mike Digmanae730b12019-04-25 11:10:31 -07002948 public int getAzLabelRowCount() {
2949 // Only show a label if the a-z list is showing
Mike Digman849a9d12019-04-29 11:20:48 -07002950 return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
Mike Digmanae730b12019-04-25 11:10:31 -07002951 }
2952
Adam Powell7d758002015-05-06 17:49:36 -07002953 @Override
2954 public Object getItem(int position) {
2955 // We have nothing useful to return here.
2956 return position;
2957 }
2958
2959 @Override
2960 public long getItemId(int position) {
2961 return position;
2962 }
2963
2964 @Override
2965 public View getView(int position, View convertView, ViewGroup parent) {
Adam Powell63b31692015-09-28 10:45:00 -07002966 final RowViewHolder holder;
Matt Pietal5b648562019-03-12 07:40:26 -04002967 int viewType = getItemViewType(position);
2968
Matt Pietal1ef88002019-03-13 10:43:18 -04002969 if (viewType == VIEW_TYPE_CONTENT_PREVIEW) {
2970 return createContentPreviewView(convertView, parent);
2971 }
2972
Matt Pietal74c6ed02019-04-18 13:38:46 -04002973 if (viewType == VIEW_TYPE_PROFILE) {
2974 return createProfileView(convertView, parent);
2975 }
2976
Mike Digmanae730b12019-04-25 11:10:31 -07002977 if (viewType == VIEW_TYPE_AZ_LABEL) {
2978 return createAzLabelView(parent);
2979 }
2980
Adam Powell7d758002015-05-06 17:49:36 -07002981 if (convertView == null) {
Matt Pietal5b648562019-03-12 07:40:26 -04002982 holder = createViewHolder(viewType, parent);
Adam Powell7d758002015-05-06 17:49:36 -07002983 } else {
Adam Powell63b31692015-09-28 10:45:00 -07002984 holder = (RowViewHolder) convertView.getTag();
Adam Powell7d758002015-05-06 17:49:36 -07002985 }
Adam Powell7d758002015-05-06 17:49:36 -07002986
Matt Pietalfaedea82019-03-21 10:36:54 -04002987 bindViewHolder(position, holder);
Matt Pietal5b648562019-03-12 07:40:26 -04002988
2989 return holder.getViewGroup();
Adam Powell7d758002015-05-06 17:49:36 -07002990 }
2991
Matt Pietal5b648562019-03-12 07:40:26 -04002992 @Override
2993 public int getItemViewType(int position) {
Mike Digmanae730b12019-04-25 11:10:31 -07002994 int count;
Matt Pietal1ef88002019-03-13 10:43:18 -04002995
Mike Digmanae730b12019-04-25 11:10:31 -07002996 int countSum = (count = getContentPreviewRowCount());
2997 if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002998
Mike Digmanae730b12019-04-25 11:10:31 -07002999 countSum += (count = getProfileRowCount());
3000 if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
Adam Powell63b31692015-09-28 10:45:00 -07003001
Mike Digmanae730b12019-04-25 11:10:31 -07003002 countSum += (count = getServiceTargetRowCount());
3003 if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
3004
3005 countSum += (count = getCallerAndRankedTargetRowCount());
3006 if (count > 0 && position < countSum) return VIEW_TYPE_NORMAL;
3007
3008 countSum += (count = getAzLabelRowCount());
3009 if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
Matt Pietal5b648562019-03-12 07:40:26 -04003010
3011 return VIEW_TYPE_NORMAL;
3012 }
3013
3014 @Override
3015 public int getViewTypeCount() {
Mike Digmanae730b12019-04-25 11:10:31 -07003016 return 5;
Matt Pietal1ef88002019-03-13 10:43:18 -04003017 }
3018
3019 private ViewGroup createContentPreviewView(View convertView, ViewGroup parent) {
3020 Intent targetIntent = getTargetIntent();
3021 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
3022
3023 if (convertView == null) {
3024 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
3025 .setSubtype(previewType));
3026 }
3027
3028 return displayContentPreview(previewType, targetIntent, mLayoutInflater,
3029 (ViewGroup) convertView, parent);
Matt Pietal5b648562019-03-12 07:40:26 -04003030 }
3031
Matt Pietal74c6ed02019-04-18 13:38:46 -04003032 private View createProfileView(View convertView, ViewGroup parent) {
3033 View profileRow = convertView != null ? convertView : mLayoutInflater.inflate(
3034 R.layout.chooser_profile_row, parent, false);
3035 profileRow.setBackground(
3036 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
3037 mProfileView = profileRow.findViewById(R.id.profile_button);
3038 mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
3039 bindProfileView();
3040 return profileRow;
3041 }
3042
Mike Digmanae730b12019-04-25 11:10:31 -07003043 private View createAzLabelView(ViewGroup parent) {
3044 return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
3045 }
3046
Matt Pietal5b648562019-03-12 07:40:26 -04003047 private RowViewHolder loadViewsIntoRow(RowViewHolder holder) {
3048 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietalab986b52019-04-10 10:14:32 -04003049 final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
3050 MeasureSpec.EXACTLY);
Matt Pietal5b648562019-03-12 07:40:26 -04003051 int columnCount = holder.getColumnCount();
3052
Mike Digmanba232682019-03-27 14:55:26 -07003053 final boolean isDirectShare = holder instanceof DirectShareViewHolder;
3054
Matt Pietal5b648562019-03-12 07:40:26 -04003055 for (int i = 0; i < columnCount; i++) {
Mike Digmanba232682019-03-27 14:55:26 -07003056 final View v = mChooserListAdapter.createView(holder.getRowByIndex(i));
Adam Powell4eb98712015-10-14 13:10:18 -07003057 final int column = i;
Adam Powell63b31692015-09-28 10:45:00 -07003058 v.setOnClickListener(new OnClickListener() {
3059 @Override
3060 public void onClick(View v) {
Matt Pietal5b648562019-03-12 07:40:26 -04003061 startSelected(holder.getItemIndex(column), false, true);
Adam Powell63b31692015-09-28 10:45:00 -07003062 }
3063 });
3064 v.setOnLongClickListener(new OnLongClickListener() {
3065 @Override
3066 public boolean onLongClick(View v) {
Adam Powell23882512016-01-29 10:21:00 -08003067 showTargetDetails(
Adam Powell4eb98712015-10-14 13:10:18 -07003068 mChooserListAdapter.resolveInfoForPosition(
Matt Pietal5b648562019-03-12 07:40:26 -04003069 holder.getItemIndex(column), true));
Adam Powell63b31692015-09-28 10:45:00 -07003070 return true;
3071 }
3072 });
Matt Pietal5b648562019-03-12 07:40:26 -04003073 ViewGroup row = holder.addView(i, v);
Adam Powell63b31692015-09-28 10:45:00 -07003074
Mike Digmanba232682019-03-27 14:55:26 -07003075 // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
3076 // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be
3077 // done before measuring.
3078 if (isDirectShare) {
3079 final ViewHolder vh = (ViewHolder) v.getTag();
3080 vh.text.setLines(2);
3081 vh.text.setHorizontallyScrolling(false);
3082 vh.text2.setVisibility(View.GONE);
Adam Powell63b31692015-09-28 10:45:00 -07003083 }
Mike Digmanba232682019-03-27 14:55:26 -07003084
3085 // Force height to be a given so we don't have visual disruption during scaling.
Matt Pietalab986b52019-04-10 10:14:32 -04003086 v.measure(exactSpec, spec);
3087 setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight());
Adam Powell63b31692015-09-28 10:45:00 -07003088 }
3089
Matt Pietal5b648562019-03-12 07:40:26 -04003090 final ViewGroup viewGroup = holder.getViewGroup();
3091
Mike Digmanba232682019-03-27 14:55:26 -07003092 // Pre-measure and fix height so we can scale later.
Adam Powell63b31692015-09-28 10:45:00 -07003093 holder.measure();
Matt Pietalab986b52019-04-10 10:14:32 -04003094 setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight());
Mike Digmanba232682019-03-27 14:55:26 -07003095
3096 if (isDirectShare) {
3097 DirectShareViewHolder dsvh = (DirectShareViewHolder) holder;
Matt Pietalab986b52019-04-10 10:14:32 -04003098 setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
3099 setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
Adam Powell7d758002015-05-06 17:49:36 -07003100 }
Matt Pietal5b648562019-03-12 07:40:26 -04003101
3102 viewGroup.setTag(holder);
3103
Adam Powell7d758002015-05-06 17:49:36 -07003104 return holder;
3105 }
3106
Matt Pietalab986b52019-04-10 10:14:32 -04003107 private void setViewBounds(View view, int widthPx, int heightPx) {
Mike Digmanba232682019-03-27 14:55:26 -07003108 LayoutParams lp = view.getLayoutParams();
3109 if (lp == null) {
Matt Pietalab986b52019-04-10 10:14:32 -04003110 lp = new LayoutParams(widthPx, heightPx);
Mike Digmanba232682019-03-27 14:55:26 -07003111 view.setLayoutParams(lp);
3112 } else {
3113 lp.height = heightPx;
Matt Pietalab986b52019-04-10 10:14:32 -04003114 lp.width = widthPx;
Mike Digmanba232682019-03-27 14:55:26 -07003115 }
3116 }
3117
Matt Pietal5b648562019-03-12 07:40:26 -04003118 RowViewHolder createViewHolder(int viewType, ViewGroup parent) {
3119 if (viewType == VIEW_TYPE_DIRECT_SHARE) {
3120 ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate(
3121 R.layout.chooser_row_direct_share, parent, false);
3122 ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3123 parentGroup, false);
3124 ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3125 parentGroup, false);
3126 parentGroup.addView(row1);
3127 parentGroup.addView(row2);
3128
3129 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
3130 Lists.newArrayList(row1, row2), getMaxTargetsPerRow());
3131 loadViewsIntoRow(mDirectShareViewHolder);
3132
3133 return mDirectShareViewHolder;
3134 } else {
3135 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
3136 false);
3137 RowViewHolder holder = new SingleRowViewHolder(row, getMaxTargetsPerRow());
3138 loadViewsIntoRow(holder);
3139
3140 return holder;
3141 }
3142 }
3143
Matt Pietaldadc0d12019-04-16 12:53:28 -04003144 /**
Mike Digmanae730b12019-04-25 11:10:31 -07003145 * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
3146 * showing on top of the AZ list if the AZ label is visible. All other types are placed into
3147 * their own row as determined by their target type, and dividers are added in the list to
3148 * separate each type.
Matt Pietaldadc0d12019-04-16 12:53:28 -04003149 */
3150 int getRowType(int rowPosition) {
Mike Digmanae730b12019-04-25 11:10:31 -07003151 // Merge caller and ranked standard into a single row
Matt Pietaldadc0d12019-04-16 12:53:28 -04003152 int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
3153 if (positionType == ChooserListAdapter.TARGET_CALLER) {
3154 return ChooserListAdapter.TARGET_STANDARD;
3155 }
3156
Mike Digmanae730b12019-04-25 11:10:31 -07003157 // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
3158 // row type the same as the suggestion row type
3159 if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
3160 return ChooserListAdapter.TARGET_STANDARD;
3161 }
3162
Matt Pietaldadc0d12019-04-16 12:53:28 -04003163 return positionType;
3164 }
3165
Matt Pietalfaedea82019-03-21 10:36:54 -04003166 void bindViewHolder(int rowPosition, RowViewHolder holder) {
Adam Powell7d758002015-05-06 17:49:36 -07003167 final int start = getFirstRowPosition(rowPosition);
Matt Pietaldadc0d12019-04-16 12:53:28 -04003168 final int startType = getRowType(start);
3169 final int lastStartType = getRowType(getFirstRowPosition(rowPosition - 1));
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003170
Matt Pietal5b648562019-03-12 07:40:26 -04003171 final ViewGroup row = holder.getViewGroup();
3172
Matt Pietal74c6ed02019-04-18 13:38:46 -04003173 if (startType != lastStartType
3174 || rowPosition == getContentPreviewRowCount() + getProfileRowCount()) {
Matt Pietal84a55bd2019-05-01 12:32:47 -04003175 row.setForeground(
Matt Pietal74c6ed02019-04-18 13:38:46 -04003176 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003177 } else {
Matt Pietal84a55bd2019-05-01 12:32:47 -04003178 row.setForeground(null);
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003179 }
3180
Matt Pietalfaedea82019-03-21 10:36:54 -04003181 int columnCount = holder.getColumnCount();
Matt Pietal5b648562019-03-12 07:40:26 -04003182 int end = start + columnCount - 1;
Matt Pietaldadc0d12019-04-16 12:53:28 -04003183 while (getRowType(end) != startType && end >= start) {
Adam Powell7d758002015-05-06 17:49:36 -07003184 end--;
3185 }
3186
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003187 if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
Matt Pietal5b648562019-03-12 07:40:26 -04003188 final TextView textView = row.findViewById(R.id.chooser_row_text_option);
Adam Powell63b31692015-09-28 10:45:00 -07003189
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003190 if (textView.getVisibility() != View.VISIBLE) {
3191 textView.setAlpha(0.0f);
3192 textView.setVisibility(View.VISIBLE);
3193 textView.setText(R.string.chooser_no_direct_share_targets);
3194
3195 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
3196 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3197
3198 float translationInPx = getResources().getDimensionPixelSize(
3199 R.dimen.chooser_row_text_option_translate);
3200 textView.setTranslationY(translationInPx);
3201 ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
3202 0.0f);
3203 translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3204
3205 AnimatorSet animSet = new AnimatorSet();
3206 animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3207 animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3208 animSet.playTogether(fadeAnim, translateAnim);
3209 animSet.start();
3210 }
Adam Powell7d758002015-05-06 17:49:36 -07003211 }
3212
Matt Pietal5b648562019-03-12 07:40:26 -04003213 for (int i = 0; i < columnCount; i++) {
3214 final View v = holder.getView(i);
Adam Powell7d758002015-05-06 17:49:36 -07003215 if (start + i <= end) {
Matt Pietalfaedea82019-03-21 10:36:54 -04003216 holder.setViewVisibility(i, View.VISIBLE);
Matt Pietal5b648562019-03-12 07:40:26 -04003217 holder.setItemIndex(i, start + i);
3218 mChooserListAdapter.bindView(holder.getItemIndex(i), v);
Adam Powell7d758002015-05-06 17:49:36 -07003219 } else {
Matt Pietalfaedea82019-03-21 10:36:54 -04003220 holder.setViewVisibility(i, View.INVISIBLE);
Adam Powell7d758002015-05-06 17:49:36 -07003221 }
3222 }
3223 }
3224
3225 int getFirstRowPosition(int row) {
Matt Pietal74c6ed02019-04-18 13:38:46 -04003226 row -= getContentPreviewRowCount() + getProfileRowCount();
Matt Pietal1ef88002019-03-13 10:43:18 -04003227
Matt Pietal5b648562019-03-12 07:40:26 -04003228 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
3229 final int serviceRows = (int) Math.ceil((float) serviceCount
3230 / ChooserListAdapter.MAX_SERVICE_TARGETS);
3231 if (row < serviceRows) {
3232 return row * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07003233 }
3234
Matt Pietaldadc0d12019-04-16 12:53:28 -04003235 final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
3236 + mChooserListAdapter.getRankedTargetCount();
3237 final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
3238 if (row < callerAndRankedRows + serviceRows) {
Matt Pietal5b648562019-03-12 07:40:26 -04003239 return serviceCount + (row - serviceRows) * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07003240 }
3241
Mike Digmanae730b12019-04-25 11:10:31 -07003242 row -= getAzLabelRowCount();
3243
Matt Pietaldadc0d12019-04-16 12:53:28 -04003244 return callerAndRankedCount + serviceCount
3245 + (row - callerAndRankedRows - serviceRows) * getMaxTargetsPerRow();
Matt Pietal5b648562019-03-12 07:40:26 -04003246 }
3247
3248 public void handleScroll(View v, int y, int oldy) {
Matt Pietale54dcc2e2019-05-02 12:59:38 -04003249 // Only expand direct share area if there is a minimum number of shortcuts,
3250 // which will help reduce the amount of visible shuffling due to older-style
3251 // direct share targets.
3252 int orientation = getResources().getConfiguration().orientation;
3253 boolean canExpandDirectShare =
3254 mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
Matt Pietal3e4b56f2019-05-31 12:06:17 -04003255 && orientation == Configuration.ORIENTATION_PORTRAIT
3256 && !isInMultiWindowMode();
Matt Pietale54dcc2e2019-05-02 12:59:38 -04003257
3258 if (mDirectShareViewHolder != null && canExpandDirectShare) {
Matt Pietal5b648562019-03-12 07:40:26 -04003259 mDirectShareViewHolder.handleScroll(mAdapterView, y, oldy, getMaxTargetsPerRow());
3260 }
Adam Powell7d758002015-05-06 17:49:36 -07003261 }
3262 }
3263
Matt Pietal5b648562019-03-12 07:40:26 -04003264 abstract class RowViewHolder {
3265 protected int mMeasuredRowHeight;
3266 private int[] mItemIndices;
3267 protected final View[] mCells;
Matt Pietal5b648562019-03-12 07:40:26 -04003268 private final int mColumnCount;
Adam Powell63b31692015-09-28 10:45:00 -07003269
Matt Pietal5b648562019-03-12 07:40:26 -04003270 RowViewHolder(int cellCount) {
3271 this.mCells = new View[cellCount];
3272 this.mItemIndices = new int[cellCount];
Matt Pietal5b648562019-03-12 07:40:26 -04003273 this.mColumnCount = cellCount;
3274 }
3275
3276 abstract ViewGroup addView(int index, View v);
3277
3278 abstract ViewGroup getViewGroup();
3279
Mike Digmanba232682019-03-27 14:55:26 -07003280 abstract ViewGroup getRowByIndex(int index);
3281
3282 abstract ViewGroup getRow(int rowNumber);
Matt Pietal5b648562019-03-12 07:40:26 -04003283
Matt Pietalfaedea82019-03-21 10:36:54 -04003284 abstract void setViewVisibility(int i, int visibility);
3285
Matt Pietal5b648562019-03-12 07:40:26 -04003286 public int getColumnCount() {
3287 return mColumnCount;
3288 }
3289
Adam Powell63b31692015-09-28 10:45:00 -07003290 public void measure() {
3291 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietal5b648562019-03-12 07:40:26 -04003292 getViewGroup().measure(spec, spec);
3293 mMeasuredRowHeight = getViewGroup().getMeasuredHeight();
3294 }
3295
3296 public int getMeasuredRowHeight() {
3297 return mMeasuredRowHeight;
3298 }
3299
Matt Pietal5b648562019-03-12 07:40:26 -04003300 public void setItemIndex(int itemIndex, int listIndex) {
3301 mItemIndices[itemIndex] = listIndex;
3302 }
3303
3304 public int getItemIndex(int itemIndex) {
3305 return mItemIndices[itemIndex];
3306 }
3307
3308 public View getView(int index) {
3309 return mCells[index];
3310 }
3311 }
3312
3313 class SingleRowViewHolder extends RowViewHolder {
3314 private final ViewGroup mRow;
3315
3316 SingleRowViewHolder(ViewGroup row, int cellCount) {
3317 super(cellCount);
3318
3319 this.mRow = row;
3320 }
3321
3322 public ViewGroup getViewGroup() {
3323 return mRow;
3324 }
3325
Mike Digmanba232682019-03-27 14:55:26 -07003326 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04003327 return mRow;
3328 }
3329
Mike Digmanba232682019-03-27 14:55:26 -07003330 public ViewGroup getRow(int rowNumber) {
3331 if (rowNumber == 0) return mRow;
3332 return null;
3333 }
3334
Matt Pietal5b648562019-03-12 07:40:26 -04003335 public ViewGroup addView(int index, View v) {
3336 mRow.addView(v);
3337 mCells[index] = v;
3338
Matt Pietal5b648562019-03-12 07:40:26 -04003339 return mRow;
3340 }
Matt Pietalfaedea82019-03-21 10:36:54 -04003341
3342 public void setViewVisibility(int i, int visibility) {
3343 getView(i).setVisibility(visibility);
3344 }
Matt Pietal5b648562019-03-12 07:40:26 -04003345 }
3346
3347 class DirectShareViewHolder extends RowViewHolder {
3348 private final ViewGroup mParent;
3349 private final List<ViewGroup> mRows;
3350 private int mCellCountPerRow;
3351
3352 private boolean mHideDirectShareExpansion = false;
3353 private int mDirectShareMinHeight = 0;
3354 private int mDirectShareCurrHeight = 0;
3355 private int mDirectShareMaxHeight = 0;
3356
Matt Pietalfaedea82019-03-21 10:36:54 -04003357 private final boolean[] mCellVisibility;
3358
Matt Pietal5b648562019-03-12 07:40:26 -04003359 DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow) {
3360 super(rows.size() * cellCountPerRow);
3361
3362 this.mParent = parent;
3363 this.mRows = rows;
3364 this.mCellCountPerRow = cellCountPerRow;
Matt Pietalfaedea82019-03-21 10:36:54 -04003365 this.mCellVisibility = new boolean[rows.size() * cellCountPerRow];
Matt Pietal5b648562019-03-12 07:40:26 -04003366 }
3367
3368 public ViewGroup addView(int index, View v) {
Mike Digmanba232682019-03-27 14:55:26 -07003369 ViewGroup row = getRowByIndex(index);
Matt Pietal5b648562019-03-12 07:40:26 -04003370 row.addView(v);
3371 mCells[index] = v;
3372
Matt Pietal5b648562019-03-12 07:40:26 -04003373 return row;
3374 }
3375
3376 public ViewGroup getViewGroup() {
3377 return mParent;
3378 }
3379
Mike Digmanba232682019-03-27 14:55:26 -07003380 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04003381 return mRows.get(index / mCellCountPerRow);
3382 }
3383
Mike Digmanba232682019-03-27 14:55:26 -07003384 public ViewGroup getRow(int rowNumber) {
3385 return mRows.get(rowNumber);
3386 }
3387
Matt Pietal5b648562019-03-12 07:40:26 -04003388 public void measure() {
3389 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3390 getRow(0).measure(spec, spec);
3391 getRow(1).measure(spec, spec);
3392
Matt Pietal5b648562019-03-12 07:40:26 -04003393 mDirectShareMinHeight = getRow(0).getMeasuredHeight();
3394 mDirectShareCurrHeight = mDirectShareCurrHeight > 0
3395 ? mDirectShareCurrHeight : mDirectShareMinHeight;
3396 mDirectShareMaxHeight = 2 * mDirectShareMinHeight;
3397 }
3398
3399 public int getMeasuredRowHeight() {
3400 return mDirectShareCurrHeight;
3401 }
3402
Matt Pietala9c8e502019-04-10 14:27:35 -04003403 public int getMinRowHeight() {
3404 return mDirectShareMinHeight;
3405 }
3406
Matt Pietalfaedea82019-03-21 10:36:54 -04003407 public void setViewVisibility(int i, int visibility) {
3408 final View v = getView(i);
3409 if (visibility == View.VISIBLE) {
3410 mCellVisibility[i] = true;
3411 v.setVisibility(visibility);
3412 v.setAlpha(1.0f);
3413 } else if (visibility == View.INVISIBLE && mCellVisibility[i]) {
3414 mCellVisibility[i] = false;
3415
3416 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
3417 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3418 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
3419 fadeAnim.addListener(new AnimatorListenerAdapter() {
3420 public void onAnimationEnd(Animator animation) {
3421 v.setVisibility(View.INVISIBLE);
3422 }
3423 });
3424 fadeAnim.start();
3425 }
3426 }
3427
Matt Pietal5b648562019-03-12 07:40:26 -04003428 public void handleScroll(AbsListView view, int y, int oldy, int maxTargetsPerRow) {
Matt Pietala9c8e502019-04-10 14:27:35 -04003429 // only exit early if fully collapsed, otherwise onListRebuilt() with shifting
3430 // targets can lock us into an expanded mode
3431 boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight;
3432 if (notExpanded) {
3433 if (mHideDirectShareExpansion) {
3434 return;
3435 }
Matt Pietal5b648562019-03-12 07:40:26 -04003436
Matt Pietala9c8e502019-04-10 14:27:35 -04003437 // only expand if we have more than maxTargetsPerRow, and delay that decision
3438 // until they start to scroll
3439 if (mChooserListAdapter.getSelectableServiceTargetCount() <= maxTargetsPerRow) {
3440 mHideDirectShareExpansion = true;
3441 return;
3442 }
Matt Pietal5b648562019-03-12 07:40:26 -04003443 }
3444
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003445 int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal5b648562019-03-12 07:40:26 -04003446
3447 int prevHeight = mDirectShareCurrHeight;
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003448 int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
3449 newHeight = Math.max(newHeight, mDirectShareMinHeight);
3450 yDiff = newHeight - prevHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04003451
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003452 if (view == null || view.getChildCount() == 0 || yDiff == 0) {
Matt Pietal5b648562019-03-12 07:40:26 -04003453 return;
3454 }
3455
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003456 // locate the item to expand, and offset the rows below that one
3457 boolean foundExpansion = false;
3458 for (int i = 0; i < view.getChildCount(); i++) {
3459 View child = view.getChildAt(i);
Matt Pietal1ef88002019-03-13 10:43:18 -04003460
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003461 if (foundExpansion) {
3462 child.offsetTopAndBottom(yDiff);
3463 } else {
3464 if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
3465 int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
3466 MeasureSpec.EXACTLY);
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003467 int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003468 MeasureSpec.EXACTLY);
3469 child.measure(widthSpec, heightSpec);
3470 child.getLayoutParams().height = child.getMeasuredHeight();
3471 child.layout(child.getLeft(), child.getTop(), child.getRight(),
3472 child.getTop() + child.getMeasuredHeight());
Matt Pietal5b648562019-03-12 07:40:26 -04003473
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003474 foundExpansion = true;
3475 }
3476 }
Matt Pietal5b648562019-03-12 07:40:26 -04003477 }
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003478
3479 if (foundExpansion) {
3480 mDirectShareCurrHeight = newHeight;
3481 }
Adam Powell63b31692015-09-28 10:45:00 -07003482 }
3483 }
3484
Adam Powell9761ab22015-09-08 17:01:49 -07003485 static class ChooserTargetServiceConnection implements ServiceConnection {
Adam Powell52c39212016-04-07 15:14:18 -07003486 private DisplayResolveInfo mOriginalTarget;
Adam Powell9761ab22015-09-08 17:01:49 -07003487 private ComponentName mConnectedComponent;
3488 private ChooserActivity mChooserActivity;
3489 private final Object mLock = new Object();
Adam Powell24428412015-04-01 17:19:56 -07003490
3491 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
3492 @Override
3493 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
Adam Powell9761ab22015-09-08 17:01:49 -07003494 synchronized (mLock) {
3495 if (mChooserActivity == null) {
3496 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
3497 + mConnectedComponent + "; ignoring...");
3498 return;
3499 }
3500 mChooserActivity.filterServiceTargets(
3501 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
3502 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04003503 msg.what = ChooserHandler.CHOOSER_TARGET_SERVICE_RESULT;
Adam Powell9761ab22015-09-08 17:01:49 -07003504 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
3505 ChooserTargetServiceConnection.this);
3506 mChooserActivity.mChooserHandler.sendMessage(msg);
3507 }
Adam Powell24428412015-04-01 17:19:56 -07003508 }
3509 };
3510
Adam Powell9761ab22015-09-08 17:01:49 -07003511 public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
3512 DisplayResolveInfo dri) {
3513 mChooserActivity = chooserActivity;
Adam Powell24428412015-04-01 17:19:56 -07003514 mOriginalTarget = dri;
3515 }
3516
3517 @Override
3518 public void onServiceConnected(ComponentName name, IBinder service) {
3519 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003520 synchronized (mLock) {
3521 if (mChooserActivity == null) {
3522 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
3523 return;
3524 }
3525
3526 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
3527 try {
3528 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
3529 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
3530 } catch (RemoteException e) {
3531 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
3532 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003533 mChooserActivity.mServiceConnections.remove(this);
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003534 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003535 }
Adam Powell24428412015-04-01 17:19:56 -07003536 }
3537 }
3538
3539 @Override
3540 public void onServiceDisconnected(ComponentName name) {
3541 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003542 synchronized (mLock) {
3543 if (mChooserActivity == null) {
3544 Log.e(TAG,
3545 "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
3546 return;
3547 }
3548
3549 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003550 mChooserActivity.mServiceConnections.remove(this);
3551 if (mChooserActivity.mServiceConnections.isEmpty()) {
Adam Powell9761ab22015-09-08 17:01:49 -07003552 mChooserActivity.sendVoiceChoicesIfNeeded();
3553 }
3554 mConnectedComponent = null;
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003555 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003556 }
3557 }
3558
3559 public void destroy() {
3560 synchronized (mLock) {
3561 mChooserActivity = null;
Adam Powell52c39212016-04-07 15:14:18 -07003562 mOriginalTarget = null;
Adam Powell4c470d62015-06-19 17:46:17 -07003563 }
Adam Powell24428412015-04-01 17:19:56 -07003564 }
3565
3566 @Override
3567 public String toString() {
Adam Powell9761ab22015-09-08 17:01:49 -07003568 return "ChooserTargetServiceConnection{service="
3569 + mConnectedComponent + ", activity="
Adam Powell52c39212016-04-07 15:14:18 -07003570 + (mOriginalTarget != null
3571 ? mOriginalTarget.getResolveInfo().activityInfo.toString()
3572 : "<connection destroyed>") + "}";
Adam Powell24428412015-04-01 17:19:56 -07003573 }
3574 }
3575
3576 static class ServiceResultInfo {
3577 public final DisplayResolveInfo originalTarget;
3578 public final List<ChooserTarget> resultTargets;
3579 public final ChooserTargetServiceConnection connection;
3580
3581 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
3582 ChooserTargetServiceConnection c) {
3583 originalTarget = ot;
3584 resultTargets = rt;
3585 connection = c;
3586 }
3587 }
Adam Powell2ed547e2015-04-29 18:45:04 -07003588
3589 static class RefinementResultReceiver extends ResultReceiver {
3590 private ChooserActivity mChooserActivity;
3591 private TargetInfo mSelectedTarget;
3592
3593 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
3594 Handler handler) {
3595 super(handler);
3596 mChooserActivity = host;
3597 mSelectedTarget = target;
3598 }
3599
3600 @Override
3601 protected void onReceiveResult(int resultCode, Bundle resultData) {
3602 if (mChooserActivity == null) {
3603 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
3604 return;
3605 }
3606 if (resultData == null) {
3607 Log.e(TAG, "RefinementResultReceiver received null resultData");
3608 return;
3609 }
3610
3611 switch (resultCode) {
3612 case RESULT_CANCELED:
3613 mChooserActivity.onRefinementCanceled();
3614 break;
3615 case RESULT_OK:
3616 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
3617 if (intentParcelable instanceof Intent) {
3618 mChooserActivity.onRefinementResult(mSelectedTarget,
3619 (Intent) intentParcelable);
3620 } else {
3621 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
3622 + " in resultData with key Intent.EXTRA_INTENT");
3623 }
3624 break;
3625 default:
3626 Log.w(TAG, "Unknown result code " + resultCode
3627 + " sent to RefinementResultReceiver");
3628 break;
3629 }
3630 }
3631
3632 public void destroy() {
3633 mChooserActivity = null;
3634 mSelectedTarget = null;
3635 }
3636 }
Adam Powell63b31692015-09-28 10:45:00 -07003637
Matt Pietal26038402019-01-08 07:29:34 -05003638 /**
3639 * Used internally to round image corners while obeying view padding.
3640 */
3641 public static class RoundedRectImageView extends ImageView {
3642 private int mRadius = 0;
3643 private Path mPath = new Path();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003644 private Paint mOverlayPaint = new Paint(0);
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003645 private Paint mRoundRectPaint = new Paint(0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003646 private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
3647 private String mExtraImageCount = null;
Matt Pietal26038402019-01-08 07:29:34 -05003648
3649 public RoundedRectImageView(Context context) {
3650 super(context);
3651 }
3652
3653 public RoundedRectImageView(Context context, AttributeSet attrs) {
3654 this(context, attrs, 0);
3655 }
3656
3657 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
3658 this(context, attrs, defStyleAttr, 0);
3659 }
3660
3661 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
3662 int defStyleRes) {
3663 super(context, attrs, defStyleAttr, defStyleRes);
3664 mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003665
3666 mOverlayPaint.setColor(0x99000000);
3667 mOverlayPaint.setStyle(Paint.Style.FILL);
3668
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003669 mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider));
3670 mRoundRectPaint.setStyle(Paint.Style.STROKE);
3671 mRoundRectPaint.setStrokeWidth(context.getResources()
3672 .getDimensionPixelSize(R.dimen.chooser_preview_image_border));
3673
Matt Pietal0ea391b2019-01-30 10:44:15 -05003674 mTextPaint.setColor(Color.WHITE);
3675 mTextPaint.setTextSize(context.getResources()
3676 .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
3677 mTextPaint.setTextAlign(Paint.Align.CENTER);
Matt Pietal26038402019-01-08 07:29:34 -05003678 }
3679
3680 private void updatePath(int width, int height) {
3681 mPath.reset();
3682
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003683 int imageWidth = width - getPaddingRight() - getPaddingLeft();
3684 int imageHeight = height - getPaddingBottom() - getPaddingTop();
Matt Pietal26038402019-01-08 07:29:34 -05003685 mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
3686 mRadius, Path.Direction.CW);
3687 }
3688
3689 /**
3690 * Sets the corner radius on all corners
3691 *
3692 * param radius 0 for no radius, &gt; 0 for a visible corner radius
3693 */
3694 public void setRadius(int radius) {
3695 mRadius = radius;
3696 updatePath(getWidth(), getHeight());
3697 }
3698
Matt Pietal0ea391b2019-01-30 10:44:15 -05003699 /**
3700 * Display an overlay with extra image count on 3rd image
3701 */
3702 public void setExtraImageCount(int count) {
3703 if (count > 0) {
3704 this.mExtraImageCount = "+" + count;
3705 } else {
3706 this.mExtraImageCount = null;
3707 }
3708 }
3709
Matt Pietal26038402019-01-08 07:29:34 -05003710 @Override
3711 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
3712 super.onSizeChanged(width, height, oldWidth, oldHeight);
3713 updatePath(width, height);
3714 }
3715
3716 @Override
3717 protected void onDraw(Canvas canvas) {
3718 if (mRadius != 0) {
3719 canvas.clipPath(mPath);
3720 }
3721
3722 super.onDraw(canvas);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003723
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003724 int x = getPaddingLeft();
3725 int y = getPaddingRight();
3726 int width = getWidth() - getPaddingRight() - getPaddingLeft();
3727 int height = getHeight() - getPaddingBottom() - getPaddingTop();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003728 if (mExtraImageCount != null) {
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003729 canvas.drawRect(x, y, width, height, mOverlayPaint);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003730
3731 int xPos = canvas.getWidth() / 2;
3732 int yPos = (int) ((canvas.getHeight() / 2.0f)
3733 - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
3734
3735 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
3736 }
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003737
3738 canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
Matt Pietal26038402019-01-08 07:29:34 -05003739 }
3740 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003741}