blob: 00206fc38d1d40ceffa280ebbe2d4968a2fd772d [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;
Adam Powell0b3c1122014-10-09 12:50:14 -070028import android.app.Activity;
Ng Zhi And3ec5fc2018-05-10 09:13:00 -070029import android.app.ActivityManager;
George Hodulik69d4a082019-01-18 11:27:03 -080030import android.app.prediction.AppPredictionContext;
31import android.app.prediction.AppPredictionManager;
32import android.app.prediction.AppPredictor;
33import android.app.prediction.AppTarget;
George Hodulikf2b0d342019-01-25 12:43:54 -080034import android.app.prediction.AppTargetEvent;
Matt Pietal26038402019-01-08 07:29:34 -050035import android.content.ClipData;
Matt Pietal1fa7d802019-01-30 10:44:15 -050036import android.content.ClipboardManager;
Adam Powell0b3c1122014-10-09 12:50:14 -070037import android.content.ComponentName;
Matt Pietal0ea391b2019-01-30 10:44:15 -050038import android.content.ContentResolver;
Adam Powell24428412015-04-01 17:19:56 -070039import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.content.Intent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080041import android.content.IntentFilter;
Adam Powell0b3c1122014-10-09 12:50:14 -070042import android.content.IntentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -070043import android.content.IntentSender.SendIntentException;
Adam Powell24428412015-04-01 17:19:56 -070044import android.content.ServiceConnection;
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +000045import android.content.pm.ActivityInfo;
Matt Pietala4b30072019-04-04 13:44:36 -040046import android.content.pm.ApplicationInfo;
Adam Powell7d758002015-05-06 17:49:36 -070047import android.content.pm.LabeledIntent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080048import android.content.pm.LauncherApps;
Adam Powell24428412015-04-01 17:19:56 -070049import android.content.pm.PackageManager;
50import android.content.pm.PackageManager.NameNotFoundException;
51import android.content.pm.ResolveInfo;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080052import android.content.pm.ShortcutInfo;
53import android.content.pm.ShortcutManager;
Matt Pietal18bbd822019-02-12 15:21:36 -050054import android.content.res.Configuration;
Matt Pietal46d828c2019-02-05 08:07:07 -050055import android.database.Cursor;
Adam Powell7d758002015-05-06 17:49:36 -070056import android.database.DataSetObserver;
Matt Pietal26038402019-01-08 07:29:34 -050057import android.graphics.Bitmap;
58import android.graphics.Canvas;
Adam Powell63b31692015-09-28 10:45:00 -070059import android.graphics.Color;
Matt Pietal0ea391b2019-01-30 10:44:15 -050060import android.graphics.Paint;
Matt Pietal26038402019-01-08 07:29:34 -050061import android.graphics.Path;
Mike Digmanac1d88c2019-04-18 15:15:55 -070062import android.graphics.drawable.AnimatedVectorDrawable;
Mike Digman9c4ae502019-03-19 17:02:25 -070063import android.graphics.drawable.BitmapDrawable;
Adam Powell24428412015-04-01 17:19:56 -070064import android.graphics.drawable.Drawable;
Adam Powell13036be2015-05-12 14:43:56 -070065import android.graphics.drawable.Icon;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -050066import android.metrics.LogMaker;
Matt Pietal26038402019-01-08 07:29:34 -050067import android.net.Uri;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080068import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069import android.os.Bundle;
Adam Powell24428412015-04-01 17:19:56 -070070import android.os.Handler;
71import android.os.IBinder;
72import android.os.Message;
Dianne Hackborneb034652009-09-07 00:49:58 -070073import android.os.Parcelable;
Adam Powell52c39212016-04-07 15:14:18 -070074import android.os.Process;
Adam Powell24428412015-04-01 17:19:56 -070075import android.os.RemoteException;
Adam Powell2ed547e2015-04-29 18:45:04 -070076import android.os.ResultReceiver;
Adam Powell24428412015-04-01 17:19:56 -070077import android.os.UserHandle;
Adam Powell7d758002015-05-06 17:49:36 -070078import android.os.UserManager;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -040079import android.provider.DeviceConfig;
Matt Pietal46d828c2019-02-05 08:07:07 -050080import android.provider.DocumentsContract;
Matt Pietalf38e9d22019-02-15 10:01:03 -050081import android.provider.Downloads;
Matt Pietal46d828c2019-02-05 08:07:07 -050082import android.provider.OpenableColumns;
Adam Powell24428412015-04-01 17:19:56 -070083import android.service.chooser.ChooserTarget;
84import android.service.chooser.ChooserTargetService;
85import android.service.chooser.IChooserTargetResult;
86import android.service.chooser.IChooserTargetService;
Matt Pietal9d501432019-04-12 10:05:29 -040087import android.text.SpannableStringBuilder;
Adam Powell24428412015-04-01 17:19:56 -070088import android.text.TextUtils;
Matt Pietal26038402019-01-08 07:29:34 -050089import android.util.AttributeSet;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -040090import android.util.HashedStringCache;
Dianne Hackborneb034652009-09-07 00:49:58 -070091import android.util.Log;
Matt Pietal26038402019-01-08 07:29:34 -050092import android.util.Size;
Adam Powell0b3c1122014-10-09 12:50:14 -070093import android.util.Slog;
Adam Powell7d758002015-05-06 17:49:36 -070094import android.view.LayoutInflater;
Adam Powell24428412015-04-01 17:19:56 -070095import android.view.View;
Adam Powell63b31692015-09-28 10:45:00 -070096import android.view.View.MeasureSpec;
Adam Powell7d758002015-05-06 17:49:36 -070097import android.view.View.OnClickListener;
Adam Powell98b7f892015-06-19 12:38:45 -070098import android.view.View.OnLongClickListener;
Adam Powell24428412015-04-01 17:19:56 -070099import android.view.ViewGroup;
Adam Powell63b31692015-09-28 10:45:00 -0700100import android.view.ViewGroup.LayoutParams;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500101import android.view.animation.AccelerateInterpolator;
102import android.view.animation.DecelerateInterpolator;
Adam Powell7d758002015-05-06 17:49:36 -0700103import android.widget.AbsListView;
104import android.widget.BaseAdapter;
Matt Pietal26038402019-01-08 07:29:34 -0500105import android.widget.ImageView;
Adam Powell7d758002015-05-06 17:49:36 -0700106import android.widget.ListView;
Matt Pietal26038402019-01-08 07:29:34 -0500107import android.widget.TextView;
Matt Pietal1fa7d802019-01-30 10:44:15 -0500108import android.widget.Toast;
Jason Monk027dcfa2017-06-27 18:37:35 -0400109
Adam Powell7d758002015-05-06 17:49:36 -0700110import com.android.internal.R;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800111import com.android.internal.annotations.VisibleForTesting;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400112import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
Matt Pietalab73a882019-06-05 07:04:55 -0400113import com.android.internal.content.PackageMonitor;
Adam Powell98b7f892015-06-19 12:38:45 -0700114import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +0100115import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500116import com.android.internal.util.ImageUtils;
Mike Digman849a9d12019-04-29 11:20:48 -0700117import com.android.internal.widget.ResolverDrawerLayout;
Alison Cichowlas3e340502018-08-07 17:15:01 -0400118
Adam Powell52c39212016-04-07 15:14:18 -0700119import com.google.android.collect.Lists;
Adam Powell24428412015-04-01 17:19:56 -0700120
Matt Pietal26038402019-01-08 07:29:34 -0500121import java.io.IOException;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500122import java.lang.annotation.Retention;
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400123import java.text.Collator;
Adam Powell24428412015-04-01 17:19:56 -0700124import java.util.ArrayList;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800125import java.util.Arrays;
Adam Powella182e452015-07-06 16:57:56 -0700126import java.util.Collections;
127import java.util.Comparator;
George Hodulikaa5238c2019-04-18 14:17:51 -0700128import java.util.HashMap;
Matt Pietalab73a882019-06-05 07:04:55 -0400129import java.util.HashSet;
Adam Powell24428412015-04-01 17:19:56 -0700130import java.util.List;
George Hodulikaa5238c2019-04-18 14:17:51 -0700131import java.util.Map;
Matt Pietalab73a882019-06-05 07:04:55 -0400132import java.util.Set;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400134/**
135 * The Chooser Activity handles intent resolution specifically for sharing intents -
136 * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
137 *
138 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139public class ChooserActivity extends ResolverActivity {
Adam Powell0b3c1122014-10-09 12:50:14 -0700140 private static final String TAG = "ChooserActivity";
141
George Hodulik145b3a52019-03-27 11:18:43 -0700142
Jorim Jaggif631ef72017-02-24 13:49:47 +0100143 /**
144 * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
145 * in onStop when launched in a new task. If this extra is set to true, we do not finish
146 * ourselves when onStop gets called.
147 */
148 public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
149 = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
150
Mike Digman849a9d12019-04-29 11:20:48 -0700151 private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
152
Mehdi Alizadeh3c335a22019-01-17 16:03:19 -0800153 private static final boolean DEBUG = false;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800154
George Hodulik69d4a082019-01-18 11:27:03 -0800155 /**
156 * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
157 * {@link AppPredictionManager} will be queried for direct share targets.
158 */
159 // TODO(b/123089490): Replace with system flag
George Hodulik1428beb2019-05-01 17:04:23 -0700160 private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = true;
161 private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
George Hodulik69d4a082019-01-18 11:27:03 -0800162 // TODO(b/123088566) Share these in a better way.
163 private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
George Hodulikf2b0d342019-01-25 12:43:54 -0800164 public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
George Hodulik69d4a082019-01-18 11:27:03 -0800165 private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
166 public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
167 private AppPredictor mAppPredictor;
168 private AppPredictor.Callback mAppPredictorCallback;
George Hodulikaa5238c2019-04-18 14:17:51 -0700169 private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
George Hodulik69d4a082019-01-18 11:27:03 -0800170
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800171 /**
172 * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
173 * binding to every ChooserTargetService implementation.
174 */
175 // TODO(b/121287573): Replace with a system flag (setprop?)
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800176 private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
177 private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
178
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500179 /**
180 * The transition time between placeholders for direct share to a message
181 * indicating that non are available.
182 */
183 private static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200;
184
Matt Pietal394ebd02019-05-03 07:36:21 -0400185 private static final float DIRECT_SHARE_EXPANSION_RATE = 0.78f;
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400186
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800187 // TODO(b/121287224): Re-evaluate this limit
188 private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
Adam Powell24428412015-04-01 17:19:56 -0700189
Adam Powell2ed547e2015-04-29 18:45:04 -0700190 private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
Adam Powell24428412015-04-01 17:19:56 -0700191
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400192 private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7;
193 private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
194 SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
195 DEFAULT_SALT_EXPIRATION_DAYS);
196
Adam Powelle49d9392014-07-17 18:45:19 -0700197 private Bundle mReplacementExtras;
Adam Powell0b3c1122014-10-09 12:50:14 -0700198 private IntentSender mChosenComponentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -0700199 private IntentSender mRefinementIntentSender;
200 private RefinementResultReceiver mRefinementResultReceiver;
Adam Powell52c39212016-04-07 15:14:18 -0700201 private ChooserTarget[] mCallerChooserTargets;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800202 private ComponentName[] mFilteredComponentNames;
Adam Powelle49d9392014-07-17 18:45:19 -0700203
Adam Powell13036be2015-05-12 14:43:56 -0700204 private Intent mReferrerFillInIntent;
205
Kang Li9082f5b2016-12-02 10:56:21 -0800206 private long mChooserShownTime;
Kang Li64b018e2017-01-05 17:30:06 -0800207 protected boolean mIsSuccessfullySelected;
Kang Li9082f5b2016-12-02 10:56:21 -0800208
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700209 private long mQueriedTargetServicesTimeMs;
210 private long mQueriedSharingShortcutsTimeMs;
211
Adam Powell7d758002015-05-06 17:49:36 -0700212 private ChooserListAdapter mChooserListAdapter;
Adam Powell63b31692015-09-28 10:45:00 -0700213 private ChooserRowAdapter mChooserRowAdapter;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500214 private int mChooserRowServiceSpacing;
Adam Powell24428412015-04-01 17:19:56 -0700215
Matt Pietalab73a882019-06-05 07:04:55 -0400216 private int mCurrAvailableWidth = 0;
217
Matt Pietalfbfa0492019-04-01 11:29:56 -0400218 /** {@link ChooserActivity#getBaseScore} */
Adam Powell52c39212016-04-07 15:14:18 -0700219 private static final float CALLER_TARGET_SCORE_BOOST = 900.f;
Matt Pietalfbfa0492019-04-01 11:29:56 -0400220 /** {@link ChooserActivity#getBaseScore} */
Matt Pietal39d181d2019-05-08 14:48:30 -0400221 private static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
Adam Powell23882512016-01-29 10:21:00 -0800222 private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400223 // TODO: Update to handle landscape instead of using static value
224 private static final int MAX_RANKED_TARGETS = 4;
Adam Powell23882512016-01-29 10:21:00 -0800225
Adam Powell24428412015-04-01 17:19:56 -0700226 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
Matt Pietalab73a882019-06-05 07:04:55 -0400227 private final Set<ComponentName> mServicesRequested = new HashSet<>();
Matt Pietalaf044ae2019-03-29 06:53:53 -0400228
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -0400229 private static final int MAX_LOG_RANK_POSITION = 12;
230
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400231 @VisibleForTesting
232 public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
Matt Pietalaf044ae2019-03-29 06:53:53 -0400233
Matt Pietal4e2e3632019-04-05 08:32:47 -0400234 private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400235 private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
Matt Pietal4e2e3632019-04-05 08:32:47 -0400236
Matt Pietalaf044ae2019-03-29 06:53:53 -0400237 private boolean mListViewDataChanged = false;
Adam Powell24428412015-04-01 17:19:56 -0700238
Matt Pietal0ea391b2019-01-30 10:44:15 -0500239 @Retention(SOURCE)
240 @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
241 private @interface ContentPreviewType {
242 }
243
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500244 // Starting at 1 since 0 is considered "undefined" for some of the database transformations
245 // of tron logs.
246 private static final int CONTENT_PREVIEW_IMAGE = 1;
247 private static final int CONTENT_PREVIEW_FILE = 2;
248 private static final int CONTENT_PREVIEW_TEXT = 3;
249 protected MetricsLogger mMetricsLogger;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500250
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400251 // Sorted list of DisplayResolveInfos for the alphabetical app section.
252 private List<ResolverActivity.DisplayResolveInfo> mSortedList = new ArrayList<>();
253
Matt Pietale7cacab2019-05-23 07:21:36 -0400254 private ContentPreviewCoordinator mPreviewCoord;
255
256 private class ContentPreviewCoordinator {
Matt Pietale7cacab2019-05-23 07:21:36 -0400257 private static final int IMAGE_FADE_IN_MILLIS = 150;
258 private static final int IMAGE_LOAD_TIMEOUT = 1;
259 private static final int IMAGE_LOAD_INTO_VIEW = 2;
260
Matt Pietalab73a882019-06-05 07:04:55 -0400261 private final int mImageLoadTimeoutMillis =
262 getResources().getInteger(R.integer.config_shortAnimTime);
263
Matt Pietale7cacab2019-05-23 07:21:36 -0400264 private final View mParentView;
265 private boolean mHideParentOnFail;
266 private boolean mAtLeastOneLoaded = false;
267
268 class LoadUriTask {
269 public final Uri mUri;
270 public final int mImageResourceId;
271 public final int mExtraCount;
272 public final Bitmap mBmp;
273
274 LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) {
275 this.mImageResourceId = imageResourceId;
276 this.mUri = uri;
277 this.mExtraCount = extraCount;
278 this.mBmp = bmp;
279 }
280 }
281
282 // If at least one image loads within the timeout period, allow other
283 // loads to continue. Otherwise terminate and optionally hide
284 // the parent area
285 private final Handler mHandler = new Handler() {
286 @Override
287 public void handleMessage(Message msg) {
288 switch (msg.what) {
289 case IMAGE_LOAD_TIMEOUT:
290 maybeHideContentPreview();
291 break;
292
293 case IMAGE_LOAD_INTO_VIEW:
294 if (isFinishing()) break;
295
296 LoadUriTask task = (LoadUriTask) msg.obj;
297 RoundedRectImageView imageView = mParentView.findViewById(
298 task.mImageResourceId);
299 if (task.mBmp == null) {
300 imageView.setVisibility(View.GONE);
301 maybeHideContentPreview();
302 return;
303 }
304
305 mAtLeastOneLoaded = true;
306 imageView.setVisibility(View.VISIBLE);
307 imageView.setAlpha(0.0f);
308 imageView.setImageBitmap(task.mBmp);
309
310 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f,
311 1.0f);
312 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
313 fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS);
314 fadeAnim.start();
315
316 if (task.mExtraCount > 0) {
317 imageView.setExtraImageCount(task.mExtraCount);
318 }
319 }
320 }
321 };
322
323 ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) {
324 super();
325
326 this.mParentView = parentView;
327 this.mHideParentOnFail = hideParentOnFail;
328 }
329
330 private void loadUriIntoView(final int imageResourceId, final Uri uri,
331 final int extraImages) {
Matt Pietalab73a882019-06-05 07:04:55 -0400332 mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, mImageLoadTimeoutMillis);
Matt Pietale7cacab2019-05-23 07:21:36 -0400333
334 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
335 final Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
336 final Message msg = Message.obtain();
337 msg.what = IMAGE_LOAD_INTO_VIEW;
338 msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
339 mHandler.sendMessage(msg);
340 });
341 }
342
343 private void cancelLoads() {
344 mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW);
345 mHandler.removeMessages(IMAGE_LOAD_TIMEOUT);
346 }
347
348 private void maybeHideContentPreview() {
349 if (!mAtLeastOneLoaded && mHideParentOnFail) {
350 Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
Matt Pietalab73a882019-06-05 07:04:55 -0400351 + " within " + mImageLoadTimeoutMillis + "ms.");
Matt Pietale7cacab2019-05-23 07:21:36 -0400352 collapseParentView();
353 if (mChooserRowAdapter != null) {
354 mChooserRowAdapter.hideContentPreview();
355 }
356 mHideParentOnFail = false;
357 }
358 }
359
360 private void collapseParentView() {
361 // This will effectively hide the content preview row by forcing the height
362 // to zero. It is faster than forcing a relayout of the listview
363 final View v = mParentView;
364 int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY);
365 int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
366 v.measure(widthSpec, heightSpec);
367 v.getLayoutParams().height = 0;
368 v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop());
369 v.invalidate();
370 }
371 }
372
Matt Pietalab73a882019-06-05 07:04:55 -0400373 private final ChooserHandler mChooserHandler = new ChooserHandler();
374
375 private class ChooserHandler extends Handler {
376 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
377 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT = 2;
378 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT = 3;
379 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
380 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
381 private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
382
383 private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
384 private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;
385
386 private boolean mMinTimeoutPassed = false;
387
388 private void removeAllMessages() {
389 removeMessages(LIST_VIEW_UPDATE_MESSAGE);
390 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
391 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
392 removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
393 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
394 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
395 }
396
397 private void restartServiceRequestTimer() {
398 mMinTimeoutPassed = false;
399 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
400 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
401
402 if (DEBUG) {
403 Log.d(TAG, "queryTargets setting watchdog timer for "
404 + WATCHDOG_TIMEOUT_MIN_MILLIS + "-"
405 + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
406 }
407
408 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
409 WATCHDOG_TIMEOUT_MIN_MILLIS);
410 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
411 WATCHDOG_TIMEOUT_MAX_MILLIS);
412 }
413
414 private void maybeStopServiceRequestTimer() {
415 // Set a minimum timeout threshold, to ensure both apis, sharing shortcuts
416 // and older-style direct share services, have had time to load, otherwise
417 // just checking mServiceConnections could force us to end prematurely
418 if (mMinTimeoutPassed && mServiceConnections.isEmpty()) {
419 logDirectShareTargetReceived(
420 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
421 sendVoiceChoicesIfNeeded();
422 mChooserListAdapter.completeServiceTargetLoading();
423 }
424 }
425
Adam Powell24428412015-04-01 17:19:56 -0700426 @Override
427 public void handleMessage(Message msg) {
Matt Pietalaf044ae2019-03-29 06:53:53 -0400428 if (mChooserListAdapter == null || isDestroyed()) {
429 return;
430 }
431
Adam Powell24428412015-04-01 17:19:56 -0700432 switch (msg.what) {
433 case CHOOSER_TARGET_SERVICE_RESULT:
434 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
Adam Powell24428412015-04-01 17:19:56 -0700435 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
436 if (!mServiceConnections.contains(sri.connection)) {
437 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
438 + " returned after being removed from active connections."
439 + " Have you considered returning results faster?");
440 break;
441 }
Adam Powella182e452015-07-06 16:57:56 -0700442 if (sri.resultTargets != null) {
443 mChooserListAdapter.addServiceResults(sri.originalTarget,
Matt Pietalfbfa0492019-04-01 11:29:56 -0400444 sri.resultTargets, false);
Adam Powella182e452015-07-06 16:57:56 -0700445 }
Adam Powell24428412015-04-01 17:19:56 -0700446 unbindService(sri.connection);
Adam Powell9761ab22015-09-08 17:01:49 -0700447 sri.connection.destroy();
Adam Powell24428412015-04-01 17:19:56 -0700448 mServiceConnections.remove(sri.connection);
Matt Pietalab73a882019-06-05 07:04:55 -0400449 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700450 break;
451
Matt Pietalab73a882019-06-05 07:04:55 -0400452 case CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT:
453 mMinTimeoutPassed = true;
454 maybeStopServiceRequestTimer();
455 break;
Matt Pietalaf044ae2019-03-29 06:53:53 -0400456
Matt Pietalab73a882019-06-05 07:04:55 -0400457 case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
Adam Powell24428412015-04-01 17:19:56 -0700458 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -0400459 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700460 break;
461
Matt Pietalaf044ae2019-03-29 06:53:53 -0400462 case LIST_VIEW_UPDATE_MESSAGE:
463 if (DEBUG) {
464 Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
465 }
466
467 mChooserListAdapter.refreshListView();
468 break;
469
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800470 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
471 if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800472 final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
473 if (resultInfo.resultTargets != null) {
474 mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
Matt Pietalfbfa0492019-04-01 11:29:56 -0400475 resultInfo.resultTargets, true);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800476 }
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800477 break;
478
479 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700480 logDirectShareTargetReceived(
481 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800482 sendVoiceChoicesIfNeeded();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800483 break;
484
Adam Powell24428412015-04-01 17:19:56 -0700485 default:
486 super.handleMessage(msg);
487 }
488 }
489 };
490
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 @Override
492 protected void onCreate(Bundle savedInstanceState) {
Kang Li9082f5b2016-12-02 10:56:21 -0800493 final long intentReceivedTime = System.currentTimeMillis();
494 mIsSuccessfullySelected = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 Intent intent = getIntent();
Dianne Hackborneb034652009-09-07 00:49:58 -0700496 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
497 if (!(targetParcelable instanceof Intent)) {
Christopher Tate9d6376a2014-02-12 13:14:10 -0800498 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
Dianne Hackborneb034652009-09-07 00:49:58 -0700499 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800500 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700501 return;
502 }
Adam Powell24428412015-04-01 17:19:56 -0700503 Intent target = (Intent) targetParcelable;
Craig Mautner411d2aed2014-05-08 09:07:43 -0700504 if (target != null) {
Adam Powelle49d9392014-07-17 18:45:19 -0700505 modifyTargetIntent(target);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700506 }
Adam Powell2ed547e2015-04-29 18:45:04 -0700507 Parcelable[] targetsParcelable
508 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
509 if (targetsParcelable != null) {
510 final boolean offset = target == null;
511 Intent[] additionalTargets =
512 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
513 for (int i = 0; i < targetsParcelable.length; i++) {
514 if (!(targetsParcelable[i] instanceof Intent)) {
515 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
516 + targetsParcelable[i]);
517 finish();
518 super.onCreate(null);
519 return;
520 }
521 final Intent additionalTarget = (Intent) targetsParcelable[i];
522 if (i == 0 && target == null) {
523 target = additionalTarget;
524 modifyTargetIntent(target);
525 } else {
526 additionalTargets[offset ? i - 1 : i] = additionalTarget;
527 modifyTargetIntent(additionalTarget);
528 }
529 }
530 setAdditionalTargets(additionalTargets);
531 }
532
Adam Powelle49d9392014-07-17 18:45:19 -0700533 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
Matt Pietal26038402019-01-08 07:29:34 -0500534
535 // Do not allow the title to be changed when sharing content
536 CharSequence title = null;
537 if (target != null) {
Matt Pietal95574b02019-03-13 08:12:25 -0400538 if (!isSendAction(target)) {
Matt Pietal26038402019-01-08 07:29:34 -0500539 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
540 } else {
541 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
542 + " preview title by using EXTRA_TITLE property of the wrapped"
543 + " EXTRA_INTENT.");
544 }
545 }
546
Adam Powell278902c2014-07-12 18:33:22 -0700547 int defaultTitleRes = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548 if (title == null) {
Adam Powell278902c2014-07-12 18:33:22 -0700549 defaultTitleRes = com.android.internal.R.string.chooseActivity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800550 }
Matt Pietal26038402019-01-08 07:29:34 -0500551
Dianne Hackborneb034652009-09-07 00:49:58 -0700552 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
553 Intent[] initialIntents = null;
554 if (pa != null) {
Matt Pietal4e2e3632019-04-05 08:32:47 -0400555 int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS);
556 initialIntents = new Intent[count];
557 for (int i = 0; i < count; i++) {
Dianne Hackborneb034652009-09-07 00:49:58 -0700558 if (!(pa[i] instanceof Intent)) {
Adam Powell2ed547e2015-04-29 18:45:04 -0700559 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
Dianne Hackborneb034652009-09-07 00:49:58 -0700560 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800561 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700562 return;
563 }
Craig Mautner411d2aed2014-05-08 09:07:43 -0700564 final Intent in = (Intent) pa[i];
Adam Powelle49d9392014-07-17 18:45:19 -0700565 modifyTargetIntent(in);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700566 initialIntents[i] = in;
Dianne Hackborneb034652009-09-07 00:49:58 -0700567 }
568 }
Adam Powell24428412015-04-01 17:19:56 -0700569
Adam Powell13036be2015-05-12 14:43:56 -0700570 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
571
Adam Powell0b3c1122014-10-09 12:50:14 -0700572 mChosenComponentSender = intent.getParcelableExtra(
573 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
Adam Powell2ed547e2015-04-29 18:45:04 -0700574 mRefinementIntentSender = intent.getParcelableExtra(
575 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
Dianne Hackborn028ceeb2014-08-17 17:45:48 -0700576 setSafeForwardingMode(true);
Adam Powell23882512016-01-29 10:21:00 -0800577
Adam Powell52c39212016-04-07 15:14:18 -0700578 pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
579 if (pa != null) {
580 ComponentName[] names = new ComponentName[pa.length];
581 for (int i = 0; i < pa.length; i++) {
582 if (!(pa[i] instanceof ComponentName)) {
583 Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
584 names = null;
585 break;
586 }
587 names[i] = (ComponentName) pa[i];
588 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800589 mFilteredComponentNames = names;
Adam Powell52c39212016-04-07 15:14:18 -0700590 }
591
592 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
593 if (pa != null) {
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400594 int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS);
595 ChooserTarget[] targets = new ChooserTarget[count];
596 for (int i = 0; i < count; i++) {
Adam Powell52c39212016-04-07 15:14:18 -0700597 if (!(pa[i] instanceof ChooserTarget)) {
598 Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
599 targets = null;
600 break;
601 }
602 targets[i] = (ChooserTarget) pa[i];
603 }
604 mCallerChooserTargets = targets;
605 }
606
Jorim Jaggif631ef72017-02-24 13:49:47 +0100607 setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
Adam Powell278902c2014-07-12 18:33:22 -0700608 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
609 null, false);
Adam Powell98b7f892015-06-19 12:38:45 -0700610
Kang Li9082f5b2016-12-02 10:56:21 -0800611 mChooserShownTime = System.currentTimeMillis();
612 final long systemCost = mChooserShownTime - intentReceivedTime;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500613
614 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500615 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
616 MetricsEvent.PARENT_PROFILE)
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500617 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
618 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
George Hodulik69d4a082019-01-18 11:27:03 -0800619
George Hodulik145b3a52019-03-27 11:18:43 -0700620 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
621 if (appPredictor != null) {
George Hodulikaa5238c2019-04-18 14:17:51 -0700622 mDirectShareAppTargetCache = new HashMap<>();
George Hodulik69d4a082019-01-18 11:27:03 -0800623 mAppPredictorCallback = resultList -> {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500624 if (isFinishing() || isDestroyed()) {
625 return;
626 }
George Hodulik0dd5fbe2019-03-06 12:00:26 -0800627 // May be null if there are no apps to perform share/open action.
628 if (mChooserListAdapter == null) {
629 return;
630 }
George Hodulik3f399f22019-04-26 16:17:54 -0700631 if (resultList.isEmpty()) {
632 // APS may be disabled, so try querying targets ourselves.
633 queryDirectShareTargets(mChooserListAdapter, true);
634 return;
635 }
George Hodulik69d4a082019-01-18 11:27:03 -0800636 final List<DisplayResolveInfo> driList =
637 getDisplayResolveInfos(mChooserListAdapter);
638 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
639 new ArrayList<>();
640 for (AppTarget appTarget : resultList) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500641 if (appTarget.getShortcutInfo() == null) {
642 continue;
643 }
George Hodulik69d4a082019-01-18 11:27:03 -0800644 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
645 appTarget.getShortcutInfo(),
646 new ComponentName(
647 appTarget.getPackageName(), appTarget.getClassName())));
648 }
George Hodulikaa5238c2019-04-18 14:17:51 -0700649 sendShareShortcutInfoList(shareShortcutInfos, driList, resultList);
George Hodulik69d4a082019-01-18 11:27:03 -0800650 };
George Hodulik145b3a52019-03-27 11:18:43 -0700651 appPredictor
652 .registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
George Hodulik69d4a082019-01-18 11:27:03 -0800653 }
654
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500655 mChooserRowServiceSpacing = getResources()
656 .getDimensionPixelSize(R.dimen.chooser_service_spacing);
657
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400658 if (mResolverDrawerLayout != null) {
659 mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
660
661 // expand/shrink direct share 4 -> 8 viewgroup
662 if (isSendAction(target)) {
663 mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
664 }
Matt Pietalb1d629d2019-04-23 11:35:53 -0400665
666 final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
667 final float defaultElevation = chooserHeader.getElevation();
668 final float chooserHeaderScrollElevation =
669 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
670
671 mAdapterView.setOnScrollListener(new AbsListView.OnScrollListener() {
672 public void onScrollStateChanged(AbsListView view, int scrollState) {
673 }
674
675 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
676 int totalItemCount) {
677 if (view.getChildCount() > 0) {
678 if (firstVisibleItem > 0 || view.getChildAt(0).getTop() < 0) {
679 chooserHeader.setElevation(chooserHeaderScrollElevation);
680 return;
681 }
682 }
683
684 chooserHeader.setElevation(defaultElevation);
685 }
686 });
Mike Digman849a9d12019-04-29 11:20:48 -0700687
688 mResolverDrawerLayout.setOnCollapsedChangedListener(
689 new ResolverDrawerLayout.OnCollapsedChangedListener() {
690
691 // Only consider one expansion per activity creation
692 private boolean mWrittenOnce = false;
693
694 @Override
695 public void onCollapsedChanged(boolean isCollapsed) {
696 if (!isCollapsed && !mWrittenOnce) {
697 incrementNumSheetExpansions();
698 mWrittenOnce = true;
699 }
700 }
701 });
Matt Pietal5b648562019-03-12 07:40:26 -0400702 }
703
Kang Li9082f5b2016-12-02 10:56:21 -0800704 if (DEBUG) {
705 Log.d(TAG, "System Time Cost is " + systemCost);
706 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800707 }
Adam Powelle49d9392014-07-17 18:45:19 -0700708
Matt Pietal26038402019-01-08 07:29:34 -0500709 /**
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500710 * Check if the profile currently used is a work profile.
711 * @return true if it is work profile, false if it is parent profile (or no work profile is
712 * set up)
713 */
714 protected boolean isWorkProfile() {
715 return ((UserManager) getSystemService(Context.USER_SERVICE))
716 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
717 }
718
Matt Pietalab73a882019-06-05 07:04:55 -0400719 @Override
720 protected PackageMonitor createPackageMonitor() {
721 return new PackageMonitor() {
722 @Override
723 public void onSomePackagesChanged() {
724 mAdapter.handlePackagesChanged();
725 bindProfileView();
726 }
727 };
728 }
729
Matt Pietal46d828c2019-02-05 08:07:07 -0500730 private void onCopyButtonClicked(View v) {
731 Intent targetIntent = getTargetIntent();
732 if (targetIntent == null) {
733 finish();
734 } else {
735 final String action = targetIntent.getAction();
736
737 ClipData clipData = null;
738 if (Intent.ACTION_SEND.equals(action)) {
739 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
740 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
741
742 if (extraText != null) {
743 clipData = ClipData.newPlainText(null, extraText);
744 } else if (extraStream != null) {
745 clipData = ClipData.newUri(getContentResolver(), null, extraStream);
746 } else {
747 Log.w(TAG, "No data available to copy to clipboard");
748 return;
749 }
750 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
751 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
752 Intent.EXTRA_STREAM);
753 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
754 for (int i = 1; i < streams.size(); i++) {
755 clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
756 }
757 } else {
758 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
759 // so warn about unexpected action
760 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
761 return;
762 }
763
764 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
765 Context.CLIPBOARD_SERVICE);
766 clipboardManager.setPrimaryClip(clipData);
767 Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
768
769 finish();
770 }
771 }
772
Matt Pietal18bbd822019-02-12 15:21:36 -0500773 @Override
774 public void onConfigurationChanged(Configuration newConfig) {
775 super.onConfigurationChanged(newConfig);
776
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400777 adjustPreviewWidth(newConfig.orientation, null);
778 }
779
780 private boolean shouldDisplayLandscape(int orientation) {
781 // Sharesheet fixes the # of items per row and therefore can not correctly lay out
782 // when in the restricted size of multi-window mode. In the future, would be nice
783 // to use minimum dp size requirements instead
784 return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
785 }
786
787 private void adjustPreviewWidth(int orientation, View parent) {
Matt Pietal18bbd822019-02-12 15:21:36 -0500788 int width = -1;
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400789 if (shouldDisplayLandscape(orientation)) {
Matt Pietal18bbd822019-02-12 15:21:36 -0500790 width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
791 }
792
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400793 parent = parent == null ? getWindow().getDecorView() : parent;
794
795 updateLayoutWidth(R.id.content_preview_text_layout, width, parent);
796 updateLayoutWidth(R.id.content_preview_title_layout, width, parent);
797 updateLayoutWidth(R.id.content_preview_file_layout, width, parent);
Matt Pietal18bbd822019-02-12 15:21:36 -0500798 }
799
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400800 private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
801 View view = parent.findViewById(layoutResourceId);
Matt Pietal1ef88002019-03-13 10:43:18 -0400802 if (view != null && view.getLayoutParams() != null) {
803 LayoutParams params = view.getLayoutParams();
804 params.width = width;
805 view.setLayoutParams(params);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500806 }
807 }
808
Matt Pietal1ef88002019-03-13 10:43:18 -0400809 private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
810 Intent targetIntent, LayoutInflater layoutInflater, ViewGroup convertView,
811 ViewGroup parent) {
Matt Pietale7cacab2019-05-23 07:21:36 -0400812 if (convertView != null) return convertView;
813
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400814 ViewGroup layout = null;
815
Matt Pietal1ef88002019-03-13 10:43:18 -0400816 switch (previewType) {
817 case CONTENT_PREVIEW_TEXT:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400818 layout = displayTextContentPreview(targetIntent, layoutInflater, parent);
819 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400820 case CONTENT_PREVIEW_IMAGE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400821 layout = displayImageContentPreview(targetIntent, layoutInflater, parent);
822 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400823 case CONTENT_PREVIEW_FILE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400824 layout = displayFileContentPreview(targetIntent, layoutInflater, parent);
825 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400826 default:
827 Log.e(TAG, "Unexpected content preview type: " + previewType);
828 }
Matt Pietal0ea391b2019-01-30 10:44:15 -0500829
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400830 if (layout != null) {
831 adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
832 }
833
834 return layout;
Matt Pietal1ef88002019-03-13 10:43:18 -0400835 }
836
837 private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -0400838 ViewGroup parent) {
839 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
840 R.layout.chooser_grid_preview_text, parent, false);
Matt Pietal1ef88002019-03-13 10:43:18 -0400841
842 contentPreviewLayout.findViewById(R.id.copy_button).setOnClickListener(
843 this::onCopyButtonClicked);
Matt Pietal46d828c2019-02-05 08:07:07 -0500844
Matt Pietal26038402019-01-08 07:29:34 -0500845 CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
Matt Pietal26038402019-01-08 07:29:34 -0500846 if (sharingText == null) {
Matt Pietal1ef88002019-03-13 10:43:18 -0400847 contentPreviewLayout.findViewById(R.id.content_preview_text_layout).setVisibility(
848 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500849 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -0400850 TextView textView = contentPreviewLayout.findViewById(R.id.content_preview_text);
Matt Pietal1fa7d802019-01-30 10:44:15 -0500851 textView.setText(sharingText);
Matt Pietal26038402019-01-08 07:29:34 -0500852 }
853
854 String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
Matt Pietal46d828c2019-02-05 08:07:07 -0500855 if (TextUtils.isEmpty(previewTitle)) {
Matt Pietal1ef88002019-03-13 10:43:18 -0400856 contentPreviewLayout.findViewById(R.id.content_preview_title_layout).setVisibility(
857 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500858 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -0400859 TextView previewTitleView = contentPreviewLayout.findViewById(
860 R.id.content_preview_title);
Matt Pietal26038402019-01-08 07:29:34 -0500861 previewTitleView.setText(previewTitle);
Matt Pietal26038402019-01-08 07:29:34 -0500862
Matt Pietal1fa7d802019-01-30 10:44:15 -0500863 ClipData previewData = targetIntent.getClipData();
864 Uri previewThumbnail = null;
865 if (previewData != null) {
866 if (previewData.getItemCount() > 0) {
867 ClipData.Item previewDataItem = previewData.getItemAt(0);
868 previewThumbnail = previewDataItem.getUri();
869 }
Matt Pietal26038402019-01-08 07:29:34 -0500870 }
Matt Pietal26038402019-01-08 07:29:34 -0500871
Matt Pietal1ef88002019-03-13 10:43:18 -0400872 ImageView previewThumbnailView = contentPreviewLayout.findViewById(
873 R.id.content_preview_thumbnail);
Matt Pietal1fa7d802019-01-30 10:44:15 -0500874 if (previewThumbnail == null) {
Matt Pietal26038402019-01-08 07:29:34 -0500875 previewThumbnailView.setVisibility(View.GONE);
876 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -0400877 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
878 mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
Matt Pietal26038402019-01-08 07:29:34 -0500879 }
880 }
Matt Pietal1ef88002019-03-13 10:43:18 -0400881
882 return contentPreviewLayout;
Matt Pietal26038402019-01-08 07:29:34 -0500883 }
884
Matt Pietal1ef88002019-03-13 10:43:18 -0400885 private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -0400886 ViewGroup parent) {
887 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
888 R.layout.chooser_grid_preview_image, parent, false);
889 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500890
891 String action = targetIntent.getAction();
892 if (Intent.ACTION_SEND.equals(action)) {
893 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietale7cacab2019-05-23 07:21:36 -0400894 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500895 } else {
896 ContentResolver resolver = getContentResolver();
897
898 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
899 List<Uri> imageUris = new ArrayList<>();
900 for (Uri uri : uris) {
901 if (isImageType(resolver.getType(uri))) {
902 imageUris.add(uri);
903 }
904 }
905
906 if (imageUris.size() == 0) {
907 Log.i(TAG, "Attempted to display image preview area with zero"
908 + " available images detected in EXTRA_STREAM list");
Matt Pietal46d828c2019-02-05 08:07:07 -0500909 contentPreviewLayout.setVisibility(View.GONE);
Matt Pietal1ef88002019-03-13 10:43:18 -0400910 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500911 }
912
Matt Pietale7cacab2019-05-23 07:21:36 -0400913 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500914
915 if (imageUris.size() == 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -0400916 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
917 imageUris.get(1), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500918 } else if (imageUris.size() > 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -0400919 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
920 imageUris.get(1), 0);
921 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
922 imageUris.get(2), imageUris.size() - 3);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500923 }
924 }
Matt Pietal1ef88002019-03-13 10:43:18 -0400925
926 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500927 }
928
Matt Pietal46d828c2019-02-05 08:07:07 -0500929 private static class FileInfo {
930 public final String name;
931 public final boolean hasThumbnail;
932
933 FileInfo(String name, boolean hasThumbnail) {
934 this.name = name;
935 this.hasThumbnail = hasThumbnail;
936 }
937 }
938
Matt Pietalf38e9d22019-02-15 10:01:03 -0500939 /**
940 * Wrapping the ContentResolver call to expose for easier mocking,
941 * and to avoid mocking Android core classes.
942 */
943 @VisibleForTesting
944 public Cursor queryResolver(ContentResolver resolver, Uri uri) {
945 return resolver.query(uri, null, null, null, null);
946 }
947
Matt Pietal46d828c2019-02-05 08:07:07 -0500948 private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
949 String fileName = null;
950 boolean hasThumbnail = false;
Matt Pietal3087bca2019-02-14 12:19:16 -0500951
Matt Pietalf38e9d22019-02-15 10:01:03 -0500952 try (Cursor cursor = queryResolver(resolver, uri)) {
953 if (cursor != null && cursor.getCount() > 0) {
954 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
955 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
956 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
957
958 cursor.moveToFirst();
959 if (nameIndex != -1) {
960 fileName = cursor.getString(nameIndex);
961 } else if (titleIndex != -1) {
962 fileName = cursor.getString(titleIndex);
963 }
964
965 if (flagsIndex != -1) {
966 hasThumbnail = (cursor.getInt(flagsIndex)
967 & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
968 }
969 }
Matt Pietal73a873f2019-03-15 08:46:20 -0400970 } catch (SecurityException | NullPointerException e) {
Matt Pietal62532e52019-05-07 09:51:37 -0400971 logContentPreviewWarning(uri);
Matt Pietal3087bca2019-02-14 12:19:16 -0500972 }
973
Matt Pietal46d828c2019-02-05 08:07:07 -0500974 if (TextUtils.isEmpty(fileName)) {
975 fileName = uri.getPath();
976 int index = fileName.lastIndexOf('/');
977 if (index != -1) {
978 fileName = fileName.substring(index + 1);
979 }
980 }
981
982 return new FileInfo(fileName, hasThumbnail);
983 }
984
Matt Pietal62532e52019-05-07 09:51:37 -0400985 private void logContentPreviewWarning(Uri uri) {
986 // The ContentResolver already logs the exception. Log something more informative.
987 Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
988 + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
989 + "and set your Intent's clipData and flags in accordance with that method's "
990 + "documentation");
991 }
992
Matt Pietal1ef88002019-03-13 10:43:18 -0400993 private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -0400994 ViewGroup parent) {
Matt Pietal1ef88002019-03-13 10:43:18 -0400995
Matt Pietale7cacab2019-05-23 07:21:36 -0400996 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
997 R.layout.chooser_grid_preview_file, parent, false);
Matt Pietal46d828c2019-02-05 08:07:07 -0500998
999 // TODO(b/120417119): Disable file copy until after moving to sysui,
1000 // due to permissions issues
Matt Pietal1ef88002019-03-13 10:43:18 -04001001 contentPreviewLayout.findViewById(R.id.file_copy_button).setVisibility(View.GONE);
Matt Pietal46d828c2019-02-05 08:07:07 -05001002
Matt Pietal3087bca2019-02-14 12:19:16 -05001003 String action = targetIntent.getAction();
1004 if (Intent.ACTION_SEND.equals(action)) {
1005 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietal1ef88002019-03-13 10:43:18 -04001006 loadFileUriIntoView(uri, contentPreviewLayout);
Matt Pietal3087bca2019-02-14 12:19:16 -05001007 } else {
1008 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1009 int uriCount = uris.size();
Matt Pietal46d828c2019-02-05 08:07:07 -05001010
Matt Pietal3087bca2019-02-14 12:19:16 -05001011 if (uriCount == 0) {
1012 contentPreviewLayout.setVisibility(View.GONE);
1013 Log.i(TAG,
1014 "Appears to be no uris available in EXTRA_STREAM, removing "
1015 + "preview area");
Matt Pietal1ef88002019-03-13 10:43:18 -04001016 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001017 } else if (uriCount == 1) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001018 loadFileUriIntoView(uris.get(0), contentPreviewLayout);
Matt Pietal46d828c2019-02-05 08:07:07 -05001019 } else {
Matt Pietal3087bca2019-02-14 12:19:16 -05001020 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
1021 int remUriCount = uriCount - 1;
Matt Pietalacabc572019-02-14 11:02:05 -05001022 String fileName = getResources().getQuantityString(R.plurals.file_count,
Matt Pietal3087bca2019-02-14 12:19:16 -05001023 remUriCount, fileInfo.name, remUriCount);
Matt Pietalacabc572019-02-14 11:02:05 -05001024
Matt Pietal1ef88002019-03-13 10:43:18 -04001025 TextView fileNameView = contentPreviewLayout.findViewById(
1026 R.id.content_preview_filename);
Matt Pietalacabc572019-02-14 11:02:05 -05001027 fileNameView.setText(fileName);
Matt Pietal3087bca2019-02-14 12:19:16 -05001028
Matt Pietale7cacab2019-05-23 07:21:36 -04001029 View thumbnailView = contentPreviewLayout.findViewById(
1030 R.id.content_preview_file_thumbnail);
1031 thumbnailView.setVisibility(View.GONE);
1032
Matt Pietal1ef88002019-03-13 10:43:18 -04001033 ImageView fileIconView = contentPreviewLayout.findViewById(
1034 R.id.content_preview_file_icon);
Matt Pietal46d828c2019-02-05 08:07:07 -05001035 fileIconView.setVisibility(View.VISIBLE);
Matt Pietalacabc572019-02-14 11:02:05 -05001036 fileIconView.setImageResource(R.drawable.ic_file_copy);
Matt Pietal46d828c2019-02-05 08:07:07 -05001037 }
Matt Pietal3087bca2019-02-14 12:19:16 -05001038 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001039
1040 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001041 }
1042
Matt Pietale7cacab2019-05-23 07:21:36 -04001043 private void loadFileUriIntoView(final Uri uri, final View parent) {
Matt Pietal3087bca2019-02-14 12:19:16 -05001044 FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
1045
Matt Pietal1ef88002019-03-13 10:43:18 -04001046 TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
Matt Pietal3087bca2019-02-14 12:19:16 -05001047 fileNameView.setText(fileInfo.name);
1048
1049 if (fileInfo.hasThumbnail) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001050 mPreviewCoord = new ContentPreviewCoordinator(parent, false);
1051 mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
Matt Pietal3087bca2019-02-14 12:19:16 -05001052 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -04001053 View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
1054 thumbnailView.setVisibility(View.GONE);
1055
Matt Pietal1ef88002019-03-13 10:43:18 -04001056 ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
Matt Pietal3087bca2019-02-14 12:19:16 -05001057 fileIconView.setVisibility(View.VISIBLE);
Matt Pietal832cdbf2019-04-05 13:20:31 -04001058 fileIconView.setImageResource(R.drawable.chooser_file_generic);
Matt Pietal46d828c2019-02-05 08:07:07 -05001059 }
Matt Pietal0ea391b2019-01-30 10:44:15 -05001060 }
1061
Matt Pietal0ea391b2019-01-30 10:44:15 -05001062 @VisibleForTesting
1063 protected boolean isImageType(String mimeType) {
1064 return mimeType != null && mimeType.startsWith("image/");
1065 }
1066
1067 @ContentPreviewType
1068 private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
1069 if (uri == null) {
1070 return CONTENT_PREVIEW_TEXT;
1071 }
1072
1073 String mimeType = resolver.getType(uri);
1074 return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
1075 }
1076
1077 /**
1078 * In {@link android.content.Intent#getType}, the app may specify a very general
1079 * mime-type that broadly covers all data being shared, such as {@literal *}/*
1080 * when sending an image and text. We therefore should inspect each item for the
1081 * the preferred type, in order of IMAGE, FILE, TEXT.
1082 */
1083 @ContentPreviewType
1084 private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
1085 String action = targetIntent.getAction();
1086 if (Intent.ACTION_SEND.equals(action)) {
1087 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1088 return findPreferredContentPreview(uri, resolver);
1089 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1090 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1091 if (uris == null || uris.isEmpty()) {
1092 return CONTENT_PREVIEW_TEXT;
1093 }
1094
1095 for (Uri uri : uris) {
Matt Pietal832cdbf2019-04-05 13:20:31 -04001096 // Defaulting to file preview when there are mixed image/file types is
1097 // preferable, as it shows the user the correct number of items being shared
1098 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_FILE) {
1099 return CONTENT_PREVIEW_FILE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001100 }
1101 }
1102
Matt Pietal832cdbf2019-04-05 13:20:31 -04001103 return CONTENT_PREVIEW_IMAGE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001104 }
1105
1106 return CONTENT_PREVIEW_TEXT;
1107 }
1108
Mike Digman849a9d12019-04-29 11:20:48 -07001109 private int getNumSheetExpansions() {
1110 return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0);
1111 }
1112
1113 private void incrementNumSheetExpansions() {
1114 getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS,
1115 getNumSheetExpansions() + 1).apply();
1116 }
1117
Adam Powell0b3c1122014-10-09 12:50:14 -07001118 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07001119 protected void onDestroy() {
1120 super.onDestroy();
1121 if (mRefinementResultReceiver != null) {
1122 mRefinementResultReceiver.destroy();
1123 mRefinementResultReceiver = null;
1124 }
Adam Powell9761ab22015-09-08 17:01:49 -07001125 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -04001126 mChooserHandler.removeAllMessages();
Matt Pietale7cacab2019-05-23 07:21:36 -04001127
1128 if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
1129
George Hodulik145b3a52019-03-27 11:18:43 -07001130 if (mAppPredictor != null) {
George Hodulik69d4a082019-01-18 11:27:03 -08001131 mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
1132 mAppPredictor.destroy();
1133 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001134 }
1135
1136 @Override
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001137 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
1138 Intent result = defIntent;
Adam Powelle49d9392014-07-17 18:45:19 -07001139 if (mReplacementExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001140 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
Adam Powelle49d9392014-07-17 18:45:19 -07001141 if (replExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001142 result = new Intent(defIntent);
Adam Powelle49d9392014-07-17 18:45:19 -07001143 result.putExtras(replExtras);
Adam Powelle49d9392014-07-17 18:45:19 -07001144 }
1145 }
Nicolas Prevot741abfc2015-08-11 12:03:51 +01001146 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001147 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
1148 result = Intent.createChooser(result,
1149 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
Hakan Seyalioglu7317e8a2016-12-12 16:15:38 -08001150
1151 // Don't auto-launch single intents if the intent is being forwarded. This is done
1152 // because automatically launching a resolving application as a response to the user
1153 // action of switching accounts is pretty unexpected.
1154 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001155 }
1156 return result;
Adam Powelle49d9392014-07-17 18:45:19 -07001157 }
1158
Adam Powell0b3c1122014-10-09 12:50:14 -07001159 @Override
Adam Powell23882512016-01-29 10:21:00 -08001160 public void onActivityStarted(TargetInfo cti) {
Adam Powell0b3c1122014-10-09 12:50:14 -07001161 if (mChosenComponentSender != null) {
Adam Powell24428412015-04-01 17:19:56 -07001162 final ComponentName target = cti.getResolvedComponentName();
Adam Powell0b3c1122014-10-09 12:50:14 -07001163 if (target != null) {
1164 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
1165 try {
1166 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
1167 } catch (IntentSender.SendIntentException e) {
1168 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
1169 + "the chosen component: " + e);
1170 }
1171 }
1172 }
1173 }
1174
Adam Powell24428412015-04-01 17:19:56 -07001175 @Override
Hakan Seyalioglu13405c52017-01-31 19:01:31 -08001176 public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
Adam Powell7d758002015-05-06 17:49:36 -07001177 final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
1178 mChooserListAdapter = (ChooserListAdapter) adapter;
Adam Powell52c39212016-04-07 15:14:18 -07001179 if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
Matt Pietalfbfa0492019-04-01 11:29:56 -04001180 mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets),
1181 false);
Adam Powell52c39212016-04-07 15:14:18 -07001182 }
Adam Powell63b31692015-09-28 10:45:00 -07001183 mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
Adam Powell7d758002015-05-06 17:49:36 -07001184 if (listView != null) {
1185 listView.setItemsCanFocus(true);
1186 }
1187 }
1188
1189 @Override
Adam Powell23882512016-01-29 10:21:00 -08001190 public int getLayoutResource() {
Adam Powell7d758002015-05-06 17:49:36 -07001191 return R.layout.chooser_grid;
Adam Powell24428412015-04-01 17:19:56 -07001192 }
1193
1194 @Override
Adam Powell23882512016-01-29 10:21:00 -08001195 public boolean shouldGetActivityMetadata() {
Adam Powell24428412015-04-01 17:19:56 -07001196 return true;
1197 }
1198
Adam Powell9761ab22015-09-08 17:01:49 -07001199 @Override
Ben Lin145b0ca2016-10-14 14:23:40 -07001200 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
Hakan Seyalioglu13405c52017-01-31 19:01:31 -08001201 // Note that this is only safe because the Intent handled by the ChooserActivity is
1202 // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
1203 // method can not be replaced in the ResolverActivity whole hog.
Matt Pietala4b30072019-04-04 13:44:36 -04001204 if (!super.shouldAutoLaunchSingleChoice(target)) {
1205 return false;
1206 }
1207
1208 return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
Ben Lin145b0ca2016-10-14 14:23:40 -07001209 }
1210
1211 @Override
Adam Powell23882512016-01-29 10:21:00 -08001212 public void showTargetDetails(ResolveInfo ri) {
sanryhuang296ca9e2018-03-31 11:17:13 +08001213 if (ri == null) {
1214 return;
1215 }
1216
Adam Powell23882512016-01-29 10:21:00 -08001217 ComponentName name = ri.activityInfo.getComponentName();
Adam Powell23882512016-01-29 10:21:00 -08001218 ResolverTargetActionsDialogFragment f =
1219 new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
Matt Pietaldf634cc2019-03-13 09:55:28 -04001220 name);
Adam Powell23882512016-01-29 10:21:00 -08001221 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1222 }
1223
Adam Powelle49d9392014-07-17 18:45:19 -07001224 private void modifyTargetIntent(Intent in) {
Matt Pietal95574b02019-03-13 08:12:25 -04001225 if (isSendAction(in)) {
Adam Powelle49d9392014-07-17 18:45:19 -07001226 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
Dianne Hackborn13420f22014-07-18 15:43:56 -07001227 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Adam Powelle49d9392014-07-17 18:45:19 -07001228 }
1229 }
Adam Powell24428412015-04-01 17:19:56 -07001230
Adam Powell2ed547e2015-04-29 18:45:04 -07001231 @Override
1232 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
1233 if (mRefinementIntentSender != null) {
1234 final Intent fillIn = new Intent();
1235 final List<Intent> sourceIntents = target.getAllSourceIntents();
1236 if (!sourceIntents.isEmpty()) {
1237 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
1238 if (sourceIntents.size() > 1) {
1239 final Intent[] alts = new Intent[sourceIntents.size() - 1];
1240 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
1241 alts[i - 1] = sourceIntents.get(i);
1242 }
1243 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
1244 }
1245 if (mRefinementResultReceiver != null) {
1246 mRefinementResultReceiver.destroy();
1247 }
1248 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
1249 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
1250 mRefinementResultReceiver);
1251 try {
1252 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
1253 return false;
1254 } catch (SendIntentException e) {
1255 Log.e(TAG, "Refinement IntentSender failed to send", e);
1256 }
1257 }
1258 }
Kang Li9fa2a2c2017-01-06 13:33:24 -08001259 updateModelAndChooserCounts(target);
Adam Powell2ed547e2015-04-29 18:45:04 -07001260 return super.onTargetSelected(target, alwaysCheck);
1261 }
1262
Adam Powell98b7f892015-06-19 12:38:45 -07001263 @Override
Adam Powell23882512016-01-29 10:21:00 -08001264 public void startSelected(int which, boolean always, boolean filtered) {
Matt Pietala4b30072019-04-04 13:44:36 -04001265 TargetInfo targetInfo = mChooserListAdapter.targetInfoForPosition(which, filtered);
1266 if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
1267 return;
1268 }
1269
Kang Li9082f5b2016-12-02 10:56:21 -08001270 final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
Adam Powell98b7f892015-06-19 12:38:45 -07001271 super.startSelected(which, always, filtered);
1272
1273 if (mChooserListAdapter != null) {
1274 // Log the index of which type of target the user picked.
1275 // Lower values mean the ranking was better.
1276 int cat = 0;
1277 int value = which;
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001278 int directTargetAlsoRanked = -1;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001279 int numCallerProvided = 0;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001280 HashedStringCache.HashResult directTargetHashed = null;
Adam Powell98b7f892015-06-19 12:38:45 -07001281 switch (mChooserListAdapter.getPositionTargetType(which)) {
Adam Powell98b7f892015-06-19 12:38:45 -07001282 case ChooserListAdapter.TARGET_SERVICE:
Chris Wrenf6e9228b2016-01-26 18:04:35 -05001283 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001284 // Log the package name + target name to answer the question if most users
1285 // share to mostly the same person or to a bunch of different people.
1286 ChooserTarget target =
1287 mChooserListAdapter.mServiceTargets.get(value).getChooserTarget();
1288 directTargetHashed = HashedStringCache.getInstance().hashString(
1289 this,
1290 TAG,
1291 target.getComponentName().getPackageName()
1292 + target.getTitle().toString(),
1293 mMaxHashSaltDays);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001294 directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001295
1296 if (mCallerChooserTargets != null) {
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001297 numCallerProvided = mCallerChooserTargets.length;
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001298 }
Adam Powell98b7f892015-06-19 12:38:45 -07001299 break;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001300 case ChooserListAdapter.TARGET_CALLER:
Adam Powell98b7f892015-06-19 12:38:45 -07001301 case ChooserListAdapter.TARGET_STANDARD:
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001302 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
1303 value -= mChooserListAdapter.getSelectableServiceTargetCount();
1304 numCallerProvided = mChooserListAdapter.getCallerTargetCount();
Adam Powell98b7f892015-06-19 12:38:45 -07001305 break;
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04001306 case ChooserListAdapter.TARGET_STANDARD_AZ:
1307 // A-Z targets are unranked standard targets; we use -1 to mark that they
1308 // are from the alphabetical pool.
1309 value = -1;
1310 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
1311 break;
Adam Powell98b7f892015-06-19 12:38:45 -07001312 }
1313
1314 if (cat != 0) {
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001315 LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
1316 if (directTargetHashed != null) {
1317 targetLogMaker.addTaggedData(
1318 MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
1319 targetLogMaker.addTaggedData(
1320 MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
1321 directTargetHashed.saltGeneration);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001322 targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
1323 directTargetAlsoRanked);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001324 }
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001325 targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED,
1326 numCallerProvided);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001327 getMetricsLogger().write(targetLogMaker);
Adam Powell98b7f892015-06-19 12:38:45 -07001328 }
Kang Li9082f5b2016-12-02 10:56:21 -08001329
1330 if (mIsSuccessfullySelected) {
1331 if (DEBUG) {
1332 Log.d(TAG, "User Selection Time Cost is " + selectionCost);
1333 Log.d(TAG, "position of selected app/service/caller is " +
1334 Integer.toString(value));
1335 }
1336 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
1337 (int) selectionCost);
1338 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
1339 }
Adam Powell98b7f892015-06-19 12:38:45 -07001340 }
1341 }
1342
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001343 private int getRankedPosition(SelectableTargetInfo targetInfo) {
1344 String targetPackageName =
1345 targetInfo.getChooserTarget().getComponentName().getPackageName();
1346 int maxRankedResults = Math.min(mChooserListAdapter.mDisplayList.size(),
1347 MAX_LOG_RANK_POSITION);
1348
1349 for (int i = 0; i < maxRankedResults; i++) {
1350 if (mChooserListAdapter.mDisplayList.get(i)
1351 .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
1352 return i;
1353 }
1354 }
1355 return -1;
1356 }
1357
Adam Powell24428412015-04-01 17:19:56 -07001358 void queryTargetServices(ChooserListAdapter adapter) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001359 mQueriedTargetServicesTimeMs = System.currentTimeMillis();
1360
Adam Powell24428412015-04-01 17:19:56 -07001361 final PackageManager pm = getPackageManager();
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001362 ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
Adam Powell24428412015-04-01 17:19:56 -07001363 int targetsToQuery = 0;
Matt Pietalab73a882019-06-05 07:04:55 -04001364
Adam Powell24428412015-04-01 17:19:56 -07001365 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
1366 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
Adam Powell3a09c522015-10-21 13:21:28 -07001367 if (adapter.getScore(dri) == 0) {
1368 // A score of 0 means the app hasn't been used in some time;
1369 // don't query it as it's not likely to be relevant.
1370 continue;
1371 }
Adam Powell24428412015-04-01 17:19:56 -07001372 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001373 if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
1374 && sm.hasShareTargets(ai.packageName)) {
1375 // Share targets will be queried from ShortcutManager
1376 continue;
1377 }
Adam Powell24428412015-04-01 17:19:56 -07001378 final Bundle md = ai.metaData;
1379 final String serviceName = md != null ? convertServiceName(ai.packageName,
1380 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
1381 if (serviceName != null) {
1382 final ComponentName serviceComponent = new ComponentName(
1383 ai.packageName, serviceName);
Matt Pietalab73a882019-06-05 07:04:55 -04001384
1385 if (mServicesRequested.contains(serviceComponent)) {
1386 continue;
1387 }
1388 mServicesRequested.add(serviceComponent);
1389
Adam Powell24428412015-04-01 17:19:56 -07001390 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
1391 .setComponent(serviceComponent);
1392
1393 if (DEBUG) {
1394 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
1395 }
1396
1397 try {
1398 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
1399 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
1400 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
1401 + " permission " + ChooserTargetService.BIND_PERMISSION
1402 + " - this service will not be queried for ChooserTargets."
1403 + " add android:permission=\""
1404 + ChooserTargetService.BIND_PERMISSION + "\""
1405 + " to the <service> tag for " + serviceComponent
1406 + " in the manifest.");
1407 continue;
1408 }
1409 } catch (NameNotFoundException e) {
Adam Powell52c39212016-04-07 15:14:18 -07001410 Log.e(TAG, "Could not look up service " + serviceComponent
1411 + "; component name not found");
Adam Powell24428412015-04-01 17:19:56 -07001412 continue;
1413 }
1414
Adam Powell9761ab22015-09-08 17:01:49 -07001415 final ChooserTargetServiceConnection conn =
1416 new ChooserTargetServiceConnection(this, dri);
Adam Powell52c39212016-04-07 15:14:18 -07001417
1418 // Explicitly specify Process.myUserHandle instead of calling bindService
1419 // to avoid the warning from calling from the system process without an explicit
1420 // user handle
1421 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
1422 Process.myUserHandle())) {
Adam Powell24428412015-04-01 17:19:56 -07001423 if (DEBUG) {
1424 Log.d(TAG, "Binding service connection for target " + dri
1425 + " intent " + serviceIntent);
1426 }
1427 mServiceConnections.add(conn);
1428 targetsToQuery++;
1429 }
1430 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001431 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
Matt Pietal26038402019-01-08 07:29:34 -05001432 if (DEBUG) {
1433 Log.d(TAG, "queryTargets hit query target limit "
1434 + QUERY_TARGET_SERVICE_LIMIT);
1435 }
Adam Powell24428412015-04-01 17:19:56 -07001436 break;
1437 }
1438 }
1439
Matt Pietalab73a882019-06-05 07:04:55 -04001440 mChooserHandler.restartServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -07001441 }
1442
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001443 private IntentFilter getTargetIntentFilter() {
1444 try {
1445 final Intent intent = getTargetIntent();
1446 String dataString = intent.getDataString();
1447 if (TextUtils.isEmpty(dataString)) {
1448 dataString = intent.getType();
1449 }
1450 return new IntentFilter(intent.getAction(), dataString);
1451 } catch (Exception e) {
1452 Log.e(TAG, "failed to get target intent filter " + e);
1453 return null;
1454 }
1455 }
1456
George Hodulik69d4a082019-01-18 11:27:03 -08001457 private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001458 // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
1459 // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
George Hodulik69d4a082019-01-18 11:27:03 -08001460 List<DisplayResolveInfo> driList = new ArrayList<>();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001461 int targetsToQuery = 0;
1462 for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
1463 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
1464 if (adapter.getScore(dri) == 0) {
1465 // A score of 0 means the app hasn't been used in some time;
1466 // don't query it as it's not likely to be relevant.
1467 continue;
1468 }
1469 driList.add(dri);
1470 targetsToQuery++;
1471 // TODO(b/121287224): Do we need this here? (similar to queryTargetServices)
1472 if (targetsToQuery >= SHARE_TARGET_QUERY_PACKAGE_LIMIT) {
1473 if (DEBUG) {
1474 Log.d(TAG, "queryTargets hit query target limit "
1475 + SHARE_TARGET_QUERY_PACKAGE_LIMIT);
1476 }
1477 break;
1478 }
1479 }
George Hodulik69d4a082019-01-18 11:27:03 -08001480 return driList;
1481 }
1482
George Hodulik3f399f22019-04-26 16:17:54 -07001483 private void queryDirectShareTargets(
1484 ChooserListAdapter adapter, boolean skipAppPredictionService) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001485 mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
George Hodulik3f399f22019-04-26 16:17:54 -07001486 if (!skipAppPredictionService) {
1487 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
1488 if (appPredictor != null) {
1489 appPredictor.requestPredictionUpdate();
1490 return;
1491 }
George Hodulik69d4a082019-01-18 11:27:03 -08001492 }
George Hodulik145b3a52019-03-27 11:18:43 -07001493 // Default to just querying ShortcutManager if AppPredictor not present.
George Hodulik69d4a082019-01-18 11:27:03 -08001494 final IntentFilter filter = getTargetIntentFilter();
1495 if (filter == null) {
1496 return;
1497 }
1498 final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001499
1500 AsyncTask.execute(() -> {
1501 ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
1502 List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
George Hodulikaa5238c2019-04-18 14:17:51 -07001503 sendShareShortcutInfoList(resultList, driList, null);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001504 });
1505 }
1506
George Hodulik69d4a082019-01-18 11:27:03 -08001507 private void sendShareShortcutInfoList(
1508 List<ShortcutManager.ShareShortcutInfo> resultList,
George Hodulikaa5238c2019-04-18 14:17:51 -07001509 List<DisplayResolveInfo> driList,
1510 @Nullable List<AppTarget> appTargets) {
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001511 if (appTargets != null && appTargets.size() != resultList.size()) {
1512 throw new RuntimeException("resultList and appTargets must have the same size."
1513 + " resultList.size()=" + resultList.size()
1514 + " appTargets.size()=" + appTargets.size());
1515 }
1516
1517 for (int i = resultList.size() - 1; i >= 0; i--) {
1518 final String packageName = resultList.get(i).getTargetComponent().getPackageName();
1519 if (!isPackageEnabled(packageName)) {
1520 resultList.remove(i);
1521 if (appTargets != null) {
1522 appTargets.remove(i);
1523 }
1524 }
1525 }
1526
George Hodulik69d4a082019-01-18 11:27:03 -08001527 // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
1528 // for direct share targets. After ShareSheet is refactored we should use the
1529 // ShareShortcutInfos directly.
1530 boolean resultMessageSent = false;
1531 for (int i = 0; i < driList.size(); i++) {
1532 List<ChooserTarget> chooserTargets = new ArrayList<>();
1533 for (int j = 0; j < resultList.size(); j++) {
1534 if (driList.get(i).getResolvedComponentName().equals(
1535 resultList.get(j).getTargetComponent())) {
George Hodulikaa5238c2019-04-18 14:17:51 -07001536 ShortcutManager.ShareShortcutInfo shareShortcutInfo = resultList.get(j);
Matt Pietalbdbeea22019-07-01 13:06:21 -04001537 // Incoming results are ordered but without a score. Create a score
1538 // based on the index in order to be sorted appropriately when joined
1539 // with legacy direct share api results.
1540 float score = Math.max(1.0f - (0.05f * j), 0.0f);
1541 ChooserTarget chooserTarget = convertToChooserTarget(shareShortcutInfo, score);
George Hodulikaa5238c2019-04-18 14:17:51 -07001542 chooserTargets.add(chooserTarget);
1543 if (mDirectShareAppTargetCache != null && appTargets != null) {
George Hodulikaa5238c2019-04-18 14:17:51 -07001544 mDirectShareAppTargetCache.put(chooserTarget, appTargets.get(j));
1545 }
George Hodulik69d4a082019-01-18 11:27:03 -08001546 }
1547 }
1548 if (chooserTargets.isEmpty()) {
1549 continue;
1550 }
1551 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04001552 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
George Hodulik69d4a082019-01-18 11:27:03 -08001553 msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
1554 mChooserHandler.sendMessage(msg);
1555 resultMessageSent = true;
1556 }
1557
1558 if (resultMessageSent) {
George Hodulik145b3a52019-03-27 11:18:43 -07001559 sendShortcutManagerShareTargetResultCompleted();
George Hodulik69d4a082019-01-18 11:27:03 -08001560 }
1561 }
1562
George Hodulik145b3a52019-03-27 11:18:43 -07001563 private void sendShortcutManagerShareTargetResultCompleted() {
1564 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04001565 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
George Hodulik145b3a52019-03-27 11:18:43 -07001566 mChooserHandler.sendMessage(msg);
1567 }
1568
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001569 private boolean isPackageEnabled(String packageName) {
1570 if (TextUtils.isEmpty(packageName)) {
1571 return false;
1572 }
1573 ApplicationInfo appInfo;
1574 try {
1575 appInfo = getPackageManager().getApplicationInfo(packageName, 0);
1576 } catch (NameNotFoundException e) {
1577 return false;
1578 }
1579
1580 if (appInfo != null && appInfo.enabled
1581 && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
1582 return true;
1583 }
1584 return false;
1585 }
1586
Matt Pietalbdbeea22019-07-01 13:06:21 -04001587 private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut,
1588 float score) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001589 ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
1590 Bundle extras = new Bundle();
1591 extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
1592 return new ChooserTarget(
1593 // The name of this target.
1594 shortcutInfo.getShortLabel(),
1595 // Don't load the icon until it is selected to be shown
1596 null,
1597 // The ranking score for this target (0.0-1.0); the system will omit items with low
1598 // scores when there are too many Direct Share items.
Matt Pietalbdbeea22019-07-01 13:06:21 -04001599 score,
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001600 // The name of the component to be launched if this target is chosen.
1601 shareShortcut.getTargetComponent().clone(),
1602 // The extra values here will be merged into the Intent when this target is chosen.
1603 extras);
1604 }
1605
Adam Powell24428412015-04-01 17:19:56 -07001606 private String convertServiceName(String packageName, String serviceName) {
1607 if (TextUtils.isEmpty(serviceName)) {
1608 return null;
1609 }
1610
1611 final String fullName;
1612 if (serviceName.startsWith(".")) {
1613 // Relative to the app package. Prepend the app package name.
1614 fullName = packageName + serviceName;
1615 } else if (serviceName.indexOf('.') >= 0) {
1616 // Fully qualified package name.
1617 fullName = serviceName;
1618 } else {
1619 fullName = null;
1620 }
1621 return fullName;
1622 }
1623
1624 void unbindRemainingServices() {
1625 if (DEBUG) {
1626 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
1627 }
1628 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
1629 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
1630 if (DEBUG) Log.d(TAG, "unbinding " + conn);
1631 unbindService(conn);
Adam Powell9761ab22015-09-08 17:01:49 -07001632 conn.destroy();
Adam Powell24428412015-04-01 17:19:56 -07001633 }
Matt Pietalab73a882019-06-05 07:04:55 -04001634 mServicesRequested.clear();
Adam Powell24428412015-04-01 17:19:56 -07001635 mServiceConnections.clear();
Adam Powell24428412015-04-01 17:19:56 -07001636 }
1637
Adam Powell23882512016-01-29 10:21:00 -08001638 public void onSetupVoiceInteraction() {
Adam Powell4c470d62015-06-19 17:46:17 -07001639 // Do nothing. We'll send the voice stuff ourselves.
1640 }
1641
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001642 private void logDirectShareTargetReceived(int logCategory) {
1643 final long queryTime =
1644 logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
1645 ? mQueriedSharingShortcutsTimeMs : mQueriedTargetServicesTimeMs;
1646 final int apiLatency = (int) (System.currentTimeMillis() - queryTime);
1647 getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
1648 }
1649
Kang Li9fa2a2c2017-01-06 13:33:24 -08001650 void updateModelAndChooserCounts(TargetInfo info) {
Kang Li53b43142016-11-14 14:38:25 -08001651 if (info != null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001652 sendClickToAppPredictor(info);
Kang Li53b43142016-11-14 14:38:25 -08001653 final ResolveInfo ri = info.getResolveInfo();
Kang Li64b018e2017-01-05 17:30:06 -08001654 Intent targetIntent = getTargetIntent();
1655 if (ri != null && ri.activityInfo != null && targetIntent != null) {
Kang Li0cef910d2017-01-05 09:14:36 -08001656 if (mAdapter != null) {
1657 mAdapter.updateModel(info.getResolvedComponentName());
Kang Li9fa2a2c2017-01-06 13:33:24 -08001658 mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
1659 targetIntent.getAction());
Kang Li0cef910d2017-01-05 09:14:36 -08001660 }
Kang Li53b43142016-11-14 14:38:25 -08001661 if (DEBUG) {
Kang Li64b018e2017-01-05 17:30:06 -08001662 Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
Kang Li64b018e2017-01-05 17:30:06 -08001663 Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
Kang Li53b43142016-11-14 14:38:25 -08001664 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001665 } else if (DEBUG) {
Kang Li53b43142016-11-14 14:38:25 -08001666 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
1667 }
1668 }
Kang Li9082f5b2016-12-02 10:56:21 -08001669 mIsSuccessfullySelected = true;
Kang Li53b43142016-11-14 14:38:25 -08001670 }
1671
George Hodulikf2b0d342019-01-25 12:43:54 -08001672 private void sendClickToAppPredictor(TargetInfo targetInfo) {
George Hodulikaa5238c2019-04-18 14:17:51 -07001673 AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled();
1674 if (directShareAppPredictor == null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001675 return;
1676 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001677 if (!(targetInfo instanceof ChooserTargetInfo)) {
1678 return;
1679 }
1680 ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
George Hodulikaa5238c2019-04-18 14:17:51 -07001681 AppTarget appTarget = null;
1682 if (mDirectShareAppTargetCache != null) {
1683 appTarget = mDirectShareAppTargetCache.get(chooserTarget);
George Hodulikf2b0d342019-01-25 12:43:54 -08001684 }
George Hodulikaa5238c2019-04-18 14:17:51 -07001685 // This is a direct share click that was provided by the APS
1686 if (appTarget != null) {
1687 directShareAppPredictor.notifyAppTargetEvent(
1688 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
1689 .setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
1690 .build());
George Hodulikf2b0d342019-01-25 12:43:54 -08001691 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001692 }
1693
George Hodulik145b3a52019-03-27 11:18:43 -07001694 @Nullable
1695 private AppPredictor getAppPredictor() {
1696 if (mAppPredictor == null
1697 && getPackageManager().getAppPredictionServicePackageName() != null) {
1698 final IntentFilter filter = getTargetIntentFilter();
1699 Bundle extras = new Bundle();
1700 extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
1701 AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(this)
1702 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
1703 .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
1704 .setExtras(extras)
1705 .build();
1706 AppPredictionManager appPredictionManager
1707 = getSystemService(AppPredictionManager.class);
1708 mAppPredictor = appPredictionManager.createAppPredictionSession(appPredictionContext);
1709 }
1710 return mAppPredictor;
1711 }
1712
1713 /**
1714 * This will return an app predictor if it is enabled for direct share sorting
1715 * and if one exists. Otherwise, it returns null.
1716 */
1717 @Nullable
1718 private AppPredictor getAppPredictorForDirectShareIfEnabled() {
Matt Pietal030bd842019-05-29 07:14:14 -04001719 return USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS && !ActivityManager.isLowRamDeviceStatic()
1720 ? getAppPredictor() : null;
George Hodulik145b3a52019-03-27 11:18:43 -07001721 }
1722
George Hodulikc681ce42019-04-12 17:10:31 -07001723 /**
1724 * This will return an app predictor if it is enabled for share activity sorting
1725 * and if one exists. Otherwise, it returns null.
1726 */
1727 @Nullable
1728 private AppPredictor getAppPredictorForShareActivitesIfEnabled() {
1729 return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? getAppPredictor() : null;
1730 }
1731
Adam Powell2ed547e2015-04-29 18:45:04 -07001732 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
1733 if (mRefinementResultReceiver != null) {
1734 mRefinementResultReceiver.destroy();
1735 mRefinementResultReceiver = null;
1736 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001737 if (selectedTarget == null) {
1738 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
1739 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
1740 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
1741 + " cannot match refined source intent " + matchingIntent);
Kang Li53b43142016-11-14 14:38:25 -08001742 } else {
1743 TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
1744 if (super.onTargetSelected(clonedTarget, false)) {
Kang Li9fa2a2c2017-01-06 13:33:24 -08001745 updateModelAndChooserCounts(clonedTarget);
Kang Li53b43142016-11-14 14:38:25 -08001746 finish();
1747 return;
1748 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001749 }
1750 onRefinementCanceled();
1751 }
1752
1753 void onRefinementCanceled() {
1754 if (mRefinementResultReceiver != null) {
1755 mRefinementResultReceiver.destroy();
1756 mRefinementResultReceiver = null;
1757 }
1758 finish();
1759 }
1760
1761 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
1762 final List<Intent> targetIntents = target.getAllSourceIntents();
1763 for (int i = 0, N = targetIntents.size(); i < N; i++) {
1764 final Intent targetIntent = targetIntents.get(i);
1765 if (targetIntent.filterEquals(matchingIntent)) {
1766 return true;
1767 }
1768 }
1769 return false;
1770 }
1771
Adam Powell666d82a2015-07-15 20:14:57 -07001772 void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
1773 if (targets == null) {
1774 return;
1775 }
1776
1777 final PackageManager pm = getPackageManager();
1778 for (int i = targets.size() - 1; i >= 0; i--) {
1779 final ChooserTarget target = targets.get(i);
1780 final ComponentName targetName = target.getComponentName();
1781 if (packageName != null && packageName.equals(targetName.getPackageName())) {
1782 // Anything from the original target's package is fine.
1783 continue;
1784 }
1785
1786 boolean remove;
1787 try {
1788 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
1789 remove = !ai.exported || ai.permission != null;
1790 } catch (NameNotFoundException e) {
1791 Log.e(TAG, "Target " + target + " returned by " + packageName
1792 + " component not found");
1793 remove = true;
1794 }
1795
1796 if (remove) {
1797 targets.remove(i);
1798 }
1799 }
1800 }
1801
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04001802 private void updateAlphabeticalList() {
Alison Cichowlas13314612019-04-11 15:20:39 -04001803 mSortedList.clear();
1804 mSortedList.addAll(getDisplayList());
1805 Collections.sort(mSortedList, new AzInfoComparator(ChooserActivity.this));
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04001806 }
1807
1808 /**
1809 * Sort intents alphabetically based on display label.
1810 */
1811 class AzInfoComparator implements Comparator<ResolverActivity.DisplayResolveInfo> {
1812 Collator mCollator;
1813 AzInfoComparator(Context context) {
1814 mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
1815 }
1816
1817 @Override
1818 public int compare(ResolverActivity.DisplayResolveInfo lhsp,
1819 ResolverActivity.DisplayResolveInfo rhsp) {
1820 return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
1821 }
1822 }
1823
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -05001824 protected MetricsLogger getMetricsLogger() {
1825 if (mMetricsLogger == null) {
1826 mMetricsLogger = new MetricsLogger();
1827 }
1828 return mMetricsLogger;
1829 }
1830
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001831 public class ChooserListController extends ResolverListController {
1832 public ChooserListController(Context context,
1833 PackageManager pm,
1834 Intent targetIntent,
1835 String referrerPackageName,
George Hodulikc681ce42019-04-12 17:10:31 -07001836 int launchedFromUid,
1837 AbstractResolverComparator resolverComparator) {
1838 super(context, pm, targetIntent, referrerPackageName, launchedFromUid,
1839 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001840 }
1841
1842 @Override
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001843 boolean isComponentFiltered(ComponentName name) {
1844 if (mFilteredComponentNames == null) {
1845 return false;
1846 }
1847 for (ComponentName filteredComponentName : mFilteredComponentNames) {
1848 if (name.equals(filteredComponentName)) {
1849 return true;
1850 }
1851 }
1852 return false;
1853 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001854 }
1855
Adam Powell24428412015-04-01 17:19:56 -07001856 @Override
Adam Powell23882512016-01-29 10:21:00 -08001857 public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
Adam Powell7d758002015-05-06 17:49:36 -07001858 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
1859 boolean filterLastUsed) {
1860 final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001861 initialIntents, rList, launchedFromUid, filterLastUsed, createListController());
Adam Powell24428412015-04-01 17:19:56 -07001862 return adapter;
1863 }
1864
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001865 @VisibleForTesting
1866 protected ResolverListController createListController() {
George Hodulikc681ce42019-04-12 17:10:31 -07001867 AppPredictor appPredictor = getAppPredictorForShareActivitesIfEnabled();
1868 AbstractResolverComparator resolverComparator;
1869 if (appPredictor != null) {
1870 resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
George Hodulik3f399f22019-04-26 16:17:54 -07001871 getReferrerPackageName(), appPredictor, getUser());
George Hodulikc681ce42019-04-12 17:10:31 -07001872 } else {
1873 resolverComparator =
1874 new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
1875 getReferrerPackageName(), null);
1876 }
1877
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001878 return new ChooserListController(
1879 this,
1880 mPm,
1881 getTargetIntent(),
1882 getReferrerPackageName(),
George Hodulikc681ce42019-04-12 17:10:31 -07001883 mLaunchedFromUid,
1884 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001885 }
1886
Matt Pietal26038402019-01-08 07:29:34 -05001887 @VisibleForTesting
1888 protected Bitmap loadThumbnail(Uri uri, Size size) {
1889 if (uri == null || size == null) {
1890 return null;
1891 }
1892
1893 try {
Matt Pietal46d828c2019-02-05 08:07:07 -05001894 return ImageUtils.loadThumbnail(getContentResolver(), uri, size);
1895 } catch (IOException | NullPointerException | SecurityException ex) {
Matt Pietal62532e52019-05-07 09:51:37 -04001896 logContentPreviewWarning(uri);
Matt Pietal26038402019-01-08 07:29:34 -05001897 }
1898 return null;
1899 }
1900
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001901 interface ChooserTargetInfo extends TargetInfo {
1902 float getModifiedScore();
1903
1904 ChooserTarget getChooserTarget();
Matt Pietal9d501432019-04-12 10:05:29 -04001905
1906 /**
1907 * Do not label as 'equals', since this doesn't quite work
1908 * as intended with java 8.
1909 */
1910 default boolean isSimilar(ChooserTargetInfo other) {
1911 if (other == null) return false;
1912
1913 ChooserTarget ct1 = getChooserTarget();
1914 ChooserTarget ct2 = other.getChooserTarget();
1915
1916 // If either is null, there is not enough info to make an informed decision
1917 // about equality, so just exit
1918 if (ct1 == null || ct2 == null) return false;
1919
1920 if (ct1.getComponentName().equals(ct2.getComponentName())
1921 && TextUtils.equals(getDisplayLabel(), other.getDisplayLabel())
1922 && TextUtils.equals(getExtendedInfo(), other.getExtendedInfo())) {
1923 return true;
1924 }
1925
1926 return false;
1927 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001928 }
1929
1930 /**
1931 * Distinguish between targets that selectable by the user, vs those that are
1932 * placeholders for the system while information is loading in an async manner.
1933 */
1934 abstract class NotSelectableTargetInfo implements ChooserTargetInfo {
1935
1936 public Intent getResolvedIntent() {
1937 return null;
1938 }
1939
1940 public ComponentName getResolvedComponentName() {
1941 return null;
1942 }
1943
1944 public boolean start(Activity activity, Bundle options) {
1945 return false;
1946 }
1947
1948 public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
1949 return false;
1950 }
1951
1952 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
1953 return false;
1954 }
1955
1956 public ResolveInfo getResolveInfo() {
1957 return null;
1958 }
1959
1960 public CharSequence getDisplayLabel() {
1961 return null;
1962 }
1963
1964 public CharSequence getExtendedInfo() {
1965 return null;
1966 }
1967
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001968 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
1969 return null;
1970 }
1971
1972 public List<Intent> getAllSourceIntents() {
1973 return null;
1974 }
1975
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001976 public float getModifiedScore() {
Matt Pietalfbfa0492019-04-01 11:29:56 -04001977 return -0.1f;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001978 }
1979
1980 public ChooserTarget getChooserTarget() {
1981 return null;
1982 }
Matt Pietala4b30072019-04-04 13:44:36 -04001983
1984 public boolean isSuspended() {
1985 return false;
1986 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001987 }
1988
1989 final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
1990 public Drawable getDisplayIcon() {
Mike Digmanac1d88c2019-04-18 15:15:55 -07001991 AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
1992 getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
1993 avd.start(); // Start animation after generation
1994 return avd;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001995 }
1996 }
1997
1998
1999 final class EmptyTargetInfo extends NotSelectableTargetInfo {
2000 public Drawable getDisplayIcon() {
2001 return null;
2002 }
2003 }
2004
2005 final class SelectableTargetInfo implements ChooserTargetInfo {
Adam Powell2ed547e2015-04-29 18:45:04 -07002006 private final DisplayResolveInfo mSourceInfo;
Adam Powell0ccc0e92015-04-23 17:19:37 -07002007 private final ResolveInfo mBackupResolveInfo;
Adam Powell24428412015-04-01 17:19:56 -07002008 private final ChooserTarget mChooserTarget;
Matt Pietal9d501432019-04-12 10:05:29 -04002009 private final String mDisplayLabel;
Adam Powell7d758002015-05-06 17:49:36 -07002010 private Drawable mBadgeIcon = null;
Alan Viverettece5d92c2015-07-31 16:46:56 -04002011 private CharSequence mBadgeContentDescription;
Adam Powell13036be2015-05-12 14:43:56 -07002012 private Drawable mDisplayIcon;
Adam Powell2ed547e2015-04-29 18:45:04 -07002013 private final Intent mFillInIntent;
2014 private final int mFillInFlags;
Adam Powella182e452015-07-06 16:57:56 -07002015 private final float mModifiedScore;
Matt Pietalab986b52019-04-10 10:14:32 -04002016 private boolean mIsSuspended = false;
Adam Powell24428412015-04-01 17:19:56 -07002017
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002018 SelectableTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
Adam Powella182e452015-07-06 16:57:56 -07002019 float modifiedScore) {
Adam Powell24428412015-04-01 17:19:56 -07002020 mSourceInfo = sourceInfo;
2021 mChooserTarget = chooserTarget;
Adam Powella182e452015-07-06 16:57:56 -07002022 mModifiedScore = modifiedScore;
Adam Powell7d758002015-05-06 17:49:36 -07002023 if (sourceInfo != null) {
2024 final ResolveInfo ri = sourceInfo.getResolveInfo();
2025 if (ri != null) {
2026 final ActivityInfo ai = ri.activityInfo;
2027 if (ai != null && ai.applicationInfo != null) {
Alan Viverettece5d92c2015-07-31 16:46:56 -04002028 final PackageManager pm = getPackageManager();
2029 mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
2030 mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
Matt Pietalab986b52019-04-10 10:14:32 -04002031 mIsSuspended =
2032 (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
Adam Powell7d758002015-05-06 17:49:36 -07002033 }
2034 }
2035 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002036 // TODO(b/121287224): do this in the background thread, and only for selected targets
2037 mDisplayIcon = getChooserTargetIconDrawable(chooserTarget);
Adam Powell0ccc0e92015-04-23 17:19:37 -07002038
2039 if (sourceInfo != null) {
2040 mBackupResolveInfo = null;
2041 } else {
2042 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
2043 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002044
2045 mFillInIntent = null;
2046 mFillInFlags = 0;
Matt Pietal9d501432019-04-12 10:05:29 -04002047
2048 mDisplayLabel = sanitizeDisplayLabel(chooserTarget.getTitle());
Adam Powell2ed547e2015-04-29 18:45:04 -07002049 }
2050
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002051 private SelectableTargetInfo(SelectableTargetInfo other, Intent fillInIntent, int flags) {
Adam Powell2ed547e2015-04-29 18:45:04 -07002052 mSourceInfo = other.mSourceInfo;
2053 mBackupResolveInfo = other.mBackupResolveInfo;
2054 mChooserTarget = other.mChooserTarget;
Adam Powell7d758002015-05-06 17:49:36 -07002055 mBadgeIcon = other.mBadgeIcon;
Alan Viverettece5d92c2015-07-31 16:46:56 -04002056 mBadgeContentDescription = other.mBadgeContentDescription;
Adam Powell2ed547e2015-04-29 18:45:04 -07002057 mDisplayIcon = other.mDisplayIcon;
2058 mFillInIntent = fillInIntent;
2059 mFillInFlags = flags;
Adam Powella182e452015-07-06 16:57:56 -07002060 mModifiedScore = other.mModifiedScore;
Matt Pietal9d501432019-04-12 10:05:29 -04002061
2062 mDisplayLabel = sanitizeDisplayLabel(mChooserTarget.getTitle());
2063 }
2064
2065 private String sanitizeDisplayLabel(CharSequence label) {
2066 SpannableStringBuilder sb = new SpannableStringBuilder(label);
2067 sb.clearSpans();
2068 return sb.toString();
Adam Powella182e452015-07-06 16:57:56 -07002069 }
2070
Matt Pietala4b30072019-04-04 13:44:36 -04002071 public boolean isSuspended() {
2072 return mIsSuspended;
2073 }
2074
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002075 /**
2076 * Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
2077 * the call to LauncherApps#getShortcuts(ShortcutQuery).
2078 */
2079 // TODO(121287224): Refactor code to apply the suggestion above
2080 private Drawable getChooserTargetIconDrawable(ChooserTarget target) {
Mike Digman9c4ae502019-03-19 17:02:25 -07002081 Drawable directShareIcon = null;
2082
2083 // First get the target drawable and associated activity info
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002084 final Icon icon = target.getIcon();
2085 if (icon != null) {
Mike Digman9c4ae502019-03-19 17:02:25 -07002086 directShareIcon = icon.loadDrawable(ChooserActivity.this);
2087 } else if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
2088 Bundle extras = target.getIntentExtras();
2089 if (extras != null && extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
2090 CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
2091 LauncherApps launcherApps = (LauncherApps) getSystemService(
2092 Context.LAUNCHER_APPS_SERVICE);
2093 final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
2094 q.setPackage(target.getComponentName().getPackageName());
2095 q.setShortcutIds(Arrays.asList(shortcutId.toString()));
2096 q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
2097 final List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(q, getUser());
2098 if (shortcuts != null && shortcuts.size() > 0) {
2099 directShareIcon = launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
2100 }
2101 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002102 }
2103
Mike Digman9c4ae502019-03-19 17:02:25 -07002104 if (directShareIcon == null) return null;
2105
2106 ActivityInfo info = null;
2107 try {
2108 info = mPm.getActivityInfo(target.getComponentName(), 0);
2109 } catch (NameNotFoundException error) {
2110 Log.e(TAG, "Could not find activity associated with ChooserTarget");
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002111 }
2112
Mike Digman9c4ae502019-03-19 17:02:25 -07002113 if (info == null) return null;
2114
2115 // Now fetch app icon and raster with no badging even in work profile
Mike Digmanb2e5e712019-04-19 15:49:10 -07002116 Bitmap appIcon = makePresentationGetter(info).getIconBitmap(
2117 UserHandle.getUserHandleForUid(UserHandle.myUserId()));
Mike Digman9c4ae502019-03-19 17:02:25 -07002118
2119 // Raster target drawable with appIcon as a badge
2120 SimpleIconFactory sif = SimpleIconFactory.obtain(ChooserActivity.this);
2121 Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
2122 sif.recycle();
2123
2124 return new BitmapDrawable(getResources(), directShareBadgedIcon);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002125 }
2126
Adam Powella182e452015-07-06 16:57:56 -07002127 public float getModifiedScore() {
2128 return mModifiedScore;
Adam Powell24428412015-04-01 17:19:56 -07002129 }
2130
2131 @Override
2132 public Intent getResolvedIntent() {
Adam Powell7d758002015-05-06 17:49:36 -07002133 if (mSourceInfo != null) {
Adam Powell0ccc0e92015-04-23 17:19:37 -07002134 return mSourceInfo.getResolvedIntent();
2135 }
Adam Powell52c39212016-04-07 15:14:18 -07002136
2137 final Intent targetIntent = new Intent(getTargetIntent());
2138 targetIntent.setComponent(mChooserTarget.getComponentName());
2139 targetIntent.putExtras(mChooserTarget.getIntentExtras());
2140 return targetIntent;
Adam Powell24428412015-04-01 17:19:56 -07002141 }
2142
2143 @Override
2144 public ComponentName getResolvedComponentName() {
Adam Powell0ccc0e92015-04-23 17:19:37 -07002145 if (mSourceInfo != null) {
2146 return mSourceInfo.getResolvedComponentName();
2147 } else if (mBackupResolveInfo != null) {
2148 return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
2149 mBackupResolveInfo.activityInfo.name);
2150 }
2151 return null;
2152 }
2153
Adam Powell666d82a2015-07-15 20:14:57 -07002154 private Intent getBaseIntentToSend() {
Adam Powell52c39212016-04-07 15:14:18 -07002155 Intent result = getResolvedIntent();
Adam Powell2ed547e2015-04-29 18:45:04 -07002156 if (result == null) {
Adam Powell666d82a2015-07-15 20:14:57 -07002157 Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
Adam Powell13036be2015-05-12 14:43:56 -07002158 } else {
Adam Powell2ed547e2015-04-29 18:45:04 -07002159 result = new Intent(result);
Adam Powell13036be2015-05-12 14:43:56 -07002160 if (mFillInIntent != null) {
2161 result.fillIn(mFillInIntent, mFillInFlags);
2162 }
2163 result.fillIn(mReferrerFillInIntent, 0);
Adam Powell2ed547e2015-04-29 18:45:04 -07002164 }
2165 return result;
Adam Powell24428412015-04-01 17:19:56 -07002166 }
2167
2168 @Override
2169 public boolean start(Activity activity, Bundle options) {
Adam Powell666d82a2015-07-15 20:14:57 -07002170 throw new RuntimeException("ChooserTargets should be started as caller.");
Adam Powell24428412015-04-01 17:19:56 -07002171 }
2172
2173 @Override
Alison Cichowlas3e340502018-08-07 17:15:01 -04002174 public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
Adam Powell666d82a2015-07-15 20:14:57 -07002175 final Intent intent = getBaseIntentToSend();
Adam Powell2ed547e2015-04-29 18:45:04 -07002176 if (intent == null) {
2177 return false;
2178 }
Adam Powell666d82a2015-07-15 20:14:57 -07002179 intent.setComponent(mChooserTarget.getComponentName());
Makoto Onuki99302b52017-03-29 12:42:26 -07002180 intent.putExtras(mChooserTarget.getIntentExtras());
Adam Powell52c39212016-04-07 15:14:18 -07002181
2182 // Important: we will ignore the target security checks in ActivityManager
2183 // if and only if the ChooserTarget's target package is the same package
2184 // where we got the ChooserTargetService that provided it. This lets a
2185 // ChooserTargetService provide a non-exported or permission-guarded target
2186 // to the chooser for the user to pick.
2187 //
2188 // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
2189 // so we'll obey the caller's normal security checks.
2190 final boolean ignoreTargetSecurity = mSourceInfo != null
2191 && mSourceInfo.getResolvedComponentName().getPackageName()
2192 .equals(mChooserTarget.getComponentName().getPackageName());
Alison Cichowlas3e340502018-08-07 17:15:01 -04002193 return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
Adam Powell24428412015-04-01 17:19:56 -07002194 }
2195
2196 @Override
2197 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
Adam Powell666d82a2015-07-15 20:14:57 -07002198 throw new RuntimeException("ChooserTargets should be started as caller.");
Adam Powell24428412015-04-01 17:19:56 -07002199 }
2200
2201 @Override
2202 public ResolveInfo getResolveInfo() {
Adam Powell0ccc0e92015-04-23 17:19:37 -07002203 return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
Adam Powell24428412015-04-01 17:19:56 -07002204 }
2205
2206 @Override
2207 public CharSequence getDisplayLabel() {
Matt Pietal9d501432019-04-12 10:05:29 -04002208 return mDisplayLabel;
Adam Powell24428412015-04-01 17:19:56 -07002209 }
2210
2211 @Override
2212 public CharSequence getExtendedInfo() {
Adam Powell00f4aad2015-09-17 13:38:16 -07002213 // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
2214 return null;
Adam Powell24428412015-04-01 17:19:56 -07002215 }
2216
2217 @Override
2218 public Drawable getDisplayIcon() {
2219 return mDisplayIcon;
2220 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002221
George Hodulikf2b0d342019-01-25 12:43:54 -08002222 public ChooserTarget getChooserTarget() {
2223 return mChooserTarget;
2224 }
2225
Alan Viverettece5d92c2015-07-31 16:46:56 -04002226 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07002227 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002228 return new SelectableTargetInfo(this, fillInIntent, flags);
Adam Powell2ed547e2015-04-29 18:45:04 -07002229 }
2230
2231 @Override
2232 public List<Intent> getAllSourceIntents() {
2233 final List<Intent> results = new ArrayList<>();
2234 if (mSourceInfo != null) {
2235 // We only queried the service for the first one in our sourceinfo.
2236 results.add(mSourceInfo.getAllSourceIntents().get(0));
2237 }
2238 return results;
2239 }
Adam Powell24428412015-04-01 17:19:56 -07002240 }
2241
Matt Pietal5b648562019-03-12 07:40:26 -04002242 private void handleScroll(View view, int x, int y, int oldx, int oldy) {
2243 if (mChooserRowAdapter != null) {
2244 mChooserRowAdapter.handleScroll(view, y, oldy);
2245 }
2246 }
2247
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002248 /*
2249 * Need to dynamically adjust how many icons can fit per row before we add them,
2250 * which also means setting the correct offset to initially show the content
2251 * preview area + 2 rows of targets
2252 */
2253 private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
2254 int oldTop, int oldRight, int oldBottom) {
2255 if (mChooserRowAdapter == null || mAdapterView == null) {
2256 return;
2257 }
2258
Matt Pietalab73a882019-06-05 07:04:55 -04002259 final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
Matt Pietale7cacab2019-05-23 07:21:36 -04002260 if (mChooserRowAdapter.consumeLayoutRequest()
2261 || mChooserRowAdapter.calculateChooserTargetWidth(availableWidth)
Matt Pietalab73a882019-06-05 07:04:55 -04002262 || mAdapterView.getAdapter() == null
2263 || availableWidth != mCurrAvailableWidth) {
2264 mCurrAvailableWidth = availableWidth;
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002265 mAdapterView.setAdapter(mChooserRowAdapter);
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002266
2267 getMainThreadHandler().post(() -> {
2268 if (mResolverDrawerLayout == null || mChooserRowAdapter == null) {
2269 return;
2270 }
2271
Matt Pietal800136a2019-05-08 07:46:39 -04002272 final int bottomInset = mSystemWindowInsets != null
2273 ? mSystemWindowInsets.bottom : 0;
2274 int offset = bottomInset;
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002275 int rowsToShow = mChooserRowAdapter.getContentPreviewRowCount()
Matt Pietal74c6ed02019-04-18 13:38:46 -04002276 + mChooserRowAdapter.getProfileRowCount()
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002277 + mChooserRowAdapter.getServiceTargetRowCount()
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002278 + mChooserRowAdapter.getCallerAndRankedTargetRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002279
2280 // then this is most likely not a SEND_* action, so check
2281 // the app target count
2282 if (rowsToShow == 0) {
2283 rowsToShow = mChooserRowAdapter.getCount();
2284 }
2285
2286 // still zero? then use a default height and leave, which
2287 // can happen when there are no targets to show
2288 if (rowsToShow == 0) {
Matt Pietal800136a2019-05-08 07:46:39 -04002289 offset += getResources().getDimensionPixelSize(
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002290 R.dimen.chooser_max_collapsed_height);
2291 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2292 return;
2293 }
2294
Matt Pietal394ebd02019-05-03 07:36:21 -04002295 int directShareHeight = 0;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002296 rowsToShow = Math.min(4, rowsToShow);
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002297 for (int i = 0; i < Math.min(rowsToShow, mAdapterView.getChildCount()); i++) {
Matt Pietal394ebd02019-05-03 07:36:21 -04002298 View child = mAdapterView.getChildAt(i);
2299 int height = child.getHeight();
2300 offset += height;
2301
2302 if (child.getTag() != null
2303 && (child.getTag() instanceof DirectShareViewHolder)) {
2304 directShareHeight = height;
2305 }
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002306 }
2307
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002308 boolean isExpandable = getResources().getConfiguration().orientation
2309 == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
2310 if (directShareHeight != 0 && isSendAction(getTargetIntent()) && isExpandable) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002311 // make sure to leave room for direct share 4->8 expansion
Matt Pietal394ebd02019-05-03 07:36:21 -04002312 int requiredExpansionHeight =
2313 (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal800136a2019-05-08 07:46:39 -04002314 int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
Matt Pietal394ebd02019-05-03 07:36:21 -04002315 int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
Matt Pietal800136a2019-05-08 07:46:39 -04002316 - requiredExpansionHeight - topInset - bottomInset;
Matt Pietal394ebd02019-05-03 07:36:21 -04002317
2318 offset = Math.min(offset, minHeight);
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002319 }
2320
Matt Pietal399e8c72019-04-04 15:49:48 -04002321 mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top));
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002322 });
2323 }
2324 }
2325
Adam Powell24428412015-04-01 17:19:56 -07002326 public class ChooserListAdapter extends ResolveListAdapter {
Adam Powell7d758002015-05-06 17:49:36 -07002327 public static final int TARGET_BAD = -1;
2328 public static final int TARGET_CALLER = 0;
2329 public static final int TARGET_SERVICE = 1;
2330 public static final int TARGET_STANDARD = 2;
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002331 public static final int TARGET_STANDARD_AZ = 3;
Adam Powell7d758002015-05-06 17:49:36 -07002332
Matt Pietal5b648562019-03-12 07:40:26 -04002333 private static final int MAX_SUGGESTED_APP_TARGETS = 4;
Matt Pietal791b1c32019-04-30 13:36:49 -04002334 private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
Adam Powella182e452015-07-06 16:57:56 -07002335
Matt Pietal5b648562019-03-12 07:40:26 -04002336 private static final int MAX_SERVICE_TARGETS = 8;
2337
Matt Pietal3ed20a72019-06-24 12:14:52 -04002338 private final int mMaxShortcutTargetsPerApp =
2339 getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp);
2340
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002341 private int mNumShortcutResults = 0;
2342
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002343 // Reserve spots for incoming direct share targets by adding placeholders
2344 private ChooserTargetInfo mPlaceHolderTargetInfo = new PlaceHolderTargetInfo();
Matt Pietal5b648562019-03-12 07:40:26 -04002345 private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
Adam Powell7d758002015-05-06 17:49:36 -07002346 private final List<TargetInfo> mCallerTargets = new ArrayList<>();
Dan Sandlerf5e17692018-06-04 22:13:40 -04002347
Adam Powella182e452015-07-06 16:57:56 -07002348 private final BaseChooserTargetComparator mBaseTargetComparator
2349 = new BaseChooserTargetComparator();
2350
Adam Powell7d758002015-05-06 17:49:36 -07002351 public ChooserListAdapter(Context context, List<Intent> payloadIntents,
2352 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002353 boolean filterLastUsed, ResolverListController resolverListController) {
Adam Powell7d758002015-05-06 17:49:36 -07002354 // Don't send the initial intents through the shared ResolverActivity path,
2355 // we want to separate them into a different section.
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002356 super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed,
2357 resolverListController);
Adam Powell0ccc0e92015-04-23 17:19:37 -07002358
Matt Pietal5b648562019-03-12 07:40:26 -04002359 createPlaceHolders();
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002360
Adam Powell7d758002015-05-06 17:49:36 -07002361 if (initialIntents != null) {
2362 final PackageManager pm = getPackageManager();
2363 for (int i = 0; i < initialIntents.length; i++) {
2364 final Intent ii = initialIntents[i];
2365 if (ii == null) {
2366 continue;
2367 }
Adam Powell86100d12016-05-12 16:13:17 -07002368
2369 // We reimplement Intent#resolveActivityInfo here because if we have an
2370 // implicit intent, we want the ResolveInfo returned by PackageManager
2371 // instead of one we reconstruct ourselves. The ResolveInfo returned might
2372 // have extra metadata and resolvePackageName set and we want to respect that.
2373 ResolveInfo ri = null;
2374 ActivityInfo ai = null;
2375 final ComponentName cn = ii.getComponent();
2376 if (cn != null) {
2377 try {
2378 ai = pm.getActivityInfo(ii.getComponent(), 0);
2379 ri = new ResolveInfo();
2380 ri.activityInfo = ai;
2381 } catch (PackageManager.NameNotFoundException ignored) {
2382 // ai will == null below
2383 }
2384 }
2385 if (ai == null) {
2386 ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
2387 ai = ri != null ? ri.activityInfo : null;
2388 }
Adam Powell7d758002015-05-06 17:49:36 -07002389 if (ai == null) {
2390 Log.w(TAG, "No activity found for " + ii);
2391 continue;
2392 }
Adam Powell7d758002015-05-06 17:49:36 -07002393 UserManager userManager =
2394 (UserManager) getSystemService(Context.USER_SERVICE);
Adam Powell7d758002015-05-06 17:49:36 -07002395 if (ii instanceof LabeledIntent) {
Matt Pietal26038402019-01-08 07:29:34 -05002396 LabeledIntent li = (LabeledIntent) ii;
Adam Powell7d758002015-05-06 17:49:36 -07002397 ri.resolvePackageName = li.getSourcePackage();
2398 ri.labelRes = li.getLabelResource();
2399 ri.nonLocalizedLabel = li.getNonLocalizedLabel();
2400 ri.icon = li.getIconResource();
Sudheer Shanka9ded7602015-05-19 21:17:25 +01002401 ri.iconResourceId = ri.icon;
2402 }
2403 if (userManager.isManagedProfile()) {
2404 ri.noResourceId = true;
2405 ri.icon = 0;
Adam Powell7d758002015-05-06 17:49:36 -07002406 }
Mike Digmanba232682019-03-27 14:55:26 -07002407 ResolveInfoPresentationGetter getter = makePresentationGetter(ri);
Adam Powell7d758002015-05-06 17:49:36 -07002408 mCallerTargets.add(new DisplayResolveInfo(ii, ri,
Mike Digmanba232682019-03-27 14:55:26 -07002409 getter.getLabel(), getter.getSubLabel(), ii));
Adam Powelld974c7b2015-04-28 15:41:46 -07002410 }
Adam Powell0ccc0e92015-04-23 17:19:37 -07002411 }
Adam Powell24428412015-04-01 17:19:56 -07002412 }
2413
Matt Pietalaf044ae2019-03-29 06:53:53 -04002414 @Override
Matt Pietalab73a882019-06-05 07:04:55 -04002415 public void handlePackagesChanged() {
2416 if (DEBUG) {
2417 Log.d(TAG, "clearing queryTargets on package change");
2418 }
2419 createPlaceHolders();
2420 mServicesRequested.clear();
2421 notifyDataSetChanged();
2422
2423 super.handlePackagesChanged();
2424 }
2425
2426 @Override
Matt Pietalaf044ae2019-03-29 06:53:53 -04002427 public void notifyDataSetChanged() {
2428 if (!mListViewDataChanged) {
Matt Pietalab73a882019-06-05 07:04:55 -04002429 mChooserHandler.sendEmptyMessageDelayed(ChooserHandler.LIST_VIEW_UPDATE_MESSAGE,
Matt Pietalaf044ae2019-03-29 06:53:53 -04002430 LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
2431 mListViewDataChanged = true;
2432 }
2433 }
2434
2435 private void refreshListView() {
2436 if (mListViewDataChanged) {
2437 super.notifyDataSetChanged();
2438 }
2439 mListViewDataChanged = false;
2440 }
2441
2442
Matt Pietal5b648562019-03-12 07:40:26 -04002443 private void createPlaceHolders() {
Matt Pietalab73a882019-06-05 07:04:55 -04002444 mNumShortcutResults = 0;
Matt Pietal5b648562019-03-12 07:40:26 -04002445 mServiceTargets.clear();
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002446 for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
Matt Pietal5b648562019-03-12 07:40:26 -04002447 mServiceTargets.add(mPlaceHolderTargetInfo);
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002448 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002449 }
2450
Adam Powell24428412015-04-01 17:19:56 -07002451 @Override
Adam Powell7d758002015-05-06 17:49:36 -07002452 public View onCreateView(ViewGroup parent) {
Adam Powell24428412015-04-01 17:19:56 -07002453 return mInflater.inflate(
2454 com.android.internal.R.layout.resolve_grid_item, parent, false);
2455 }
2456
2457 @Override
Mike Digmanbd7e0df2019-04-23 14:52:45 -07002458 protected void onBindView(View view, TargetInfo info) {
2459 super.onBindView(view, info);
2460
Mike Digman4b83c212019-05-03 10:17:35 -07002461 // If target is loading, show a special placeholder shape in the label, make unclickable
Mike Digmanbd7e0df2019-04-23 14:52:45 -07002462 final ViewHolder holder = (ViewHolder) view.getTag();
2463 if (info instanceof PlaceHolderTargetInfo) {
2464 final int maxWidth = getResources().getDimensionPixelSize(
2465 R.dimen.chooser_direct_share_label_placeholder_max_width);
2466 holder.text.setMaxWidth(maxWidth);
2467 holder.text.setBackground(getResources().getDrawable(
2468 R.drawable.chooser_direct_share_label_placeholder, getTheme()));
Mike Digman4b83c212019-05-03 10:17:35 -07002469 // Prevent rippling by removing background containing ripple
2470 holder.itemView.setBackground(null);
Mike Digmanbd7e0df2019-04-23 14:52:45 -07002471 } else {
2472 holder.text.setMaxWidth(Integer.MAX_VALUE);
2473 holder.text.setBackground(null);
Mike Digman4b83c212019-05-03 10:17:35 -07002474 holder.itemView.setBackground(holder.defaultItemViewBackground);
Mike Digmanbd7e0df2019-04-23 14:52:45 -07002475 }
2476 }
2477
2478 @Override
Adam Powell24428412015-04-01 17:19:56 -07002479 public void onListRebuilt() {
Matt Pietal030bd842019-05-29 07:14:14 -04002480 updateAlphabeticalList();
2481
Ng Zhi And3ec5fc2018-05-10 09:13:00 -07002482 // don't support direct share on low ram devices
2483 if (ActivityManager.isLowRamDeviceStatic()) {
2484 return;
2485 }
2486
George Hodulik145b3a52019-03-27 11:18:43 -07002487 if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
2488 || USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002489 if (DEBUG) {
2490 Log.d(TAG, "querying direct share targets from ShortcutManager");
2491 }
Matt Pietalaf044ae2019-03-29 06:53:53 -04002492
George Hodulik3f399f22019-04-26 16:17:54 -07002493 queryDirectShareTargets(this, false);
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -08002494 }
2495 if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
2496 if (DEBUG) {
2497 Log.d(TAG, "List built querying services");
2498 }
Matt Pietalaf044ae2019-03-29 06:53:53 -04002499
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002500 queryTargetServices(this);
2501 }
Adam Powell24428412015-04-01 17:19:56 -07002502 }
2503
2504 @Override
Adam Powellc6d5e3a2015-04-23 12:22:20 -07002505 public boolean shouldGetResolvedFilter() {
2506 return true;
2507 }
2508
2509 @Override
Adam Powell24428412015-04-01 17:19:56 -07002510 public int getCount() {
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002511 return getRankedTargetCount() + getAlphaTargetCount()
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002512 + getSelectableServiceTargetCount() + getCallerTargetCount();
Adam Powell24428412015-04-01 17:19:56 -07002513 }
2514
Adam Powell50077352015-05-26 18:01:55 -07002515 @Override
2516 public int getUnfilteredCount() {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002517 int appTargets = super.getUnfilteredCount();
Alison Cichowlas13314612019-04-11 15:20:39 -04002518 if (appTargets > getMaxRankedTargets()) {
2519 appTargets = appTargets + getMaxRankedTargets();
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002520 }
2521 return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
Adam Powell50077352015-05-26 18:01:55 -07002522 }
2523
Alison Cichowlas13314612019-04-11 15:20:39 -04002524
Adam Powell50077352015-05-26 18:01:55 -07002525 public int getCallerTargetCount() {
Matt Pietal5b648562019-03-12 07:40:26 -04002526 return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS);
Adam Powell7d758002015-05-06 17:49:36 -07002527 }
2528
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002529 /**
2530 * Filter out placeholders and non-selectable service targets
2531 */
2532 public int getSelectableServiceTargetCount() {
2533 int count = 0;
2534 for (ChooserTargetInfo info : mServiceTargets) {
2535 if (info instanceof SelectableTargetInfo) {
2536 count++;
2537 }
Adam Powell565943f2016-02-11 10:29:05 -08002538 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002539 return count;
2540 }
2541
2542 public int getServiceTargetCount() {
Matt Pietal6e88b512019-06-10 10:20:15 -04002543 if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
Matt Pietal95574b02019-03-13 08:12:25 -04002544 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
2545 }
2546
2547 return 0;
Adam Powell7d758002015-05-06 17:49:36 -07002548 }
2549
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002550 int getAlphaTargetCount() {
2551 int standardCount = super.getCount();
Alison Cichowlas13314612019-04-11 15:20:39 -04002552 return standardCount > getMaxRankedTargets() ? standardCount : 0;
Adam Powell7d758002015-05-06 17:49:36 -07002553 }
2554
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002555 int getRankedTargetCount() {
Alison Cichowlas13314612019-04-11 15:20:39 -04002556 int spacesAvailable = getMaxRankedTargets() - getCallerTargetCount();
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002557 return Math.min(spacesAvailable, super.getCount());
2558 }
2559
Alison Cichowlas13314612019-04-11 15:20:39 -04002560 private int getMaxRankedTargets() {
2561 return mChooserRowAdapter == null ? 4 : mChooserRowAdapter.getMaxTargetsPerRow();
2562 }
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002563
Adam Powell7d758002015-05-06 17:49:36 -07002564 public int getPositionTargetType(int position) {
2565 int offset = 0;
2566
Adam Powella182e452015-07-06 16:57:56 -07002567 final int serviceTargetCount = getServiceTargetCount();
Matt Pietal5b648562019-03-12 07:40:26 -04002568 if (position < serviceTargetCount) {
Adam Powell7d758002015-05-06 17:49:36 -07002569 return TARGET_SERVICE;
2570 }
2571 offset += serviceTargetCount;
2572
Matt Pietal5b648562019-03-12 07:40:26 -04002573 final int callerTargetCount = getCallerTargetCount();
2574 if (position - offset < callerTargetCount) {
2575 return TARGET_CALLER;
2576 }
2577 offset += callerTargetCount;
2578
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002579 final int rankedTargetCount = getRankedTargetCount();
2580 if (position - offset < rankedTargetCount) {
Adam Powell7d758002015-05-06 17:49:36 -07002581 return TARGET_STANDARD;
2582 }
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002583 offset += rankedTargetCount;
2584
2585 final int standardTargetCount = getAlphaTargetCount();
2586 if (position - offset < standardTargetCount) {
2587 return TARGET_STANDARD_AZ;
2588 }
Adam Powell7d758002015-05-06 17:49:36 -07002589
2590 return TARGET_BAD;
2591 }
2592
Adam Powell24428412015-04-01 17:19:56 -07002593 @Override
2594 public TargetInfo getItem(int position) {
Adam Powell50077352015-05-26 18:01:55 -07002595 return targetInfoForPosition(position, true);
2596 }
2597
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002598
2599 /**
2600 * Find target info for a given position.
2601 * Since ChooserActivity displays several sections of content, determine which
2602 * section provides this item.
2603 */
Adam Powell50077352015-05-26 18:01:55 -07002604 @Override
2605 public TargetInfo targetInfoForPosition(int position, boolean filtered) {
Adam Powell24428412015-04-01 17:19:56 -07002606 int offset = 0;
Adam Powell0ccc0e92015-04-23 17:19:37 -07002607
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002608 // Direct share targets
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002609 final int serviceTargetCount = filtered ? getServiceTargetCount() :
2610 getSelectableServiceTargetCount();
Matt Pietal5b648562019-03-12 07:40:26 -04002611 if (position < serviceTargetCount) {
2612 return mServiceTargets.get(position);
Adam Powell0ccc0e92015-04-23 17:19:37 -07002613 }
2614 offset += serviceTargetCount;
2615
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002616 // Targets provided by calling app
Matt Pietal5b648562019-03-12 07:40:26 -04002617 final int callerTargetCount = getCallerTargetCount();
2618 if (position - offset < callerTargetCount) {
2619 return mCallerTargets.get(position - offset);
2620 }
2621 offset += callerTargetCount;
2622
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002623 // Ranked standard app targets
2624 final int rankedTargetCount = getRankedTargetCount();
2625 if (position - offset < rankedTargetCount) {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002626 return filtered ? super.getItem(position - offset)
2627 : getDisplayResolveInfo(position - offset);
2628 }
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002629 offset += rankedTargetCount;
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002630
2631 // Alphabetical complete app target list.
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002632 if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002633 return mSortedList.get(position - offset);
2634 }
2635
2636 return null;
Adam Powell24428412015-04-01 17:19:56 -07002637 }
2638
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002639
Matt Pietalfbfa0492019-04-01 11:29:56 -04002640 /**
2641 * Evaluate targets for inclusion in the direct share area. May not be included
2642 * if score is too low.
2643 */
2644 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
2645 boolean isShortcutResult) {
Matt Pietal26038402019-01-08 07:29:34 -05002646 if (DEBUG) {
2647 Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
2648 + " targets");
2649 }
Dan Sandlerf5e17692018-06-04 22:13:40 -04002650
Matt Pietalfbfa0492019-04-01 11:29:56 -04002651 if (targets.size() == 0) {
2652 return;
2653 }
2654
Matt Pietalfbfa0492019-04-01 11:29:56 -04002655 final float baseScore = getBaseScore(origTarget, isShortcutResult);
Adam Powella182e452015-07-06 16:57:56 -07002656 Collections.sort(targets, mBaseTargetComparator);
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002657
Matt Pietal3ed20a72019-06-24 12:14:52 -04002658 final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
Matt Pietal791b1c32019-04-30 13:36:49 -04002659 : MAX_CHOOSER_TARGETS_PER_APP;
Adam Powella182e452015-07-06 16:57:56 -07002660 float lastScore = 0;
Matt Pietalfbfa0492019-04-01 11:29:56 -04002661 boolean shouldNotify = false;
Matt Pietal791b1c32019-04-30 13:36:49 -04002662 for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
Adam Powella182e452015-07-06 16:57:56 -07002663 final ChooserTarget target = targets.get(i);
2664 float targetScore = target.getScore();
Matt Pietalfbfa0492019-04-01 11:29:56 -04002665 targetScore *= baseScore;
Adam Powella182e452015-07-06 16:57:56 -07002666 if (i > 0 && targetScore >= lastScore) {
2667 // Apply a decay so that the top app can't crowd out everything else.
2668 // This incents ChooserTargetServices to define what's truly better.
2669 targetScore = lastScore * 0.95f;
2670 }
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002671 boolean isInserted = insertServiceTarget(
Matt Pietalfbfa0492019-04-01 11:29:56 -04002672 new SelectableTargetInfo(origTarget, target, targetScore));
Adam Powella182e452015-07-06 16:57:56 -07002673
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002674 if (isInserted && isShortcutResult) {
2675 mNumShortcutResults++;
2676 }
2677
2678 shouldNotify |= isInserted;
2679
Adam Powella182e452015-07-06 16:57:56 -07002680 if (DEBUG) {
2681 Log.d(TAG, " => " + target.toString() + " score=" + targetScore
2682 + " base=" + target.getScore()
2683 + " lastScore=" + lastScore
Matt Pietalfbfa0492019-04-01 11:29:56 -04002684 + " baseScore=" + baseScore);
Adam Powella182e452015-07-06 16:57:56 -07002685 }
2686
2687 lastScore = targetScore;
Adam Powell24428412015-04-01 17:19:56 -07002688 }
2689
Matt Pietalfbfa0492019-04-01 11:29:56 -04002690 if (shouldNotify) {
2691 notifyDataSetChanged();
2692 }
2693 }
Adam Powell24428412015-04-01 17:19:56 -07002694
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002695 private int getNumShortcutResults() {
2696 return mNumShortcutResults;
2697 }
2698
Matt Pietalfbfa0492019-04-01 11:29:56 -04002699 /**
Matt Pietal39d181d2019-05-08 14:48:30 -04002700 * Use the scoring system along with artificial boosts to create up to 4 distinct buckets:
Matt Pietalfbfa0492019-04-01 11:29:56 -04002701 * <ol>
2702 * <li>App-supplied targets
Matt Pietal39d181d2019-05-08 14:48:30 -04002703 * <li>Shortcuts ranked via App Prediction Manager
2704 * <li>Shortcuts ranked via legacy heuristics
Matt Pietalfbfa0492019-04-01 11:29:56 -04002705 * <li>Legacy direct share targets
2706 * </ol>
2707 */
2708 private float getBaseScore(DisplayResolveInfo target, boolean isShortcutResult) {
2709 if (target == null) {
2710 return CALLER_TARGET_SCORE_BOOST;
2711 }
2712
Matt Pietal39d181d2019-05-08 14:48:30 -04002713 if (isShortcutResult && getAppPredictorForDirectShareIfEnabled() != null) {
Matt Pietalfbfa0492019-04-01 11:29:56 -04002714 return SHORTCUT_TARGET_SCORE_BOOST;
2715 }
2716
2717 float score = super.getScore(target);
2718 if (isShortcutResult) {
2719 return score * SHORTCUT_TARGET_SCORE_BOOST;
2720 }
2721
2722 return score;
Adam Powell24428412015-04-01 17:19:56 -07002723 }
2724
Adam Powell565943f2016-02-11 10:29:05 -08002725 /**
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002726 * Calling this marks service target loading complete, and will attempt to no longer
2727 * update the direct share area.
2728 */
2729 public void completeServiceTargetLoading() {
2730 mServiceTargets.removeIf(o -> o instanceof PlaceHolderTargetInfo);
2731
2732 if (mServiceTargets.isEmpty()) {
2733 mServiceTargets.add(new EmptyTargetInfo());
2734 }
2735 notifyDataSetChanged();
2736 }
2737
Matt Pietalfbfa0492019-04-01 11:29:56 -04002738 private boolean insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002739 // Avoid inserting any potentially late results
2740 if (mServiceTargets.size() == 1
2741 && mServiceTargets.get(0) instanceof EmptyTargetInfo) {
Matt Pietalfbfa0492019-04-01 11:29:56 -04002742 return false;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002743 }
2744
Matt Pietal9d501432019-04-12 10:05:29 -04002745 // Check for duplicates and abort if found
2746 for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
2747 if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
2748 return false;
2749 }
2750 }
2751
Matt Pietalfbfa0492019-04-01 11:29:56 -04002752 int currentSize = mServiceTargets.size();
Matt Pietal9d501432019-04-12 10:05:29 -04002753 final float newScore = chooserTargetInfo.getModifiedScore();
Matt Pietalfbfa0492019-04-01 11:29:56 -04002754 for (int i = 0; i < Math.min(currentSize, MAX_SERVICE_TARGETS); i++) {
Adam Powella182e452015-07-06 16:57:56 -07002755 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002756 if (serviceTarget == null) {
2757 mServiceTargets.set(i, chooserTargetInfo);
Matt Pietalfbfa0492019-04-01 11:29:56 -04002758 return true;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002759 } else if (newScore > serviceTarget.getModifiedScore()) {
Adam Powella182e452015-07-06 16:57:56 -07002760 mServiceTargets.add(i, chooserTargetInfo);
Matt Pietalfbfa0492019-04-01 11:29:56 -04002761 return true;
Adam Powella182e452015-07-06 16:57:56 -07002762 }
2763 }
Matt Pietalfbfa0492019-04-01 11:29:56 -04002764
2765 if (currentSize < MAX_SERVICE_TARGETS) {
2766 mServiceTargets.add(chooserTargetInfo);
2767 return true;
2768 }
2769
2770 return false;
Adam Powella182e452015-07-06 16:57:56 -07002771 }
Adam Powell24428412015-04-01 17:19:56 -07002772 }
2773
Adam Powella182e452015-07-06 16:57:56 -07002774 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
2775 @Override
2776 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
2777 // Descending order
Adam Powell77a533f2015-10-16 10:47:32 -07002778 return (int) Math.signum(rhs.getScore() - lhs.getScore());
Adam Powella182e452015-07-06 16:57:56 -07002779 }
2780 }
2781
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002782
Matt Pietal95574b02019-03-13 08:12:25 -04002783 private boolean isSendAction(Intent targetIntent) {
2784 if (targetIntent == null) {
2785 return false;
2786 }
2787
2788 String action = targetIntent.getAction();
2789 if (action == null) {
2790 return false;
2791 }
2792
2793 if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
2794 return true;
2795 }
2796
2797 return false;
2798 }
2799
Adam Powell7d758002015-05-06 17:49:36 -07002800 class ChooserRowAdapter extends BaseAdapter {
2801 private ChooserListAdapter mChooserListAdapter;
2802 private final LayoutInflater mLayoutInflater;
Adam Powell7d758002015-05-06 17:49:36 -07002803
Matt Pietal5b648562019-03-12 07:40:26 -04002804 private DirectShareViewHolder mDirectShareViewHolder;
Matt Pietalab986b52019-04-10 10:14:32 -04002805 private int mChooserTargetWidth = 0;
Mike Digman849a9d12019-04-29 11:20:48 -07002806 private boolean mShowAzLabelIfPoss;
Matt Pietal5b648562019-03-12 07:40:26 -04002807
Matt Pietale7cacab2019-05-23 07:21:36 -04002808 private boolean mHideContentPreview = false;
2809 private boolean mLayoutRequested = false;
2810
Matt Pietal5b648562019-03-12 07:40:26 -04002811 private static final int VIEW_TYPE_DIRECT_SHARE = 0;
2812 private static final int VIEW_TYPE_NORMAL = 1;
Matt Pietal1ef88002019-03-13 10:43:18 -04002813 private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002814 private static final int VIEW_TYPE_PROFILE = 3;
Mike Digmanae730b12019-04-25 11:10:31 -07002815 private static final int VIEW_TYPE_AZ_LABEL = 4;
Matt Pietal5b648562019-03-12 07:40:26 -04002816
Matt Pietalfaedea82019-03-21 10:36:54 -04002817 private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
2818 private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
2819
Mike Digman849a9d12019-04-29 11:20:48 -07002820 private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
2821
Adam Powell7d758002015-05-06 17:49:36 -07002822 public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
2823 mChooserListAdapter = wrappedAdapter;
2824 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
2825
Mike Digman849a9d12019-04-29 11:20:48 -07002826 mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
2827
Adam Powell7d758002015-05-06 17:49:36 -07002828 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
2829 @Override
2830 public void onChanged() {
2831 super.onChanged();
2832 notifyDataSetChanged();
2833 }
2834
2835 @Override
2836 public void onInvalidated() {
2837 super.onInvalidated();
2838 notifyDataSetInvalidated();
2839 }
2840 });
2841 }
2842
Matt Pietalfaedea82019-03-21 10:36:54 -04002843 /**
Matt Pietalab986b52019-04-10 10:14:32 -04002844 * Calculate the chooser target width to maximize space per item
Matt Pietalfaedea82019-03-21 10:36:54 -04002845 *
2846 * @param width The new row width to use for recalculation
Matt Pietalab986b52019-04-10 10:14:32 -04002847 * @return true if the view width has changed
Matt Pietalfaedea82019-03-21 10:36:54 -04002848 */
Matt Pietalab986b52019-04-10 10:14:32 -04002849 public boolean calculateChooserTargetWidth(int width) {
Matt Pietalab986b52019-04-10 10:14:32 -04002850 if (width == 0) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002851 return false;
2852 }
2853
Matt Pietal9d501432019-04-12 10:05:29 -04002854 int newWidth = width / getMaxTargetsPerRow();
Matt Pietalab986b52019-04-10 10:14:32 -04002855 if (newWidth != mChooserTargetWidth) {
2856 mChooserTargetWidth = newWidth;
Matt Pietalfaedea82019-03-21 10:36:54 -04002857 return true;
2858 }
2859
2860 return false;
2861 }
2862
Matt Pietal5b648562019-03-12 07:40:26 -04002863 private int getMaxTargetsPerRow() {
Matt Pietalfaedea82019-03-21 10:36:54 -04002864 int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002865 if (shouldDisplayLandscape(getResources().getConfiguration().orientation)) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002866 maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
2867 }
2868
Matt Pietalab986b52019-04-10 10:14:32 -04002869 return maxTargets;
Matt Pietal5b648562019-03-12 07:40:26 -04002870 }
2871
Matt Pietale7cacab2019-05-23 07:21:36 -04002872 public void hideContentPreview() {
2873 mHideContentPreview = true;
2874 mLayoutRequested = true;
2875 notifyDataSetChanged();
2876 }
2877
2878 public boolean consumeLayoutRequest() {
2879 boolean oldValue = mLayoutRequested;
2880 mLayoutRequested = false;
2881 return oldValue;
2882 }
2883
Adam Powell7d758002015-05-06 17:49:36 -07002884 @Override
Matt Pietal8a8cfc42019-04-30 07:59:14 -04002885 public boolean areAllItemsEnabled() {
2886 return false;
2887 }
2888
2889 @Override
2890 public boolean isEnabled(int position) {
2891 int viewType = getItemViewType(position);
Matt Pietal9462c0b2019-05-16 09:26:33 -04002892 if (viewType == VIEW_TYPE_CONTENT_PREVIEW || viewType == VIEW_TYPE_AZ_LABEL) {
Matt Pietal8a8cfc42019-04-30 07:59:14 -04002893 return false;
2894 }
2895 return true;
2896 }
2897
2898 @Override
Adam Powell7d758002015-05-06 17:49:36 -07002899 public int getCount() {
2900 return (int) (
Matt Pietal1ef88002019-03-13 10:43:18 -04002901 getContentPreviewRowCount()
Matt Pietal74c6ed02019-04-18 13:38:46 -04002902 + getProfileRowCount()
Matt Pietal26038402019-01-08 07:29:34 -05002903 + getServiceTargetRowCount()
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002904 + getCallerAndRankedTargetRowCount()
Mike Digmanae730b12019-04-25 11:10:31 -07002905 + getAzLabelRowCount()
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002906 + Math.ceil(
2907 (float) mChooserListAdapter.getAlphaTargetCount()
2908 / getMaxTargetsPerRow())
Adam Powell7d758002015-05-06 17:49:36 -07002909 );
2910 }
2911
Matt Pietal1ef88002019-03-13 10:43:18 -04002912 public int getContentPreviewRowCount() {
2913 if (!isSendAction(getTargetIntent())) {
2914 return 0;
2915 }
2916
Matt Pietale7cacab2019-05-23 07:21:36 -04002917 if (mHideContentPreview || mChooserListAdapter == null
2918 || mChooserListAdapter.getCount() == 0) {
Matt Pietal1ef88002019-03-13 10:43:18 -04002919 return 0;
2920 }
2921
2922 return 1;
2923 }
2924
Matt Pietal74c6ed02019-04-18 13:38:46 -04002925 public int getProfileRowCount() {
2926 return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
2927 }
2928
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002929 public int getCallerAndRankedTargetRowCount() {
Adam Powell63b31692015-09-28 10:45:00 -07002930 return (int) Math.ceil(
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002931 ((float) mChooserListAdapter.getCallerTargetCount()
2932 + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow());
Adam Powell63b31692015-09-28 10:45:00 -07002933 }
2934
Matt Pietal5b648562019-03-12 07:40:26 -04002935 // There can be at most one row in the listview, that is internally
2936 // a ViewGroup with 2 rows
Adam Powell63b31692015-09-28 10:45:00 -07002937 public int getServiceTargetRowCount() {
Matt Pietal030bd842019-05-29 07:14:14 -04002938 if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
Matt Pietal95574b02019-03-13 08:12:25 -04002939 return 1;
2940 }
2941 return 0;
Adam Powell63b31692015-09-28 10:45:00 -07002942 }
2943
Mike Digmanae730b12019-04-25 11:10:31 -07002944 public int getAzLabelRowCount() {
2945 // Only show a label if the a-z list is showing
Mike Digman849a9d12019-04-29 11:20:48 -07002946 return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
Mike Digmanae730b12019-04-25 11:10:31 -07002947 }
2948
Adam Powell7d758002015-05-06 17:49:36 -07002949 @Override
2950 public Object getItem(int position) {
2951 // We have nothing useful to return here.
2952 return position;
2953 }
2954
2955 @Override
2956 public long getItemId(int position) {
2957 return position;
2958 }
2959
2960 @Override
2961 public View getView(int position, View convertView, ViewGroup parent) {
Adam Powell63b31692015-09-28 10:45:00 -07002962 final RowViewHolder holder;
Matt Pietal5b648562019-03-12 07:40:26 -04002963 int viewType = getItemViewType(position);
2964
Matt Pietal1ef88002019-03-13 10:43:18 -04002965 if (viewType == VIEW_TYPE_CONTENT_PREVIEW) {
2966 return createContentPreviewView(convertView, parent);
2967 }
2968
Matt Pietal74c6ed02019-04-18 13:38:46 -04002969 if (viewType == VIEW_TYPE_PROFILE) {
2970 return createProfileView(convertView, parent);
2971 }
2972
Mike Digmanae730b12019-04-25 11:10:31 -07002973 if (viewType == VIEW_TYPE_AZ_LABEL) {
2974 return createAzLabelView(parent);
2975 }
2976
Adam Powell7d758002015-05-06 17:49:36 -07002977 if (convertView == null) {
Matt Pietal5b648562019-03-12 07:40:26 -04002978 holder = createViewHolder(viewType, parent);
Adam Powell7d758002015-05-06 17:49:36 -07002979 } else {
Adam Powell63b31692015-09-28 10:45:00 -07002980 holder = (RowViewHolder) convertView.getTag();
Adam Powell7d758002015-05-06 17:49:36 -07002981 }
Adam Powell7d758002015-05-06 17:49:36 -07002982
Matt Pietalfaedea82019-03-21 10:36:54 -04002983 bindViewHolder(position, holder);
Matt Pietal5b648562019-03-12 07:40:26 -04002984
2985 return holder.getViewGroup();
Adam Powell7d758002015-05-06 17:49:36 -07002986 }
2987
Matt Pietal5b648562019-03-12 07:40:26 -04002988 @Override
2989 public int getItemViewType(int position) {
Mike Digmanae730b12019-04-25 11:10:31 -07002990 int count;
Matt Pietal1ef88002019-03-13 10:43:18 -04002991
Mike Digmanae730b12019-04-25 11:10:31 -07002992 int countSum = (count = getContentPreviewRowCount());
2993 if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002994
Mike Digmanae730b12019-04-25 11:10:31 -07002995 countSum += (count = getProfileRowCount());
2996 if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
Adam Powell63b31692015-09-28 10:45:00 -07002997
Mike Digmanae730b12019-04-25 11:10:31 -07002998 countSum += (count = getServiceTargetRowCount());
2999 if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
3000
3001 countSum += (count = getCallerAndRankedTargetRowCount());
3002 if (count > 0 && position < countSum) return VIEW_TYPE_NORMAL;
3003
3004 countSum += (count = getAzLabelRowCount());
3005 if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
Matt Pietal5b648562019-03-12 07:40:26 -04003006
3007 return VIEW_TYPE_NORMAL;
3008 }
3009
3010 @Override
3011 public int getViewTypeCount() {
Mike Digmanae730b12019-04-25 11:10:31 -07003012 return 5;
Matt Pietal1ef88002019-03-13 10:43:18 -04003013 }
3014
3015 private ViewGroup createContentPreviewView(View convertView, ViewGroup parent) {
3016 Intent targetIntent = getTargetIntent();
3017 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
3018
3019 if (convertView == null) {
3020 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
3021 .setSubtype(previewType));
3022 }
3023
3024 return displayContentPreview(previewType, targetIntent, mLayoutInflater,
3025 (ViewGroup) convertView, parent);
Matt Pietal5b648562019-03-12 07:40:26 -04003026 }
3027
Matt Pietal74c6ed02019-04-18 13:38:46 -04003028 private View createProfileView(View convertView, ViewGroup parent) {
3029 View profileRow = convertView != null ? convertView : mLayoutInflater.inflate(
3030 R.layout.chooser_profile_row, parent, false);
3031 profileRow.setBackground(
3032 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
3033 mProfileView = profileRow.findViewById(R.id.profile_button);
3034 mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
3035 bindProfileView();
3036 return profileRow;
3037 }
3038
Mike Digmanae730b12019-04-25 11:10:31 -07003039 private View createAzLabelView(ViewGroup parent) {
3040 return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
3041 }
3042
Matt Pietal5b648562019-03-12 07:40:26 -04003043 private RowViewHolder loadViewsIntoRow(RowViewHolder holder) {
3044 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietalab986b52019-04-10 10:14:32 -04003045 final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
3046 MeasureSpec.EXACTLY);
Matt Pietal5b648562019-03-12 07:40:26 -04003047 int columnCount = holder.getColumnCount();
3048
Mike Digmanba232682019-03-27 14:55:26 -07003049 final boolean isDirectShare = holder instanceof DirectShareViewHolder;
3050
Matt Pietal5b648562019-03-12 07:40:26 -04003051 for (int i = 0; i < columnCount; i++) {
Mike Digmanba232682019-03-27 14:55:26 -07003052 final View v = mChooserListAdapter.createView(holder.getRowByIndex(i));
Adam Powell4eb98712015-10-14 13:10:18 -07003053 final int column = i;
Adam Powell63b31692015-09-28 10:45:00 -07003054 v.setOnClickListener(new OnClickListener() {
3055 @Override
3056 public void onClick(View v) {
Matt Pietal5b648562019-03-12 07:40:26 -04003057 startSelected(holder.getItemIndex(column), false, true);
Adam Powell63b31692015-09-28 10:45:00 -07003058 }
3059 });
3060 v.setOnLongClickListener(new OnLongClickListener() {
3061 @Override
3062 public boolean onLongClick(View v) {
Adam Powell23882512016-01-29 10:21:00 -08003063 showTargetDetails(
Adam Powell4eb98712015-10-14 13:10:18 -07003064 mChooserListAdapter.resolveInfoForPosition(
Matt Pietal5b648562019-03-12 07:40:26 -04003065 holder.getItemIndex(column), true));
Adam Powell63b31692015-09-28 10:45:00 -07003066 return true;
3067 }
3068 });
Matt Pietal5b648562019-03-12 07:40:26 -04003069 ViewGroup row = holder.addView(i, v);
Adam Powell63b31692015-09-28 10:45:00 -07003070
Mike Digmanba232682019-03-27 14:55:26 -07003071 // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
3072 // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be
3073 // done before measuring.
3074 if (isDirectShare) {
3075 final ViewHolder vh = (ViewHolder) v.getTag();
3076 vh.text.setLines(2);
3077 vh.text.setHorizontallyScrolling(false);
3078 vh.text2.setVisibility(View.GONE);
Adam Powell63b31692015-09-28 10:45:00 -07003079 }
Mike Digmanba232682019-03-27 14:55:26 -07003080
3081 // Force height to be a given so we don't have visual disruption during scaling.
Matt Pietalab986b52019-04-10 10:14:32 -04003082 v.measure(exactSpec, spec);
3083 setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight());
Adam Powell63b31692015-09-28 10:45:00 -07003084 }
3085
Matt Pietal5b648562019-03-12 07:40:26 -04003086 final ViewGroup viewGroup = holder.getViewGroup();
3087
Mike Digmanba232682019-03-27 14:55:26 -07003088 // Pre-measure and fix height so we can scale later.
Adam Powell63b31692015-09-28 10:45:00 -07003089 holder.measure();
Matt Pietalab986b52019-04-10 10:14:32 -04003090 setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight());
Mike Digmanba232682019-03-27 14:55:26 -07003091
3092 if (isDirectShare) {
3093 DirectShareViewHolder dsvh = (DirectShareViewHolder) holder;
Matt Pietalab986b52019-04-10 10:14:32 -04003094 setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
3095 setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
Adam Powell7d758002015-05-06 17:49:36 -07003096 }
Matt Pietal5b648562019-03-12 07:40:26 -04003097
3098 viewGroup.setTag(holder);
3099
Adam Powell7d758002015-05-06 17:49:36 -07003100 return holder;
3101 }
3102
Matt Pietalab986b52019-04-10 10:14:32 -04003103 private void setViewBounds(View view, int widthPx, int heightPx) {
Mike Digmanba232682019-03-27 14:55:26 -07003104 LayoutParams lp = view.getLayoutParams();
3105 if (lp == null) {
Matt Pietalab986b52019-04-10 10:14:32 -04003106 lp = new LayoutParams(widthPx, heightPx);
Mike Digmanba232682019-03-27 14:55:26 -07003107 view.setLayoutParams(lp);
3108 } else {
3109 lp.height = heightPx;
Matt Pietalab986b52019-04-10 10:14:32 -04003110 lp.width = widthPx;
Mike Digmanba232682019-03-27 14:55:26 -07003111 }
3112 }
3113
Matt Pietal5b648562019-03-12 07:40:26 -04003114 RowViewHolder createViewHolder(int viewType, ViewGroup parent) {
3115 if (viewType == VIEW_TYPE_DIRECT_SHARE) {
3116 ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate(
3117 R.layout.chooser_row_direct_share, parent, false);
3118 ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3119 parentGroup, false);
3120 ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3121 parentGroup, false);
3122 parentGroup.addView(row1);
3123 parentGroup.addView(row2);
3124
3125 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
3126 Lists.newArrayList(row1, row2), getMaxTargetsPerRow());
3127 loadViewsIntoRow(mDirectShareViewHolder);
3128
3129 return mDirectShareViewHolder;
3130 } else {
3131 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
3132 false);
3133 RowViewHolder holder = new SingleRowViewHolder(row, getMaxTargetsPerRow());
3134 loadViewsIntoRow(holder);
3135
3136 return holder;
3137 }
3138 }
3139
Matt Pietaldadc0d12019-04-16 12:53:28 -04003140 /**
Mike Digmanae730b12019-04-25 11:10:31 -07003141 * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
3142 * showing on top of the AZ list if the AZ label is visible. All other types are placed into
3143 * their own row as determined by their target type, and dividers are added in the list to
3144 * separate each type.
Matt Pietaldadc0d12019-04-16 12:53:28 -04003145 */
3146 int getRowType(int rowPosition) {
Mike Digmanae730b12019-04-25 11:10:31 -07003147 // Merge caller and ranked standard into a single row
Matt Pietaldadc0d12019-04-16 12:53:28 -04003148 int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
3149 if (positionType == ChooserListAdapter.TARGET_CALLER) {
3150 return ChooserListAdapter.TARGET_STANDARD;
3151 }
3152
Mike Digmanae730b12019-04-25 11:10:31 -07003153 // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
3154 // row type the same as the suggestion row type
3155 if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
3156 return ChooserListAdapter.TARGET_STANDARD;
3157 }
3158
Matt Pietaldadc0d12019-04-16 12:53:28 -04003159 return positionType;
3160 }
3161
Matt Pietalfaedea82019-03-21 10:36:54 -04003162 void bindViewHolder(int rowPosition, RowViewHolder holder) {
Adam Powell7d758002015-05-06 17:49:36 -07003163 final int start = getFirstRowPosition(rowPosition);
Matt Pietaldadc0d12019-04-16 12:53:28 -04003164 final int startType = getRowType(start);
3165 final int lastStartType = getRowType(getFirstRowPosition(rowPosition - 1));
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003166
Matt Pietal5b648562019-03-12 07:40:26 -04003167 final ViewGroup row = holder.getViewGroup();
3168
Matt Pietal74c6ed02019-04-18 13:38:46 -04003169 if (startType != lastStartType
3170 || rowPosition == getContentPreviewRowCount() + getProfileRowCount()) {
Matt Pietal84a55bd2019-05-01 12:32:47 -04003171 row.setForeground(
Matt Pietal74c6ed02019-04-18 13:38:46 -04003172 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003173 } else {
Matt Pietal84a55bd2019-05-01 12:32:47 -04003174 row.setForeground(null);
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003175 }
3176
Matt Pietalfaedea82019-03-21 10:36:54 -04003177 int columnCount = holder.getColumnCount();
Matt Pietal5b648562019-03-12 07:40:26 -04003178 int end = start + columnCount - 1;
Matt Pietaldadc0d12019-04-16 12:53:28 -04003179 while (getRowType(end) != startType && end >= start) {
Adam Powell7d758002015-05-06 17:49:36 -07003180 end--;
3181 }
3182
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003183 if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
Matt Pietal5b648562019-03-12 07:40:26 -04003184 final TextView textView = row.findViewById(R.id.chooser_row_text_option);
Adam Powell63b31692015-09-28 10:45:00 -07003185
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003186 if (textView.getVisibility() != View.VISIBLE) {
3187 textView.setAlpha(0.0f);
3188 textView.setVisibility(View.VISIBLE);
3189 textView.setText(R.string.chooser_no_direct_share_targets);
3190
3191 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
3192 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3193
3194 float translationInPx = getResources().getDimensionPixelSize(
3195 R.dimen.chooser_row_text_option_translate);
3196 textView.setTranslationY(translationInPx);
3197 ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
3198 0.0f);
3199 translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3200
3201 AnimatorSet animSet = new AnimatorSet();
3202 animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3203 animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3204 animSet.playTogether(fadeAnim, translateAnim);
3205 animSet.start();
3206 }
Adam Powell7d758002015-05-06 17:49:36 -07003207 }
3208
Matt Pietal5b648562019-03-12 07:40:26 -04003209 for (int i = 0; i < columnCount; i++) {
3210 final View v = holder.getView(i);
Adam Powell7d758002015-05-06 17:49:36 -07003211 if (start + i <= end) {
Matt Pietalfaedea82019-03-21 10:36:54 -04003212 holder.setViewVisibility(i, View.VISIBLE);
Matt Pietal5b648562019-03-12 07:40:26 -04003213 holder.setItemIndex(i, start + i);
3214 mChooserListAdapter.bindView(holder.getItemIndex(i), v);
Adam Powell7d758002015-05-06 17:49:36 -07003215 } else {
Matt Pietalfaedea82019-03-21 10:36:54 -04003216 holder.setViewVisibility(i, View.INVISIBLE);
Adam Powell7d758002015-05-06 17:49:36 -07003217 }
3218 }
3219 }
3220
3221 int getFirstRowPosition(int row) {
Matt Pietal74c6ed02019-04-18 13:38:46 -04003222 row -= getContentPreviewRowCount() + getProfileRowCount();
Matt Pietal1ef88002019-03-13 10:43:18 -04003223
Matt Pietal5b648562019-03-12 07:40:26 -04003224 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
3225 final int serviceRows = (int) Math.ceil((float) serviceCount
3226 / ChooserListAdapter.MAX_SERVICE_TARGETS);
3227 if (row < serviceRows) {
3228 return row * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07003229 }
3230
Matt Pietaldadc0d12019-04-16 12:53:28 -04003231 final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
3232 + mChooserListAdapter.getRankedTargetCount();
3233 final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
3234 if (row < callerAndRankedRows + serviceRows) {
Matt Pietal5b648562019-03-12 07:40:26 -04003235 return serviceCount + (row - serviceRows) * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07003236 }
3237
Mike Digmanae730b12019-04-25 11:10:31 -07003238 row -= getAzLabelRowCount();
3239
Matt Pietaldadc0d12019-04-16 12:53:28 -04003240 return callerAndRankedCount + serviceCount
3241 + (row - callerAndRankedRows - serviceRows) * getMaxTargetsPerRow();
Matt Pietal5b648562019-03-12 07:40:26 -04003242 }
3243
3244 public void handleScroll(View v, int y, int oldy) {
Matt Pietale54dcc2e2019-05-02 12:59:38 -04003245 // Only expand direct share area if there is a minimum number of shortcuts,
3246 // which will help reduce the amount of visible shuffling due to older-style
3247 // direct share targets.
3248 int orientation = getResources().getConfiguration().orientation;
3249 boolean canExpandDirectShare =
3250 mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
Matt Pietal3e4b56f2019-05-31 12:06:17 -04003251 && orientation == Configuration.ORIENTATION_PORTRAIT
3252 && !isInMultiWindowMode();
Matt Pietale54dcc2e2019-05-02 12:59:38 -04003253
3254 if (mDirectShareViewHolder != null && canExpandDirectShare) {
Matt Pietal5b648562019-03-12 07:40:26 -04003255 mDirectShareViewHolder.handleScroll(mAdapterView, y, oldy, getMaxTargetsPerRow());
3256 }
Adam Powell7d758002015-05-06 17:49:36 -07003257 }
3258 }
3259
Matt Pietal5b648562019-03-12 07:40:26 -04003260 abstract class RowViewHolder {
3261 protected int mMeasuredRowHeight;
3262 private int[] mItemIndices;
3263 protected final View[] mCells;
Matt Pietal5b648562019-03-12 07:40:26 -04003264 private final int mColumnCount;
Adam Powell63b31692015-09-28 10:45:00 -07003265
Matt Pietal5b648562019-03-12 07:40:26 -04003266 RowViewHolder(int cellCount) {
3267 this.mCells = new View[cellCount];
3268 this.mItemIndices = new int[cellCount];
Matt Pietal5b648562019-03-12 07:40:26 -04003269 this.mColumnCount = cellCount;
3270 }
3271
3272 abstract ViewGroup addView(int index, View v);
3273
3274 abstract ViewGroup getViewGroup();
3275
Mike Digmanba232682019-03-27 14:55:26 -07003276 abstract ViewGroup getRowByIndex(int index);
3277
3278 abstract ViewGroup getRow(int rowNumber);
Matt Pietal5b648562019-03-12 07:40:26 -04003279
Matt Pietalfaedea82019-03-21 10:36:54 -04003280 abstract void setViewVisibility(int i, int visibility);
3281
Matt Pietal5b648562019-03-12 07:40:26 -04003282 public int getColumnCount() {
3283 return mColumnCount;
3284 }
3285
Adam Powell63b31692015-09-28 10:45:00 -07003286 public void measure() {
3287 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietal5b648562019-03-12 07:40:26 -04003288 getViewGroup().measure(spec, spec);
3289 mMeasuredRowHeight = getViewGroup().getMeasuredHeight();
3290 }
3291
3292 public int getMeasuredRowHeight() {
3293 return mMeasuredRowHeight;
3294 }
3295
Matt Pietal5b648562019-03-12 07:40:26 -04003296 public void setItemIndex(int itemIndex, int listIndex) {
3297 mItemIndices[itemIndex] = listIndex;
3298 }
3299
3300 public int getItemIndex(int itemIndex) {
3301 return mItemIndices[itemIndex];
3302 }
3303
3304 public View getView(int index) {
3305 return mCells[index];
3306 }
3307 }
3308
3309 class SingleRowViewHolder extends RowViewHolder {
3310 private final ViewGroup mRow;
3311
3312 SingleRowViewHolder(ViewGroup row, int cellCount) {
3313 super(cellCount);
3314
3315 this.mRow = row;
3316 }
3317
3318 public ViewGroup getViewGroup() {
3319 return mRow;
3320 }
3321
Mike Digmanba232682019-03-27 14:55:26 -07003322 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04003323 return mRow;
3324 }
3325
Mike Digmanba232682019-03-27 14:55:26 -07003326 public ViewGroup getRow(int rowNumber) {
3327 if (rowNumber == 0) return mRow;
3328 return null;
3329 }
3330
Matt Pietal5b648562019-03-12 07:40:26 -04003331 public ViewGroup addView(int index, View v) {
3332 mRow.addView(v);
3333 mCells[index] = v;
3334
Matt Pietal5b648562019-03-12 07:40:26 -04003335 return mRow;
3336 }
Matt Pietalfaedea82019-03-21 10:36:54 -04003337
3338 public void setViewVisibility(int i, int visibility) {
3339 getView(i).setVisibility(visibility);
3340 }
Matt Pietal5b648562019-03-12 07:40:26 -04003341 }
3342
3343 class DirectShareViewHolder extends RowViewHolder {
3344 private final ViewGroup mParent;
3345 private final List<ViewGroup> mRows;
3346 private int mCellCountPerRow;
3347
3348 private boolean mHideDirectShareExpansion = false;
3349 private int mDirectShareMinHeight = 0;
3350 private int mDirectShareCurrHeight = 0;
3351 private int mDirectShareMaxHeight = 0;
3352
Matt Pietalfaedea82019-03-21 10:36:54 -04003353 private final boolean[] mCellVisibility;
3354
Matt Pietal5b648562019-03-12 07:40:26 -04003355 DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow) {
3356 super(rows.size() * cellCountPerRow);
3357
3358 this.mParent = parent;
3359 this.mRows = rows;
3360 this.mCellCountPerRow = cellCountPerRow;
Matt Pietalfaedea82019-03-21 10:36:54 -04003361 this.mCellVisibility = new boolean[rows.size() * cellCountPerRow];
Matt Pietal5b648562019-03-12 07:40:26 -04003362 }
3363
3364 public ViewGroup addView(int index, View v) {
Mike Digmanba232682019-03-27 14:55:26 -07003365 ViewGroup row = getRowByIndex(index);
Matt Pietal5b648562019-03-12 07:40:26 -04003366 row.addView(v);
3367 mCells[index] = v;
3368
Matt Pietal5b648562019-03-12 07:40:26 -04003369 return row;
3370 }
3371
3372 public ViewGroup getViewGroup() {
3373 return mParent;
3374 }
3375
Mike Digmanba232682019-03-27 14:55:26 -07003376 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04003377 return mRows.get(index / mCellCountPerRow);
3378 }
3379
Mike Digmanba232682019-03-27 14:55:26 -07003380 public ViewGroup getRow(int rowNumber) {
3381 return mRows.get(rowNumber);
3382 }
3383
Matt Pietal5b648562019-03-12 07:40:26 -04003384 public void measure() {
3385 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3386 getRow(0).measure(spec, spec);
3387 getRow(1).measure(spec, spec);
3388
Matt Pietal5b648562019-03-12 07:40:26 -04003389 mDirectShareMinHeight = getRow(0).getMeasuredHeight();
3390 mDirectShareCurrHeight = mDirectShareCurrHeight > 0
3391 ? mDirectShareCurrHeight : mDirectShareMinHeight;
3392 mDirectShareMaxHeight = 2 * mDirectShareMinHeight;
3393 }
3394
3395 public int getMeasuredRowHeight() {
3396 return mDirectShareCurrHeight;
3397 }
3398
Matt Pietala9c8e502019-04-10 14:27:35 -04003399 public int getMinRowHeight() {
3400 return mDirectShareMinHeight;
3401 }
3402
Matt Pietalfaedea82019-03-21 10:36:54 -04003403 public void setViewVisibility(int i, int visibility) {
3404 final View v = getView(i);
3405 if (visibility == View.VISIBLE) {
3406 mCellVisibility[i] = true;
3407 v.setVisibility(visibility);
3408 v.setAlpha(1.0f);
3409 } else if (visibility == View.INVISIBLE && mCellVisibility[i]) {
3410 mCellVisibility[i] = false;
3411
3412 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
3413 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3414 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
3415 fadeAnim.addListener(new AnimatorListenerAdapter() {
3416 public void onAnimationEnd(Animator animation) {
3417 v.setVisibility(View.INVISIBLE);
3418 }
3419 });
3420 fadeAnim.start();
3421 }
3422 }
3423
Matt Pietal5b648562019-03-12 07:40:26 -04003424 public void handleScroll(AbsListView view, int y, int oldy, int maxTargetsPerRow) {
Matt Pietala9c8e502019-04-10 14:27:35 -04003425 // only exit early if fully collapsed, otherwise onListRebuilt() with shifting
3426 // targets can lock us into an expanded mode
3427 boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight;
3428 if (notExpanded) {
3429 if (mHideDirectShareExpansion) {
3430 return;
3431 }
Matt Pietal5b648562019-03-12 07:40:26 -04003432
Matt Pietala9c8e502019-04-10 14:27:35 -04003433 // only expand if we have more than maxTargetsPerRow, and delay that decision
3434 // until they start to scroll
3435 if (mChooserListAdapter.getSelectableServiceTargetCount() <= maxTargetsPerRow) {
3436 mHideDirectShareExpansion = true;
3437 return;
3438 }
Matt Pietal5b648562019-03-12 07:40:26 -04003439 }
3440
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003441 int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal5b648562019-03-12 07:40:26 -04003442
3443 int prevHeight = mDirectShareCurrHeight;
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003444 int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
3445 newHeight = Math.max(newHeight, mDirectShareMinHeight);
3446 yDiff = newHeight - prevHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04003447
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003448 if (view == null || view.getChildCount() == 0 || yDiff == 0) {
Matt Pietal5b648562019-03-12 07:40:26 -04003449 return;
3450 }
3451
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003452 // locate the item to expand, and offset the rows below that one
3453 boolean foundExpansion = false;
3454 for (int i = 0; i < view.getChildCount(); i++) {
3455 View child = view.getChildAt(i);
Matt Pietal1ef88002019-03-13 10:43:18 -04003456
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003457 if (foundExpansion) {
3458 child.offsetTopAndBottom(yDiff);
3459 } else {
3460 if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
3461 int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
3462 MeasureSpec.EXACTLY);
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003463 int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003464 MeasureSpec.EXACTLY);
3465 child.measure(widthSpec, heightSpec);
3466 child.getLayoutParams().height = child.getMeasuredHeight();
3467 child.layout(child.getLeft(), child.getTop(), child.getRight(),
3468 child.getTop() + child.getMeasuredHeight());
Matt Pietal5b648562019-03-12 07:40:26 -04003469
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003470 foundExpansion = true;
3471 }
3472 }
Matt Pietal5b648562019-03-12 07:40:26 -04003473 }
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003474
3475 if (foundExpansion) {
3476 mDirectShareCurrHeight = newHeight;
3477 }
Adam Powell63b31692015-09-28 10:45:00 -07003478 }
3479 }
3480
Adam Powell9761ab22015-09-08 17:01:49 -07003481 static class ChooserTargetServiceConnection implements ServiceConnection {
Adam Powell52c39212016-04-07 15:14:18 -07003482 private DisplayResolveInfo mOriginalTarget;
Adam Powell9761ab22015-09-08 17:01:49 -07003483 private ComponentName mConnectedComponent;
3484 private ChooserActivity mChooserActivity;
3485 private final Object mLock = new Object();
Adam Powell24428412015-04-01 17:19:56 -07003486
3487 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
3488 @Override
3489 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
Adam Powell9761ab22015-09-08 17:01:49 -07003490 synchronized (mLock) {
3491 if (mChooserActivity == null) {
3492 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
3493 + mConnectedComponent + "; ignoring...");
3494 return;
3495 }
3496 mChooserActivity.filterServiceTargets(
3497 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
3498 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04003499 msg.what = ChooserHandler.CHOOSER_TARGET_SERVICE_RESULT;
Adam Powell9761ab22015-09-08 17:01:49 -07003500 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
3501 ChooserTargetServiceConnection.this);
3502 mChooserActivity.mChooserHandler.sendMessage(msg);
3503 }
Adam Powell24428412015-04-01 17:19:56 -07003504 }
3505 };
3506
Adam Powell9761ab22015-09-08 17:01:49 -07003507 public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
3508 DisplayResolveInfo dri) {
3509 mChooserActivity = chooserActivity;
Adam Powell24428412015-04-01 17:19:56 -07003510 mOriginalTarget = dri;
3511 }
3512
3513 @Override
3514 public void onServiceConnected(ComponentName name, IBinder service) {
3515 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003516 synchronized (mLock) {
3517 if (mChooserActivity == null) {
3518 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
3519 return;
3520 }
3521
3522 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
3523 try {
3524 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
3525 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
3526 } catch (RemoteException e) {
3527 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
3528 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003529 mChooserActivity.mServiceConnections.remove(this);
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003530 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003531 }
Adam Powell24428412015-04-01 17:19:56 -07003532 }
3533 }
3534
3535 @Override
3536 public void onServiceDisconnected(ComponentName name) {
3537 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003538 synchronized (mLock) {
3539 if (mChooserActivity == null) {
3540 Log.e(TAG,
3541 "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
3542 return;
3543 }
3544
3545 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003546 mChooserActivity.mServiceConnections.remove(this);
3547 if (mChooserActivity.mServiceConnections.isEmpty()) {
Adam Powell9761ab22015-09-08 17:01:49 -07003548 mChooserActivity.sendVoiceChoicesIfNeeded();
3549 }
3550 mConnectedComponent = null;
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003551 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003552 }
3553 }
3554
3555 public void destroy() {
3556 synchronized (mLock) {
3557 mChooserActivity = null;
Adam Powell52c39212016-04-07 15:14:18 -07003558 mOriginalTarget = null;
Adam Powell4c470d62015-06-19 17:46:17 -07003559 }
Adam Powell24428412015-04-01 17:19:56 -07003560 }
3561
3562 @Override
3563 public String toString() {
Adam Powell9761ab22015-09-08 17:01:49 -07003564 return "ChooserTargetServiceConnection{service="
3565 + mConnectedComponent + ", activity="
Adam Powell52c39212016-04-07 15:14:18 -07003566 + (mOriginalTarget != null
3567 ? mOriginalTarget.getResolveInfo().activityInfo.toString()
3568 : "<connection destroyed>") + "}";
Adam Powell24428412015-04-01 17:19:56 -07003569 }
3570 }
3571
3572 static class ServiceResultInfo {
3573 public final DisplayResolveInfo originalTarget;
3574 public final List<ChooserTarget> resultTargets;
3575 public final ChooserTargetServiceConnection connection;
3576
3577 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
3578 ChooserTargetServiceConnection c) {
3579 originalTarget = ot;
3580 resultTargets = rt;
3581 connection = c;
3582 }
3583 }
Adam Powell2ed547e2015-04-29 18:45:04 -07003584
3585 static class RefinementResultReceiver extends ResultReceiver {
3586 private ChooserActivity mChooserActivity;
3587 private TargetInfo mSelectedTarget;
3588
3589 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
3590 Handler handler) {
3591 super(handler);
3592 mChooserActivity = host;
3593 mSelectedTarget = target;
3594 }
3595
3596 @Override
3597 protected void onReceiveResult(int resultCode, Bundle resultData) {
3598 if (mChooserActivity == null) {
3599 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
3600 return;
3601 }
3602 if (resultData == null) {
3603 Log.e(TAG, "RefinementResultReceiver received null resultData");
3604 return;
3605 }
3606
3607 switch (resultCode) {
3608 case RESULT_CANCELED:
3609 mChooserActivity.onRefinementCanceled();
3610 break;
3611 case RESULT_OK:
3612 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
3613 if (intentParcelable instanceof Intent) {
3614 mChooserActivity.onRefinementResult(mSelectedTarget,
3615 (Intent) intentParcelable);
3616 } else {
3617 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
3618 + " in resultData with key Intent.EXTRA_INTENT");
3619 }
3620 break;
3621 default:
3622 Log.w(TAG, "Unknown result code " + resultCode
3623 + " sent to RefinementResultReceiver");
3624 break;
3625 }
3626 }
3627
3628 public void destroy() {
3629 mChooserActivity = null;
3630 mSelectedTarget = null;
3631 }
3632 }
Adam Powell63b31692015-09-28 10:45:00 -07003633
Matt Pietal26038402019-01-08 07:29:34 -05003634 /**
3635 * Used internally to round image corners while obeying view padding.
3636 */
3637 public static class RoundedRectImageView extends ImageView {
3638 private int mRadius = 0;
3639 private Path mPath = new Path();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003640 private Paint mOverlayPaint = new Paint(0);
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003641 private Paint mRoundRectPaint = new Paint(0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003642 private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
3643 private String mExtraImageCount = null;
Matt Pietal26038402019-01-08 07:29:34 -05003644
3645 public RoundedRectImageView(Context context) {
3646 super(context);
3647 }
3648
3649 public RoundedRectImageView(Context context, AttributeSet attrs) {
3650 this(context, attrs, 0);
3651 }
3652
3653 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
3654 this(context, attrs, defStyleAttr, 0);
3655 }
3656
3657 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
3658 int defStyleRes) {
3659 super(context, attrs, defStyleAttr, defStyleRes);
3660 mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003661
3662 mOverlayPaint.setColor(0x99000000);
3663 mOverlayPaint.setStyle(Paint.Style.FILL);
3664
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003665 mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider));
3666 mRoundRectPaint.setStyle(Paint.Style.STROKE);
3667 mRoundRectPaint.setStrokeWidth(context.getResources()
3668 .getDimensionPixelSize(R.dimen.chooser_preview_image_border));
3669
Matt Pietal0ea391b2019-01-30 10:44:15 -05003670 mTextPaint.setColor(Color.WHITE);
3671 mTextPaint.setTextSize(context.getResources()
3672 .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
3673 mTextPaint.setTextAlign(Paint.Align.CENTER);
Matt Pietal26038402019-01-08 07:29:34 -05003674 }
3675
3676 private void updatePath(int width, int height) {
3677 mPath.reset();
3678
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003679 int imageWidth = width - getPaddingRight() - getPaddingLeft();
3680 int imageHeight = height - getPaddingBottom() - getPaddingTop();
Matt Pietal26038402019-01-08 07:29:34 -05003681 mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
3682 mRadius, Path.Direction.CW);
3683 }
3684
3685 /**
3686 * Sets the corner radius on all corners
3687 *
3688 * param radius 0 for no radius, &gt; 0 for a visible corner radius
3689 */
3690 public void setRadius(int radius) {
3691 mRadius = radius;
3692 updatePath(getWidth(), getHeight());
3693 }
3694
Matt Pietal0ea391b2019-01-30 10:44:15 -05003695 /**
3696 * Display an overlay with extra image count on 3rd image
3697 */
3698 public void setExtraImageCount(int count) {
3699 if (count > 0) {
3700 this.mExtraImageCount = "+" + count;
3701 } else {
3702 this.mExtraImageCount = null;
3703 }
3704 }
3705
Matt Pietal26038402019-01-08 07:29:34 -05003706 @Override
3707 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
3708 super.onSizeChanged(width, height, oldWidth, oldHeight);
3709 updatePath(width, height);
3710 }
3711
3712 @Override
3713 protected void onDraw(Canvas canvas) {
3714 if (mRadius != 0) {
3715 canvas.clipPath(mPath);
3716 }
3717
3718 super.onDraw(canvas);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003719
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003720 int x = getPaddingLeft();
3721 int y = getPaddingRight();
3722 int width = getWidth() - getPaddingRight() - getPaddingLeft();
3723 int height = getHeight() - getPaddingBottom() - getPaddingTop();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003724 if (mExtraImageCount != null) {
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003725 canvas.drawRect(x, y, width, height, mOverlayPaint);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003726
3727 int xPos = canvas.getWidth() / 2;
3728 int yPos = (int) ((canvas.getHeight() / 2.0f)
3729 - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
3730
3731 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
3732 }
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003733
3734 canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
Matt Pietal26038402019-01-08 07:29:34 -05003735 }
3736 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003737}