blob: 183e9a6faf099b04d4f8d7718428ef3f79c48fa0 [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;
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -070027import android.annotation.NonNull;
George Hodulik145b3a52019-03-27 11:18:43 -070028import android.annotation.Nullable;
Artur Satayev751e5512019-11-15 19:12:49 +000029import android.annotation.UnsupportedAppUsage;
Adam Powell0b3c1122014-10-09 12:50:14 -070030import android.app.Activity;
Ng Zhi And3ec5fc2018-05-10 09:13:00 -070031import android.app.ActivityManager;
George Hodulik69d4a082019-01-18 11:27:03 -080032import android.app.prediction.AppPredictionContext;
33import android.app.prediction.AppPredictionManager;
34import android.app.prediction.AppPredictor;
35import android.app.prediction.AppTarget;
George Hodulikf2b0d342019-01-25 12:43:54 -080036import android.app.prediction.AppTargetEvent;
Matt Pietal26038402019-01-08 07:29:34 -050037import android.content.ClipData;
Matt Pietal1fa7d802019-01-30 10:44:15 -050038import android.content.ClipboardManager;
Adam Powell0b3c1122014-10-09 12:50:14 -070039import android.content.ComponentName;
Matt Pietal0ea391b2019-01-30 10:44:15 -050040import android.content.ContentResolver;
Adam Powell24428412015-04-01 17:19:56 -070041import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.content.Intent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080043import android.content.IntentFilter;
Adam Powell0b3c1122014-10-09 12:50:14 -070044import android.content.IntentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -070045import android.content.IntentSender.SendIntentException;
Adam Powell24428412015-04-01 17:19:56 -070046import android.content.ServiceConnection;
Alison Cichowlas1fd47152019-11-14 19:50:55 -050047import android.content.SharedPreferences;
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +000048import android.content.pm.ActivityInfo;
Matt Pietala4b30072019-04-04 13:44:36 -040049import android.content.pm.ApplicationInfo;
Adam Powell24428412015-04-01 17:19:56 -070050import android.content.pm.PackageManager;
51import android.content.pm.PackageManager.NameNotFoundException;
52import android.content.pm.ResolveInfo;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080053import android.content.pm.ShortcutInfo;
54import android.content.pm.ShortcutManager;
Matt Pietal18bbd822019-02-12 15:21:36 -050055import android.content.res.Configuration;
Matt Pietal46d828c2019-02-05 08:07:07 -050056import android.database.Cursor;
Adam Powell7d758002015-05-06 17:49:36 -070057import android.database.DataSetObserver;
Matt Pietal26038402019-01-08 07:29:34 -050058import android.graphics.Bitmap;
59import android.graphics.Canvas;
Adam Powell63b31692015-09-28 10:45:00 -070060import android.graphics.Color;
Matt Pietal0ea391b2019-01-30 10:44:15 -050061import android.graphics.Paint;
Matt Pietal26038402019-01-08 07:29:34 -050062import android.graphics.Path;
Mike Digmanac1d88c2019-04-18 15:15:55 -070063import android.graphics.drawable.AnimatedVectorDrawable;
Adam Powell24428412015-04-01 17:19:56 -070064import android.graphics.drawable.Drawable;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -050065import android.metrics.LogMaker;
Matt Pietal26038402019-01-08 07:29:34 -050066import android.net.Uri;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080067import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068import android.os.Bundle;
Alison Cichowlas1fd47152019-11-14 19:50:55 -050069import android.os.Environment;
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;
Alison Cichowlas1fd47152019-11-14 19:50:55 -050079import android.os.storage.StorageManager;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -040080import android.provider.DeviceConfig;
Matt Pietal46d828c2019-02-05 08:07:07 -050081import android.provider.DocumentsContract;
Matt Pietalf38e9d22019-02-15 10:01:03 -050082import android.provider.Downloads;
Matt Pietal46d828c2019-02-05 08:07:07 -050083import android.provider.OpenableColumns;
Adam Powell24428412015-04-01 17:19:56 -070084import android.service.chooser.ChooserTarget;
85import android.service.chooser.ChooserTargetService;
86import android.service.chooser.IChooserTargetResult;
87import android.service.chooser.IChooserTargetService;
88import 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;
Matt Pietal26038402019-01-08 07:29:34 -0500103import android.widget.ImageView;
Matt Pietal26038402019-01-08 07:29:34 -0500104import android.widget.TextView;
Matt Pietal1fa7d802019-01-30 10:44:15 -0500105import android.widget.Toast;
Jason Monk027dcfa2017-06-27 18:37:35 -0400106
Adam Powell7d758002015-05-06 17:49:36 -0700107import com.android.internal.R;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800108import com.android.internal.annotations.VisibleForTesting;
arangelovb0802dc2019-10-18 18:03:44 +0100109import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
110import com.android.internal.app.ResolverListAdapter.ViewHolder;
111import com.android.internal.app.chooser.ChooserTargetInfo;
112import com.android.internal.app.chooser.DisplayResolveInfo;
113import com.android.internal.app.chooser.NotSelectableTargetInfo;
114import com.android.internal.app.chooser.SelectableTargetInfo;
115import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
116import com.android.internal.app.chooser.TargetInfo;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400117import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
Matt Pietalab73a882019-06-05 07:04:55 -0400118import com.android.internal.content.PackageMonitor;
Adam Powell98b7f892015-06-19 12:38:45 -0700119import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +0100120import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500121import com.android.internal.util.ImageUtils;
Zhen Zhangbde7b462019-11-11 11:49:33 -0800122import com.android.internal.widget.GridLayoutManager;
123import com.android.internal.widget.RecyclerView;
Mike Digman849a9d12019-04-29 11:20:48 -0700124import com.android.internal.widget.ResolverDrawerLayout;
Alison Cichowlas3e340502018-08-07 17:15:01 -0400125
Adam Powell52c39212016-04-07 15:14:18 -0700126import com.google.android.collect.Lists;
Adam Powell24428412015-04-01 17:19:56 -0700127
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500128import java.io.File;
Matt Pietal26038402019-01-08 07:29:34 -0500129import java.io.IOException;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500130import java.lang.annotation.Retention;
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700131import java.lang.annotation.RetentionPolicy;
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400132import java.text.Collator;
Adam Powell24428412015-04-01 17:19:56 -0700133import java.util.ArrayList;
Adam Powella182e452015-07-06 16:57:56 -0700134import java.util.Collections;
135import java.util.Comparator;
George Hodulikaa5238c2019-04-18 14:17:51 -0700136import java.util.HashMap;
Matt Pietalab73a882019-06-05 07:04:55 -0400137import java.util.HashSet;
Adam Powell24428412015-04-01 17:19:56 -0700138import java.util.List;
George Hodulikaa5238c2019-04-18 14:17:51 -0700139import java.util.Map;
Matt Pietalab73a882019-06-05 07:04:55 -0400140import java.util.Set;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400142/**
143 * The Chooser Activity handles intent resolution specifically for sharing intents -
144 * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
145 *
146 */
arangelovb0802dc2019-10-18 18:03:44 +0100147public class ChooserActivity extends ResolverActivity implements
148 ChooserListAdapter.ChooserListCommunicator,
149 SelectableTargetInfoCommunicator {
Adam Powell0b3c1122014-10-09 12:50:14 -0700150 private static final String TAG = "ChooserActivity";
151
Artur Satayev751e5512019-11-15 19:12:49 +0000152 @UnsupportedAppUsage
153 public ChooserActivity() {
154 }
Jorim Jaggif631ef72017-02-24 13:49:47 +0100155 /**
156 * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
157 * in onStop when launched in a new task. If this extra is set to true, we do not finish
158 * ourselves when onStop gets called.
159 */
160 public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
161 = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
162
Mike Digman849a9d12019-04-29 11:20:48 -0700163 private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
164
Mehdi Alizadeh3c335a22019-01-17 16:03:19 -0800165 private static final boolean DEBUG = false;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800166
George Hodulik1428beb2019-05-01 17:04:23 -0700167 private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
George Hodulik69d4a082019-01-18 11:27:03 -0800168 // TODO(b/123088566) Share these in a better way.
169 private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
George Hodulikf2b0d342019-01-25 12:43:54 -0800170 public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
George Hodulik69d4a082019-01-18 11:27:03 -0800171 private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
172 public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700173
arangelovb0802dc2019-10-18 18:03:44 +0100174 @VisibleForTesting
175 public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
Zhen Zhangbde7b462019-11-11 11:49:33 -0800176 private static final int SINGLE_CELL_SPAN_SIZE = 1;
arangelovb0802dc2019-10-18 18:03:44 +0100177
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700178 private boolean mIsAppPredictorComponentAvailable;
George Hodulik69d4a082019-01-18 11:27:03 -0800179 private AppPredictor mAppPredictor;
180 private AppPredictor.Callback mAppPredictorCallback;
George Hodulikaa5238c2019-04-18 14:17:51 -0700181 private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
George Hodulik69d4a082019-01-18 11:27:03 -0800182
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700183 public static final int TARGET_TYPE_DEFAULT = 0;
184 public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
185 public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
186 public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
187
arangelovb0802dc2019-10-18 18:03:44 +0100188 private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
189
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700190 @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
191 TARGET_TYPE_DEFAULT,
192 TARGET_TYPE_CHOOSER_TARGET,
193 TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
194 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
195 })
196 @Retention(RetentionPolicy.SOURCE)
197 public @interface ShareTargetType {}
198
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500199 /**
200 * The transition time between placeholders for direct share to a message
201 * indicating that non are available.
202 */
203 private static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200;
204
Matt Pietal394ebd02019-05-03 07:36:21 -0400205 private static final float DIRECT_SHARE_EXPANSION_RATE = 0.78f;
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400206
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800207 // TODO(b/121287224): Re-evaluate this limit
208 private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
Adam Powell24428412015-04-01 17:19:56 -0700209
Adam Powell2ed547e2015-04-29 18:45:04 -0700210 private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
Adam Powell24428412015-04-01 17:19:56 -0700211
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400212 private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7;
213 private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
214 SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
215 DEFAULT_SALT_EXPIRATION_DAYS);
216
Adam Powelle49d9392014-07-17 18:45:19 -0700217 private Bundle mReplacementExtras;
Adam Powell0b3c1122014-10-09 12:50:14 -0700218 private IntentSender mChosenComponentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -0700219 private IntentSender mRefinementIntentSender;
220 private RefinementResultReceiver mRefinementResultReceiver;
Adam Powell52c39212016-04-07 15:14:18 -0700221 private ChooserTarget[] mCallerChooserTargets;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800222 private ComponentName[] mFilteredComponentNames;
Adam Powelle49d9392014-07-17 18:45:19 -0700223
Adam Powell13036be2015-05-12 14:43:56 -0700224 private Intent mReferrerFillInIntent;
225
Kang Li9082f5b2016-12-02 10:56:21 -0800226 private long mChooserShownTime;
Kang Li64b018e2017-01-05 17:30:06 -0800227 protected boolean mIsSuccessfullySelected;
Kang Li9082f5b2016-12-02 10:56:21 -0800228
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700229 private long mQueriedTargetServicesTimeMs;
230 private long mQueriedSharingShortcutsTimeMs;
231
Zhen Zhangbde7b462019-11-11 11:49:33 -0800232 private RecyclerView mRecyclerView;
Adam Powell7d758002015-05-06 17:49:36 -0700233 private ChooserListAdapter mChooserListAdapter;
Zhen Zhangbde7b462019-11-11 11:49:33 -0800234 private ChooserGridAdapter mChooserGridAdapter;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500235 private int mChooserRowServiceSpacing;
Adam Powell24428412015-04-01 17:19:56 -0700236
Matt Pietalab73a882019-06-05 07:04:55 -0400237 private int mCurrAvailableWidth = 0;
238
Adam Powell23882512016-01-29 10:21:00 -0800239 private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400240 // TODO: Update to handle landscape instead of using static value
241 private static final int MAX_RANKED_TARGETS = 4;
Adam Powell23882512016-01-29 10:21:00 -0800242
Adam Powell24428412015-04-01 17:19:56 -0700243 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
Matt Pietalab73a882019-06-05 07:04:55 -0400244 private final Set<ComponentName> mServicesRequested = new HashSet<>();
Matt Pietalaf044ae2019-03-29 06:53:53 -0400245
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -0400246 private static final int MAX_LOG_RANK_POSITION = 12;
247
Matt Pietal4e2e3632019-04-05 08:32:47 -0400248 private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400249 private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
Matt Pietal4e2e3632019-04-05 08:32:47 -0400250
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500251 private SharedPreferences mPinnedSharedPrefs;
252 private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
253
Matt Pietal0ea391b2019-01-30 10:44:15 -0500254 @Retention(SOURCE)
255 @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
256 private @interface ContentPreviewType {
257 }
258
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500259 // Starting at 1 since 0 is considered "undefined" for some of the database transformations
260 // of tron logs.
261 private static final int CONTENT_PREVIEW_IMAGE = 1;
262 private static final int CONTENT_PREVIEW_FILE = 2;
263 private static final int CONTENT_PREVIEW_TEXT = 3;
264 protected MetricsLogger mMetricsLogger;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500265
Matt Pietale7cacab2019-05-23 07:21:36 -0400266 private ContentPreviewCoordinator mPreviewCoord;
267
268 private class ContentPreviewCoordinator {
Matt Pietale7cacab2019-05-23 07:21:36 -0400269 private static final int IMAGE_FADE_IN_MILLIS = 150;
270 private static final int IMAGE_LOAD_TIMEOUT = 1;
271 private static final int IMAGE_LOAD_INTO_VIEW = 2;
272
Matt Pietalab73a882019-06-05 07:04:55 -0400273 private final int mImageLoadTimeoutMillis =
274 getResources().getInteger(R.integer.config_shortAnimTime);
275
Matt Pietale7cacab2019-05-23 07:21:36 -0400276 private final View mParentView;
277 private boolean mHideParentOnFail;
278 private boolean mAtLeastOneLoaded = false;
279
280 class LoadUriTask {
281 public final Uri mUri;
282 public final int mImageResourceId;
283 public final int mExtraCount;
284 public final Bitmap mBmp;
285
286 LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) {
287 this.mImageResourceId = imageResourceId;
288 this.mUri = uri;
289 this.mExtraCount = extraCount;
290 this.mBmp = bmp;
291 }
292 }
293
294 // If at least one image loads within the timeout period, allow other
295 // loads to continue. Otherwise terminate and optionally hide
296 // the parent area
297 private final Handler mHandler = new Handler() {
298 @Override
299 public void handleMessage(Message msg) {
300 switch (msg.what) {
301 case IMAGE_LOAD_TIMEOUT:
302 maybeHideContentPreview();
303 break;
304
305 case IMAGE_LOAD_INTO_VIEW:
306 if (isFinishing()) break;
307
308 LoadUriTask task = (LoadUriTask) msg.obj;
309 RoundedRectImageView imageView = mParentView.findViewById(
310 task.mImageResourceId);
311 if (task.mBmp == null) {
312 imageView.setVisibility(View.GONE);
313 maybeHideContentPreview();
314 return;
315 }
316
317 mAtLeastOneLoaded = true;
318 imageView.setVisibility(View.VISIBLE);
319 imageView.setAlpha(0.0f);
320 imageView.setImageBitmap(task.mBmp);
321
322 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f,
323 1.0f);
324 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
325 fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS);
326 fadeAnim.start();
327
328 if (task.mExtraCount > 0) {
329 imageView.setExtraImageCount(task.mExtraCount);
330 }
331 }
332 }
333 };
334
335 ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) {
336 super();
337
338 this.mParentView = parentView;
339 this.mHideParentOnFail = hideParentOnFail;
340 }
341
342 private void loadUriIntoView(final int imageResourceId, final Uri uri,
343 final int extraImages) {
Matt Pietalab73a882019-06-05 07:04:55 -0400344 mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, mImageLoadTimeoutMillis);
Matt Pietale7cacab2019-05-23 07:21:36 -0400345
346 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
347 final Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
348 final Message msg = Message.obtain();
349 msg.what = IMAGE_LOAD_INTO_VIEW;
350 msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
351 mHandler.sendMessage(msg);
352 });
353 }
354
355 private void cancelLoads() {
356 mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW);
357 mHandler.removeMessages(IMAGE_LOAD_TIMEOUT);
358 }
359
360 private void maybeHideContentPreview() {
361 if (!mAtLeastOneLoaded && mHideParentOnFail) {
362 Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
Matt Pietalab73a882019-06-05 07:04:55 -0400363 + " within " + mImageLoadTimeoutMillis + "ms.");
Matt Pietale7cacab2019-05-23 07:21:36 -0400364 collapseParentView();
Zhen Zhangbde7b462019-11-11 11:49:33 -0800365 if (mChooserGridAdapter != null) {
366 mChooserGridAdapter.hideContentPreview();
Matt Pietale7cacab2019-05-23 07:21:36 -0400367 }
368 mHideParentOnFail = false;
369 }
370 }
371
372 private void collapseParentView() {
373 // This will effectively hide the content preview row by forcing the height
374 // to zero. It is faster than forcing a relayout of the listview
375 final View v = mParentView;
376 int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY);
377 int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
378 v.measure(widthSpec, heightSpec);
379 v.getLayoutParams().height = 0;
380 v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop());
381 v.invalidate();
382 }
383 }
384
Matt Pietalab73a882019-06-05 07:04:55 -0400385 private final ChooserHandler mChooserHandler = new ChooserHandler();
386
387 private class ChooserHandler extends Handler {
388 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
389 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT = 2;
390 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT = 3;
391 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
392 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
393 private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
394
395 private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
396 private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;
397
398 private boolean mMinTimeoutPassed = false;
399
400 private void removeAllMessages() {
401 removeMessages(LIST_VIEW_UPDATE_MESSAGE);
402 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
403 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
404 removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
405 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
406 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
407 }
408
409 private void restartServiceRequestTimer() {
410 mMinTimeoutPassed = false;
411 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
412 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
413
414 if (DEBUG) {
415 Log.d(TAG, "queryTargets setting watchdog timer for "
416 + WATCHDOG_TIMEOUT_MIN_MILLIS + "-"
417 + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
418 }
419
420 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
421 WATCHDOG_TIMEOUT_MIN_MILLIS);
422 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
423 WATCHDOG_TIMEOUT_MAX_MILLIS);
424 }
425
426 private void maybeStopServiceRequestTimer() {
427 // Set a minimum timeout threshold, to ensure both apis, sharing shortcuts
428 // and older-style direct share services, have had time to load, otherwise
429 // just checking mServiceConnections could force us to end prematurely
430 if (mMinTimeoutPassed && mServiceConnections.isEmpty()) {
431 logDirectShareTargetReceived(
432 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
433 sendVoiceChoicesIfNeeded();
434 mChooserListAdapter.completeServiceTargetLoading();
435 }
436 }
437
Adam Powell24428412015-04-01 17:19:56 -0700438 @Override
439 public void handleMessage(Message msg) {
Matt Pietalaf044ae2019-03-29 06:53:53 -0400440 if (mChooserListAdapter == null || isDestroyed()) {
441 return;
442 }
443
Adam Powell24428412015-04-01 17:19:56 -0700444 switch (msg.what) {
445 case CHOOSER_TARGET_SERVICE_RESULT:
446 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
Adam Powell24428412015-04-01 17:19:56 -0700447 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
448 if (!mServiceConnections.contains(sri.connection)) {
449 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
450 + " returned after being removed from active connections."
451 + " Have you considered returning results faster?");
452 break;
453 }
Adam Powella182e452015-07-06 16:57:56 -0700454 if (sri.resultTargets != null) {
455 mChooserListAdapter.addServiceResults(sri.originalTarget,
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700456 sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET);
Adam Powella182e452015-07-06 16:57:56 -0700457 }
Adam Powell24428412015-04-01 17:19:56 -0700458 unbindService(sri.connection);
Adam Powell9761ab22015-09-08 17:01:49 -0700459 sri.connection.destroy();
Adam Powell24428412015-04-01 17:19:56 -0700460 mServiceConnections.remove(sri.connection);
Matt Pietalab73a882019-06-05 07:04:55 -0400461 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700462 break;
463
Matt Pietalab73a882019-06-05 07:04:55 -0400464 case CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT:
465 mMinTimeoutPassed = true;
466 maybeStopServiceRequestTimer();
467 break;
Matt Pietalaf044ae2019-03-29 06:53:53 -0400468
Matt Pietalab73a882019-06-05 07:04:55 -0400469 case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
Adam Powell24428412015-04-01 17:19:56 -0700470 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -0400471 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700472 break;
473
Matt Pietalaf044ae2019-03-29 06:53:53 -0400474 case LIST_VIEW_UPDATE_MESSAGE:
475 if (DEBUG) {
476 Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
477 }
478
479 mChooserListAdapter.refreshListView();
480 break;
481
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800482 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
483 if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800484 final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
485 if (resultInfo.resultTargets != null) {
486 mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700487 resultInfo.resultTargets, msg.arg1);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800488 }
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800489 break;
490
491 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700492 logDirectShareTargetReceived(
493 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800494 sendVoiceChoicesIfNeeded();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800495 break;
496
Adam Powell24428412015-04-01 17:19:56 -0700497 default:
498 super.handleMessage(msg);
499 }
500 }
501 };
502
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 @Override
504 protected void onCreate(Bundle savedInstanceState) {
Kang Li9082f5b2016-12-02 10:56:21 -0800505 final long intentReceivedTime = System.currentTimeMillis();
Mehdi Alizadeh5cc5f712019-09-06 12:17:52 -0700506 // This is the only place this value is being set. Effectively final.
507 mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
508
Kang Li9082f5b2016-12-02 10:56:21 -0800509 mIsSuccessfullySelected = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800510 Intent intent = getIntent();
Dianne Hackborneb034652009-09-07 00:49:58 -0700511 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
512 if (!(targetParcelable instanceof Intent)) {
Christopher Tate9d6376a2014-02-12 13:14:10 -0800513 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
Dianne Hackborneb034652009-09-07 00:49:58 -0700514 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800515 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700516 return;
517 }
Adam Powell24428412015-04-01 17:19:56 -0700518 Intent target = (Intent) targetParcelable;
Craig Mautner411d2aed2014-05-08 09:07:43 -0700519 if (target != null) {
Adam Powelle49d9392014-07-17 18:45:19 -0700520 modifyTargetIntent(target);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700521 }
Adam Powell2ed547e2015-04-29 18:45:04 -0700522 Parcelable[] targetsParcelable
523 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
524 if (targetsParcelable != null) {
525 final boolean offset = target == null;
526 Intent[] additionalTargets =
527 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
528 for (int i = 0; i < targetsParcelable.length; i++) {
529 if (!(targetsParcelable[i] instanceof Intent)) {
530 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
531 + targetsParcelable[i]);
532 finish();
533 super.onCreate(null);
534 return;
535 }
536 final Intent additionalTarget = (Intent) targetsParcelable[i];
537 if (i == 0 && target == null) {
538 target = additionalTarget;
539 modifyTargetIntent(target);
540 } else {
541 additionalTargets[offset ? i - 1 : i] = additionalTarget;
542 modifyTargetIntent(additionalTarget);
543 }
544 }
545 setAdditionalTargets(additionalTargets);
546 }
547
Adam Powelle49d9392014-07-17 18:45:19 -0700548 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
Matt Pietal26038402019-01-08 07:29:34 -0500549
550 // Do not allow the title to be changed when sharing content
551 CharSequence title = null;
552 if (target != null) {
Matt Pietal95574b02019-03-13 08:12:25 -0400553 if (!isSendAction(target)) {
Matt Pietal26038402019-01-08 07:29:34 -0500554 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
555 } else {
556 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
557 + " preview title by using EXTRA_TITLE property of the wrapped"
558 + " EXTRA_INTENT.");
559 }
560 }
561
Adam Powell278902c2014-07-12 18:33:22 -0700562 int defaultTitleRes = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800563 if (title == null) {
Adam Powell278902c2014-07-12 18:33:22 -0700564 defaultTitleRes = com.android.internal.R.string.chooseActivity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565 }
Matt Pietal26038402019-01-08 07:29:34 -0500566
Dianne Hackborneb034652009-09-07 00:49:58 -0700567 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
568 Intent[] initialIntents = null;
569 if (pa != null) {
Matt Pietal4e2e3632019-04-05 08:32:47 -0400570 int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS);
571 initialIntents = new Intent[count];
572 for (int i = 0; i < count; i++) {
Dianne Hackborneb034652009-09-07 00:49:58 -0700573 if (!(pa[i] instanceof Intent)) {
Adam Powell2ed547e2015-04-29 18:45:04 -0700574 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
Dianne Hackborneb034652009-09-07 00:49:58 -0700575 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800576 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700577 return;
578 }
Craig Mautner411d2aed2014-05-08 09:07:43 -0700579 final Intent in = (Intent) pa[i];
Adam Powelle49d9392014-07-17 18:45:19 -0700580 modifyTargetIntent(in);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700581 initialIntents[i] = in;
Dianne Hackborneb034652009-09-07 00:49:58 -0700582 }
583 }
Adam Powell24428412015-04-01 17:19:56 -0700584
Adam Powell13036be2015-05-12 14:43:56 -0700585 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
586
Adam Powell0b3c1122014-10-09 12:50:14 -0700587 mChosenComponentSender = intent.getParcelableExtra(
588 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
Adam Powell2ed547e2015-04-29 18:45:04 -0700589 mRefinementIntentSender = intent.getParcelableExtra(
590 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
Dianne Hackborn028ceeb2014-08-17 17:45:48 -0700591 setSafeForwardingMode(true);
Adam Powell23882512016-01-29 10:21:00 -0800592
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500593 mPinnedSharedPrefs = getPinnedSharedPrefs(this);
594
Adam Powell52c39212016-04-07 15:14:18 -0700595 pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
596 if (pa != null) {
597 ComponentName[] names = new ComponentName[pa.length];
598 for (int i = 0; i < pa.length; i++) {
599 if (!(pa[i] instanceof ComponentName)) {
600 Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
601 names = null;
602 break;
603 }
604 names[i] = (ComponentName) pa[i];
605 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800606 mFilteredComponentNames = names;
Adam Powell52c39212016-04-07 15:14:18 -0700607 }
608
609 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
610 if (pa != null) {
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400611 int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS);
612 ChooserTarget[] targets = new ChooserTarget[count];
613 for (int i = 0; i < count; i++) {
Adam Powell52c39212016-04-07 15:14:18 -0700614 if (!(pa[i] instanceof ChooserTarget)) {
615 Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
616 targets = null;
617 break;
618 }
619 targets[i] = (ChooserTarget) pa[i];
620 }
621 mCallerChooserTargets = targets;
622 }
623
Jorim Jaggif631ef72017-02-24 13:49:47 +0100624 setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
Adam Powell278902c2014-07-12 18:33:22 -0700625 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
626 null, false);
Adam Powell98b7f892015-06-19 12:38:45 -0700627
Kang Li9082f5b2016-12-02 10:56:21 -0800628 mChooserShownTime = System.currentTimeMillis();
629 final long systemCost = mChooserShownTime - intentReceivedTime;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500630
631 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500632 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
633 MetricsEvent.PARENT_PROFILE)
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500634 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
635 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
George Hodulik69d4a082019-01-18 11:27:03 -0800636
George Hodulik145b3a52019-03-27 11:18:43 -0700637 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
638 if (appPredictor != null) {
George Hodulikaa5238c2019-04-18 14:17:51 -0700639 mDirectShareAppTargetCache = new HashMap<>();
George Hodulik69d4a082019-01-18 11:27:03 -0800640 mAppPredictorCallback = resultList -> {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500641 if (isFinishing() || isDestroyed()) {
642 return;
643 }
arangelovb0802dc2019-10-18 18:03:44 +0100644 if (mChooserListAdapter.getCount() == 0) {
George Hodulik0dd5fbe2019-03-06 12:00:26 -0800645 return;
646 }
George Hodulik3f399f22019-04-26 16:17:54 -0700647 if (resultList.isEmpty()) {
648 // APS may be disabled, so try querying targets ourselves.
649 queryDirectShareTargets(mChooserListAdapter, true);
650 return;
651 }
George Hodulik69d4a082019-01-18 11:27:03 -0800652 final List<DisplayResolveInfo> driList =
653 getDisplayResolveInfos(mChooserListAdapter);
654 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
655 new ArrayList<>();
656 for (AppTarget appTarget : resultList) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500657 if (appTarget.getShortcutInfo() == null) {
658 continue;
659 }
George Hodulik69d4a082019-01-18 11:27:03 -0800660 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
661 appTarget.getShortcutInfo(),
662 new ComponentName(
663 appTarget.getPackageName(), appTarget.getClassName())));
664 }
George Hodulikaa5238c2019-04-18 14:17:51 -0700665 sendShareShortcutInfoList(shareShortcutInfos, driList, resultList);
George Hodulik69d4a082019-01-18 11:27:03 -0800666 };
George Hodulik145b3a52019-03-27 11:18:43 -0700667 appPredictor
668 .registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
George Hodulik69d4a082019-01-18 11:27:03 -0800669 }
670
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500671 mChooserRowServiceSpacing = getResources()
672 .getDimensionPixelSize(R.dimen.chooser_service_spacing);
673
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400674 if (mResolverDrawerLayout != null) {
675 mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
676
677 // expand/shrink direct share 4 -> 8 viewgroup
678 if (isSendAction(target)) {
679 mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
680 }
Matt Pietalb1d629d2019-04-23 11:35:53 -0400681
682 final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
683 final float defaultElevation = chooserHeader.getElevation();
684 final float chooserHeaderScrollElevation =
685 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
686
Zhen Zhangbde7b462019-11-11 11:49:33 -0800687 mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
688 public void onScrollStateChanged(RecyclerView view, int scrollState) {
Matt Pietalb1d629d2019-04-23 11:35:53 -0400689 }
690
Zhen Zhangbde7b462019-11-11 11:49:33 -0800691 public void onScrolled(RecyclerView view, int dx, int dy) {
Matt Pietalb1d629d2019-04-23 11:35:53 -0400692 if (view.getChildCount() > 0) {
Zhen Zhangbde7b462019-11-11 11:49:33 -0800693 View child = view.getLayoutManager().findViewByPosition(0);
694 if (child == null || child.getTop() < 0) {
Matt Pietalb1d629d2019-04-23 11:35:53 -0400695 chooserHeader.setElevation(chooserHeaderScrollElevation);
696 return;
697 }
698 }
699
700 chooserHeader.setElevation(defaultElevation);
701 }
702 });
Mike Digman849a9d12019-04-29 11:20:48 -0700703
704 mResolverDrawerLayout.setOnCollapsedChangedListener(
705 new ResolverDrawerLayout.OnCollapsedChangedListener() {
706
707 // Only consider one expansion per activity creation
708 private boolean mWrittenOnce = false;
709
710 @Override
711 public void onCollapsedChanged(boolean isCollapsed) {
712 if (!isCollapsed && !mWrittenOnce) {
713 incrementNumSheetExpansions();
714 mWrittenOnce = true;
715 }
716 }
717 });
Matt Pietal5b648562019-03-12 07:40:26 -0400718 }
719
Kang Li9082f5b2016-12-02 10:56:21 -0800720 if (DEBUG) {
721 Log.d(TAG, "System Time Cost is " + systemCost);
722 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800723 }
Adam Powelle49d9392014-07-17 18:45:19 -0700724
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500725 static SharedPreferences getPinnedSharedPrefs(Context context) {
726 // The code below is because in the android:ui process, no one can hear you scream.
727 // The package info in the context isn't initialized in the way it is for normal apps,
728 // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
729 // build the path manually below using the same policy that appears in ContextImpl.
730 // This fails silently under the hood if there's a problem, so if we find ourselves in
731 // the case where we don't have access to credential encrypted storage we just won't
732 // have our pinned target info.
733 final File prefsFile = new File(new File(
734 Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
735 context.getUserId(), context.getPackageName()),
736 "shared_prefs"),
737 PINNED_SHARED_PREFS_NAME + ".xml");
738 return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
739 }
740
Matt Pietal26038402019-01-08 07:29:34 -0500741 /**
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700742 * Returns true if app prediction service is defined and the component exists on device.
743 */
Mehdi Alizadehe870e972019-09-11 17:54:15 -0700744 @VisibleForTesting
745 public boolean isAppPredictionServiceAvailable() {
746 if (getPackageManager().getAppPredictionServicePackageName() == null) {
747 // Default AppPredictionService is not defined.
748 return false;
749 }
750
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700751 final String appPredictionServiceName =
752 getString(R.string.config_defaultAppPredictionService);
753 if (appPredictionServiceName == null) {
754 return false;
755 }
756 final ComponentName appPredictionComponentName =
757 ComponentName.unflattenFromString(appPredictionServiceName);
758 if (appPredictionComponentName == null) {
759 return false;
760 }
761
762 // Check if the app prediction component actually exists on the device.
763 Intent intent = new Intent();
764 intent.setComponent(appPredictionComponentName);
765 if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) {
766 Log.e(TAG, "App prediction service is defined, but does not exist: "
767 + appPredictionServiceName);
768 return false;
769 }
770 return true;
771 }
772
773 /**
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500774 * Check if the profile currently used is a work profile.
775 * @return true if it is work profile, false if it is parent profile (or no work profile is
776 * set up)
777 */
778 protected boolean isWorkProfile() {
779 return ((UserManager) getSystemService(Context.USER_SERVICE))
780 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
781 }
782
Matt Pietalab73a882019-06-05 07:04:55 -0400783 @Override
784 protected PackageMonitor createPackageMonitor() {
785 return new PackageMonitor() {
786 @Override
787 public void onSomePackagesChanged() {
788 mAdapter.handlePackagesChanged();
arangelovb0802dc2019-10-18 18:03:44 +0100789 updateProfileViewButton();
Matt Pietalab73a882019-06-05 07:04:55 -0400790 }
791 };
792 }
793
Matt Pietal46d828c2019-02-05 08:07:07 -0500794 private void onCopyButtonClicked(View v) {
795 Intent targetIntent = getTargetIntent();
796 if (targetIntent == null) {
797 finish();
798 } else {
799 final String action = targetIntent.getAction();
800
801 ClipData clipData = null;
802 if (Intent.ACTION_SEND.equals(action)) {
803 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
804 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
805
806 if (extraText != null) {
807 clipData = ClipData.newPlainText(null, extraText);
808 } else if (extraStream != null) {
809 clipData = ClipData.newUri(getContentResolver(), null, extraStream);
810 } else {
811 Log.w(TAG, "No data available to copy to clipboard");
812 return;
813 }
814 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
815 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
816 Intent.EXTRA_STREAM);
817 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
818 for (int i = 1; i < streams.size(); i++) {
819 clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
820 }
821 } else {
822 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
823 // so warn about unexpected action
824 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
825 return;
826 }
827
828 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
829 Context.CLIPBOARD_SERVICE);
830 clipboardManager.setPrimaryClip(clipData);
831 Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
832
Alison Cichowlasaa7f79f2019-09-12 15:57:26 -0400833 // Log share completion via copy
834 LogMaker targetLogMaker = new LogMaker(
835 MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
836 getMetricsLogger().write(targetLogMaker);
837
Matt Pietal46d828c2019-02-05 08:07:07 -0500838 finish();
839 }
840 }
841
Matt Pietal18bbd822019-02-12 15:21:36 -0500842 @Override
843 public void onConfigurationChanged(Configuration newConfig) {
844 super.onConfigurationChanged(newConfig);
845
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400846 adjustPreviewWidth(newConfig.orientation, null);
847 }
848
849 private boolean shouldDisplayLandscape(int orientation) {
850 // Sharesheet fixes the # of items per row and therefore can not correctly lay out
851 // when in the restricted size of multi-window mode. In the future, would be nice
852 // to use minimum dp size requirements instead
853 return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
854 }
855
856 private void adjustPreviewWidth(int orientation, View parent) {
Matt Pietal18bbd822019-02-12 15:21:36 -0500857 int width = -1;
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400858 if (shouldDisplayLandscape(orientation)) {
Matt Pietal18bbd822019-02-12 15:21:36 -0500859 width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
860 }
861
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400862 parent = parent == null ? getWindow().getDecorView() : parent;
863
864 updateLayoutWidth(R.id.content_preview_text_layout, width, parent);
865 updateLayoutWidth(R.id.content_preview_title_layout, width, parent);
866 updateLayoutWidth(R.id.content_preview_file_layout, width, parent);
Matt Pietal18bbd822019-02-12 15:21:36 -0500867 }
868
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400869 private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
870 View view = parent.findViewById(layoutResourceId);
Matt Pietal1ef88002019-03-13 10:43:18 -0400871 if (view != null && view.getLayoutParams() != null) {
872 LayoutParams params = view.getLayoutParams();
873 params.width = width;
874 view.setLayoutParams(params);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500875 }
876 }
877
Matt Pietal1ef88002019-03-13 10:43:18 -0400878 private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
Zhen Zhangbde7b462019-11-11 11:49:33 -0800879 Intent targetIntent, LayoutInflater layoutInflater, ViewGroup parent) {
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400880 ViewGroup layout = null;
881
Matt Pietal1ef88002019-03-13 10:43:18 -0400882 switch (previewType) {
883 case CONTENT_PREVIEW_TEXT:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400884 layout = displayTextContentPreview(targetIntent, layoutInflater, parent);
885 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400886 case CONTENT_PREVIEW_IMAGE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400887 layout = displayImageContentPreview(targetIntent, layoutInflater, parent);
888 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400889 case CONTENT_PREVIEW_FILE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400890 layout = displayFileContentPreview(targetIntent, layoutInflater, parent);
891 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400892 default:
893 Log.e(TAG, "Unexpected content preview type: " + previewType);
894 }
Matt Pietal0ea391b2019-01-30 10:44:15 -0500895
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400896 if (layout != null) {
897 adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
898 }
899
900 return layout;
Matt Pietal1ef88002019-03-13 10:43:18 -0400901 }
902
903 private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -0400904 ViewGroup parent) {
905 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
906 R.layout.chooser_grid_preview_text, parent, false);
Matt Pietal1ef88002019-03-13 10:43:18 -0400907
908 contentPreviewLayout.findViewById(R.id.copy_button).setOnClickListener(
909 this::onCopyButtonClicked);
Matt Pietal46d828c2019-02-05 08:07:07 -0500910
Matt Pietal26038402019-01-08 07:29:34 -0500911 CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
Matt Pietal26038402019-01-08 07:29:34 -0500912 if (sharingText == null) {
Matt Pietal1ef88002019-03-13 10:43:18 -0400913 contentPreviewLayout.findViewById(R.id.content_preview_text_layout).setVisibility(
914 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500915 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -0400916 TextView textView = contentPreviewLayout.findViewById(R.id.content_preview_text);
Matt Pietal1fa7d802019-01-30 10:44:15 -0500917 textView.setText(sharingText);
Matt Pietal26038402019-01-08 07:29:34 -0500918 }
919
920 String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
Matt Pietal46d828c2019-02-05 08:07:07 -0500921 if (TextUtils.isEmpty(previewTitle)) {
Matt Pietal1ef88002019-03-13 10:43:18 -0400922 contentPreviewLayout.findViewById(R.id.content_preview_title_layout).setVisibility(
923 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500924 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -0400925 TextView previewTitleView = contentPreviewLayout.findViewById(
926 R.id.content_preview_title);
Matt Pietal26038402019-01-08 07:29:34 -0500927 previewTitleView.setText(previewTitle);
Matt Pietal26038402019-01-08 07:29:34 -0500928
Matt Pietal1fa7d802019-01-30 10:44:15 -0500929 ClipData previewData = targetIntent.getClipData();
930 Uri previewThumbnail = null;
931 if (previewData != null) {
932 if (previewData.getItemCount() > 0) {
933 ClipData.Item previewDataItem = previewData.getItemAt(0);
934 previewThumbnail = previewDataItem.getUri();
935 }
Matt Pietal26038402019-01-08 07:29:34 -0500936 }
Matt Pietal26038402019-01-08 07:29:34 -0500937
Matt Pietal1ef88002019-03-13 10:43:18 -0400938 ImageView previewThumbnailView = contentPreviewLayout.findViewById(
939 R.id.content_preview_thumbnail);
Matt Pietal1fa7d802019-01-30 10:44:15 -0500940 if (previewThumbnail == null) {
Matt Pietal26038402019-01-08 07:29:34 -0500941 previewThumbnailView.setVisibility(View.GONE);
942 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -0400943 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
944 mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
Matt Pietal26038402019-01-08 07:29:34 -0500945 }
946 }
Matt Pietal1ef88002019-03-13 10:43:18 -0400947
948 return contentPreviewLayout;
Matt Pietal26038402019-01-08 07:29:34 -0500949 }
950
Matt Pietal1ef88002019-03-13 10:43:18 -0400951 private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -0400952 ViewGroup parent) {
953 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
954 R.layout.chooser_grid_preview_image, parent, false);
955 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500956
957 String action = targetIntent.getAction();
958 if (Intent.ACTION_SEND.equals(action)) {
959 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietale7cacab2019-05-23 07:21:36 -0400960 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500961 } else {
962 ContentResolver resolver = getContentResolver();
963
964 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
965 List<Uri> imageUris = new ArrayList<>();
966 for (Uri uri : uris) {
967 if (isImageType(resolver.getType(uri))) {
968 imageUris.add(uri);
969 }
970 }
971
972 if (imageUris.size() == 0) {
973 Log.i(TAG, "Attempted to display image preview area with zero"
974 + " available images detected in EXTRA_STREAM list");
Matt Pietal46d828c2019-02-05 08:07:07 -0500975 contentPreviewLayout.setVisibility(View.GONE);
Matt Pietal1ef88002019-03-13 10:43:18 -0400976 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500977 }
978
Matt Pietale7cacab2019-05-23 07:21:36 -0400979 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500980
981 if (imageUris.size() == 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -0400982 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
983 imageUris.get(1), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500984 } else if (imageUris.size() > 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -0400985 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
986 imageUris.get(1), 0);
987 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
988 imageUris.get(2), imageUris.size() - 3);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500989 }
990 }
Matt Pietal1ef88002019-03-13 10:43:18 -0400991
992 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500993 }
994
Matt Pietal46d828c2019-02-05 08:07:07 -0500995 private static class FileInfo {
996 public final String name;
997 public final boolean hasThumbnail;
998
999 FileInfo(String name, boolean hasThumbnail) {
1000 this.name = name;
1001 this.hasThumbnail = hasThumbnail;
1002 }
1003 }
1004
Matt Pietalf38e9d22019-02-15 10:01:03 -05001005 /**
1006 * Wrapping the ContentResolver call to expose for easier mocking,
1007 * and to avoid mocking Android core classes.
1008 */
1009 @VisibleForTesting
1010 public Cursor queryResolver(ContentResolver resolver, Uri uri) {
1011 return resolver.query(uri, null, null, null, null);
1012 }
1013
Matt Pietal46d828c2019-02-05 08:07:07 -05001014 private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
1015 String fileName = null;
1016 boolean hasThumbnail = false;
Matt Pietal3087bca2019-02-14 12:19:16 -05001017
Matt Pietalf38e9d22019-02-15 10:01:03 -05001018 try (Cursor cursor = queryResolver(resolver, uri)) {
1019 if (cursor != null && cursor.getCount() > 0) {
1020 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
1021 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
1022 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
1023
1024 cursor.moveToFirst();
1025 if (nameIndex != -1) {
1026 fileName = cursor.getString(nameIndex);
1027 } else if (titleIndex != -1) {
1028 fileName = cursor.getString(titleIndex);
1029 }
1030
1031 if (flagsIndex != -1) {
1032 hasThumbnail = (cursor.getInt(flagsIndex)
1033 & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
1034 }
1035 }
Matt Pietal73a873f2019-03-15 08:46:20 -04001036 } catch (SecurityException | NullPointerException e) {
Matt Pietal62532e52019-05-07 09:51:37 -04001037 logContentPreviewWarning(uri);
Matt Pietal3087bca2019-02-14 12:19:16 -05001038 }
1039
Matt Pietal46d828c2019-02-05 08:07:07 -05001040 if (TextUtils.isEmpty(fileName)) {
1041 fileName = uri.getPath();
1042 int index = fileName.lastIndexOf('/');
1043 if (index != -1) {
1044 fileName = fileName.substring(index + 1);
1045 }
1046 }
1047
1048 return new FileInfo(fileName, hasThumbnail);
1049 }
1050
Matt Pietal62532e52019-05-07 09:51:37 -04001051 private void logContentPreviewWarning(Uri uri) {
1052 // The ContentResolver already logs the exception. Log something more informative.
1053 Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
1054 + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
1055 + "and set your Intent's clipData and flags in accordance with that method's "
1056 + "documentation");
1057 }
1058
Matt Pietal1ef88002019-03-13 10:43:18 -04001059 private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001060 ViewGroup parent) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001061
Matt Pietale7cacab2019-05-23 07:21:36 -04001062 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1063 R.layout.chooser_grid_preview_file, parent, false);
Matt Pietal46d828c2019-02-05 08:07:07 -05001064
1065 // TODO(b/120417119): Disable file copy until after moving to sysui,
1066 // due to permissions issues
Matt Pietal1ef88002019-03-13 10:43:18 -04001067 contentPreviewLayout.findViewById(R.id.file_copy_button).setVisibility(View.GONE);
Matt Pietal46d828c2019-02-05 08:07:07 -05001068
Matt Pietal3087bca2019-02-14 12:19:16 -05001069 String action = targetIntent.getAction();
1070 if (Intent.ACTION_SEND.equals(action)) {
1071 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietal1ef88002019-03-13 10:43:18 -04001072 loadFileUriIntoView(uri, contentPreviewLayout);
Matt Pietal3087bca2019-02-14 12:19:16 -05001073 } else {
1074 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1075 int uriCount = uris.size();
Matt Pietal46d828c2019-02-05 08:07:07 -05001076
Matt Pietal3087bca2019-02-14 12:19:16 -05001077 if (uriCount == 0) {
1078 contentPreviewLayout.setVisibility(View.GONE);
1079 Log.i(TAG,
1080 "Appears to be no uris available in EXTRA_STREAM, removing "
1081 + "preview area");
Matt Pietal1ef88002019-03-13 10:43:18 -04001082 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001083 } else if (uriCount == 1) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001084 loadFileUriIntoView(uris.get(0), contentPreviewLayout);
Matt Pietal46d828c2019-02-05 08:07:07 -05001085 } else {
Matt Pietal3087bca2019-02-14 12:19:16 -05001086 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
1087 int remUriCount = uriCount - 1;
Matt Pietalacabc572019-02-14 11:02:05 -05001088 String fileName = getResources().getQuantityString(R.plurals.file_count,
Matt Pietal3087bca2019-02-14 12:19:16 -05001089 remUriCount, fileInfo.name, remUriCount);
Matt Pietalacabc572019-02-14 11:02:05 -05001090
Matt Pietal1ef88002019-03-13 10:43:18 -04001091 TextView fileNameView = contentPreviewLayout.findViewById(
1092 R.id.content_preview_filename);
Matt Pietalacabc572019-02-14 11:02:05 -05001093 fileNameView.setText(fileName);
Matt Pietal3087bca2019-02-14 12:19:16 -05001094
Matt Pietale7cacab2019-05-23 07:21:36 -04001095 View thumbnailView = contentPreviewLayout.findViewById(
1096 R.id.content_preview_file_thumbnail);
1097 thumbnailView.setVisibility(View.GONE);
1098
Matt Pietal1ef88002019-03-13 10:43:18 -04001099 ImageView fileIconView = contentPreviewLayout.findViewById(
1100 R.id.content_preview_file_icon);
Matt Pietal46d828c2019-02-05 08:07:07 -05001101 fileIconView.setVisibility(View.VISIBLE);
Matt Pietalacabc572019-02-14 11:02:05 -05001102 fileIconView.setImageResource(R.drawable.ic_file_copy);
Matt Pietal46d828c2019-02-05 08:07:07 -05001103 }
Matt Pietal3087bca2019-02-14 12:19:16 -05001104 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001105
1106 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001107 }
1108
Matt Pietale7cacab2019-05-23 07:21:36 -04001109 private void loadFileUriIntoView(final Uri uri, final View parent) {
Matt Pietal3087bca2019-02-14 12:19:16 -05001110 FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
1111
Matt Pietal1ef88002019-03-13 10:43:18 -04001112 TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
Matt Pietal3087bca2019-02-14 12:19:16 -05001113 fileNameView.setText(fileInfo.name);
1114
1115 if (fileInfo.hasThumbnail) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001116 mPreviewCoord = new ContentPreviewCoordinator(parent, false);
1117 mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
Matt Pietal3087bca2019-02-14 12:19:16 -05001118 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -04001119 View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
1120 thumbnailView.setVisibility(View.GONE);
1121
Matt Pietal1ef88002019-03-13 10:43:18 -04001122 ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
Matt Pietal3087bca2019-02-14 12:19:16 -05001123 fileIconView.setVisibility(View.VISIBLE);
Matt Pietal832cdbf2019-04-05 13:20:31 -04001124 fileIconView.setImageResource(R.drawable.chooser_file_generic);
Matt Pietal46d828c2019-02-05 08:07:07 -05001125 }
Matt Pietal0ea391b2019-01-30 10:44:15 -05001126 }
1127
Matt Pietal0ea391b2019-01-30 10:44:15 -05001128 @VisibleForTesting
1129 protected boolean isImageType(String mimeType) {
1130 return mimeType != null && mimeType.startsWith("image/");
1131 }
1132
1133 @ContentPreviewType
1134 private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
1135 if (uri == null) {
1136 return CONTENT_PREVIEW_TEXT;
1137 }
1138
1139 String mimeType = resolver.getType(uri);
1140 return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
1141 }
1142
1143 /**
1144 * In {@link android.content.Intent#getType}, the app may specify a very general
1145 * mime-type that broadly covers all data being shared, such as {@literal *}/*
1146 * when sending an image and text. We therefore should inspect each item for the
1147 * the preferred type, in order of IMAGE, FILE, TEXT.
1148 */
1149 @ContentPreviewType
1150 private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
1151 String action = targetIntent.getAction();
1152 if (Intent.ACTION_SEND.equals(action)) {
1153 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1154 return findPreferredContentPreview(uri, resolver);
1155 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1156 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1157 if (uris == null || uris.isEmpty()) {
1158 return CONTENT_PREVIEW_TEXT;
1159 }
1160
1161 for (Uri uri : uris) {
Matt Pietal832cdbf2019-04-05 13:20:31 -04001162 // Defaulting to file preview when there are mixed image/file types is
1163 // preferable, as it shows the user the correct number of items being shared
1164 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_FILE) {
1165 return CONTENT_PREVIEW_FILE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001166 }
1167 }
1168
Matt Pietal832cdbf2019-04-05 13:20:31 -04001169 return CONTENT_PREVIEW_IMAGE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001170 }
1171
1172 return CONTENT_PREVIEW_TEXT;
1173 }
1174
Mike Digman849a9d12019-04-29 11:20:48 -07001175 private int getNumSheetExpansions() {
1176 return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0);
1177 }
1178
1179 private void incrementNumSheetExpansions() {
1180 getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS,
1181 getNumSheetExpansions() + 1).apply();
1182 }
1183
Adam Powell0b3c1122014-10-09 12:50:14 -07001184 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07001185 protected void onDestroy() {
1186 super.onDestroy();
1187 if (mRefinementResultReceiver != null) {
1188 mRefinementResultReceiver.destroy();
1189 mRefinementResultReceiver = null;
1190 }
Adam Powell9761ab22015-09-08 17:01:49 -07001191 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -04001192 mChooserHandler.removeAllMessages();
Matt Pietale7cacab2019-05-23 07:21:36 -04001193
1194 if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
1195
George Hodulik145b3a52019-03-27 11:18:43 -07001196 if (mAppPredictor != null) {
George Hodulik69d4a082019-01-18 11:27:03 -08001197 mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
1198 mAppPredictor.destroy();
1199 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001200 }
1201
arangelovb0802dc2019-10-18 18:03:44 +01001202 @Override // ResolverListCommunicator
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001203 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
1204 Intent result = defIntent;
Adam Powelle49d9392014-07-17 18:45:19 -07001205 if (mReplacementExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001206 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
Adam Powelle49d9392014-07-17 18:45:19 -07001207 if (replExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001208 result = new Intent(defIntent);
Adam Powelle49d9392014-07-17 18:45:19 -07001209 result.putExtras(replExtras);
Adam Powelle49d9392014-07-17 18:45:19 -07001210 }
1211 }
Nicolas Prevot741abfc2015-08-11 12:03:51 +01001212 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001213 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
1214 result = Intent.createChooser(result,
1215 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
Hakan Seyalioglu7317e8a2016-12-12 16:15:38 -08001216
1217 // Don't auto-launch single intents if the intent is being forwarded. This is done
1218 // because automatically launching a resolving application as a response to the user
1219 // action of switching accounts is pretty unexpected.
1220 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001221 }
1222 return result;
Adam Powelle49d9392014-07-17 18:45:19 -07001223 }
1224
Adam Powell0b3c1122014-10-09 12:50:14 -07001225 @Override
Adam Powell23882512016-01-29 10:21:00 -08001226 public void onActivityStarted(TargetInfo cti) {
Adam Powell0b3c1122014-10-09 12:50:14 -07001227 if (mChosenComponentSender != null) {
Adam Powell24428412015-04-01 17:19:56 -07001228 final ComponentName target = cti.getResolvedComponentName();
Adam Powell0b3c1122014-10-09 12:50:14 -07001229 if (target != null) {
1230 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
1231 try {
1232 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
1233 } catch (IntentSender.SendIntentException e) {
1234 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
1235 + "the chosen component: " + e);
1236 }
1237 }
1238 }
1239 }
1240
Adam Powell24428412015-04-01 17:19:56 -07001241 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08001242 public void onPrepareAdapterView(ResolverListAdapter adapter, boolean isVisible) {
1243 mRecyclerView = findViewById(R.id.resolver_list);
1244 if (!isVisible) {
1245 mRecyclerView.setVisibility(View.GONE);
1246 return;
1247 }
1248 mRecyclerView.setVisibility(View.VISIBLE);
Adam Powell52c39212016-04-07 15:14:18 -07001249 if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
Matt Pietalfbfa0492019-04-01 11:29:56 -04001250 mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets),
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07001251 TARGET_TYPE_DEFAULT);
Adam Powell52c39212016-04-07 15:14:18 -07001252 }
Zhen Zhangbde7b462019-11-11 11:49:33 -08001253 mChooserGridAdapter = new ChooserGridAdapter(mChooserListAdapter);
1254 GridLayoutManager glm = (GridLayoutManager) mRecyclerView.getLayoutManager();
1255 glm.setSpanCount(mChooserGridAdapter.getMaxTargetsPerRow());
1256 glm.setSpanSizeLookup(
1257 new GridLayoutManager.SpanSizeLookup() {
1258 @Override
1259 public int getSpanSize(int position) {
1260 return mChooserGridAdapter.getItemViewType(position)
1261 == ChooserGridAdapter.VIEW_TYPE_NORMAL
1262 ? SINGLE_CELL_SPAN_SIZE
1263 : glm.getSpanCount();
1264 }
1265 });
Adam Powell7d758002015-05-06 17:49:36 -07001266 }
1267
1268 @Override
arangelovbb572332019-11-13 12:50:08 +00001269 protected boolean postRebuildList(boolean rebuildCompleted) {
arangelovb0802dc2019-10-18 18:03:44 +01001270 mChooserListAdapter = (ChooserListAdapter) mAdapter;
arangelovbb572332019-11-13 12:50:08 +00001271 return postRebuildListInternal(rebuildCompleted);
arangelovb0802dc2019-10-18 18:03:44 +01001272 }
1273
1274 @Override
Adam Powell23882512016-01-29 10:21:00 -08001275 public int getLayoutResource() {
Adam Powell7d758002015-05-06 17:49:36 -07001276 return R.layout.chooser_grid;
Adam Powell24428412015-04-01 17:19:56 -07001277 }
1278
arangelovb0802dc2019-10-18 18:03:44 +01001279 @Override // ResolverListCommunicator
Adam Powell23882512016-01-29 10:21:00 -08001280 public boolean shouldGetActivityMetadata() {
Adam Powell24428412015-04-01 17:19:56 -07001281 return true;
1282 }
1283
Adam Powell9761ab22015-09-08 17:01:49 -07001284 @Override
Ben Lin145b0ca2016-10-14 14:23:40 -07001285 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
Hakan Seyalioglu13405c52017-01-31 19:01:31 -08001286 // Note that this is only safe because the Intent handled by the ChooserActivity is
1287 // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
1288 // method can not be replaced in the ResolverActivity whole hog.
Matt Pietala4b30072019-04-04 13:44:36 -04001289 if (!super.shouldAutoLaunchSingleChoice(target)) {
1290 return false;
1291 }
1292
1293 return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
Ben Lin145b0ca2016-10-14 14:23:40 -07001294 }
1295
1296 @Override
Adam Powell23882512016-01-29 10:21:00 -08001297 public void showTargetDetails(ResolveInfo ri) {
sanryhuang296ca9e2018-03-31 11:17:13 +08001298 if (ri == null) {
1299 return;
1300 }
1301
Adam Powell23882512016-01-29 10:21:00 -08001302 ComponentName name = ri.activityInfo.getComponentName();
Alison Cichowlas1fd47152019-11-14 19:50:55 -05001303 boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
Adam Powell23882512016-01-29 10:21:00 -08001304 ResolverTargetActionsDialogFragment f =
1305 new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
Alison Cichowlas1fd47152019-11-14 19:50:55 -05001306 name, pinned);
Adam Powell23882512016-01-29 10:21:00 -08001307 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1308 }
1309
Adam Powelle49d9392014-07-17 18:45:19 -07001310 private void modifyTargetIntent(Intent in) {
Matt Pietal95574b02019-03-13 08:12:25 -04001311 if (isSendAction(in)) {
Adam Powelle49d9392014-07-17 18:45:19 -07001312 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
Dianne Hackborn13420f22014-07-18 15:43:56 -07001313 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Adam Powelle49d9392014-07-17 18:45:19 -07001314 }
1315 }
Adam Powell24428412015-04-01 17:19:56 -07001316
Adam Powell2ed547e2015-04-29 18:45:04 -07001317 @Override
1318 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
1319 if (mRefinementIntentSender != null) {
1320 final Intent fillIn = new Intent();
1321 final List<Intent> sourceIntents = target.getAllSourceIntents();
1322 if (!sourceIntents.isEmpty()) {
1323 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
1324 if (sourceIntents.size() > 1) {
1325 final Intent[] alts = new Intent[sourceIntents.size() - 1];
1326 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
1327 alts[i - 1] = sourceIntents.get(i);
1328 }
1329 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
1330 }
1331 if (mRefinementResultReceiver != null) {
1332 mRefinementResultReceiver.destroy();
1333 }
1334 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
1335 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
1336 mRefinementResultReceiver);
1337 try {
1338 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
1339 return false;
1340 } catch (SendIntentException e) {
1341 Log.e(TAG, "Refinement IntentSender failed to send", e);
1342 }
1343 }
1344 }
Kang Li9fa2a2c2017-01-06 13:33:24 -08001345 updateModelAndChooserCounts(target);
Adam Powell2ed547e2015-04-29 18:45:04 -07001346 return super.onTargetSelected(target, alwaysCheck);
1347 }
1348
Adam Powell98b7f892015-06-19 12:38:45 -07001349 @Override
Adam Powell23882512016-01-29 10:21:00 -08001350 public void startSelected(int which, boolean always, boolean filtered) {
Matt Pietala4b30072019-04-04 13:44:36 -04001351 TargetInfo targetInfo = mChooserListAdapter.targetInfoForPosition(which, filtered);
1352 if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
1353 return;
1354 }
1355
Kang Li9082f5b2016-12-02 10:56:21 -08001356 final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
Adam Powell98b7f892015-06-19 12:38:45 -07001357 super.startSelected(which, always, filtered);
1358
arangelovb0802dc2019-10-18 18:03:44 +01001359 if (mChooserListAdapter.getCount() > 0) {
Adam Powell98b7f892015-06-19 12:38:45 -07001360 // Log the index of which type of target the user picked.
1361 // Lower values mean the ranking was better.
1362 int cat = 0;
1363 int value = which;
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001364 int directTargetAlsoRanked = -1;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001365 int numCallerProvided = 0;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001366 HashedStringCache.HashResult directTargetHashed = null;
Adam Powell98b7f892015-06-19 12:38:45 -07001367 switch (mChooserListAdapter.getPositionTargetType(which)) {
Adam Powell98b7f892015-06-19 12:38:45 -07001368 case ChooserListAdapter.TARGET_SERVICE:
Chris Wrenf6e9228b2016-01-26 18:04:35 -05001369 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001370 // Log the package name + target name to answer the question if most users
1371 // share to mostly the same person or to a bunch of different people.
1372 ChooserTarget target =
arangelovb0802dc2019-10-18 18:03:44 +01001373 mChooserListAdapter.getChooserTargetForValue(value);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001374 directTargetHashed = HashedStringCache.getInstance().hashString(
1375 this,
1376 TAG,
1377 target.getComponentName().getPackageName()
1378 + target.getTitle().toString(),
1379 mMaxHashSaltDays);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001380 directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001381
1382 if (mCallerChooserTargets != null) {
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001383 numCallerProvided = mCallerChooserTargets.length;
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001384 }
Adam Powell98b7f892015-06-19 12:38:45 -07001385 break;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001386 case ChooserListAdapter.TARGET_CALLER:
Adam Powell98b7f892015-06-19 12:38:45 -07001387 case ChooserListAdapter.TARGET_STANDARD:
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001388 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
1389 value -= mChooserListAdapter.getSelectableServiceTargetCount();
1390 numCallerProvided = mChooserListAdapter.getCallerTargetCount();
Adam Powell98b7f892015-06-19 12:38:45 -07001391 break;
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04001392 case ChooserListAdapter.TARGET_STANDARD_AZ:
1393 // A-Z targets are unranked standard targets; we use -1 to mark that they
1394 // are from the alphabetical pool.
1395 value = -1;
1396 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
1397 break;
Adam Powell98b7f892015-06-19 12:38:45 -07001398 }
1399
1400 if (cat != 0) {
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001401 LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
1402 if (directTargetHashed != null) {
1403 targetLogMaker.addTaggedData(
1404 MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
1405 targetLogMaker.addTaggedData(
1406 MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
1407 directTargetHashed.saltGeneration);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001408 targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
1409 directTargetAlsoRanked);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001410 }
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001411 targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED,
1412 numCallerProvided);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001413 getMetricsLogger().write(targetLogMaker);
Adam Powell98b7f892015-06-19 12:38:45 -07001414 }
Kang Li9082f5b2016-12-02 10:56:21 -08001415
1416 if (mIsSuccessfullySelected) {
1417 if (DEBUG) {
1418 Log.d(TAG, "User Selection Time Cost is " + selectionCost);
1419 Log.d(TAG, "position of selected app/service/caller is " +
1420 Integer.toString(value));
1421 }
1422 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
1423 (int) selectionCost);
1424 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
1425 }
Adam Powell98b7f892015-06-19 12:38:45 -07001426 }
1427 }
1428
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001429 private int getRankedPosition(SelectableTargetInfo targetInfo) {
1430 String targetPackageName =
1431 targetInfo.getChooserTarget().getComponentName().getPackageName();
1432 int maxRankedResults = Math.min(mChooserListAdapter.mDisplayList.size(),
1433 MAX_LOG_RANK_POSITION);
1434
1435 for (int i = 0; i < maxRankedResults; i++) {
1436 if (mChooserListAdapter.mDisplayList.get(i)
1437 .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
1438 return i;
1439 }
1440 }
1441 return -1;
1442 }
1443
Adam Powell24428412015-04-01 17:19:56 -07001444 void queryTargetServices(ChooserListAdapter adapter) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001445 mQueriedTargetServicesTimeMs = System.currentTimeMillis();
1446
Adam Powell24428412015-04-01 17:19:56 -07001447 final PackageManager pm = getPackageManager();
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001448 ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
Adam Powell24428412015-04-01 17:19:56 -07001449 int targetsToQuery = 0;
Matt Pietalab73a882019-06-05 07:04:55 -04001450
Adam Powell24428412015-04-01 17:19:56 -07001451 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
1452 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
Adam Powell3a09c522015-10-21 13:21:28 -07001453 if (adapter.getScore(dri) == 0) {
1454 // A score of 0 means the app hasn't been used in some time;
1455 // don't query it as it's not likely to be relevant.
1456 continue;
1457 }
Adam Powell24428412015-04-01 17:19:56 -07001458 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
arangelovb0802dc2019-10-18 18:03:44 +01001459 if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001460 && sm.hasShareTargets(ai.packageName)) {
1461 // Share targets will be queried from ShortcutManager
1462 continue;
1463 }
Adam Powell24428412015-04-01 17:19:56 -07001464 final Bundle md = ai.metaData;
1465 final String serviceName = md != null ? convertServiceName(ai.packageName,
1466 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
1467 if (serviceName != null) {
1468 final ComponentName serviceComponent = new ComponentName(
1469 ai.packageName, serviceName);
Matt Pietalab73a882019-06-05 07:04:55 -04001470
1471 if (mServicesRequested.contains(serviceComponent)) {
1472 continue;
1473 }
1474 mServicesRequested.add(serviceComponent);
1475
Adam Powell24428412015-04-01 17:19:56 -07001476 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
1477 .setComponent(serviceComponent);
1478
1479 if (DEBUG) {
1480 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
1481 }
1482
1483 try {
1484 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
1485 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
1486 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
1487 + " permission " + ChooserTargetService.BIND_PERMISSION
1488 + " - this service will not be queried for ChooserTargets."
1489 + " add android:permission=\""
1490 + ChooserTargetService.BIND_PERMISSION + "\""
1491 + " to the <service> tag for " + serviceComponent
1492 + " in the manifest.");
1493 continue;
1494 }
1495 } catch (NameNotFoundException e) {
Adam Powell52c39212016-04-07 15:14:18 -07001496 Log.e(TAG, "Could not look up service " + serviceComponent
1497 + "; component name not found");
Adam Powell24428412015-04-01 17:19:56 -07001498 continue;
1499 }
1500
Adam Powell9761ab22015-09-08 17:01:49 -07001501 final ChooserTargetServiceConnection conn =
1502 new ChooserTargetServiceConnection(this, dri);
Adam Powell52c39212016-04-07 15:14:18 -07001503
1504 // Explicitly specify Process.myUserHandle instead of calling bindService
1505 // to avoid the warning from calling from the system process without an explicit
1506 // user handle
1507 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
1508 Process.myUserHandle())) {
Adam Powell24428412015-04-01 17:19:56 -07001509 if (DEBUG) {
1510 Log.d(TAG, "Binding service connection for target " + dri
1511 + " intent " + serviceIntent);
1512 }
1513 mServiceConnections.add(conn);
1514 targetsToQuery++;
1515 }
1516 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001517 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
Matt Pietal26038402019-01-08 07:29:34 -05001518 if (DEBUG) {
1519 Log.d(TAG, "queryTargets hit query target limit "
1520 + QUERY_TARGET_SERVICE_LIMIT);
1521 }
Adam Powell24428412015-04-01 17:19:56 -07001522 break;
1523 }
1524 }
1525
Matt Pietalab73a882019-06-05 07:04:55 -04001526 mChooserHandler.restartServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -07001527 }
1528
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001529 private IntentFilter getTargetIntentFilter() {
1530 try {
1531 final Intent intent = getTargetIntent();
1532 String dataString = intent.getDataString();
1533 if (TextUtils.isEmpty(dataString)) {
1534 dataString = intent.getType();
1535 }
1536 return new IntentFilter(intent.getAction(), dataString);
1537 } catch (Exception e) {
1538 Log.e(TAG, "failed to get target intent filter " + e);
1539 return null;
1540 }
1541 }
1542
George Hodulik69d4a082019-01-18 11:27:03 -08001543 private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001544 // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
1545 // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
George Hodulik69d4a082019-01-18 11:27:03 -08001546 List<DisplayResolveInfo> driList = new ArrayList<>();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001547 int targetsToQuery = 0;
1548 for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
1549 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
1550 if (adapter.getScore(dri) == 0) {
1551 // A score of 0 means the app hasn't been used in some time;
1552 // don't query it as it's not likely to be relevant.
1553 continue;
1554 }
1555 driList.add(dri);
1556 targetsToQuery++;
1557 // TODO(b/121287224): Do we need this here? (similar to queryTargetServices)
1558 if (targetsToQuery >= SHARE_TARGET_QUERY_PACKAGE_LIMIT) {
1559 if (DEBUG) {
1560 Log.d(TAG, "queryTargets hit query target limit "
1561 + SHARE_TARGET_QUERY_PACKAGE_LIMIT);
1562 }
1563 break;
1564 }
1565 }
George Hodulik69d4a082019-01-18 11:27:03 -08001566 return driList;
1567 }
1568
George Hodulik3f399f22019-04-26 16:17:54 -07001569 private void queryDirectShareTargets(
1570 ChooserListAdapter adapter, boolean skipAppPredictionService) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001571 mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
George Hodulik3f399f22019-04-26 16:17:54 -07001572 if (!skipAppPredictionService) {
1573 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
1574 if (appPredictor != null) {
1575 appPredictor.requestPredictionUpdate();
1576 return;
1577 }
George Hodulik69d4a082019-01-18 11:27:03 -08001578 }
George Hodulik145b3a52019-03-27 11:18:43 -07001579 // Default to just querying ShortcutManager if AppPredictor not present.
George Hodulik69d4a082019-01-18 11:27:03 -08001580 final IntentFilter filter = getTargetIntentFilter();
1581 if (filter == null) {
1582 return;
1583 }
1584 final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001585
1586 AsyncTask.execute(() -> {
1587 ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
1588 List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
George Hodulikaa5238c2019-04-18 14:17:51 -07001589 sendShareShortcutInfoList(resultList, driList, null);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001590 });
1591 }
1592
George Hodulik69d4a082019-01-18 11:27:03 -08001593 private void sendShareShortcutInfoList(
1594 List<ShortcutManager.ShareShortcutInfo> resultList,
George Hodulikaa5238c2019-04-18 14:17:51 -07001595 List<DisplayResolveInfo> driList,
1596 @Nullable List<AppTarget> appTargets) {
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001597 if (appTargets != null && appTargets.size() != resultList.size()) {
1598 throw new RuntimeException("resultList and appTargets must have the same size."
1599 + " resultList.size()=" + resultList.size()
1600 + " appTargets.size()=" + appTargets.size());
1601 }
1602
1603 for (int i = resultList.size() - 1; i >= 0; i--) {
1604 final String packageName = resultList.get(i).getTargetComponent().getPackageName();
1605 if (!isPackageEnabled(packageName)) {
1606 resultList.remove(i);
1607 if (appTargets != null) {
1608 appTargets.remove(i);
1609 }
1610 }
1611 }
1612
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07001613 // If |appTargets| is not null, results are from AppPredictionService and already sorted.
1614 final int shortcutType = (appTargets == null ? TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER :
1615 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
1616
George Hodulik69d4a082019-01-18 11:27:03 -08001617 // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
1618 // for direct share targets. After ShareSheet is refactored we should use the
1619 // ShareShortcutInfos directly.
1620 boolean resultMessageSent = false;
1621 for (int i = 0; i < driList.size(); i++) {
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001622 List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
George Hodulik69d4a082019-01-18 11:27:03 -08001623 for (int j = 0; j < resultList.size(); j++) {
1624 if (driList.get(i).getResolvedComponentName().equals(
1625 resultList.get(j).getTargetComponent())) {
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001626 matchingShortcuts.add(resultList.get(j));
George Hodulik69d4a082019-01-18 11:27:03 -08001627 }
1628 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001629 if (matchingShortcuts.isEmpty()) {
George Hodulik69d4a082019-01-18 11:27:03 -08001630 continue;
1631 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001632 List<ChooserTarget> chooserTargets = convertToChooserTarget(
1633 matchingShortcuts, resultList, appTargets, shortcutType);
1634
George Hodulik69d4a082019-01-18 11:27:03 -08001635 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04001636 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
George Hodulik69d4a082019-01-18 11:27:03 -08001637 msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07001638 msg.arg1 = shortcutType;
George Hodulik69d4a082019-01-18 11:27:03 -08001639 mChooserHandler.sendMessage(msg);
1640 resultMessageSent = true;
1641 }
1642
1643 if (resultMessageSent) {
George Hodulik145b3a52019-03-27 11:18:43 -07001644 sendShortcutManagerShareTargetResultCompleted();
George Hodulik69d4a082019-01-18 11:27:03 -08001645 }
1646 }
1647
George Hodulik145b3a52019-03-27 11:18:43 -07001648 private void sendShortcutManagerShareTargetResultCompleted() {
1649 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04001650 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
George Hodulik145b3a52019-03-27 11:18:43 -07001651 mChooserHandler.sendMessage(msg);
1652 }
1653
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001654 private boolean isPackageEnabled(String packageName) {
1655 if (TextUtils.isEmpty(packageName)) {
1656 return false;
1657 }
1658 ApplicationInfo appInfo;
1659 try {
1660 appInfo = getPackageManager().getApplicationInfo(packageName, 0);
1661 } catch (NameNotFoundException e) {
1662 return false;
1663 }
1664
1665 if (appInfo != null && appInfo.enabled
1666 && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
1667 return true;
1668 }
1669 return false;
1670 }
1671
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001672 /**
1673 * Converts a list of ShareShortcutInfos to ChooserTargets.
1674 * @param matchingShortcuts List of shortcuts, all from the same package, that match the current
1675 * share intent filter.
1676 * @param allShortcuts List of all the shortcuts from all the packages on the device that are
1677 * returned for the current sharing action.
1678 * @param allAppTargets List of AppTargets. Null if the results are not from prediction service.
1679 * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or
1680 * TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
1681 * @return A list of ChooserTargets sorted by score in descending order.
1682 */
1683 @VisibleForTesting
1684 @NonNull
1685 public List<ChooserTarget> convertToChooserTarget(
1686 @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts,
1687 @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts,
1688 @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) {
1689 // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted
1690 // list instead of the actual rank value when converting a rank to a score.
1691 List<Integer> scoreList = new ArrayList<>();
1692 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
1693 for (int i = 0; i < matchingShortcuts.size(); i++) {
1694 int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank();
1695 if (!scoreList.contains(shortcutRank)) {
1696 scoreList.add(shortcutRank);
1697 }
1698 }
1699 Collections.sort(scoreList);
1700 }
1701
1702 List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size());
1703 for (int i = 0; i < matchingShortcuts.size(); i++) {
1704 ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo();
1705 int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i));
1706
1707 float score;
1708 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
1709 // Incoming results are ordered. Create a score based on index in the original list.
1710 score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f);
1711 } else {
1712 // Create a score based on the rank of the shortcut.
1713 int rankIndex = scoreList.indexOf(shortcutInfo.getRank());
1714 score = Math.max(1.0f - (0.01f * rankIndex), 0.0f);
1715 }
1716
1717 Bundle extras = new Bundle();
1718 extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
1719 ChooserTarget chooserTarget = new ChooserTarget(shortcutInfo.getShortLabel(),
1720 null, // Icon will be loaded later if this target is selected to be shown.
1721 score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);
1722
1723 chooserTargetList.add(chooserTarget);
1724 if (mDirectShareAppTargetCache != null && allAppTargets != null) {
1725 mDirectShareAppTargetCache.put(chooserTarget,
1726 allAppTargets.get(indexInAllShortcuts));
1727 }
1728 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001729 // Sort ChooserTargets by score in descending order
1730 Comparator<ChooserTarget> byScore =
1731 (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore());
1732 Collections.sort(chooserTargetList, byScore);
1733 return chooserTargetList;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001734 }
1735
Adam Powell24428412015-04-01 17:19:56 -07001736 private String convertServiceName(String packageName, String serviceName) {
1737 if (TextUtils.isEmpty(serviceName)) {
1738 return null;
1739 }
1740
1741 final String fullName;
1742 if (serviceName.startsWith(".")) {
1743 // Relative to the app package. Prepend the app package name.
1744 fullName = packageName + serviceName;
1745 } else if (serviceName.indexOf('.') >= 0) {
1746 // Fully qualified package name.
1747 fullName = serviceName;
1748 } else {
1749 fullName = null;
1750 }
1751 return fullName;
1752 }
1753
1754 void unbindRemainingServices() {
1755 if (DEBUG) {
1756 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
1757 }
1758 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
1759 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
1760 if (DEBUG) Log.d(TAG, "unbinding " + conn);
1761 unbindService(conn);
Adam Powell9761ab22015-09-08 17:01:49 -07001762 conn.destroy();
Adam Powell24428412015-04-01 17:19:56 -07001763 }
Matt Pietalab73a882019-06-05 07:04:55 -04001764 mServicesRequested.clear();
Adam Powell24428412015-04-01 17:19:56 -07001765 mServiceConnections.clear();
Adam Powell24428412015-04-01 17:19:56 -07001766 }
1767
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001768 private void logDirectShareTargetReceived(int logCategory) {
1769 final long queryTime =
1770 logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
1771 ? mQueriedSharingShortcutsTimeMs : mQueriedTargetServicesTimeMs;
1772 final int apiLatency = (int) (System.currentTimeMillis() - queryTime);
1773 getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
1774 }
1775
Kang Li9fa2a2c2017-01-06 13:33:24 -08001776 void updateModelAndChooserCounts(TargetInfo info) {
Kang Li53b43142016-11-14 14:38:25 -08001777 if (info != null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001778 sendClickToAppPredictor(info);
Kang Li53b43142016-11-14 14:38:25 -08001779 final ResolveInfo ri = info.getResolveInfo();
Kang Li64b018e2017-01-05 17:30:06 -08001780 Intent targetIntent = getTargetIntent();
1781 if (ri != null && ri.activityInfo != null && targetIntent != null) {
Kang Li0cef910d2017-01-05 09:14:36 -08001782 if (mAdapter != null) {
1783 mAdapter.updateModel(info.getResolvedComponentName());
Kang Li9fa2a2c2017-01-06 13:33:24 -08001784 mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
1785 targetIntent.getAction());
Kang Li0cef910d2017-01-05 09:14:36 -08001786 }
Kang Li53b43142016-11-14 14:38:25 -08001787 if (DEBUG) {
Kang Li64b018e2017-01-05 17:30:06 -08001788 Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
Kang Li64b018e2017-01-05 17:30:06 -08001789 Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
Kang Li53b43142016-11-14 14:38:25 -08001790 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001791 } else if (DEBUG) {
Kang Li53b43142016-11-14 14:38:25 -08001792 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
1793 }
1794 }
Kang Li9082f5b2016-12-02 10:56:21 -08001795 mIsSuccessfullySelected = true;
Kang Li53b43142016-11-14 14:38:25 -08001796 }
1797
George Hodulikf2b0d342019-01-25 12:43:54 -08001798 private void sendClickToAppPredictor(TargetInfo targetInfo) {
George Hodulikaa5238c2019-04-18 14:17:51 -07001799 AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled();
1800 if (directShareAppPredictor == null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001801 return;
1802 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001803 if (!(targetInfo instanceof ChooserTargetInfo)) {
1804 return;
1805 }
1806 ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
George Hodulikaa5238c2019-04-18 14:17:51 -07001807 AppTarget appTarget = null;
1808 if (mDirectShareAppTargetCache != null) {
1809 appTarget = mDirectShareAppTargetCache.get(chooserTarget);
George Hodulikf2b0d342019-01-25 12:43:54 -08001810 }
George Hodulikaa5238c2019-04-18 14:17:51 -07001811 // This is a direct share click that was provided by the APS
1812 if (appTarget != null) {
1813 directShareAppPredictor.notifyAppTargetEvent(
1814 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
1815 .setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
1816 .build());
George Hodulikf2b0d342019-01-25 12:43:54 -08001817 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001818 }
1819
George Hodulik145b3a52019-03-27 11:18:43 -07001820 @Nullable
1821 private AppPredictor getAppPredictor() {
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -07001822 if (!mIsAppPredictorComponentAvailable) {
1823 return null;
1824 }
Mehdi Alizadehe870e972019-09-11 17:54:15 -07001825 if (mAppPredictor == null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001826 final IntentFilter filter = getTargetIntentFilter();
1827 Bundle extras = new Bundle();
1828 extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
1829 AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(this)
1830 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
1831 .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
1832 .setExtras(extras)
1833 .build();
1834 AppPredictionManager appPredictionManager
1835 = getSystemService(AppPredictionManager.class);
1836 mAppPredictor = appPredictionManager.createAppPredictionSession(appPredictionContext);
1837 }
1838 return mAppPredictor;
1839 }
1840
1841 /**
1842 * This will return an app predictor if it is enabled for direct share sorting
1843 * and if one exists. Otherwise, it returns null.
1844 */
1845 @Nullable
1846 private AppPredictor getAppPredictorForDirectShareIfEnabled() {
arangelovb0802dc2019-10-18 18:03:44 +01001847 return ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS
1848 && !ActivityManager.isLowRamDeviceStatic() ? getAppPredictor() : null;
George Hodulik145b3a52019-03-27 11:18:43 -07001849 }
1850
George Hodulikc681ce42019-04-12 17:10:31 -07001851 /**
1852 * This will return an app predictor if it is enabled for share activity sorting
1853 * and if one exists. Otherwise, it returns null.
1854 */
1855 @Nullable
1856 private AppPredictor getAppPredictorForShareActivitesIfEnabled() {
1857 return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? getAppPredictor() : null;
1858 }
1859
Adam Powell2ed547e2015-04-29 18:45:04 -07001860 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
1861 if (mRefinementResultReceiver != null) {
1862 mRefinementResultReceiver.destroy();
1863 mRefinementResultReceiver = null;
1864 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001865 if (selectedTarget == null) {
1866 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
1867 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
1868 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
1869 + " cannot match refined source intent " + matchingIntent);
Kang Li53b43142016-11-14 14:38:25 -08001870 } else {
1871 TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
1872 if (super.onTargetSelected(clonedTarget, false)) {
Kang Li9fa2a2c2017-01-06 13:33:24 -08001873 updateModelAndChooserCounts(clonedTarget);
Kang Li53b43142016-11-14 14:38:25 -08001874 finish();
1875 return;
1876 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001877 }
1878 onRefinementCanceled();
1879 }
1880
1881 void onRefinementCanceled() {
1882 if (mRefinementResultReceiver != null) {
1883 mRefinementResultReceiver.destroy();
1884 mRefinementResultReceiver = null;
1885 }
1886 finish();
1887 }
1888
1889 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
1890 final List<Intent> targetIntents = target.getAllSourceIntents();
1891 for (int i = 0, N = targetIntents.size(); i < N; i++) {
1892 final Intent targetIntent = targetIntents.get(i);
1893 if (targetIntent.filterEquals(matchingIntent)) {
1894 return true;
1895 }
1896 }
1897 return false;
1898 }
1899
Adam Powell666d82a2015-07-15 20:14:57 -07001900 void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
1901 if (targets == null) {
1902 return;
1903 }
1904
1905 final PackageManager pm = getPackageManager();
1906 for (int i = targets.size() - 1; i >= 0; i--) {
1907 final ChooserTarget target = targets.get(i);
1908 final ComponentName targetName = target.getComponentName();
1909 if (packageName != null && packageName.equals(targetName.getPackageName())) {
1910 // Anything from the original target's package is fine.
1911 continue;
1912 }
1913
1914 boolean remove;
1915 try {
1916 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
1917 remove = !ai.exported || ai.permission != null;
1918 } catch (NameNotFoundException e) {
1919 Log.e(TAG, "Target " + target + " returned by " + packageName
1920 + " component not found");
1921 remove = true;
1922 }
1923
1924 if (remove) {
1925 targets.remove(i);
1926 }
1927 }
1928 }
1929
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04001930 /**
1931 * Sort intents alphabetically based on display label.
1932 */
arangelovb0802dc2019-10-18 18:03:44 +01001933 static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04001934 Collator mCollator;
1935 AzInfoComparator(Context context) {
1936 mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
1937 }
1938
1939 @Override
arangelovb0802dc2019-10-18 18:03:44 +01001940 public int compare(
1941 DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04001942 return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
1943 }
1944 }
1945
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -05001946 protected MetricsLogger getMetricsLogger() {
1947 if (mMetricsLogger == null) {
1948 mMetricsLogger = new MetricsLogger();
1949 }
1950 return mMetricsLogger;
1951 }
1952
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001953 public class ChooserListController extends ResolverListController {
1954 public ChooserListController(Context context,
1955 PackageManager pm,
1956 Intent targetIntent,
1957 String referrerPackageName,
George Hodulikc681ce42019-04-12 17:10:31 -07001958 int launchedFromUid,
1959 AbstractResolverComparator resolverComparator) {
1960 super(context, pm, targetIntent, referrerPackageName, launchedFromUid,
1961 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001962 }
1963
1964 @Override
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001965 boolean isComponentFiltered(ComponentName name) {
1966 if (mFilteredComponentNames == null) {
1967 return false;
1968 }
1969 for (ComponentName filteredComponentName : mFilteredComponentNames) {
1970 if (name.equals(filteredComponentName)) {
1971 return true;
1972 }
1973 }
1974 return false;
1975 }
Alison Cichowlas1fd47152019-11-14 19:50:55 -05001976
1977 @Override
1978 public boolean isComponentPinned(ComponentName name) {
1979 return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
1980 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001981 }
1982
Adam Powell24428412015-04-01 17:19:56 -07001983 @Override
arangelovb0802dc2019-10-18 18:03:44 +01001984 public ResolverListAdapter createAdapter(Context context, List<Intent> payloadIntents,
1985 Intent[] initialIntents, List<ResolveInfo> rList,
1986 boolean filterLastUsed, boolean useLayoutForBrowsables) {
1987 return new ChooserListAdapter(context, payloadIntents,
1988 initialIntents, rList, filterLastUsed, createListController(),
1989 useLayoutForBrowsables, this, this);
Adam Powell24428412015-04-01 17:19:56 -07001990 }
1991
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001992 @VisibleForTesting
1993 protected ResolverListController createListController() {
George Hodulikc681ce42019-04-12 17:10:31 -07001994 AppPredictor appPredictor = getAppPredictorForShareActivitesIfEnabled();
1995 AbstractResolverComparator resolverComparator;
1996 if (appPredictor != null) {
1997 resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
George Hodulik3f399f22019-04-26 16:17:54 -07001998 getReferrerPackageName(), appPredictor, getUser());
George Hodulikc681ce42019-04-12 17:10:31 -07001999 } else {
2000 resolverComparator =
2001 new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
2002 getReferrerPackageName(), null);
2003 }
2004
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002005 return new ChooserListController(
2006 this,
2007 mPm,
2008 getTargetIntent(),
2009 getReferrerPackageName(),
George Hodulikc681ce42019-04-12 17:10:31 -07002010 mLaunchedFromUid,
2011 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002012 }
2013
Matt Pietal26038402019-01-08 07:29:34 -05002014 @VisibleForTesting
2015 protected Bitmap loadThumbnail(Uri uri, Size size) {
2016 if (uri == null || size == null) {
2017 return null;
2018 }
2019
2020 try {
Matt Pietal46d828c2019-02-05 08:07:07 -05002021 return ImageUtils.loadThumbnail(getContentResolver(), uri, size);
2022 } catch (IOException | NullPointerException | SecurityException ex) {
Matt Pietal62532e52019-05-07 09:51:37 -04002023 logContentPreviewWarning(uri);
Matt Pietal26038402019-01-08 07:29:34 -05002024 }
2025 return null;
2026 }
2027
arangelovb0802dc2019-10-18 18:03:44 +01002028 static final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
2029 public Drawable getDisplayIcon(Context context) {
Mike Digmanac1d88c2019-04-18 15:15:55 -07002030 AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
arangelovb0802dc2019-10-18 18:03:44 +01002031 context.getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
Mike Digmanac1d88c2019-04-18 15:15:55 -07002032 avd.start(); // Start animation after generation
2033 return avd;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002034 }
2035 }
2036
arangelovb0802dc2019-10-18 18:03:44 +01002037 static final class EmptyTargetInfo extends NotSelectableTargetInfo {
2038 public Drawable getDisplayIcon(Context context) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002039 return null;
2040 }
2041 }
2042
Matt Pietal5b648562019-03-12 07:40:26 -04002043 private void handleScroll(View view, int x, int y, int oldx, int oldy) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08002044 if (mChooserGridAdapter != null) {
2045 mChooserGridAdapter.handleScroll(view, y, oldy);
Matt Pietal5b648562019-03-12 07:40:26 -04002046 }
2047 }
2048
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002049 /*
2050 * Need to dynamically adjust how many icons can fit per row before we add them,
2051 * which also means setting the correct offset to initially show the content
2052 * preview area + 2 rows of targets
2053 */
2054 private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
2055 int oldTop, int oldRight, int oldBottom) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08002056 if (mChooserGridAdapter == null || mRecyclerView == null) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002057 return;
2058 }
2059
Matt Pietalab73a882019-06-05 07:04:55 -04002060 final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
Zhen Zhangbde7b462019-11-11 11:49:33 -08002061 if (mChooserGridAdapter.consumeLayoutRequest()
2062 || mChooserGridAdapter.calculateChooserTargetWidth(availableWidth)
2063 || mRecyclerView.getAdapter() == null
Matt Pietalab73a882019-06-05 07:04:55 -04002064 || availableWidth != mCurrAvailableWidth) {
2065 mCurrAvailableWidth = availableWidth;
Zhen Zhangbde7b462019-11-11 11:49:33 -08002066 mRecyclerView.setAdapter(mChooserGridAdapter);
2067 ((GridLayoutManager) mRecyclerView.getLayoutManager())
2068 .setSpanCount(mChooserGridAdapter.getMaxTargetsPerRow());
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002069
2070 getMainThreadHandler().post(() -> {
Zhen Zhangbde7b462019-11-11 11:49:33 -08002071 if (mResolverDrawerLayout == null || mChooserGridAdapter == null) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002072 return;
2073 }
2074
Matt Pietal800136a2019-05-08 07:46:39 -04002075 final int bottomInset = mSystemWindowInsets != null
2076 ? mSystemWindowInsets.bottom : 0;
2077 int offset = bottomInset;
Zhen Zhangbde7b462019-11-11 11:49:33 -08002078 int rowsToShow = mChooserGridAdapter.getContentPreviewRowCount()
2079 + mChooserGridAdapter.getProfileRowCount()
2080 + mChooserGridAdapter.getServiceTargetRowCount()
2081 + mChooserGridAdapter.getCallerAndRankedTargetRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002082
2083 // then this is most likely not a SEND_* action, so check
2084 // the app target count
2085 if (rowsToShow == 0) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08002086 rowsToShow = mChooserGridAdapter.getRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002087 }
2088
2089 // still zero? then use a default height and leave, which
2090 // can happen when there are no targets to show
2091 if (rowsToShow == 0) {
Matt Pietal800136a2019-05-08 07:46:39 -04002092 offset += getResources().getDimensionPixelSize(
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002093 R.dimen.chooser_max_collapsed_height);
2094 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2095 return;
2096 }
2097
Matt Pietal394ebd02019-05-03 07:36:21 -04002098 int directShareHeight = 0;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002099 rowsToShow = Math.min(4, rowsToShow);
Zhen Zhangbde7b462019-11-11 11:49:33 -08002100 for (int i = 0, childCount = mRecyclerView.getChildCount();
2101 i < childCount && rowsToShow > 0; i++) {
2102 View child = mRecyclerView.getChildAt(i);
2103 if (((GridLayoutManager.LayoutParams)
2104 child.getLayoutParams()).getSpanIndex() != 0) {
2105 continue;
2106 }
Matt Pietal394ebd02019-05-03 07:36:21 -04002107 int height = child.getHeight();
2108 offset += height;
2109
Zhen Zhangbde7b462019-11-11 11:49:33 -08002110 if (mChooserGridAdapter.getTargetType(
2111 mRecyclerView.getChildAdapterPosition(child))
2112 == mChooserListAdapter.TARGET_SERVICE) {
Matt Pietal394ebd02019-05-03 07:36:21 -04002113 directShareHeight = height;
2114 }
Zhen Zhangbde7b462019-11-11 11:49:33 -08002115 rowsToShow--;
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002116 }
2117
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002118 boolean isExpandable = getResources().getConfiguration().orientation
2119 == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
arangelovb0802dc2019-10-18 18:03:44 +01002120 if (directShareHeight != 0 && isSendAction(getTargetIntent())
2121 && isExpandable) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002122 // make sure to leave room for direct share 4->8 expansion
Matt Pietal394ebd02019-05-03 07:36:21 -04002123 int requiredExpansionHeight =
2124 (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal800136a2019-05-08 07:46:39 -04002125 int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
Matt Pietal394ebd02019-05-03 07:36:21 -04002126 int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
Matt Pietal800136a2019-05-08 07:46:39 -04002127 - requiredExpansionHeight - topInset - bottomInset;
Matt Pietal394ebd02019-05-03 07:36:21 -04002128
2129 offset = Math.min(offset, minHeight);
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002130 }
2131
Matt Pietal399e8c72019-04-04 15:49:48 -04002132 mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top));
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002133 });
2134 }
2135 }
2136
Adam Powella182e452015-07-06 16:57:56 -07002137 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
2138 @Override
2139 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
2140 // Descending order
Adam Powell77a533f2015-10-16 10:47:32 -07002141 return (int) Math.signum(rhs.getScore() - lhs.getScore());
Adam Powella182e452015-07-06 16:57:56 -07002142 }
2143 }
2144
arangelovb0802dc2019-10-18 18:03:44 +01002145 @Override // ResolverListCommunicator
2146 public void onHandlePackagesChanged() {
2147 mServicesRequested.clear();
2148 mAdapter.notifyDataSetChanged();
2149 super.onHandlePackagesChanged();
2150 }
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002151
arangelovb0802dc2019-10-18 18:03:44 +01002152 @Override // SelectableTargetInfoCommunicator
2153 public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) {
2154 return mChooserListAdapter.makePresentationGetter(info);
2155 }
2156
2157 @Override // SelectableTargetInfoCommunicator
2158 public Intent getReferrerFillInIntent() {
2159 return mReferrerFillInIntent;
2160 }
2161
2162 @Override // ChooserListCommunicator
2163 public int getMaxRankedTargets() {
Zhen Zhangbde7b462019-11-11 11:49:33 -08002164 return mChooserGridAdapter == null
2165 ? ChooserGridAdapter.MAX_TARGETS_PER_ROW_PORTRAIT
2166 : mChooserGridAdapter.getMaxTargetsPerRow();
arangelovb0802dc2019-10-18 18:03:44 +01002167 }
2168
2169 @Override // ChooserListCommunicator
2170 public void sendListViewUpdateMessage() {
2171 mChooserHandler.sendEmptyMessageDelayed(ChooserHandler.LIST_VIEW_UPDATE_MESSAGE,
2172 LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
2173 }
2174
2175 @Override
2176 public void onListRebuilt() {
2177 if (mChooserListAdapter.mDisplayList == null
2178 || mChooserListAdapter.mDisplayList.isEmpty()) {
2179 mChooserListAdapter.notifyDataSetChanged();
2180 } else {
2181 new AsyncTask<Void, Void, Void>() {
2182 @Override
2183 protected Void doInBackground(Void... voids) {
2184 mChooserListAdapter.updateAlphabeticalList();
2185 return null;
2186 }
2187 @Override
2188 protected void onPostExecute(Void aVoid) {
2189 mChooserListAdapter.notifyDataSetChanged();
2190 }
2191 }.execute();
2192 }
2193
2194 // don't support direct share on low ram devices
2195 if (ActivityManager.isLowRamDeviceStatic()) {
2196 return;
2197 }
2198
2199 if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
2200 || ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
2201 if (DEBUG) {
2202 Log.d(TAG, "querying direct share targets from ShortcutManager");
2203 }
2204
2205 queryDirectShareTargets(mChooserListAdapter, false);
2206 }
2207 if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
2208 if (DEBUG) {
2209 Log.d(TAG, "List built querying services");
2210 }
2211
2212 queryTargetServices(mChooserListAdapter);
2213 }
2214 }
2215
2216 @Override // ChooserListCommunicator
2217 public boolean isSendAction(Intent targetIntent) {
Matt Pietal95574b02019-03-13 08:12:25 -04002218 if (targetIntent == null) {
2219 return false;
2220 }
2221
2222 String action = targetIntent.getAction();
2223 if (action == null) {
2224 return false;
2225 }
2226
2227 if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
2228 return true;
2229 }
2230
2231 return false;
2232 }
2233
Zhen Zhangbde7b462019-11-11 11:49:33 -08002234 /**
2235 * Used to bind types of individual item including
2236 * {@link ChooserGridAdapter#VIEW_TYPE_NORMAL},
2237 * {@link ChooserGridAdapter#VIEW_TYPE_CONTENT_PREVIEW},
2238 * {@link ChooserGridAdapter#VIEW_TYPE_PROFILE},
2239 * and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}.
2240 */
2241 final class ItemViewHolder extends RecyclerView.ViewHolder {
2242 ResolverListAdapter.ViewHolder mWrappedViewHolder;
2243 int mListPosition = ChooserListAdapter.NO_POSITION;
2244
2245 ItemViewHolder(View itemView, boolean isClickable) {
2246 super(itemView);
2247 mWrappedViewHolder = new ResolverListAdapter.ViewHolder(itemView);
2248 if (isClickable) {
2249 itemView.setOnClickListener(v -> startSelected(mListPosition,
2250 false/* always */, true/* filterd */));
2251 itemView.setOnLongClickListener(v -> {
2252 showTargetDetails(
2253 mChooserListAdapter.resolveInfoForPosition(
2254 mListPosition, true/* filtered */));
2255 return true;
2256 });
2257 }
2258 }
2259 }
2260
2261 /**
2262 * Adapter for all types of items and targets in ShareSheet.
2263 * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the
2264 * row level by this adapter but not on the item level. Individual targets within the row are
2265 * handled by {@link ChooserListAdapter}
2266 */
2267 final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
Adam Powell7d758002015-05-06 17:49:36 -07002268 private ChooserListAdapter mChooserListAdapter;
2269 private final LayoutInflater mLayoutInflater;
Adam Powell7d758002015-05-06 17:49:36 -07002270
Matt Pietal5b648562019-03-12 07:40:26 -04002271 private DirectShareViewHolder mDirectShareViewHolder;
Matt Pietalab986b52019-04-10 10:14:32 -04002272 private int mChooserTargetWidth = 0;
Mike Digman849a9d12019-04-29 11:20:48 -07002273 private boolean mShowAzLabelIfPoss;
Matt Pietal5b648562019-03-12 07:40:26 -04002274
Matt Pietale7cacab2019-05-23 07:21:36 -04002275 private boolean mHideContentPreview = false;
2276 private boolean mLayoutRequested = false;
2277
Matt Pietal5b648562019-03-12 07:40:26 -04002278 private static final int VIEW_TYPE_DIRECT_SHARE = 0;
2279 private static final int VIEW_TYPE_NORMAL = 1;
Matt Pietal1ef88002019-03-13 10:43:18 -04002280 private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002281 private static final int VIEW_TYPE_PROFILE = 3;
Mike Digmanae730b12019-04-25 11:10:31 -07002282 private static final int VIEW_TYPE_AZ_LABEL = 4;
Zhen Zhangbde7b462019-11-11 11:49:33 -08002283 private static final int VIEW_TYPE_CALLER_AND_RANK = 5;
Matt Pietal5b648562019-03-12 07:40:26 -04002284
Matt Pietalfaedea82019-03-21 10:36:54 -04002285 private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
2286 private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
2287
Mike Digman849a9d12019-04-29 11:20:48 -07002288 private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
2289
Zhen Zhangbde7b462019-11-11 11:49:33 -08002290 ChooserGridAdapter(ChooserListAdapter wrappedAdapter) {
2291 super();
Adam Powell7d758002015-05-06 17:49:36 -07002292 mChooserListAdapter = wrappedAdapter;
2293 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
2294
Mike Digman849a9d12019-04-29 11:20:48 -07002295 mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
2296
Adam Powell7d758002015-05-06 17:49:36 -07002297 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
2298 @Override
2299 public void onChanged() {
2300 super.onChanged();
2301 notifyDataSetChanged();
2302 }
2303
2304 @Override
2305 public void onInvalidated() {
2306 super.onInvalidated();
Zhen Zhangbde7b462019-11-11 11:49:33 -08002307 notifyDataSetChanged();
Adam Powell7d758002015-05-06 17:49:36 -07002308 }
2309 });
2310 }
2311
Matt Pietalfaedea82019-03-21 10:36:54 -04002312 /**
Matt Pietalab986b52019-04-10 10:14:32 -04002313 * Calculate the chooser target width to maximize space per item
Matt Pietalfaedea82019-03-21 10:36:54 -04002314 *
2315 * @param width The new row width to use for recalculation
Matt Pietalab986b52019-04-10 10:14:32 -04002316 * @return true if the view width has changed
Matt Pietalfaedea82019-03-21 10:36:54 -04002317 */
Matt Pietalab986b52019-04-10 10:14:32 -04002318 public boolean calculateChooserTargetWidth(int width) {
Matt Pietalab986b52019-04-10 10:14:32 -04002319 if (width == 0) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002320 return false;
2321 }
2322
Zhen Zhangbde7b462019-11-11 11:49:33 -08002323 int newWidth = width / getMaxTargetsPerRow();
Matt Pietalab986b52019-04-10 10:14:32 -04002324 if (newWidth != mChooserTargetWidth) {
2325 mChooserTargetWidth = newWidth;
Matt Pietalfaedea82019-03-21 10:36:54 -04002326 return true;
2327 }
2328
2329 return false;
2330 }
2331
Matt Pietal5b648562019-03-12 07:40:26 -04002332 private int getMaxTargetsPerRow() {
Matt Pietalfaedea82019-03-21 10:36:54 -04002333 int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002334 if (shouldDisplayLandscape(getResources().getConfiguration().orientation)) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002335 maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
2336 }
2337
Matt Pietalab986b52019-04-10 10:14:32 -04002338 return maxTargets;
Matt Pietal5b648562019-03-12 07:40:26 -04002339 }
2340
Matt Pietale7cacab2019-05-23 07:21:36 -04002341 public void hideContentPreview() {
2342 mHideContentPreview = true;
2343 mLayoutRequested = true;
2344 notifyDataSetChanged();
2345 }
2346
2347 public boolean consumeLayoutRequest() {
2348 boolean oldValue = mLayoutRequested;
2349 mLayoutRequested = false;
2350 return oldValue;
2351 }
2352
Zhen Zhangbde7b462019-11-11 11:49:33 -08002353 public int getRowCount() {
Adam Powell7d758002015-05-06 17:49:36 -07002354 return (int) (
Matt Pietal1ef88002019-03-13 10:43:18 -04002355 getContentPreviewRowCount()
Matt Pietal74c6ed02019-04-18 13:38:46 -04002356 + getProfileRowCount()
Matt Pietal26038402019-01-08 07:29:34 -05002357 + getServiceTargetRowCount()
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002358 + getCallerAndRankedTargetRowCount()
Mike Digmanae730b12019-04-25 11:10:31 -07002359 + getAzLabelRowCount()
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002360 + Math.ceil(
2361 (float) mChooserListAdapter.getAlphaTargetCount()
2362 / getMaxTargetsPerRow())
Adam Powell7d758002015-05-06 17:49:36 -07002363 );
2364 }
2365
Matt Pietal1ef88002019-03-13 10:43:18 -04002366 public int getContentPreviewRowCount() {
2367 if (!isSendAction(getTargetIntent())) {
2368 return 0;
2369 }
2370
Matt Pietale7cacab2019-05-23 07:21:36 -04002371 if (mHideContentPreview || mChooserListAdapter == null
2372 || mChooserListAdapter.getCount() == 0) {
Matt Pietal1ef88002019-03-13 10:43:18 -04002373 return 0;
2374 }
2375
2376 return 1;
2377 }
2378
Matt Pietal74c6ed02019-04-18 13:38:46 -04002379 public int getProfileRowCount() {
2380 return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
2381 }
2382
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002383 public int getCallerAndRankedTargetRowCount() {
Adam Powell63b31692015-09-28 10:45:00 -07002384 return (int) Math.ceil(
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002385 ((float) mChooserListAdapter.getCallerTargetCount()
2386 + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow());
Adam Powell63b31692015-09-28 10:45:00 -07002387 }
2388
Matt Pietal5b648562019-03-12 07:40:26 -04002389 // There can be at most one row in the listview, that is internally
2390 // a ViewGroup with 2 rows
Adam Powell63b31692015-09-28 10:45:00 -07002391 public int getServiceTargetRowCount() {
arangelovb0802dc2019-10-18 18:03:44 +01002392 if (isSendAction(getTargetIntent())
2393 && !ActivityManager.isLowRamDeviceStatic()) {
Matt Pietal95574b02019-03-13 08:12:25 -04002394 return 1;
2395 }
2396 return 0;
Adam Powell63b31692015-09-28 10:45:00 -07002397 }
2398
Mike Digmanae730b12019-04-25 11:10:31 -07002399 public int getAzLabelRowCount() {
2400 // Only show a label if the a-z list is showing
Mike Digman849a9d12019-04-29 11:20:48 -07002401 return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
Mike Digmanae730b12019-04-25 11:10:31 -07002402 }
2403
Adam Powell7d758002015-05-06 17:49:36 -07002404 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08002405 public int getItemCount() {
2406 return (int) (
2407 getContentPreviewRowCount()
2408 + getProfileRowCount()
2409 + getServiceTargetRowCount()
2410 + getCallerAndRankedTargetRowCount()
2411 + getAzLabelRowCount()
2412 + mChooserListAdapter.getAlphaTargetCount()
2413 );
Adam Powell7d758002015-05-06 17:49:36 -07002414 }
2415
2416 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08002417 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
2418 switch (viewType) {
2419 case VIEW_TYPE_CONTENT_PREVIEW:
2420 return new ItemViewHolder(createContentPreviewView(parent), false);
2421 case VIEW_TYPE_PROFILE:
2422 return new ItemViewHolder(createProfileView(parent), false);
2423 case VIEW_TYPE_AZ_LABEL:
2424 return new ItemViewHolder(createAzLabelView(parent), false);
2425 case VIEW_TYPE_NORMAL:
2426 return new ItemViewHolder(mChooserListAdapter.createView(parent), true);
2427 case VIEW_TYPE_DIRECT_SHARE:
2428 case VIEW_TYPE_CALLER_AND_RANK:
2429 return createItemGroupViewHolder(viewType, parent);
2430 default:
2431 // Since we catch all possible viewTypes above, no chance this is being called.
2432 return null;
2433 }
Adam Powell7d758002015-05-06 17:49:36 -07002434 }
2435
2436 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08002437 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Matt Pietal5b648562019-03-12 07:40:26 -04002438 int viewType = getItemViewType(position);
Zhen Zhangbde7b462019-11-11 11:49:33 -08002439 switch (viewType) {
2440 case VIEW_TYPE_DIRECT_SHARE:
2441 case VIEW_TYPE_CALLER_AND_RANK:
2442 bindItemGroupViewHolder(position, (ItemGroupViewHolder) holder);
2443 break;
2444 case VIEW_TYPE_NORMAL:
2445 bindItemViewHolder(position, (ItemViewHolder) holder);
2446 break;
2447 default:
Matt Pietal1ef88002019-03-13 10:43:18 -04002448 }
Adam Powell7d758002015-05-06 17:49:36 -07002449 }
2450
Matt Pietal5b648562019-03-12 07:40:26 -04002451 @Override
2452 public int getItemViewType(int position) {
Mike Digmanae730b12019-04-25 11:10:31 -07002453 int count;
Matt Pietal1ef88002019-03-13 10:43:18 -04002454
Mike Digmanae730b12019-04-25 11:10:31 -07002455 int countSum = (count = getContentPreviewRowCount());
2456 if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002457
Mike Digmanae730b12019-04-25 11:10:31 -07002458 countSum += (count = getProfileRowCount());
2459 if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
Adam Powell63b31692015-09-28 10:45:00 -07002460
Mike Digmanae730b12019-04-25 11:10:31 -07002461 countSum += (count = getServiceTargetRowCount());
2462 if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
2463
2464 countSum += (count = getCallerAndRankedTargetRowCount());
Zhen Zhangbde7b462019-11-11 11:49:33 -08002465 if (count > 0 && position < countSum) return VIEW_TYPE_CALLER_AND_RANK;
Mike Digmanae730b12019-04-25 11:10:31 -07002466
2467 countSum += (count = getAzLabelRowCount());
2468 if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
Matt Pietal5b648562019-03-12 07:40:26 -04002469
2470 return VIEW_TYPE_NORMAL;
2471 }
2472
Zhen Zhangbde7b462019-11-11 11:49:33 -08002473 public int getTargetType(int position) {
2474 return mChooserListAdapter.getPositionTargetType(getListPosition(position));
Matt Pietal1ef88002019-03-13 10:43:18 -04002475 }
2476
Zhen Zhangbde7b462019-11-11 11:49:33 -08002477 private ViewGroup createContentPreviewView(ViewGroup parent) {
Matt Pietal1ef88002019-03-13 10:43:18 -04002478 Intent targetIntent = getTargetIntent();
2479 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
2480
Zhen Zhangbde7b462019-11-11 11:49:33 -08002481 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
Matt Pietal1ef88002019-03-13 10:43:18 -04002482 .setSubtype(previewType));
Matt Pietal1ef88002019-03-13 10:43:18 -04002483
Zhen Zhangbde7b462019-11-11 11:49:33 -08002484 return displayContentPreview(previewType, targetIntent, mLayoutInflater, parent);
Matt Pietal5b648562019-03-12 07:40:26 -04002485 }
2486
Zhen Zhangbde7b462019-11-11 11:49:33 -08002487 private View createProfileView(ViewGroup parent) {
2488 View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false);
Matt Pietal74c6ed02019-04-18 13:38:46 -04002489 profileRow.setBackground(
2490 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
2491 mProfileView = profileRow.findViewById(R.id.profile_button);
2492 mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
arangelovb0802dc2019-10-18 18:03:44 +01002493 updateProfileViewButton();
Matt Pietal74c6ed02019-04-18 13:38:46 -04002494 return profileRow;
2495 }
2496
Mike Digmanae730b12019-04-25 11:10:31 -07002497 private View createAzLabelView(ViewGroup parent) {
2498 return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
2499 }
2500
Zhen Zhangbde7b462019-11-11 11:49:33 -08002501 private ItemGroupViewHolder loadViewsIntoGroup(ItemGroupViewHolder holder) {
Matt Pietal5b648562019-03-12 07:40:26 -04002502 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietalab986b52019-04-10 10:14:32 -04002503 final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
2504 MeasureSpec.EXACTLY);
Matt Pietal5b648562019-03-12 07:40:26 -04002505 int columnCount = holder.getColumnCount();
2506
Mike Digmanba232682019-03-27 14:55:26 -07002507 final boolean isDirectShare = holder instanceof DirectShareViewHolder;
2508
Matt Pietal5b648562019-03-12 07:40:26 -04002509 for (int i = 0; i < columnCount; i++) {
Mike Digmanba232682019-03-27 14:55:26 -07002510 final View v = mChooserListAdapter.createView(holder.getRowByIndex(i));
Adam Powell4eb98712015-10-14 13:10:18 -07002511 final int column = i;
Adam Powell63b31692015-09-28 10:45:00 -07002512 v.setOnClickListener(new OnClickListener() {
2513 @Override
2514 public void onClick(View v) {
Matt Pietal5b648562019-03-12 07:40:26 -04002515 startSelected(holder.getItemIndex(column), false, true);
Adam Powell63b31692015-09-28 10:45:00 -07002516 }
2517 });
2518 v.setOnLongClickListener(new OnLongClickListener() {
2519 @Override
2520 public boolean onLongClick(View v) {
Adam Powell23882512016-01-29 10:21:00 -08002521 showTargetDetails(
Adam Powell4eb98712015-10-14 13:10:18 -07002522 mChooserListAdapter.resolveInfoForPosition(
Matt Pietal5b648562019-03-12 07:40:26 -04002523 holder.getItemIndex(column), true));
Adam Powell63b31692015-09-28 10:45:00 -07002524 return true;
2525 }
2526 });
Zhen Zhangbde7b462019-11-11 11:49:33 -08002527 holder.addView(i, v);
Adam Powell63b31692015-09-28 10:45:00 -07002528
Mike Digmanba232682019-03-27 14:55:26 -07002529 // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
2530 // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be
2531 // done before measuring.
2532 if (isDirectShare) {
2533 final ViewHolder vh = (ViewHolder) v.getTag();
2534 vh.text.setLines(2);
2535 vh.text.setHorizontallyScrolling(false);
2536 vh.text2.setVisibility(View.GONE);
Adam Powell63b31692015-09-28 10:45:00 -07002537 }
Mike Digmanba232682019-03-27 14:55:26 -07002538
2539 // Force height to be a given so we don't have visual disruption during scaling.
Matt Pietalab986b52019-04-10 10:14:32 -04002540 v.measure(exactSpec, spec);
2541 setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight());
Adam Powell63b31692015-09-28 10:45:00 -07002542 }
2543
Matt Pietal5b648562019-03-12 07:40:26 -04002544 final ViewGroup viewGroup = holder.getViewGroup();
2545
Mike Digmanba232682019-03-27 14:55:26 -07002546 // Pre-measure and fix height so we can scale later.
Adam Powell63b31692015-09-28 10:45:00 -07002547 holder.measure();
Matt Pietalab986b52019-04-10 10:14:32 -04002548 setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight());
Mike Digmanba232682019-03-27 14:55:26 -07002549
2550 if (isDirectShare) {
2551 DirectShareViewHolder dsvh = (DirectShareViewHolder) holder;
Matt Pietalab986b52019-04-10 10:14:32 -04002552 setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
2553 setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
Adam Powell7d758002015-05-06 17:49:36 -07002554 }
Matt Pietal5b648562019-03-12 07:40:26 -04002555
2556 viewGroup.setTag(holder);
2557
Adam Powell7d758002015-05-06 17:49:36 -07002558 return holder;
2559 }
2560
Matt Pietalab986b52019-04-10 10:14:32 -04002561 private void setViewBounds(View view, int widthPx, int heightPx) {
Mike Digmanba232682019-03-27 14:55:26 -07002562 LayoutParams lp = view.getLayoutParams();
2563 if (lp == null) {
Matt Pietalab986b52019-04-10 10:14:32 -04002564 lp = new LayoutParams(widthPx, heightPx);
Mike Digmanba232682019-03-27 14:55:26 -07002565 view.setLayoutParams(lp);
2566 } else {
2567 lp.height = heightPx;
Matt Pietalab986b52019-04-10 10:14:32 -04002568 lp.width = widthPx;
Mike Digmanba232682019-03-27 14:55:26 -07002569 }
2570 }
2571
Zhen Zhangbde7b462019-11-11 11:49:33 -08002572 ItemGroupViewHolder createItemGroupViewHolder(int viewType, ViewGroup parent) {
Matt Pietal5b648562019-03-12 07:40:26 -04002573 if (viewType == VIEW_TYPE_DIRECT_SHARE) {
2574 ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate(
2575 R.layout.chooser_row_direct_share, parent, false);
2576 ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
2577 parentGroup, false);
2578 ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
2579 parentGroup, false);
2580 parentGroup.addView(row1);
2581 parentGroup.addView(row2);
2582
2583 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
2584 Lists.newArrayList(row1, row2), getMaxTargetsPerRow());
Zhen Zhangbde7b462019-11-11 11:49:33 -08002585 loadViewsIntoGroup(mDirectShareViewHolder);
Matt Pietal5b648562019-03-12 07:40:26 -04002586
2587 return mDirectShareViewHolder;
2588 } else {
2589 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
2590 false);
Zhen Zhangbde7b462019-11-11 11:49:33 -08002591 ItemGroupViewHolder holder = new SingleRowViewHolder(row, getMaxTargetsPerRow());
2592 loadViewsIntoGroup(holder);
Matt Pietal5b648562019-03-12 07:40:26 -04002593
2594 return holder;
2595 }
2596 }
2597
Matt Pietaldadc0d12019-04-16 12:53:28 -04002598 /**
Mike Digmanae730b12019-04-25 11:10:31 -07002599 * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
2600 * showing on top of the AZ list if the AZ label is visible. All other types are placed into
2601 * their own row as determined by their target type, and dividers are added in the list to
2602 * separate each type.
Matt Pietaldadc0d12019-04-16 12:53:28 -04002603 */
2604 int getRowType(int rowPosition) {
Mike Digmanae730b12019-04-25 11:10:31 -07002605 // Merge caller and ranked standard into a single row
Matt Pietaldadc0d12019-04-16 12:53:28 -04002606 int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
2607 if (positionType == ChooserListAdapter.TARGET_CALLER) {
2608 return ChooserListAdapter.TARGET_STANDARD;
2609 }
2610
Mike Digmanae730b12019-04-25 11:10:31 -07002611 // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
2612 // row type the same as the suggestion row type
2613 if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
2614 return ChooserListAdapter.TARGET_STANDARD;
2615 }
2616
Matt Pietaldadc0d12019-04-16 12:53:28 -04002617 return positionType;
2618 }
2619
Zhen Zhangbde7b462019-11-11 11:49:33 -08002620 void bindItemViewHolder(int position, ItemViewHolder holder) {
2621 View v = holder.itemView;
2622 int listPosition = getListPosition(position);
2623 holder.mListPosition = listPosition;
2624 mChooserListAdapter.bindView(listPosition, v);
2625 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002626
Zhen Zhangbde7b462019-11-11 11:49:33 -08002627 void bindItemGroupViewHolder(int position, ItemGroupViewHolder holder) {
2628 final ViewGroup viewGroup = (ViewGroup) holder.itemView;
2629 int start = getListPosition(position);
2630 int startType = getRowType(start);
2631 if (viewGroup.getForeground() == null) {
2632 viewGroup.setForeground(
Matt Pietal74c6ed02019-04-18 13:38:46 -04002633 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002634 }
2635
Matt Pietalfaedea82019-03-21 10:36:54 -04002636 int columnCount = holder.getColumnCount();
Matt Pietal5b648562019-03-12 07:40:26 -04002637 int end = start + columnCount - 1;
Matt Pietaldadc0d12019-04-16 12:53:28 -04002638 while (getRowType(end) != startType && end >= start) {
Adam Powell7d758002015-05-06 17:49:36 -07002639 end--;
2640 }
2641
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002642 if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08002643 final TextView textView = viewGroup.findViewById(R.id.chooser_row_text_option);
Adam Powell63b31692015-09-28 10:45:00 -07002644
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002645 if (textView.getVisibility() != View.VISIBLE) {
2646 textView.setAlpha(0.0f);
2647 textView.setVisibility(View.VISIBLE);
2648 textView.setText(R.string.chooser_no_direct_share_targets);
2649
2650 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
2651 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
2652
2653 float translationInPx = getResources().getDimensionPixelSize(
2654 R.dimen.chooser_row_text_option_translate);
2655 textView.setTranslationY(translationInPx);
2656 ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
2657 0.0f);
2658 translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
2659
2660 AnimatorSet animSet = new AnimatorSet();
2661 animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
2662 animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
2663 animSet.playTogether(fadeAnim, translateAnim);
2664 animSet.start();
2665 }
Adam Powell7d758002015-05-06 17:49:36 -07002666 }
2667
Matt Pietal5b648562019-03-12 07:40:26 -04002668 for (int i = 0; i < columnCount; i++) {
2669 final View v = holder.getView(i);
Adam Powell7d758002015-05-06 17:49:36 -07002670 if (start + i <= end) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002671 holder.setViewVisibility(i, View.VISIBLE);
Matt Pietal5b648562019-03-12 07:40:26 -04002672 holder.setItemIndex(i, start + i);
2673 mChooserListAdapter.bindView(holder.getItemIndex(i), v);
Adam Powell7d758002015-05-06 17:49:36 -07002674 } else {
Matt Pietalfaedea82019-03-21 10:36:54 -04002675 holder.setViewVisibility(i, View.INVISIBLE);
Adam Powell7d758002015-05-06 17:49:36 -07002676 }
2677 }
2678 }
2679
Zhen Zhangbde7b462019-11-11 11:49:33 -08002680 int getListPosition(int position) {
2681 position -= getContentPreviewRowCount() + getProfileRowCount();
Matt Pietal1ef88002019-03-13 10:43:18 -04002682
Matt Pietal5b648562019-03-12 07:40:26 -04002683 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
2684 final int serviceRows = (int) Math.ceil((float) serviceCount
2685 / ChooserListAdapter.MAX_SERVICE_TARGETS);
Zhen Zhangbde7b462019-11-11 11:49:33 -08002686 if (position < serviceRows) {
2687 return position * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07002688 }
2689
Zhen Zhangbde7b462019-11-11 11:49:33 -08002690 position -= serviceRows;
2691
Matt Pietaldadc0d12019-04-16 12:53:28 -04002692 final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
2693 + mChooserListAdapter.getRankedTargetCount();
2694 final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
Zhen Zhangbde7b462019-11-11 11:49:33 -08002695 if (position < callerAndRankedRows) {
2696 return serviceCount + position * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07002697 }
2698
Zhen Zhangbde7b462019-11-11 11:49:33 -08002699 position -= getAzLabelRowCount() + callerAndRankedRows;
Mike Digmanae730b12019-04-25 11:10:31 -07002700
Zhen Zhangbde7b462019-11-11 11:49:33 -08002701 return callerAndRankedCount + serviceCount + position;
Matt Pietal5b648562019-03-12 07:40:26 -04002702 }
2703
2704 public void handleScroll(View v, int y, int oldy) {
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002705 // Only expand direct share area if there is a minimum number of shortcuts,
2706 // which will help reduce the amount of visible shuffling due to older-style
2707 // direct share targets.
2708 int orientation = getResources().getConfiguration().orientation;
2709 boolean canExpandDirectShare =
2710 mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002711 && orientation == Configuration.ORIENTATION_PORTRAIT
2712 && !isInMultiWindowMode();
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002713
2714 if (mDirectShareViewHolder != null && canExpandDirectShare) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08002715 mDirectShareViewHolder.handleScroll(mRecyclerView, y, oldy, getMaxTargetsPerRow());
Matt Pietal5b648562019-03-12 07:40:26 -04002716 }
Adam Powell7d758002015-05-06 17:49:36 -07002717 }
2718 }
2719
Zhen Zhangbde7b462019-11-11 11:49:33 -08002720 /**
2721 * Used to bind types for group of items including:
2722 * {@link ChooserGridAdapter#VIEW_TYPE_DIRECT_SHARE},
2723 * and {@link ChooserGridAdapter#VIEW_TYPE_CALLER_AND_RANK}.
2724 */
2725 abstract class ItemGroupViewHolder extends RecyclerView.ViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04002726 protected int mMeasuredRowHeight;
2727 private int[] mItemIndices;
2728 protected final View[] mCells;
Matt Pietal5b648562019-03-12 07:40:26 -04002729 private final int mColumnCount;
Adam Powell63b31692015-09-28 10:45:00 -07002730
Zhen Zhangbde7b462019-11-11 11:49:33 -08002731 ItemGroupViewHolder(int cellCount, View itemView) {
2732 super(itemView);
Matt Pietal5b648562019-03-12 07:40:26 -04002733 this.mCells = new View[cellCount];
2734 this.mItemIndices = new int[cellCount];
Matt Pietal5b648562019-03-12 07:40:26 -04002735 this.mColumnCount = cellCount;
2736 }
2737
2738 abstract ViewGroup addView(int index, View v);
2739
2740 abstract ViewGroup getViewGroup();
2741
Mike Digmanba232682019-03-27 14:55:26 -07002742 abstract ViewGroup getRowByIndex(int index);
2743
2744 abstract ViewGroup getRow(int rowNumber);
Matt Pietal5b648562019-03-12 07:40:26 -04002745
Matt Pietalfaedea82019-03-21 10:36:54 -04002746 abstract void setViewVisibility(int i, int visibility);
2747
Matt Pietal5b648562019-03-12 07:40:26 -04002748 public int getColumnCount() {
2749 return mColumnCount;
2750 }
2751
Adam Powell63b31692015-09-28 10:45:00 -07002752 public void measure() {
2753 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietal5b648562019-03-12 07:40:26 -04002754 getViewGroup().measure(spec, spec);
2755 mMeasuredRowHeight = getViewGroup().getMeasuredHeight();
2756 }
2757
2758 public int getMeasuredRowHeight() {
2759 return mMeasuredRowHeight;
2760 }
2761
Matt Pietal5b648562019-03-12 07:40:26 -04002762 public void setItemIndex(int itemIndex, int listIndex) {
2763 mItemIndices[itemIndex] = listIndex;
2764 }
2765
2766 public int getItemIndex(int itemIndex) {
2767 return mItemIndices[itemIndex];
2768 }
2769
2770 public View getView(int index) {
2771 return mCells[index];
2772 }
2773 }
2774
Zhen Zhangbde7b462019-11-11 11:49:33 -08002775 class SingleRowViewHolder extends ItemGroupViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04002776 private final ViewGroup mRow;
2777
2778 SingleRowViewHolder(ViewGroup row, int cellCount) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08002779 super(cellCount, row);
Matt Pietal5b648562019-03-12 07:40:26 -04002780
2781 this.mRow = row;
2782 }
2783
2784 public ViewGroup getViewGroup() {
2785 return mRow;
2786 }
2787
Mike Digmanba232682019-03-27 14:55:26 -07002788 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04002789 return mRow;
2790 }
2791
Mike Digmanba232682019-03-27 14:55:26 -07002792 public ViewGroup getRow(int rowNumber) {
2793 if (rowNumber == 0) return mRow;
2794 return null;
2795 }
2796
Matt Pietal5b648562019-03-12 07:40:26 -04002797 public ViewGroup addView(int index, View v) {
2798 mRow.addView(v);
2799 mCells[index] = v;
2800
Matt Pietal5b648562019-03-12 07:40:26 -04002801 return mRow;
2802 }
Matt Pietalfaedea82019-03-21 10:36:54 -04002803
2804 public void setViewVisibility(int i, int visibility) {
2805 getView(i).setVisibility(visibility);
2806 }
Matt Pietal5b648562019-03-12 07:40:26 -04002807 }
2808
Zhen Zhangbde7b462019-11-11 11:49:33 -08002809 class DirectShareViewHolder extends ItemGroupViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04002810 private final ViewGroup mParent;
2811 private final List<ViewGroup> mRows;
2812 private int mCellCountPerRow;
2813
2814 private boolean mHideDirectShareExpansion = false;
2815 private int mDirectShareMinHeight = 0;
2816 private int mDirectShareCurrHeight = 0;
2817 private int mDirectShareMaxHeight = 0;
2818
Matt Pietalfaedea82019-03-21 10:36:54 -04002819 private final boolean[] mCellVisibility;
2820
Matt Pietal5b648562019-03-12 07:40:26 -04002821 DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08002822 super(rows.size() * cellCountPerRow, parent);
Matt Pietal5b648562019-03-12 07:40:26 -04002823
2824 this.mParent = parent;
2825 this.mRows = rows;
2826 this.mCellCountPerRow = cellCountPerRow;
Matt Pietalfaedea82019-03-21 10:36:54 -04002827 this.mCellVisibility = new boolean[rows.size() * cellCountPerRow];
Matt Pietal5b648562019-03-12 07:40:26 -04002828 }
2829
2830 public ViewGroup addView(int index, View v) {
Mike Digmanba232682019-03-27 14:55:26 -07002831 ViewGroup row = getRowByIndex(index);
Matt Pietal5b648562019-03-12 07:40:26 -04002832 row.addView(v);
2833 mCells[index] = v;
2834
Matt Pietal5b648562019-03-12 07:40:26 -04002835 return row;
2836 }
2837
2838 public ViewGroup getViewGroup() {
2839 return mParent;
2840 }
2841
Mike Digmanba232682019-03-27 14:55:26 -07002842 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04002843 return mRows.get(index / mCellCountPerRow);
2844 }
2845
Mike Digmanba232682019-03-27 14:55:26 -07002846 public ViewGroup getRow(int rowNumber) {
2847 return mRows.get(rowNumber);
2848 }
2849
Matt Pietal5b648562019-03-12 07:40:26 -04002850 public void measure() {
2851 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
2852 getRow(0).measure(spec, spec);
2853 getRow(1).measure(spec, spec);
2854
Matt Pietal5b648562019-03-12 07:40:26 -04002855 mDirectShareMinHeight = getRow(0).getMeasuredHeight();
2856 mDirectShareCurrHeight = mDirectShareCurrHeight > 0
Zhen Zhangbde7b462019-11-11 11:49:33 -08002857 ? mDirectShareCurrHeight : mDirectShareMinHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04002858 mDirectShareMaxHeight = 2 * mDirectShareMinHeight;
2859 }
2860
2861 public int getMeasuredRowHeight() {
2862 return mDirectShareCurrHeight;
2863 }
2864
Matt Pietala9c8e502019-04-10 14:27:35 -04002865 public int getMinRowHeight() {
2866 return mDirectShareMinHeight;
2867 }
2868
Matt Pietalfaedea82019-03-21 10:36:54 -04002869 public void setViewVisibility(int i, int visibility) {
2870 final View v = getView(i);
2871 if (visibility == View.VISIBLE) {
2872 mCellVisibility[i] = true;
2873 v.setVisibility(visibility);
2874 v.setAlpha(1.0f);
2875 } else if (visibility == View.INVISIBLE && mCellVisibility[i]) {
2876 mCellVisibility[i] = false;
2877
2878 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
2879 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
2880 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
2881 fadeAnim.addListener(new AnimatorListenerAdapter() {
2882 public void onAnimationEnd(Animator animation) {
2883 v.setVisibility(View.INVISIBLE);
2884 }
2885 });
2886 fadeAnim.start();
2887 }
2888 }
2889
Zhen Zhangbde7b462019-11-11 11:49:33 -08002890 public void handleScroll(RecyclerView view, int y, int oldy, int maxTargetsPerRow) {
Matt Pietala9c8e502019-04-10 14:27:35 -04002891 // only exit early if fully collapsed, otherwise onListRebuilt() with shifting
2892 // targets can lock us into an expanded mode
2893 boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight;
2894 if (notExpanded) {
2895 if (mHideDirectShareExpansion) {
2896 return;
2897 }
Matt Pietal5b648562019-03-12 07:40:26 -04002898
Matt Pietala9c8e502019-04-10 14:27:35 -04002899 // only expand if we have more than maxTargetsPerRow, and delay that decision
2900 // until they start to scroll
2901 if (mChooserListAdapter.getSelectableServiceTargetCount() <= maxTargetsPerRow) {
2902 mHideDirectShareExpansion = true;
2903 return;
2904 }
Matt Pietal5b648562019-03-12 07:40:26 -04002905 }
2906
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002907 int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal5b648562019-03-12 07:40:26 -04002908
2909 int prevHeight = mDirectShareCurrHeight;
Matt Pietalc6d3ac22019-04-25 14:38:30 -04002910 int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
2911 newHeight = Math.max(newHeight, mDirectShareMinHeight);
2912 yDiff = newHeight - prevHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04002913
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002914 if (view == null || view.getChildCount() == 0 || yDiff == 0) {
Matt Pietal5b648562019-03-12 07:40:26 -04002915 return;
2916 }
2917
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002918 // locate the item to expand, and offset the rows below that one
2919 boolean foundExpansion = false;
2920 for (int i = 0; i < view.getChildCount(); i++) {
2921 View child = view.getChildAt(i);
Matt Pietal1ef88002019-03-13 10:43:18 -04002922
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002923 if (foundExpansion) {
2924 child.offsetTopAndBottom(yDiff);
2925 } else {
2926 if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
2927 int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
2928 MeasureSpec.EXACTLY);
Matt Pietalc6d3ac22019-04-25 14:38:30 -04002929 int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002930 MeasureSpec.EXACTLY);
2931 child.measure(widthSpec, heightSpec);
2932 child.getLayoutParams().height = child.getMeasuredHeight();
2933 child.layout(child.getLeft(), child.getTop(), child.getRight(),
2934 child.getTop() + child.getMeasuredHeight());
Matt Pietal5b648562019-03-12 07:40:26 -04002935
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002936 foundExpansion = true;
2937 }
2938 }
Matt Pietal5b648562019-03-12 07:40:26 -04002939 }
Matt Pietalc6d3ac22019-04-25 14:38:30 -04002940
2941 if (foundExpansion) {
2942 mDirectShareCurrHeight = newHeight;
2943 }
Adam Powell63b31692015-09-28 10:45:00 -07002944 }
2945 }
2946
Adam Powell9761ab22015-09-08 17:01:49 -07002947 static class ChooserTargetServiceConnection implements ServiceConnection {
Adam Powell52c39212016-04-07 15:14:18 -07002948 private DisplayResolveInfo mOriginalTarget;
Adam Powell9761ab22015-09-08 17:01:49 -07002949 private ComponentName mConnectedComponent;
2950 private ChooserActivity mChooserActivity;
2951 private final Object mLock = new Object();
Adam Powell24428412015-04-01 17:19:56 -07002952
2953 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
2954 @Override
2955 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
Adam Powell9761ab22015-09-08 17:01:49 -07002956 synchronized (mLock) {
2957 if (mChooserActivity == null) {
2958 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
2959 + mConnectedComponent + "; ignoring...");
2960 return;
2961 }
2962 mChooserActivity.filterServiceTargets(
2963 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
2964 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04002965 msg.what = ChooserHandler.CHOOSER_TARGET_SERVICE_RESULT;
Adam Powell9761ab22015-09-08 17:01:49 -07002966 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
2967 ChooserTargetServiceConnection.this);
2968 mChooserActivity.mChooserHandler.sendMessage(msg);
2969 }
Adam Powell24428412015-04-01 17:19:56 -07002970 }
2971 };
2972
Adam Powell9761ab22015-09-08 17:01:49 -07002973 public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
2974 DisplayResolveInfo dri) {
2975 mChooserActivity = chooserActivity;
Adam Powell24428412015-04-01 17:19:56 -07002976 mOriginalTarget = dri;
2977 }
2978
2979 @Override
2980 public void onServiceConnected(ComponentName name, IBinder service) {
2981 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07002982 synchronized (mLock) {
2983 if (mChooserActivity == null) {
2984 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
2985 return;
2986 }
2987
2988 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
2989 try {
2990 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
2991 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
2992 } catch (RemoteException e) {
2993 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
2994 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07002995 mChooserActivity.mServiceConnections.remove(this);
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04002996 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07002997 }
Adam Powell24428412015-04-01 17:19:56 -07002998 }
2999 }
3000
3001 @Override
3002 public void onServiceDisconnected(ComponentName name) {
3003 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003004 synchronized (mLock) {
3005 if (mChooserActivity == null) {
3006 Log.e(TAG,
3007 "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
3008 return;
3009 }
3010
3011 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003012 mChooserActivity.mServiceConnections.remove(this);
3013 if (mChooserActivity.mServiceConnections.isEmpty()) {
Adam Powell9761ab22015-09-08 17:01:49 -07003014 mChooserActivity.sendVoiceChoicesIfNeeded();
3015 }
3016 mConnectedComponent = null;
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003017 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003018 }
3019 }
3020
3021 public void destroy() {
3022 synchronized (mLock) {
3023 mChooserActivity = null;
Adam Powell52c39212016-04-07 15:14:18 -07003024 mOriginalTarget = null;
Adam Powell4c470d62015-06-19 17:46:17 -07003025 }
Adam Powell24428412015-04-01 17:19:56 -07003026 }
3027
3028 @Override
3029 public String toString() {
Adam Powell9761ab22015-09-08 17:01:49 -07003030 return "ChooserTargetServiceConnection{service="
3031 + mConnectedComponent + ", activity="
Adam Powell52c39212016-04-07 15:14:18 -07003032 + (mOriginalTarget != null
3033 ? mOriginalTarget.getResolveInfo().activityInfo.toString()
3034 : "<connection destroyed>") + "}";
Adam Powell24428412015-04-01 17:19:56 -07003035 }
3036 }
3037
3038 static class ServiceResultInfo {
3039 public final DisplayResolveInfo originalTarget;
3040 public final List<ChooserTarget> resultTargets;
3041 public final ChooserTargetServiceConnection connection;
3042
3043 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
3044 ChooserTargetServiceConnection c) {
3045 originalTarget = ot;
3046 resultTargets = rt;
3047 connection = c;
3048 }
3049 }
Adam Powell2ed547e2015-04-29 18:45:04 -07003050
3051 static class RefinementResultReceiver extends ResultReceiver {
3052 private ChooserActivity mChooserActivity;
3053 private TargetInfo mSelectedTarget;
3054
3055 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
3056 Handler handler) {
3057 super(handler);
3058 mChooserActivity = host;
3059 mSelectedTarget = target;
3060 }
3061
3062 @Override
3063 protected void onReceiveResult(int resultCode, Bundle resultData) {
3064 if (mChooserActivity == null) {
3065 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
3066 return;
3067 }
3068 if (resultData == null) {
3069 Log.e(TAG, "RefinementResultReceiver received null resultData");
3070 return;
3071 }
3072
3073 switch (resultCode) {
3074 case RESULT_CANCELED:
3075 mChooserActivity.onRefinementCanceled();
3076 break;
3077 case RESULT_OK:
3078 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
3079 if (intentParcelable instanceof Intent) {
3080 mChooserActivity.onRefinementResult(mSelectedTarget,
3081 (Intent) intentParcelable);
3082 } else {
3083 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
3084 + " in resultData with key Intent.EXTRA_INTENT");
3085 }
3086 break;
3087 default:
3088 Log.w(TAG, "Unknown result code " + resultCode
3089 + " sent to RefinementResultReceiver");
3090 break;
3091 }
3092 }
3093
3094 public void destroy() {
3095 mChooserActivity = null;
3096 mSelectedTarget = null;
3097 }
3098 }
Adam Powell63b31692015-09-28 10:45:00 -07003099
Matt Pietal26038402019-01-08 07:29:34 -05003100 /**
3101 * Used internally to round image corners while obeying view padding.
3102 */
3103 public static class RoundedRectImageView extends ImageView {
3104 private int mRadius = 0;
3105 private Path mPath = new Path();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003106 private Paint mOverlayPaint = new Paint(0);
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003107 private Paint mRoundRectPaint = new Paint(0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003108 private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
3109 private String mExtraImageCount = null;
Matt Pietal26038402019-01-08 07:29:34 -05003110
3111 public RoundedRectImageView(Context context) {
3112 super(context);
3113 }
3114
3115 public RoundedRectImageView(Context context, AttributeSet attrs) {
3116 this(context, attrs, 0);
3117 }
3118
3119 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
3120 this(context, attrs, defStyleAttr, 0);
3121 }
3122
3123 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
3124 int defStyleRes) {
3125 super(context, attrs, defStyleAttr, defStyleRes);
3126 mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003127
3128 mOverlayPaint.setColor(0x99000000);
3129 mOverlayPaint.setStyle(Paint.Style.FILL);
3130
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003131 mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider));
3132 mRoundRectPaint.setStyle(Paint.Style.STROKE);
3133 mRoundRectPaint.setStrokeWidth(context.getResources()
3134 .getDimensionPixelSize(R.dimen.chooser_preview_image_border));
3135
Matt Pietal0ea391b2019-01-30 10:44:15 -05003136 mTextPaint.setColor(Color.WHITE);
3137 mTextPaint.setTextSize(context.getResources()
3138 .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
3139 mTextPaint.setTextAlign(Paint.Align.CENTER);
Matt Pietal26038402019-01-08 07:29:34 -05003140 }
3141
3142 private void updatePath(int width, int height) {
3143 mPath.reset();
3144
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003145 int imageWidth = width - getPaddingRight() - getPaddingLeft();
3146 int imageHeight = height - getPaddingBottom() - getPaddingTop();
Matt Pietal26038402019-01-08 07:29:34 -05003147 mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
3148 mRadius, Path.Direction.CW);
3149 }
3150
3151 /**
3152 * Sets the corner radius on all corners
3153 *
3154 * param radius 0 for no radius, &gt; 0 for a visible corner radius
3155 */
3156 public void setRadius(int radius) {
3157 mRadius = radius;
3158 updatePath(getWidth(), getHeight());
3159 }
3160
Matt Pietal0ea391b2019-01-30 10:44:15 -05003161 /**
3162 * Display an overlay with extra image count on 3rd image
3163 */
3164 public void setExtraImageCount(int count) {
3165 if (count > 0) {
3166 this.mExtraImageCount = "+" + count;
3167 } else {
3168 this.mExtraImageCount = null;
3169 }
3170 }
3171
Matt Pietal26038402019-01-08 07:29:34 -05003172 @Override
3173 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
3174 super.onSizeChanged(width, height, oldWidth, oldHeight);
3175 updatePath(width, height);
3176 }
3177
3178 @Override
3179 protected void onDraw(Canvas canvas) {
3180 if (mRadius != 0) {
3181 canvas.clipPath(mPath);
3182 }
3183
3184 super.onDraw(canvas);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003185
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003186 int x = getPaddingLeft();
3187 int y = getPaddingRight();
3188 int width = getWidth() - getPaddingRight() - getPaddingLeft();
3189 int height = getHeight() - getPaddingBottom() - getPaddingTop();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003190 if (mExtraImageCount != null) {
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003191 canvas.drawRect(x, y, width, height, mOverlayPaint);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003192
3193 int xPos = canvas.getWidth() / 2;
3194 int yPos = (int) ((canvas.getHeight() / 2.0f)
3195 - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
3196
3197 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
3198 }
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003199
3200 canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
Matt Pietal26038402019-01-08 07:29:34 -05003201 }
3202 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003203}