blob: 1af39265dbcba24ea4920ee553ffb833731d9b3b [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;
arangelov38a6fce2019-12-02 18:21:22 +0000101import android.view.WindowInsets;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500102import android.view.animation.AccelerateInterpolator;
103import android.view.animation.DecelerateInterpolator;
Matt Pietal26038402019-01-08 07:29:34 -0500104import android.widget.ImageView;
Matt Pietal26038402019-01-08 07:29:34 -0500105import android.widget.TextView;
Matt Pietal1fa7d802019-01-30 10:44:15 -0500106import android.widget.Toast;
Jason Monk027dcfa2017-06-27 18:37:35 -0400107
Adam Powell7d758002015-05-06 17:49:36 -0700108import com.android.internal.R;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800109import com.android.internal.annotations.VisibleForTesting;
arangelovb0802dc2019-10-18 18:03:44 +0100110import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
111import com.android.internal.app.ResolverListAdapter.ViewHolder;
112import com.android.internal.app.chooser.ChooserTargetInfo;
113import com.android.internal.app.chooser.DisplayResolveInfo;
114import com.android.internal.app.chooser.NotSelectableTargetInfo;
115import com.android.internal.app.chooser.SelectableTargetInfo;
116import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
117import com.android.internal.app.chooser.TargetInfo;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400118import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
Matt Pietalab73a882019-06-05 07:04:55 -0400119import com.android.internal.content.PackageMonitor;
Adam Powell98b7f892015-06-19 12:38:45 -0700120import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +0100121import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500122import com.android.internal.util.ImageUtils;
Zhen Zhangbde7b462019-11-11 11:49:33 -0800123import com.android.internal.widget.GridLayoutManager;
124import com.android.internal.widget.RecyclerView;
Mike Digman849a9d12019-04-29 11:20:48 -0700125import com.android.internal.widget.ResolverDrawerLayout;
Alison Cichowlas3e340502018-08-07 17:15:01 -0400126
Adam Powell52c39212016-04-07 15:14:18 -0700127import com.google.android.collect.Lists;
Adam Powell24428412015-04-01 17:19:56 -0700128
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500129import java.io.File;
Matt Pietal26038402019-01-08 07:29:34 -0500130import java.io.IOException;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500131import java.lang.annotation.Retention;
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700132import java.lang.annotation.RetentionPolicy;
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400133import java.text.Collator;
Adam Powell24428412015-04-01 17:19:56 -0700134import java.util.ArrayList;
Adam Powella182e452015-07-06 16:57:56 -0700135import java.util.Collections;
136import java.util.Comparator;
George Hodulikaa5238c2019-04-18 14:17:51 -0700137import java.util.HashMap;
Matt Pietalab73a882019-06-05 07:04:55 -0400138import java.util.HashSet;
Adam Powell24428412015-04-01 17:19:56 -0700139import java.util.List;
George Hodulikaa5238c2019-04-18 14:17:51 -0700140import java.util.Map;
Matt Pietalab73a882019-06-05 07:04:55 -0400141import java.util.Set;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400143/**
144 * The Chooser Activity handles intent resolution specifically for sharing intents -
145 * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
146 *
147 */
arangelovb0802dc2019-10-18 18:03:44 +0100148public class ChooserActivity extends ResolverActivity implements
149 ChooserListAdapter.ChooserListCommunicator,
150 SelectableTargetInfoCommunicator {
Adam Powell0b3c1122014-10-09 12:50:14 -0700151 private static final String TAG = "ChooserActivity";
152
Artur Satayev751e5512019-11-15 19:12:49 +0000153 @UnsupportedAppUsage
154 public ChooserActivity() {
155 }
Jorim Jaggif631ef72017-02-24 13:49:47 +0100156 /**
157 * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
158 * in onStop when launched in a new task. If this extra is set to true, we do not finish
159 * ourselves when onStop gets called.
160 */
161 public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
162 = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
163
Mike Digman849a9d12019-04-29 11:20:48 -0700164 private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
165
Mehdi Alizadeh3c335a22019-01-17 16:03:19 -0800166 private static final boolean DEBUG = false;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800167
George Hodulik1428beb2019-05-01 17:04:23 -0700168 private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
George Hodulik69d4a082019-01-18 11:27:03 -0800169 // TODO(b/123088566) Share these in a better way.
170 private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
George Hodulikf2b0d342019-01-25 12:43:54 -0800171 public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
George Hodulik69d4a082019-01-18 11:27:03 -0800172 private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
173 public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700174
arangelovb0802dc2019-10-18 18:03:44 +0100175 @VisibleForTesting
176 public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
177
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
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500232 private int mChooserRowServiceSpacing;
Adam Powell24428412015-04-01 17:19:56 -0700233
Matt Pietalab73a882019-06-05 07:04:55 -0400234 private int mCurrAvailableWidth = 0;
235
Adam Powell23882512016-01-29 10:21:00 -0800236 private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400237 // TODO: Update to handle landscape instead of using static value
238 private static final int MAX_RANKED_TARGETS = 4;
Adam Powell23882512016-01-29 10:21:00 -0800239
Adam Powell24428412015-04-01 17:19:56 -0700240 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
Matt Pietalab73a882019-06-05 07:04:55 -0400241 private final Set<ComponentName> mServicesRequested = new HashSet<>();
Matt Pietalaf044ae2019-03-29 06:53:53 -0400242
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -0400243 private static final int MAX_LOG_RANK_POSITION = 12;
244
Matt Pietal4e2e3632019-04-05 08:32:47 -0400245 private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400246 private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
Matt Pietal4e2e3632019-04-05 08:32:47 -0400247
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500248 private SharedPreferences mPinnedSharedPrefs;
249 private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
250
Matt Pietal0ea391b2019-01-30 10:44:15 -0500251 @Retention(SOURCE)
252 @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
253 private @interface ContentPreviewType {
254 }
255
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500256 // Starting at 1 since 0 is considered "undefined" for some of the database transformations
257 // of tron logs.
258 private static final int CONTENT_PREVIEW_IMAGE = 1;
259 private static final int CONTENT_PREVIEW_FILE = 2;
260 private static final int CONTENT_PREVIEW_TEXT = 3;
261 protected MetricsLogger mMetricsLogger;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500262
Matt Pietale7cacab2019-05-23 07:21:36 -0400263 private ContentPreviewCoordinator mPreviewCoord;
264
arangelov38a6fce2019-12-02 18:21:22 +0000265 @VisibleForTesting
266 protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
267
Matt Pietale7cacab2019-05-23 07:21:36 -0400268 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();
arangelov38a6fce2019-12-02 18:21:22 +0000365 if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
366 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().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();
arangelov38a6fce2019-12-02 18:21:22 +0000434 mChooserMultiProfilePagerAdapter.getCurrentListAdapter()
435 .completeServiceTargetLoading();
Matt Pietalab73a882019-06-05 07:04:55 -0400436 }
437 }
438
Adam Powell24428412015-04-01 17:19:56 -0700439 @Override
440 public void handleMessage(Message msg) {
arangelov38a6fce2019-12-02 18:21:22 +0000441 if (mChooserMultiProfilePagerAdapter.getCurrentListAdapter() == null || isDestroyed()) {
Matt Pietalaf044ae2019-03-29 06:53:53 -0400442 return;
443 }
444
Adam Powell24428412015-04-01 17:19:56 -0700445 switch (msg.what) {
446 case CHOOSER_TARGET_SERVICE_RESULT:
447 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
Adam Powell24428412015-04-01 17:19:56 -0700448 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
449 if (!mServiceConnections.contains(sri.connection)) {
450 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
451 + " returned after being removed from active connections."
452 + " Have you considered returning results faster?");
453 break;
454 }
Adam Powella182e452015-07-06 16:57:56 -0700455 if (sri.resultTargets != null) {
arangelov38a6fce2019-12-02 18:21:22 +0000456 // TODO(arangelov): Instead of using getCurrentListAdapter(), pass the
457 // profileId as part of the message.
458 mChooserMultiProfilePagerAdapter.getCurrentListAdapter().addServiceResults(
459 sri.originalTarget, sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET);
Adam Powella182e452015-07-06 16:57:56 -0700460 }
Adam Powell24428412015-04-01 17:19:56 -0700461 unbindService(sri.connection);
Adam Powell9761ab22015-09-08 17:01:49 -0700462 sri.connection.destroy();
Adam Powell24428412015-04-01 17:19:56 -0700463 mServiceConnections.remove(sri.connection);
Matt Pietalab73a882019-06-05 07:04:55 -0400464 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700465 break;
466
Matt Pietalab73a882019-06-05 07:04:55 -0400467 case CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT:
468 mMinTimeoutPassed = true;
469 maybeStopServiceRequestTimer();
470 break;
Matt Pietalaf044ae2019-03-29 06:53:53 -0400471
Matt Pietalab73a882019-06-05 07:04:55 -0400472 case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
Adam Powell24428412015-04-01 17:19:56 -0700473 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -0400474 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700475 break;
476
Matt Pietalaf044ae2019-03-29 06:53:53 -0400477 case LIST_VIEW_UPDATE_MESSAGE:
478 if (DEBUG) {
479 Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
480 }
481
arangelov38a6fce2019-12-02 18:21:22 +0000482 mChooserMultiProfilePagerAdapter.getCurrentListAdapter().refreshListView();
Matt Pietalaf044ae2019-03-29 06:53:53 -0400483 break;
484
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800485 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
486 if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800487 final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
488 if (resultInfo.resultTargets != null) {
arangelov38a6fce2019-12-02 18:21:22 +0000489 mChooserMultiProfilePagerAdapter.getCurrentListAdapter().addServiceResults(
490 resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800491 }
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800492 break;
493
494 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700495 logDirectShareTargetReceived(
496 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800497 sendVoiceChoicesIfNeeded();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800498 break;
499
Adam Powell24428412015-04-01 17:19:56 -0700500 default:
501 super.handleMessage(msg);
502 }
503 }
504 };
505
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506 @Override
507 protected void onCreate(Bundle savedInstanceState) {
Kang Li9082f5b2016-12-02 10:56:21 -0800508 final long intentReceivedTime = System.currentTimeMillis();
Mehdi Alizadeh5cc5f712019-09-06 12:17:52 -0700509 // This is the only place this value is being set. Effectively final.
arangelov38a6fce2019-12-02 18:21:22 +0000510 //TODO(arangelov) - should there be a mIsAppPredictorComponentAvailable flag for work tab?
Mehdi Alizadeh5cc5f712019-09-06 12:17:52 -0700511 mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
512
Kang Li9082f5b2016-12-02 10:56:21 -0800513 mIsSuccessfullySelected = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 Intent intent = getIntent();
Dianne Hackborneb034652009-09-07 00:49:58 -0700515 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
516 if (!(targetParcelable instanceof Intent)) {
Christopher Tate9d6376a2014-02-12 13:14:10 -0800517 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
Dianne Hackborneb034652009-09-07 00:49:58 -0700518 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800519 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700520 return;
521 }
Adam Powell24428412015-04-01 17:19:56 -0700522 Intent target = (Intent) targetParcelable;
Craig Mautner411d2aed2014-05-08 09:07:43 -0700523 if (target != null) {
Adam Powelle49d9392014-07-17 18:45:19 -0700524 modifyTargetIntent(target);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700525 }
Adam Powell2ed547e2015-04-29 18:45:04 -0700526 Parcelable[] targetsParcelable
527 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
528 if (targetsParcelable != null) {
529 final boolean offset = target == null;
530 Intent[] additionalTargets =
531 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
532 for (int i = 0; i < targetsParcelable.length; i++) {
533 if (!(targetsParcelable[i] instanceof Intent)) {
534 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
535 + targetsParcelable[i]);
536 finish();
537 super.onCreate(null);
538 return;
539 }
540 final Intent additionalTarget = (Intent) targetsParcelable[i];
541 if (i == 0 && target == null) {
542 target = additionalTarget;
543 modifyTargetIntent(target);
544 } else {
545 additionalTargets[offset ? i - 1 : i] = additionalTarget;
546 modifyTargetIntent(additionalTarget);
547 }
548 }
549 setAdditionalTargets(additionalTargets);
550 }
551
Adam Powelle49d9392014-07-17 18:45:19 -0700552 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
Matt Pietal26038402019-01-08 07:29:34 -0500553
554 // Do not allow the title to be changed when sharing content
555 CharSequence title = null;
556 if (target != null) {
Matt Pietal95574b02019-03-13 08:12:25 -0400557 if (!isSendAction(target)) {
Matt Pietal26038402019-01-08 07:29:34 -0500558 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
559 } else {
560 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
561 + " preview title by using EXTRA_TITLE property of the wrapped"
562 + " EXTRA_INTENT.");
563 }
564 }
565
Adam Powell278902c2014-07-12 18:33:22 -0700566 int defaultTitleRes = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800567 if (title == null) {
Adam Powell278902c2014-07-12 18:33:22 -0700568 defaultTitleRes = com.android.internal.R.string.chooseActivity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 }
Matt Pietal26038402019-01-08 07:29:34 -0500570
Dianne Hackborneb034652009-09-07 00:49:58 -0700571 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
572 Intent[] initialIntents = null;
573 if (pa != null) {
Matt Pietal4e2e3632019-04-05 08:32:47 -0400574 int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS);
575 initialIntents = new Intent[count];
576 for (int i = 0; i < count; i++) {
Dianne Hackborneb034652009-09-07 00:49:58 -0700577 if (!(pa[i] instanceof Intent)) {
Adam Powell2ed547e2015-04-29 18:45:04 -0700578 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
Dianne Hackborneb034652009-09-07 00:49:58 -0700579 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800580 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700581 return;
582 }
Craig Mautner411d2aed2014-05-08 09:07:43 -0700583 final Intent in = (Intent) pa[i];
Adam Powelle49d9392014-07-17 18:45:19 -0700584 modifyTargetIntent(in);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700585 initialIntents[i] = in;
Dianne Hackborneb034652009-09-07 00:49:58 -0700586 }
587 }
Adam Powell24428412015-04-01 17:19:56 -0700588
Adam Powell13036be2015-05-12 14:43:56 -0700589 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
590
Adam Powell0b3c1122014-10-09 12:50:14 -0700591 mChosenComponentSender = intent.getParcelableExtra(
592 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
Adam Powell2ed547e2015-04-29 18:45:04 -0700593 mRefinementIntentSender = intent.getParcelableExtra(
594 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
Dianne Hackborn028ceeb2014-08-17 17:45:48 -0700595 setSafeForwardingMode(true);
Adam Powell23882512016-01-29 10:21:00 -0800596
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500597 mPinnedSharedPrefs = getPinnedSharedPrefs(this);
598
Adam Powell52c39212016-04-07 15:14:18 -0700599 pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
600 if (pa != null) {
601 ComponentName[] names = new ComponentName[pa.length];
602 for (int i = 0; i < pa.length; i++) {
603 if (!(pa[i] instanceof ComponentName)) {
604 Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
605 names = null;
606 break;
607 }
608 names[i] = (ComponentName) pa[i];
609 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800610 mFilteredComponentNames = names;
Adam Powell52c39212016-04-07 15:14:18 -0700611 }
612
613 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
614 if (pa != null) {
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400615 int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS);
616 ChooserTarget[] targets = new ChooserTarget[count];
617 for (int i = 0; i < count; i++) {
Adam Powell52c39212016-04-07 15:14:18 -0700618 if (!(pa[i] instanceof ChooserTarget)) {
619 Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
620 targets = null;
621 break;
622 }
623 targets[i] = (ChooserTarget) pa[i];
624 }
625 mCallerChooserTargets = targets;
626 }
627
Jorim Jaggif631ef72017-02-24 13:49:47 +0100628 setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
Adam Powell278902c2014-07-12 18:33:22 -0700629 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
630 null, false);
Adam Powell98b7f892015-06-19 12:38:45 -0700631
Kang Li9082f5b2016-12-02 10:56:21 -0800632 mChooserShownTime = System.currentTimeMillis();
633 final long systemCost = mChooserShownTime - intentReceivedTime;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500634
635 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500636 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
637 MetricsEvent.PARENT_PROFILE)
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500638 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
639 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
George Hodulik69d4a082019-01-18 11:27:03 -0800640
George Hodulik145b3a52019-03-27 11:18:43 -0700641 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
642 if (appPredictor != null) {
George Hodulikaa5238c2019-04-18 14:17:51 -0700643 mDirectShareAppTargetCache = new HashMap<>();
George Hodulik69d4a082019-01-18 11:27:03 -0800644 mAppPredictorCallback = resultList -> {
arangelov38a6fce2019-12-02 18:21:22 +0000645 //TODO(arangelov) Take care of edge case when callback called after swiping tabs
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500646 if (isFinishing() || isDestroyed()) {
647 return;
648 }
arangelov38a6fce2019-12-02 18:21:22 +0000649 if (mChooserMultiProfilePagerAdapter.getCurrentListAdapter().getCount() == 0) {
George Hodulik0dd5fbe2019-03-06 12:00:26 -0800650 return;
651 }
George Hodulik3f399f22019-04-26 16:17:54 -0700652 if (resultList.isEmpty()) {
653 // APS may be disabled, so try querying targets ourselves.
arangelov38a6fce2019-12-02 18:21:22 +0000654 //TODO(arangelov) queryDirectShareTargets indirectly uses mIntents.
655 // Investigate implications for work tab.
656 queryDirectShareTargets(
657 mChooserMultiProfilePagerAdapter.getCurrentListAdapter(), true);
George Hodulik3f399f22019-04-26 16:17:54 -0700658 return;
659 }
George Hodulik69d4a082019-01-18 11:27:03 -0800660 final List<DisplayResolveInfo> driList =
arangelov38a6fce2019-12-02 18:21:22 +0000661 getDisplayResolveInfos(
662 mChooserMultiProfilePagerAdapter.getCurrentListAdapter());
George Hodulik69d4a082019-01-18 11:27:03 -0800663 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
664 new ArrayList<>();
665 for (AppTarget appTarget : resultList) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500666 if (appTarget.getShortcutInfo() == null) {
667 continue;
668 }
George Hodulik69d4a082019-01-18 11:27:03 -0800669 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
670 appTarget.getShortcutInfo(),
671 new ComponentName(
672 appTarget.getPackageName(), appTarget.getClassName())));
673 }
George Hodulikaa5238c2019-04-18 14:17:51 -0700674 sendShareShortcutInfoList(shareShortcutInfos, driList, resultList);
George Hodulik69d4a082019-01-18 11:27:03 -0800675 };
George Hodulik145b3a52019-03-27 11:18:43 -0700676 appPredictor
677 .registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
George Hodulik69d4a082019-01-18 11:27:03 -0800678 }
679
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500680 mChooserRowServiceSpacing = getResources()
681 .getDimensionPixelSize(R.dimen.chooser_service_spacing);
682
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400683 if (mResolverDrawerLayout != null) {
684 mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
685
686 // expand/shrink direct share 4 -> 8 viewgroup
687 if (isSendAction(target)) {
688 mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
689 }
Matt Pietalb1d629d2019-04-23 11:35:53 -0400690
691 final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
692 final float defaultElevation = chooserHeader.getElevation();
693 final float chooserHeaderScrollElevation =
694 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
695
arangelov38a6fce2019-12-02 18:21:22 +0000696 mChooserMultiProfilePagerAdapter.getCurrentAdapterView().addOnScrollListener(
697 new RecyclerView.OnScrollListener() {
698 public void onScrollStateChanged(RecyclerView view, int scrollState) {
Matt Pietalb1d629d2019-04-23 11:35:53 -0400699 }
Matt Pietalb1d629d2019-04-23 11:35:53 -0400700
arangelov38a6fce2019-12-02 18:21:22 +0000701 public void onScrolled(RecyclerView view, int dx, int dy) {
702 if (view.getChildCount() > 0) {
703 View child = view.getLayoutManager().findViewByPosition(0);
704 if (child == null || child.getTop() < 0) {
705 chooserHeader.setElevation(chooserHeaderScrollElevation);
706 return;
707 }
708 }
709
710 chooserHeader.setElevation(defaultElevation);
711 }
Matt Pietalb1d629d2019-04-23 11:35:53 -0400712 });
Mike Digman849a9d12019-04-29 11:20:48 -0700713
714 mResolverDrawerLayout.setOnCollapsedChangedListener(
715 new ResolverDrawerLayout.OnCollapsedChangedListener() {
716
717 // Only consider one expansion per activity creation
718 private boolean mWrittenOnce = false;
719
720 @Override
721 public void onCollapsedChanged(boolean isCollapsed) {
722 if (!isCollapsed && !mWrittenOnce) {
723 incrementNumSheetExpansions();
724 mWrittenOnce = true;
725 }
726 }
727 });
Matt Pietal5b648562019-03-12 07:40:26 -0400728 }
729
Kang Li9082f5b2016-12-02 10:56:21 -0800730 if (DEBUG) {
731 Log.d(TAG, "System Time Cost is " + systemCost);
732 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 }
Adam Powelle49d9392014-07-17 18:45:19 -0700734
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500735 static SharedPreferences getPinnedSharedPrefs(Context context) {
736 // The code below is because in the android:ui process, no one can hear you scream.
737 // The package info in the context isn't initialized in the way it is for normal apps,
738 // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
739 // build the path manually below using the same policy that appears in ContextImpl.
740 // This fails silently under the hood if there's a problem, so if we find ourselves in
741 // the case where we don't have access to credential encrypted storage we just won't
742 // have our pinned target info.
743 final File prefsFile = new File(new File(
744 Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
745 context.getUserId(), context.getPackageName()),
746 "shared_prefs"),
747 PINNED_SHARED_PREFS_NAME + ".xml");
748 return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
749 }
750
arangelov38a6fce2019-12-02 18:21:22 +0000751 @Override
752 protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
753 Intent[] initialIntents,
754 List<ResolveInfo> rList,
755 boolean filterLastUsed) {
756 if (hasWorkProfile() && ENABLE_TABBED_VIEW) {
757 mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles(
758 initialIntents, rList, filterLastUsed);
759 } else {
760 mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile(
761 initialIntents, rList, filterLastUsed);
762 }
763 return mChooserMultiProfilePagerAdapter;
764 }
765
766 private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
767 Intent[] initialIntents,
768 List<ResolveInfo> rList,
769 boolean filterLastUsed) {
770 ChooserGridAdapter adapter = createChooserGridAdapter(
771 /* context */ this,
772 /* payloadIntents */ mIntents,
773 initialIntents,
774 rList,
775 filterLastUsed,
776 mUseLayoutForBrowsables,
777 /* userHandle */ UserHandle.of(UserHandle.myUserId()));
778 return new ChooserMultiProfilePagerAdapter(
779 /* context */ this,
780 adapter);
781 }
782
783 private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
784 Intent[] initialIntents,
785 List<ResolveInfo> rList,
786 boolean filterLastUsed) {
787 ChooserGridAdapter personalAdapter = createChooserGridAdapter(
788 /* context */ this,
789 /* payloadIntents */ mIntents,
790 initialIntents,
791 rList,
792 filterLastUsed,
793 mUseLayoutForBrowsables,
794 /* userHandle */ getPersonalProfileUserHandle());
795 ChooserGridAdapter workAdapter = createChooserGridAdapter(
796 /* context */ this,
797 /* payloadIntents */ mIntents,
798 initialIntents,
799 rList,
800 filterLastUsed,
801 mUseLayoutForBrowsables,
802 /* userHandle */ getWorkProfileUserHandle());
803 return new ChooserMultiProfilePagerAdapter(
804 /* context */ this,
805 personalAdapter,
806 workAdapter,
807 /* defaultProfile */ getCurrentProfile());
808 }
809
810 @Override
811 protected boolean postRebuildList(boolean rebuildCompleted) {
812 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().maybeLogActionShareWithPreview();
813 return postRebuildListInternal(rebuildCompleted);
814 }
815
Matt Pietal26038402019-01-08 07:29:34 -0500816 /**
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700817 * Returns true if app prediction service is defined and the component exists on device.
818 */
Mehdi Alizadehe870e972019-09-11 17:54:15 -0700819 @VisibleForTesting
820 public boolean isAppPredictionServiceAvailable() {
821 if (getPackageManager().getAppPredictionServicePackageName() == null) {
822 // Default AppPredictionService is not defined.
823 return false;
824 }
825
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700826 final String appPredictionServiceName =
827 getString(R.string.config_defaultAppPredictionService);
828 if (appPredictionServiceName == null) {
829 return false;
830 }
831 final ComponentName appPredictionComponentName =
832 ComponentName.unflattenFromString(appPredictionServiceName);
833 if (appPredictionComponentName == null) {
834 return false;
835 }
836
837 // Check if the app prediction component actually exists on the device.
838 Intent intent = new Intent();
839 intent.setComponent(appPredictionComponentName);
840 if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) {
841 Log.e(TAG, "App prediction service is defined, but does not exist: "
842 + appPredictionServiceName);
843 return false;
844 }
845 return true;
846 }
847
848 /**
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500849 * Check if the profile currently used is a work profile.
850 * @return true if it is work profile, false if it is parent profile (or no work profile is
851 * set up)
852 */
853 protected boolean isWorkProfile() {
arangelov38a6fce2019-12-02 18:21:22 +0000854 return getSystemService(UserManager.class)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500855 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
856 }
857
Matt Pietalab73a882019-06-05 07:04:55 -0400858 @Override
859 protected PackageMonitor createPackageMonitor() {
860 return new PackageMonitor() {
861 @Override
862 public void onSomePackagesChanged() {
arangelov38a6fce2019-12-02 18:21:22 +0000863 // TODO(arangelov): Dispatch this to all adapters when we have the helper methods
864 // in a follow-up CL
865 mChooserMultiProfilePagerAdapter.getCurrentListAdapter().handlePackagesChanged();
arangelovb0802dc2019-10-18 18:03:44 +0100866 updateProfileViewButton();
Matt Pietalab73a882019-06-05 07:04:55 -0400867 }
868 };
869 }
870
Matt Pietal46d828c2019-02-05 08:07:07 -0500871 private void onCopyButtonClicked(View v) {
872 Intent targetIntent = getTargetIntent();
873 if (targetIntent == null) {
874 finish();
875 } else {
876 final String action = targetIntent.getAction();
877
878 ClipData clipData = null;
879 if (Intent.ACTION_SEND.equals(action)) {
880 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
881 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
882
883 if (extraText != null) {
884 clipData = ClipData.newPlainText(null, extraText);
885 } else if (extraStream != null) {
886 clipData = ClipData.newUri(getContentResolver(), null, extraStream);
887 } else {
888 Log.w(TAG, "No data available to copy to clipboard");
889 return;
890 }
891 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
892 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
893 Intent.EXTRA_STREAM);
894 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
895 for (int i = 1; i < streams.size(); i++) {
896 clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
897 }
898 } else {
899 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
900 // so warn about unexpected action
901 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
902 return;
903 }
904
905 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
906 Context.CLIPBOARD_SERVICE);
907 clipboardManager.setPrimaryClip(clipData);
908 Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
909
Alison Cichowlasaa7f79f2019-09-12 15:57:26 -0400910 // Log share completion via copy
911 LogMaker targetLogMaker = new LogMaker(
912 MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
913 getMetricsLogger().write(targetLogMaker);
914
Matt Pietal46d828c2019-02-05 08:07:07 -0500915 finish();
916 }
917 }
918
Matt Pietal18bbd822019-02-12 15:21:36 -0500919 @Override
920 public void onConfigurationChanged(Configuration newConfig) {
921 super.onConfigurationChanged(newConfig);
922
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400923 adjustPreviewWidth(newConfig.orientation, null);
924 }
925
926 private boolean shouldDisplayLandscape(int orientation) {
927 // Sharesheet fixes the # of items per row and therefore can not correctly lay out
928 // when in the restricted size of multi-window mode. In the future, would be nice
929 // to use minimum dp size requirements instead
930 return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
931 }
932
933 private void adjustPreviewWidth(int orientation, View parent) {
Matt Pietal18bbd822019-02-12 15:21:36 -0500934 int width = -1;
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400935 if (shouldDisplayLandscape(orientation)) {
Matt Pietal18bbd822019-02-12 15:21:36 -0500936 width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
937 }
938
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400939 parent = parent == null ? getWindow().getDecorView() : parent;
940
941 updateLayoutWidth(R.id.content_preview_text_layout, width, parent);
942 updateLayoutWidth(R.id.content_preview_title_layout, width, parent);
943 updateLayoutWidth(R.id.content_preview_file_layout, width, parent);
Matt Pietal18bbd822019-02-12 15:21:36 -0500944 }
945
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400946 private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
947 View view = parent.findViewById(layoutResourceId);
Matt Pietal1ef88002019-03-13 10:43:18 -0400948 if (view != null && view.getLayoutParams() != null) {
949 LayoutParams params = view.getLayoutParams();
950 params.width = width;
951 view.setLayoutParams(params);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500952 }
953 }
954
Matt Pietal1ef88002019-03-13 10:43:18 -0400955 private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
Zhen Zhangbde7b462019-11-11 11:49:33 -0800956 Intent targetIntent, LayoutInflater layoutInflater, ViewGroup parent) {
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400957 ViewGroup layout = null;
958
Matt Pietal1ef88002019-03-13 10:43:18 -0400959 switch (previewType) {
960 case CONTENT_PREVIEW_TEXT:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400961 layout = displayTextContentPreview(targetIntent, layoutInflater, parent);
962 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400963 case CONTENT_PREVIEW_IMAGE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400964 layout = displayImageContentPreview(targetIntent, layoutInflater, parent);
965 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400966 case CONTENT_PREVIEW_FILE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400967 layout = displayFileContentPreview(targetIntent, layoutInflater, parent);
968 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400969 default:
970 Log.e(TAG, "Unexpected content preview type: " + previewType);
971 }
Matt Pietal0ea391b2019-01-30 10:44:15 -0500972
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400973 if (layout != null) {
974 adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
975 }
976
977 return layout;
Matt Pietal1ef88002019-03-13 10:43:18 -0400978 }
979
980 private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -0400981 ViewGroup parent) {
982 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
983 R.layout.chooser_grid_preview_text, parent, false);
Matt Pietal1ef88002019-03-13 10:43:18 -0400984
985 contentPreviewLayout.findViewById(R.id.copy_button).setOnClickListener(
986 this::onCopyButtonClicked);
Matt Pietal46d828c2019-02-05 08:07:07 -0500987
Matt Pietal26038402019-01-08 07:29:34 -0500988 CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
Matt Pietal26038402019-01-08 07:29:34 -0500989 if (sharingText == null) {
Matt Pietal1ef88002019-03-13 10:43:18 -0400990 contentPreviewLayout.findViewById(R.id.content_preview_text_layout).setVisibility(
991 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500992 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -0400993 TextView textView = contentPreviewLayout.findViewById(R.id.content_preview_text);
Matt Pietal1fa7d802019-01-30 10:44:15 -0500994 textView.setText(sharingText);
Matt Pietal26038402019-01-08 07:29:34 -0500995 }
996
997 String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
Matt Pietal46d828c2019-02-05 08:07:07 -0500998 if (TextUtils.isEmpty(previewTitle)) {
Matt Pietal1ef88002019-03-13 10:43:18 -0400999 contentPreviewLayout.findViewById(R.id.content_preview_title_layout).setVisibility(
1000 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -05001001 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -04001002 TextView previewTitleView = contentPreviewLayout.findViewById(
1003 R.id.content_preview_title);
Matt Pietal26038402019-01-08 07:29:34 -05001004 previewTitleView.setText(previewTitle);
Matt Pietal26038402019-01-08 07:29:34 -05001005
Matt Pietal1fa7d802019-01-30 10:44:15 -05001006 ClipData previewData = targetIntent.getClipData();
1007 Uri previewThumbnail = null;
1008 if (previewData != null) {
1009 if (previewData.getItemCount() > 0) {
1010 ClipData.Item previewDataItem = previewData.getItemAt(0);
1011 previewThumbnail = previewDataItem.getUri();
1012 }
Matt Pietal26038402019-01-08 07:29:34 -05001013 }
Matt Pietal26038402019-01-08 07:29:34 -05001014
Matt Pietal1ef88002019-03-13 10:43:18 -04001015 ImageView previewThumbnailView = contentPreviewLayout.findViewById(
1016 R.id.content_preview_thumbnail);
Matt Pietal1fa7d802019-01-30 10:44:15 -05001017 if (previewThumbnail == null) {
Matt Pietal26038402019-01-08 07:29:34 -05001018 previewThumbnailView.setVisibility(View.GONE);
1019 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -04001020 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
1021 mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
Matt Pietal26038402019-01-08 07:29:34 -05001022 }
1023 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001024
1025 return contentPreviewLayout;
Matt Pietal26038402019-01-08 07:29:34 -05001026 }
1027
Matt Pietal1ef88002019-03-13 10:43:18 -04001028 private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001029 ViewGroup parent) {
1030 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1031 R.layout.chooser_grid_preview_image, parent, false);
1032 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001033
1034 String action = targetIntent.getAction();
1035 if (Intent.ACTION_SEND.equals(action)) {
1036 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietale7cacab2019-05-23 07:21:36 -04001037 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001038 } else {
1039 ContentResolver resolver = getContentResolver();
1040
1041 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1042 List<Uri> imageUris = new ArrayList<>();
1043 for (Uri uri : uris) {
1044 if (isImageType(resolver.getType(uri))) {
1045 imageUris.add(uri);
1046 }
1047 }
1048
1049 if (imageUris.size() == 0) {
1050 Log.i(TAG, "Attempted to display image preview area with zero"
1051 + " available images detected in EXTRA_STREAM list");
Matt Pietal46d828c2019-02-05 08:07:07 -05001052 contentPreviewLayout.setVisibility(View.GONE);
Matt Pietal1ef88002019-03-13 10:43:18 -04001053 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001054 }
1055
Matt Pietale7cacab2019-05-23 07:21:36 -04001056 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001057
1058 if (imageUris.size() == 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001059 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
1060 imageUris.get(1), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001061 } else if (imageUris.size() > 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001062 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
1063 imageUris.get(1), 0);
1064 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
1065 imageUris.get(2), imageUris.size() - 3);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001066 }
1067 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001068
1069 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001070 }
1071
Matt Pietal46d828c2019-02-05 08:07:07 -05001072 private static class FileInfo {
1073 public final String name;
1074 public final boolean hasThumbnail;
1075
1076 FileInfo(String name, boolean hasThumbnail) {
1077 this.name = name;
1078 this.hasThumbnail = hasThumbnail;
1079 }
1080 }
1081
Matt Pietalf38e9d22019-02-15 10:01:03 -05001082 /**
1083 * Wrapping the ContentResolver call to expose for easier mocking,
1084 * and to avoid mocking Android core classes.
1085 */
1086 @VisibleForTesting
1087 public Cursor queryResolver(ContentResolver resolver, Uri uri) {
1088 return resolver.query(uri, null, null, null, null);
1089 }
1090
Matt Pietal46d828c2019-02-05 08:07:07 -05001091 private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
1092 String fileName = null;
1093 boolean hasThumbnail = false;
Matt Pietal3087bca2019-02-14 12:19:16 -05001094
Matt Pietalf38e9d22019-02-15 10:01:03 -05001095 try (Cursor cursor = queryResolver(resolver, uri)) {
1096 if (cursor != null && cursor.getCount() > 0) {
1097 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
1098 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
1099 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
1100
1101 cursor.moveToFirst();
1102 if (nameIndex != -1) {
1103 fileName = cursor.getString(nameIndex);
1104 } else if (titleIndex != -1) {
1105 fileName = cursor.getString(titleIndex);
1106 }
1107
1108 if (flagsIndex != -1) {
1109 hasThumbnail = (cursor.getInt(flagsIndex)
1110 & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
1111 }
1112 }
Matt Pietal73a873f2019-03-15 08:46:20 -04001113 } catch (SecurityException | NullPointerException e) {
Matt Pietal62532e52019-05-07 09:51:37 -04001114 logContentPreviewWarning(uri);
Matt Pietal3087bca2019-02-14 12:19:16 -05001115 }
1116
Matt Pietal46d828c2019-02-05 08:07:07 -05001117 if (TextUtils.isEmpty(fileName)) {
1118 fileName = uri.getPath();
1119 int index = fileName.lastIndexOf('/');
1120 if (index != -1) {
1121 fileName = fileName.substring(index + 1);
1122 }
1123 }
1124
1125 return new FileInfo(fileName, hasThumbnail);
1126 }
1127
Matt Pietal62532e52019-05-07 09:51:37 -04001128 private void logContentPreviewWarning(Uri uri) {
1129 // The ContentResolver already logs the exception. Log something more informative.
1130 Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
1131 + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
1132 + "and set your Intent's clipData and flags in accordance with that method's "
1133 + "documentation");
1134 }
1135
Matt Pietal1ef88002019-03-13 10:43:18 -04001136 private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001137 ViewGroup parent) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001138
Matt Pietale7cacab2019-05-23 07:21:36 -04001139 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1140 R.layout.chooser_grid_preview_file, parent, false);
Matt Pietal46d828c2019-02-05 08:07:07 -05001141
1142 // TODO(b/120417119): Disable file copy until after moving to sysui,
1143 // due to permissions issues
Matt Pietal1ef88002019-03-13 10:43:18 -04001144 contentPreviewLayout.findViewById(R.id.file_copy_button).setVisibility(View.GONE);
Matt Pietal46d828c2019-02-05 08:07:07 -05001145
Matt Pietal3087bca2019-02-14 12:19:16 -05001146 String action = targetIntent.getAction();
1147 if (Intent.ACTION_SEND.equals(action)) {
1148 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietal1ef88002019-03-13 10:43:18 -04001149 loadFileUriIntoView(uri, contentPreviewLayout);
Matt Pietal3087bca2019-02-14 12:19:16 -05001150 } else {
1151 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1152 int uriCount = uris.size();
Matt Pietal46d828c2019-02-05 08:07:07 -05001153
Matt Pietal3087bca2019-02-14 12:19:16 -05001154 if (uriCount == 0) {
1155 contentPreviewLayout.setVisibility(View.GONE);
1156 Log.i(TAG,
1157 "Appears to be no uris available in EXTRA_STREAM, removing "
1158 + "preview area");
Matt Pietal1ef88002019-03-13 10:43:18 -04001159 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001160 } else if (uriCount == 1) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001161 loadFileUriIntoView(uris.get(0), contentPreviewLayout);
Matt Pietal46d828c2019-02-05 08:07:07 -05001162 } else {
Matt Pietal3087bca2019-02-14 12:19:16 -05001163 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
1164 int remUriCount = uriCount - 1;
Matt Pietalacabc572019-02-14 11:02:05 -05001165 String fileName = getResources().getQuantityString(R.plurals.file_count,
Matt Pietal3087bca2019-02-14 12:19:16 -05001166 remUriCount, fileInfo.name, remUriCount);
Matt Pietalacabc572019-02-14 11:02:05 -05001167
Matt Pietal1ef88002019-03-13 10:43:18 -04001168 TextView fileNameView = contentPreviewLayout.findViewById(
1169 R.id.content_preview_filename);
Matt Pietalacabc572019-02-14 11:02:05 -05001170 fileNameView.setText(fileName);
Matt Pietal3087bca2019-02-14 12:19:16 -05001171
Matt Pietale7cacab2019-05-23 07:21:36 -04001172 View thumbnailView = contentPreviewLayout.findViewById(
1173 R.id.content_preview_file_thumbnail);
1174 thumbnailView.setVisibility(View.GONE);
1175
Matt Pietal1ef88002019-03-13 10:43:18 -04001176 ImageView fileIconView = contentPreviewLayout.findViewById(
1177 R.id.content_preview_file_icon);
Matt Pietal46d828c2019-02-05 08:07:07 -05001178 fileIconView.setVisibility(View.VISIBLE);
Matt Pietalacabc572019-02-14 11:02:05 -05001179 fileIconView.setImageResource(R.drawable.ic_file_copy);
Matt Pietal46d828c2019-02-05 08:07:07 -05001180 }
Matt Pietal3087bca2019-02-14 12:19:16 -05001181 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001182
1183 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001184 }
1185
Matt Pietale7cacab2019-05-23 07:21:36 -04001186 private void loadFileUriIntoView(final Uri uri, final View parent) {
Matt Pietal3087bca2019-02-14 12:19:16 -05001187 FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
1188
Matt Pietal1ef88002019-03-13 10:43:18 -04001189 TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
Matt Pietal3087bca2019-02-14 12:19:16 -05001190 fileNameView.setText(fileInfo.name);
1191
1192 if (fileInfo.hasThumbnail) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001193 mPreviewCoord = new ContentPreviewCoordinator(parent, false);
1194 mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
Matt Pietal3087bca2019-02-14 12:19:16 -05001195 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -04001196 View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
1197 thumbnailView.setVisibility(View.GONE);
1198
Matt Pietal1ef88002019-03-13 10:43:18 -04001199 ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
Matt Pietal3087bca2019-02-14 12:19:16 -05001200 fileIconView.setVisibility(View.VISIBLE);
Matt Pietal832cdbf2019-04-05 13:20:31 -04001201 fileIconView.setImageResource(R.drawable.chooser_file_generic);
Matt Pietal46d828c2019-02-05 08:07:07 -05001202 }
Matt Pietal0ea391b2019-01-30 10:44:15 -05001203 }
1204
Matt Pietal0ea391b2019-01-30 10:44:15 -05001205 @VisibleForTesting
1206 protected boolean isImageType(String mimeType) {
1207 return mimeType != null && mimeType.startsWith("image/");
1208 }
1209
1210 @ContentPreviewType
1211 private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
1212 if (uri == null) {
1213 return CONTENT_PREVIEW_TEXT;
1214 }
1215
1216 String mimeType = resolver.getType(uri);
1217 return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
1218 }
1219
1220 /**
1221 * In {@link android.content.Intent#getType}, the app may specify a very general
1222 * mime-type that broadly covers all data being shared, such as {@literal *}/*
1223 * when sending an image and text. We therefore should inspect each item for the
1224 * the preferred type, in order of IMAGE, FILE, TEXT.
1225 */
1226 @ContentPreviewType
1227 private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
1228 String action = targetIntent.getAction();
1229 if (Intent.ACTION_SEND.equals(action)) {
1230 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1231 return findPreferredContentPreview(uri, resolver);
1232 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1233 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1234 if (uris == null || uris.isEmpty()) {
1235 return CONTENT_PREVIEW_TEXT;
1236 }
1237
1238 for (Uri uri : uris) {
Matt Pietal832cdbf2019-04-05 13:20:31 -04001239 // Defaulting to file preview when there are mixed image/file types is
1240 // preferable, as it shows the user the correct number of items being shared
1241 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_FILE) {
1242 return CONTENT_PREVIEW_FILE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001243 }
1244 }
1245
Matt Pietal832cdbf2019-04-05 13:20:31 -04001246 return CONTENT_PREVIEW_IMAGE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001247 }
1248
1249 return CONTENT_PREVIEW_TEXT;
1250 }
1251
Mike Digman849a9d12019-04-29 11:20:48 -07001252 private int getNumSheetExpansions() {
1253 return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0);
1254 }
1255
1256 private void incrementNumSheetExpansions() {
1257 getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS,
1258 getNumSheetExpansions() + 1).apply();
1259 }
1260
Adam Powell0b3c1122014-10-09 12:50:14 -07001261 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07001262 protected void onDestroy() {
1263 super.onDestroy();
1264 if (mRefinementResultReceiver != null) {
1265 mRefinementResultReceiver.destroy();
1266 mRefinementResultReceiver = null;
1267 }
Adam Powell9761ab22015-09-08 17:01:49 -07001268 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -04001269 mChooserHandler.removeAllMessages();
Matt Pietale7cacab2019-05-23 07:21:36 -04001270
1271 if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
1272
George Hodulik145b3a52019-03-27 11:18:43 -07001273 if (mAppPredictor != null) {
George Hodulik69d4a082019-01-18 11:27:03 -08001274 mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
1275 mAppPredictor.destroy();
1276 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001277 }
1278
arangelovb0802dc2019-10-18 18:03:44 +01001279 @Override // ResolverListCommunicator
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001280 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
1281 Intent result = defIntent;
Adam Powelle49d9392014-07-17 18:45:19 -07001282 if (mReplacementExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001283 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
Adam Powelle49d9392014-07-17 18:45:19 -07001284 if (replExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001285 result = new Intent(defIntent);
Adam Powelle49d9392014-07-17 18:45:19 -07001286 result.putExtras(replExtras);
Adam Powelle49d9392014-07-17 18:45:19 -07001287 }
1288 }
Nicolas Prevot741abfc2015-08-11 12:03:51 +01001289 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001290 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
1291 result = Intent.createChooser(result,
1292 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
Hakan Seyalioglu7317e8a2016-12-12 16:15:38 -08001293
1294 // Don't auto-launch single intents if the intent is being forwarded. This is done
1295 // because automatically launching a resolving application as a response to the user
1296 // action of switching accounts is pretty unexpected.
1297 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001298 }
1299 return result;
Adam Powelle49d9392014-07-17 18:45:19 -07001300 }
1301
Adam Powell0b3c1122014-10-09 12:50:14 -07001302 @Override
Adam Powell23882512016-01-29 10:21:00 -08001303 public void onActivityStarted(TargetInfo cti) {
Adam Powell0b3c1122014-10-09 12:50:14 -07001304 if (mChosenComponentSender != null) {
Adam Powell24428412015-04-01 17:19:56 -07001305 final ComponentName target = cti.getResolvedComponentName();
Adam Powell0b3c1122014-10-09 12:50:14 -07001306 if (target != null) {
1307 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
1308 try {
1309 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
1310 } catch (IntentSender.SendIntentException e) {
1311 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
1312 + "the chosen component: " + e);
1313 }
1314 }
1315 }
1316 }
1317
Adam Powell24428412015-04-01 17:19:56 -07001318 @Override
arangelov38a6fce2019-12-02 18:21:22 +00001319 public void onPrepareAdapterView(ResolverListAdapter adapter) {
1320 mChooserMultiProfilePagerAdapter.getCurrentAdapterView().setVisibility(View.VISIBLE);
Adam Powell52c39212016-04-07 15:14:18 -07001321 if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
arangelov38a6fce2019-12-02 18:21:22 +00001322 mChooserMultiProfilePagerAdapter.getCurrentListAdapter().addServiceResults(
1323 /* origTarget */ null,
1324 Lists.newArrayList(mCallerChooserTargets),
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07001325 TARGET_TYPE_DEFAULT);
Adam Powell52c39212016-04-07 15:14:18 -07001326 }
arangelovb0802dc2019-10-18 18:03:44 +01001327 }
1328
1329 @Override
Adam Powell23882512016-01-29 10:21:00 -08001330 public int getLayoutResource() {
Adam Powell7d758002015-05-06 17:49:36 -07001331 return R.layout.chooser_grid;
Adam Powell24428412015-04-01 17:19:56 -07001332 }
1333
arangelovb0802dc2019-10-18 18:03:44 +01001334 @Override // ResolverListCommunicator
Adam Powell23882512016-01-29 10:21:00 -08001335 public boolean shouldGetActivityMetadata() {
Adam Powell24428412015-04-01 17:19:56 -07001336 return true;
1337 }
1338
Adam Powell9761ab22015-09-08 17:01:49 -07001339 @Override
Ben Lin145b0ca2016-10-14 14:23:40 -07001340 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
Hakan Seyalioglu13405c52017-01-31 19:01:31 -08001341 // Note that this is only safe because the Intent handled by the ChooserActivity is
1342 // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
1343 // method can not be replaced in the ResolverActivity whole hog.
Matt Pietala4b30072019-04-04 13:44:36 -04001344 if (!super.shouldAutoLaunchSingleChoice(target)) {
1345 return false;
1346 }
1347
1348 return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
Ben Lin145b0ca2016-10-14 14:23:40 -07001349 }
1350
1351 @Override
Adam Powell23882512016-01-29 10:21:00 -08001352 public void showTargetDetails(ResolveInfo ri) {
sanryhuang296ca9e2018-03-31 11:17:13 +08001353 if (ri == null) {
1354 return;
1355 }
1356
Adam Powell23882512016-01-29 10:21:00 -08001357 ComponentName name = ri.activityInfo.getComponentName();
Alison Cichowlas1fd47152019-11-14 19:50:55 -05001358 boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
Adam Powell23882512016-01-29 10:21:00 -08001359 ResolverTargetActionsDialogFragment f =
1360 new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
Alison Cichowlas1fd47152019-11-14 19:50:55 -05001361 name, pinned);
Adam Powell23882512016-01-29 10:21:00 -08001362 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1363 }
1364
Adam Powelle49d9392014-07-17 18:45:19 -07001365 private void modifyTargetIntent(Intent in) {
Matt Pietal95574b02019-03-13 08:12:25 -04001366 if (isSendAction(in)) {
Adam Powelle49d9392014-07-17 18:45:19 -07001367 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
Dianne Hackborn13420f22014-07-18 15:43:56 -07001368 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Adam Powelle49d9392014-07-17 18:45:19 -07001369 }
1370 }
Adam Powell24428412015-04-01 17:19:56 -07001371
Adam Powell2ed547e2015-04-29 18:45:04 -07001372 @Override
1373 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
1374 if (mRefinementIntentSender != null) {
1375 final Intent fillIn = new Intent();
1376 final List<Intent> sourceIntents = target.getAllSourceIntents();
1377 if (!sourceIntents.isEmpty()) {
1378 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
1379 if (sourceIntents.size() > 1) {
1380 final Intent[] alts = new Intent[sourceIntents.size() - 1];
1381 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
1382 alts[i - 1] = sourceIntents.get(i);
1383 }
1384 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
1385 }
1386 if (mRefinementResultReceiver != null) {
1387 mRefinementResultReceiver.destroy();
1388 }
1389 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
1390 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
1391 mRefinementResultReceiver);
1392 try {
1393 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
1394 return false;
1395 } catch (SendIntentException e) {
1396 Log.e(TAG, "Refinement IntentSender failed to send", e);
1397 }
1398 }
1399 }
Kang Li9fa2a2c2017-01-06 13:33:24 -08001400 updateModelAndChooserCounts(target);
Adam Powell2ed547e2015-04-29 18:45:04 -07001401 return super.onTargetSelected(target, alwaysCheck);
1402 }
1403
Adam Powell98b7f892015-06-19 12:38:45 -07001404 @Override
Adam Powell23882512016-01-29 10:21:00 -08001405 public void startSelected(int which, boolean always, boolean filtered) {
arangelov38a6fce2019-12-02 18:21:22 +00001406 ChooserListAdapter currentListAdapter =
1407 mChooserMultiProfilePagerAdapter.getCurrentListAdapter();
1408 TargetInfo targetInfo = currentListAdapter
1409 .targetInfoForPosition(which, filtered);
Matt Pietala4b30072019-04-04 13:44:36 -04001410 if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
1411 return;
1412 }
1413
Kang Li9082f5b2016-12-02 10:56:21 -08001414 final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
Adam Powell98b7f892015-06-19 12:38:45 -07001415 super.startSelected(which, always, filtered);
1416
arangelov38a6fce2019-12-02 18:21:22 +00001417 if (currentListAdapter.getCount() > 0) {
Adam Powell98b7f892015-06-19 12:38:45 -07001418 // Log the index of which type of target the user picked.
1419 // Lower values mean the ranking was better.
1420 int cat = 0;
1421 int value = which;
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001422 int directTargetAlsoRanked = -1;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001423 int numCallerProvided = 0;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001424 HashedStringCache.HashResult directTargetHashed = null;
arangelov38a6fce2019-12-02 18:21:22 +00001425 switch (currentListAdapter.getPositionTargetType(which)) {
Adam Powell98b7f892015-06-19 12:38:45 -07001426 case ChooserListAdapter.TARGET_SERVICE:
Chris Wrenf6e9228b2016-01-26 18:04:35 -05001427 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001428 // Log the package name + target name to answer the question if most users
1429 // share to mostly the same person or to a bunch of different people.
arangelov38a6fce2019-12-02 18:21:22 +00001430 ChooserTarget target = currentListAdapter.getChooserTargetForValue(value);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001431 directTargetHashed = HashedStringCache.getInstance().hashString(
1432 this,
1433 TAG,
1434 target.getComponentName().getPackageName()
1435 + target.getTitle().toString(),
1436 mMaxHashSaltDays);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001437 directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001438
1439 if (mCallerChooserTargets != null) {
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001440 numCallerProvided = mCallerChooserTargets.length;
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001441 }
Adam Powell98b7f892015-06-19 12:38:45 -07001442 break;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001443 case ChooserListAdapter.TARGET_CALLER:
Adam Powell98b7f892015-06-19 12:38:45 -07001444 case ChooserListAdapter.TARGET_STANDARD:
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001445 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
arangelov38a6fce2019-12-02 18:21:22 +00001446 value -= currentListAdapter.getSelectableServiceTargetCount();
1447 numCallerProvided = currentListAdapter.getCallerTargetCount();
Adam Powell98b7f892015-06-19 12:38:45 -07001448 break;
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04001449 case ChooserListAdapter.TARGET_STANDARD_AZ:
1450 // A-Z targets are unranked standard targets; we use -1 to mark that they
1451 // are from the alphabetical pool.
1452 value = -1;
1453 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
1454 break;
Adam Powell98b7f892015-06-19 12:38:45 -07001455 }
1456
1457 if (cat != 0) {
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001458 LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
1459 if (directTargetHashed != null) {
1460 targetLogMaker.addTaggedData(
1461 MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
1462 targetLogMaker.addTaggedData(
1463 MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
1464 directTargetHashed.saltGeneration);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001465 targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
1466 directTargetAlsoRanked);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001467 }
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001468 targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED,
1469 numCallerProvided);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001470 getMetricsLogger().write(targetLogMaker);
Adam Powell98b7f892015-06-19 12:38:45 -07001471 }
Kang Li9082f5b2016-12-02 10:56:21 -08001472
1473 if (mIsSuccessfullySelected) {
1474 if (DEBUG) {
1475 Log.d(TAG, "User Selection Time Cost is " + selectionCost);
1476 Log.d(TAG, "position of selected app/service/caller is " +
1477 Integer.toString(value));
1478 }
1479 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
1480 (int) selectionCost);
1481 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
1482 }
Adam Powell98b7f892015-06-19 12:38:45 -07001483 }
1484 }
1485
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001486 private int getRankedPosition(SelectableTargetInfo targetInfo) {
1487 String targetPackageName =
1488 targetInfo.getChooserTarget().getComponentName().getPackageName();
arangelov38a6fce2019-12-02 18:21:22 +00001489 ChooserListAdapter currentListAdapter =
1490 mChooserMultiProfilePagerAdapter.getCurrentListAdapter();
1491 int maxRankedResults = Math.min(currentListAdapter.mDisplayList.size(),
1492 MAX_LOG_RANK_POSITION);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001493
1494 for (int i = 0; i < maxRankedResults; i++) {
arangelov38a6fce2019-12-02 18:21:22 +00001495 if (currentListAdapter.mDisplayList.get(i)
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001496 .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
1497 return i;
1498 }
1499 }
1500 return -1;
1501 }
1502
Adam Powell24428412015-04-01 17:19:56 -07001503 void queryTargetServices(ChooserListAdapter adapter) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001504 mQueriedTargetServicesTimeMs = System.currentTimeMillis();
1505
Adam Powell24428412015-04-01 17:19:56 -07001506 final PackageManager pm = getPackageManager();
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001507 ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
Adam Powell24428412015-04-01 17:19:56 -07001508 int targetsToQuery = 0;
Matt Pietalab73a882019-06-05 07:04:55 -04001509
Adam Powell24428412015-04-01 17:19:56 -07001510 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
1511 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
Adam Powell3a09c522015-10-21 13:21:28 -07001512 if (adapter.getScore(dri) == 0) {
1513 // A score of 0 means the app hasn't been used in some time;
1514 // don't query it as it's not likely to be relevant.
1515 continue;
1516 }
Adam Powell24428412015-04-01 17:19:56 -07001517 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
arangelovb0802dc2019-10-18 18:03:44 +01001518 if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001519 && sm.hasShareTargets(ai.packageName)) {
1520 // Share targets will be queried from ShortcutManager
1521 continue;
1522 }
Adam Powell24428412015-04-01 17:19:56 -07001523 final Bundle md = ai.metaData;
1524 final String serviceName = md != null ? convertServiceName(ai.packageName,
1525 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
1526 if (serviceName != null) {
1527 final ComponentName serviceComponent = new ComponentName(
1528 ai.packageName, serviceName);
Matt Pietalab73a882019-06-05 07:04:55 -04001529
1530 if (mServicesRequested.contains(serviceComponent)) {
1531 continue;
1532 }
1533 mServicesRequested.add(serviceComponent);
1534
Adam Powell24428412015-04-01 17:19:56 -07001535 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
1536 .setComponent(serviceComponent);
1537
1538 if (DEBUG) {
1539 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
1540 }
1541
1542 try {
1543 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
1544 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
1545 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
1546 + " permission " + ChooserTargetService.BIND_PERMISSION
1547 + " - this service will not be queried for ChooserTargets."
1548 + " add android:permission=\""
1549 + ChooserTargetService.BIND_PERMISSION + "\""
1550 + " to the <service> tag for " + serviceComponent
1551 + " in the manifest.");
1552 continue;
1553 }
1554 } catch (NameNotFoundException e) {
Adam Powell52c39212016-04-07 15:14:18 -07001555 Log.e(TAG, "Could not look up service " + serviceComponent
1556 + "; component name not found");
Adam Powell24428412015-04-01 17:19:56 -07001557 continue;
1558 }
1559
Adam Powell9761ab22015-09-08 17:01:49 -07001560 final ChooserTargetServiceConnection conn =
1561 new ChooserTargetServiceConnection(this, dri);
Adam Powell52c39212016-04-07 15:14:18 -07001562
1563 // Explicitly specify Process.myUserHandle instead of calling bindService
1564 // to avoid the warning from calling from the system process without an explicit
1565 // user handle
1566 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
1567 Process.myUserHandle())) {
Adam Powell24428412015-04-01 17:19:56 -07001568 if (DEBUG) {
1569 Log.d(TAG, "Binding service connection for target " + dri
1570 + " intent " + serviceIntent);
1571 }
1572 mServiceConnections.add(conn);
1573 targetsToQuery++;
1574 }
1575 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001576 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
Matt Pietal26038402019-01-08 07:29:34 -05001577 if (DEBUG) {
1578 Log.d(TAG, "queryTargets hit query target limit "
1579 + QUERY_TARGET_SERVICE_LIMIT);
1580 }
Adam Powell24428412015-04-01 17:19:56 -07001581 break;
1582 }
1583 }
1584
Matt Pietalab73a882019-06-05 07:04:55 -04001585 mChooserHandler.restartServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -07001586 }
1587
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001588 private IntentFilter getTargetIntentFilter() {
1589 try {
1590 final Intent intent = getTargetIntent();
1591 String dataString = intent.getDataString();
1592 if (TextUtils.isEmpty(dataString)) {
1593 dataString = intent.getType();
1594 }
1595 return new IntentFilter(intent.getAction(), dataString);
1596 } catch (Exception e) {
1597 Log.e(TAG, "failed to get target intent filter " + e);
1598 return null;
1599 }
1600 }
1601
George Hodulik69d4a082019-01-18 11:27:03 -08001602 private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001603 // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
1604 // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
George Hodulik69d4a082019-01-18 11:27:03 -08001605 List<DisplayResolveInfo> driList = new ArrayList<>();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001606 int targetsToQuery = 0;
1607 for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
1608 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
1609 if (adapter.getScore(dri) == 0) {
1610 // A score of 0 means the app hasn't been used in some time;
1611 // don't query it as it's not likely to be relevant.
1612 continue;
1613 }
1614 driList.add(dri);
1615 targetsToQuery++;
1616 // TODO(b/121287224): Do we need this here? (similar to queryTargetServices)
1617 if (targetsToQuery >= SHARE_TARGET_QUERY_PACKAGE_LIMIT) {
1618 if (DEBUG) {
1619 Log.d(TAG, "queryTargets hit query target limit "
1620 + SHARE_TARGET_QUERY_PACKAGE_LIMIT);
1621 }
1622 break;
1623 }
1624 }
George Hodulik69d4a082019-01-18 11:27:03 -08001625 return driList;
1626 }
1627
George Hodulik3f399f22019-04-26 16:17:54 -07001628 private void queryDirectShareTargets(
1629 ChooserListAdapter adapter, boolean skipAppPredictionService) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001630 mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
George Hodulik3f399f22019-04-26 16:17:54 -07001631 if (!skipAppPredictionService) {
1632 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
1633 if (appPredictor != null) {
1634 appPredictor.requestPredictionUpdate();
1635 return;
1636 }
George Hodulik69d4a082019-01-18 11:27:03 -08001637 }
George Hodulik145b3a52019-03-27 11:18:43 -07001638 // Default to just querying ShortcutManager if AppPredictor not present.
arangelov38a6fce2019-12-02 18:21:22 +00001639 //TODO(arangelov) we're using mIntents here, investicate possible implications on work tab
George Hodulik69d4a082019-01-18 11:27:03 -08001640 final IntentFilter filter = getTargetIntentFilter();
1641 if (filter == null) {
1642 return;
1643 }
1644 final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001645
1646 AsyncTask.execute(() -> {
arangelov38a6fce2019-12-02 18:21:22 +00001647 //TODO(arangelov) use the selected probile tab's ShortcutManager
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001648 ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
1649 List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
George Hodulikaa5238c2019-04-18 14:17:51 -07001650 sendShareShortcutInfoList(resultList, driList, null);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001651 });
1652 }
1653
George Hodulik69d4a082019-01-18 11:27:03 -08001654 private void sendShareShortcutInfoList(
1655 List<ShortcutManager.ShareShortcutInfo> resultList,
George Hodulikaa5238c2019-04-18 14:17:51 -07001656 List<DisplayResolveInfo> driList,
1657 @Nullable List<AppTarget> appTargets) {
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001658 if (appTargets != null && appTargets.size() != resultList.size()) {
1659 throw new RuntimeException("resultList and appTargets must have the same size."
1660 + " resultList.size()=" + resultList.size()
1661 + " appTargets.size()=" + appTargets.size());
1662 }
1663
1664 for (int i = resultList.size() - 1; i >= 0; i--) {
1665 final String packageName = resultList.get(i).getTargetComponent().getPackageName();
1666 if (!isPackageEnabled(packageName)) {
1667 resultList.remove(i);
1668 if (appTargets != null) {
1669 appTargets.remove(i);
1670 }
1671 }
1672 }
1673
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07001674 // If |appTargets| is not null, results are from AppPredictionService and already sorted.
1675 final int shortcutType = (appTargets == null ? TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER :
1676 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
1677
George Hodulik69d4a082019-01-18 11:27:03 -08001678 // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
1679 // for direct share targets. After ShareSheet is refactored we should use the
1680 // ShareShortcutInfos directly.
1681 boolean resultMessageSent = false;
1682 for (int i = 0; i < driList.size(); i++) {
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001683 List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
George Hodulik69d4a082019-01-18 11:27:03 -08001684 for (int j = 0; j < resultList.size(); j++) {
1685 if (driList.get(i).getResolvedComponentName().equals(
1686 resultList.get(j).getTargetComponent())) {
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001687 matchingShortcuts.add(resultList.get(j));
George Hodulik69d4a082019-01-18 11:27:03 -08001688 }
1689 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001690 if (matchingShortcuts.isEmpty()) {
George Hodulik69d4a082019-01-18 11:27:03 -08001691 continue;
1692 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001693 List<ChooserTarget> chooserTargets = convertToChooserTarget(
1694 matchingShortcuts, resultList, appTargets, shortcutType);
1695
George Hodulik69d4a082019-01-18 11:27:03 -08001696 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04001697 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
George Hodulik69d4a082019-01-18 11:27:03 -08001698 msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07001699 msg.arg1 = shortcutType;
George Hodulik69d4a082019-01-18 11:27:03 -08001700 mChooserHandler.sendMessage(msg);
1701 resultMessageSent = true;
1702 }
1703
1704 if (resultMessageSent) {
George Hodulik145b3a52019-03-27 11:18:43 -07001705 sendShortcutManagerShareTargetResultCompleted();
George Hodulik69d4a082019-01-18 11:27:03 -08001706 }
1707 }
1708
George Hodulik145b3a52019-03-27 11:18:43 -07001709 private void sendShortcutManagerShareTargetResultCompleted() {
1710 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04001711 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
George Hodulik145b3a52019-03-27 11:18:43 -07001712 mChooserHandler.sendMessage(msg);
1713 }
1714
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001715 private boolean isPackageEnabled(String packageName) {
1716 if (TextUtils.isEmpty(packageName)) {
1717 return false;
1718 }
1719 ApplicationInfo appInfo;
1720 try {
1721 appInfo = getPackageManager().getApplicationInfo(packageName, 0);
1722 } catch (NameNotFoundException e) {
1723 return false;
1724 }
1725
1726 if (appInfo != null && appInfo.enabled
1727 && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
1728 return true;
1729 }
1730 return false;
1731 }
1732
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001733 /**
1734 * Converts a list of ShareShortcutInfos to ChooserTargets.
1735 * @param matchingShortcuts List of shortcuts, all from the same package, that match the current
1736 * share intent filter.
1737 * @param allShortcuts List of all the shortcuts from all the packages on the device that are
1738 * returned for the current sharing action.
1739 * @param allAppTargets List of AppTargets. Null if the results are not from prediction service.
1740 * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or
1741 * TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
1742 * @return A list of ChooserTargets sorted by score in descending order.
1743 */
1744 @VisibleForTesting
1745 @NonNull
1746 public List<ChooserTarget> convertToChooserTarget(
1747 @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts,
1748 @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts,
1749 @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) {
1750 // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted
1751 // list instead of the actual rank value when converting a rank to a score.
1752 List<Integer> scoreList = new ArrayList<>();
1753 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
1754 for (int i = 0; i < matchingShortcuts.size(); i++) {
1755 int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank();
1756 if (!scoreList.contains(shortcutRank)) {
1757 scoreList.add(shortcutRank);
1758 }
1759 }
1760 Collections.sort(scoreList);
1761 }
1762
1763 List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size());
1764 for (int i = 0; i < matchingShortcuts.size(); i++) {
1765 ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo();
1766 int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i));
1767
1768 float score;
1769 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
1770 // Incoming results are ordered. Create a score based on index in the original list.
1771 score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f);
1772 } else {
1773 // Create a score based on the rank of the shortcut.
1774 int rankIndex = scoreList.indexOf(shortcutInfo.getRank());
1775 score = Math.max(1.0f - (0.01f * rankIndex), 0.0f);
1776 }
1777
1778 Bundle extras = new Bundle();
1779 extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
1780 ChooserTarget chooserTarget = new ChooserTarget(shortcutInfo.getShortLabel(),
1781 null, // Icon will be loaded later if this target is selected to be shown.
1782 score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);
1783
1784 chooserTargetList.add(chooserTarget);
1785 if (mDirectShareAppTargetCache != null && allAppTargets != null) {
1786 mDirectShareAppTargetCache.put(chooserTarget,
1787 allAppTargets.get(indexInAllShortcuts));
1788 }
1789 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001790 // Sort ChooserTargets by score in descending order
1791 Comparator<ChooserTarget> byScore =
1792 (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore());
1793 Collections.sort(chooserTargetList, byScore);
1794 return chooserTargetList;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001795 }
1796
Adam Powell24428412015-04-01 17:19:56 -07001797 private String convertServiceName(String packageName, String serviceName) {
1798 if (TextUtils.isEmpty(serviceName)) {
1799 return null;
1800 }
1801
1802 final String fullName;
1803 if (serviceName.startsWith(".")) {
1804 // Relative to the app package. Prepend the app package name.
1805 fullName = packageName + serviceName;
1806 } else if (serviceName.indexOf('.') >= 0) {
1807 // Fully qualified package name.
1808 fullName = serviceName;
1809 } else {
1810 fullName = null;
1811 }
1812 return fullName;
1813 }
1814
1815 void unbindRemainingServices() {
1816 if (DEBUG) {
1817 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
1818 }
1819 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
1820 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
1821 if (DEBUG) Log.d(TAG, "unbinding " + conn);
1822 unbindService(conn);
Adam Powell9761ab22015-09-08 17:01:49 -07001823 conn.destroy();
Adam Powell24428412015-04-01 17:19:56 -07001824 }
Matt Pietalab73a882019-06-05 07:04:55 -04001825 mServicesRequested.clear();
Adam Powell24428412015-04-01 17:19:56 -07001826 mServiceConnections.clear();
Adam Powell24428412015-04-01 17:19:56 -07001827 }
1828
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001829 private void logDirectShareTargetReceived(int logCategory) {
1830 final long queryTime =
1831 logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
1832 ? mQueriedSharingShortcutsTimeMs : mQueriedTargetServicesTimeMs;
1833 final int apiLatency = (int) (System.currentTimeMillis() - queryTime);
1834 getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
1835 }
1836
Kang Li9fa2a2c2017-01-06 13:33:24 -08001837 void updateModelAndChooserCounts(TargetInfo info) {
Kang Li53b43142016-11-14 14:38:25 -08001838 if (info != null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001839 sendClickToAppPredictor(info);
Kang Li53b43142016-11-14 14:38:25 -08001840 final ResolveInfo ri = info.getResolveInfo();
Kang Li64b018e2017-01-05 17:30:06 -08001841 Intent targetIntent = getTargetIntent();
1842 if (ri != null && ri.activityInfo != null && targetIntent != null) {
arangelov38a6fce2019-12-02 18:21:22 +00001843 ChooserListAdapter currentListAdapter =
1844 mChooserMultiProfilePagerAdapter.getCurrentListAdapter();
1845 if (currentListAdapter != null) {
1846 currentListAdapter.updateModel(info.getResolvedComponentName());
1847 currentListAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
Kang Li9fa2a2c2017-01-06 13:33:24 -08001848 targetIntent.getAction());
Kang Li0cef910d2017-01-05 09:14:36 -08001849 }
Kang Li53b43142016-11-14 14:38:25 -08001850 if (DEBUG) {
Kang Li64b018e2017-01-05 17:30:06 -08001851 Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
Kang Li64b018e2017-01-05 17:30:06 -08001852 Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
Kang Li53b43142016-11-14 14:38:25 -08001853 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001854 } else if (DEBUG) {
Kang Li53b43142016-11-14 14:38:25 -08001855 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
1856 }
1857 }
Kang Li9082f5b2016-12-02 10:56:21 -08001858 mIsSuccessfullySelected = true;
Kang Li53b43142016-11-14 14:38:25 -08001859 }
1860
George Hodulikf2b0d342019-01-25 12:43:54 -08001861 private void sendClickToAppPredictor(TargetInfo targetInfo) {
George Hodulikaa5238c2019-04-18 14:17:51 -07001862 AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled();
1863 if (directShareAppPredictor == null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001864 return;
1865 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001866 if (!(targetInfo instanceof ChooserTargetInfo)) {
1867 return;
1868 }
1869 ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
George Hodulikaa5238c2019-04-18 14:17:51 -07001870 AppTarget appTarget = null;
1871 if (mDirectShareAppTargetCache != null) {
1872 appTarget = mDirectShareAppTargetCache.get(chooserTarget);
George Hodulikf2b0d342019-01-25 12:43:54 -08001873 }
George Hodulikaa5238c2019-04-18 14:17:51 -07001874 // This is a direct share click that was provided by the APS
1875 if (appTarget != null) {
1876 directShareAppPredictor.notifyAppTargetEvent(
1877 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
1878 .setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
1879 .build());
George Hodulikf2b0d342019-01-25 12:43:54 -08001880 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001881 }
1882
George Hodulik145b3a52019-03-27 11:18:43 -07001883 @Nullable
1884 private AppPredictor getAppPredictor() {
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -07001885 if (!mIsAppPredictorComponentAvailable) {
1886 return null;
1887 }
Mehdi Alizadehe870e972019-09-11 17:54:15 -07001888 if (mAppPredictor == null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001889 final IntentFilter filter = getTargetIntentFilter();
1890 Bundle extras = new Bundle();
1891 extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
1892 AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(this)
1893 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
1894 .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
1895 .setExtras(extras)
1896 .build();
1897 AppPredictionManager appPredictionManager
1898 = getSystemService(AppPredictionManager.class);
1899 mAppPredictor = appPredictionManager.createAppPredictionSession(appPredictionContext);
1900 }
1901 return mAppPredictor;
1902 }
1903
1904 /**
1905 * This will return an app predictor if it is enabled for direct share sorting
1906 * and if one exists. Otherwise, it returns null.
1907 */
1908 @Nullable
1909 private AppPredictor getAppPredictorForDirectShareIfEnabled() {
arangelovb0802dc2019-10-18 18:03:44 +01001910 return ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS
1911 && !ActivityManager.isLowRamDeviceStatic() ? getAppPredictor() : null;
George Hodulik145b3a52019-03-27 11:18:43 -07001912 }
1913
George Hodulikc681ce42019-04-12 17:10:31 -07001914 /**
1915 * This will return an app predictor if it is enabled for share activity sorting
1916 * and if one exists. Otherwise, it returns null.
1917 */
1918 @Nullable
1919 private AppPredictor getAppPredictorForShareActivitesIfEnabled() {
1920 return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? getAppPredictor() : null;
1921 }
1922
Adam Powell2ed547e2015-04-29 18:45:04 -07001923 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
1924 if (mRefinementResultReceiver != null) {
1925 mRefinementResultReceiver.destroy();
1926 mRefinementResultReceiver = null;
1927 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001928 if (selectedTarget == null) {
1929 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
1930 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
1931 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
1932 + " cannot match refined source intent " + matchingIntent);
Kang Li53b43142016-11-14 14:38:25 -08001933 } else {
1934 TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
1935 if (super.onTargetSelected(clonedTarget, false)) {
Kang Li9fa2a2c2017-01-06 13:33:24 -08001936 updateModelAndChooserCounts(clonedTarget);
Kang Li53b43142016-11-14 14:38:25 -08001937 finish();
1938 return;
1939 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001940 }
1941 onRefinementCanceled();
1942 }
1943
1944 void onRefinementCanceled() {
1945 if (mRefinementResultReceiver != null) {
1946 mRefinementResultReceiver.destroy();
1947 mRefinementResultReceiver = null;
1948 }
1949 finish();
1950 }
1951
1952 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
1953 final List<Intent> targetIntents = target.getAllSourceIntents();
1954 for (int i = 0, N = targetIntents.size(); i < N; i++) {
1955 final Intent targetIntent = targetIntents.get(i);
1956 if (targetIntent.filterEquals(matchingIntent)) {
1957 return true;
1958 }
1959 }
1960 return false;
1961 }
1962
Adam Powell666d82a2015-07-15 20:14:57 -07001963 void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
1964 if (targets == null) {
1965 return;
1966 }
1967
1968 final PackageManager pm = getPackageManager();
1969 for (int i = targets.size() - 1; i >= 0; i--) {
1970 final ChooserTarget target = targets.get(i);
1971 final ComponentName targetName = target.getComponentName();
1972 if (packageName != null && packageName.equals(targetName.getPackageName())) {
1973 // Anything from the original target's package is fine.
1974 continue;
1975 }
1976
1977 boolean remove;
1978 try {
1979 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
1980 remove = !ai.exported || ai.permission != null;
1981 } catch (NameNotFoundException e) {
1982 Log.e(TAG, "Target " + target + " returned by " + packageName
1983 + " component not found");
1984 remove = true;
1985 }
1986
1987 if (remove) {
1988 targets.remove(i);
1989 }
1990 }
1991 }
1992
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04001993 /**
1994 * Sort intents alphabetically based on display label.
1995 */
arangelovb0802dc2019-10-18 18:03:44 +01001996 static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04001997 Collator mCollator;
1998 AzInfoComparator(Context context) {
1999 mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
2000 }
2001
2002 @Override
arangelovb0802dc2019-10-18 18:03:44 +01002003 public int compare(
2004 DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002005 return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
2006 }
2007 }
2008
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -05002009 protected MetricsLogger getMetricsLogger() {
2010 if (mMetricsLogger == null) {
2011 mMetricsLogger = new MetricsLogger();
2012 }
2013 return mMetricsLogger;
2014 }
2015
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002016 public class ChooserListController extends ResolverListController {
2017 public ChooserListController(Context context,
2018 PackageManager pm,
2019 Intent targetIntent,
2020 String referrerPackageName,
George Hodulikc681ce42019-04-12 17:10:31 -07002021 int launchedFromUid,
arangelov38a6fce2019-12-02 18:21:22 +00002022 UserHandle userId,
George Hodulikc681ce42019-04-12 17:10:31 -07002023 AbstractResolverComparator resolverComparator) {
arangelov38a6fce2019-12-02 18:21:22 +00002024 super(context, pm, targetIntent, referrerPackageName, launchedFromUid, userId,
George Hodulikc681ce42019-04-12 17:10:31 -07002025 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002026 }
2027
2028 @Override
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002029 boolean isComponentFiltered(ComponentName name) {
2030 if (mFilteredComponentNames == null) {
2031 return false;
2032 }
2033 for (ComponentName filteredComponentName : mFilteredComponentNames) {
2034 if (name.equals(filteredComponentName)) {
2035 return true;
2036 }
2037 }
2038 return false;
2039 }
Alison Cichowlas1fd47152019-11-14 19:50:55 -05002040
2041 @Override
2042 public boolean isComponentPinned(ComponentName name) {
2043 return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
2044 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002045 }
2046
arangelov38a6fce2019-12-02 18:21:22 +00002047 @VisibleForTesting
2048 public ChooserGridAdapter createChooserGridAdapter(Context context,
2049 List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
2050 boolean filterLastUsed, boolean useLayoutForBrowsables, UserHandle userHandle) {
2051 return new ChooserGridAdapter(
2052 new ChooserListAdapter(context, payloadIntents, initialIntents, rList,
2053 filterLastUsed, createListController(userHandle), useLayoutForBrowsables,
2054 this, this));
Adam Powell24428412015-04-01 17:19:56 -07002055 }
2056
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002057 @VisibleForTesting
arangelov38a6fce2019-12-02 18:21:22 +00002058 protected ResolverListController createListController(UserHandle userHandle) {
George Hodulikc681ce42019-04-12 17:10:31 -07002059 AppPredictor appPredictor = getAppPredictorForShareActivitesIfEnabled();
2060 AbstractResolverComparator resolverComparator;
2061 if (appPredictor != null) {
2062 resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
George Hodulik3f399f22019-04-26 16:17:54 -07002063 getReferrerPackageName(), appPredictor, getUser());
George Hodulikc681ce42019-04-12 17:10:31 -07002064 } else {
2065 resolverComparator =
2066 new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
2067 getReferrerPackageName(), null);
2068 }
2069
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002070 return new ChooserListController(
2071 this,
2072 mPm,
2073 getTargetIntent(),
2074 getReferrerPackageName(),
George Hodulikc681ce42019-04-12 17:10:31 -07002075 mLaunchedFromUid,
arangelov38a6fce2019-12-02 18:21:22 +00002076 userHandle,
George Hodulikc681ce42019-04-12 17:10:31 -07002077 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002078 }
2079
Matt Pietal26038402019-01-08 07:29:34 -05002080 @VisibleForTesting
2081 protected Bitmap loadThumbnail(Uri uri, Size size) {
2082 if (uri == null || size == null) {
2083 return null;
2084 }
2085
2086 try {
Matt Pietal46d828c2019-02-05 08:07:07 -05002087 return ImageUtils.loadThumbnail(getContentResolver(), uri, size);
2088 } catch (IOException | NullPointerException | SecurityException ex) {
Matt Pietal62532e52019-05-07 09:51:37 -04002089 logContentPreviewWarning(uri);
Matt Pietal26038402019-01-08 07:29:34 -05002090 }
2091 return null;
2092 }
2093
arangelovb0802dc2019-10-18 18:03:44 +01002094 static final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
2095 public Drawable getDisplayIcon(Context context) {
Mike Digmanac1d88c2019-04-18 15:15:55 -07002096 AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
arangelovb0802dc2019-10-18 18:03:44 +01002097 context.getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
Mike Digmanac1d88c2019-04-18 15:15:55 -07002098 avd.start(); // Start animation after generation
2099 return avd;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002100 }
2101 }
2102
arangelovb0802dc2019-10-18 18:03:44 +01002103 static final class EmptyTargetInfo extends NotSelectableTargetInfo {
2104 public Drawable getDisplayIcon(Context context) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002105 return null;
2106 }
2107 }
2108
Matt Pietal5b648562019-03-12 07:40:26 -04002109 private void handleScroll(View view, int x, int y, int oldx, int oldy) {
arangelov38a6fce2019-12-02 18:21:22 +00002110 if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
2111 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().handleScroll(view, y, oldy);
Matt Pietal5b648562019-03-12 07:40:26 -04002112 }
2113 }
2114
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002115 /*
2116 * Need to dynamically adjust how many icons can fit per row before we add them,
2117 * which also means setting the correct offset to initially show the content
2118 * preview area + 2 rows of targets
2119 */
2120 private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
2121 int oldTop, int oldRight, int oldBottom) {
arangelov38a6fce2019-12-02 18:21:22 +00002122 if (mChooserMultiProfilePagerAdapter == null) {
2123 return;
2124 }
2125 RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getCurrentAdapterView();
2126 ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
2127 if (gridAdapter == null || recyclerView == null) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002128 return;
2129 }
2130
Matt Pietalab73a882019-06-05 07:04:55 -04002131 final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
arangelov38a6fce2019-12-02 18:21:22 +00002132 if (gridAdapter.consumeLayoutRequest()
2133 || gridAdapter.calculateChooserTargetWidth(availableWidth)
2134 || recyclerView.getAdapter() == null
Matt Pietalab73a882019-06-05 07:04:55 -04002135 || availableWidth != mCurrAvailableWidth) {
2136 mCurrAvailableWidth = availableWidth;
arangelov38a6fce2019-12-02 18:21:22 +00002137 recyclerView.setAdapter(gridAdapter);
2138 ((GridLayoutManager) recyclerView.getLayoutManager())
2139 .setSpanCount(gridAdapter.getMaxTargetsPerRow());
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002140
2141 getMainThreadHandler().post(() -> {
arangelov38a6fce2019-12-02 18:21:22 +00002142 if (mResolverDrawerLayout == null || gridAdapter == null) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002143 return;
2144 }
2145
Matt Pietal800136a2019-05-08 07:46:39 -04002146 final int bottomInset = mSystemWindowInsets != null
2147 ? mSystemWindowInsets.bottom : 0;
2148 int offset = bottomInset;
arangelov38a6fce2019-12-02 18:21:22 +00002149 int rowsToShow = gridAdapter.getContentPreviewRowCount()
2150 + gridAdapter.getProfileRowCount()
2151 + gridAdapter.getServiceTargetRowCount()
2152 + gridAdapter.getCallerAndRankedTargetRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002153
2154 // then this is most likely not a SEND_* action, so check
2155 // the app target count
2156 if (rowsToShow == 0) {
arangelov38a6fce2019-12-02 18:21:22 +00002157 rowsToShow = gridAdapter.getRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002158 }
2159
2160 // still zero? then use a default height and leave, which
2161 // can happen when there are no targets to show
2162 if (rowsToShow == 0) {
Matt Pietal800136a2019-05-08 07:46:39 -04002163 offset += getResources().getDimensionPixelSize(
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002164 R.dimen.chooser_max_collapsed_height);
2165 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2166 return;
2167 }
2168
Matt Pietal394ebd02019-05-03 07:36:21 -04002169 int directShareHeight = 0;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002170 rowsToShow = Math.min(4, rowsToShow);
arangelov38a6fce2019-12-02 18:21:22 +00002171 for (int i = 0, childCount = recyclerView.getChildCount();
Zhen Zhangbde7b462019-11-11 11:49:33 -08002172 i < childCount && rowsToShow > 0; i++) {
arangelov38a6fce2019-12-02 18:21:22 +00002173 View child = recyclerView.getChildAt(i);
Zhen Zhangbde7b462019-11-11 11:49:33 -08002174 if (((GridLayoutManager.LayoutParams)
2175 child.getLayoutParams()).getSpanIndex() != 0) {
2176 continue;
2177 }
Matt Pietal394ebd02019-05-03 07:36:21 -04002178 int height = child.getHeight();
2179 offset += height;
2180
arangelov38a6fce2019-12-02 18:21:22 +00002181 if (gridAdapter.getTargetType(
2182 recyclerView.getChildAdapterPosition(child))
2183 == ChooserListAdapter.TARGET_SERVICE) {
Matt Pietal394ebd02019-05-03 07:36:21 -04002184 directShareHeight = height;
2185 }
Zhen Zhangbde7b462019-11-11 11:49:33 -08002186 rowsToShow--;
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002187 }
2188
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002189 boolean isExpandable = getResources().getConfiguration().orientation
2190 == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
arangelovb0802dc2019-10-18 18:03:44 +01002191 if (directShareHeight != 0 && isSendAction(getTargetIntent())
2192 && isExpandable) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002193 // make sure to leave room for direct share 4->8 expansion
Matt Pietal394ebd02019-05-03 07:36:21 -04002194 int requiredExpansionHeight =
2195 (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal800136a2019-05-08 07:46:39 -04002196 int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
Matt Pietal394ebd02019-05-03 07:36:21 -04002197 int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
Matt Pietal800136a2019-05-08 07:46:39 -04002198 - requiredExpansionHeight - topInset - bottomInset;
Matt Pietal394ebd02019-05-03 07:36:21 -04002199
2200 offset = Math.min(offset, minHeight);
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002201 }
2202
Matt Pietal399e8c72019-04-04 15:49:48 -04002203 mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top));
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002204 });
2205 }
2206 }
2207
Adam Powella182e452015-07-06 16:57:56 -07002208 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
2209 @Override
2210 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
2211 // Descending order
Adam Powell77a533f2015-10-16 10:47:32 -07002212 return (int) Math.signum(rhs.getScore() - lhs.getScore());
Adam Powella182e452015-07-06 16:57:56 -07002213 }
2214 }
2215
arangelovb0802dc2019-10-18 18:03:44 +01002216 @Override // ResolverListCommunicator
2217 public void onHandlePackagesChanged() {
2218 mServicesRequested.clear();
arangelov38a6fce2019-12-02 18:21:22 +00002219 mChooserMultiProfilePagerAdapter.getCurrentListAdapter().notifyDataSetChanged();
arangelovb0802dc2019-10-18 18:03:44 +01002220 super.onHandlePackagesChanged();
2221 }
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002222
arangelovb0802dc2019-10-18 18:03:44 +01002223 @Override // SelectableTargetInfoCommunicator
2224 public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) {
arangelov38a6fce2019-12-02 18:21:22 +00002225 return mChooserMultiProfilePagerAdapter.getCurrentListAdapter().makePresentationGetter(info);
arangelovb0802dc2019-10-18 18:03:44 +01002226 }
2227
2228 @Override // SelectableTargetInfoCommunicator
2229 public Intent getReferrerFillInIntent() {
2230 return mReferrerFillInIntent;
2231 }
2232
2233 @Override // ChooserListCommunicator
2234 public int getMaxRankedTargets() {
arangelov38a6fce2019-12-02 18:21:22 +00002235 return mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() == null
Zhen Zhangbde7b462019-11-11 11:49:33 -08002236 ? ChooserGridAdapter.MAX_TARGETS_PER_ROW_PORTRAIT
arangelov38a6fce2019-12-02 18:21:22 +00002237 : mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().getMaxTargetsPerRow();
arangelovb0802dc2019-10-18 18:03:44 +01002238 }
2239
2240 @Override // ChooserListCommunicator
2241 public void sendListViewUpdateMessage() {
2242 mChooserHandler.sendEmptyMessageDelayed(ChooserHandler.LIST_VIEW_UPDATE_MESSAGE,
2243 LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
2244 }
2245
2246 @Override
2247 public void onListRebuilt() {
arangelov38a6fce2019-12-02 18:21:22 +00002248 final ChooserListAdapter currentListAdapter =
2249 mChooserMultiProfilePagerAdapter.getCurrentListAdapter();
2250 if (currentListAdapter.mDisplayList == null
2251 || currentListAdapter.mDisplayList.isEmpty()) {
2252 currentListAdapter.notifyDataSetChanged();
arangelovb0802dc2019-10-18 18:03:44 +01002253 } else {
2254 new AsyncTask<Void, Void, Void>() {
2255 @Override
2256 protected Void doInBackground(Void... voids) {
arangelov38a6fce2019-12-02 18:21:22 +00002257 currentListAdapter.updateAlphabeticalList();
arangelovb0802dc2019-10-18 18:03:44 +01002258 return null;
2259 }
2260 @Override
2261 protected void onPostExecute(Void aVoid) {
arangelov38a6fce2019-12-02 18:21:22 +00002262 currentListAdapter.notifyDataSetChanged();
arangelovb0802dc2019-10-18 18:03:44 +01002263 }
2264 }.execute();
2265 }
2266
2267 // don't support direct share on low ram devices
2268 if (ActivityManager.isLowRamDeviceStatic()) {
2269 return;
2270 }
2271
2272 if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
2273 || ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
2274 if (DEBUG) {
2275 Log.d(TAG, "querying direct share targets from ShortcutManager");
2276 }
2277
arangelov38a6fce2019-12-02 18:21:22 +00002278 queryDirectShareTargets(currentListAdapter, false);
arangelovb0802dc2019-10-18 18:03:44 +01002279 }
2280 if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
2281 if (DEBUG) {
2282 Log.d(TAG, "List built querying services");
2283 }
2284
arangelov38a6fce2019-12-02 18:21:22 +00002285 queryTargetServices(currentListAdapter);
arangelovb0802dc2019-10-18 18:03:44 +01002286 }
2287 }
2288
2289 @Override // ChooserListCommunicator
2290 public boolean isSendAction(Intent targetIntent) {
Matt Pietal95574b02019-03-13 08:12:25 -04002291 if (targetIntent == null) {
2292 return false;
2293 }
2294
2295 String action = targetIntent.getAction();
2296 if (action == null) {
2297 return false;
2298 }
2299
2300 if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
2301 return true;
2302 }
2303
2304 return false;
2305 }
2306
Zhen Zhangbde7b462019-11-11 11:49:33 -08002307 /**
2308 * Used to bind types of individual item including
2309 * {@link ChooserGridAdapter#VIEW_TYPE_NORMAL},
2310 * {@link ChooserGridAdapter#VIEW_TYPE_CONTENT_PREVIEW},
2311 * {@link ChooserGridAdapter#VIEW_TYPE_PROFILE},
2312 * and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}.
2313 */
2314 final class ItemViewHolder extends RecyclerView.ViewHolder {
2315 ResolverListAdapter.ViewHolder mWrappedViewHolder;
2316 int mListPosition = ChooserListAdapter.NO_POSITION;
2317
2318 ItemViewHolder(View itemView, boolean isClickable) {
2319 super(itemView);
2320 mWrappedViewHolder = new ResolverListAdapter.ViewHolder(itemView);
2321 if (isClickable) {
2322 itemView.setOnClickListener(v -> startSelected(mListPosition,
2323 false/* always */, true/* filterd */));
2324 itemView.setOnLongClickListener(v -> {
2325 showTargetDetails(
arangelov38a6fce2019-12-02 18:21:22 +00002326 mChooserMultiProfilePagerAdapter.getCurrentListAdapter()
2327 .resolveInfoForPosition(mListPosition, /* filtered */ true));
Zhen Zhangbde7b462019-11-11 11:49:33 -08002328 return true;
2329 });
2330 }
2331 }
2332 }
2333
2334 /**
arangelov38a6fce2019-12-02 18:21:22 +00002335 * Intentionally override the {@link ResolverActivity} implementation as we only need that
2336 * implementation for the intent resolver case.
2337 */
2338 @Override
2339 protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
2340 return insets.consumeSystemWindowInsets();
2341 }
2342
2343 /**
2344 * Intentionally override the {@link ResolverActivity} implementation as we only need that
2345 * implementation for the intent resolver case.
2346 */
2347 @Override
2348 public void onButtonClick(View v) {}
2349
2350 /**
2351 * Intentionally override the {@link ResolverActivity} implementation as we only need that
2352 * implementation for the intent resolver case.
2353 */
2354 @Override
2355 protected void resetButtonBar() {}
2356
2357 /**
Zhen Zhangbde7b462019-11-11 11:49:33 -08002358 * Adapter for all types of items and targets in ShareSheet.
2359 * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the
2360 * row level by this adapter but not on the item level. Individual targets within the row are
2361 * handled by {@link ChooserListAdapter}
2362 */
2363 final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
Adam Powell7d758002015-05-06 17:49:36 -07002364 private ChooserListAdapter mChooserListAdapter;
2365 private final LayoutInflater mLayoutInflater;
Adam Powell7d758002015-05-06 17:49:36 -07002366
Matt Pietal5b648562019-03-12 07:40:26 -04002367 private DirectShareViewHolder mDirectShareViewHolder;
Matt Pietalab986b52019-04-10 10:14:32 -04002368 private int mChooserTargetWidth = 0;
Mike Digman849a9d12019-04-29 11:20:48 -07002369 private boolean mShowAzLabelIfPoss;
Matt Pietal5b648562019-03-12 07:40:26 -04002370
Matt Pietale7cacab2019-05-23 07:21:36 -04002371 private boolean mHideContentPreview = false;
2372 private boolean mLayoutRequested = false;
2373
Matt Pietal5b648562019-03-12 07:40:26 -04002374 private static final int VIEW_TYPE_DIRECT_SHARE = 0;
2375 private static final int VIEW_TYPE_NORMAL = 1;
Matt Pietal1ef88002019-03-13 10:43:18 -04002376 private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002377 private static final int VIEW_TYPE_PROFILE = 3;
Mike Digmanae730b12019-04-25 11:10:31 -07002378 private static final int VIEW_TYPE_AZ_LABEL = 4;
Zhen Zhangbde7b462019-11-11 11:49:33 -08002379 private static final int VIEW_TYPE_CALLER_AND_RANK = 5;
Matt Pietal5b648562019-03-12 07:40:26 -04002380
Matt Pietalfaedea82019-03-21 10:36:54 -04002381 private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
2382 private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
2383
Mike Digman849a9d12019-04-29 11:20:48 -07002384 private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
2385
Zhen Zhangbde7b462019-11-11 11:49:33 -08002386 ChooserGridAdapter(ChooserListAdapter wrappedAdapter) {
2387 super();
Adam Powell7d758002015-05-06 17:49:36 -07002388 mChooserListAdapter = wrappedAdapter;
2389 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
2390
Mike Digman849a9d12019-04-29 11:20:48 -07002391 mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
2392
Adam Powell7d758002015-05-06 17:49:36 -07002393 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
2394 @Override
2395 public void onChanged() {
2396 super.onChanged();
2397 notifyDataSetChanged();
2398 }
2399
2400 @Override
2401 public void onInvalidated() {
2402 super.onInvalidated();
Zhen Zhangbde7b462019-11-11 11:49:33 -08002403 notifyDataSetChanged();
Adam Powell7d758002015-05-06 17:49:36 -07002404 }
2405 });
2406 }
2407
Matt Pietalfaedea82019-03-21 10:36:54 -04002408 /**
Matt Pietalab986b52019-04-10 10:14:32 -04002409 * Calculate the chooser target width to maximize space per item
Matt Pietalfaedea82019-03-21 10:36:54 -04002410 *
2411 * @param width The new row width to use for recalculation
Matt Pietalab986b52019-04-10 10:14:32 -04002412 * @return true if the view width has changed
Matt Pietalfaedea82019-03-21 10:36:54 -04002413 */
Matt Pietalab986b52019-04-10 10:14:32 -04002414 public boolean calculateChooserTargetWidth(int width) {
Matt Pietalab986b52019-04-10 10:14:32 -04002415 if (width == 0) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002416 return false;
2417 }
2418
Zhen Zhangbde7b462019-11-11 11:49:33 -08002419 int newWidth = width / getMaxTargetsPerRow();
Matt Pietalab986b52019-04-10 10:14:32 -04002420 if (newWidth != mChooserTargetWidth) {
2421 mChooserTargetWidth = newWidth;
Matt Pietalfaedea82019-03-21 10:36:54 -04002422 return true;
2423 }
2424
2425 return false;
2426 }
2427
arangelov38a6fce2019-12-02 18:21:22 +00002428 int getMaxTargetsPerRow() {
Matt Pietalfaedea82019-03-21 10:36:54 -04002429 int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002430 if (shouldDisplayLandscape(getResources().getConfiguration().orientation)) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002431 maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
2432 }
Matt Pietalab986b52019-04-10 10:14:32 -04002433 return maxTargets;
Matt Pietal5b648562019-03-12 07:40:26 -04002434 }
2435
Matt Pietale7cacab2019-05-23 07:21:36 -04002436 public void hideContentPreview() {
2437 mHideContentPreview = true;
2438 mLayoutRequested = true;
2439 notifyDataSetChanged();
2440 }
2441
2442 public boolean consumeLayoutRequest() {
2443 boolean oldValue = mLayoutRequested;
2444 mLayoutRequested = false;
2445 return oldValue;
2446 }
2447
Zhen Zhangbde7b462019-11-11 11:49:33 -08002448 public int getRowCount() {
Adam Powell7d758002015-05-06 17:49:36 -07002449 return (int) (
Matt Pietal1ef88002019-03-13 10:43:18 -04002450 getContentPreviewRowCount()
Matt Pietal74c6ed02019-04-18 13:38:46 -04002451 + getProfileRowCount()
Matt Pietal26038402019-01-08 07:29:34 -05002452 + getServiceTargetRowCount()
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002453 + getCallerAndRankedTargetRowCount()
Mike Digmanae730b12019-04-25 11:10:31 -07002454 + getAzLabelRowCount()
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002455 + Math.ceil(
2456 (float) mChooserListAdapter.getAlphaTargetCount()
2457 / getMaxTargetsPerRow())
Adam Powell7d758002015-05-06 17:49:36 -07002458 );
2459 }
2460
Matt Pietal1ef88002019-03-13 10:43:18 -04002461 public int getContentPreviewRowCount() {
2462 if (!isSendAction(getTargetIntent())) {
2463 return 0;
2464 }
2465
Matt Pietale7cacab2019-05-23 07:21:36 -04002466 if (mHideContentPreview || mChooserListAdapter == null
2467 || mChooserListAdapter.getCount() == 0) {
Matt Pietal1ef88002019-03-13 10:43:18 -04002468 return 0;
2469 }
2470
2471 return 1;
2472 }
2473
Matt Pietal74c6ed02019-04-18 13:38:46 -04002474 public int getProfileRowCount() {
2475 return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
2476 }
2477
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002478 public int getCallerAndRankedTargetRowCount() {
Adam Powell63b31692015-09-28 10:45:00 -07002479 return (int) Math.ceil(
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002480 ((float) mChooserListAdapter.getCallerTargetCount()
2481 + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow());
Adam Powell63b31692015-09-28 10:45:00 -07002482 }
2483
Matt Pietal5b648562019-03-12 07:40:26 -04002484 // There can be at most one row in the listview, that is internally
2485 // a ViewGroup with 2 rows
Adam Powell63b31692015-09-28 10:45:00 -07002486 public int getServiceTargetRowCount() {
arangelovb0802dc2019-10-18 18:03:44 +01002487 if (isSendAction(getTargetIntent())
2488 && !ActivityManager.isLowRamDeviceStatic()) {
Matt Pietal95574b02019-03-13 08:12:25 -04002489 return 1;
2490 }
2491 return 0;
Adam Powell63b31692015-09-28 10:45:00 -07002492 }
2493
Mike Digmanae730b12019-04-25 11:10:31 -07002494 public int getAzLabelRowCount() {
2495 // Only show a label if the a-z list is showing
Mike Digman849a9d12019-04-29 11:20:48 -07002496 return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
Mike Digmanae730b12019-04-25 11:10:31 -07002497 }
2498
Adam Powell7d758002015-05-06 17:49:36 -07002499 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08002500 public int getItemCount() {
2501 return (int) (
2502 getContentPreviewRowCount()
2503 + getProfileRowCount()
2504 + getServiceTargetRowCount()
2505 + getCallerAndRankedTargetRowCount()
2506 + getAzLabelRowCount()
2507 + mChooserListAdapter.getAlphaTargetCount()
2508 );
Adam Powell7d758002015-05-06 17:49:36 -07002509 }
2510
2511 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08002512 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
2513 switch (viewType) {
2514 case VIEW_TYPE_CONTENT_PREVIEW:
2515 return new ItemViewHolder(createContentPreviewView(parent), false);
2516 case VIEW_TYPE_PROFILE:
2517 return new ItemViewHolder(createProfileView(parent), false);
2518 case VIEW_TYPE_AZ_LABEL:
2519 return new ItemViewHolder(createAzLabelView(parent), false);
2520 case VIEW_TYPE_NORMAL:
2521 return new ItemViewHolder(mChooserListAdapter.createView(parent), true);
2522 case VIEW_TYPE_DIRECT_SHARE:
2523 case VIEW_TYPE_CALLER_AND_RANK:
2524 return createItemGroupViewHolder(viewType, parent);
2525 default:
2526 // Since we catch all possible viewTypes above, no chance this is being called.
2527 return null;
2528 }
Adam Powell7d758002015-05-06 17:49:36 -07002529 }
2530
2531 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08002532 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Matt Pietal5b648562019-03-12 07:40:26 -04002533 int viewType = getItemViewType(position);
Zhen Zhangbde7b462019-11-11 11:49:33 -08002534 switch (viewType) {
2535 case VIEW_TYPE_DIRECT_SHARE:
2536 case VIEW_TYPE_CALLER_AND_RANK:
2537 bindItemGroupViewHolder(position, (ItemGroupViewHolder) holder);
2538 break;
2539 case VIEW_TYPE_NORMAL:
2540 bindItemViewHolder(position, (ItemViewHolder) holder);
2541 break;
2542 default:
Matt Pietal1ef88002019-03-13 10:43:18 -04002543 }
Adam Powell7d758002015-05-06 17:49:36 -07002544 }
2545
Matt Pietal5b648562019-03-12 07:40:26 -04002546 @Override
2547 public int getItemViewType(int position) {
Mike Digmanae730b12019-04-25 11:10:31 -07002548 int count;
Matt Pietal1ef88002019-03-13 10:43:18 -04002549
Mike Digmanae730b12019-04-25 11:10:31 -07002550 int countSum = (count = getContentPreviewRowCount());
2551 if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002552
Mike Digmanae730b12019-04-25 11:10:31 -07002553 countSum += (count = getProfileRowCount());
2554 if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
Adam Powell63b31692015-09-28 10:45:00 -07002555
Mike Digmanae730b12019-04-25 11:10:31 -07002556 countSum += (count = getServiceTargetRowCount());
2557 if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
2558
2559 countSum += (count = getCallerAndRankedTargetRowCount());
Zhen Zhangbde7b462019-11-11 11:49:33 -08002560 if (count > 0 && position < countSum) return VIEW_TYPE_CALLER_AND_RANK;
Mike Digmanae730b12019-04-25 11:10:31 -07002561
2562 countSum += (count = getAzLabelRowCount());
2563 if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
Matt Pietal5b648562019-03-12 07:40:26 -04002564
2565 return VIEW_TYPE_NORMAL;
2566 }
2567
Zhen Zhangbde7b462019-11-11 11:49:33 -08002568 public int getTargetType(int position) {
2569 return mChooserListAdapter.getPositionTargetType(getListPosition(position));
Matt Pietal1ef88002019-03-13 10:43:18 -04002570 }
2571
Zhen Zhangbde7b462019-11-11 11:49:33 -08002572 private ViewGroup createContentPreviewView(ViewGroup parent) {
Matt Pietal1ef88002019-03-13 10:43:18 -04002573 Intent targetIntent = getTargetIntent();
2574 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
Zhen Zhangbde7b462019-11-11 11:49:33 -08002575 return displayContentPreview(previewType, targetIntent, mLayoutInflater, parent);
Matt Pietal5b648562019-03-12 07:40:26 -04002576 }
2577
Zhen Zhangbde7b462019-11-11 11:49:33 -08002578 private View createProfileView(ViewGroup parent) {
2579 View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false);
Matt Pietal74c6ed02019-04-18 13:38:46 -04002580 profileRow.setBackground(
2581 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
2582 mProfileView = profileRow.findViewById(R.id.profile_button);
2583 mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
arangelovb0802dc2019-10-18 18:03:44 +01002584 updateProfileViewButton();
Matt Pietal74c6ed02019-04-18 13:38:46 -04002585 return profileRow;
2586 }
2587
Mike Digmanae730b12019-04-25 11:10:31 -07002588 private View createAzLabelView(ViewGroup parent) {
2589 return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
2590 }
2591
Zhen Zhangbde7b462019-11-11 11:49:33 -08002592 private ItemGroupViewHolder loadViewsIntoGroup(ItemGroupViewHolder holder) {
Matt Pietal5b648562019-03-12 07:40:26 -04002593 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietalab986b52019-04-10 10:14:32 -04002594 final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
2595 MeasureSpec.EXACTLY);
Matt Pietal5b648562019-03-12 07:40:26 -04002596 int columnCount = holder.getColumnCount();
2597
Mike Digmanba232682019-03-27 14:55:26 -07002598 final boolean isDirectShare = holder instanceof DirectShareViewHolder;
2599
Matt Pietal5b648562019-03-12 07:40:26 -04002600 for (int i = 0; i < columnCount; i++) {
Mike Digmanba232682019-03-27 14:55:26 -07002601 final View v = mChooserListAdapter.createView(holder.getRowByIndex(i));
Adam Powell4eb98712015-10-14 13:10:18 -07002602 final int column = i;
Adam Powell63b31692015-09-28 10:45:00 -07002603 v.setOnClickListener(new OnClickListener() {
2604 @Override
2605 public void onClick(View v) {
Matt Pietal5b648562019-03-12 07:40:26 -04002606 startSelected(holder.getItemIndex(column), false, true);
Adam Powell63b31692015-09-28 10:45:00 -07002607 }
2608 });
2609 v.setOnLongClickListener(new OnLongClickListener() {
2610 @Override
2611 public boolean onLongClick(View v) {
Adam Powell23882512016-01-29 10:21:00 -08002612 showTargetDetails(
Adam Powell4eb98712015-10-14 13:10:18 -07002613 mChooserListAdapter.resolveInfoForPosition(
Matt Pietal5b648562019-03-12 07:40:26 -04002614 holder.getItemIndex(column), true));
Adam Powell63b31692015-09-28 10:45:00 -07002615 return true;
2616 }
2617 });
Zhen Zhangbde7b462019-11-11 11:49:33 -08002618 holder.addView(i, v);
Adam Powell63b31692015-09-28 10:45:00 -07002619
Mike Digmanba232682019-03-27 14:55:26 -07002620 // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
2621 // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be
2622 // done before measuring.
2623 if (isDirectShare) {
2624 final ViewHolder vh = (ViewHolder) v.getTag();
2625 vh.text.setLines(2);
2626 vh.text.setHorizontallyScrolling(false);
2627 vh.text2.setVisibility(View.GONE);
Adam Powell63b31692015-09-28 10:45:00 -07002628 }
Mike Digmanba232682019-03-27 14:55:26 -07002629
2630 // Force height to be a given so we don't have visual disruption during scaling.
Matt Pietalab986b52019-04-10 10:14:32 -04002631 v.measure(exactSpec, spec);
2632 setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight());
Adam Powell63b31692015-09-28 10:45:00 -07002633 }
2634
Matt Pietal5b648562019-03-12 07:40:26 -04002635 final ViewGroup viewGroup = holder.getViewGroup();
2636
Mike Digmanba232682019-03-27 14:55:26 -07002637 // Pre-measure and fix height so we can scale later.
Adam Powell63b31692015-09-28 10:45:00 -07002638 holder.measure();
Matt Pietalab986b52019-04-10 10:14:32 -04002639 setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight());
Mike Digmanba232682019-03-27 14:55:26 -07002640
2641 if (isDirectShare) {
2642 DirectShareViewHolder dsvh = (DirectShareViewHolder) holder;
Matt Pietalab986b52019-04-10 10:14:32 -04002643 setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
2644 setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
Adam Powell7d758002015-05-06 17:49:36 -07002645 }
Matt Pietal5b648562019-03-12 07:40:26 -04002646
2647 viewGroup.setTag(holder);
2648
Adam Powell7d758002015-05-06 17:49:36 -07002649 return holder;
2650 }
2651
Matt Pietalab986b52019-04-10 10:14:32 -04002652 private void setViewBounds(View view, int widthPx, int heightPx) {
Mike Digmanba232682019-03-27 14:55:26 -07002653 LayoutParams lp = view.getLayoutParams();
2654 if (lp == null) {
Matt Pietalab986b52019-04-10 10:14:32 -04002655 lp = new LayoutParams(widthPx, heightPx);
Mike Digmanba232682019-03-27 14:55:26 -07002656 view.setLayoutParams(lp);
2657 } else {
2658 lp.height = heightPx;
Matt Pietalab986b52019-04-10 10:14:32 -04002659 lp.width = widthPx;
Mike Digmanba232682019-03-27 14:55:26 -07002660 }
2661 }
2662
Zhen Zhangbde7b462019-11-11 11:49:33 -08002663 ItemGroupViewHolder createItemGroupViewHolder(int viewType, ViewGroup parent) {
Matt Pietal5b648562019-03-12 07:40:26 -04002664 if (viewType == VIEW_TYPE_DIRECT_SHARE) {
2665 ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate(
2666 R.layout.chooser_row_direct_share, parent, false);
2667 ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
2668 parentGroup, false);
2669 ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
2670 parentGroup, false);
2671 parentGroup.addView(row1);
2672 parentGroup.addView(row2);
2673
2674 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
2675 Lists.newArrayList(row1, row2), getMaxTargetsPerRow());
Zhen Zhangbde7b462019-11-11 11:49:33 -08002676 loadViewsIntoGroup(mDirectShareViewHolder);
Matt Pietal5b648562019-03-12 07:40:26 -04002677
2678 return mDirectShareViewHolder;
2679 } else {
2680 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
2681 false);
Zhen Zhangbde7b462019-11-11 11:49:33 -08002682 ItemGroupViewHolder holder = new SingleRowViewHolder(row, getMaxTargetsPerRow());
2683 loadViewsIntoGroup(holder);
Matt Pietal5b648562019-03-12 07:40:26 -04002684
2685 return holder;
2686 }
2687 }
2688
Matt Pietaldadc0d12019-04-16 12:53:28 -04002689 /**
Mike Digmanae730b12019-04-25 11:10:31 -07002690 * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
2691 * showing on top of the AZ list if the AZ label is visible. All other types are placed into
2692 * their own row as determined by their target type, and dividers are added in the list to
2693 * separate each type.
Matt Pietaldadc0d12019-04-16 12:53:28 -04002694 */
2695 int getRowType(int rowPosition) {
Mike Digmanae730b12019-04-25 11:10:31 -07002696 // Merge caller and ranked standard into a single row
Matt Pietaldadc0d12019-04-16 12:53:28 -04002697 int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
2698 if (positionType == ChooserListAdapter.TARGET_CALLER) {
2699 return ChooserListAdapter.TARGET_STANDARD;
2700 }
2701
Mike Digmanae730b12019-04-25 11:10:31 -07002702 // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
2703 // row type the same as the suggestion row type
2704 if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
2705 return ChooserListAdapter.TARGET_STANDARD;
2706 }
2707
Matt Pietaldadc0d12019-04-16 12:53:28 -04002708 return positionType;
2709 }
2710
Zhen Zhangbde7b462019-11-11 11:49:33 -08002711 void bindItemViewHolder(int position, ItemViewHolder holder) {
2712 View v = holder.itemView;
2713 int listPosition = getListPosition(position);
2714 holder.mListPosition = listPosition;
2715 mChooserListAdapter.bindView(listPosition, v);
2716 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002717
Zhen Zhangbde7b462019-11-11 11:49:33 -08002718 void bindItemGroupViewHolder(int position, ItemGroupViewHolder holder) {
2719 final ViewGroup viewGroup = (ViewGroup) holder.itemView;
2720 int start = getListPosition(position);
2721 int startType = getRowType(start);
2722 if (viewGroup.getForeground() == null) {
2723 viewGroup.setForeground(
Matt Pietal74c6ed02019-04-18 13:38:46 -04002724 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002725 }
2726
Matt Pietalfaedea82019-03-21 10:36:54 -04002727 int columnCount = holder.getColumnCount();
Matt Pietal5b648562019-03-12 07:40:26 -04002728 int end = start + columnCount - 1;
Matt Pietaldadc0d12019-04-16 12:53:28 -04002729 while (getRowType(end) != startType && end >= start) {
Adam Powell7d758002015-05-06 17:49:36 -07002730 end--;
2731 }
2732
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002733 if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08002734 final TextView textView = viewGroup.findViewById(R.id.chooser_row_text_option);
Adam Powell63b31692015-09-28 10:45:00 -07002735
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002736 if (textView.getVisibility() != View.VISIBLE) {
2737 textView.setAlpha(0.0f);
2738 textView.setVisibility(View.VISIBLE);
2739 textView.setText(R.string.chooser_no_direct_share_targets);
2740
2741 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
2742 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
2743
2744 float translationInPx = getResources().getDimensionPixelSize(
2745 R.dimen.chooser_row_text_option_translate);
2746 textView.setTranslationY(translationInPx);
2747 ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
2748 0.0f);
2749 translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
2750
2751 AnimatorSet animSet = new AnimatorSet();
2752 animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
2753 animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
2754 animSet.playTogether(fadeAnim, translateAnim);
2755 animSet.start();
2756 }
Adam Powell7d758002015-05-06 17:49:36 -07002757 }
2758
Matt Pietal5b648562019-03-12 07:40:26 -04002759 for (int i = 0; i < columnCount; i++) {
2760 final View v = holder.getView(i);
arangelov38a6fce2019-12-02 18:21:22 +00002761
Adam Powell7d758002015-05-06 17:49:36 -07002762 if (start + i <= end) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002763 holder.setViewVisibility(i, View.VISIBLE);
Matt Pietal5b648562019-03-12 07:40:26 -04002764 holder.setItemIndex(i, start + i);
2765 mChooserListAdapter.bindView(holder.getItemIndex(i), v);
Adam Powell7d758002015-05-06 17:49:36 -07002766 } else {
Matt Pietalfaedea82019-03-21 10:36:54 -04002767 holder.setViewVisibility(i, View.INVISIBLE);
Adam Powell7d758002015-05-06 17:49:36 -07002768 }
2769 }
2770 }
2771
Zhen Zhangbde7b462019-11-11 11:49:33 -08002772 int getListPosition(int position) {
2773 position -= getContentPreviewRowCount() + getProfileRowCount();
Matt Pietal1ef88002019-03-13 10:43:18 -04002774
Matt Pietal5b648562019-03-12 07:40:26 -04002775 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
2776 final int serviceRows = (int) Math.ceil((float) serviceCount
2777 / ChooserListAdapter.MAX_SERVICE_TARGETS);
Zhen Zhangbde7b462019-11-11 11:49:33 -08002778 if (position < serviceRows) {
2779 return position * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07002780 }
2781
Zhen Zhangbde7b462019-11-11 11:49:33 -08002782 position -= serviceRows;
2783
Matt Pietaldadc0d12019-04-16 12:53:28 -04002784 final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
2785 + mChooserListAdapter.getRankedTargetCount();
2786 final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
Zhen Zhangbde7b462019-11-11 11:49:33 -08002787 if (position < callerAndRankedRows) {
2788 return serviceCount + position * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07002789 }
2790
Zhen Zhangbde7b462019-11-11 11:49:33 -08002791 position -= getAzLabelRowCount() + callerAndRankedRows;
Mike Digmanae730b12019-04-25 11:10:31 -07002792
Zhen Zhangbde7b462019-11-11 11:49:33 -08002793 return callerAndRankedCount + serviceCount + position;
Matt Pietal5b648562019-03-12 07:40:26 -04002794 }
2795
2796 public void handleScroll(View v, int y, int oldy) {
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002797 // Only expand direct share area if there is a minimum number of shortcuts,
2798 // which will help reduce the amount of visible shuffling due to older-style
2799 // direct share targets.
2800 int orientation = getResources().getConfiguration().orientation;
2801 boolean canExpandDirectShare =
2802 mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002803 && orientation == Configuration.ORIENTATION_PORTRAIT
2804 && !isInMultiWindowMode();
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002805
2806 if (mDirectShareViewHolder != null && canExpandDirectShare) {
arangelov38a6fce2019-12-02 18:21:22 +00002807 mDirectShareViewHolder.handleScroll(
2808 mChooserMultiProfilePagerAdapter.getCurrentAdapterView(), y, oldy,
2809 getMaxTargetsPerRow());
Matt Pietal5b648562019-03-12 07:40:26 -04002810 }
Adam Powell7d758002015-05-06 17:49:36 -07002811 }
arangelov38a6fce2019-12-02 18:21:22 +00002812
2813 public ChooserListAdapter getListAdapter() {
2814 return mChooserListAdapter;
2815 }
2816
2817 void maybeLogActionShareWithPreview() {
2818 if (getContentPreviewRowCount() == 0) {
2819 return;
2820 }
2821 Intent targetIntent = getTargetIntent();
2822 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
2823 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
2824 .setSubtype(previewType));
2825 }
2826
2827 boolean shouldCellSpan(int position) {
2828 return getItemViewType(position) == VIEW_TYPE_NORMAL;
2829 }
Adam Powell7d758002015-05-06 17:49:36 -07002830 }
2831
Zhen Zhangbde7b462019-11-11 11:49:33 -08002832 /**
2833 * Used to bind types for group of items including:
2834 * {@link ChooserGridAdapter#VIEW_TYPE_DIRECT_SHARE},
2835 * and {@link ChooserGridAdapter#VIEW_TYPE_CALLER_AND_RANK}.
2836 */
2837 abstract class ItemGroupViewHolder extends RecyclerView.ViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04002838 protected int mMeasuredRowHeight;
2839 private int[] mItemIndices;
2840 protected final View[] mCells;
Matt Pietal5b648562019-03-12 07:40:26 -04002841 private final int mColumnCount;
Adam Powell63b31692015-09-28 10:45:00 -07002842
Zhen Zhangbde7b462019-11-11 11:49:33 -08002843 ItemGroupViewHolder(int cellCount, View itemView) {
2844 super(itemView);
Matt Pietal5b648562019-03-12 07:40:26 -04002845 this.mCells = new View[cellCount];
2846 this.mItemIndices = new int[cellCount];
Matt Pietal5b648562019-03-12 07:40:26 -04002847 this.mColumnCount = cellCount;
2848 }
2849
2850 abstract ViewGroup addView(int index, View v);
2851
2852 abstract ViewGroup getViewGroup();
2853
Mike Digmanba232682019-03-27 14:55:26 -07002854 abstract ViewGroup getRowByIndex(int index);
2855
2856 abstract ViewGroup getRow(int rowNumber);
Matt Pietal5b648562019-03-12 07:40:26 -04002857
Matt Pietalfaedea82019-03-21 10:36:54 -04002858 abstract void setViewVisibility(int i, int visibility);
2859
Matt Pietal5b648562019-03-12 07:40:26 -04002860 public int getColumnCount() {
2861 return mColumnCount;
2862 }
2863
Adam Powell63b31692015-09-28 10:45:00 -07002864 public void measure() {
2865 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietal5b648562019-03-12 07:40:26 -04002866 getViewGroup().measure(spec, spec);
2867 mMeasuredRowHeight = getViewGroup().getMeasuredHeight();
2868 }
2869
2870 public int getMeasuredRowHeight() {
2871 return mMeasuredRowHeight;
2872 }
2873
Matt Pietal5b648562019-03-12 07:40:26 -04002874 public void setItemIndex(int itemIndex, int listIndex) {
2875 mItemIndices[itemIndex] = listIndex;
2876 }
2877
2878 public int getItemIndex(int itemIndex) {
2879 return mItemIndices[itemIndex];
2880 }
2881
2882 public View getView(int index) {
2883 return mCells[index];
2884 }
2885 }
2886
Zhen Zhangbde7b462019-11-11 11:49:33 -08002887 class SingleRowViewHolder extends ItemGroupViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04002888 private final ViewGroup mRow;
2889
2890 SingleRowViewHolder(ViewGroup row, int cellCount) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08002891 super(cellCount, row);
Matt Pietal5b648562019-03-12 07:40:26 -04002892
2893 this.mRow = row;
2894 }
2895
2896 public ViewGroup getViewGroup() {
2897 return mRow;
2898 }
2899
Mike Digmanba232682019-03-27 14:55:26 -07002900 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04002901 return mRow;
2902 }
2903
Mike Digmanba232682019-03-27 14:55:26 -07002904 public ViewGroup getRow(int rowNumber) {
2905 if (rowNumber == 0) return mRow;
2906 return null;
2907 }
2908
Matt Pietal5b648562019-03-12 07:40:26 -04002909 public ViewGroup addView(int index, View v) {
2910 mRow.addView(v);
2911 mCells[index] = v;
2912
Matt Pietal5b648562019-03-12 07:40:26 -04002913 return mRow;
2914 }
Matt Pietalfaedea82019-03-21 10:36:54 -04002915
2916 public void setViewVisibility(int i, int visibility) {
2917 getView(i).setVisibility(visibility);
2918 }
Matt Pietal5b648562019-03-12 07:40:26 -04002919 }
2920
Zhen Zhangbde7b462019-11-11 11:49:33 -08002921 class DirectShareViewHolder extends ItemGroupViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04002922 private final ViewGroup mParent;
2923 private final List<ViewGroup> mRows;
2924 private int mCellCountPerRow;
2925
2926 private boolean mHideDirectShareExpansion = false;
2927 private int mDirectShareMinHeight = 0;
2928 private int mDirectShareCurrHeight = 0;
2929 private int mDirectShareMaxHeight = 0;
2930
Matt Pietalfaedea82019-03-21 10:36:54 -04002931 private final boolean[] mCellVisibility;
2932
Matt Pietal5b648562019-03-12 07:40:26 -04002933 DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08002934 super(rows.size() * cellCountPerRow, parent);
Matt Pietal5b648562019-03-12 07:40:26 -04002935
2936 this.mParent = parent;
2937 this.mRows = rows;
2938 this.mCellCountPerRow = cellCountPerRow;
Matt Pietalfaedea82019-03-21 10:36:54 -04002939 this.mCellVisibility = new boolean[rows.size() * cellCountPerRow];
Matt Pietal5b648562019-03-12 07:40:26 -04002940 }
2941
2942 public ViewGroup addView(int index, View v) {
Mike Digmanba232682019-03-27 14:55:26 -07002943 ViewGroup row = getRowByIndex(index);
Matt Pietal5b648562019-03-12 07:40:26 -04002944 row.addView(v);
2945 mCells[index] = v;
2946
Matt Pietal5b648562019-03-12 07:40:26 -04002947 return row;
2948 }
2949
2950 public ViewGroup getViewGroup() {
2951 return mParent;
2952 }
2953
Mike Digmanba232682019-03-27 14:55:26 -07002954 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04002955 return mRows.get(index / mCellCountPerRow);
2956 }
2957
Mike Digmanba232682019-03-27 14:55:26 -07002958 public ViewGroup getRow(int rowNumber) {
2959 return mRows.get(rowNumber);
2960 }
2961
Matt Pietal5b648562019-03-12 07:40:26 -04002962 public void measure() {
2963 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
2964 getRow(0).measure(spec, spec);
2965 getRow(1).measure(spec, spec);
2966
Matt Pietal5b648562019-03-12 07:40:26 -04002967 mDirectShareMinHeight = getRow(0).getMeasuredHeight();
2968 mDirectShareCurrHeight = mDirectShareCurrHeight > 0
Zhen Zhangbde7b462019-11-11 11:49:33 -08002969 ? mDirectShareCurrHeight : mDirectShareMinHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04002970 mDirectShareMaxHeight = 2 * mDirectShareMinHeight;
2971 }
2972
2973 public int getMeasuredRowHeight() {
2974 return mDirectShareCurrHeight;
2975 }
2976
Matt Pietala9c8e502019-04-10 14:27:35 -04002977 public int getMinRowHeight() {
2978 return mDirectShareMinHeight;
2979 }
2980
Matt Pietalfaedea82019-03-21 10:36:54 -04002981 public void setViewVisibility(int i, int visibility) {
2982 final View v = getView(i);
2983 if (visibility == View.VISIBLE) {
2984 mCellVisibility[i] = true;
2985 v.setVisibility(visibility);
2986 v.setAlpha(1.0f);
2987 } else if (visibility == View.INVISIBLE && mCellVisibility[i]) {
2988 mCellVisibility[i] = false;
2989
2990 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
2991 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
2992 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
2993 fadeAnim.addListener(new AnimatorListenerAdapter() {
2994 public void onAnimationEnd(Animator animation) {
2995 v.setVisibility(View.INVISIBLE);
2996 }
2997 });
2998 fadeAnim.start();
2999 }
3000 }
3001
Zhen Zhangbde7b462019-11-11 11:49:33 -08003002 public void handleScroll(RecyclerView view, int y, int oldy, int maxTargetsPerRow) {
Matt Pietala9c8e502019-04-10 14:27:35 -04003003 // only exit early if fully collapsed, otherwise onListRebuilt() with shifting
3004 // targets can lock us into an expanded mode
3005 boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight;
3006 if (notExpanded) {
3007 if (mHideDirectShareExpansion) {
3008 return;
3009 }
Matt Pietal5b648562019-03-12 07:40:26 -04003010
Matt Pietala9c8e502019-04-10 14:27:35 -04003011 // only expand if we have more than maxTargetsPerRow, and delay that decision
3012 // until they start to scroll
arangelov38a6fce2019-12-02 18:21:22 +00003013 if (mChooserMultiProfilePagerAdapter.getCurrentListAdapter()
3014 .getSelectableServiceTargetCount() <= maxTargetsPerRow) {
Matt Pietala9c8e502019-04-10 14:27:35 -04003015 mHideDirectShareExpansion = true;
3016 return;
3017 }
Matt Pietal5b648562019-03-12 07:40:26 -04003018 }
3019
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003020 int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal5b648562019-03-12 07:40:26 -04003021
3022 int prevHeight = mDirectShareCurrHeight;
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003023 int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
3024 newHeight = Math.max(newHeight, mDirectShareMinHeight);
3025 yDiff = newHeight - prevHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04003026
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003027 if (view == null || view.getChildCount() == 0 || yDiff == 0) {
Matt Pietal5b648562019-03-12 07:40:26 -04003028 return;
3029 }
3030
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003031 // locate the item to expand, and offset the rows below that one
3032 boolean foundExpansion = false;
3033 for (int i = 0; i < view.getChildCount(); i++) {
3034 View child = view.getChildAt(i);
Matt Pietal1ef88002019-03-13 10:43:18 -04003035
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003036 if (foundExpansion) {
3037 child.offsetTopAndBottom(yDiff);
3038 } else {
3039 if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
3040 int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
3041 MeasureSpec.EXACTLY);
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003042 int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003043 MeasureSpec.EXACTLY);
3044 child.measure(widthSpec, heightSpec);
3045 child.getLayoutParams().height = child.getMeasuredHeight();
3046 child.layout(child.getLeft(), child.getTop(), child.getRight(),
3047 child.getTop() + child.getMeasuredHeight());
Matt Pietal5b648562019-03-12 07:40:26 -04003048
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003049 foundExpansion = true;
3050 }
3051 }
Matt Pietal5b648562019-03-12 07:40:26 -04003052 }
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003053
3054 if (foundExpansion) {
3055 mDirectShareCurrHeight = newHeight;
3056 }
Adam Powell63b31692015-09-28 10:45:00 -07003057 }
3058 }
3059
Adam Powell9761ab22015-09-08 17:01:49 -07003060 static class ChooserTargetServiceConnection implements ServiceConnection {
Adam Powell52c39212016-04-07 15:14:18 -07003061 private DisplayResolveInfo mOriginalTarget;
Adam Powell9761ab22015-09-08 17:01:49 -07003062 private ComponentName mConnectedComponent;
3063 private ChooserActivity mChooserActivity;
3064 private final Object mLock = new Object();
Adam Powell24428412015-04-01 17:19:56 -07003065
3066 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
3067 @Override
3068 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
Adam Powell9761ab22015-09-08 17:01:49 -07003069 synchronized (mLock) {
3070 if (mChooserActivity == null) {
3071 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
3072 + mConnectedComponent + "; ignoring...");
3073 return;
3074 }
3075 mChooserActivity.filterServiceTargets(
3076 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
3077 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04003078 msg.what = ChooserHandler.CHOOSER_TARGET_SERVICE_RESULT;
Adam Powell9761ab22015-09-08 17:01:49 -07003079 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
3080 ChooserTargetServiceConnection.this);
3081 mChooserActivity.mChooserHandler.sendMessage(msg);
3082 }
Adam Powell24428412015-04-01 17:19:56 -07003083 }
3084 };
3085
Adam Powell9761ab22015-09-08 17:01:49 -07003086 public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
3087 DisplayResolveInfo dri) {
3088 mChooserActivity = chooserActivity;
Adam Powell24428412015-04-01 17:19:56 -07003089 mOriginalTarget = dri;
3090 }
3091
3092 @Override
3093 public void onServiceConnected(ComponentName name, IBinder service) {
3094 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003095 synchronized (mLock) {
3096 if (mChooserActivity == null) {
3097 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
3098 return;
3099 }
3100
3101 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
3102 try {
3103 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
3104 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
3105 } catch (RemoteException e) {
3106 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
3107 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003108 mChooserActivity.mServiceConnections.remove(this);
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003109 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003110 }
Adam Powell24428412015-04-01 17:19:56 -07003111 }
3112 }
3113
3114 @Override
3115 public void onServiceDisconnected(ComponentName name) {
3116 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003117 synchronized (mLock) {
3118 if (mChooserActivity == null) {
3119 Log.e(TAG,
3120 "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
3121 return;
3122 }
3123
3124 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003125 mChooserActivity.mServiceConnections.remove(this);
3126 if (mChooserActivity.mServiceConnections.isEmpty()) {
Adam Powell9761ab22015-09-08 17:01:49 -07003127 mChooserActivity.sendVoiceChoicesIfNeeded();
3128 }
3129 mConnectedComponent = null;
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003130 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003131 }
3132 }
3133
3134 public void destroy() {
3135 synchronized (mLock) {
3136 mChooserActivity = null;
Adam Powell52c39212016-04-07 15:14:18 -07003137 mOriginalTarget = null;
Adam Powell4c470d62015-06-19 17:46:17 -07003138 }
Adam Powell24428412015-04-01 17:19:56 -07003139 }
3140
3141 @Override
3142 public String toString() {
Adam Powell9761ab22015-09-08 17:01:49 -07003143 return "ChooserTargetServiceConnection{service="
3144 + mConnectedComponent + ", activity="
Adam Powell52c39212016-04-07 15:14:18 -07003145 + (mOriginalTarget != null
3146 ? mOriginalTarget.getResolveInfo().activityInfo.toString()
3147 : "<connection destroyed>") + "}";
Adam Powell24428412015-04-01 17:19:56 -07003148 }
3149 }
3150
3151 static class ServiceResultInfo {
3152 public final DisplayResolveInfo originalTarget;
3153 public final List<ChooserTarget> resultTargets;
3154 public final ChooserTargetServiceConnection connection;
3155
3156 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
3157 ChooserTargetServiceConnection c) {
3158 originalTarget = ot;
3159 resultTargets = rt;
3160 connection = c;
3161 }
3162 }
Adam Powell2ed547e2015-04-29 18:45:04 -07003163
3164 static class RefinementResultReceiver extends ResultReceiver {
3165 private ChooserActivity mChooserActivity;
3166 private TargetInfo mSelectedTarget;
3167
3168 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
3169 Handler handler) {
3170 super(handler);
3171 mChooserActivity = host;
3172 mSelectedTarget = target;
3173 }
3174
3175 @Override
3176 protected void onReceiveResult(int resultCode, Bundle resultData) {
3177 if (mChooserActivity == null) {
3178 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
3179 return;
3180 }
3181 if (resultData == null) {
3182 Log.e(TAG, "RefinementResultReceiver received null resultData");
3183 return;
3184 }
3185
3186 switch (resultCode) {
3187 case RESULT_CANCELED:
3188 mChooserActivity.onRefinementCanceled();
3189 break;
3190 case RESULT_OK:
3191 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
3192 if (intentParcelable instanceof Intent) {
3193 mChooserActivity.onRefinementResult(mSelectedTarget,
3194 (Intent) intentParcelable);
3195 } else {
3196 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
3197 + " in resultData with key Intent.EXTRA_INTENT");
3198 }
3199 break;
3200 default:
3201 Log.w(TAG, "Unknown result code " + resultCode
3202 + " sent to RefinementResultReceiver");
3203 break;
3204 }
3205 }
3206
3207 public void destroy() {
3208 mChooserActivity = null;
3209 mSelectedTarget = null;
3210 }
3211 }
Adam Powell63b31692015-09-28 10:45:00 -07003212
Matt Pietal26038402019-01-08 07:29:34 -05003213 /**
3214 * Used internally to round image corners while obeying view padding.
3215 */
3216 public static class RoundedRectImageView extends ImageView {
3217 private int mRadius = 0;
3218 private Path mPath = new Path();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003219 private Paint mOverlayPaint = new Paint(0);
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003220 private Paint mRoundRectPaint = new Paint(0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003221 private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
3222 private String mExtraImageCount = null;
Matt Pietal26038402019-01-08 07:29:34 -05003223
3224 public RoundedRectImageView(Context context) {
3225 super(context);
3226 }
3227
3228 public RoundedRectImageView(Context context, AttributeSet attrs) {
3229 this(context, attrs, 0);
3230 }
3231
3232 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
3233 this(context, attrs, defStyleAttr, 0);
3234 }
3235
3236 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
3237 int defStyleRes) {
3238 super(context, attrs, defStyleAttr, defStyleRes);
3239 mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003240
3241 mOverlayPaint.setColor(0x99000000);
3242 mOverlayPaint.setStyle(Paint.Style.FILL);
3243
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003244 mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider));
3245 mRoundRectPaint.setStyle(Paint.Style.STROKE);
3246 mRoundRectPaint.setStrokeWidth(context.getResources()
3247 .getDimensionPixelSize(R.dimen.chooser_preview_image_border));
3248
Matt Pietal0ea391b2019-01-30 10:44:15 -05003249 mTextPaint.setColor(Color.WHITE);
3250 mTextPaint.setTextSize(context.getResources()
3251 .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
3252 mTextPaint.setTextAlign(Paint.Align.CENTER);
Matt Pietal26038402019-01-08 07:29:34 -05003253 }
3254
3255 private void updatePath(int width, int height) {
3256 mPath.reset();
3257
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003258 int imageWidth = width - getPaddingRight() - getPaddingLeft();
3259 int imageHeight = height - getPaddingBottom() - getPaddingTop();
Matt Pietal26038402019-01-08 07:29:34 -05003260 mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
3261 mRadius, Path.Direction.CW);
3262 }
3263
3264 /**
3265 * Sets the corner radius on all corners
3266 *
3267 * param radius 0 for no radius, &gt; 0 for a visible corner radius
3268 */
3269 public void setRadius(int radius) {
3270 mRadius = radius;
3271 updatePath(getWidth(), getHeight());
3272 }
3273
Matt Pietal0ea391b2019-01-30 10:44:15 -05003274 /**
3275 * Display an overlay with extra image count on 3rd image
3276 */
3277 public void setExtraImageCount(int count) {
3278 if (count > 0) {
3279 this.mExtraImageCount = "+" + count;
3280 } else {
3281 this.mExtraImageCount = null;
3282 }
3283 }
3284
Matt Pietal26038402019-01-08 07:29:34 -05003285 @Override
3286 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
3287 super.onSizeChanged(width, height, oldWidth, oldHeight);
3288 updatePath(width, height);
3289 }
3290
3291 @Override
3292 protected void onDraw(Canvas canvas) {
3293 if (mRadius != 0) {
3294 canvas.clipPath(mPath);
3295 }
3296
3297 super.onDraw(canvas);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003298
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003299 int x = getPaddingLeft();
3300 int y = getPaddingRight();
3301 int width = getWidth() - getPaddingRight() - getPaddingLeft();
3302 int height = getHeight() - getPaddingBottom() - getPaddingTop();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003303 if (mExtraImageCount != null) {
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003304 canvas.drawRect(x, y, width, height, mOverlayPaint);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003305
3306 int xPos = canvas.getWidth() / 2;
3307 int yPos = (int) ((canvas.getHeight() / 2.0f)
3308 - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
3309
3310 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
3311 }
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003312
3313 canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
Matt Pietal26038402019-01-08 07:29:34 -05003314 }
3315 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003316}