blob: 78a0ae0907ff6d4ed45f28ee5c48b7e4ab5052b3 [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;
Adam Powell0b3c1122014-10-09 12:50:14 -070029import android.app.Activity;
Ng Zhi And3ec5fc2018-05-10 09:13:00 -070030import android.app.ActivityManager;
George Hodulik69d4a082019-01-18 11:27:03 -080031import android.app.prediction.AppPredictionContext;
32import android.app.prediction.AppPredictionManager;
33import android.app.prediction.AppPredictor;
34import android.app.prediction.AppTarget;
George Hodulikf2b0d342019-01-25 12:43:54 -080035import android.app.prediction.AppTargetEvent;
Artur Satayeved5a6ae2019-12-10 17:47:54 +000036import android.compat.annotation.UnsupportedAppUsage;
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;
Dan Sandlere3d19932019-11-22 11:58:58 -050056import android.content.res.Resources;
Matt Pietal46d828c2019-02-05 08:07:07 -050057import android.database.Cursor;
Adam Powell7d758002015-05-06 17:49:36 -070058import android.database.DataSetObserver;
Matt Pietal26038402019-01-08 07:29:34 -050059import android.graphics.Bitmap;
60import android.graphics.Canvas;
Adam Powell63b31692015-09-28 10:45:00 -070061import android.graphics.Color;
Matt Pietal0ea391b2019-01-30 10:44:15 -050062import android.graphics.Paint;
Matt Pietal26038402019-01-08 07:29:34 -050063import android.graphics.Path;
Mike Digmanac1d88c2019-04-18 15:15:55 -070064import android.graphics.drawable.AnimatedVectorDrawable;
Adam Powell24428412015-04-01 17:19:56 -070065import android.graphics.drawable.Drawable;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -050066import android.metrics.LogMaker;
Matt Pietal26038402019-01-08 07:29:34 -050067import android.net.Uri;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080068import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069import android.os.Bundle;
Alison Cichowlas1fd47152019-11-14 19:50:55 -050070import android.os.Environment;
Adam Powell24428412015-04-01 17:19:56 -070071import android.os.Handler;
72import android.os.IBinder;
73import android.os.Message;
Dianne Hackborneb034652009-09-07 00:49:58 -070074import android.os.Parcelable;
Katsiaryna Naliuka66cd0562020-01-16 18:01:30 +010075import android.os.PatternMatcher;
Adam Powell24428412015-04-01 17:19:56 -070076import android.os.RemoteException;
Adam Powell2ed547e2015-04-29 18:45:04 -070077import android.os.ResultReceiver;
Adam Powell24428412015-04-01 17:19:56 -070078import android.os.UserHandle;
Adam Powell7d758002015-05-06 17:49:36 -070079import android.os.UserManager;
Alison Cichowlas1fd47152019-11-14 19:50:55 -050080import android.os.storage.StorageManager;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -040081import android.provider.DeviceConfig;
Matt Pietal46d828c2019-02-05 08:07:07 -050082import android.provider.DocumentsContract;
Matt Pietalf38e9d22019-02-15 10:01:03 -050083import android.provider.Downloads;
Matt Pietal46d828c2019-02-05 08:07:07 -050084import android.provider.OpenableColumns;
Dan Sandlere3d19932019-11-22 11:58:58 -050085import android.provider.Settings;
Adam Powell24428412015-04-01 17:19:56 -070086import android.service.chooser.ChooserTarget;
87import android.service.chooser.ChooserTargetService;
88import android.service.chooser.IChooserTargetResult;
89import android.service.chooser.IChooserTargetService;
90import android.text.TextUtils;
Matt Pietal26038402019-01-08 07:29:34 -050091import android.util.AttributeSet;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -040092import android.util.HashedStringCache;
Dianne Hackborneb034652009-09-07 00:49:58 -070093import android.util.Log;
arangelov5fc9e7d2020-01-07 17:59:14 +000094import android.util.Pair;
Matt Pietal26038402019-01-08 07:29:34 -050095import android.util.Size;
Adam Powell0b3c1122014-10-09 12:50:14 -070096import android.util.Slog;
Adam Powell7d758002015-05-06 17:49:36 -070097import android.view.LayoutInflater;
Adam Powell24428412015-04-01 17:19:56 -070098import android.view.View;
Adam Powell63b31692015-09-28 10:45:00 -070099import android.view.View.MeasureSpec;
Adam Powell7d758002015-05-06 17:49:36 -0700100import android.view.View.OnClickListener;
Adam Powell98b7f892015-06-19 12:38:45 -0700101import android.view.View.OnLongClickListener;
Adam Powell24428412015-04-01 17:19:56 -0700102import android.view.ViewGroup;
Adam Powell63b31692015-09-28 10:45:00 -0700103import android.view.ViewGroup.LayoutParams;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500104import android.view.animation.AccelerateInterpolator;
105import android.view.animation.DecelerateInterpolator;
Dan Sandlere3d19932019-11-22 11:58:58 -0500106import android.widget.Button;
Matt Pietal26038402019-01-08 07:29:34 -0500107import android.widget.ImageView;
Matt Pietal9236adc2019-12-12 09:24:23 -0500108import android.widget.Space;
Matt Pietal26038402019-01-08 07:29:34 -0500109import android.widget.TextView;
Matt Pietal1fa7d802019-01-30 10:44:15 -0500110import android.widget.Toast;
Jason Monk027dcfa2017-06-27 18:37:35 -0400111
Adam Powell7d758002015-05-06 17:49:36 -0700112import com.android.internal.R;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800113import com.android.internal.annotations.VisibleForTesting;
arangelovb0802dc2019-10-18 18:03:44 +0100114import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
115import com.android.internal.app.ResolverListAdapter.ViewHolder;
116import com.android.internal.app.chooser.ChooserTargetInfo;
117import com.android.internal.app.chooser.DisplayResolveInfo;
Alison Cichowlas19ee2922019-12-16 19:43:12 -0500118import com.android.internal.app.chooser.MultiDisplayResolveInfo;
arangelovb0802dc2019-10-18 18:03:44 +0100119import com.android.internal.app.chooser.NotSelectableTargetInfo;
120import com.android.internal.app.chooser.SelectableTargetInfo;
121import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
122import com.android.internal.app.chooser.TargetInfo;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400123import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
Matt Pietalab73a882019-06-05 07:04:55 -0400124import com.android.internal.content.PackageMonitor;
Adam Powell98b7f892015-06-19 12:38:45 -0700125import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +0100126import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Zhen Zhangbde7b462019-11-11 11:49:33 -0800127import com.android.internal.widget.GridLayoutManager;
128import com.android.internal.widget.RecyclerView;
Mike Digman849a9d12019-04-29 11:20:48 -0700129import com.android.internal.widget.ResolverDrawerLayout;
Alison Cichowlas3e340502018-08-07 17:15:01 -0400130
Adam Powell52c39212016-04-07 15:14:18 -0700131import com.google.android.collect.Lists;
Adam Powell24428412015-04-01 17:19:56 -0700132
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500133import java.io.File;
Matt Pietal26038402019-01-08 07:29:34 -0500134import java.io.IOException;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500135import java.lang.annotation.Retention;
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700136import java.lang.annotation.RetentionPolicy;
Dan Sandlere3d19932019-11-22 11:58:58 -0500137import java.net.URISyntaxException;
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400138import java.text.Collator;
Adam Powell24428412015-04-01 17:19:56 -0700139import java.util.ArrayList;
Adam Powella182e452015-07-06 16:57:56 -0700140import java.util.Collections;
141import java.util.Comparator;
George Hodulikaa5238c2019-04-18 14:17:51 -0700142import java.util.HashMap;
Matt Pietalab73a882019-06-05 07:04:55 -0400143import java.util.HashSet;
Adam Powell24428412015-04-01 17:19:56 -0700144import java.util.List;
George Hodulikaa5238c2019-04-18 14:17:51 -0700145import java.util.Map;
Matt Pietalab73a882019-06-05 07:04:55 -0400146import java.util.Set;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400148/**
149 * The Chooser Activity handles intent resolution specifically for sharing intents -
150 * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
151 *
152 */
arangelovb0802dc2019-10-18 18:03:44 +0100153public class ChooserActivity extends ResolverActivity implements
154 ChooserListAdapter.ChooserListCommunicator,
155 SelectableTargetInfoCommunicator {
Adam Powell0b3c1122014-10-09 12:50:14 -0700156 private static final String TAG = "ChooserActivity";
arangelov5fc9e7d2020-01-07 17:59:14 +0000157 private AppPredictor mPersonalAppPredictor;
158 private AppPredictor mWorkAppPredictor;
Adam Powell0b3c1122014-10-09 12:50:14 -0700159
Artur Satayev751e5512019-11-15 19:12:49 +0000160 @UnsupportedAppUsage
161 public ChooserActivity() {
162 }
Jorim Jaggif631ef72017-02-24 13:49:47 +0100163 /**
164 * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
165 * in onStop when launched in a new task. If this extra is set to true, we do not finish
166 * ourselves when onStop gets called.
167 */
168 public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
169 = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
170
Mike Digman849a9d12019-04-29 11:20:48 -0700171 private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
172
Dan Sandlere3d19932019-11-22 11:58:58 -0500173 private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label";
174 private static final String CHIP_ICON_METADATA_KEY = "android.service.chooser.chip_icon";
175
arangelov5fc9e7d2020-01-07 17:59:14 +0000176 private static final boolean DEBUG = true;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800177
George Hodulik1428beb2019-05-01 17:04:23 -0700178 private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
George Hodulik69d4a082019-01-18 11:27:03 -0800179 // TODO(b/123088566) Share these in a better way.
180 private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
George Hodulikf2b0d342019-01-25 12:43:54 -0800181 public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
George Hodulik69d4a082019-01-18 11:27:03 -0800182 private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
183 public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700184
arangelovb0802dc2019-10-18 18:03:44 +0100185 @VisibleForTesting
186 public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
187
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700188 private boolean mIsAppPredictorComponentAvailable;
George Hodulikaa5238c2019-04-18 14:17:51 -0700189 private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
arangelov5fc9e7d2020-01-07 17:59:14 +0000190 private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache;
George Hodulik69d4a082019-01-18 11:27:03 -0800191
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700192 public static final int TARGET_TYPE_DEFAULT = 0;
193 public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
194 public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
195 public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
196
arangelovb0802dc2019-10-18 18:03:44 +0100197 private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
198
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700199 @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
200 TARGET_TYPE_DEFAULT,
201 TARGET_TYPE_CHOOSER_TARGET,
202 TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
203 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
204 })
205 @Retention(RetentionPolicy.SOURCE)
206 public @interface ShareTargetType {}
207
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500208 /**
209 * The transition time between placeholders for direct share to a message
210 * indicating that non are available.
211 */
212 private static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200;
213
Matt Pietal394ebd02019-05-03 07:36:21 -0400214 private static final float DIRECT_SHARE_EXPANSION_RATE = 0.78f;
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400215
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800216 // TODO(b/121287224): Re-evaluate this limit
217 private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
Adam Powell24428412015-04-01 17:19:56 -0700218
Adam Powell2ed547e2015-04-29 18:45:04 -0700219 private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
Adam Powell24428412015-04-01 17:19:56 -0700220
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400221 private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7;
222 private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
223 SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
224 DEFAULT_SALT_EXPIRATION_DAYS);
225
Song Hue2deffd2020-03-09 15:22:29 -0700226 private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
227 DeviceConfig.NAMESPACE_SYSTEMUI,
228 SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
229 false);
230
Adam Powelle49d9392014-07-17 18:45:19 -0700231 private Bundle mReplacementExtras;
Adam Powell0b3c1122014-10-09 12:50:14 -0700232 private IntentSender mChosenComponentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -0700233 private IntentSender mRefinementIntentSender;
234 private RefinementResultReceiver mRefinementResultReceiver;
Adam Powell52c39212016-04-07 15:14:18 -0700235 private ChooserTarget[] mCallerChooserTargets;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800236 private ComponentName[] mFilteredComponentNames;
Adam Powelle49d9392014-07-17 18:45:19 -0700237
Adam Powell13036be2015-05-12 14:43:56 -0700238 private Intent mReferrerFillInIntent;
239
Kang Li9082f5b2016-12-02 10:56:21 -0800240 private long mChooserShownTime;
Kang Li64b018e2017-01-05 17:30:06 -0800241 protected boolean mIsSuccessfullySelected;
Kang Li9082f5b2016-12-02 10:56:21 -0800242
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700243 private long mQueriedTargetServicesTimeMs;
244 private long mQueriedSharingShortcutsTimeMs;
245
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500246 private int mChooserRowServiceSpacing;
Adam Powell24428412015-04-01 17:19:56 -0700247
Matt Pietalab73a882019-06-05 07:04:55 -0400248 private int mCurrAvailableWidth = 0;
arangelovc4dbdbd2020-02-18 20:54:16 +0000249 private int mLastNumberOfChildren = -1;
Matt Pietalab73a882019-06-05 07:04:55 -0400250
Adam Powell23882512016-01-29 10:21:00 -0800251 private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400252 // TODO: Update to handle landscape instead of using static value
253 private static final int MAX_RANKED_TARGETS = 4;
Adam Powell23882512016-01-29 10:21:00 -0800254
Adam Powell24428412015-04-01 17:19:56 -0700255 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
arangelov5fc9e7d2020-01-07 17:59:14 +0000256 private final Set<Pair<ComponentName, UserHandle>> mServicesRequested = new HashSet<>();
Matt Pietalaf044ae2019-03-29 06:53:53 -0400257
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -0400258 private static final int MAX_LOG_RANK_POSITION = 12;
259
Matt Pietal4e2e3632019-04-05 08:32:47 -0400260 private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400261 private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
Matt Pietal4e2e3632019-04-05 08:32:47 -0400262
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500263 private SharedPreferences mPinnedSharedPrefs;
264 private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
265
Matt Pietal0ea391b2019-01-30 10:44:15 -0500266 @Retention(SOURCE)
267 @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
268 private @interface ContentPreviewType {
269 }
270
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500271 // Starting at 1 since 0 is considered "undefined" for some of the database transformations
272 // of tron logs.
273 private static final int CONTENT_PREVIEW_IMAGE = 1;
274 private static final int CONTENT_PREVIEW_FILE = 2;
275 private static final int CONTENT_PREVIEW_TEXT = 3;
276 protected MetricsLogger mMetricsLogger;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500277
Matt Pietale7cacab2019-05-23 07:21:36 -0400278 private ContentPreviewCoordinator mPreviewCoord;
279
arangelov38a6fce2019-12-02 18:21:22 +0000280 @VisibleForTesting
281 protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
282
Matt Pietale7cacab2019-05-23 07:21:36 -0400283 private class ContentPreviewCoordinator {
Matt Pietale7cacab2019-05-23 07:21:36 -0400284 private static final int IMAGE_FADE_IN_MILLIS = 150;
285 private static final int IMAGE_LOAD_TIMEOUT = 1;
286 private static final int IMAGE_LOAD_INTO_VIEW = 2;
287
Matt Pietalab73a882019-06-05 07:04:55 -0400288 private final int mImageLoadTimeoutMillis =
289 getResources().getInteger(R.integer.config_shortAnimTime);
290
Matt Pietale7cacab2019-05-23 07:21:36 -0400291 private final View mParentView;
292 private boolean mHideParentOnFail;
293 private boolean mAtLeastOneLoaded = false;
294
295 class LoadUriTask {
296 public final Uri mUri;
297 public final int mImageResourceId;
298 public final int mExtraCount;
299 public final Bitmap mBmp;
300
301 LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) {
302 this.mImageResourceId = imageResourceId;
303 this.mUri = uri;
304 this.mExtraCount = extraCount;
305 this.mBmp = bmp;
306 }
307 }
308
309 // If at least one image loads within the timeout period, allow other
310 // loads to continue. Otherwise terminate and optionally hide
311 // the parent area
312 private final Handler mHandler = new Handler() {
313 @Override
314 public void handleMessage(Message msg) {
315 switch (msg.what) {
316 case IMAGE_LOAD_TIMEOUT:
317 maybeHideContentPreview();
318 break;
319
320 case IMAGE_LOAD_INTO_VIEW:
321 if (isFinishing()) break;
322
323 LoadUriTask task = (LoadUriTask) msg.obj;
324 RoundedRectImageView imageView = mParentView.findViewById(
325 task.mImageResourceId);
326 if (task.mBmp == null) {
327 imageView.setVisibility(View.GONE);
328 maybeHideContentPreview();
329 return;
330 }
331
332 mAtLeastOneLoaded = true;
333 imageView.setVisibility(View.VISIBLE);
334 imageView.setAlpha(0.0f);
335 imageView.setImageBitmap(task.mBmp);
336
337 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f,
338 1.0f);
339 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
340 fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS);
341 fadeAnim.start();
342
343 if (task.mExtraCount > 0) {
344 imageView.setExtraImageCount(task.mExtraCount);
345 }
346 }
347 }
348 };
349
350 ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) {
351 super();
352
353 this.mParentView = parentView;
354 this.mHideParentOnFail = hideParentOnFail;
355 }
356
357 private void loadUriIntoView(final int imageResourceId, final Uri uri,
358 final int extraImages) {
Matt Pietalab73a882019-06-05 07:04:55 -0400359 mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, mImageLoadTimeoutMillis);
Matt Pietale7cacab2019-05-23 07:21:36 -0400360
361 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
Alison Cichowlas07acade2019-12-20 11:26:23 -0500362 int size = getResources().getDimensionPixelSize(
363 R.dimen.chooser_preview_image_max_dimen);
364 final Bitmap bmp = loadThumbnail(uri, new Size(size, size));
Matt Pietale7cacab2019-05-23 07:21:36 -0400365 final Message msg = Message.obtain();
366 msg.what = IMAGE_LOAD_INTO_VIEW;
367 msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
368 mHandler.sendMessage(msg);
369 });
370 }
371
372 private void cancelLoads() {
373 mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW);
374 mHandler.removeMessages(IMAGE_LOAD_TIMEOUT);
375 }
376
377 private void maybeHideContentPreview() {
378 if (!mAtLeastOneLoaded && mHideParentOnFail) {
379 Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
Matt Pietalab73a882019-06-05 07:04:55 -0400380 + " within " + mImageLoadTimeoutMillis + "ms.");
Matt Pietale7cacab2019-05-23 07:21:36 -0400381 collapseParentView();
arangelovf163a882020-02-18 17:18:47 +0000382 if (shouldShowTabs()) {
arangelov590fba32020-02-11 18:05:42 +0000383 hideStickyContentPreview();
384 } else if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
385 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().hideContentPreview();
386 }
Matt Pietale7cacab2019-05-23 07:21:36 -0400387 mHideParentOnFail = false;
388 }
389 }
390
391 private void collapseParentView() {
392 // This will effectively hide the content preview row by forcing the height
393 // to zero. It is faster than forcing a relayout of the listview
394 final View v = mParentView;
395 int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY);
396 int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
397 v.measure(widthSpec, heightSpec);
398 v.getLayoutParams().height = 0;
399 v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop());
400 v.invalidate();
401 }
402 }
403
Matt Pietalab73a882019-06-05 07:04:55 -0400404 private final ChooserHandler mChooserHandler = new ChooserHandler();
405
406 private class ChooserHandler extends Handler {
407 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
408 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT = 2;
409 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT = 3;
410 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
411 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
412 private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
413
414 private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
415 private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;
416
Song Hue2deffd2020-03-09 15:22:29 -0700417 private static final int DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS = 1500;
418 private int mDirectShareTimeout = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
419 SystemUiDeviceConfigFlags.SHARE_SHEET_DIRECT_SHARE_TIMEOUT,
420 DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS);
421
Matt Pietalab73a882019-06-05 07:04:55 -0400422 private boolean mMinTimeoutPassed = false;
423
424 private void removeAllMessages() {
425 removeMessages(LIST_VIEW_UPDATE_MESSAGE);
426 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
427 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
428 removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
429 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
430 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
431 }
432
433 private void restartServiceRequestTimer() {
434 mMinTimeoutPassed = false;
435 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
436 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
437
438 if (DEBUG) {
439 Log.d(TAG, "queryTargets setting watchdog timer for "
Song Hue2deffd2020-03-09 15:22:29 -0700440 + mDirectShareTimeout + "-"
Matt Pietalab73a882019-06-05 07:04:55 -0400441 + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
442 }
443
444 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
445 WATCHDOG_TIMEOUT_MIN_MILLIS);
446 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
Song Hue2deffd2020-03-09 15:22:29 -0700447 mAppendDirectShareEnabled ? mDirectShareTimeout : WATCHDOG_TIMEOUT_MAX_MILLIS);
Matt Pietalab73a882019-06-05 07:04:55 -0400448 }
449
450 private void maybeStopServiceRequestTimer() {
451 // Set a minimum timeout threshold, to ensure both apis, sharing shortcuts
452 // and older-style direct share services, have had time to load, otherwise
453 // just checking mServiceConnections could force us to end prematurely
454 if (mMinTimeoutPassed && mServiceConnections.isEmpty()) {
455 logDirectShareTargetReceived(
456 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
457 sendVoiceChoicesIfNeeded();
arangelova3912cf2019-12-13 14:34:45 +0000458 mChooserMultiProfilePagerAdapter.getActiveListAdapter()
arangelov38a6fce2019-12-02 18:21:22 +0000459 .completeServiceTargetLoading();
Matt Pietalab73a882019-06-05 07:04:55 -0400460 }
461 }
462
Adam Powell24428412015-04-01 17:19:56 -0700463 @Override
464 public void handleMessage(Message msg) {
arangelova3912cf2019-12-13 14:34:45 +0000465 if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == null || isDestroyed()) {
Matt Pietalaf044ae2019-03-29 06:53:53 -0400466 return;
467 }
468
Adam Powell24428412015-04-01 17:19:56 -0700469 switch (msg.what) {
470 case CHOOSER_TARGET_SERVICE_RESULT:
471 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
Adam Powell24428412015-04-01 17:19:56 -0700472 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
473 if (!mServiceConnections.contains(sri.connection)) {
474 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
Song Hue2deffd2020-03-09 15:22:29 -0700475 + sri.originalTarget.getResolveInfo().activityInfo.packageName
Adam Powell24428412015-04-01 17:19:56 -0700476 + " returned after being removed from active connections."
477 + " Have you considered returning results faster?");
478 break;
479 }
Adam Powella182e452015-07-06 16:57:56 -0700480 if (sri.resultTargets != null) {
arangelov5fc9e7d2020-01-07 17:59:14 +0000481 ChooserListAdapter adapterForUserHandle =
482 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
483 sri.userHandle);
484 if (adapterForUserHandle != null) {
485 adapterForUserHandle.addServiceResults(sri.originalTarget,
486 sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET,
Song Hue2deffd2020-03-09 15:22:29 -0700487 /* directShareShortcutInfoCache */ null, mServiceConnections);
arangelov5fc9e7d2020-01-07 17:59:14 +0000488 }
Adam Powella182e452015-07-06 16:57:56 -0700489 }
Adam Powell24428412015-04-01 17:19:56 -0700490 unbindService(sri.connection);
Adam Powell9761ab22015-09-08 17:01:49 -0700491 sri.connection.destroy();
Adam Powell24428412015-04-01 17:19:56 -0700492 mServiceConnections.remove(sri.connection);
Matt Pietalab73a882019-06-05 07:04:55 -0400493 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700494 break;
495
Matt Pietalab73a882019-06-05 07:04:55 -0400496 case CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT:
497 mMinTimeoutPassed = true;
498 maybeStopServiceRequestTimer();
499 break;
Matt Pietalaf044ae2019-03-29 06:53:53 -0400500
Matt Pietalab73a882019-06-05 07:04:55 -0400501 case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
Song Hue2deffd2020-03-09 15:22:29 -0700502 mMinTimeoutPassed = true;
Adam Powell24428412015-04-01 17:19:56 -0700503 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -0400504 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700505 break;
506
Matt Pietalaf044ae2019-03-29 06:53:53 -0400507 case LIST_VIEW_UPDATE_MESSAGE:
508 if (DEBUG) {
509 Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
510 }
511
arangelov5fc9e7d2020-01-07 17:59:14 +0000512 UserHandle userHandle = (UserHandle) msg.obj;
513 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(userHandle)
514 .refreshListView();
Matt Pietalaf044ae2019-03-29 06:53:53 -0400515 break;
516
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800517 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
518 if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800519 final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
520 if (resultInfo.resultTargets != null) {
arangelov5fc9e7d2020-01-07 17:59:14 +0000521 ChooserListAdapter adapterForUserHandle =
522 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
523 resultInfo.userHandle);
524 if (adapterForUserHandle != null) {
525 adapterForUserHandle.addServiceResults(
526 resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1,
Song Hue2deffd2020-03-09 15:22:29 -0700527 mDirectShareShortcutInfoCache, mServiceConnections);
arangelov5fc9e7d2020-01-07 17:59:14 +0000528 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800529 }
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800530 break;
531
532 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700533 logDirectShareTargetReceived(
534 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800535 sendVoiceChoicesIfNeeded();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800536 break;
537
Adam Powell24428412015-04-01 17:19:56 -0700538 default:
539 super.handleMessage(msg);
540 }
541 }
542 };
543
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544 @Override
545 protected void onCreate(Bundle savedInstanceState) {
Kang Li9082f5b2016-12-02 10:56:21 -0800546 final long intentReceivedTime = System.currentTimeMillis();
Mehdi Alizadeh5cc5f712019-09-06 12:17:52 -0700547 // This is the only place this value is being set. Effectively final.
548 mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
549
Kang Li9082f5b2016-12-02 10:56:21 -0800550 mIsSuccessfullySelected = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800551 Intent intent = getIntent();
Dianne Hackborneb034652009-09-07 00:49:58 -0700552 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
Dan Sandlere3d19932019-11-22 11:58:58 -0500553 if (targetParcelable instanceof Uri) {
554 try {
555 targetParcelable = Intent.parseUri(targetParcelable.toString(),
556 Intent.URI_INTENT_SCHEME);
557 } catch (URISyntaxException ex) {
558 // doesn't parse as an intent; let the next test fail and error out
559 }
560 }
561
Dianne Hackborneb034652009-09-07 00:49:58 -0700562 if (!(targetParcelable instanceof Intent)) {
Christopher Tate9d6376a2014-02-12 13:14:10 -0800563 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
Dianne Hackborneb034652009-09-07 00:49:58 -0700564 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800565 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700566 return;
567 }
Adam Powell24428412015-04-01 17:19:56 -0700568 Intent target = (Intent) targetParcelable;
Craig Mautner411d2aed2014-05-08 09:07:43 -0700569 if (target != null) {
Adam Powelle49d9392014-07-17 18:45:19 -0700570 modifyTargetIntent(target);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700571 }
Adam Powell2ed547e2015-04-29 18:45:04 -0700572 Parcelable[] targetsParcelable
573 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
574 if (targetsParcelable != null) {
575 final boolean offset = target == null;
576 Intent[] additionalTargets =
577 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
578 for (int i = 0; i < targetsParcelable.length; i++) {
579 if (!(targetsParcelable[i] instanceof Intent)) {
580 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
581 + targetsParcelable[i]);
582 finish();
583 super.onCreate(null);
584 return;
585 }
586 final Intent additionalTarget = (Intent) targetsParcelable[i];
587 if (i == 0 && target == null) {
588 target = additionalTarget;
589 modifyTargetIntent(target);
590 } else {
591 additionalTargets[offset ? i - 1 : i] = additionalTarget;
592 modifyTargetIntent(additionalTarget);
593 }
594 }
595 setAdditionalTargets(additionalTargets);
596 }
597
Adam Powelle49d9392014-07-17 18:45:19 -0700598 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
Matt Pietal26038402019-01-08 07:29:34 -0500599
600 // Do not allow the title to be changed when sharing content
601 CharSequence title = null;
602 if (target != null) {
Matt Pietal95574b02019-03-13 08:12:25 -0400603 if (!isSendAction(target)) {
Matt Pietal26038402019-01-08 07:29:34 -0500604 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
605 } else {
606 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
607 + " preview title by using EXTRA_TITLE property of the wrapped"
608 + " EXTRA_INTENT.");
609 }
610 }
611
Adam Powell278902c2014-07-12 18:33:22 -0700612 int defaultTitleRes = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613 if (title == null) {
Adam Powell278902c2014-07-12 18:33:22 -0700614 defaultTitleRes = com.android.internal.R.string.chooseActivity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800615 }
Matt Pietal26038402019-01-08 07:29:34 -0500616
Dianne Hackborneb034652009-09-07 00:49:58 -0700617 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
618 Intent[] initialIntents = null;
619 if (pa != null) {
Matt Pietal4e2e3632019-04-05 08:32:47 -0400620 int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS);
621 initialIntents = new Intent[count];
622 for (int i = 0; i < count; i++) {
Dianne Hackborneb034652009-09-07 00:49:58 -0700623 if (!(pa[i] instanceof Intent)) {
Adam Powell2ed547e2015-04-29 18:45:04 -0700624 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
Dianne Hackborneb034652009-09-07 00:49:58 -0700625 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800626 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700627 return;
628 }
Craig Mautner411d2aed2014-05-08 09:07:43 -0700629 final Intent in = (Intent) pa[i];
Adam Powelle49d9392014-07-17 18:45:19 -0700630 modifyTargetIntent(in);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700631 initialIntents[i] = in;
Dianne Hackborneb034652009-09-07 00:49:58 -0700632 }
633 }
Adam Powell24428412015-04-01 17:19:56 -0700634
Adam Powell13036be2015-05-12 14:43:56 -0700635 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
636
Adam Powell0b3c1122014-10-09 12:50:14 -0700637 mChosenComponentSender = intent.getParcelableExtra(
638 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
Adam Powell2ed547e2015-04-29 18:45:04 -0700639 mRefinementIntentSender = intent.getParcelableExtra(
640 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
Dianne Hackborn028ceeb2014-08-17 17:45:48 -0700641 setSafeForwardingMode(true);
Adam Powell23882512016-01-29 10:21:00 -0800642
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500643 mPinnedSharedPrefs = getPinnedSharedPrefs(this);
644
Adam Powell52c39212016-04-07 15:14:18 -0700645 pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
646 if (pa != null) {
647 ComponentName[] names = new ComponentName[pa.length];
648 for (int i = 0; i < pa.length; i++) {
649 if (!(pa[i] instanceof ComponentName)) {
650 Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
651 names = null;
652 break;
653 }
654 names[i] = (ComponentName) pa[i];
655 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800656 mFilteredComponentNames = names;
Adam Powell52c39212016-04-07 15:14:18 -0700657 }
658
659 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
660 if (pa != null) {
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400661 int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS);
662 ChooserTarget[] targets = new ChooserTarget[count];
663 for (int i = 0; i < count; i++) {
Adam Powell52c39212016-04-07 15:14:18 -0700664 if (!(pa[i] instanceof ChooserTarget)) {
665 Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
666 targets = null;
667 break;
668 }
669 targets[i] = (ChooserTarget) pa[i];
670 }
671 mCallerChooserTargets = targets;
672 }
673
Jorim Jaggif631ef72017-02-24 13:49:47 +0100674 setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
Adam Powell278902c2014-07-12 18:33:22 -0700675 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
676 null, false);
Adam Powell98b7f892015-06-19 12:38:45 -0700677
Kang Li9082f5b2016-12-02 10:56:21 -0800678 mChooserShownTime = System.currentTimeMillis();
679 final long systemCost = mChooserShownTime - intentReceivedTime;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500680
681 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500682 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
683 MetricsEvent.PARENT_PROFILE)
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500684 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
685 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
George Hodulik69d4a082019-01-18 11:27:03 -0800686
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500687 mChooserRowServiceSpacing = getResources()
688 .getDimensionPixelSize(R.dimen.chooser_service_spacing);
689
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400690 if (mResolverDrawerLayout != null) {
691 mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
692
693 // expand/shrink direct share 4 -> 8 viewgroup
694 if (isSendAction(target)) {
695 mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
696 }
Matt Pietalb1d629d2019-04-23 11:35:53 -0400697
Mike Digman849a9d12019-04-29 11:20:48 -0700698 mResolverDrawerLayout.setOnCollapsedChangedListener(
699 new ResolverDrawerLayout.OnCollapsedChangedListener() {
700
701 // Only consider one expansion per activity creation
702 private boolean mWrittenOnce = false;
703
704 @Override
705 public void onCollapsedChanged(boolean isCollapsed) {
706 if (!isCollapsed && !mWrittenOnce) {
707 incrementNumSheetExpansions();
708 mWrittenOnce = true;
709 }
710 }
711 });
Matt Pietal5b648562019-03-12 07:40:26 -0400712 }
713
Kang Li9082f5b2016-12-02 10:56:21 -0800714 if (DEBUG) {
715 Log.d(TAG, "System Time Cost is " + systemCost);
716 }
arangelov5fc9e7d2020-01-07 17:59:14 +0000717
718 mDirectShareShortcutInfoCache = new HashMap<>();
719 }
720
721 private AppPredictor setupAppPredictorForUser(UserHandle userHandle,
722 AppPredictor.Callback appPredictorCallback) {
723 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
724 if (appPredictor == null) {
725 return null;
726 }
727 mDirectShareAppTargetCache = new HashMap<>();
728 appPredictor.registerPredictionUpdates(this.getMainExecutor(), appPredictorCallback);
729 return appPredictor;
730 }
731
732 private AppPredictor.Callback createAppPredictorCallback(
733 ChooserListAdapter chooserListAdapter) {
734 return resultList -> {
735 //TODO(arangelov) Take care of edge case when callback called after swiping tabs
736 if (isFinishing() || isDestroyed()) {
737 return;
738 }
739 if (chooserListAdapter.getCount() == 0) {
740 return;
741 }
742 if (resultList.isEmpty()) {
743 // APS may be disabled, so try querying targets ourselves.
744 //TODO(arangelov) queryDirectShareTargets indirectly uses mIntents.
745 // Investigate implications for work tab.
746 queryDirectShareTargets(chooserListAdapter, true);
747 return;
748 }
749 final List<DisplayResolveInfo> driList =
750 getDisplayResolveInfos(chooserListAdapter);
751 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
752 new ArrayList<>();
753 for (AppTarget appTarget : resultList) {
754 if (appTarget.getShortcutInfo() == null) {
755 continue;
756 }
757 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
758 appTarget.getShortcutInfo(),
759 new ComponentName(
760 appTarget.getPackageName(), appTarget.getClassName())));
761 }
762 sendShareShortcutInfoList(shareShortcutInfos, driList, resultList,
763 chooserListAdapter.getUserHandle());
764 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800765 }
Adam Powelle49d9392014-07-17 18:45:19 -0700766
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500767 static SharedPreferences getPinnedSharedPrefs(Context context) {
768 // The code below is because in the android:ui process, no one can hear you scream.
769 // The package info in the context isn't initialized in the way it is for normal apps,
770 // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
771 // build the path manually below using the same policy that appears in ContextImpl.
772 // This fails silently under the hood if there's a problem, so if we find ourselves in
773 // the case where we don't have access to credential encrypted storage we just won't
774 // have our pinned target info.
775 final File prefsFile = new File(new File(
776 Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
777 context.getUserId(), context.getPackageName()),
778 "shared_prefs"),
779 PINNED_SHARED_PREFS_NAME + ".xml");
780 return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
781 }
782
arangelov38a6fce2019-12-02 18:21:22 +0000783 @Override
784 protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
785 Intent[] initialIntents,
786 List<ResolveInfo> rList,
787 boolean filterLastUsed) {
arangelovf163a882020-02-18 17:18:47 +0000788 if (shouldShowTabs()) {
arangelov38a6fce2019-12-02 18:21:22 +0000789 mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles(
790 initialIntents, rList, filterLastUsed);
791 } else {
792 mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile(
793 initialIntents, rList, filterLastUsed);
794 }
795 return mChooserMultiProfilePagerAdapter;
796 }
797
798 private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
799 Intent[] initialIntents,
800 List<ResolveInfo> rList,
801 boolean filterLastUsed) {
802 ChooserGridAdapter adapter = createChooserGridAdapter(
803 /* context */ this,
804 /* payloadIntents */ mIntents,
805 initialIntents,
806 rList,
807 filterLastUsed,
808 mUseLayoutForBrowsables,
809 /* userHandle */ UserHandle.of(UserHandle.myUserId()));
810 return new ChooserMultiProfilePagerAdapter(
811 /* context */ this,
arangelov7981b122020-01-16 10:58:27 +0000812 adapter,
813 getPersonalProfileUserHandle(),
814 /* workProfileUserHandle= */ null);
arangelov38a6fce2019-12-02 18:21:22 +0000815 }
816
817 private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
818 Intent[] initialIntents,
819 List<ResolveInfo> rList,
820 boolean filterLastUsed) {
821 ChooserGridAdapter personalAdapter = createChooserGridAdapter(
822 /* context */ this,
823 /* payloadIntents */ mIntents,
824 initialIntents,
825 rList,
826 filterLastUsed,
827 mUseLayoutForBrowsables,
828 /* userHandle */ getPersonalProfileUserHandle());
829 ChooserGridAdapter workAdapter = createChooserGridAdapter(
830 /* context */ this,
831 /* payloadIntents */ mIntents,
832 initialIntents,
833 rList,
834 filterLastUsed,
835 mUseLayoutForBrowsables,
836 /* userHandle */ getWorkProfileUserHandle());
837 return new ChooserMultiProfilePagerAdapter(
838 /* context */ this,
839 personalAdapter,
840 workAdapter,
arangelov7981b122020-01-16 10:58:27 +0000841 /* defaultProfile */ getCurrentProfile(),
842 getPersonalProfileUserHandle(),
843 getWorkProfileUserHandle());
arangelov38a6fce2019-12-02 18:21:22 +0000844 }
845
846 @Override
847 protected boolean postRebuildList(boolean rebuildCompleted) {
arangelov590fba32020-02-11 18:05:42 +0000848 updateStickyContentPreview();
849 if (shouldShowStickyContentPreview()
850 || mChooserMultiProfilePagerAdapter
851 .getCurrentRootAdapter().getContentPreviewRowCount() != 0) {
852 logActionShareWithPreview();
853 }
arangelov38a6fce2019-12-02 18:21:22 +0000854 return postRebuildListInternal(rebuildCompleted);
855 }
856
Matt Pietal26038402019-01-08 07:29:34 -0500857 /**
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700858 * Returns true if app prediction service is defined and the component exists on device.
859 */
Mehdi Alizadehe870e972019-09-11 17:54:15 -0700860 @VisibleForTesting
861 public boolean isAppPredictionServiceAvailable() {
862 if (getPackageManager().getAppPredictionServicePackageName() == null) {
863 // Default AppPredictionService is not defined.
864 return false;
865 }
866
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700867 final String appPredictionServiceName =
868 getString(R.string.config_defaultAppPredictionService);
869 if (appPredictionServiceName == null) {
870 return false;
871 }
872 final ComponentName appPredictionComponentName =
873 ComponentName.unflattenFromString(appPredictionServiceName);
874 if (appPredictionComponentName == null) {
875 return false;
876 }
877
878 // Check if the app prediction component actually exists on the device.
879 Intent intent = new Intent();
880 intent.setComponent(appPredictionComponentName);
881 if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) {
882 Log.e(TAG, "App prediction service is defined, but does not exist: "
883 + appPredictionServiceName);
884 return false;
885 }
886 return true;
887 }
888
889 /**
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500890 * Check if the profile currently used is a work profile.
891 * @return true if it is work profile, false if it is parent profile (or no work profile is
892 * set up)
893 */
894 protected boolean isWorkProfile() {
arangelov38a6fce2019-12-02 18:21:22 +0000895 return getSystemService(UserManager.class)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500896 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
897 }
898
Matt Pietalab73a882019-06-05 07:04:55 -0400899 @Override
arangelov7981b122020-01-16 10:58:27 +0000900 protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) {
Matt Pietalab73a882019-06-05 07:04:55 -0400901 return new PackageMonitor() {
902 @Override
903 public void onSomePackagesChanged() {
arangelov7981b122020-01-16 10:58:27 +0000904 handlePackagesChanged(listAdapter);
Matt Pietalab73a882019-06-05 07:04:55 -0400905 }
906 };
907 }
908
Alison Cichowlasbc290812019-12-17 19:42:29 -0500909 /**
910 * Update UI to reflect changes in data.
911 */
912 public void handlePackagesChanged() {
arangelov7981b122020-01-16 10:58:27 +0000913 handlePackagesChanged(/* listAdapter */ null);
914 }
915
916 /**
917 * Update UI to reflect changes in data.
918 * <p>If {@code listAdapter} is {@code null}, both profile list adapters are updated.
919 */
920 private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) {
921 if (listAdapter == null) {
922 mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
923 } else {
924 listAdapter.handlePackagesChanged();
925 }
Alison Cichowlasbc290812019-12-17 19:42:29 -0500926 updateProfileViewButton();
927 }
928
Matt Pietal46d828c2019-02-05 08:07:07 -0500929 private void onCopyButtonClicked(View v) {
930 Intent targetIntent = getTargetIntent();
931 if (targetIntent == null) {
932 finish();
933 } else {
934 final String action = targetIntent.getAction();
935
936 ClipData clipData = null;
937 if (Intent.ACTION_SEND.equals(action)) {
938 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
939 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
940
941 if (extraText != null) {
942 clipData = ClipData.newPlainText(null, extraText);
943 } else if (extraStream != null) {
944 clipData = ClipData.newUri(getContentResolver(), null, extraStream);
945 } else {
946 Log.w(TAG, "No data available to copy to clipboard");
947 return;
948 }
949 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
950 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
951 Intent.EXTRA_STREAM);
952 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
953 for (int i = 1; i < streams.size(); i++) {
954 clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
955 }
956 } else {
957 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
958 // so warn about unexpected action
959 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
960 return;
961 }
962
963 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
964 Context.CLIPBOARD_SERVICE);
965 clipboardManager.setPrimaryClip(clipData);
966 Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
967
Alison Cichowlasaa7f79f2019-09-12 15:57:26 -0400968 // Log share completion via copy
969 LogMaker targetLogMaker = new LogMaker(
970 MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
971 getMetricsLogger().write(targetLogMaker);
972
Matt Pietal46d828c2019-02-05 08:07:07 -0500973 finish();
974 }
975 }
976
Matt Pietal18bbd822019-02-12 15:21:36 -0500977 @Override
978 public void onConfigurationChanged(Configuration newConfig) {
979 super.onConfigurationChanged(newConfig);
980
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400981 adjustPreviewWidth(newConfig.orientation, null);
arangelovc4dbdbd2020-02-18 20:54:16 +0000982 updateStickyContentPreview();
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400983 }
984
985 private boolean shouldDisplayLandscape(int orientation) {
986 // Sharesheet fixes the # of items per row and therefore can not correctly lay out
987 // when in the restricted size of multi-window mode. In the future, would be nice
988 // to use minimum dp size requirements instead
989 return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
990 }
991
992 private void adjustPreviewWidth(int orientation, View parent) {
Matt Pietal18bbd822019-02-12 15:21:36 -0500993 int width = -1;
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400994 if (shouldDisplayLandscape(orientation)) {
Matt Pietal18bbd822019-02-12 15:21:36 -0500995 width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
996 }
997
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400998 parent = parent == null ? getWindow().getDecorView() : parent;
999
1000 updateLayoutWidth(R.id.content_preview_text_layout, width, parent);
1001 updateLayoutWidth(R.id.content_preview_title_layout, width, parent);
1002 updateLayoutWidth(R.id.content_preview_file_layout, width, parent);
Matt Pietal18bbd822019-02-12 15:21:36 -05001003 }
1004
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001005 private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
1006 View view = parent.findViewById(layoutResourceId);
Matt Pietal1ef88002019-03-13 10:43:18 -04001007 if (view != null && view.getLayoutParams() != null) {
1008 LayoutParams params = view.getLayoutParams();
1009 params.width = width;
1010 view.setLayoutParams(params);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001011 }
1012 }
1013
arangelova3912cf2019-12-13 14:34:45 +00001014 private ViewGroup createContentPreviewView(ViewGroup parent) {
1015 Intent targetIntent = getTargetIntent();
1016 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
1017 return displayContentPreview(previewType, targetIntent, getLayoutInflater(), parent);
1018 }
1019
Dan Sandlere3d19932019-11-22 11:58:58 -05001020 private ComponentName getNearbySharingComponent() {
1021 String nearbyComponent = Settings.Secure.getString(
1022 getContentResolver(),
1023 Settings.Secure.NEARBY_SHARING_COMPONENT);
1024 if (TextUtils.isEmpty(nearbyComponent)) {
1025 nearbyComponent = getString(R.string.config_defaultNearbySharingComponent);
1026 }
1027 if (TextUtils.isEmpty(nearbyComponent)) {
1028 return null;
1029 }
1030 return ComponentName.unflattenFromString(nearbyComponent);
1031 }
1032
1033 private TargetInfo getNearbySharingTarget(Intent originalIntent) {
1034 final ComponentName cn = getNearbySharingComponent();
1035 if (cn == null) return null;
1036
1037 final Intent resolveIntent = new Intent();
1038 resolveIntent.setComponent(cn);
1039 final ResolveInfo ri = getPackageManager().resolveActivity(
1040 resolveIntent, PackageManager.GET_META_DATA);
1041 if (ri == null || ri.activityInfo == null) {
1042 Log.e(TAG, "Device-specified nearby sharing component (" + cn
1043 + ") not available");
1044 return null;
1045 }
1046
1047 // Allow the nearby sharing component to provide a more appropriate icon and label
1048 // for the chip.
1049 CharSequence name = null;
1050 Drawable icon = null;
1051 final Bundle metaData = ri.activityInfo.metaData;
1052 if (metaData != null) {
1053 try {
1054 final Resources pkgRes = getPackageManager().getResourcesForActivity(cn);
1055 final int nameResId = metaData.getInt(CHIP_LABEL_METADATA_KEY);
1056 name = pkgRes.getString(nameResId);
1057 final int resId = metaData.getInt(CHIP_ICON_METADATA_KEY);
1058 icon = pkgRes.getDrawable(resId);
1059 } catch (Resources.NotFoundException ex) {
1060 } catch (NameNotFoundException ex) {
1061 }
1062 }
1063 if (TextUtils.isEmpty(name)) {
1064 name = ri.loadLabel(getPackageManager());
1065 }
1066 if (icon == null) {
1067 icon = ri.loadIcon(getPackageManager());
1068 }
1069
1070 final DisplayResolveInfo dri = new DisplayResolveInfo(
1071 originalIntent, ri, name, "", resolveIntent, null);
1072 dri.setDisplayIcon(icon);
1073 return dri;
1074 }
1075
1076 private Button createActionButton(Drawable icon, CharSequence title, View.OnClickListener r) {
1077 Button b = (Button) LayoutInflater.from(this).inflate(R.layout.chooser_action_button, null);
1078 if (icon != null) {
1079 final int size = getResources()
1080 .getDimensionPixelSize(R.dimen.chooser_action_button_icon_size);
1081 icon.setBounds(0, 0, size, size);
1082 b.setCompoundDrawablesRelative(icon, null, null, null);
1083 }
1084 b.setText(title);
1085 b.setOnClickListener(r);
1086 return b;
1087 }
1088
1089 private Button createCopyButton() {
1090 final Button b = createActionButton(
1091 getDrawable(R.drawable.ic_menu_copy_material),
1092 getString(R.string.copy), this::onCopyButtonClicked);
1093 b.setId(R.id.chooser_copy_button);
1094 return b;
1095 }
1096
1097 private @Nullable Button createNearbyButton(Intent originalIntent) {
1098 final TargetInfo ti = getNearbySharingTarget(originalIntent);
1099 if (ti == null) return null;
1100
1101 return createActionButton(
1102 ti.getDisplayIcon(this),
1103 ti.getDisplayLabel(),
1104 (View unused) -> {
1105 safelyStartActivity(ti);
1106 finish();
1107 }
1108 );
1109 }
1110
1111 private void addActionButton(ViewGroup parent, Button b) {
1112 if (b == null) return;
1113 final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(
1114 LayoutParams.WRAP_CONTENT,
1115 LayoutParams.WRAP_CONTENT
1116 );
1117 final int gap = getResources().getDimensionPixelSize(R.dimen.resolver_icon_margin) / 2;
1118 lp.setMarginsRelative(gap, 0, gap, 0);
1119 parent.addView(b, lp);
1120 }
1121
Matt Pietal1ef88002019-03-13 10:43:18 -04001122 private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
Zhen Zhangbde7b462019-11-11 11:49:33 -08001123 Intent targetIntent, LayoutInflater layoutInflater, ViewGroup parent) {
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001124 ViewGroup layout = null;
1125
Matt Pietal1ef88002019-03-13 10:43:18 -04001126 switch (previewType) {
1127 case CONTENT_PREVIEW_TEXT:
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001128 layout = displayTextContentPreview(targetIntent, layoutInflater, parent);
1129 break;
Matt Pietal1ef88002019-03-13 10:43:18 -04001130 case CONTENT_PREVIEW_IMAGE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001131 layout = displayImageContentPreview(targetIntent, layoutInflater, parent);
1132 break;
Matt Pietal1ef88002019-03-13 10:43:18 -04001133 case CONTENT_PREVIEW_FILE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001134 layout = displayFileContentPreview(targetIntent, layoutInflater, parent);
1135 break;
Matt Pietal1ef88002019-03-13 10:43:18 -04001136 default:
1137 Log.e(TAG, "Unexpected content preview type: " + previewType);
1138 }
Matt Pietal0ea391b2019-01-30 10:44:15 -05001139
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001140 if (layout != null) {
1141 adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
1142 }
1143
1144 return layout;
Matt Pietal1ef88002019-03-13 10:43:18 -04001145 }
1146
1147 private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001148 ViewGroup parent) {
1149 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1150 R.layout.chooser_grid_preview_text, parent, false);
Matt Pietal1ef88002019-03-13 10:43:18 -04001151
Dan Sandlere3d19932019-11-22 11:58:58 -05001152 final ViewGroup actionRow =
1153 (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
1154 addActionButton(actionRow, createCopyButton());
1155 addActionButton(actionRow, createNearbyButton(targetIntent));
Matt Pietal46d828c2019-02-05 08:07:07 -05001156
Matt Pietal26038402019-01-08 07:29:34 -05001157 CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
Matt Pietal26038402019-01-08 07:29:34 -05001158 if (sharingText == null) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001159 contentPreviewLayout.findViewById(R.id.content_preview_text_layout).setVisibility(
1160 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -05001161 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -04001162 TextView textView = contentPreviewLayout.findViewById(R.id.content_preview_text);
Matt Pietal1fa7d802019-01-30 10:44:15 -05001163 textView.setText(sharingText);
Matt Pietal26038402019-01-08 07:29:34 -05001164 }
1165
1166 String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
Matt Pietal46d828c2019-02-05 08:07:07 -05001167 if (TextUtils.isEmpty(previewTitle)) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001168 contentPreviewLayout.findViewById(R.id.content_preview_title_layout).setVisibility(
1169 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -05001170 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -04001171 TextView previewTitleView = contentPreviewLayout.findViewById(
1172 R.id.content_preview_title);
Matt Pietal26038402019-01-08 07:29:34 -05001173 previewTitleView.setText(previewTitle);
Matt Pietal26038402019-01-08 07:29:34 -05001174
Matt Pietal1fa7d802019-01-30 10:44:15 -05001175 ClipData previewData = targetIntent.getClipData();
1176 Uri previewThumbnail = null;
1177 if (previewData != null) {
1178 if (previewData.getItemCount() > 0) {
1179 ClipData.Item previewDataItem = previewData.getItemAt(0);
1180 previewThumbnail = previewDataItem.getUri();
1181 }
Matt Pietal26038402019-01-08 07:29:34 -05001182 }
Matt Pietal26038402019-01-08 07:29:34 -05001183
Matt Pietal1ef88002019-03-13 10:43:18 -04001184 ImageView previewThumbnailView = contentPreviewLayout.findViewById(
1185 R.id.content_preview_thumbnail);
Matt Pietal1fa7d802019-01-30 10:44:15 -05001186 if (previewThumbnail == null) {
Matt Pietal26038402019-01-08 07:29:34 -05001187 previewThumbnailView.setVisibility(View.GONE);
1188 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -04001189 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
1190 mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
Matt Pietal26038402019-01-08 07:29:34 -05001191 }
1192 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001193
1194 return contentPreviewLayout;
Matt Pietal26038402019-01-08 07:29:34 -05001195 }
1196
Matt Pietal1ef88002019-03-13 10:43:18 -04001197 private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001198 ViewGroup parent) {
1199 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1200 R.layout.chooser_grid_preview_image, parent, false);
1201 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001202
1203 String action = targetIntent.getAction();
1204 if (Intent.ACTION_SEND.equals(action)) {
1205 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietale7cacab2019-05-23 07:21:36 -04001206 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001207 } else {
1208 ContentResolver resolver = getContentResolver();
1209
1210 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1211 List<Uri> imageUris = new ArrayList<>();
1212 for (Uri uri : uris) {
1213 if (isImageType(resolver.getType(uri))) {
1214 imageUris.add(uri);
1215 }
1216 }
1217
1218 if (imageUris.size() == 0) {
1219 Log.i(TAG, "Attempted to display image preview area with zero"
1220 + " available images detected in EXTRA_STREAM list");
Matt Pietal46d828c2019-02-05 08:07:07 -05001221 contentPreviewLayout.setVisibility(View.GONE);
Matt Pietal1ef88002019-03-13 10:43:18 -04001222 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001223 }
1224
Matt Pietale7cacab2019-05-23 07:21:36 -04001225 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001226
1227 if (imageUris.size() == 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001228 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
1229 imageUris.get(1), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001230 } else if (imageUris.size() > 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001231 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
1232 imageUris.get(1), 0);
1233 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
1234 imageUris.get(2), imageUris.size() - 3);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001235 }
1236 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001237
1238 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001239 }
1240
Matt Pietal46d828c2019-02-05 08:07:07 -05001241 private static class FileInfo {
1242 public final String name;
1243 public final boolean hasThumbnail;
1244
1245 FileInfo(String name, boolean hasThumbnail) {
1246 this.name = name;
1247 this.hasThumbnail = hasThumbnail;
1248 }
1249 }
1250
Matt Pietalf38e9d22019-02-15 10:01:03 -05001251 /**
1252 * Wrapping the ContentResolver call to expose for easier mocking,
1253 * and to avoid mocking Android core classes.
1254 */
1255 @VisibleForTesting
1256 public Cursor queryResolver(ContentResolver resolver, Uri uri) {
1257 return resolver.query(uri, null, null, null, null);
1258 }
1259
Matt Pietal46d828c2019-02-05 08:07:07 -05001260 private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
1261 String fileName = null;
1262 boolean hasThumbnail = false;
Matt Pietal3087bca2019-02-14 12:19:16 -05001263
Matt Pietalf38e9d22019-02-15 10:01:03 -05001264 try (Cursor cursor = queryResolver(resolver, uri)) {
1265 if (cursor != null && cursor.getCount() > 0) {
1266 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
1267 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
1268 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
1269
1270 cursor.moveToFirst();
1271 if (nameIndex != -1) {
1272 fileName = cursor.getString(nameIndex);
1273 } else if (titleIndex != -1) {
1274 fileName = cursor.getString(titleIndex);
1275 }
1276
1277 if (flagsIndex != -1) {
1278 hasThumbnail = (cursor.getInt(flagsIndex)
1279 & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
1280 }
1281 }
Matt Pietal73a873f2019-03-15 08:46:20 -04001282 } catch (SecurityException | NullPointerException e) {
Matt Pietal62532e52019-05-07 09:51:37 -04001283 logContentPreviewWarning(uri);
Matt Pietal3087bca2019-02-14 12:19:16 -05001284 }
1285
Matt Pietal46d828c2019-02-05 08:07:07 -05001286 if (TextUtils.isEmpty(fileName)) {
1287 fileName = uri.getPath();
1288 int index = fileName.lastIndexOf('/');
1289 if (index != -1) {
1290 fileName = fileName.substring(index + 1);
1291 }
1292 }
1293
1294 return new FileInfo(fileName, hasThumbnail);
1295 }
1296
Matt Pietal62532e52019-05-07 09:51:37 -04001297 private void logContentPreviewWarning(Uri uri) {
1298 // The ContentResolver already logs the exception. Log something more informative.
1299 Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
1300 + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
1301 + "and set your Intent's clipData and flags in accordance with that method's "
1302 + "documentation");
1303 }
1304
Matt Pietal1ef88002019-03-13 10:43:18 -04001305 private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001306 ViewGroup parent) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001307
Matt Pietale7cacab2019-05-23 07:21:36 -04001308 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1309 R.layout.chooser_grid_preview_file, parent, false);
Matt Pietal46d828c2019-02-05 08:07:07 -05001310
1311 // TODO(b/120417119): Disable file copy until after moving to sysui,
1312 // due to permissions issues
Dan Sandlere3d19932019-11-22 11:58:58 -05001313 //((ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row))
1314 // .addView(createCopyButton());
Matt Pietal46d828c2019-02-05 08:07:07 -05001315
Matt Pietal3087bca2019-02-14 12:19:16 -05001316 String action = targetIntent.getAction();
1317 if (Intent.ACTION_SEND.equals(action)) {
1318 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietal1ef88002019-03-13 10:43:18 -04001319 loadFileUriIntoView(uri, contentPreviewLayout);
Matt Pietal3087bca2019-02-14 12:19:16 -05001320 } else {
1321 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1322 int uriCount = uris.size();
Matt Pietal46d828c2019-02-05 08:07:07 -05001323
Matt Pietal3087bca2019-02-14 12:19:16 -05001324 if (uriCount == 0) {
1325 contentPreviewLayout.setVisibility(View.GONE);
1326 Log.i(TAG,
1327 "Appears to be no uris available in EXTRA_STREAM, removing "
1328 + "preview area");
Matt Pietal1ef88002019-03-13 10:43:18 -04001329 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001330 } else if (uriCount == 1) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001331 loadFileUriIntoView(uris.get(0), contentPreviewLayout);
Matt Pietal46d828c2019-02-05 08:07:07 -05001332 } else {
Matt Pietal3087bca2019-02-14 12:19:16 -05001333 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
1334 int remUriCount = uriCount - 1;
Matt Pietalacabc572019-02-14 11:02:05 -05001335 String fileName = getResources().getQuantityString(R.plurals.file_count,
Matt Pietal3087bca2019-02-14 12:19:16 -05001336 remUriCount, fileInfo.name, remUriCount);
Matt Pietalacabc572019-02-14 11:02:05 -05001337
Matt Pietal1ef88002019-03-13 10:43:18 -04001338 TextView fileNameView = contentPreviewLayout.findViewById(
1339 R.id.content_preview_filename);
Matt Pietalacabc572019-02-14 11:02:05 -05001340 fileNameView.setText(fileName);
Matt Pietal3087bca2019-02-14 12:19:16 -05001341
Matt Pietale7cacab2019-05-23 07:21:36 -04001342 View thumbnailView = contentPreviewLayout.findViewById(
1343 R.id.content_preview_file_thumbnail);
1344 thumbnailView.setVisibility(View.GONE);
1345
Matt Pietal1ef88002019-03-13 10:43:18 -04001346 ImageView fileIconView = contentPreviewLayout.findViewById(
1347 R.id.content_preview_file_icon);
Matt Pietal46d828c2019-02-05 08:07:07 -05001348 fileIconView.setVisibility(View.VISIBLE);
Matt Pietalacabc572019-02-14 11:02:05 -05001349 fileIconView.setImageResource(R.drawable.ic_file_copy);
Matt Pietal46d828c2019-02-05 08:07:07 -05001350 }
Matt Pietal3087bca2019-02-14 12:19:16 -05001351 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001352
1353 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001354 }
1355
Matt Pietale7cacab2019-05-23 07:21:36 -04001356 private void loadFileUriIntoView(final Uri uri, final View parent) {
Matt Pietal3087bca2019-02-14 12:19:16 -05001357 FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
1358
Matt Pietal1ef88002019-03-13 10:43:18 -04001359 TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
Matt Pietal3087bca2019-02-14 12:19:16 -05001360 fileNameView.setText(fileInfo.name);
1361
1362 if (fileInfo.hasThumbnail) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001363 mPreviewCoord = new ContentPreviewCoordinator(parent, false);
1364 mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
Matt Pietal3087bca2019-02-14 12:19:16 -05001365 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -04001366 View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
1367 thumbnailView.setVisibility(View.GONE);
1368
Matt Pietal1ef88002019-03-13 10:43:18 -04001369 ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
Matt Pietal3087bca2019-02-14 12:19:16 -05001370 fileIconView.setVisibility(View.VISIBLE);
Matt Pietal832cdbf2019-04-05 13:20:31 -04001371 fileIconView.setImageResource(R.drawable.chooser_file_generic);
Matt Pietal46d828c2019-02-05 08:07:07 -05001372 }
Matt Pietal0ea391b2019-01-30 10:44:15 -05001373 }
1374
Matt Pietal0ea391b2019-01-30 10:44:15 -05001375 @VisibleForTesting
1376 protected boolean isImageType(String mimeType) {
1377 return mimeType != null && mimeType.startsWith("image/");
1378 }
1379
1380 @ContentPreviewType
1381 private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
1382 if (uri == null) {
1383 return CONTENT_PREVIEW_TEXT;
1384 }
1385
1386 String mimeType = resolver.getType(uri);
1387 return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
1388 }
1389
1390 /**
1391 * In {@link android.content.Intent#getType}, the app may specify a very general
1392 * mime-type that broadly covers all data being shared, such as {@literal *}/*
1393 * when sending an image and text. We therefore should inspect each item for the
1394 * the preferred type, in order of IMAGE, FILE, TEXT.
1395 */
1396 @ContentPreviewType
1397 private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
1398 String action = targetIntent.getAction();
1399 if (Intent.ACTION_SEND.equals(action)) {
1400 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1401 return findPreferredContentPreview(uri, resolver);
1402 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1403 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1404 if (uris == null || uris.isEmpty()) {
1405 return CONTENT_PREVIEW_TEXT;
1406 }
1407
1408 for (Uri uri : uris) {
Matt Pietal832cdbf2019-04-05 13:20:31 -04001409 // Defaulting to file preview when there are mixed image/file types is
1410 // preferable, as it shows the user the correct number of items being shared
1411 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_FILE) {
1412 return CONTENT_PREVIEW_FILE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001413 }
1414 }
1415
Matt Pietal832cdbf2019-04-05 13:20:31 -04001416 return CONTENT_PREVIEW_IMAGE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001417 }
1418
1419 return CONTENT_PREVIEW_TEXT;
1420 }
1421
Mike Digman849a9d12019-04-29 11:20:48 -07001422 private int getNumSheetExpansions() {
1423 return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0);
1424 }
1425
1426 private void incrementNumSheetExpansions() {
1427 getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS,
1428 getNumSheetExpansions() + 1).apply();
1429 }
1430
Adam Powell0b3c1122014-10-09 12:50:14 -07001431 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07001432 protected void onDestroy() {
1433 super.onDestroy();
1434 if (mRefinementResultReceiver != null) {
1435 mRefinementResultReceiver.destroy();
1436 mRefinementResultReceiver = null;
1437 }
Adam Powell9761ab22015-09-08 17:01:49 -07001438 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -04001439 mChooserHandler.removeAllMessages();
Matt Pietale7cacab2019-05-23 07:21:36 -04001440
1441 if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
1442
arangelov5fc9e7d2020-01-07 17:59:14 +00001443 mChooserMultiProfilePagerAdapter.getActiveListAdapter().destroyAppPredictor();
1444 if (mChooserMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
1445 mChooserMultiProfilePagerAdapter.getInactiveListAdapter().destroyAppPredictor();
George Hodulik69d4a082019-01-18 11:27:03 -08001446 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001447 }
1448
arangelovb0802dc2019-10-18 18:03:44 +01001449 @Override // ResolverListCommunicator
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001450 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
1451 Intent result = defIntent;
Adam Powelle49d9392014-07-17 18:45:19 -07001452 if (mReplacementExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001453 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
Adam Powelle49d9392014-07-17 18:45:19 -07001454 if (replExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001455 result = new Intent(defIntent);
Adam Powelle49d9392014-07-17 18:45:19 -07001456 result.putExtras(replExtras);
Adam Powelle49d9392014-07-17 18:45:19 -07001457 }
1458 }
Nicolas Prevot741abfc2015-08-11 12:03:51 +01001459 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001460 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
1461 result = Intent.createChooser(result,
1462 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
Hakan Seyalioglu7317e8a2016-12-12 16:15:38 -08001463
1464 // Don't auto-launch single intents if the intent is being forwarded. This is done
1465 // because automatically launching a resolving application as a response to the user
1466 // action of switching accounts is pretty unexpected.
1467 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001468 }
1469 return result;
Adam Powelle49d9392014-07-17 18:45:19 -07001470 }
1471
Adam Powell0b3c1122014-10-09 12:50:14 -07001472 @Override
Adam Powell23882512016-01-29 10:21:00 -08001473 public void onActivityStarted(TargetInfo cti) {
Adam Powell0b3c1122014-10-09 12:50:14 -07001474 if (mChosenComponentSender != null) {
Adam Powell24428412015-04-01 17:19:56 -07001475 final ComponentName target = cti.getResolvedComponentName();
Adam Powell0b3c1122014-10-09 12:50:14 -07001476 if (target != null) {
1477 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
1478 try {
1479 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
1480 } catch (IntentSender.SendIntentException e) {
1481 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
1482 + "the chosen component: " + e);
1483 }
1484 }
1485 }
1486 }
1487
Adam Powell24428412015-04-01 17:19:56 -07001488 @Override
arangelovcf268642020-01-15 15:09:51 +00001489 public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
Adam Powell52c39212016-04-07 15:14:18 -07001490 if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
arangelova3912cf2019-12-13 14:34:45 +00001491 mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
arangelov38a6fce2019-12-02 18:21:22 +00001492 /* origTarget */ null,
1493 Lists.newArrayList(mCallerChooserTargets),
arangelov5fc9e7d2020-01-07 17:59:14 +00001494 TARGET_TYPE_DEFAULT,
Song Hue2deffd2020-03-09 15:22:29 -07001495 /* directShareShortcutInfoCache */ null, mServiceConnections);
Adam Powell52c39212016-04-07 15:14:18 -07001496 }
arangelovb0802dc2019-10-18 18:03:44 +01001497 }
1498
1499 @Override
Adam Powell23882512016-01-29 10:21:00 -08001500 public int getLayoutResource() {
Adam Powell7d758002015-05-06 17:49:36 -07001501 return R.layout.chooser_grid;
Adam Powell24428412015-04-01 17:19:56 -07001502 }
1503
arangelovb0802dc2019-10-18 18:03:44 +01001504 @Override // ResolverListCommunicator
Adam Powell23882512016-01-29 10:21:00 -08001505 public boolean shouldGetActivityMetadata() {
Adam Powell24428412015-04-01 17:19:56 -07001506 return true;
1507 }
1508
Adam Powell9761ab22015-09-08 17:01:49 -07001509 @Override
Ben Lin145b0ca2016-10-14 14:23:40 -07001510 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
Hakan Seyalioglu13405c52017-01-31 19:01:31 -08001511 // Note that this is only safe because the Intent handled by the ChooserActivity is
1512 // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
1513 // method can not be replaced in the ResolverActivity whole hog.
Matt Pietala4b30072019-04-04 13:44:36 -04001514 if (!super.shouldAutoLaunchSingleChoice(target)) {
1515 return false;
1516 }
1517
1518 return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
Ben Lin145b0ca2016-10-14 14:23:40 -07001519 }
1520
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001521 void showTargetDetails(TargetInfo ti) {
1522 if (ti == null) {
sanryhuang296ca9e2018-03-31 11:17:13 +08001523 return;
1524 }
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001525 ComponentName name = ti.getResolveInfo().activityInfo.getComponentName();
Alison Cichowlas1fd47152019-11-14 19:50:55 -05001526 boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001527
1528 ResolverTargetActionsDialogFragment f;
1529
1530 // For multiple targets, include info on all targets
1531 if (ti instanceof MultiDisplayResolveInfo) {
1532 MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) ti;
1533 List<CharSequence> labels = new ArrayList<>();
1534
1535 for (TargetInfo innerInfo : mti.getTargets()) {
1536 labels.add(innerInfo.getResolveInfo().loadLabel(getPackageManager()));
1537 }
Alison Cichowlas4d21ff02019-12-19 18:41:52 -05001538 f = new ResolverTargetActionsDialogFragment(mti.getDisplayLabel(), name,
arangelovebf3c052020-03-17 13:11:37 +00001539 mti.getTargets(), labels,
1540 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001541 } else {
1542 f = new ResolverTargetActionsDialogFragment(
arangelovebf3c052020-03-17 13:11:37 +00001543 ti.getResolveInfo().loadLabel(getPackageManager()), name, pinned,
1544 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001545 }
1546
Adam Powell23882512016-01-29 10:21:00 -08001547 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1548 }
1549
Adam Powelle49d9392014-07-17 18:45:19 -07001550 private void modifyTargetIntent(Intent in) {
Matt Pietal95574b02019-03-13 08:12:25 -04001551 if (isSendAction(in)) {
Adam Powelle49d9392014-07-17 18:45:19 -07001552 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
Dianne Hackborn13420f22014-07-18 15:43:56 -07001553 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Adam Powelle49d9392014-07-17 18:45:19 -07001554 }
1555 }
Adam Powell24428412015-04-01 17:19:56 -07001556
Adam Powell2ed547e2015-04-29 18:45:04 -07001557 @Override
1558 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
1559 if (mRefinementIntentSender != null) {
1560 final Intent fillIn = new Intent();
1561 final List<Intent> sourceIntents = target.getAllSourceIntents();
1562 if (!sourceIntents.isEmpty()) {
1563 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
1564 if (sourceIntents.size() > 1) {
1565 final Intent[] alts = new Intent[sourceIntents.size() - 1];
1566 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
1567 alts[i - 1] = sourceIntents.get(i);
1568 }
1569 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
1570 }
1571 if (mRefinementResultReceiver != null) {
1572 mRefinementResultReceiver.destroy();
1573 }
1574 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
1575 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
1576 mRefinementResultReceiver);
1577 try {
1578 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
1579 return false;
1580 } catch (SendIntentException e) {
1581 Log.e(TAG, "Refinement IntentSender failed to send", e);
1582 }
1583 }
1584 }
Kang Li9fa2a2c2017-01-06 13:33:24 -08001585 updateModelAndChooserCounts(target);
Adam Powell2ed547e2015-04-29 18:45:04 -07001586 return super.onTargetSelected(target, alwaysCheck);
1587 }
1588
Adam Powell98b7f892015-06-19 12:38:45 -07001589 @Override
Adam Powell23882512016-01-29 10:21:00 -08001590 public void startSelected(int which, boolean always, boolean filtered) {
arangelov38a6fce2019-12-02 18:21:22 +00001591 ChooserListAdapter currentListAdapter =
arangelova3912cf2019-12-13 14:34:45 +00001592 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
arangelov38a6fce2019-12-02 18:21:22 +00001593 TargetInfo targetInfo = currentListAdapter
1594 .targetInfoForPosition(which, filtered);
Matt Pietala4b30072019-04-04 13:44:36 -04001595 if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
1596 return;
1597 }
1598
Kang Li9082f5b2016-12-02 10:56:21 -08001599 final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001600
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001601 if (targetInfo instanceof MultiDisplayResolveInfo) {
1602 MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
Alison Cichowlase12616a2019-12-20 19:05:27 -05001603 if (!mti.hasSelected()) {
1604 // Stacked apps get a disambiguation first
1605 CharSequence[] labels = new CharSequence[mti.getTargets().size()];
1606 int i = 0;
1607 for (TargetInfo ti : mti.getTargets()) {
1608 labels[i++] = ti.getResolveInfo().loadLabel(getPackageManager());
1609 }
1610 ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment(
1611 targetInfo.getDisplayLabel(),
1612 ((MultiDisplayResolveInfo) targetInfo), labels, which);
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001613
Alison Cichowlase12616a2019-12-20 19:05:27 -05001614 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1615 return;
1616 }
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001617 }
1618
Adam Powell98b7f892015-06-19 12:38:45 -07001619 super.startSelected(which, always, filtered);
1620
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001621
arangelov38a6fce2019-12-02 18:21:22 +00001622 if (currentListAdapter.getCount() > 0) {
Adam Powell98b7f892015-06-19 12:38:45 -07001623 // Log the index of which type of target the user picked.
1624 // Lower values mean the ranking was better.
1625 int cat = 0;
1626 int value = which;
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001627 int directTargetAlsoRanked = -1;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001628 int numCallerProvided = 0;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001629 HashedStringCache.HashResult directTargetHashed = null;
arangelov38a6fce2019-12-02 18:21:22 +00001630 switch (currentListAdapter.getPositionTargetType(which)) {
Adam Powell98b7f892015-06-19 12:38:45 -07001631 case ChooserListAdapter.TARGET_SERVICE:
Chris Wrenf6e9228b2016-01-26 18:04:35 -05001632 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001633 // Log the package name + target name to answer the question if most users
1634 // share to mostly the same person or to a bunch of different people.
arangelov38a6fce2019-12-02 18:21:22 +00001635 ChooserTarget target = currentListAdapter.getChooserTargetForValue(value);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001636 directTargetHashed = HashedStringCache.getInstance().hashString(
1637 this,
1638 TAG,
1639 target.getComponentName().getPackageName()
1640 + target.getTitle().toString(),
1641 mMaxHashSaltDays);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001642 directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001643
1644 if (mCallerChooserTargets != null) {
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001645 numCallerProvided = mCallerChooserTargets.length;
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001646 }
Adam Powell98b7f892015-06-19 12:38:45 -07001647 break;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001648 case ChooserListAdapter.TARGET_CALLER:
Adam Powell98b7f892015-06-19 12:38:45 -07001649 case ChooserListAdapter.TARGET_STANDARD:
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001650 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
arangelov38a6fce2019-12-02 18:21:22 +00001651 value -= currentListAdapter.getSelectableServiceTargetCount();
1652 numCallerProvided = currentListAdapter.getCallerTargetCount();
Adam Powell98b7f892015-06-19 12:38:45 -07001653 break;
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04001654 case ChooserListAdapter.TARGET_STANDARD_AZ:
1655 // A-Z targets are unranked standard targets; we use -1 to mark that they
1656 // are from the alphabetical pool.
1657 value = -1;
1658 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
1659 break;
Adam Powell98b7f892015-06-19 12:38:45 -07001660 }
1661
1662 if (cat != 0) {
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001663 LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
1664 if (directTargetHashed != null) {
1665 targetLogMaker.addTaggedData(
1666 MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
1667 targetLogMaker.addTaggedData(
1668 MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
1669 directTargetHashed.saltGeneration);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001670 targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
1671 directTargetAlsoRanked);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001672 }
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001673 targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED,
1674 numCallerProvided);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001675 getMetricsLogger().write(targetLogMaker);
Adam Powell98b7f892015-06-19 12:38:45 -07001676 }
Kang Li9082f5b2016-12-02 10:56:21 -08001677
1678 if (mIsSuccessfullySelected) {
1679 if (DEBUG) {
1680 Log.d(TAG, "User Selection Time Cost is " + selectionCost);
1681 Log.d(TAG, "position of selected app/service/caller is " +
1682 Integer.toString(value));
1683 }
1684 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
1685 (int) selectionCost);
1686 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
1687 }
Adam Powell98b7f892015-06-19 12:38:45 -07001688 }
1689 }
1690
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001691 private int getRankedPosition(SelectableTargetInfo targetInfo) {
1692 String targetPackageName =
1693 targetInfo.getChooserTarget().getComponentName().getPackageName();
arangelov38a6fce2019-12-02 18:21:22 +00001694 ChooserListAdapter currentListAdapter =
arangelova3912cf2019-12-13 14:34:45 +00001695 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
arangelov38a6fce2019-12-02 18:21:22 +00001696 int maxRankedResults = Math.min(currentListAdapter.mDisplayList.size(),
1697 MAX_LOG_RANK_POSITION);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001698
1699 for (int i = 0; i < maxRankedResults; i++) {
arangelov38a6fce2019-12-02 18:21:22 +00001700 if (currentListAdapter.mDisplayList.get(i)
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001701 .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
1702 return i;
1703 }
1704 }
1705 return -1;
1706 }
1707
Matt Pietal9236adc2019-12-12 09:24:23 -05001708 @Override
1709 protected boolean shouldAddFooterView() {
1710 // To accommodate for window insets
1711 return true;
1712 }
1713
1714 @Override
1715 protected void applyFooterView(int height) {
1716 int count = mChooserMultiProfilePagerAdapter.getItemCount();
1717
1718 for (int i = 0; i < count; i++) {
1719 mChooserMultiProfilePagerAdapter.getAdapterForIndex(i).setFooterHeight(height);
1720 }
1721 }
1722
Adam Powell24428412015-04-01 17:19:56 -07001723 void queryTargetServices(ChooserListAdapter adapter) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001724 mQueriedTargetServicesTimeMs = System.currentTimeMillis();
1725
arangelov5fc9e7d2020-01-07 17:59:14 +00001726 Context selectedProfileContext = createContextAsUser(
1727 adapter.getUserHandle(), 0 /* flags */);
1728 final PackageManager pm = selectedProfileContext.getPackageManager();
1729 ShortcutManager sm = selectedProfileContext.getSystemService(ShortcutManager.class);
Adam Powell24428412015-04-01 17:19:56 -07001730 int targetsToQuery = 0;
Matt Pietalab73a882019-06-05 07:04:55 -04001731
Adam Powell24428412015-04-01 17:19:56 -07001732 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
1733 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
Adam Powell3a09c522015-10-21 13:21:28 -07001734 if (adapter.getScore(dri) == 0) {
1735 // A score of 0 means the app hasn't been used in some time;
1736 // don't query it as it's not likely to be relevant.
1737 continue;
1738 }
Adam Powell24428412015-04-01 17:19:56 -07001739 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
arangelovb0802dc2019-10-18 18:03:44 +01001740 if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001741 && sm.hasShareTargets(ai.packageName)) {
1742 // Share targets will be queried from ShortcutManager
1743 continue;
1744 }
Adam Powell24428412015-04-01 17:19:56 -07001745 final Bundle md = ai.metaData;
1746 final String serviceName = md != null ? convertServiceName(ai.packageName,
1747 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
1748 if (serviceName != null) {
1749 final ComponentName serviceComponent = new ComponentName(
1750 ai.packageName, serviceName);
Matt Pietalab73a882019-06-05 07:04:55 -04001751
arangelov5fc9e7d2020-01-07 17:59:14 +00001752 UserHandle userHandle = adapter.getUserHandle();
1753 Pair<ComponentName, UserHandle> requestedItem =
1754 new Pair<>(serviceComponent, userHandle);
1755 if (mServicesRequested.contains(requestedItem)) {
Matt Pietalab73a882019-06-05 07:04:55 -04001756 continue;
1757 }
arangelov5fc9e7d2020-01-07 17:59:14 +00001758 mServicesRequested.add(requestedItem);
Matt Pietalab73a882019-06-05 07:04:55 -04001759
Adam Powell24428412015-04-01 17:19:56 -07001760 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
1761 .setComponent(serviceComponent);
1762
1763 if (DEBUG) {
1764 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
1765 }
1766
1767 try {
1768 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
1769 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
1770 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
1771 + " permission " + ChooserTargetService.BIND_PERMISSION
1772 + " - this service will not be queried for ChooserTargets."
1773 + " add android:permission=\""
1774 + ChooserTargetService.BIND_PERMISSION + "\""
1775 + " to the <service> tag for " + serviceComponent
1776 + " in the manifest.");
1777 continue;
1778 }
1779 } catch (NameNotFoundException e) {
Adam Powell52c39212016-04-07 15:14:18 -07001780 Log.e(TAG, "Could not look up service " + serviceComponent
1781 + "; component name not found");
Adam Powell24428412015-04-01 17:19:56 -07001782 continue;
1783 }
1784
Adam Powell9761ab22015-09-08 17:01:49 -07001785 final ChooserTargetServiceConnection conn =
arangelov5fc9e7d2020-01-07 17:59:14 +00001786 new ChooserTargetServiceConnection(this, dri,
1787 adapter.getUserHandle());
Adam Powell52c39212016-04-07 15:14:18 -07001788
arangelov5fc9e7d2020-01-07 17:59:14 +00001789 // Explicitly specify the user handle instead of calling bindService
Adam Powell52c39212016-04-07 15:14:18 -07001790 // to avoid the warning from calling from the system process without an explicit
1791 // user handle
1792 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
arangelov5fc9e7d2020-01-07 17:59:14 +00001793 adapter.getUserHandle())) {
Adam Powell24428412015-04-01 17:19:56 -07001794 if (DEBUG) {
1795 Log.d(TAG, "Binding service connection for target " + dri
1796 + " intent " + serviceIntent);
1797 }
1798 mServiceConnections.add(conn);
1799 targetsToQuery++;
1800 }
1801 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001802 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
Matt Pietal26038402019-01-08 07:29:34 -05001803 if (DEBUG) {
1804 Log.d(TAG, "queryTargets hit query target limit "
1805 + QUERY_TARGET_SERVICE_LIMIT);
1806 }
Adam Powell24428412015-04-01 17:19:56 -07001807 break;
1808 }
1809 }
1810
Matt Pietalab73a882019-06-05 07:04:55 -04001811 mChooserHandler.restartServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -07001812 }
1813
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001814 private IntentFilter getTargetIntentFilter() {
1815 try {
1816 final Intent intent = getTargetIntent();
1817 String dataString = intent.getDataString();
Katsiaryna Naliuka66cd0562020-01-16 18:01:30 +01001818 if (!TextUtils.isEmpty(dataString)) {
1819 return new IntentFilter(intent.getAction(), dataString);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001820 }
Katsiaryna Naliukae2c0b5f2020-02-18 19:26:21 +01001821 if (intent.getType() == null) {
1822 Log.e(TAG, "Failed to get target intent filter: intent data and type are null");
1823 return null;
1824 }
Katsiaryna Naliuka66cd0562020-01-16 18:01:30 +01001825 IntentFilter intentFilter = new IntentFilter(intent.getAction(), intent.getType());
1826 List<Uri> contentUris = new ArrayList<>();
1827 if (Intent.ACTION_SEND.equals(intent.getAction())) {
1828 Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
1829 if (uri != null) {
1830 contentUris.add(uri);
1831 }
1832 } else {
1833 List<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1834 if (uris != null) {
1835 contentUris.addAll(uris);
1836 }
1837 }
1838 for (Uri uri : contentUris) {
1839 intentFilter.addDataScheme(uri.getScheme());
1840 intentFilter.addDataAuthority(uri.getAuthority(), null);
1841 intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
1842 }
1843 return intentFilter;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001844 } catch (Exception e) {
Katsiaryna Naliukae2c0b5f2020-02-18 19:26:21 +01001845 Log.e(TAG, "Failed to get target intent filter", e);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001846 return null;
1847 }
1848 }
1849
George Hodulik69d4a082019-01-18 11:27:03 -08001850 private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001851 // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
1852 // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
George Hodulik69d4a082019-01-18 11:27:03 -08001853 List<DisplayResolveInfo> driList = new ArrayList<>();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001854 int targetsToQuery = 0;
1855 for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
1856 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
1857 if (adapter.getScore(dri) == 0) {
1858 // A score of 0 means the app hasn't been used in some time;
1859 // don't query it as it's not likely to be relevant.
1860 continue;
1861 }
1862 driList.add(dri);
1863 targetsToQuery++;
1864 // TODO(b/121287224): Do we need this here? (similar to queryTargetServices)
1865 if (targetsToQuery >= SHARE_TARGET_QUERY_PACKAGE_LIMIT) {
1866 if (DEBUG) {
1867 Log.d(TAG, "queryTargets hit query target limit "
1868 + SHARE_TARGET_QUERY_PACKAGE_LIMIT);
1869 }
1870 break;
1871 }
1872 }
George Hodulik69d4a082019-01-18 11:27:03 -08001873 return driList;
1874 }
1875
George Hodulik3f399f22019-04-26 16:17:54 -07001876 private void queryDirectShareTargets(
1877 ChooserListAdapter adapter, boolean skipAppPredictionService) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001878 mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
arangelov5fc9e7d2020-01-07 17:59:14 +00001879 UserHandle userHandle = adapter.getUserHandle();
George Hodulik3f399f22019-04-26 16:17:54 -07001880 if (!skipAppPredictionService) {
arangelov5fc9e7d2020-01-07 17:59:14 +00001881 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
George Hodulik3f399f22019-04-26 16:17:54 -07001882 if (appPredictor != null) {
1883 appPredictor.requestPredictionUpdate();
1884 return;
1885 }
George Hodulik69d4a082019-01-18 11:27:03 -08001886 }
George Hodulik145b3a52019-03-27 11:18:43 -07001887 // Default to just querying ShortcutManager if AppPredictor not present.
arangelov38a6fce2019-12-02 18:21:22 +00001888 //TODO(arangelov) we're using mIntents here, investicate possible implications on work tab
George Hodulik69d4a082019-01-18 11:27:03 -08001889 final IntentFilter filter = getTargetIntentFilter();
1890 if (filter == null) {
1891 return;
1892 }
1893 final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001894
1895 AsyncTask.execute(() -> {
arangelov5fc9e7d2020-01-07 17:59:14 +00001896 Context selectedProfileContext = createContextAsUser(userHandle, 0 /* flags */);
1897 ShortcutManager sm = (ShortcutManager) selectedProfileContext
1898 .getSystemService(Context.SHORTCUT_SERVICE);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001899 List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
arangelov5fc9e7d2020-01-07 17:59:14 +00001900 sendShareShortcutInfoList(resultList, driList, null, userHandle);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001901 });
1902 }
1903
George Hodulik69d4a082019-01-18 11:27:03 -08001904 private void sendShareShortcutInfoList(
1905 List<ShortcutManager.ShareShortcutInfo> resultList,
George Hodulikaa5238c2019-04-18 14:17:51 -07001906 List<DisplayResolveInfo> driList,
arangelov5fc9e7d2020-01-07 17:59:14 +00001907 @Nullable List<AppTarget> appTargets, UserHandle userHandle) {
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001908 if (appTargets != null && appTargets.size() != resultList.size()) {
1909 throw new RuntimeException("resultList and appTargets must have the same size."
1910 + " resultList.size()=" + resultList.size()
1911 + " appTargets.size()=" + appTargets.size());
1912 }
1913
1914 for (int i = resultList.size() - 1; i >= 0; i--) {
1915 final String packageName = resultList.get(i).getTargetComponent().getPackageName();
1916 if (!isPackageEnabled(packageName)) {
1917 resultList.remove(i);
1918 if (appTargets != null) {
1919 appTargets.remove(i);
1920 }
1921 }
1922 }
1923
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07001924 // If |appTargets| is not null, results are from AppPredictionService and already sorted.
1925 final int shortcutType = (appTargets == null ? TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER :
1926 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
1927
George Hodulik69d4a082019-01-18 11:27:03 -08001928 // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
1929 // for direct share targets. After ShareSheet is refactored we should use the
1930 // ShareShortcutInfos directly.
1931 boolean resultMessageSent = false;
1932 for (int i = 0; i < driList.size(); i++) {
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001933 List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
George Hodulik69d4a082019-01-18 11:27:03 -08001934 for (int j = 0; j < resultList.size(); j++) {
1935 if (driList.get(i).getResolvedComponentName().equals(
1936 resultList.get(j).getTargetComponent())) {
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001937 matchingShortcuts.add(resultList.get(j));
George Hodulik69d4a082019-01-18 11:27:03 -08001938 }
1939 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001940 if (matchingShortcuts.isEmpty()) {
George Hodulik69d4a082019-01-18 11:27:03 -08001941 continue;
1942 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001943 List<ChooserTarget> chooserTargets = convertToChooserTarget(
1944 matchingShortcuts, resultList, appTargets, shortcutType);
1945
arangelov5fc9e7d2020-01-07 17:59:14 +00001946
1947
George Hodulik69d4a082019-01-18 11:27:03 -08001948 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04001949 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
arangelov5fc9e7d2020-01-07 17:59:14 +00001950 msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null, userHandle);
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07001951 msg.arg1 = shortcutType;
George Hodulik69d4a082019-01-18 11:27:03 -08001952 mChooserHandler.sendMessage(msg);
1953 resultMessageSent = true;
1954 }
1955
1956 if (resultMessageSent) {
George Hodulik145b3a52019-03-27 11:18:43 -07001957 sendShortcutManagerShareTargetResultCompleted();
George Hodulik69d4a082019-01-18 11:27:03 -08001958 }
1959 }
1960
George Hodulik145b3a52019-03-27 11:18:43 -07001961 private void sendShortcutManagerShareTargetResultCompleted() {
1962 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04001963 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
George Hodulik145b3a52019-03-27 11:18:43 -07001964 mChooserHandler.sendMessage(msg);
1965 }
1966
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001967 private boolean isPackageEnabled(String packageName) {
1968 if (TextUtils.isEmpty(packageName)) {
1969 return false;
1970 }
1971 ApplicationInfo appInfo;
1972 try {
1973 appInfo = getPackageManager().getApplicationInfo(packageName, 0);
1974 } catch (NameNotFoundException e) {
1975 return false;
1976 }
1977
1978 if (appInfo != null && appInfo.enabled
1979 && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
1980 return true;
1981 }
1982 return false;
1983 }
1984
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001985 /**
1986 * Converts a list of ShareShortcutInfos to ChooserTargets.
1987 * @param matchingShortcuts List of shortcuts, all from the same package, that match the current
1988 * share intent filter.
1989 * @param allShortcuts List of all the shortcuts from all the packages on the device that are
1990 * returned for the current sharing action.
1991 * @param allAppTargets List of AppTargets. Null if the results are not from prediction service.
1992 * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or
1993 * TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
1994 * @return A list of ChooserTargets sorted by score in descending order.
1995 */
1996 @VisibleForTesting
1997 @NonNull
1998 public List<ChooserTarget> convertToChooserTarget(
1999 @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts,
2000 @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts,
2001 @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) {
2002 // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted
2003 // list instead of the actual rank value when converting a rank to a score.
2004 List<Integer> scoreList = new ArrayList<>();
2005 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
2006 for (int i = 0; i < matchingShortcuts.size(); i++) {
2007 int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank();
2008 if (!scoreList.contains(shortcutRank)) {
2009 scoreList.add(shortcutRank);
2010 }
2011 }
2012 Collections.sort(scoreList);
2013 }
2014
2015 List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size());
2016 for (int i = 0; i < matchingShortcuts.size(); i++) {
2017 ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo();
2018 int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i));
2019
2020 float score;
2021 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
2022 // Incoming results are ordered. Create a score based on index in the original list.
2023 score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f);
2024 } else {
2025 // Create a score based on the rank of the shortcut.
2026 int rankIndex = scoreList.indexOf(shortcutInfo.getRank());
2027 score = Math.max(1.0f - (0.01f * rankIndex), 0.0f);
2028 }
2029
2030 Bundle extras = new Bundle();
2031 extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
2032 ChooserTarget chooserTarget = new ChooserTarget(shortcutInfo.getShortLabel(),
2033 null, // Icon will be loaded later if this target is selected to be shown.
2034 score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);
2035
2036 chooserTargetList.add(chooserTarget);
2037 if (mDirectShareAppTargetCache != null && allAppTargets != null) {
2038 mDirectShareAppTargetCache.put(chooserTarget,
2039 allAppTargets.get(indexInAllShortcuts));
2040 }
arangelov5fc9e7d2020-01-07 17:59:14 +00002041 if (mDirectShareShortcutInfoCache != null) {
2042 mDirectShareShortcutInfoCache.put(chooserTarget, shortcutInfo);
2043 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002044 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002045 // Sort ChooserTargets by score in descending order
2046 Comparator<ChooserTarget> byScore =
2047 (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore());
2048 Collections.sort(chooserTargetList, byScore);
2049 return chooserTargetList;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002050 }
2051
Adam Powell24428412015-04-01 17:19:56 -07002052 private String convertServiceName(String packageName, String serviceName) {
2053 if (TextUtils.isEmpty(serviceName)) {
2054 return null;
2055 }
2056
2057 final String fullName;
2058 if (serviceName.startsWith(".")) {
2059 // Relative to the app package. Prepend the app package name.
2060 fullName = packageName + serviceName;
2061 } else if (serviceName.indexOf('.') >= 0) {
2062 // Fully qualified package name.
2063 fullName = serviceName;
2064 } else {
2065 fullName = null;
2066 }
2067 return fullName;
2068 }
2069
2070 void unbindRemainingServices() {
2071 if (DEBUG) {
2072 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
2073 }
2074 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
2075 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
2076 if (DEBUG) Log.d(TAG, "unbinding " + conn);
2077 unbindService(conn);
Adam Powell9761ab22015-09-08 17:01:49 -07002078 conn.destroy();
Adam Powell24428412015-04-01 17:19:56 -07002079 }
Matt Pietalab73a882019-06-05 07:04:55 -04002080 mServicesRequested.clear();
Adam Powell24428412015-04-01 17:19:56 -07002081 mServiceConnections.clear();
Adam Powell24428412015-04-01 17:19:56 -07002082 }
2083
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07002084 private void logDirectShareTargetReceived(int logCategory) {
2085 final long queryTime =
2086 logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
2087 ? mQueriedSharingShortcutsTimeMs : mQueriedTargetServicesTimeMs;
2088 final int apiLatency = (int) (System.currentTimeMillis() - queryTime);
2089 getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
2090 }
2091
Kang Li9fa2a2c2017-01-06 13:33:24 -08002092 void updateModelAndChooserCounts(TargetInfo info) {
Kang Li53b43142016-11-14 14:38:25 -08002093 if (info != null) {
George Hodulik145b3a52019-03-27 11:18:43 -07002094 sendClickToAppPredictor(info);
Kang Li53b43142016-11-14 14:38:25 -08002095 final ResolveInfo ri = info.getResolveInfo();
Kang Li64b018e2017-01-05 17:30:06 -08002096 Intent targetIntent = getTargetIntent();
2097 if (ri != null && ri.activityInfo != null && targetIntent != null) {
arangelov38a6fce2019-12-02 18:21:22 +00002098 ChooserListAdapter currentListAdapter =
arangelova3912cf2019-12-13 14:34:45 +00002099 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
arangelov38a6fce2019-12-02 18:21:22 +00002100 if (currentListAdapter != null) {
2101 currentListAdapter.updateModel(info.getResolvedComponentName());
2102 currentListAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
Kang Li9fa2a2c2017-01-06 13:33:24 -08002103 targetIntent.getAction());
Kang Li0cef910d2017-01-05 09:14:36 -08002104 }
Kang Li53b43142016-11-14 14:38:25 -08002105 if (DEBUG) {
Kang Li64b018e2017-01-05 17:30:06 -08002106 Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
Kang Li64b018e2017-01-05 17:30:06 -08002107 Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
Kang Li53b43142016-11-14 14:38:25 -08002108 }
George Hodulikf2b0d342019-01-25 12:43:54 -08002109 } else if (DEBUG) {
Kang Li53b43142016-11-14 14:38:25 -08002110 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
2111 }
2112 }
Kang Li9082f5b2016-12-02 10:56:21 -08002113 mIsSuccessfullySelected = true;
Kang Li53b43142016-11-14 14:38:25 -08002114 }
2115
George Hodulikf2b0d342019-01-25 12:43:54 -08002116 private void sendClickToAppPredictor(TargetInfo targetInfo) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002117 AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
2118 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
George Hodulikaa5238c2019-04-18 14:17:51 -07002119 if (directShareAppPredictor == null) {
George Hodulik145b3a52019-03-27 11:18:43 -07002120 return;
2121 }
George Hodulikf2b0d342019-01-25 12:43:54 -08002122 if (!(targetInfo instanceof ChooserTargetInfo)) {
2123 return;
2124 }
2125 ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
George Hodulikaa5238c2019-04-18 14:17:51 -07002126 AppTarget appTarget = null;
2127 if (mDirectShareAppTargetCache != null) {
2128 appTarget = mDirectShareAppTargetCache.get(chooserTarget);
George Hodulikf2b0d342019-01-25 12:43:54 -08002129 }
George Hodulikaa5238c2019-04-18 14:17:51 -07002130 // This is a direct share click that was provided by the APS
2131 if (appTarget != null) {
2132 directShareAppPredictor.notifyAppTargetEvent(
2133 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
2134 .setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
2135 .build());
George Hodulikf2b0d342019-01-25 12:43:54 -08002136 }
George Hodulikf2b0d342019-01-25 12:43:54 -08002137 }
2138
George Hodulik145b3a52019-03-27 11:18:43 -07002139 @Nullable
arangelov5fc9e7d2020-01-07 17:59:14 +00002140 private AppPredictor createAppPredictor(UserHandle userHandle) {
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -07002141 if (!mIsAppPredictorComponentAvailable) {
2142 return null;
2143 }
arangelov5fc9e7d2020-01-07 17:59:14 +00002144
arangelove5b369c2020-03-12 17:36:05 +00002145 if (getPersonalProfileUserHandle().equals(userHandle)) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002146 if (mPersonalAppPredictor != null) {
2147 return mPersonalAppPredictor;
2148 }
2149 } else {
2150 if (mWorkAppPredictor != null) {
2151 return mWorkAppPredictor;
2152 }
George Hodulik145b3a52019-03-27 11:18:43 -07002153 }
arangelov5fc9e7d2020-01-07 17:59:14 +00002154
2155 // TODO(b/148230574): Currently AppPredictor fetches only the same-profile app targets.
2156 // Make AppPredictor work cross-profile.
2157 Context contextAsUser = createContextAsUser(userHandle, 0 /* flags */);
2158 final IntentFilter filter = getTargetIntentFilter();
2159 Bundle extras = new Bundle();
2160 extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
2161 AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(contextAsUser)
2162 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
2163 .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
2164 .setExtras(extras)
2165 .build();
2166 AppPredictionManager appPredictionManager =
2167 contextAsUser
2168 .getSystemService(AppPredictionManager.class);
2169 AppPredictor appPredictionSession = appPredictionManager.createAppPredictionSession(
2170 appPredictionContext);
arangelove5b369c2020-03-12 17:36:05 +00002171 if (getPersonalProfileUserHandle().equals(userHandle)) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002172 mPersonalAppPredictor = appPredictionSession;
2173 } else {
2174 mWorkAppPredictor = appPredictionSession;
2175 }
2176 return appPredictionSession;
George Hodulik145b3a52019-03-27 11:18:43 -07002177 }
2178
2179 /**
2180 * This will return an app predictor if it is enabled for direct share sorting
2181 * and if one exists. Otherwise, it returns null.
arangelov5fc9e7d2020-01-07 17:59:14 +00002182 * @param userHandle
George Hodulik145b3a52019-03-27 11:18:43 -07002183 */
2184 @Nullable
arangelov5fc9e7d2020-01-07 17:59:14 +00002185 private AppPredictor getAppPredictorForDirectShareIfEnabled(UserHandle userHandle) {
arangelovb0802dc2019-10-18 18:03:44 +01002186 return ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS
arangelov5fc9e7d2020-01-07 17:59:14 +00002187 && !ActivityManager.isLowRamDeviceStatic() ? createAppPredictor(userHandle) : null;
George Hodulik145b3a52019-03-27 11:18:43 -07002188 }
2189
George Hodulikc681ce42019-04-12 17:10:31 -07002190 /**
2191 * This will return an app predictor if it is enabled for share activity sorting
2192 * and if one exists. Otherwise, it returns null.
2193 */
2194 @Nullable
arangelov5fc9e7d2020-01-07 17:59:14 +00002195 private AppPredictor getAppPredictorForShareActivitiesIfEnabled(UserHandle userHandle) {
2196 return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? createAppPredictor(userHandle) : null;
George Hodulikc681ce42019-04-12 17:10:31 -07002197 }
2198
Adam Powell2ed547e2015-04-29 18:45:04 -07002199 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
2200 if (mRefinementResultReceiver != null) {
2201 mRefinementResultReceiver.destroy();
2202 mRefinementResultReceiver = null;
2203 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002204 if (selectedTarget == null) {
2205 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
2206 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
2207 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
2208 + " cannot match refined source intent " + matchingIntent);
Kang Li53b43142016-11-14 14:38:25 -08002209 } else {
2210 TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
2211 if (super.onTargetSelected(clonedTarget, false)) {
Kang Li9fa2a2c2017-01-06 13:33:24 -08002212 updateModelAndChooserCounts(clonedTarget);
Kang Li53b43142016-11-14 14:38:25 -08002213 finish();
2214 return;
2215 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002216 }
2217 onRefinementCanceled();
2218 }
2219
2220 void onRefinementCanceled() {
2221 if (mRefinementResultReceiver != null) {
2222 mRefinementResultReceiver.destroy();
2223 mRefinementResultReceiver = null;
2224 }
2225 finish();
2226 }
2227
2228 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
2229 final List<Intent> targetIntents = target.getAllSourceIntents();
2230 for (int i = 0, N = targetIntents.size(); i < N; i++) {
2231 final Intent targetIntent = targetIntents.get(i);
2232 if (targetIntent.filterEquals(matchingIntent)) {
2233 return true;
2234 }
2235 }
2236 return false;
2237 }
2238
arangelov5fc9e7d2020-01-07 17:59:14 +00002239 void filterServiceTargets(Context contextAsUser, String packageName,
2240 List<ChooserTarget> targets) {
Adam Powell666d82a2015-07-15 20:14:57 -07002241 if (targets == null) {
2242 return;
2243 }
2244
arangelov5fc9e7d2020-01-07 17:59:14 +00002245 final PackageManager pm = contextAsUser.getPackageManager();
Adam Powell666d82a2015-07-15 20:14:57 -07002246 for (int i = targets.size() - 1; i >= 0; i--) {
2247 final ChooserTarget target = targets.get(i);
2248 final ComponentName targetName = target.getComponentName();
2249 if (packageName != null && packageName.equals(targetName.getPackageName())) {
2250 // Anything from the original target's package is fine.
2251 continue;
2252 }
2253
2254 boolean remove;
2255 try {
2256 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
2257 remove = !ai.exported || ai.permission != null;
2258 } catch (NameNotFoundException e) {
2259 Log.e(TAG, "Target " + target + " returned by " + packageName
2260 + " component not found");
2261 remove = true;
2262 }
2263
2264 if (remove) {
2265 targets.remove(i);
2266 }
2267 }
2268 }
2269
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002270 /**
2271 * Sort intents alphabetically based on display label.
2272 */
arangelovb0802dc2019-10-18 18:03:44 +01002273 static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002274 Collator mCollator;
2275 AzInfoComparator(Context context) {
2276 mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
2277 }
2278
2279 @Override
arangelovb0802dc2019-10-18 18:03:44 +01002280 public int compare(
2281 DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002282 return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
2283 }
2284 }
2285
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -05002286 protected MetricsLogger getMetricsLogger() {
2287 if (mMetricsLogger == null) {
2288 mMetricsLogger = new MetricsLogger();
2289 }
2290 return mMetricsLogger;
2291 }
2292
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002293 public class ChooserListController extends ResolverListController {
2294 public ChooserListController(Context context,
2295 PackageManager pm,
2296 Intent targetIntent,
2297 String referrerPackageName,
George Hodulikc681ce42019-04-12 17:10:31 -07002298 int launchedFromUid,
arangelov38a6fce2019-12-02 18:21:22 +00002299 UserHandle userId,
George Hodulikc681ce42019-04-12 17:10:31 -07002300 AbstractResolverComparator resolverComparator) {
arangelov38a6fce2019-12-02 18:21:22 +00002301 super(context, pm, targetIntent, referrerPackageName, launchedFromUid, userId,
George Hodulikc681ce42019-04-12 17:10:31 -07002302 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002303 }
2304
2305 @Override
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002306 boolean isComponentFiltered(ComponentName name) {
2307 if (mFilteredComponentNames == null) {
2308 return false;
2309 }
2310 for (ComponentName filteredComponentName : mFilteredComponentNames) {
2311 if (name.equals(filteredComponentName)) {
2312 return true;
2313 }
2314 }
2315 return false;
2316 }
Alison Cichowlas1fd47152019-11-14 19:50:55 -05002317
2318 @Override
2319 public boolean isComponentPinned(ComponentName name) {
2320 return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
2321 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002322 }
2323
arangelov38a6fce2019-12-02 18:21:22 +00002324 @VisibleForTesting
2325 public ChooserGridAdapter createChooserGridAdapter(Context context,
2326 List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
2327 boolean filterLastUsed, boolean useLayoutForBrowsables, UserHandle userHandle) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002328 ChooserListAdapter chooserListAdapter = new ChooserListAdapter(context, payloadIntents,
2329 initialIntents, rList,
2330 filterLastUsed, createListController(userHandle), useLayoutForBrowsables,
2331 this, this);
2332 AppPredictor.Callback appPredictorCallback = createAppPredictorCallback(chooserListAdapter);
2333 AppPredictor appPredictor = setupAppPredictorForUser(userHandle, appPredictorCallback);
2334 chooserListAdapter.setAppPredictor(appPredictor);
2335 chooserListAdapter.setAppPredictorCallback(appPredictorCallback);
2336 return new ChooserGridAdapter(chooserListAdapter);
Adam Powell24428412015-04-01 17:19:56 -07002337 }
2338
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002339 @VisibleForTesting
arangelov38a6fce2019-12-02 18:21:22 +00002340 protected ResolverListController createListController(UserHandle userHandle) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002341 AppPredictor appPredictor = getAppPredictorForShareActivitiesIfEnabled(userHandle);
George Hodulikc681ce42019-04-12 17:10:31 -07002342 AbstractResolverComparator resolverComparator;
2343 if (appPredictor != null) {
2344 resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
arangelov5fc9e7d2020-01-07 17:59:14 +00002345 getReferrerPackageName(), appPredictor, userHandle);
George Hodulikc681ce42019-04-12 17:10:31 -07002346 } else {
2347 resolverComparator =
2348 new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
2349 getReferrerPackageName(), null);
2350 }
2351
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002352 return new ChooserListController(
2353 this,
2354 mPm,
2355 getTargetIntent(),
2356 getReferrerPackageName(),
George Hodulikc681ce42019-04-12 17:10:31 -07002357 mLaunchedFromUid,
arangelov38a6fce2019-12-02 18:21:22 +00002358 userHandle,
George Hodulikc681ce42019-04-12 17:10:31 -07002359 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002360 }
2361
Matt Pietal26038402019-01-08 07:29:34 -05002362 @VisibleForTesting
2363 protected Bitmap loadThumbnail(Uri uri, Size size) {
2364 if (uri == null || size == null) {
2365 return null;
2366 }
2367
2368 try {
Alison Cichowlas07acade2019-12-20 11:26:23 -05002369 return getContentResolver().loadThumbnail(uri, size, null);
Matt Pietal46d828c2019-02-05 08:07:07 -05002370 } catch (IOException | NullPointerException | SecurityException ex) {
Matt Pietal62532e52019-05-07 09:51:37 -04002371 logContentPreviewWarning(uri);
Matt Pietal26038402019-01-08 07:29:34 -05002372 }
2373 return null;
2374 }
2375
arangelovb0802dc2019-10-18 18:03:44 +01002376 static final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
2377 public Drawable getDisplayIcon(Context context) {
Mike Digmanac1d88c2019-04-18 15:15:55 -07002378 AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
arangelovb0802dc2019-10-18 18:03:44 +01002379 context.getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
Mike Digmanac1d88c2019-04-18 15:15:55 -07002380 avd.start(); // Start animation after generation
2381 return avd;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002382 }
2383 }
2384
arangelovb0802dc2019-10-18 18:03:44 +01002385 static final class EmptyTargetInfo extends NotSelectableTargetInfo {
2386 public Drawable getDisplayIcon(Context context) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002387 return null;
2388 }
2389 }
2390
Matt Pietal5b648562019-03-12 07:40:26 -04002391 private void handleScroll(View view, int x, int y, int oldx, int oldy) {
arangelov38a6fce2019-12-02 18:21:22 +00002392 if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
2393 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().handleScroll(view, y, oldy);
Matt Pietal5b648562019-03-12 07:40:26 -04002394 }
2395 }
2396
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002397 /*
2398 * Need to dynamically adjust how many icons can fit per row before we add them,
2399 * which also means setting the correct offset to initially show the content
2400 * preview area + 2 rows of targets
2401 */
2402 private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
2403 int oldTop, int oldRight, int oldBottom) {
arangelov38a6fce2019-12-02 18:21:22 +00002404 if (mChooserMultiProfilePagerAdapter == null) {
2405 return;
2406 }
arangelovcf268642020-01-15 15:09:51 +00002407 RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
arangelov38a6fce2019-12-02 18:21:22 +00002408 ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
2409 if (gridAdapter == null || recyclerView == null) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002410 return;
2411 }
2412
Matt Pietalab73a882019-06-05 07:04:55 -04002413 final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
arangelov19e1fea2020-03-10 18:09:39 +00002414 boolean isLayoutUpdated = gridAdapter.consumeLayoutRequest()
arangelov38a6fce2019-12-02 18:21:22 +00002415 || gridAdapter.calculateChooserTargetWidth(availableWidth)
2416 || recyclerView.getAdapter() == null
arangelov19e1fea2020-03-10 18:09:39 +00002417 || availableWidth != mCurrAvailableWidth;
2418 if (isLayoutUpdated
2419 || mLastNumberOfChildren != recyclerView.getChildCount()) {
Matt Pietalab73a882019-06-05 07:04:55 -04002420 mCurrAvailableWidth = availableWidth;
arangelov19e1fea2020-03-10 18:09:39 +00002421 if (isLayoutUpdated
2422 && mChooserMultiProfilePagerAdapter.getCurrentUserHandle() != getUser()) {
2423 // This fixes b/150936654 - empty work tab in share sheet when swiping
2424 mChooserMultiProfilePagerAdapter.getActiveAdapterView()
2425 .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter());
2426 return;
arangelova30787b2020-03-11 18:33:26 +00002427 } else if (mChooserMultiProfilePagerAdapter.getCurrentUserHandle() != getUser()) {
2428 return;
arangelov19e1fea2020-03-10 18:09:39 +00002429 }
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002430
2431 getMainThreadHandler().post(() -> {
arangelov38a6fce2019-12-02 18:21:22 +00002432 if (mResolverDrawerLayout == null || gridAdapter == null) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002433 return;
2434 }
2435
Matt Pietal800136a2019-05-08 07:46:39 -04002436 final int bottomInset = mSystemWindowInsets != null
2437 ? mSystemWindowInsets.bottom : 0;
2438 int offset = bottomInset;
arangelovc4dbdbd2020-02-18 20:54:16 +00002439 int rowsToShow = gridAdapter.getContentPreviewRowCount()
2440 + gridAdapter.getProfileRowCount()
arangelov38a6fce2019-12-02 18:21:22 +00002441 + gridAdapter.getServiceTargetRowCount()
2442 + gridAdapter.getCallerAndRankedTargetRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002443
2444 // then this is most likely not a SEND_* action, so check
2445 // the app target count
2446 if (rowsToShow == 0) {
arangelov38a6fce2019-12-02 18:21:22 +00002447 rowsToShow = gridAdapter.getRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002448 }
2449
2450 // still zero? then use a default height and leave, which
2451 // can happen when there are no targets to show
arangelov590fba32020-02-11 18:05:42 +00002452 if (rowsToShow == 0 && !shouldShowStickyContentPreview()) {
Matt Pietal800136a2019-05-08 07:46:39 -04002453 offset += getResources().getDimensionPixelSize(
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002454 R.dimen.chooser_max_collapsed_height);
2455 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2456 return;
2457 }
2458
arangelovc4dbdbd2020-02-18 20:54:16 +00002459 View stickyContentPreview = findViewById(R.id.content_preview_container);
2460 if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) {
2461 offset += stickyContentPreview.getHeight();
arangelova3912cf2019-12-13 14:34:45 +00002462 }
2463
arangelovf163a882020-02-18 17:18:47 +00002464 if (shouldShowTabs()) {
arangelova69e6762020-02-05 21:25:15 +00002465 offset += findViewById(R.id.tabs).getHeight();
2466 }
2467
arangelov2a4d0b12020-03-04 18:15:04 +00002468 View tabDivider = findViewById(R.id.resolver_tab_divider);
2469 if (tabDivider.getVisibility() == View.VISIBLE) {
2470 offset += tabDivider.getHeight();
2471 }
2472
arangelova30787b2020-03-11 18:33:26 +00002473 if (recyclerView.getVisibility() == View.VISIBLE) {
2474 int directShareHeight = 0;
2475 rowsToShow = Math.min(4, rowsToShow);
2476 mLastNumberOfChildren = recyclerView.getChildCount();
2477 for (int i = 0, childCount = recyclerView.getChildCount();
2478 i < childCount && rowsToShow > 0; i++) {
2479 View child = recyclerView.getChildAt(i);
2480 if (((GridLayoutManager.LayoutParams)
2481 child.getLayoutParams()).getSpanIndex() != 0) {
2482 continue;
2483 }
2484 int height = child.getHeight();
2485 offset += height;
2486
2487 if (gridAdapter.getTargetType(
2488 recyclerView.getChildAdapterPosition(child))
2489 == ChooserListAdapter.TARGET_SERVICE) {
2490 directShareHeight = height;
2491 }
2492 rowsToShow--;
Zhen Zhangbde7b462019-11-11 11:49:33 -08002493 }
Matt Pietal394ebd02019-05-03 07:36:21 -04002494
arangelova30787b2020-03-11 18:33:26 +00002495 boolean isExpandable = getResources().getConfiguration().orientation
2496 == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
2497 if (directShareHeight != 0 && isSendAction(getTargetIntent())
2498 && isExpandable) {
2499 // make sure to leave room for direct share 4->8 expansion
2500 int requiredExpansionHeight =
2501 (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
2502 int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
2503 int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
2504 - requiredExpansionHeight - topInset - bottomInset;
2505
2506 offset = Math.min(offset, minHeight);
Matt Pietal394ebd02019-05-03 07:36:21 -04002507 }
arangelova30787b2020-03-11 18:33:26 +00002508 } else {
2509 ViewGroup currentEmptyStateView = getCurrentEmptyStateView();
2510 if (currentEmptyStateView.getVisibility() == View.VISIBLE) {
2511 offset += currentEmptyStateView.getHeight();
2512 }
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002513 }
2514
Matt Pietal399e8c72019-04-04 15:49:48 -04002515 mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top));
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002516 });
2517 }
2518 }
2519
arangelova30787b2020-03-11 18:33:26 +00002520 private ViewGroup getCurrentEmptyStateView() {
2521 int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage();
2522 return mChooserMultiProfilePagerAdapter.getItem(currentPage).getEmptyStateView();
2523 }
2524
Adam Powella182e452015-07-06 16:57:56 -07002525 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
2526 @Override
2527 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
2528 // Descending order
Adam Powell77a533f2015-10-16 10:47:32 -07002529 return (int) Math.signum(rhs.getScore() - lhs.getScore());
Adam Powella182e452015-07-06 16:57:56 -07002530 }
2531 }
2532
arangelovb0802dc2019-10-18 18:03:44 +01002533 @Override // ResolverListCommunicator
arangelov7981b122020-01-16 10:58:27 +00002534 public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
arangelovb0802dc2019-10-18 18:03:44 +01002535 mServicesRequested.clear();
arangelova3912cf2019-12-13 14:34:45 +00002536 mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged();
arangelov7981b122020-01-16 10:58:27 +00002537 super.onHandlePackagesChanged(listAdapter);
arangelovb0802dc2019-10-18 18:03:44 +01002538 }
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002539
arangelovb0802dc2019-10-18 18:03:44 +01002540 @Override // SelectableTargetInfoCommunicator
2541 public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) {
arangelova3912cf2019-12-13 14:34:45 +00002542 return mChooserMultiProfilePagerAdapter.getActiveListAdapter().makePresentationGetter(info);
arangelovb0802dc2019-10-18 18:03:44 +01002543 }
2544
2545 @Override // SelectableTargetInfoCommunicator
2546 public Intent getReferrerFillInIntent() {
2547 return mReferrerFillInIntent;
2548 }
2549
2550 @Override // ChooserListCommunicator
2551 public int getMaxRankedTargets() {
arangelov38a6fce2019-12-02 18:21:22 +00002552 return mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() == null
Zhen Zhangbde7b462019-11-11 11:49:33 -08002553 ? ChooserGridAdapter.MAX_TARGETS_PER_ROW_PORTRAIT
arangelov38a6fce2019-12-02 18:21:22 +00002554 : mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().getMaxTargetsPerRow();
arangelovb0802dc2019-10-18 18:03:44 +01002555 }
2556
2557 @Override // ChooserListCommunicator
arangelov5fc9e7d2020-01-07 17:59:14 +00002558 public void sendListViewUpdateMessage(UserHandle userHandle) {
2559 Message msg = Message.obtain();
2560 msg.what = ChooserHandler.LIST_VIEW_UPDATE_MESSAGE;
2561 msg.obj = userHandle;
2562 mChooserHandler.sendMessageDelayed(msg, LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
arangelovb0802dc2019-10-18 18:03:44 +01002563 }
2564
2565 @Override
arangelova3912cf2019-12-13 14:34:45 +00002566 public void onListRebuilt(ResolverListAdapter listAdapter) {
arangelovcf268642020-01-15 15:09:51 +00002567 setupScrollListener();
2568
arangelova3912cf2019-12-13 14:34:45 +00002569 ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
arangelovc4dbdbd2020-02-18 20:54:16 +00002570 if (chooserListAdapter.getUserHandle()
arangelove5b369c2020-03-12 17:36:05 +00002571 .equals(mChooserMultiProfilePagerAdapter.getCurrentUserHandle())) {
arangelovc4dbdbd2020-02-18 20:54:16 +00002572 mChooserMultiProfilePagerAdapter.getActiveAdapterView()
2573 .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter());
2574 mChooserMultiProfilePagerAdapter
2575 .setupListAdapter(mChooserMultiProfilePagerAdapter.getCurrentPage());
2576 }
2577
arangelova3912cf2019-12-13 14:34:45 +00002578 if (chooserListAdapter.mDisplayList == null
2579 || chooserListAdapter.mDisplayList.isEmpty()) {
2580 chooserListAdapter.notifyDataSetChanged();
arangelovb0802dc2019-10-18 18:03:44 +01002581 } else {
2582 new AsyncTask<Void, Void, Void>() {
2583 @Override
2584 protected Void doInBackground(Void... voids) {
arangelova3912cf2019-12-13 14:34:45 +00002585 chooserListAdapter.updateAlphabeticalList();
arangelovb0802dc2019-10-18 18:03:44 +01002586 return null;
2587 }
2588 @Override
2589 protected void onPostExecute(Void aVoid) {
arangelova3912cf2019-12-13 14:34:45 +00002590 chooserListAdapter.notifyDataSetChanged();
arangelovb0802dc2019-10-18 18:03:44 +01002591 }
2592 }.execute();
2593 }
2594
2595 // don't support direct share on low ram devices
2596 if (ActivityManager.isLowRamDeviceStatic()) {
2597 return;
2598 }
2599
2600 if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
2601 || ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
2602 if (DEBUG) {
2603 Log.d(TAG, "querying direct share targets from ShortcutManager");
2604 }
2605
arangelova3912cf2019-12-13 14:34:45 +00002606 queryDirectShareTargets(chooserListAdapter, false);
arangelovb0802dc2019-10-18 18:03:44 +01002607 }
2608 if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
2609 if (DEBUG) {
2610 Log.d(TAG, "List built querying services");
2611 }
2612
arangelova3912cf2019-12-13 14:34:45 +00002613 queryTargetServices(chooserListAdapter);
arangelovb0802dc2019-10-18 18:03:44 +01002614 }
2615 }
2616
arangelovcf268642020-01-15 15:09:51 +00002617 private void setupScrollListener() {
arangelovf163a882020-02-18 17:18:47 +00002618 if (mResolverDrawerLayout == null || shouldShowTabs()) {
arangelovcf268642020-01-15 15:09:51 +00002619 return;
2620 }
2621 final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
2622 final float defaultElevation = chooserHeader.getElevation();
2623 final float chooserHeaderScrollElevation =
2624 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
2625
2626 mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener(
2627 new RecyclerView.OnScrollListener() {
2628 public void onScrollStateChanged(RecyclerView view, int scrollState) {
2629 }
2630
2631 public void onScrolled(RecyclerView view, int dx, int dy) {
2632 if (view.getChildCount() > 0) {
2633 View child = view.getLayoutManager().findViewByPosition(0);
2634 if (child == null || child.getTop() < 0) {
2635 chooserHeader.setElevation(chooserHeaderScrollElevation);
2636 return;
2637 }
2638 }
2639
2640 chooserHeader.setElevation(defaultElevation);
2641 }
2642 });
2643 }
2644
arangelovb0802dc2019-10-18 18:03:44 +01002645 @Override // ChooserListCommunicator
2646 public boolean isSendAction(Intent targetIntent) {
Matt Pietal95574b02019-03-13 08:12:25 -04002647 if (targetIntent == null) {
2648 return false;
2649 }
2650
2651 String action = targetIntent.getAction();
2652 if (action == null) {
2653 return false;
2654 }
2655
2656 if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
2657 return true;
2658 }
2659
2660 return false;
2661 }
2662
arangelov590fba32020-02-11 18:05:42 +00002663 /**
2664 * The sticky content preview is shown only when we have a tabbed view. It's shown above
2665 * the tabs so it is not part of the scrollable list. If we are not in tabbed view,
2666 * we instead show the content preview as a regular list item.
2667 */
2668 private boolean shouldShowStickyContentPreview() {
arangelovc4dbdbd2020-02-18 20:54:16 +00002669 return shouldShowStickyContentPreviewNoOrientationCheck()
arangelov590fba32020-02-11 18:05:42 +00002670 && getResources().getBoolean(R.bool.sharesheet_show_content_preview);
arangelova3912cf2019-12-13 14:34:45 +00002671 }
2672
arangelovc4dbdbd2020-02-18 20:54:16 +00002673 private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
2674 return shouldShowTabs()
2675 && mMultiProfilePagerAdapter.getListAdapterForUserHandle(
2676 UserHandle.of(UserHandle.myUserId())).getCount() > 0
2677 && isSendAction(getTargetIntent());
2678 }
2679
arangelov590fba32020-02-11 18:05:42 +00002680 private void updateStickyContentPreview() {
arangelovc4dbdbd2020-02-18 20:54:16 +00002681 if (shouldShowStickyContentPreviewNoOrientationCheck()) {
2682 // The sticky content preview is only shown when we show the work and personal tabs.
2683 // We don't show it in landscape as otherwise there is no room for scrolling.
2684 // If the sticky content preview will be shown at some point with orientation change,
2685 // then always preload it to avoid subsequent resizing of the share sheet.
2686 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
2687 if (contentPreviewContainer.getChildCount() == 0) {
2688 ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer);
2689 contentPreviewContainer.addView(contentPreviewView);
2690 }
2691 }
arangelov590fba32020-02-11 18:05:42 +00002692 if (shouldShowStickyContentPreview()) {
2693 showStickyContentPreview();
arangelova3912cf2019-12-13 14:34:45 +00002694 } else {
arangelov590fba32020-02-11 18:05:42 +00002695 hideStickyContentPreview();
arangelova3912cf2019-12-13 14:34:45 +00002696 }
2697 }
2698
arangelov590fba32020-02-11 18:05:42 +00002699 private void showStickyContentPreview() {
arangelovc4dbdbd2020-02-18 20:54:16 +00002700 if (isStickyContentPreviewShowing()) {
2701 return;
2702 }
arangelova3912cf2019-12-13 14:34:45 +00002703 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
2704 contentPreviewContainer.setVisibility(View.VISIBLE);
arangelovc4dbdbd2020-02-18 20:54:16 +00002705 }
2706
2707 private boolean isStickyContentPreviewShowing() {
2708 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
2709 return contentPreviewContainer.getVisibility() == View.VISIBLE;
arangelova3912cf2019-12-13 14:34:45 +00002710 }
2711
arangelov590fba32020-02-11 18:05:42 +00002712 private void hideStickyContentPreview() {
arangelovc4dbdbd2020-02-18 20:54:16 +00002713 if (!isStickyContentPreviewShowing()) {
2714 return;
2715 }
arangelova3912cf2019-12-13 14:34:45 +00002716 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
arangelova3912cf2019-12-13 14:34:45 +00002717 contentPreviewContainer.setVisibility(View.GONE);
2718 }
2719
2720 private void logActionShareWithPreview() {
2721 Intent targetIntent = getTargetIntent();
2722 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
2723 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
2724 .setSubtype(previewType));
2725 }
2726
Zhen Zhangbde7b462019-11-11 11:49:33 -08002727 /**
2728 * Used to bind types of individual item including
2729 * {@link ChooserGridAdapter#VIEW_TYPE_NORMAL},
arangelov590fba32020-02-11 18:05:42 +00002730 * {@link ChooserGridAdapter#VIEW_TYPE_CONTENT_PREVIEW},
Zhen Zhangbde7b462019-11-11 11:49:33 -08002731 * {@link ChooserGridAdapter#VIEW_TYPE_PROFILE},
2732 * and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}.
2733 */
2734 final class ItemViewHolder extends RecyclerView.ViewHolder {
2735 ResolverListAdapter.ViewHolder mWrappedViewHolder;
2736 int mListPosition = ChooserListAdapter.NO_POSITION;
2737
2738 ItemViewHolder(View itemView, boolean isClickable) {
2739 super(itemView);
2740 mWrappedViewHolder = new ResolverListAdapter.ViewHolder(itemView);
2741 if (isClickable) {
2742 itemView.setOnClickListener(v -> startSelected(mListPosition,
2743 false/* always */, true/* filterd */));
2744 itemView.setOnLongClickListener(v -> {
2745 showTargetDetails(
arangelova3912cf2019-12-13 14:34:45 +00002746 mChooserMultiProfilePagerAdapter.getActiveListAdapter()
Alison Cichowlas19ee2922019-12-16 19:43:12 -05002747 .targetInfoForPosition(mListPosition, /* filtered */ true));
Zhen Zhangbde7b462019-11-11 11:49:33 -08002748 return true;
2749 });
2750 }
2751 }
2752 }
2753
2754 /**
Matt Pietal9236adc2019-12-12 09:24:23 -05002755 * Add a footer to the list, to support scrolling behavior below the navbar.
arangelov38a6fce2019-12-02 18:21:22 +00002756 */
Matt Pietal9236adc2019-12-12 09:24:23 -05002757 final class FooterViewHolder extends RecyclerView.ViewHolder {
2758 FooterViewHolder(View itemView) {
2759 super(itemView);
2760 }
arangelov38a6fce2019-12-02 18:21:22 +00002761 }
2762
2763 /**
2764 * Intentionally override the {@link ResolverActivity} implementation as we only need that
2765 * implementation for the intent resolver case.
2766 */
2767 @Override
2768 public void onButtonClick(View v) {}
2769
2770 /**
2771 * Intentionally override the {@link ResolverActivity} implementation as we only need that
2772 * implementation for the intent resolver case.
2773 */
2774 @Override
2775 protected void resetButtonBar() {}
2776
arangelov4bf17472020-02-17 20:21:46 +00002777 @Override
2778 protected String getMetricsCategory() {
2779 return METRICS_CATEGORY_CHOOSER;
2780 }
2781
arangelov38a6fce2019-12-02 18:21:22 +00002782 /**
Zhen Zhangbde7b462019-11-11 11:49:33 -08002783 * Adapter for all types of items and targets in ShareSheet.
2784 * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the
2785 * row level by this adapter but not on the item level. Individual targets within the row are
2786 * handled by {@link ChooserListAdapter}
2787 */
arangelovcf268642020-01-15 15:09:51 +00002788 @VisibleForTesting
2789 public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
Adam Powell7d758002015-05-06 17:49:36 -07002790 private ChooserListAdapter mChooserListAdapter;
2791 private final LayoutInflater mLayoutInflater;
Adam Powell7d758002015-05-06 17:49:36 -07002792
Matt Pietal5b648562019-03-12 07:40:26 -04002793 private DirectShareViewHolder mDirectShareViewHolder;
Matt Pietalab986b52019-04-10 10:14:32 -04002794 private int mChooserTargetWidth = 0;
Mike Digman849a9d12019-04-29 11:20:48 -07002795 private boolean mShowAzLabelIfPoss;
Matt Pietal5b648562019-03-12 07:40:26 -04002796
arangelov590fba32020-02-11 18:05:42 +00002797 private boolean mHideContentPreview = false;
Matt Pietale7cacab2019-05-23 07:21:36 -04002798 private boolean mLayoutRequested = false;
2799
Matt Pietal2025a1b2020-01-07 10:08:07 -05002800 private int mFooterHeight = 0;
Matt Pietal9236adc2019-12-12 09:24:23 -05002801
Matt Pietal5b648562019-03-12 07:40:26 -04002802 private static final int VIEW_TYPE_DIRECT_SHARE = 0;
2803 private static final int VIEW_TYPE_NORMAL = 1;
arangelov590fba32020-02-11 18:05:42 +00002804 private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
2805 private static final int VIEW_TYPE_PROFILE = 3;
2806 private static final int VIEW_TYPE_AZ_LABEL = 4;
2807 private static final int VIEW_TYPE_CALLER_AND_RANK = 5;
2808 private static final int VIEW_TYPE_FOOTER = 6;
Matt Pietal5b648562019-03-12 07:40:26 -04002809
Matt Pietalfaedea82019-03-21 10:36:54 -04002810 private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
2811 private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
2812
Mike Digman849a9d12019-04-29 11:20:48 -07002813 private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
2814
Zhen Zhangbde7b462019-11-11 11:49:33 -08002815 ChooserGridAdapter(ChooserListAdapter wrappedAdapter) {
2816 super();
Adam Powell7d758002015-05-06 17:49:36 -07002817 mChooserListAdapter = wrappedAdapter;
2818 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
2819
Mike Digman849a9d12019-04-29 11:20:48 -07002820 mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
2821
Adam Powell7d758002015-05-06 17:49:36 -07002822 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
2823 @Override
2824 public void onChanged() {
2825 super.onChanged();
2826 notifyDataSetChanged();
2827 }
2828
2829 @Override
2830 public void onInvalidated() {
2831 super.onInvalidated();
Zhen Zhangbde7b462019-11-11 11:49:33 -08002832 notifyDataSetChanged();
Adam Powell7d758002015-05-06 17:49:36 -07002833 }
2834 });
2835 }
2836
Matt Pietal9236adc2019-12-12 09:24:23 -05002837 public void setFooterHeight(int height) {
Matt Pietal2025a1b2020-01-07 10:08:07 -05002838 mFooterHeight = height;
Matt Pietal9236adc2019-12-12 09:24:23 -05002839 }
2840
Matt Pietalfaedea82019-03-21 10:36:54 -04002841 /**
Matt Pietalab986b52019-04-10 10:14:32 -04002842 * Calculate the chooser target width to maximize space per item
Matt Pietalfaedea82019-03-21 10:36:54 -04002843 *
2844 * @param width The new row width to use for recalculation
Matt Pietalab986b52019-04-10 10:14:32 -04002845 * @return true if the view width has changed
Matt Pietalfaedea82019-03-21 10:36:54 -04002846 */
Matt Pietalab986b52019-04-10 10:14:32 -04002847 public boolean calculateChooserTargetWidth(int width) {
Matt Pietalab986b52019-04-10 10:14:32 -04002848 if (width == 0) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002849 return false;
2850 }
2851
Zhen Zhangbde7b462019-11-11 11:49:33 -08002852 int newWidth = width / getMaxTargetsPerRow();
Matt Pietalab986b52019-04-10 10:14:32 -04002853 if (newWidth != mChooserTargetWidth) {
2854 mChooserTargetWidth = newWidth;
Matt Pietalfaedea82019-03-21 10:36:54 -04002855 return true;
2856 }
2857
2858 return false;
2859 }
2860
arangelov38a6fce2019-12-02 18:21:22 +00002861 int getMaxTargetsPerRow() {
Matt Pietalfaedea82019-03-21 10:36:54 -04002862 int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002863 if (shouldDisplayLandscape(getResources().getConfiguration().orientation)) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002864 maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
2865 }
Matt Pietalab986b52019-04-10 10:14:32 -04002866 return maxTargets;
Matt Pietal5b648562019-03-12 07:40:26 -04002867 }
2868
arangelov590fba32020-02-11 18:05:42 +00002869 /**
2870 * Hides the list item content preview.
2871 * <p>Not to be confused with the sticky content preview which is above the
2872 * personal and work tabs.
2873 */
2874 public void hideContentPreview() {
2875 mHideContentPreview = true;
2876 mLayoutRequested = true;
2877 notifyDataSetChanged();
2878 }
2879
Matt Pietale7cacab2019-05-23 07:21:36 -04002880 public boolean consumeLayoutRequest() {
2881 boolean oldValue = mLayoutRequested;
2882 mLayoutRequested = false;
2883 return oldValue;
2884 }
2885
Zhen Zhangbde7b462019-11-11 11:49:33 -08002886 public int getRowCount() {
Adam Powell7d758002015-05-06 17:49:36 -07002887 return (int) (
arangelov590fba32020-02-11 18:05:42 +00002888 getContentPreviewRowCount()
2889 + getProfileRowCount()
Matt Pietal26038402019-01-08 07:29:34 -05002890 + getServiceTargetRowCount()
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002891 + getCallerAndRankedTargetRowCount()
Mike Digmanae730b12019-04-25 11:10:31 -07002892 + getAzLabelRowCount()
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002893 + Math.ceil(
2894 (float) mChooserListAdapter.getAlphaTargetCount()
2895 / getMaxTargetsPerRow())
Adam Powell7d758002015-05-06 17:49:36 -07002896 );
2897 }
2898
arangelov590fba32020-02-11 18:05:42 +00002899 /**
2900 * Returns either {@code 0} or {@code 1} depending on whether we want to show the list item
2901 * content preview. Not to be confused with the sticky content preview which is above the
2902 * personal and work tabs.
2903 */
2904 public int getContentPreviewRowCount() {
2905 // For the tabbed case we show the sticky content preview above the tabs,
2906 // please refer to shouldShowStickyContentPreview
arangelovf163a882020-02-18 17:18:47 +00002907 if (shouldShowTabs()) {
arangelov590fba32020-02-11 18:05:42 +00002908 return 0;
2909 }
2910 if (!isSendAction(getTargetIntent())) {
2911 return 0;
2912 }
2913
2914 if (mHideContentPreview || mChooserListAdapter == null
2915 || mChooserListAdapter.getCount() == 0) {
2916 return 0;
2917 }
2918
2919 return 1;
2920 }
2921
Matt Pietal74c6ed02019-04-18 13:38:46 -04002922 public int getProfileRowCount() {
arangelovf163a882020-02-18 17:18:47 +00002923 if (shouldShowTabs()) {
arangelov590fba32020-02-11 18:05:42 +00002924 return 0;
2925 }
Matt Pietal74c6ed02019-04-18 13:38:46 -04002926 return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
2927 }
2928
Matt Pietal9236adc2019-12-12 09:24:23 -05002929 public int getFooterRowCount() {
2930 return 1;
2931 }
2932
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002933 public int getCallerAndRankedTargetRowCount() {
Adam Powell63b31692015-09-28 10:45:00 -07002934 return (int) Math.ceil(
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002935 ((float) mChooserListAdapter.getCallerTargetCount()
2936 + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow());
Adam Powell63b31692015-09-28 10:45:00 -07002937 }
2938
Matt Pietal5b648562019-03-12 07:40:26 -04002939 // There can be at most one row in the listview, that is internally
2940 // a ViewGroup with 2 rows
Adam Powell63b31692015-09-28 10:45:00 -07002941 public int getServiceTargetRowCount() {
arangelovb0802dc2019-10-18 18:03:44 +01002942 if (isSendAction(getTargetIntent())
2943 && !ActivityManager.isLowRamDeviceStatic()) {
Matt Pietal95574b02019-03-13 08:12:25 -04002944 return 1;
2945 }
2946 return 0;
Adam Powell63b31692015-09-28 10:45:00 -07002947 }
2948
Mike Digmanae730b12019-04-25 11:10:31 -07002949 public int getAzLabelRowCount() {
2950 // Only show a label if the a-z list is showing
Mike Digman849a9d12019-04-29 11:20:48 -07002951 return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
Mike Digmanae730b12019-04-25 11:10:31 -07002952 }
2953
Adam Powell7d758002015-05-06 17:49:36 -07002954 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08002955 public int getItemCount() {
2956 return (int) (
arangelov590fba32020-02-11 18:05:42 +00002957 getContentPreviewRowCount()
2958 + getProfileRowCount()
Zhen Zhangbde7b462019-11-11 11:49:33 -08002959 + getServiceTargetRowCount()
2960 + getCallerAndRankedTargetRowCount()
2961 + getAzLabelRowCount()
2962 + mChooserListAdapter.getAlphaTargetCount()
Matt Pietal9236adc2019-12-12 09:24:23 -05002963 + getFooterRowCount()
Zhen Zhangbde7b462019-11-11 11:49:33 -08002964 );
Adam Powell7d758002015-05-06 17:49:36 -07002965 }
2966
2967 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08002968 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
2969 switch (viewType) {
arangelov590fba32020-02-11 18:05:42 +00002970 case VIEW_TYPE_CONTENT_PREVIEW:
2971 return new ItemViewHolder(createContentPreviewView(parent), false);
Zhen Zhangbde7b462019-11-11 11:49:33 -08002972 case VIEW_TYPE_PROFILE:
2973 return new ItemViewHolder(createProfileView(parent), false);
2974 case VIEW_TYPE_AZ_LABEL:
2975 return new ItemViewHolder(createAzLabelView(parent), false);
2976 case VIEW_TYPE_NORMAL:
2977 return new ItemViewHolder(mChooserListAdapter.createView(parent), true);
2978 case VIEW_TYPE_DIRECT_SHARE:
2979 case VIEW_TYPE_CALLER_AND_RANK:
2980 return createItemGroupViewHolder(viewType, parent);
Matt Pietal9236adc2019-12-12 09:24:23 -05002981 case VIEW_TYPE_FOOTER:
Matt Pietal2025a1b2020-01-07 10:08:07 -05002982 Space sp = new Space(parent.getContext());
2983 sp.setLayoutParams(new RecyclerView.LayoutParams(
2984 LayoutParams.MATCH_PARENT, mFooterHeight));
2985 return new FooterViewHolder(sp);
Zhen Zhangbde7b462019-11-11 11:49:33 -08002986 default:
2987 // Since we catch all possible viewTypes above, no chance this is being called.
2988 return null;
2989 }
Adam Powell7d758002015-05-06 17:49:36 -07002990 }
2991
2992 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08002993 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Matt Pietal5b648562019-03-12 07:40:26 -04002994 int viewType = getItemViewType(position);
Zhen Zhangbde7b462019-11-11 11:49:33 -08002995 switch (viewType) {
2996 case VIEW_TYPE_DIRECT_SHARE:
2997 case VIEW_TYPE_CALLER_AND_RANK:
2998 bindItemGroupViewHolder(position, (ItemGroupViewHolder) holder);
2999 break;
3000 case VIEW_TYPE_NORMAL:
3001 bindItemViewHolder(position, (ItemViewHolder) holder);
3002 break;
3003 default:
Matt Pietal1ef88002019-03-13 10:43:18 -04003004 }
Adam Powell7d758002015-05-06 17:49:36 -07003005 }
3006
Matt Pietal5b648562019-03-12 07:40:26 -04003007 @Override
3008 public int getItemViewType(int position) {
Mike Digmanae730b12019-04-25 11:10:31 -07003009 int count;
Matt Pietal1ef88002019-03-13 10:43:18 -04003010
arangelov590fba32020-02-11 18:05:42 +00003011 int countSum = (count = getContentPreviewRowCount());
3012 if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
3013
3014 countSum += (count = getProfileRowCount());
Mike Digmanae730b12019-04-25 11:10:31 -07003015 if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
Adam Powell63b31692015-09-28 10:45:00 -07003016
Mike Digmanae730b12019-04-25 11:10:31 -07003017 countSum += (count = getServiceTargetRowCount());
3018 if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
3019
3020 countSum += (count = getCallerAndRankedTargetRowCount());
Zhen Zhangbde7b462019-11-11 11:49:33 -08003021 if (count > 0 && position < countSum) return VIEW_TYPE_CALLER_AND_RANK;
Mike Digmanae730b12019-04-25 11:10:31 -07003022
3023 countSum += (count = getAzLabelRowCount());
3024 if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
Matt Pietal5b648562019-03-12 07:40:26 -04003025
Matt Pietal9236adc2019-12-12 09:24:23 -05003026 if (position == getItemCount() - 1) return VIEW_TYPE_FOOTER;
3027
Matt Pietal5b648562019-03-12 07:40:26 -04003028 return VIEW_TYPE_NORMAL;
3029 }
3030
Zhen Zhangbde7b462019-11-11 11:49:33 -08003031 public int getTargetType(int position) {
3032 return mChooserListAdapter.getPositionTargetType(getListPosition(position));
Matt Pietal1ef88002019-03-13 10:43:18 -04003033 }
3034
Zhen Zhangbde7b462019-11-11 11:49:33 -08003035 private View createProfileView(ViewGroup parent) {
3036 View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false);
Matt Pietal74c6ed02019-04-18 13:38:46 -04003037 profileRow.setBackground(
3038 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
3039 mProfileView = profileRow.findViewById(R.id.profile_button);
3040 mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
arangelovb0802dc2019-10-18 18:03:44 +01003041 updateProfileViewButton();
Matt Pietal74c6ed02019-04-18 13:38:46 -04003042 return profileRow;
3043 }
3044
Mike Digmanae730b12019-04-25 11:10:31 -07003045 private View createAzLabelView(ViewGroup parent) {
3046 return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
3047 }
3048
Zhen Zhangbde7b462019-11-11 11:49:33 -08003049 private ItemGroupViewHolder loadViewsIntoGroup(ItemGroupViewHolder holder) {
Matt Pietal5b648562019-03-12 07:40:26 -04003050 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietalab986b52019-04-10 10:14:32 -04003051 final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
3052 MeasureSpec.EXACTLY);
Matt Pietal5b648562019-03-12 07:40:26 -04003053 int columnCount = holder.getColumnCount();
3054
Mike Digmanba232682019-03-27 14:55:26 -07003055 final boolean isDirectShare = holder instanceof DirectShareViewHolder;
3056
Matt Pietal5b648562019-03-12 07:40:26 -04003057 for (int i = 0; i < columnCount; i++) {
Mike Digmanba232682019-03-27 14:55:26 -07003058 final View v = mChooserListAdapter.createView(holder.getRowByIndex(i));
Adam Powell4eb98712015-10-14 13:10:18 -07003059 final int column = i;
Adam Powell63b31692015-09-28 10:45:00 -07003060 v.setOnClickListener(new OnClickListener() {
3061 @Override
3062 public void onClick(View v) {
Matt Pietal5b648562019-03-12 07:40:26 -04003063 startSelected(holder.getItemIndex(column), false, true);
Adam Powell63b31692015-09-28 10:45:00 -07003064 }
3065 });
3066 v.setOnLongClickListener(new OnLongClickListener() {
3067 @Override
3068 public boolean onLongClick(View v) {
Adam Powell23882512016-01-29 10:21:00 -08003069 showTargetDetails(
Alison Cichowlas19ee2922019-12-16 19:43:12 -05003070 mChooserListAdapter.targetInfoForPosition(
Matt Pietal5b648562019-03-12 07:40:26 -04003071 holder.getItemIndex(column), true));
Adam Powell63b31692015-09-28 10:45:00 -07003072 return true;
3073 }
3074 });
Zhen Zhangbde7b462019-11-11 11:49:33 -08003075 holder.addView(i, v);
Adam Powell63b31692015-09-28 10:45:00 -07003076
Mike Digmanba232682019-03-27 14:55:26 -07003077 // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
3078 // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be
3079 // done before measuring.
3080 if (isDirectShare) {
3081 final ViewHolder vh = (ViewHolder) v.getTag();
3082 vh.text.setLines(2);
3083 vh.text.setHorizontallyScrolling(false);
3084 vh.text2.setVisibility(View.GONE);
Adam Powell63b31692015-09-28 10:45:00 -07003085 }
Mike Digmanba232682019-03-27 14:55:26 -07003086
3087 // Force height to be a given so we don't have visual disruption during scaling.
Matt Pietalab986b52019-04-10 10:14:32 -04003088 v.measure(exactSpec, spec);
3089 setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight());
Adam Powell63b31692015-09-28 10:45:00 -07003090 }
3091
Matt Pietal5b648562019-03-12 07:40:26 -04003092 final ViewGroup viewGroup = holder.getViewGroup();
3093
Mike Digmanba232682019-03-27 14:55:26 -07003094 // Pre-measure and fix height so we can scale later.
Adam Powell63b31692015-09-28 10:45:00 -07003095 holder.measure();
Matt Pietalab986b52019-04-10 10:14:32 -04003096 setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight());
Mike Digmanba232682019-03-27 14:55:26 -07003097
3098 if (isDirectShare) {
3099 DirectShareViewHolder dsvh = (DirectShareViewHolder) holder;
Matt Pietalab986b52019-04-10 10:14:32 -04003100 setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
3101 setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
Adam Powell7d758002015-05-06 17:49:36 -07003102 }
Matt Pietal5b648562019-03-12 07:40:26 -04003103
3104 viewGroup.setTag(holder);
3105
Adam Powell7d758002015-05-06 17:49:36 -07003106 return holder;
3107 }
3108
Matt Pietalab986b52019-04-10 10:14:32 -04003109 private void setViewBounds(View view, int widthPx, int heightPx) {
Mike Digmanba232682019-03-27 14:55:26 -07003110 LayoutParams lp = view.getLayoutParams();
3111 if (lp == null) {
Matt Pietalab986b52019-04-10 10:14:32 -04003112 lp = new LayoutParams(widthPx, heightPx);
Mike Digmanba232682019-03-27 14:55:26 -07003113 view.setLayoutParams(lp);
3114 } else {
3115 lp.height = heightPx;
Matt Pietalab986b52019-04-10 10:14:32 -04003116 lp.width = widthPx;
Mike Digmanba232682019-03-27 14:55:26 -07003117 }
3118 }
3119
Zhen Zhangbde7b462019-11-11 11:49:33 -08003120 ItemGroupViewHolder createItemGroupViewHolder(int viewType, ViewGroup parent) {
Matt Pietal5b648562019-03-12 07:40:26 -04003121 if (viewType == VIEW_TYPE_DIRECT_SHARE) {
3122 ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate(
3123 R.layout.chooser_row_direct_share, parent, false);
3124 ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3125 parentGroup, false);
3126 ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3127 parentGroup, false);
3128 parentGroup.addView(row1);
3129 parentGroup.addView(row2);
3130
3131 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
3132 Lists.newArrayList(row1, row2), getMaxTargetsPerRow());
Zhen Zhangbde7b462019-11-11 11:49:33 -08003133 loadViewsIntoGroup(mDirectShareViewHolder);
Matt Pietal5b648562019-03-12 07:40:26 -04003134
3135 return mDirectShareViewHolder;
3136 } else {
3137 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
3138 false);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003139 ItemGroupViewHolder holder = new SingleRowViewHolder(row, getMaxTargetsPerRow());
3140 loadViewsIntoGroup(holder);
Matt Pietal5b648562019-03-12 07:40:26 -04003141
3142 return holder;
3143 }
3144 }
3145
Matt Pietaldadc0d12019-04-16 12:53:28 -04003146 /**
Mike Digmanae730b12019-04-25 11:10:31 -07003147 * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
3148 * showing on top of the AZ list if the AZ label is visible. All other types are placed into
3149 * their own row as determined by their target type, and dividers are added in the list to
3150 * separate each type.
Matt Pietaldadc0d12019-04-16 12:53:28 -04003151 */
3152 int getRowType(int rowPosition) {
Mike Digmanae730b12019-04-25 11:10:31 -07003153 // Merge caller and ranked standard into a single row
Matt Pietaldadc0d12019-04-16 12:53:28 -04003154 int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
3155 if (positionType == ChooserListAdapter.TARGET_CALLER) {
3156 return ChooserListAdapter.TARGET_STANDARD;
3157 }
3158
Mike Digmanae730b12019-04-25 11:10:31 -07003159 // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
3160 // row type the same as the suggestion row type
3161 if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
3162 return ChooserListAdapter.TARGET_STANDARD;
3163 }
3164
Matt Pietaldadc0d12019-04-16 12:53:28 -04003165 return positionType;
3166 }
3167
Zhen Zhangbde7b462019-11-11 11:49:33 -08003168 void bindItemViewHolder(int position, ItemViewHolder holder) {
3169 View v = holder.itemView;
3170 int listPosition = getListPosition(position);
3171 holder.mListPosition = listPosition;
3172 mChooserListAdapter.bindView(listPosition, v);
3173 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003174
Zhen Zhangbde7b462019-11-11 11:49:33 -08003175 void bindItemGroupViewHolder(int position, ItemGroupViewHolder holder) {
3176 final ViewGroup viewGroup = (ViewGroup) holder.itemView;
3177 int start = getListPosition(position);
3178 int startType = getRowType(start);
arangelov934c64c2020-02-14 13:48:23 +00003179 if (viewGroup.getForeground() == null && position > 0) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08003180 viewGroup.setForeground(
Matt Pietal74c6ed02019-04-18 13:38:46 -04003181 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003182 }
3183
Matt Pietalfaedea82019-03-21 10:36:54 -04003184 int columnCount = holder.getColumnCount();
Matt Pietal5b648562019-03-12 07:40:26 -04003185 int end = start + columnCount - 1;
Matt Pietaldadc0d12019-04-16 12:53:28 -04003186 while (getRowType(end) != startType && end >= start) {
Adam Powell7d758002015-05-06 17:49:36 -07003187 end--;
3188 }
3189
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003190 if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08003191 final TextView textView = viewGroup.findViewById(R.id.chooser_row_text_option);
Adam Powell63b31692015-09-28 10:45:00 -07003192
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003193 if (textView.getVisibility() != View.VISIBLE) {
3194 textView.setAlpha(0.0f);
3195 textView.setVisibility(View.VISIBLE);
3196 textView.setText(R.string.chooser_no_direct_share_targets);
3197
3198 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
3199 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3200
3201 float translationInPx = getResources().getDimensionPixelSize(
3202 R.dimen.chooser_row_text_option_translate);
3203 textView.setTranslationY(translationInPx);
3204 ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
3205 0.0f);
3206 translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3207
3208 AnimatorSet animSet = new AnimatorSet();
3209 animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3210 animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3211 animSet.playTogether(fadeAnim, translateAnim);
3212 animSet.start();
3213 }
Adam Powell7d758002015-05-06 17:49:36 -07003214 }
3215
Matt Pietal5b648562019-03-12 07:40:26 -04003216 for (int i = 0; i < columnCount; i++) {
3217 final View v = holder.getView(i);
arangelov38a6fce2019-12-02 18:21:22 +00003218
Adam Powell7d758002015-05-06 17:49:36 -07003219 if (start + i <= end) {
Matt Pietalfaedea82019-03-21 10:36:54 -04003220 holder.setViewVisibility(i, View.VISIBLE);
Matt Pietal5b648562019-03-12 07:40:26 -04003221 holder.setItemIndex(i, start + i);
3222 mChooserListAdapter.bindView(holder.getItemIndex(i), v);
Adam Powell7d758002015-05-06 17:49:36 -07003223 } else {
Matt Pietalfaedea82019-03-21 10:36:54 -04003224 holder.setViewVisibility(i, View.INVISIBLE);
Adam Powell7d758002015-05-06 17:49:36 -07003225 }
3226 }
3227 }
3228
Zhen Zhangbde7b462019-11-11 11:49:33 -08003229 int getListPosition(int position) {
arangelov590fba32020-02-11 18:05:42 +00003230 position -= getContentPreviewRowCount() + getProfileRowCount();
Matt Pietal1ef88002019-03-13 10:43:18 -04003231
Matt Pietal5b648562019-03-12 07:40:26 -04003232 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
3233 final int serviceRows = (int) Math.ceil((float) serviceCount
3234 / ChooserListAdapter.MAX_SERVICE_TARGETS);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003235 if (position < serviceRows) {
3236 return position * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07003237 }
3238
Zhen Zhangbde7b462019-11-11 11:49:33 -08003239 position -= serviceRows;
3240
Matt Pietaldadc0d12019-04-16 12:53:28 -04003241 final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
3242 + mChooserListAdapter.getRankedTargetCount();
3243 final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
Zhen Zhangbde7b462019-11-11 11:49:33 -08003244 if (position < callerAndRankedRows) {
3245 return serviceCount + position * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07003246 }
3247
Zhen Zhangbde7b462019-11-11 11:49:33 -08003248 position -= getAzLabelRowCount() + callerAndRankedRows;
Mike Digmanae730b12019-04-25 11:10:31 -07003249
Zhen Zhangbde7b462019-11-11 11:49:33 -08003250 return callerAndRankedCount + serviceCount + position;
Matt Pietal5b648562019-03-12 07:40:26 -04003251 }
3252
3253 public void handleScroll(View v, int y, int oldy) {
Matt Pietale54dcc2e2019-05-02 12:59:38 -04003254 // Only expand direct share area if there is a minimum number of shortcuts,
3255 // which will help reduce the amount of visible shuffling due to older-style
3256 // direct share targets.
3257 int orientation = getResources().getConfiguration().orientation;
3258 boolean canExpandDirectShare =
3259 mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
Matt Pietal3e4b56f2019-05-31 12:06:17 -04003260 && orientation == Configuration.ORIENTATION_PORTRAIT
3261 && !isInMultiWindowMode();
Matt Pietale54dcc2e2019-05-02 12:59:38 -04003262
3263 if (mDirectShareViewHolder != null && canExpandDirectShare) {
arangelov38a6fce2019-12-02 18:21:22 +00003264 mDirectShareViewHolder.handleScroll(
arangelovcf268642020-01-15 15:09:51 +00003265 mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy,
arangelov38a6fce2019-12-02 18:21:22 +00003266 getMaxTargetsPerRow());
Matt Pietal5b648562019-03-12 07:40:26 -04003267 }
Adam Powell7d758002015-05-06 17:49:36 -07003268 }
arangelov38a6fce2019-12-02 18:21:22 +00003269
3270 public ChooserListAdapter getListAdapter() {
3271 return mChooserListAdapter;
3272 }
3273
arangelov38a6fce2019-12-02 18:21:22 +00003274 boolean shouldCellSpan(int position) {
3275 return getItemViewType(position) == VIEW_TYPE_NORMAL;
3276 }
Adam Powell7d758002015-05-06 17:49:36 -07003277 }
3278
Zhen Zhangbde7b462019-11-11 11:49:33 -08003279 /**
3280 * Used to bind types for group of items including:
3281 * {@link ChooserGridAdapter#VIEW_TYPE_DIRECT_SHARE},
3282 * and {@link ChooserGridAdapter#VIEW_TYPE_CALLER_AND_RANK}.
3283 */
3284 abstract class ItemGroupViewHolder extends RecyclerView.ViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04003285 protected int mMeasuredRowHeight;
3286 private int[] mItemIndices;
3287 protected final View[] mCells;
Matt Pietal5b648562019-03-12 07:40:26 -04003288 private final int mColumnCount;
Adam Powell63b31692015-09-28 10:45:00 -07003289
Zhen Zhangbde7b462019-11-11 11:49:33 -08003290 ItemGroupViewHolder(int cellCount, View itemView) {
3291 super(itemView);
Matt Pietal5b648562019-03-12 07:40:26 -04003292 this.mCells = new View[cellCount];
3293 this.mItemIndices = new int[cellCount];
Matt Pietal5b648562019-03-12 07:40:26 -04003294 this.mColumnCount = cellCount;
3295 }
3296
3297 abstract ViewGroup addView(int index, View v);
3298
3299 abstract ViewGroup getViewGroup();
3300
Mike Digmanba232682019-03-27 14:55:26 -07003301 abstract ViewGroup getRowByIndex(int index);
3302
3303 abstract ViewGroup getRow(int rowNumber);
Matt Pietal5b648562019-03-12 07:40:26 -04003304
Matt Pietalfaedea82019-03-21 10:36:54 -04003305 abstract void setViewVisibility(int i, int visibility);
3306
Matt Pietal5b648562019-03-12 07:40:26 -04003307 public int getColumnCount() {
3308 return mColumnCount;
3309 }
3310
Adam Powell63b31692015-09-28 10:45:00 -07003311 public void measure() {
3312 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietal5b648562019-03-12 07:40:26 -04003313 getViewGroup().measure(spec, spec);
3314 mMeasuredRowHeight = getViewGroup().getMeasuredHeight();
3315 }
3316
3317 public int getMeasuredRowHeight() {
3318 return mMeasuredRowHeight;
3319 }
3320
Matt Pietal5b648562019-03-12 07:40:26 -04003321 public void setItemIndex(int itemIndex, int listIndex) {
3322 mItemIndices[itemIndex] = listIndex;
3323 }
3324
3325 public int getItemIndex(int itemIndex) {
3326 return mItemIndices[itemIndex];
3327 }
3328
3329 public View getView(int index) {
3330 return mCells[index];
3331 }
3332 }
3333
Zhen Zhangbde7b462019-11-11 11:49:33 -08003334 class SingleRowViewHolder extends ItemGroupViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04003335 private final ViewGroup mRow;
3336
3337 SingleRowViewHolder(ViewGroup row, int cellCount) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08003338 super(cellCount, row);
Matt Pietal5b648562019-03-12 07:40:26 -04003339
3340 this.mRow = row;
3341 }
3342
3343 public ViewGroup getViewGroup() {
3344 return mRow;
3345 }
3346
Mike Digmanba232682019-03-27 14:55:26 -07003347 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04003348 return mRow;
3349 }
3350
Mike Digmanba232682019-03-27 14:55:26 -07003351 public ViewGroup getRow(int rowNumber) {
3352 if (rowNumber == 0) return mRow;
3353 return null;
3354 }
3355
Matt Pietal5b648562019-03-12 07:40:26 -04003356 public ViewGroup addView(int index, View v) {
3357 mRow.addView(v);
3358 mCells[index] = v;
3359
Matt Pietal5b648562019-03-12 07:40:26 -04003360 return mRow;
3361 }
Matt Pietalfaedea82019-03-21 10:36:54 -04003362
3363 public void setViewVisibility(int i, int visibility) {
3364 getView(i).setVisibility(visibility);
3365 }
Matt Pietal5b648562019-03-12 07:40:26 -04003366 }
3367
Zhen Zhangbde7b462019-11-11 11:49:33 -08003368 class DirectShareViewHolder extends ItemGroupViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04003369 private final ViewGroup mParent;
3370 private final List<ViewGroup> mRows;
3371 private int mCellCountPerRow;
3372
3373 private boolean mHideDirectShareExpansion = false;
3374 private int mDirectShareMinHeight = 0;
3375 private int mDirectShareCurrHeight = 0;
3376 private int mDirectShareMaxHeight = 0;
3377
Matt Pietalfaedea82019-03-21 10:36:54 -04003378 private final boolean[] mCellVisibility;
3379
Matt Pietal5b648562019-03-12 07:40:26 -04003380 DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08003381 super(rows.size() * cellCountPerRow, parent);
Matt Pietal5b648562019-03-12 07:40:26 -04003382
3383 this.mParent = parent;
3384 this.mRows = rows;
3385 this.mCellCountPerRow = cellCountPerRow;
Matt Pietalfaedea82019-03-21 10:36:54 -04003386 this.mCellVisibility = new boolean[rows.size() * cellCountPerRow];
Matt Pietal5b648562019-03-12 07:40:26 -04003387 }
3388
3389 public ViewGroup addView(int index, View v) {
Mike Digmanba232682019-03-27 14:55:26 -07003390 ViewGroup row = getRowByIndex(index);
Matt Pietal5b648562019-03-12 07:40:26 -04003391 row.addView(v);
3392 mCells[index] = v;
3393
Matt Pietal5b648562019-03-12 07:40:26 -04003394 return row;
3395 }
3396
3397 public ViewGroup getViewGroup() {
3398 return mParent;
3399 }
3400
Mike Digmanba232682019-03-27 14:55:26 -07003401 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04003402 return mRows.get(index / mCellCountPerRow);
3403 }
3404
Mike Digmanba232682019-03-27 14:55:26 -07003405 public ViewGroup getRow(int rowNumber) {
3406 return mRows.get(rowNumber);
3407 }
3408
Matt Pietal5b648562019-03-12 07:40:26 -04003409 public void measure() {
3410 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3411 getRow(0).measure(spec, spec);
3412 getRow(1).measure(spec, spec);
3413
Matt Pietal5b648562019-03-12 07:40:26 -04003414 mDirectShareMinHeight = getRow(0).getMeasuredHeight();
3415 mDirectShareCurrHeight = mDirectShareCurrHeight > 0
Zhen Zhangbde7b462019-11-11 11:49:33 -08003416 ? mDirectShareCurrHeight : mDirectShareMinHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04003417 mDirectShareMaxHeight = 2 * mDirectShareMinHeight;
3418 }
3419
3420 public int getMeasuredRowHeight() {
3421 return mDirectShareCurrHeight;
3422 }
3423
Matt Pietala9c8e502019-04-10 14:27:35 -04003424 public int getMinRowHeight() {
3425 return mDirectShareMinHeight;
3426 }
3427
Matt Pietalfaedea82019-03-21 10:36:54 -04003428 public void setViewVisibility(int i, int visibility) {
3429 final View v = getView(i);
3430 if (visibility == View.VISIBLE) {
3431 mCellVisibility[i] = true;
3432 v.setVisibility(visibility);
3433 v.setAlpha(1.0f);
3434 } else if (visibility == View.INVISIBLE && mCellVisibility[i]) {
3435 mCellVisibility[i] = false;
3436
3437 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
3438 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3439 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
3440 fadeAnim.addListener(new AnimatorListenerAdapter() {
3441 public void onAnimationEnd(Animator animation) {
3442 v.setVisibility(View.INVISIBLE);
3443 }
3444 });
3445 fadeAnim.start();
3446 }
3447 }
3448
Zhen Zhangbde7b462019-11-11 11:49:33 -08003449 public void handleScroll(RecyclerView view, int y, int oldy, int maxTargetsPerRow) {
Matt Pietala9c8e502019-04-10 14:27:35 -04003450 // only exit early if fully collapsed, otherwise onListRebuilt() with shifting
3451 // targets can lock us into an expanded mode
3452 boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight;
3453 if (notExpanded) {
3454 if (mHideDirectShareExpansion) {
3455 return;
3456 }
Matt Pietal5b648562019-03-12 07:40:26 -04003457
Matt Pietala9c8e502019-04-10 14:27:35 -04003458 // only expand if we have more than maxTargetsPerRow, and delay that decision
3459 // until they start to scroll
arangelova3912cf2019-12-13 14:34:45 +00003460 if (mChooserMultiProfilePagerAdapter.getActiveListAdapter()
arangelov38a6fce2019-12-02 18:21:22 +00003461 .getSelectableServiceTargetCount() <= maxTargetsPerRow) {
Matt Pietala9c8e502019-04-10 14:27:35 -04003462 mHideDirectShareExpansion = true;
3463 return;
3464 }
Matt Pietal5b648562019-03-12 07:40:26 -04003465 }
3466
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003467 int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal5b648562019-03-12 07:40:26 -04003468
3469 int prevHeight = mDirectShareCurrHeight;
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003470 int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
3471 newHeight = Math.max(newHeight, mDirectShareMinHeight);
3472 yDiff = newHeight - prevHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04003473
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003474 if (view == null || view.getChildCount() == 0 || yDiff == 0) {
Matt Pietal5b648562019-03-12 07:40:26 -04003475 return;
3476 }
3477
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003478 // locate the item to expand, and offset the rows below that one
3479 boolean foundExpansion = false;
3480 for (int i = 0; i < view.getChildCount(); i++) {
3481 View child = view.getChildAt(i);
Matt Pietal1ef88002019-03-13 10:43:18 -04003482
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003483 if (foundExpansion) {
3484 child.offsetTopAndBottom(yDiff);
3485 } else {
3486 if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
3487 int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
3488 MeasureSpec.EXACTLY);
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003489 int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003490 MeasureSpec.EXACTLY);
3491 child.measure(widthSpec, heightSpec);
3492 child.getLayoutParams().height = child.getMeasuredHeight();
3493 child.layout(child.getLeft(), child.getTop(), child.getRight(),
3494 child.getTop() + child.getMeasuredHeight());
Matt Pietal5b648562019-03-12 07:40:26 -04003495
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003496 foundExpansion = true;
3497 }
3498 }
Matt Pietal5b648562019-03-12 07:40:26 -04003499 }
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003500
3501 if (foundExpansion) {
3502 mDirectShareCurrHeight = newHeight;
3503 }
Adam Powell63b31692015-09-28 10:45:00 -07003504 }
3505 }
3506
Adam Powell9761ab22015-09-08 17:01:49 -07003507 static class ChooserTargetServiceConnection implements ServiceConnection {
Adam Powell52c39212016-04-07 15:14:18 -07003508 private DisplayResolveInfo mOriginalTarget;
Adam Powell9761ab22015-09-08 17:01:49 -07003509 private ComponentName mConnectedComponent;
3510 private ChooserActivity mChooserActivity;
arangelov5fc9e7d2020-01-07 17:59:14 +00003511 private final UserHandle mUserHandle;
Adam Powell9761ab22015-09-08 17:01:49 -07003512 private final Object mLock = new Object();
Adam Powell24428412015-04-01 17:19:56 -07003513
3514 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
3515 @Override
3516 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
Adam Powell9761ab22015-09-08 17:01:49 -07003517 synchronized (mLock) {
3518 if (mChooserActivity == null) {
3519 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
3520 + mConnectedComponent + "; ignoring...");
3521 return;
3522 }
arangelov5fc9e7d2020-01-07 17:59:14 +00003523 Context contextAsUser =
3524 mChooserActivity.createContextAsUser(mUserHandle, 0 /* flags */);
3525 mChooserActivity.filterServiceTargets(contextAsUser,
Adam Powell9761ab22015-09-08 17:01:49 -07003526 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
3527 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04003528 msg.what = ChooserHandler.CHOOSER_TARGET_SERVICE_RESULT;
Adam Powell9761ab22015-09-08 17:01:49 -07003529 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
arangelov5fc9e7d2020-01-07 17:59:14 +00003530 ChooserTargetServiceConnection.this, mUserHandle);
Adam Powell9761ab22015-09-08 17:01:49 -07003531 mChooserActivity.mChooserHandler.sendMessage(msg);
3532 }
Adam Powell24428412015-04-01 17:19:56 -07003533 }
3534 };
3535
Adam Powell9761ab22015-09-08 17:01:49 -07003536 public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
arangelov5fc9e7d2020-01-07 17:59:14 +00003537 DisplayResolveInfo dri, UserHandle userHandle) {
Adam Powell9761ab22015-09-08 17:01:49 -07003538 mChooserActivity = chooserActivity;
Adam Powell24428412015-04-01 17:19:56 -07003539 mOriginalTarget = dri;
arangelov5fc9e7d2020-01-07 17:59:14 +00003540 mUserHandle = userHandle;
Adam Powell24428412015-04-01 17:19:56 -07003541 }
3542
3543 @Override
3544 public void onServiceConnected(ComponentName name, IBinder service) {
3545 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003546 synchronized (mLock) {
3547 if (mChooserActivity == null) {
3548 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
3549 return;
3550 }
3551
3552 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
3553 try {
3554 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
3555 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
3556 } catch (RemoteException e) {
3557 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
3558 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003559 mChooserActivity.mServiceConnections.remove(this);
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003560 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003561 }
Adam Powell24428412015-04-01 17:19:56 -07003562 }
3563 }
3564
3565 @Override
3566 public void onServiceDisconnected(ComponentName name) {
3567 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003568 synchronized (mLock) {
3569 if (mChooserActivity == null) {
3570 Log.e(TAG,
3571 "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
3572 return;
3573 }
3574
3575 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003576 mChooserActivity.mServiceConnections.remove(this);
3577 if (mChooserActivity.mServiceConnections.isEmpty()) {
Adam Powell9761ab22015-09-08 17:01:49 -07003578 mChooserActivity.sendVoiceChoicesIfNeeded();
3579 }
3580 mConnectedComponent = null;
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003581 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003582 }
3583 }
3584
3585 public void destroy() {
3586 synchronized (mLock) {
3587 mChooserActivity = null;
Adam Powell52c39212016-04-07 15:14:18 -07003588 mOriginalTarget = null;
Adam Powell4c470d62015-06-19 17:46:17 -07003589 }
Adam Powell24428412015-04-01 17:19:56 -07003590 }
3591
3592 @Override
3593 public String toString() {
Adam Powell9761ab22015-09-08 17:01:49 -07003594 return "ChooserTargetServiceConnection{service="
3595 + mConnectedComponent + ", activity="
Adam Powell52c39212016-04-07 15:14:18 -07003596 + (mOriginalTarget != null
3597 ? mOriginalTarget.getResolveInfo().activityInfo.toString()
3598 : "<connection destroyed>") + "}";
Adam Powell24428412015-04-01 17:19:56 -07003599 }
Song Hue2deffd2020-03-09 15:22:29 -07003600
3601 public ComponentName getComponentName() {
3602 return mOriginalTarget.getResolveInfo().activityInfo.getComponentName();
3603 }
Adam Powell24428412015-04-01 17:19:56 -07003604 }
3605
3606 static class ServiceResultInfo {
3607 public final DisplayResolveInfo originalTarget;
3608 public final List<ChooserTarget> resultTargets;
3609 public final ChooserTargetServiceConnection connection;
arangelov5fc9e7d2020-01-07 17:59:14 +00003610 public final UserHandle userHandle;
Adam Powell24428412015-04-01 17:19:56 -07003611
3612 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
arangelov5fc9e7d2020-01-07 17:59:14 +00003613 ChooserTargetServiceConnection c, UserHandle userHandle) {
Adam Powell24428412015-04-01 17:19:56 -07003614 originalTarget = ot;
3615 resultTargets = rt;
3616 connection = c;
arangelov5fc9e7d2020-01-07 17:59:14 +00003617 this.userHandle = userHandle;
Adam Powell24428412015-04-01 17:19:56 -07003618 }
3619 }
Adam Powell2ed547e2015-04-29 18:45:04 -07003620
3621 static class RefinementResultReceiver extends ResultReceiver {
3622 private ChooserActivity mChooserActivity;
3623 private TargetInfo mSelectedTarget;
3624
3625 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
3626 Handler handler) {
3627 super(handler);
3628 mChooserActivity = host;
3629 mSelectedTarget = target;
3630 }
3631
3632 @Override
3633 protected void onReceiveResult(int resultCode, Bundle resultData) {
3634 if (mChooserActivity == null) {
3635 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
3636 return;
3637 }
3638 if (resultData == null) {
3639 Log.e(TAG, "RefinementResultReceiver received null resultData");
3640 return;
3641 }
3642
3643 switch (resultCode) {
3644 case RESULT_CANCELED:
3645 mChooserActivity.onRefinementCanceled();
3646 break;
3647 case RESULT_OK:
3648 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
3649 if (intentParcelable instanceof Intent) {
3650 mChooserActivity.onRefinementResult(mSelectedTarget,
3651 (Intent) intentParcelable);
3652 } else {
3653 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
3654 + " in resultData with key Intent.EXTRA_INTENT");
3655 }
3656 break;
3657 default:
3658 Log.w(TAG, "Unknown result code " + resultCode
3659 + " sent to RefinementResultReceiver");
3660 break;
3661 }
3662 }
3663
3664 public void destroy() {
3665 mChooserActivity = null;
3666 mSelectedTarget = null;
3667 }
3668 }
Adam Powell63b31692015-09-28 10:45:00 -07003669
Matt Pietal26038402019-01-08 07:29:34 -05003670 /**
3671 * Used internally to round image corners while obeying view padding.
3672 */
3673 public static class RoundedRectImageView extends ImageView {
3674 private int mRadius = 0;
3675 private Path mPath = new Path();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003676 private Paint mOverlayPaint = new Paint(0);
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003677 private Paint mRoundRectPaint = new Paint(0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003678 private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
3679 private String mExtraImageCount = null;
Matt Pietal26038402019-01-08 07:29:34 -05003680
3681 public RoundedRectImageView(Context context) {
3682 super(context);
3683 }
3684
3685 public RoundedRectImageView(Context context, AttributeSet attrs) {
3686 this(context, attrs, 0);
3687 }
3688
3689 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
3690 this(context, attrs, defStyleAttr, 0);
3691 }
3692
3693 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
3694 int defStyleRes) {
3695 super(context, attrs, defStyleAttr, defStyleRes);
3696 mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003697
3698 mOverlayPaint.setColor(0x99000000);
3699 mOverlayPaint.setStyle(Paint.Style.FILL);
3700
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003701 mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider));
3702 mRoundRectPaint.setStyle(Paint.Style.STROKE);
3703 mRoundRectPaint.setStrokeWidth(context.getResources()
3704 .getDimensionPixelSize(R.dimen.chooser_preview_image_border));
3705
Matt Pietal0ea391b2019-01-30 10:44:15 -05003706 mTextPaint.setColor(Color.WHITE);
3707 mTextPaint.setTextSize(context.getResources()
3708 .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
3709 mTextPaint.setTextAlign(Paint.Align.CENTER);
Matt Pietal26038402019-01-08 07:29:34 -05003710 }
3711
3712 private void updatePath(int width, int height) {
3713 mPath.reset();
3714
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003715 int imageWidth = width - getPaddingRight() - getPaddingLeft();
3716 int imageHeight = height - getPaddingBottom() - getPaddingTop();
Matt Pietal26038402019-01-08 07:29:34 -05003717 mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
3718 mRadius, Path.Direction.CW);
3719 }
3720
3721 /**
3722 * Sets the corner radius on all corners
3723 *
3724 * param radius 0 for no radius, &gt; 0 for a visible corner radius
3725 */
3726 public void setRadius(int radius) {
3727 mRadius = radius;
3728 updatePath(getWidth(), getHeight());
3729 }
3730
Matt Pietal0ea391b2019-01-30 10:44:15 -05003731 /**
3732 * Display an overlay with extra image count on 3rd image
3733 */
3734 public void setExtraImageCount(int count) {
3735 if (count > 0) {
3736 this.mExtraImageCount = "+" + count;
3737 } else {
3738 this.mExtraImageCount = null;
3739 }
3740 }
3741
Matt Pietal26038402019-01-08 07:29:34 -05003742 @Override
3743 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
3744 super.onSizeChanged(width, height, oldWidth, oldHeight);
3745 updatePath(width, height);
3746 }
3747
3748 @Override
3749 protected void onDraw(Canvas canvas) {
3750 if (mRadius != 0) {
3751 canvas.clipPath(mPath);
3752 }
3753
3754 super.onDraw(canvas);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003755
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003756 int x = getPaddingLeft();
3757 int y = getPaddingRight();
3758 int width = getWidth() - getPaddingRight() - getPaddingLeft();
3759 int height = getHeight() - getPaddingBottom() - getPaddingTop();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003760 if (mExtraImageCount != null) {
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003761 canvas.drawRect(x, y, width, height, mOverlayPaint);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003762
3763 int xPos = canvas.getWidth() / 2;
3764 int yPos = (int) ((canvas.getHeight() / 2.0f)
3765 - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
3766
3767 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
3768 }
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003769
3770 canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
Matt Pietal26038402019-01-08 07:29:34 -05003771 }
3772 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003773}