blob: d851a099d0e17cc69e10d957e671f7c13fca6171 [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;
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500127import com.android.internal.util.FrameworkStatsLog;
Zhen Zhangbde7b462019-11-11 11:49:33 -0800128import com.android.internal.widget.GridLayoutManager;
129import com.android.internal.widget.RecyclerView;
Mike Digman849a9d12019-04-29 11:20:48 -0700130import com.android.internal.widget.ResolverDrawerLayout;
Alison Cichowlas3e340502018-08-07 17:15:01 -0400131
Adam Powell52c39212016-04-07 15:14:18 -0700132import com.google.android.collect.Lists;
Adam Powell24428412015-04-01 17:19:56 -0700133
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500134import java.io.File;
Matt Pietal26038402019-01-08 07:29:34 -0500135import java.io.IOException;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500136import java.lang.annotation.Retention;
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700137import java.lang.annotation.RetentionPolicy;
Dan Sandlere3d19932019-11-22 11:58:58 -0500138import java.net.URISyntaxException;
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400139import java.text.Collator;
Adam Powell24428412015-04-01 17:19:56 -0700140import java.util.ArrayList;
Adam Powella182e452015-07-06 16:57:56 -0700141import java.util.Collections;
142import java.util.Comparator;
George Hodulikaa5238c2019-04-18 14:17:51 -0700143import java.util.HashMap;
Matt Pietalab73a882019-06-05 07:04:55 -0400144import java.util.HashSet;
Adam Powell24428412015-04-01 17:19:56 -0700145import java.util.List;
George Hodulikaa5238c2019-04-18 14:17:51 -0700146import java.util.Map;
Matt Pietalab73a882019-06-05 07:04:55 -0400147import java.util.Set;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400149/**
150 * The Chooser Activity handles intent resolution specifically for sharing intents -
151 * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
152 *
153 */
arangelovb0802dc2019-10-18 18:03:44 +0100154public class ChooserActivity extends ResolverActivity implements
155 ChooserListAdapter.ChooserListCommunicator,
156 SelectableTargetInfoCommunicator {
Adam Powell0b3c1122014-10-09 12:50:14 -0700157 private static final String TAG = "ChooserActivity";
arangelov5fc9e7d2020-01-07 17:59:14 +0000158 private AppPredictor mPersonalAppPredictor;
159 private AppPredictor mWorkAppPredictor;
Adam Powell0b3c1122014-10-09 12:50:14 -0700160
Artur Satayev751e5512019-11-15 19:12:49 +0000161 @UnsupportedAppUsage
162 public ChooserActivity() {
163 }
Jorim Jaggif631ef72017-02-24 13:49:47 +0100164 /**
165 * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
166 * in onStop when launched in a new task. If this extra is set to true, we do not finish
167 * ourselves when onStop gets called.
168 */
169 public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
170 = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
171
arangelovc7581392020-04-07 17:10:36 +0100172 /**
173 * Integer extra to indicate which profile should be automatically selected.
174 * <p>Can only be used if there is a work profile.
175 * <p>Possible values can be either {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}.
176 */
177 static final String EXTRA_SELECTED_PROFILE =
178 "com.android.internal.app.ChooserActivity.EXTRA_SELECTED_PROFILE";
179
180 static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL;
181 static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK;
182
Mike Digman849a9d12019-04-29 11:20:48 -0700183 private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
184
Dan Sandlere3d19932019-11-22 11:58:58 -0500185 private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label";
186 private static final String CHIP_ICON_METADATA_KEY = "android.service.chooser.chip_icon";
187
arangelov5fc9e7d2020-01-07 17:59:14 +0000188 private static final boolean DEBUG = true;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800189
George Hodulik1428beb2019-05-01 17:04:23 -0700190 private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
George Hodulik69d4a082019-01-18 11:27:03 -0800191 // TODO(b/123088566) Share these in a better way.
192 private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500193 public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share";
George Hodulik69d4a082019-01-18 11:27:03 -0800194 private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
195 public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700196
arangelovb0802dc2019-10-18 18:03:44 +0100197 @VisibleForTesting
198 public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
199
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700200 private boolean mIsAppPredictorComponentAvailable;
George Hodulikaa5238c2019-04-18 14:17:51 -0700201 private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
arangelov5fc9e7d2020-01-07 17:59:14 +0000202 private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache;
George Hodulik69d4a082019-01-18 11:27:03 -0800203
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700204 public static final int TARGET_TYPE_DEFAULT = 0;
205 public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
206 public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
207 public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
208
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500209 public static final int SELECTION_TYPE_SERVICE = 1;
210 public static final int SELECTION_TYPE_APP = 2;
211 public static final int SELECTION_TYPE_STANDARD = 3;
212 public static final int SELECTION_TYPE_COPY = 4;
213
214 // statsd logger wrapper
215 protected ChooserActivityLogger mChooserActivityLogger;
216
arangelovb0802dc2019-10-18 18:03:44 +0100217 private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
218
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700219 @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
220 TARGET_TYPE_DEFAULT,
221 TARGET_TYPE_CHOOSER_TARGET,
222 TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
223 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
224 })
225 @Retention(RetentionPolicy.SOURCE)
226 public @interface ShareTargetType {}
227
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500228 /**
229 * The transition time between placeholders for direct share to a message
230 * indicating that non are available.
231 */
232 private static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200;
233
Matt Pietal394ebd02019-05-03 07:36:21 -0400234 private static final float DIRECT_SHARE_EXPANSION_RATE = 0.78f;
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400235
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800236 // TODO(b/121287224): Re-evaluate this limit
237 private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
Adam Powell24428412015-04-01 17:19:56 -0700238
Adam Powell2ed547e2015-04-29 18:45:04 -0700239 private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
Adam Powell24428412015-04-01 17:19:56 -0700240
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400241 private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7;
242 private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
243 SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
244 DEFAULT_SALT_EXPIRATION_DAYS);
245
Song Hue2deffd2020-03-09 15:22:29 -0700246 private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
247 DeviceConfig.NAMESPACE_SYSTEMUI,
248 SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
Song Hu2eb5dbb2020-04-02 15:47:17 -0700249 true);
Song Hue2deffd2020-03-09 15:22:29 -0700250
Adam Powelle49d9392014-07-17 18:45:19 -0700251 private Bundle mReplacementExtras;
Adam Powell0b3c1122014-10-09 12:50:14 -0700252 private IntentSender mChosenComponentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -0700253 private IntentSender mRefinementIntentSender;
254 private RefinementResultReceiver mRefinementResultReceiver;
Adam Powell52c39212016-04-07 15:14:18 -0700255 private ChooserTarget[] mCallerChooserTargets;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800256 private ComponentName[] mFilteredComponentNames;
Adam Powelle49d9392014-07-17 18:45:19 -0700257
Adam Powell13036be2015-05-12 14:43:56 -0700258 private Intent mReferrerFillInIntent;
259
Kang Li9082f5b2016-12-02 10:56:21 -0800260 private long mChooserShownTime;
Kang Li64b018e2017-01-05 17:30:06 -0800261 protected boolean mIsSuccessfullySelected;
Kang Li9082f5b2016-12-02 10:56:21 -0800262
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700263 private long mQueriedTargetServicesTimeMs;
264 private long mQueriedSharingShortcutsTimeMs;
265
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500266 private int mChooserRowServiceSpacing;
Adam Powell24428412015-04-01 17:19:56 -0700267
Matt Pietalab73a882019-06-05 07:04:55 -0400268 private int mCurrAvailableWidth = 0;
arangelovc4dbdbd2020-02-18 20:54:16 +0000269 private int mLastNumberOfChildren = -1;
Matt Pietalab73a882019-06-05 07:04:55 -0400270
Adam Powell23882512016-01-29 10:21:00 -0800271 private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400272 // TODO: Update to handle landscape instead of using static value
273 private static final int MAX_RANKED_TARGETS = 4;
Adam Powell23882512016-01-29 10:21:00 -0800274
Adam Powell24428412015-04-01 17:19:56 -0700275 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
arangelov5fc9e7d2020-01-07 17:59:14 +0000276 private final Set<Pair<ComponentName, UserHandle>> mServicesRequested = new HashSet<>();
Matt Pietalaf044ae2019-03-29 06:53:53 -0400277
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -0400278 private static final int MAX_LOG_RANK_POSITION = 12;
279
Matt Pietal4e2e3632019-04-05 08:32:47 -0400280 private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400281 private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
Matt Pietal4e2e3632019-04-05 08:32:47 -0400282
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500283 private SharedPreferences mPinnedSharedPrefs;
284 private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
285
Matt Pietal0ea391b2019-01-30 10:44:15 -0500286 @Retention(SOURCE)
287 @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
288 private @interface ContentPreviewType {
289 }
290
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500291 // Starting at 1 since 0 is considered "undefined" for some of the database transformations
292 // of tron logs.
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500293 protected static final int CONTENT_PREVIEW_IMAGE = 1;
294 protected static final int CONTENT_PREVIEW_FILE = 2;
295 protected static final int CONTENT_PREVIEW_TEXT = 3;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500296 protected MetricsLogger mMetricsLogger;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500297
Matt Pietale7cacab2019-05-23 07:21:36 -0400298 private ContentPreviewCoordinator mPreviewCoord;
299
arangelov38a6fce2019-12-02 18:21:22 +0000300 @VisibleForTesting
301 protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
302
Matt Pietale7cacab2019-05-23 07:21:36 -0400303 private class ContentPreviewCoordinator {
Matt Pietale7cacab2019-05-23 07:21:36 -0400304 private static final int IMAGE_FADE_IN_MILLIS = 150;
305 private static final int IMAGE_LOAD_TIMEOUT = 1;
306 private static final int IMAGE_LOAD_INTO_VIEW = 2;
307
Matt Pietalab73a882019-06-05 07:04:55 -0400308 private final int mImageLoadTimeoutMillis =
309 getResources().getInteger(R.integer.config_shortAnimTime);
310
Matt Pietale7cacab2019-05-23 07:21:36 -0400311 private final View mParentView;
312 private boolean mHideParentOnFail;
313 private boolean mAtLeastOneLoaded = false;
314
315 class LoadUriTask {
316 public final Uri mUri;
317 public final int mImageResourceId;
318 public final int mExtraCount;
319 public final Bitmap mBmp;
320
321 LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) {
322 this.mImageResourceId = imageResourceId;
323 this.mUri = uri;
324 this.mExtraCount = extraCount;
325 this.mBmp = bmp;
326 }
327 }
328
329 // If at least one image loads within the timeout period, allow other
330 // loads to continue. Otherwise terminate and optionally hide
331 // the parent area
332 private final Handler mHandler = new Handler() {
333 @Override
334 public void handleMessage(Message msg) {
335 switch (msg.what) {
336 case IMAGE_LOAD_TIMEOUT:
337 maybeHideContentPreview();
338 break;
339
340 case IMAGE_LOAD_INTO_VIEW:
341 if (isFinishing()) break;
342
343 LoadUriTask task = (LoadUriTask) msg.obj;
344 RoundedRectImageView imageView = mParentView.findViewById(
345 task.mImageResourceId);
346 if (task.mBmp == null) {
347 imageView.setVisibility(View.GONE);
348 maybeHideContentPreview();
349 return;
350 }
351
352 mAtLeastOneLoaded = true;
353 imageView.setVisibility(View.VISIBLE);
354 imageView.setAlpha(0.0f);
355 imageView.setImageBitmap(task.mBmp);
356
357 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f,
358 1.0f);
359 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
360 fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS);
361 fadeAnim.start();
362
363 if (task.mExtraCount > 0) {
364 imageView.setExtraImageCount(task.mExtraCount);
365 }
366 }
367 }
368 };
369
370 ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) {
371 super();
372
373 this.mParentView = parentView;
374 this.mHideParentOnFail = hideParentOnFail;
375 }
376
377 private void loadUriIntoView(final int imageResourceId, final Uri uri,
378 final int extraImages) {
Matt Pietalab73a882019-06-05 07:04:55 -0400379 mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, mImageLoadTimeoutMillis);
Matt Pietale7cacab2019-05-23 07:21:36 -0400380
381 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
Alison Cichowlas07acade2019-12-20 11:26:23 -0500382 int size = getResources().getDimensionPixelSize(
383 R.dimen.chooser_preview_image_max_dimen);
384 final Bitmap bmp = loadThumbnail(uri, new Size(size, size));
Matt Pietale7cacab2019-05-23 07:21:36 -0400385 final Message msg = Message.obtain();
386 msg.what = IMAGE_LOAD_INTO_VIEW;
387 msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
388 mHandler.sendMessage(msg);
389 });
390 }
391
392 private void cancelLoads() {
393 mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW);
394 mHandler.removeMessages(IMAGE_LOAD_TIMEOUT);
395 }
396
397 private void maybeHideContentPreview() {
398 if (!mAtLeastOneLoaded && mHideParentOnFail) {
399 Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
Matt Pietalab73a882019-06-05 07:04:55 -0400400 + " within " + mImageLoadTimeoutMillis + "ms.");
Matt Pietale7cacab2019-05-23 07:21:36 -0400401 collapseParentView();
arangelovf163a882020-02-18 17:18:47 +0000402 if (shouldShowTabs()) {
arangelov590fba32020-02-11 18:05:42 +0000403 hideStickyContentPreview();
404 } else if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
405 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().hideContentPreview();
406 }
Matt Pietale7cacab2019-05-23 07:21:36 -0400407 mHideParentOnFail = false;
408 }
409 }
410
411 private void collapseParentView() {
412 // This will effectively hide the content preview row by forcing the height
413 // to zero. It is faster than forcing a relayout of the listview
414 final View v = mParentView;
415 int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY);
416 int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
417 v.measure(widthSpec, heightSpec);
418 v.getLayoutParams().height = 0;
419 v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop());
420 v.invalidate();
421 }
422 }
423
Matt Pietalab73a882019-06-05 07:04:55 -0400424 private final ChooserHandler mChooserHandler = new ChooserHandler();
425
426 private class ChooserHandler extends Handler {
427 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
428 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT = 2;
429 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT = 3;
430 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
431 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
432 private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
433
434 private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
435 private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;
436
Song Hue2deffd2020-03-09 15:22:29 -0700437 private static final int DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS = 1500;
438 private int mDirectShareTimeout = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
439 SystemUiDeviceConfigFlags.SHARE_SHEET_DIRECT_SHARE_TIMEOUT,
440 DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS);
441
Matt Pietalab73a882019-06-05 07:04:55 -0400442 private boolean mMinTimeoutPassed = false;
443
444 private void removeAllMessages() {
445 removeMessages(LIST_VIEW_UPDATE_MESSAGE);
446 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
447 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
448 removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
449 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
450 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
451 }
452
453 private void restartServiceRequestTimer() {
454 mMinTimeoutPassed = false;
455 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
456 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
457
458 if (DEBUG) {
459 Log.d(TAG, "queryTargets setting watchdog timer for "
Song Hue2deffd2020-03-09 15:22:29 -0700460 + mDirectShareTimeout + "-"
Matt Pietalab73a882019-06-05 07:04:55 -0400461 + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
462 }
463
464 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
465 WATCHDOG_TIMEOUT_MIN_MILLIS);
466 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
Song Hue2deffd2020-03-09 15:22:29 -0700467 mAppendDirectShareEnabled ? mDirectShareTimeout : WATCHDOG_TIMEOUT_MAX_MILLIS);
Matt Pietalab73a882019-06-05 07:04:55 -0400468 }
469
470 private void maybeStopServiceRequestTimer() {
471 // Set a minimum timeout threshold, to ensure both apis, sharing shortcuts
472 // and older-style direct share services, have had time to load, otherwise
473 // just checking mServiceConnections could force us to end prematurely
474 if (mMinTimeoutPassed && mServiceConnections.isEmpty()) {
475 logDirectShareTargetReceived(
476 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
477 sendVoiceChoicesIfNeeded();
arangelova3912cf2019-12-13 14:34:45 +0000478 mChooserMultiProfilePagerAdapter.getActiveListAdapter()
arangelov38a6fce2019-12-02 18:21:22 +0000479 .completeServiceTargetLoading();
Matt Pietalab73a882019-06-05 07:04:55 -0400480 }
481 }
482
Adam Powell24428412015-04-01 17:19:56 -0700483 @Override
484 public void handleMessage(Message msg) {
arangelova3912cf2019-12-13 14:34:45 +0000485 if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == null || isDestroyed()) {
Matt Pietalaf044ae2019-03-29 06:53:53 -0400486 return;
487 }
488
Adam Powell24428412015-04-01 17:19:56 -0700489 switch (msg.what) {
490 case CHOOSER_TARGET_SERVICE_RESULT:
491 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
Adam Powell24428412015-04-01 17:19:56 -0700492 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
493 if (!mServiceConnections.contains(sri.connection)) {
494 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
Song Hue2deffd2020-03-09 15:22:29 -0700495 + sri.originalTarget.getResolveInfo().activityInfo.packageName
Adam Powell24428412015-04-01 17:19:56 -0700496 + " returned after being removed from active connections."
497 + " Have you considered returning results faster?");
498 break;
499 }
Adam Powella182e452015-07-06 16:57:56 -0700500 if (sri.resultTargets != null) {
arangelov5fc9e7d2020-01-07 17:59:14 +0000501 ChooserListAdapter adapterForUserHandle =
502 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
503 sri.userHandle);
504 if (adapterForUserHandle != null) {
505 adapterForUserHandle.addServiceResults(sri.originalTarget,
506 sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET,
Song Hue2deffd2020-03-09 15:22:29 -0700507 /* directShareShortcutInfoCache */ null, mServiceConnections);
arangelov5fc9e7d2020-01-07 17:59:14 +0000508 }
Adam Powella182e452015-07-06 16:57:56 -0700509 }
Adam Powell24428412015-04-01 17:19:56 -0700510 unbindService(sri.connection);
Adam Powell9761ab22015-09-08 17:01:49 -0700511 sri.connection.destroy();
Adam Powell24428412015-04-01 17:19:56 -0700512 mServiceConnections.remove(sri.connection);
Matt Pietalab73a882019-06-05 07:04:55 -0400513 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700514 break;
515
Matt Pietalab73a882019-06-05 07:04:55 -0400516 case CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT:
517 mMinTimeoutPassed = true;
518 maybeStopServiceRequestTimer();
519 break;
Matt Pietalaf044ae2019-03-29 06:53:53 -0400520
Matt Pietalab73a882019-06-05 07:04:55 -0400521 case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
Song Hue2deffd2020-03-09 15:22:29 -0700522 mMinTimeoutPassed = true;
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500523 if (!mServiceConnections.isEmpty()) {
524 getChooserActivityLogger().logSharesheetDirectLoadTimeout();
525 }
Adam Powell24428412015-04-01 17:19:56 -0700526 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -0400527 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700528 break;
529
Matt Pietalaf044ae2019-03-29 06:53:53 -0400530 case LIST_VIEW_UPDATE_MESSAGE:
531 if (DEBUG) {
532 Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
533 }
534
arangelov5fc9e7d2020-01-07 17:59:14 +0000535 UserHandle userHandle = (UserHandle) msg.obj;
536 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(userHandle)
537 .refreshListView();
Matt Pietalaf044ae2019-03-29 06:53:53 -0400538 break;
539
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800540 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
541 if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800542 final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
543 if (resultInfo.resultTargets != null) {
arangelov5fc9e7d2020-01-07 17:59:14 +0000544 ChooserListAdapter adapterForUserHandle =
545 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
546 resultInfo.userHandle);
547 if (adapterForUserHandle != null) {
548 adapterForUserHandle.addServiceResults(
549 resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1,
Song Hue2deffd2020-03-09 15:22:29 -0700550 mDirectShareShortcutInfoCache, mServiceConnections);
arangelov5fc9e7d2020-01-07 17:59:14 +0000551 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800552 }
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800553 break;
554
555 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700556 logDirectShareTargetReceived(
557 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800558 sendVoiceChoicesIfNeeded();
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500559 getChooserActivityLogger().logSharesheetDirectLoadComplete();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800560 break;
561
Adam Powell24428412015-04-01 17:19:56 -0700562 default:
563 super.handleMessage(msg);
564 }
565 }
566 };
567
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800568 @Override
569 protected void onCreate(Bundle savedInstanceState) {
Kang Li9082f5b2016-12-02 10:56:21 -0800570 final long intentReceivedTime = System.currentTimeMillis();
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500571 getChooserActivityLogger().logSharesheetTriggered();
Mehdi Alizadeh5cc5f712019-09-06 12:17:52 -0700572 // This is the only place this value is being set. Effectively final.
573 mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
574
Kang Li9082f5b2016-12-02 10:56:21 -0800575 mIsSuccessfullySelected = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800576 Intent intent = getIntent();
Dianne Hackborneb034652009-09-07 00:49:58 -0700577 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
Dan Sandlere3d19932019-11-22 11:58:58 -0500578 if (targetParcelable instanceof Uri) {
579 try {
580 targetParcelable = Intent.parseUri(targetParcelable.toString(),
581 Intent.URI_INTENT_SCHEME);
582 } catch (URISyntaxException ex) {
583 // doesn't parse as an intent; let the next test fail and error out
584 }
585 }
586
Dianne Hackborneb034652009-09-07 00:49:58 -0700587 if (!(targetParcelable instanceof Intent)) {
Christopher Tate9d6376a2014-02-12 13:14:10 -0800588 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
Dianne Hackborneb034652009-09-07 00:49:58 -0700589 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800590 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700591 return;
592 }
Adam Powell24428412015-04-01 17:19:56 -0700593 Intent target = (Intent) targetParcelable;
Craig Mautner411d2aed2014-05-08 09:07:43 -0700594 if (target != null) {
Adam Powelle49d9392014-07-17 18:45:19 -0700595 modifyTargetIntent(target);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700596 }
Adam Powell2ed547e2015-04-29 18:45:04 -0700597 Parcelable[] targetsParcelable
598 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
599 if (targetsParcelable != null) {
600 final boolean offset = target == null;
601 Intent[] additionalTargets =
602 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
603 for (int i = 0; i < targetsParcelable.length; i++) {
604 if (!(targetsParcelable[i] instanceof Intent)) {
605 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
606 + targetsParcelable[i]);
607 finish();
608 super.onCreate(null);
609 return;
610 }
611 final Intent additionalTarget = (Intent) targetsParcelable[i];
612 if (i == 0 && target == null) {
613 target = additionalTarget;
614 modifyTargetIntent(target);
615 } else {
616 additionalTargets[offset ? i - 1 : i] = additionalTarget;
617 modifyTargetIntent(additionalTarget);
618 }
619 }
620 setAdditionalTargets(additionalTargets);
621 }
622
Adam Powelle49d9392014-07-17 18:45:19 -0700623 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
Matt Pietal26038402019-01-08 07:29:34 -0500624
625 // Do not allow the title to be changed when sharing content
626 CharSequence title = null;
627 if (target != null) {
Matt Pietal95574b02019-03-13 08:12:25 -0400628 if (!isSendAction(target)) {
Matt Pietal26038402019-01-08 07:29:34 -0500629 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
630 } else {
631 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
632 + " preview title by using EXTRA_TITLE property of the wrapped"
633 + " EXTRA_INTENT.");
634 }
635 }
636
Adam Powell278902c2014-07-12 18:33:22 -0700637 int defaultTitleRes = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800638 if (title == null) {
Adam Powell278902c2014-07-12 18:33:22 -0700639 defaultTitleRes = com.android.internal.R.string.chooseActivity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800640 }
Matt Pietal26038402019-01-08 07:29:34 -0500641
Dianne Hackborneb034652009-09-07 00:49:58 -0700642 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
643 Intent[] initialIntents = null;
644 if (pa != null) {
Matt Pietal4e2e3632019-04-05 08:32:47 -0400645 int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS);
646 initialIntents = new Intent[count];
647 for (int i = 0; i < count; i++) {
Dianne Hackborneb034652009-09-07 00:49:58 -0700648 if (!(pa[i] instanceof Intent)) {
Adam Powell2ed547e2015-04-29 18:45:04 -0700649 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
Dianne Hackborneb034652009-09-07 00:49:58 -0700650 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800651 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700652 return;
653 }
Craig Mautner411d2aed2014-05-08 09:07:43 -0700654 final Intent in = (Intent) pa[i];
Adam Powelle49d9392014-07-17 18:45:19 -0700655 modifyTargetIntent(in);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700656 initialIntents[i] = in;
Dianne Hackborneb034652009-09-07 00:49:58 -0700657 }
658 }
Adam Powell24428412015-04-01 17:19:56 -0700659
Adam Powell13036be2015-05-12 14:43:56 -0700660 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
661
Adam Powell0b3c1122014-10-09 12:50:14 -0700662 mChosenComponentSender = intent.getParcelableExtra(
663 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
Adam Powell2ed547e2015-04-29 18:45:04 -0700664 mRefinementIntentSender = intent.getParcelableExtra(
665 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
Dianne Hackborn028ceeb2014-08-17 17:45:48 -0700666 setSafeForwardingMode(true);
Adam Powell23882512016-01-29 10:21:00 -0800667
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500668 mPinnedSharedPrefs = getPinnedSharedPrefs(this);
669
Adam Powell52c39212016-04-07 15:14:18 -0700670 pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
671 if (pa != null) {
672 ComponentName[] names = new ComponentName[pa.length];
673 for (int i = 0; i < pa.length; i++) {
674 if (!(pa[i] instanceof ComponentName)) {
675 Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
676 names = null;
677 break;
678 }
679 names[i] = (ComponentName) pa[i];
680 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800681 mFilteredComponentNames = names;
Adam Powell52c39212016-04-07 15:14:18 -0700682 }
683
684 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
685 if (pa != null) {
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400686 int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS);
687 ChooserTarget[] targets = new ChooserTarget[count];
688 for (int i = 0; i < count; i++) {
Adam Powell52c39212016-04-07 15:14:18 -0700689 if (!(pa[i] instanceof ChooserTarget)) {
690 Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
691 targets = null;
692 break;
693 }
694 targets[i] = (ChooserTarget) pa[i];
695 }
696 mCallerChooserTargets = targets;
697 }
698
Jorim Jaggif631ef72017-02-24 13:49:47 +0100699 setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
Adam Powell278902c2014-07-12 18:33:22 -0700700 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
701 null, false);
Adam Powell98b7f892015-06-19 12:38:45 -0700702
Kang Li9082f5b2016-12-02 10:56:21 -0800703 mChooserShownTime = System.currentTimeMillis();
704 final long systemCost = mChooserShownTime - intentReceivedTime;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500705
706 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500707 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
708 MetricsEvent.PARENT_PROFILE)
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500709 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
710 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
George Hodulik69d4a082019-01-18 11:27:03 -0800711
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500712 mChooserRowServiceSpacing = getResources()
713 .getDimensionPixelSize(R.dimen.chooser_service_spacing);
714
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400715 if (mResolverDrawerLayout != null) {
716 mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
717
718 // expand/shrink direct share 4 -> 8 viewgroup
719 if (isSendAction(target)) {
720 mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
721 }
Matt Pietalb1d629d2019-04-23 11:35:53 -0400722
Mike Digman849a9d12019-04-29 11:20:48 -0700723 mResolverDrawerLayout.setOnCollapsedChangedListener(
724 new ResolverDrawerLayout.OnCollapsedChangedListener() {
725
726 // Only consider one expansion per activity creation
727 private boolean mWrittenOnce = false;
728
729 @Override
730 public void onCollapsedChanged(boolean isCollapsed) {
731 if (!isCollapsed && !mWrittenOnce) {
732 incrementNumSheetExpansions();
733 mWrittenOnce = true;
734 }
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500735 getChooserActivityLogger()
736 .logSharesheetExpansionChanged(isCollapsed);
Mike Digman849a9d12019-04-29 11:20:48 -0700737 }
738 });
Matt Pietal5b648562019-03-12 07:40:26 -0400739 }
740
Kang Li9082f5b2016-12-02 10:56:21 -0800741 if (DEBUG) {
742 Log.d(TAG, "System Time Cost is " + systemCost);
743 }
arangelov5fc9e7d2020-01-07 17:59:14 +0000744
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500745 getChooserActivityLogger().logShareStarted(
746 FrameworkStatsLog.SHARESHEET_STARTED,
747 getReferrerPackageName(),
748 target.getType(),
749 initialIntents == null ? 0 : initialIntents.length,
750 mCallerChooserTargets == null ? 0 : mCallerChooserTargets.length,
751 isWorkProfile(),
752 findPreferredContentPreview(getTargetIntent(), getContentResolver()),
753 target.getAction()
754 );
arangelov5fc9e7d2020-01-07 17:59:14 +0000755 mDirectShareShortcutInfoCache = new HashMap<>();
756 }
757
758 private AppPredictor setupAppPredictorForUser(UserHandle userHandle,
759 AppPredictor.Callback appPredictorCallback) {
760 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
761 if (appPredictor == null) {
762 return null;
763 }
764 mDirectShareAppTargetCache = new HashMap<>();
765 appPredictor.registerPredictionUpdates(this.getMainExecutor(), appPredictorCallback);
766 return appPredictor;
767 }
768
769 private AppPredictor.Callback createAppPredictorCallback(
770 ChooserListAdapter chooserListAdapter) {
771 return resultList -> {
772 //TODO(arangelov) Take care of edge case when callback called after swiping tabs
773 if (isFinishing() || isDestroyed()) {
774 return;
775 }
776 if (chooserListAdapter.getCount() == 0) {
777 return;
778 }
779 if (resultList.isEmpty()) {
780 // APS may be disabled, so try querying targets ourselves.
781 //TODO(arangelov) queryDirectShareTargets indirectly uses mIntents.
782 // Investigate implications for work tab.
783 queryDirectShareTargets(chooserListAdapter, true);
784 return;
785 }
786 final List<DisplayResolveInfo> driList =
787 getDisplayResolveInfos(chooserListAdapter);
788 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
789 new ArrayList<>();
790 for (AppTarget appTarget : resultList) {
791 if (appTarget.getShortcutInfo() == null) {
792 continue;
793 }
794 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
795 appTarget.getShortcutInfo(),
796 new ComponentName(
797 appTarget.getPackageName(), appTarget.getClassName())));
798 }
799 sendShareShortcutInfoList(shareShortcutInfos, driList, resultList,
800 chooserListAdapter.getUserHandle());
801 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 }
Adam Powelle49d9392014-07-17 18:45:19 -0700803
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500804 static SharedPreferences getPinnedSharedPrefs(Context context) {
805 // The code below is because in the android:ui process, no one can hear you scream.
806 // The package info in the context isn't initialized in the way it is for normal apps,
807 // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
808 // build the path manually below using the same policy that appears in ContextImpl.
809 // This fails silently under the hood if there's a problem, so if we find ourselves in
810 // the case where we don't have access to credential encrypted storage we just won't
811 // have our pinned target info.
812 final File prefsFile = new File(new File(
813 Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
814 context.getUserId(), context.getPackageName()),
815 "shared_prefs"),
816 PINNED_SHARED_PREFS_NAME + ".xml");
817 return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
818 }
819
arangelov38a6fce2019-12-02 18:21:22 +0000820 @Override
821 protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
822 Intent[] initialIntents,
823 List<ResolveInfo> rList,
824 boolean filterLastUsed) {
arangelovf163a882020-02-18 17:18:47 +0000825 if (shouldShowTabs()) {
arangelov38a6fce2019-12-02 18:21:22 +0000826 mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles(
827 initialIntents, rList, filterLastUsed);
828 } else {
829 mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile(
830 initialIntents, rList, filterLastUsed);
831 }
832 return mChooserMultiProfilePagerAdapter;
833 }
834
835 private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
836 Intent[] initialIntents,
837 List<ResolveInfo> rList,
838 boolean filterLastUsed) {
839 ChooserGridAdapter adapter = createChooserGridAdapter(
840 /* context */ this,
841 /* payloadIntents */ mIntents,
842 initialIntents,
843 rList,
844 filterLastUsed,
845 mUseLayoutForBrowsables,
846 /* userHandle */ UserHandle.of(UserHandle.myUserId()));
847 return new ChooserMultiProfilePagerAdapter(
848 /* context */ this,
arangelov7981b122020-01-16 10:58:27 +0000849 adapter,
850 getPersonalProfileUserHandle(),
851 /* workProfileUserHandle= */ null);
arangelov38a6fce2019-12-02 18:21:22 +0000852 }
853
854 private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
855 Intent[] initialIntents,
856 List<ResolveInfo> rList,
857 boolean filterLastUsed) {
858 ChooserGridAdapter personalAdapter = createChooserGridAdapter(
859 /* context */ this,
860 /* payloadIntents */ mIntents,
861 initialIntents,
862 rList,
863 filterLastUsed,
864 mUseLayoutForBrowsables,
865 /* userHandle */ getPersonalProfileUserHandle());
866 ChooserGridAdapter workAdapter = createChooserGridAdapter(
867 /* context */ this,
868 /* payloadIntents */ mIntents,
869 initialIntents,
870 rList,
871 filterLastUsed,
872 mUseLayoutForBrowsables,
873 /* userHandle */ getWorkProfileUserHandle());
arangelovc7581392020-04-07 17:10:36 +0100874 int selectedProfile = findSelectedProfile();
arangelov38a6fce2019-12-02 18:21:22 +0000875 return new ChooserMultiProfilePagerAdapter(
876 /* context */ this,
877 personalAdapter,
878 workAdapter,
arangelovc7581392020-04-07 17:10:36 +0100879 selectedProfile,
arangelov7981b122020-01-16 10:58:27 +0000880 getPersonalProfileUserHandle(),
881 getWorkProfileUserHandle());
arangelov38a6fce2019-12-02 18:21:22 +0000882 }
883
arangelovc7581392020-04-07 17:10:36 +0100884 private int findSelectedProfile() {
885 int selectedProfile;
886 if (getIntent().hasExtra(EXTRA_SELECTED_PROFILE)) {
887 selectedProfile = getIntent().getIntExtra(EXTRA_SELECTED_PROFILE, /* defValue = */ -1);
888 if (selectedProfile != PROFILE_PERSONAL && selectedProfile != PROFILE_WORK) {
889 throw new IllegalArgumentException(EXTRA_SELECTED_PROFILE + " has invalid value "
890 + selectedProfile + ". Must be either ChooserActivity.PROFILE_PERSONAL or "
891 + "ChooserActivity.PROFILE_WORK.");
892 }
893 } else {
894 selectedProfile = getProfileForUser(getUser());
895 }
896 return selectedProfile;
897 }
898
arangelov38a6fce2019-12-02 18:21:22 +0000899 @Override
900 protected boolean postRebuildList(boolean rebuildCompleted) {
arangelov590fba32020-02-11 18:05:42 +0000901 updateStickyContentPreview();
902 if (shouldShowStickyContentPreview()
903 || mChooserMultiProfilePagerAdapter
904 .getCurrentRootAdapter().getContentPreviewRowCount() != 0) {
905 logActionShareWithPreview();
906 }
arangelov38a6fce2019-12-02 18:21:22 +0000907 return postRebuildListInternal(rebuildCompleted);
908 }
909
Matt Pietal26038402019-01-08 07:29:34 -0500910 /**
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700911 * Returns true if app prediction service is defined and the component exists on device.
912 */
Mehdi Alizadehe870e972019-09-11 17:54:15 -0700913 @VisibleForTesting
914 public boolean isAppPredictionServiceAvailable() {
915 if (getPackageManager().getAppPredictionServicePackageName() == null) {
916 // Default AppPredictionService is not defined.
917 return false;
918 }
919
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700920 final String appPredictionServiceName =
921 getString(R.string.config_defaultAppPredictionService);
922 if (appPredictionServiceName == null) {
923 return false;
924 }
925 final ComponentName appPredictionComponentName =
926 ComponentName.unflattenFromString(appPredictionServiceName);
927 if (appPredictionComponentName == null) {
928 return false;
929 }
930
931 // Check if the app prediction component actually exists on the device.
932 Intent intent = new Intent();
933 intent.setComponent(appPredictionComponentName);
934 if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) {
935 Log.e(TAG, "App prediction service is defined, but does not exist: "
936 + appPredictionServiceName);
937 return false;
938 }
939 return true;
940 }
941
942 /**
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500943 * Check if the profile currently used is a work profile.
944 * @return true if it is work profile, false if it is parent profile (or no work profile is
945 * set up)
946 */
947 protected boolean isWorkProfile() {
arangelov38a6fce2019-12-02 18:21:22 +0000948 return getSystemService(UserManager.class)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500949 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
950 }
951
Matt Pietalab73a882019-06-05 07:04:55 -0400952 @Override
arangelov7981b122020-01-16 10:58:27 +0000953 protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) {
Matt Pietalab73a882019-06-05 07:04:55 -0400954 return new PackageMonitor() {
955 @Override
956 public void onSomePackagesChanged() {
arangelov7981b122020-01-16 10:58:27 +0000957 handlePackagesChanged(listAdapter);
Matt Pietalab73a882019-06-05 07:04:55 -0400958 }
959 };
960 }
961
Alison Cichowlasbc290812019-12-17 19:42:29 -0500962 /**
963 * Update UI to reflect changes in data.
964 */
965 public void handlePackagesChanged() {
arangelov7981b122020-01-16 10:58:27 +0000966 handlePackagesChanged(/* listAdapter */ null);
967 }
968
969 /**
970 * Update UI to reflect changes in data.
971 * <p>If {@code listAdapter} is {@code null}, both profile list adapters are updated.
972 */
973 private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) {
974 if (listAdapter == null) {
975 mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
976 } else {
977 listAdapter.handlePackagesChanged();
978 }
Alison Cichowlasbc290812019-12-17 19:42:29 -0500979 updateProfileViewButton();
980 }
981
Matt Pietal46d828c2019-02-05 08:07:07 -0500982 private void onCopyButtonClicked(View v) {
983 Intent targetIntent = getTargetIntent();
984 if (targetIntent == null) {
985 finish();
986 } else {
987 final String action = targetIntent.getAction();
988
989 ClipData clipData = null;
990 if (Intent.ACTION_SEND.equals(action)) {
991 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
992 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
993
994 if (extraText != null) {
995 clipData = ClipData.newPlainText(null, extraText);
996 } else if (extraStream != null) {
997 clipData = ClipData.newUri(getContentResolver(), null, extraStream);
998 } else {
999 Log.w(TAG, "No data available to copy to clipboard");
1000 return;
1001 }
1002 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1003 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
1004 Intent.EXTRA_STREAM);
1005 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
1006 for (int i = 1; i < streams.size(); i++) {
1007 clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
1008 }
1009 } else {
1010 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
1011 // so warn about unexpected action
1012 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
1013 return;
1014 }
1015
1016 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
1017 Context.CLIPBOARD_SERVICE);
1018 clipboardManager.setPrimaryClip(clipData);
1019 Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
1020
Alison Cichowlasaa7f79f2019-09-12 15:57:26 -04001021 // Log share completion via copy
1022 LogMaker targetLogMaker = new LogMaker(
1023 MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
1024 getMetricsLogger().write(targetLogMaker);
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05001025 getChooserActivityLogger().logShareTargetSelected(
1026 SELECTION_TYPE_COPY,
1027 "",
1028 -1);
Alison Cichowlasaa7f79f2019-09-12 15:57:26 -04001029
Matt Pietal46d828c2019-02-05 08:07:07 -05001030 finish();
1031 }
1032 }
1033
Matt Pietal18bbd822019-02-12 15:21:36 -05001034 @Override
1035 public void onConfigurationChanged(Configuration newConfig) {
1036 super.onConfigurationChanged(newConfig);
1037
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001038 adjustPreviewWidth(newConfig.orientation, null);
arangelovc4dbdbd2020-02-18 20:54:16 +00001039 updateStickyContentPreview();
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001040 }
1041
1042 private boolean shouldDisplayLandscape(int orientation) {
1043 // Sharesheet fixes the # of items per row and therefore can not correctly lay out
1044 // when in the restricted size of multi-window mode. In the future, would be nice
1045 // to use minimum dp size requirements instead
1046 return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
1047 }
1048
1049 private void adjustPreviewWidth(int orientation, View parent) {
Matt Pietal18bbd822019-02-12 15:21:36 -05001050 int width = -1;
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001051 if (shouldDisplayLandscape(orientation)) {
Matt Pietal18bbd822019-02-12 15:21:36 -05001052 width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
1053 }
1054
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001055 parent = parent == null ? getWindow().getDecorView() : parent;
1056
1057 updateLayoutWidth(R.id.content_preview_text_layout, width, parent);
1058 updateLayoutWidth(R.id.content_preview_title_layout, width, parent);
1059 updateLayoutWidth(R.id.content_preview_file_layout, width, parent);
Matt Pietal18bbd822019-02-12 15:21:36 -05001060 }
1061
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001062 private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
1063 View view = parent.findViewById(layoutResourceId);
Matt Pietal1ef88002019-03-13 10:43:18 -04001064 if (view != null && view.getLayoutParams() != null) {
1065 LayoutParams params = view.getLayoutParams();
1066 params.width = width;
1067 view.setLayoutParams(params);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001068 }
1069 }
1070
arangelova3912cf2019-12-13 14:34:45 +00001071 private ViewGroup createContentPreviewView(ViewGroup parent) {
1072 Intent targetIntent = getTargetIntent();
1073 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
1074 return displayContentPreview(previewType, targetIntent, getLayoutInflater(), parent);
1075 }
1076
Dan Sandlere3d19932019-11-22 11:58:58 -05001077 private ComponentName getNearbySharingComponent() {
1078 String nearbyComponent = Settings.Secure.getString(
1079 getContentResolver(),
1080 Settings.Secure.NEARBY_SHARING_COMPONENT);
1081 if (TextUtils.isEmpty(nearbyComponent)) {
1082 nearbyComponent = getString(R.string.config_defaultNearbySharingComponent);
1083 }
1084 if (TextUtils.isEmpty(nearbyComponent)) {
1085 return null;
1086 }
1087 return ComponentName.unflattenFromString(nearbyComponent);
1088 }
1089
1090 private TargetInfo getNearbySharingTarget(Intent originalIntent) {
1091 final ComponentName cn = getNearbySharingComponent();
1092 if (cn == null) return null;
1093
1094 final Intent resolveIntent = new Intent();
1095 resolveIntent.setComponent(cn);
1096 final ResolveInfo ri = getPackageManager().resolveActivity(
1097 resolveIntent, PackageManager.GET_META_DATA);
1098 if (ri == null || ri.activityInfo == null) {
1099 Log.e(TAG, "Device-specified nearby sharing component (" + cn
1100 + ") not available");
1101 return null;
1102 }
1103
1104 // Allow the nearby sharing component to provide a more appropriate icon and label
1105 // for the chip.
1106 CharSequence name = null;
1107 Drawable icon = null;
1108 final Bundle metaData = ri.activityInfo.metaData;
1109 if (metaData != null) {
1110 try {
1111 final Resources pkgRes = getPackageManager().getResourcesForActivity(cn);
1112 final int nameResId = metaData.getInt(CHIP_LABEL_METADATA_KEY);
1113 name = pkgRes.getString(nameResId);
1114 final int resId = metaData.getInt(CHIP_ICON_METADATA_KEY);
1115 icon = pkgRes.getDrawable(resId);
1116 } catch (Resources.NotFoundException ex) {
1117 } catch (NameNotFoundException ex) {
1118 }
1119 }
1120 if (TextUtils.isEmpty(name)) {
1121 name = ri.loadLabel(getPackageManager());
1122 }
1123 if (icon == null) {
1124 icon = ri.loadIcon(getPackageManager());
1125 }
1126
1127 final DisplayResolveInfo dri = new DisplayResolveInfo(
1128 originalIntent, ri, name, "", resolveIntent, null);
1129 dri.setDisplayIcon(icon);
1130 return dri;
1131 }
1132
1133 private Button createActionButton(Drawable icon, CharSequence title, View.OnClickListener r) {
1134 Button b = (Button) LayoutInflater.from(this).inflate(R.layout.chooser_action_button, null);
1135 if (icon != null) {
1136 final int size = getResources()
1137 .getDimensionPixelSize(R.dimen.chooser_action_button_icon_size);
1138 icon.setBounds(0, 0, size, size);
1139 b.setCompoundDrawablesRelative(icon, null, null, null);
1140 }
1141 b.setText(title);
1142 b.setOnClickListener(r);
1143 return b;
1144 }
1145
1146 private Button createCopyButton() {
1147 final Button b = createActionButton(
1148 getDrawable(R.drawable.ic_menu_copy_material),
1149 getString(R.string.copy), this::onCopyButtonClicked);
1150 b.setId(R.id.chooser_copy_button);
1151 return b;
1152 }
1153
1154 private @Nullable Button createNearbyButton(Intent originalIntent) {
1155 final TargetInfo ti = getNearbySharingTarget(originalIntent);
1156 if (ti == null) return null;
1157
1158 return createActionButton(
1159 ti.getDisplayIcon(this),
1160 ti.getDisplayLabel(),
1161 (View unused) -> {
1162 safelyStartActivity(ti);
1163 finish();
1164 }
1165 );
1166 }
1167
1168 private void addActionButton(ViewGroup parent, Button b) {
1169 if (b == null) return;
1170 final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(
1171 LayoutParams.WRAP_CONTENT,
1172 LayoutParams.WRAP_CONTENT
1173 );
1174 final int gap = getResources().getDimensionPixelSize(R.dimen.resolver_icon_margin) / 2;
1175 lp.setMarginsRelative(gap, 0, gap, 0);
1176 parent.addView(b, lp);
1177 }
1178
Matt Pietal1ef88002019-03-13 10:43:18 -04001179 private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
Zhen Zhangbde7b462019-11-11 11:49:33 -08001180 Intent targetIntent, LayoutInflater layoutInflater, ViewGroup parent) {
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001181 ViewGroup layout = null;
1182
Matt Pietal1ef88002019-03-13 10:43:18 -04001183 switch (previewType) {
1184 case CONTENT_PREVIEW_TEXT:
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001185 layout = displayTextContentPreview(targetIntent, layoutInflater, parent);
1186 break;
Matt Pietal1ef88002019-03-13 10:43:18 -04001187 case CONTENT_PREVIEW_IMAGE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001188 layout = displayImageContentPreview(targetIntent, layoutInflater, parent);
1189 break;
Matt Pietal1ef88002019-03-13 10:43:18 -04001190 case CONTENT_PREVIEW_FILE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001191 layout = displayFileContentPreview(targetIntent, layoutInflater, parent);
1192 break;
Matt Pietal1ef88002019-03-13 10:43:18 -04001193 default:
1194 Log.e(TAG, "Unexpected content preview type: " + previewType);
1195 }
Matt Pietal0ea391b2019-01-30 10:44:15 -05001196
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001197 if (layout != null) {
1198 adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
1199 }
1200
1201 return layout;
Matt Pietal1ef88002019-03-13 10:43:18 -04001202 }
1203
1204 private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001205 ViewGroup parent) {
1206 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1207 R.layout.chooser_grid_preview_text, parent, false);
Matt Pietal1ef88002019-03-13 10:43:18 -04001208
Dan Sandlere3d19932019-11-22 11:58:58 -05001209 final ViewGroup actionRow =
1210 (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
1211 addActionButton(actionRow, createCopyButton());
1212 addActionButton(actionRow, createNearbyButton(targetIntent));
Matt Pietal46d828c2019-02-05 08:07:07 -05001213
Matt Pietal26038402019-01-08 07:29:34 -05001214 CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
Matt Pietal26038402019-01-08 07:29:34 -05001215 if (sharingText == null) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001216 contentPreviewLayout.findViewById(R.id.content_preview_text_layout).setVisibility(
1217 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -05001218 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -04001219 TextView textView = contentPreviewLayout.findViewById(R.id.content_preview_text);
Matt Pietal1fa7d802019-01-30 10:44:15 -05001220 textView.setText(sharingText);
Matt Pietal26038402019-01-08 07:29:34 -05001221 }
1222
1223 String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
Matt Pietal46d828c2019-02-05 08:07:07 -05001224 if (TextUtils.isEmpty(previewTitle)) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001225 contentPreviewLayout.findViewById(R.id.content_preview_title_layout).setVisibility(
1226 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -05001227 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -04001228 TextView previewTitleView = contentPreviewLayout.findViewById(
1229 R.id.content_preview_title);
Matt Pietal26038402019-01-08 07:29:34 -05001230 previewTitleView.setText(previewTitle);
Matt Pietal26038402019-01-08 07:29:34 -05001231
Matt Pietal1fa7d802019-01-30 10:44:15 -05001232 ClipData previewData = targetIntent.getClipData();
1233 Uri previewThumbnail = null;
1234 if (previewData != null) {
1235 if (previewData.getItemCount() > 0) {
1236 ClipData.Item previewDataItem = previewData.getItemAt(0);
1237 previewThumbnail = previewDataItem.getUri();
1238 }
Matt Pietal26038402019-01-08 07:29:34 -05001239 }
Matt Pietal26038402019-01-08 07:29:34 -05001240
Matt Pietal1ef88002019-03-13 10:43:18 -04001241 ImageView previewThumbnailView = contentPreviewLayout.findViewById(
1242 R.id.content_preview_thumbnail);
Matt Pietal1fa7d802019-01-30 10:44:15 -05001243 if (previewThumbnail == null) {
Matt Pietal26038402019-01-08 07:29:34 -05001244 previewThumbnailView.setVisibility(View.GONE);
1245 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -04001246 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
1247 mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
Matt Pietal26038402019-01-08 07:29:34 -05001248 }
1249 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001250
1251 return contentPreviewLayout;
Matt Pietal26038402019-01-08 07:29:34 -05001252 }
1253
Matt Pietal1ef88002019-03-13 10:43:18 -04001254 private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001255 ViewGroup parent) {
1256 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1257 R.layout.chooser_grid_preview_image, parent, false);
1258 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001259
1260 String action = targetIntent.getAction();
1261 if (Intent.ACTION_SEND.equals(action)) {
1262 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietale7cacab2019-05-23 07:21:36 -04001263 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001264 } else {
1265 ContentResolver resolver = getContentResolver();
1266
1267 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1268 List<Uri> imageUris = new ArrayList<>();
1269 for (Uri uri : uris) {
1270 if (isImageType(resolver.getType(uri))) {
1271 imageUris.add(uri);
1272 }
1273 }
1274
1275 if (imageUris.size() == 0) {
1276 Log.i(TAG, "Attempted to display image preview area with zero"
1277 + " available images detected in EXTRA_STREAM list");
Matt Pietal46d828c2019-02-05 08:07:07 -05001278 contentPreviewLayout.setVisibility(View.GONE);
Matt Pietal1ef88002019-03-13 10:43:18 -04001279 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001280 }
1281
Matt Pietale7cacab2019-05-23 07:21:36 -04001282 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001283
1284 if (imageUris.size() == 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001285 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
1286 imageUris.get(1), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001287 } else if (imageUris.size() > 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001288 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
1289 imageUris.get(1), 0);
1290 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
1291 imageUris.get(2), imageUris.size() - 3);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001292 }
1293 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001294
1295 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001296 }
1297
Matt Pietal46d828c2019-02-05 08:07:07 -05001298 private static class FileInfo {
1299 public final String name;
1300 public final boolean hasThumbnail;
1301
1302 FileInfo(String name, boolean hasThumbnail) {
1303 this.name = name;
1304 this.hasThumbnail = hasThumbnail;
1305 }
1306 }
1307
Matt Pietalf38e9d22019-02-15 10:01:03 -05001308 /**
1309 * Wrapping the ContentResolver call to expose for easier mocking,
1310 * and to avoid mocking Android core classes.
1311 */
1312 @VisibleForTesting
1313 public Cursor queryResolver(ContentResolver resolver, Uri uri) {
1314 return resolver.query(uri, null, null, null, null);
1315 }
1316
Matt Pietal46d828c2019-02-05 08:07:07 -05001317 private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
1318 String fileName = null;
1319 boolean hasThumbnail = false;
Matt Pietal3087bca2019-02-14 12:19:16 -05001320
Matt Pietalf38e9d22019-02-15 10:01:03 -05001321 try (Cursor cursor = queryResolver(resolver, uri)) {
1322 if (cursor != null && cursor.getCount() > 0) {
1323 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
1324 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
1325 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
1326
1327 cursor.moveToFirst();
1328 if (nameIndex != -1) {
1329 fileName = cursor.getString(nameIndex);
1330 } else if (titleIndex != -1) {
1331 fileName = cursor.getString(titleIndex);
1332 }
1333
1334 if (flagsIndex != -1) {
1335 hasThumbnail = (cursor.getInt(flagsIndex)
1336 & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
1337 }
1338 }
Matt Pietal73a873f2019-03-15 08:46:20 -04001339 } catch (SecurityException | NullPointerException e) {
Matt Pietal62532e52019-05-07 09:51:37 -04001340 logContentPreviewWarning(uri);
Matt Pietal3087bca2019-02-14 12:19:16 -05001341 }
1342
Matt Pietal46d828c2019-02-05 08:07:07 -05001343 if (TextUtils.isEmpty(fileName)) {
1344 fileName = uri.getPath();
1345 int index = fileName.lastIndexOf('/');
1346 if (index != -1) {
1347 fileName = fileName.substring(index + 1);
1348 }
1349 }
1350
1351 return new FileInfo(fileName, hasThumbnail);
1352 }
1353
Matt Pietal62532e52019-05-07 09:51:37 -04001354 private void logContentPreviewWarning(Uri uri) {
1355 // The ContentResolver already logs the exception. Log something more informative.
1356 Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
1357 + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
1358 + "and set your Intent's clipData and flags in accordance with that method's "
1359 + "documentation");
1360 }
1361
Matt Pietal1ef88002019-03-13 10:43:18 -04001362 private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001363 ViewGroup parent) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001364
Matt Pietale7cacab2019-05-23 07:21:36 -04001365 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1366 R.layout.chooser_grid_preview_file, parent, false);
Matt Pietal46d828c2019-02-05 08:07:07 -05001367
1368 // TODO(b/120417119): Disable file copy until after moving to sysui,
1369 // due to permissions issues
Dan Sandlere3d19932019-11-22 11:58:58 -05001370 //((ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row))
1371 // .addView(createCopyButton());
Matt Pietal46d828c2019-02-05 08:07:07 -05001372
Matt Pietal3087bca2019-02-14 12:19:16 -05001373 String action = targetIntent.getAction();
1374 if (Intent.ACTION_SEND.equals(action)) {
1375 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietal1ef88002019-03-13 10:43:18 -04001376 loadFileUriIntoView(uri, contentPreviewLayout);
Matt Pietal3087bca2019-02-14 12:19:16 -05001377 } else {
1378 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1379 int uriCount = uris.size();
Matt Pietal46d828c2019-02-05 08:07:07 -05001380
Matt Pietal3087bca2019-02-14 12:19:16 -05001381 if (uriCount == 0) {
1382 contentPreviewLayout.setVisibility(View.GONE);
1383 Log.i(TAG,
1384 "Appears to be no uris available in EXTRA_STREAM, removing "
1385 + "preview area");
Matt Pietal1ef88002019-03-13 10:43:18 -04001386 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001387 } else if (uriCount == 1) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001388 loadFileUriIntoView(uris.get(0), contentPreviewLayout);
Matt Pietal46d828c2019-02-05 08:07:07 -05001389 } else {
Matt Pietal3087bca2019-02-14 12:19:16 -05001390 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
1391 int remUriCount = uriCount - 1;
Matt Pietalacabc572019-02-14 11:02:05 -05001392 String fileName = getResources().getQuantityString(R.plurals.file_count,
Matt Pietal3087bca2019-02-14 12:19:16 -05001393 remUriCount, fileInfo.name, remUriCount);
Matt Pietalacabc572019-02-14 11:02:05 -05001394
Matt Pietal1ef88002019-03-13 10:43:18 -04001395 TextView fileNameView = contentPreviewLayout.findViewById(
1396 R.id.content_preview_filename);
Matt Pietalacabc572019-02-14 11:02:05 -05001397 fileNameView.setText(fileName);
Matt Pietal3087bca2019-02-14 12:19:16 -05001398
Matt Pietale7cacab2019-05-23 07:21:36 -04001399 View thumbnailView = contentPreviewLayout.findViewById(
1400 R.id.content_preview_file_thumbnail);
1401 thumbnailView.setVisibility(View.GONE);
1402
Matt Pietal1ef88002019-03-13 10:43:18 -04001403 ImageView fileIconView = contentPreviewLayout.findViewById(
1404 R.id.content_preview_file_icon);
Matt Pietal46d828c2019-02-05 08:07:07 -05001405 fileIconView.setVisibility(View.VISIBLE);
Matt Pietalacabc572019-02-14 11:02:05 -05001406 fileIconView.setImageResource(R.drawable.ic_file_copy);
Matt Pietal46d828c2019-02-05 08:07:07 -05001407 }
Matt Pietal3087bca2019-02-14 12:19:16 -05001408 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001409
1410 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001411 }
1412
Matt Pietale7cacab2019-05-23 07:21:36 -04001413 private void loadFileUriIntoView(final Uri uri, final View parent) {
Matt Pietal3087bca2019-02-14 12:19:16 -05001414 FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
1415
Matt Pietal1ef88002019-03-13 10:43:18 -04001416 TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
Matt Pietal3087bca2019-02-14 12:19:16 -05001417 fileNameView.setText(fileInfo.name);
1418
1419 if (fileInfo.hasThumbnail) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001420 mPreviewCoord = new ContentPreviewCoordinator(parent, false);
1421 mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
Matt Pietal3087bca2019-02-14 12:19:16 -05001422 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -04001423 View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
1424 thumbnailView.setVisibility(View.GONE);
1425
Matt Pietal1ef88002019-03-13 10:43:18 -04001426 ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
Matt Pietal3087bca2019-02-14 12:19:16 -05001427 fileIconView.setVisibility(View.VISIBLE);
Matt Pietal832cdbf2019-04-05 13:20:31 -04001428 fileIconView.setImageResource(R.drawable.chooser_file_generic);
Matt Pietal46d828c2019-02-05 08:07:07 -05001429 }
Matt Pietal0ea391b2019-01-30 10:44:15 -05001430 }
1431
Matt Pietal0ea391b2019-01-30 10:44:15 -05001432 @VisibleForTesting
1433 protected boolean isImageType(String mimeType) {
1434 return mimeType != null && mimeType.startsWith("image/");
1435 }
1436
1437 @ContentPreviewType
1438 private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
1439 if (uri == null) {
1440 return CONTENT_PREVIEW_TEXT;
1441 }
1442
1443 String mimeType = resolver.getType(uri);
1444 return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
1445 }
1446
1447 /**
1448 * In {@link android.content.Intent#getType}, the app may specify a very general
1449 * mime-type that broadly covers all data being shared, such as {@literal *}/*
1450 * when sending an image and text. We therefore should inspect each item for the
1451 * the preferred type, in order of IMAGE, FILE, TEXT.
1452 */
1453 @ContentPreviewType
1454 private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
1455 String action = targetIntent.getAction();
1456 if (Intent.ACTION_SEND.equals(action)) {
1457 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1458 return findPreferredContentPreview(uri, resolver);
1459 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1460 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1461 if (uris == null || uris.isEmpty()) {
1462 return CONTENT_PREVIEW_TEXT;
1463 }
1464
1465 for (Uri uri : uris) {
Matt Pietal832cdbf2019-04-05 13:20:31 -04001466 // Defaulting to file preview when there are mixed image/file types is
1467 // preferable, as it shows the user the correct number of items being shared
1468 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_FILE) {
1469 return CONTENT_PREVIEW_FILE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001470 }
1471 }
1472
Matt Pietal832cdbf2019-04-05 13:20:31 -04001473 return CONTENT_PREVIEW_IMAGE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001474 }
1475
1476 return CONTENT_PREVIEW_TEXT;
1477 }
1478
Mike Digman849a9d12019-04-29 11:20:48 -07001479 private int getNumSheetExpansions() {
1480 return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0);
1481 }
1482
1483 private void incrementNumSheetExpansions() {
1484 getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS,
1485 getNumSheetExpansions() + 1).apply();
1486 }
1487
Adam Powell0b3c1122014-10-09 12:50:14 -07001488 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07001489 protected void onDestroy() {
1490 super.onDestroy();
1491 if (mRefinementResultReceiver != null) {
1492 mRefinementResultReceiver.destroy();
1493 mRefinementResultReceiver = null;
1494 }
Adam Powell9761ab22015-09-08 17:01:49 -07001495 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -04001496 mChooserHandler.removeAllMessages();
Matt Pietale7cacab2019-05-23 07:21:36 -04001497
1498 if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
1499
arangelov5fc9e7d2020-01-07 17:59:14 +00001500 mChooserMultiProfilePagerAdapter.getActiveListAdapter().destroyAppPredictor();
1501 if (mChooserMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
1502 mChooserMultiProfilePagerAdapter.getInactiveListAdapter().destroyAppPredictor();
George Hodulik69d4a082019-01-18 11:27:03 -08001503 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001504 }
1505
arangelovb0802dc2019-10-18 18:03:44 +01001506 @Override // ResolverListCommunicator
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001507 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
1508 Intent result = defIntent;
Adam Powelle49d9392014-07-17 18:45:19 -07001509 if (mReplacementExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001510 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
Adam Powelle49d9392014-07-17 18:45:19 -07001511 if (replExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001512 result = new Intent(defIntent);
Adam Powelle49d9392014-07-17 18:45:19 -07001513 result.putExtras(replExtras);
Adam Powelle49d9392014-07-17 18:45:19 -07001514 }
1515 }
Nicolas Prevot741abfc2015-08-11 12:03:51 +01001516 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001517 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
1518 result = Intent.createChooser(result,
1519 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
Hakan Seyalioglu7317e8a2016-12-12 16:15:38 -08001520
1521 // Don't auto-launch single intents if the intent is being forwarded. This is done
1522 // because automatically launching a resolving application as a response to the user
1523 // action of switching accounts is pretty unexpected.
1524 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001525 }
1526 return result;
Adam Powelle49d9392014-07-17 18:45:19 -07001527 }
1528
Adam Powell0b3c1122014-10-09 12:50:14 -07001529 @Override
Adam Powell23882512016-01-29 10:21:00 -08001530 public void onActivityStarted(TargetInfo cti) {
Adam Powell0b3c1122014-10-09 12:50:14 -07001531 if (mChosenComponentSender != null) {
Adam Powell24428412015-04-01 17:19:56 -07001532 final ComponentName target = cti.getResolvedComponentName();
Adam Powell0b3c1122014-10-09 12:50:14 -07001533 if (target != null) {
1534 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
1535 try {
1536 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
1537 } catch (IntentSender.SendIntentException e) {
1538 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
1539 + "the chosen component: " + e);
1540 }
1541 }
1542 }
1543 }
1544
Adam Powell24428412015-04-01 17:19:56 -07001545 @Override
arangelovcf268642020-01-15 15:09:51 +00001546 public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
Adam Powell52c39212016-04-07 15:14:18 -07001547 if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
arangelova3912cf2019-12-13 14:34:45 +00001548 mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
arangelov38a6fce2019-12-02 18:21:22 +00001549 /* origTarget */ null,
1550 Lists.newArrayList(mCallerChooserTargets),
arangelov5fc9e7d2020-01-07 17:59:14 +00001551 TARGET_TYPE_DEFAULT,
Song Hue2deffd2020-03-09 15:22:29 -07001552 /* directShareShortcutInfoCache */ null, mServiceConnections);
Adam Powell52c39212016-04-07 15:14:18 -07001553 }
arangelovb0802dc2019-10-18 18:03:44 +01001554 }
1555
1556 @Override
Adam Powell23882512016-01-29 10:21:00 -08001557 public int getLayoutResource() {
Adam Powell7d758002015-05-06 17:49:36 -07001558 return R.layout.chooser_grid;
Adam Powell24428412015-04-01 17:19:56 -07001559 }
1560
arangelovb0802dc2019-10-18 18:03:44 +01001561 @Override // ResolverListCommunicator
Adam Powell23882512016-01-29 10:21:00 -08001562 public boolean shouldGetActivityMetadata() {
Adam Powell24428412015-04-01 17:19:56 -07001563 return true;
1564 }
1565
Adam Powell9761ab22015-09-08 17:01:49 -07001566 @Override
Ben Lin145b0ca2016-10-14 14:23:40 -07001567 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
Hakan Seyalioglu13405c52017-01-31 19:01:31 -08001568 // Note that this is only safe because the Intent handled by the ChooserActivity is
1569 // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
1570 // method can not be replaced in the ResolverActivity whole hog.
Matt Pietala4b30072019-04-04 13:44:36 -04001571 if (!super.shouldAutoLaunchSingleChoice(target)) {
1572 return false;
1573 }
1574
1575 return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
Ben Lin145b0ca2016-10-14 14:23:40 -07001576 }
1577
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001578 void showTargetDetails(TargetInfo ti) {
1579 if (ti == null) {
sanryhuang296ca9e2018-03-31 11:17:13 +08001580 return;
1581 }
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001582 ComponentName name = ti.getResolveInfo().activityInfo.getComponentName();
Alison Cichowlas1fd47152019-11-14 19:50:55 -05001583 boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001584
1585 ResolverTargetActionsDialogFragment f;
1586
1587 // For multiple targets, include info on all targets
1588 if (ti instanceof MultiDisplayResolveInfo) {
1589 MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) ti;
1590 List<CharSequence> labels = new ArrayList<>();
1591
1592 for (TargetInfo innerInfo : mti.getTargets()) {
1593 labels.add(innerInfo.getResolveInfo().loadLabel(getPackageManager()));
1594 }
Alison Cichowlas4d21ff02019-12-19 18:41:52 -05001595 f = new ResolverTargetActionsDialogFragment(mti.getDisplayLabel(), name,
arangelovebf3c052020-03-17 13:11:37 +00001596 mti.getTargets(), labels,
1597 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001598 } else {
1599 f = new ResolverTargetActionsDialogFragment(
arangelovebf3c052020-03-17 13:11:37 +00001600 ti.getResolveInfo().loadLabel(getPackageManager()), name, pinned,
1601 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001602 }
1603
Adam Powell23882512016-01-29 10:21:00 -08001604 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1605 }
1606
Adam Powelle49d9392014-07-17 18:45:19 -07001607 private void modifyTargetIntent(Intent in) {
Matt Pietal95574b02019-03-13 08:12:25 -04001608 if (isSendAction(in)) {
Adam Powelle49d9392014-07-17 18:45:19 -07001609 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
Dianne Hackborn13420f22014-07-18 15:43:56 -07001610 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Adam Powelle49d9392014-07-17 18:45:19 -07001611 }
1612 }
Adam Powell24428412015-04-01 17:19:56 -07001613
Adam Powell2ed547e2015-04-29 18:45:04 -07001614 @Override
1615 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
1616 if (mRefinementIntentSender != null) {
1617 final Intent fillIn = new Intent();
1618 final List<Intent> sourceIntents = target.getAllSourceIntents();
1619 if (!sourceIntents.isEmpty()) {
1620 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
1621 if (sourceIntents.size() > 1) {
1622 final Intent[] alts = new Intent[sourceIntents.size() - 1];
1623 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
1624 alts[i - 1] = sourceIntents.get(i);
1625 }
1626 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
1627 }
1628 if (mRefinementResultReceiver != null) {
1629 mRefinementResultReceiver.destroy();
1630 }
1631 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
1632 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
1633 mRefinementResultReceiver);
1634 try {
1635 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
1636 return false;
1637 } catch (SendIntentException e) {
1638 Log.e(TAG, "Refinement IntentSender failed to send", e);
1639 }
1640 }
1641 }
Kang Li9fa2a2c2017-01-06 13:33:24 -08001642 updateModelAndChooserCounts(target);
Adam Powell2ed547e2015-04-29 18:45:04 -07001643 return super.onTargetSelected(target, alwaysCheck);
1644 }
1645
Adam Powell98b7f892015-06-19 12:38:45 -07001646 @Override
Adam Powell23882512016-01-29 10:21:00 -08001647 public void startSelected(int which, boolean always, boolean filtered) {
arangelov38a6fce2019-12-02 18:21:22 +00001648 ChooserListAdapter currentListAdapter =
arangelova3912cf2019-12-13 14:34:45 +00001649 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
arangelov38a6fce2019-12-02 18:21:22 +00001650 TargetInfo targetInfo = currentListAdapter
1651 .targetInfoForPosition(which, filtered);
Matt Pietala4b30072019-04-04 13:44:36 -04001652 if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
1653 return;
1654 }
1655
Kang Li9082f5b2016-12-02 10:56:21 -08001656 final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001657
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001658 if (targetInfo instanceof MultiDisplayResolveInfo) {
1659 MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
Alison Cichowlase12616a2019-12-20 19:05:27 -05001660 if (!mti.hasSelected()) {
1661 // Stacked apps get a disambiguation first
1662 CharSequence[] labels = new CharSequence[mti.getTargets().size()];
1663 int i = 0;
1664 for (TargetInfo ti : mti.getTargets()) {
1665 labels[i++] = ti.getResolveInfo().loadLabel(getPackageManager());
1666 }
1667 ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment(
1668 targetInfo.getDisplayLabel(),
1669 ((MultiDisplayResolveInfo) targetInfo), labels, which);
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001670
Alison Cichowlase12616a2019-12-20 19:05:27 -05001671 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1672 return;
1673 }
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001674 }
1675
Adam Powell98b7f892015-06-19 12:38:45 -07001676 super.startSelected(which, always, filtered);
1677
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001678
arangelov38a6fce2019-12-02 18:21:22 +00001679 if (currentListAdapter.getCount() > 0) {
Adam Powell98b7f892015-06-19 12:38:45 -07001680 // Log the index of which type of target the user picked.
1681 // Lower values mean the ranking was better.
1682 int cat = 0;
1683 int value = which;
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001684 int directTargetAlsoRanked = -1;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001685 int numCallerProvided = 0;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001686 HashedStringCache.HashResult directTargetHashed = null;
arangelov38a6fce2019-12-02 18:21:22 +00001687 switch (currentListAdapter.getPositionTargetType(which)) {
Adam Powell98b7f892015-06-19 12:38:45 -07001688 case ChooserListAdapter.TARGET_SERVICE:
Chris Wrenf6e9228b2016-01-26 18:04:35 -05001689 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001690 // Log the package name + target name to answer the question if most users
1691 // share to mostly the same person or to a bunch of different people.
arangelov38a6fce2019-12-02 18:21:22 +00001692 ChooserTarget target = currentListAdapter.getChooserTargetForValue(value);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001693 directTargetHashed = HashedStringCache.getInstance().hashString(
1694 this,
1695 TAG,
1696 target.getComponentName().getPackageName()
1697 + target.getTitle().toString(),
1698 mMaxHashSaltDays);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001699 directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001700
1701 if (mCallerChooserTargets != null) {
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001702 numCallerProvided = mCallerChooserTargets.length;
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001703 }
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05001704 getChooserActivityLogger().logShareTargetSelected(
1705 SELECTION_TYPE_SERVICE,
1706 targetInfo.getResolveInfo().activityInfo.processName,
1707 value
1708 );
Adam Powell98b7f892015-06-19 12:38:45 -07001709 break;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001710 case ChooserListAdapter.TARGET_CALLER:
Adam Powell98b7f892015-06-19 12:38:45 -07001711 case ChooserListAdapter.TARGET_STANDARD:
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001712 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
arangelov38a6fce2019-12-02 18:21:22 +00001713 value -= currentListAdapter.getSelectableServiceTargetCount();
1714 numCallerProvided = currentListAdapter.getCallerTargetCount();
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05001715 getChooserActivityLogger().logShareTargetSelected(
1716 SELECTION_TYPE_APP,
1717 targetInfo.getResolveInfo().activityInfo.processName,
1718 value
1719 );
Adam Powell98b7f892015-06-19 12:38:45 -07001720 break;
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04001721 case ChooserListAdapter.TARGET_STANDARD_AZ:
1722 // A-Z targets are unranked standard targets; we use -1 to mark that they
1723 // are from the alphabetical pool.
1724 value = -1;
1725 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05001726 getChooserActivityLogger().logShareTargetSelected(
1727 SELECTION_TYPE_STANDARD,
1728 targetInfo.getResolveInfo().activityInfo.processName,
1729 value
1730 );
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04001731 break;
Adam Powell98b7f892015-06-19 12:38:45 -07001732 }
1733
1734 if (cat != 0) {
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001735 LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
1736 if (directTargetHashed != null) {
1737 targetLogMaker.addTaggedData(
1738 MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
1739 targetLogMaker.addTaggedData(
1740 MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
1741 directTargetHashed.saltGeneration);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001742 targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
1743 directTargetAlsoRanked);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001744 }
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001745 targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED,
1746 numCallerProvided);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001747 getMetricsLogger().write(targetLogMaker);
Adam Powell98b7f892015-06-19 12:38:45 -07001748 }
Kang Li9082f5b2016-12-02 10:56:21 -08001749
1750 if (mIsSuccessfullySelected) {
1751 if (DEBUG) {
1752 Log.d(TAG, "User Selection Time Cost is " + selectionCost);
1753 Log.d(TAG, "position of selected app/service/caller is " +
1754 Integer.toString(value));
1755 }
1756 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
1757 (int) selectionCost);
1758 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
1759 }
Adam Powell98b7f892015-06-19 12:38:45 -07001760 }
1761 }
1762
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001763 private int getRankedPosition(SelectableTargetInfo targetInfo) {
1764 String targetPackageName =
1765 targetInfo.getChooserTarget().getComponentName().getPackageName();
arangelov38a6fce2019-12-02 18:21:22 +00001766 ChooserListAdapter currentListAdapter =
arangelova3912cf2019-12-13 14:34:45 +00001767 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
arangelov38a6fce2019-12-02 18:21:22 +00001768 int maxRankedResults = Math.min(currentListAdapter.mDisplayList.size(),
1769 MAX_LOG_RANK_POSITION);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001770
1771 for (int i = 0; i < maxRankedResults; i++) {
arangelov38a6fce2019-12-02 18:21:22 +00001772 if (currentListAdapter.mDisplayList.get(i)
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001773 .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
1774 return i;
1775 }
1776 }
1777 return -1;
1778 }
1779
Matt Pietal9236adc2019-12-12 09:24:23 -05001780 @Override
1781 protected boolean shouldAddFooterView() {
1782 // To accommodate for window insets
1783 return true;
1784 }
1785
1786 @Override
1787 protected void applyFooterView(int height) {
1788 int count = mChooserMultiProfilePagerAdapter.getItemCount();
1789
1790 for (int i = 0; i < count; i++) {
1791 mChooserMultiProfilePagerAdapter.getAdapterForIndex(i).setFooterHeight(height);
1792 }
1793 }
1794
Adam Powell24428412015-04-01 17:19:56 -07001795 void queryTargetServices(ChooserListAdapter adapter) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001796 mQueriedTargetServicesTimeMs = System.currentTimeMillis();
1797
arangelov5fc9e7d2020-01-07 17:59:14 +00001798 Context selectedProfileContext = createContextAsUser(
1799 adapter.getUserHandle(), 0 /* flags */);
1800 final PackageManager pm = selectedProfileContext.getPackageManager();
1801 ShortcutManager sm = selectedProfileContext.getSystemService(ShortcutManager.class);
Adam Powell24428412015-04-01 17:19:56 -07001802 int targetsToQuery = 0;
Matt Pietalab73a882019-06-05 07:04:55 -04001803
Adam Powell24428412015-04-01 17:19:56 -07001804 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
1805 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
Adam Powell3a09c522015-10-21 13:21:28 -07001806 if (adapter.getScore(dri) == 0) {
1807 // A score of 0 means the app hasn't been used in some time;
1808 // don't query it as it's not likely to be relevant.
1809 continue;
1810 }
Adam Powell24428412015-04-01 17:19:56 -07001811 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
arangelovb0802dc2019-10-18 18:03:44 +01001812 if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001813 && sm.hasShareTargets(ai.packageName)) {
1814 // Share targets will be queried from ShortcutManager
1815 continue;
1816 }
Adam Powell24428412015-04-01 17:19:56 -07001817 final Bundle md = ai.metaData;
1818 final String serviceName = md != null ? convertServiceName(ai.packageName,
1819 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
1820 if (serviceName != null) {
1821 final ComponentName serviceComponent = new ComponentName(
1822 ai.packageName, serviceName);
Matt Pietalab73a882019-06-05 07:04:55 -04001823
arangelov5fc9e7d2020-01-07 17:59:14 +00001824 UserHandle userHandle = adapter.getUserHandle();
1825 Pair<ComponentName, UserHandle> requestedItem =
1826 new Pair<>(serviceComponent, userHandle);
1827 if (mServicesRequested.contains(requestedItem)) {
Matt Pietalab73a882019-06-05 07:04:55 -04001828 continue;
1829 }
arangelov5fc9e7d2020-01-07 17:59:14 +00001830 mServicesRequested.add(requestedItem);
Matt Pietalab73a882019-06-05 07:04:55 -04001831
Adam Powell24428412015-04-01 17:19:56 -07001832 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
1833 .setComponent(serviceComponent);
1834
1835 if (DEBUG) {
1836 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
1837 }
1838
1839 try {
1840 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
1841 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
1842 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
1843 + " permission " + ChooserTargetService.BIND_PERMISSION
1844 + " - this service will not be queried for ChooserTargets."
1845 + " add android:permission=\""
1846 + ChooserTargetService.BIND_PERMISSION + "\""
1847 + " to the <service> tag for " + serviceComponent
1848 + " in the manifest.");
1849 continue;
1850 }
1851 } catch (NameNotFoundException e) {
Adam Powell52c39212016-04-07 15:14:18 -07001852 Log.e(TAG, "Could not look up service " + serviceComponent
1853 + "; component name not found");
Adam Powell24428412015-04-01 17:19:56 -07001854 continue;
1855 }
1856
Adam Powell9761ab22015-09-08 17:01:49 -07001857 final ChooserTargetServiceConnection conn =
arangelov5fc9e7d2020-01-07 17:59:14 +00001858 new ChooserTargetServiceConnection(this, dri,
1859 adapter.getUserHandle());
Adam Powell52c39212016-04-07 15:14:18 -07001860
arangelov5fc9e7d2020-01-07 17:59:14 +00001861 // Explicitly specify the user handle instead of calling bindService
Adam Powell52c39212016-04-07 15:14:18 -07001862 // to avoid the warning from calling from the system process without an explicit
1863 // user handle
1864 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
arangelov5fc9e7d2020-01-07 17:59:14 +00001865 adapter.getUserHandle())) {
Adam Powell24428412015-04-01 17:19:56 -07001866 if (DEBUG) {
1867 Log.d(TAG, "Binding service connection for target " + dri
1868 + " intent " + serviceIntent);
1869 }
1870 mServiceConnections.add(conn);
1871 targetsToQuery++;
1872 }
1873 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001874 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
Matt Pietal26038402019-01-08 07:29:34 -05001875 if (DEBUG) {
1876 Log.d(TAG, "queryTargets hit query target limit "
1877 + QUERY_TARGET_SERVICE_LIMIT);
1878 }
Adam Powell24428412015-04-01 17:19:56 -07001879 break;
1880 }
1881 }
1882
Matt Pietalab73a882019-06-05 07:04:55 -04001883 mChooserHandler.restartServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -07001884 }
1885
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001886 private IntentFilter getTargetIntentFilter() {
1887 try {
1888 final Intent intent = getTargetIntent();
1889 String dataString = intent.getDataString();
Katsiaryna Naliuka66cd0562020-01-16 18:01:30 +01001890 if (!TextUtils.isEmpty(dataString)) {
1891 return new IntentFilter(intent.getAction(), dataString);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001892 }
Katsiaryna Naliukae2c0b5f2020-02-18 19:26:21 +01001893 if (intent.getType() == null) {
1894 Log.e(TAG, "Failed to get target intent filter: intent data and type are null");
1895 return null;
1896 }
Katsiaryna Naliuka66cd0562020-01-16 18:01:30 +01001897 IntentFilter intentFilter = new IntentFilter(intent.getAction(), intent.getType());
1898 List<Uri> contentUris = new ArrayList<>();
1899 if (Intent.ACTION_SEND.equals(intent.getAction())) {
1900 Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
1901 if (uri != null) {
1902 contentUris.add(uri);
1903 }
1904 } else {
1905 List<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1906 if (uris != null) {
1907 contentUris.addAll(uris);
1908 }
1909 }
1910 for (Uri uri : contentUris) {
1911 intentFilter.addDataScheme(uri.getScheme());
1912 intentFilter.addDataAuthority(uri.getAuthority(), null);
1913 intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
1914 }
1915 return intentFilter;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001916 } catch (Exception e) {
Katsiaryna Naliukae2c0b5f2020-02-18 19:26:21 +01001917 Log.e(TAG, "Failed to get target intent filter", e);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001918 return null;
1919 }
1920 }
1921
George Hodulik69d4a082019-01-18 11:27:03 -08001922 private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001923 // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
1924 // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
George Hodulik69d4a082019-01-18 11:27:03 -08001925 List<DisplayResolveInfo> driList = new ArrayList<>();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001926 int targetsToQuery = 0;
1927 for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
1928 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
1929 if (adapter.getScore(dri) == 0) {
1930 // A score of 0 means the app hasn't been used in some time;
1931 // don't query it as it's not likely to be relevant.
1932 continue;
1933 }
1934 driList.add(dri);
1935 targetsToQuery++;
1936 // TODO(b/121287224): Do we need this here? (similar to queryTargetServices)
1937 if (targetsToQuery >= SHARE_TARGET_QUERY_PACKAGE_LIMIT) {
1938 if (DEBUG) {
1939 Log.d(TAG, "queryTargets hit query target limit "
1940 + SHARE_TARGET_QUERY_PACKAGE_LIMIT);
1941 }
1942 break;
1943 }
1944 }
George Hodulik69d4a082019-01-18 11:27:03 -08001945 return driList;
1946 }
1947
George Hodulik3f399f22019-04-26 16:17:54 -07001948 private void queryDirectShareTargets(
1949 ChooserListAdapter adapter, boolean skipAppPredictionService) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001950 mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
arangelov5fc9e7d2020-01-07 17:59:14 +00001951 UserHandle userHandle = adapter.getUserHandle();
George Hodulik3f399f22019-04-26 16:17:54 -07001952 if (!skipAppPredictionService) {
arangelov5fc9e7d2020-01-07 17:59:14 +00001953 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
George Hodulik3f399f22019-04-26 16:17:54 -07001954 if (appPredictor != null) {
1955 appPredictor.requestPredictionUpdate();
1956 return;
1957 }
George Hodulik69d4a082019-01-18 11:27:03 -08001958 }
George Hodulik145b3a52019-03-27 11:18:43 -07001959 // Default to just querying ShortcutManager if AppPredictor not present.
arangelov38a6fce2019-12-02 18:21:22 +00001960 //TODO(arangelov) we're using mIntents here, investicate possible implications on work tab
George Hodulik69d4a082019-01-18 11:27:03 -08001961 final IntentFilter filter = getTargetIntentFilter();
1962 if (filter == null) {
1963 return;
1964 }
1965 final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001966
1967 AsyncTask.execute(() -> {
arangelov5fc9e7d2020-01-07 17:59:14 +00001968 Context selectedProfileContext = createContextAsUser(userHandle, 0 /* flags */);
1969 ShortcutManager sm = (ShortcutManager) selectedProfileContext
1970 .getSystemService(Context.SHORTCUT_SERVICE);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001971 List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
arangelov5fc9e7d2020-01-07 17:59:14 +00001972 sendShareShortcutInfoList(resultList, driList, null, userHandle);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001973 });
1974 }
1975
George Hodulik69d4a082019-01-18 11:27:03 -08001976 private void sendShareShortcutInfoList(
1977 List<ShortcutManager.ShareShortcutInfo> resultList,
George Hodulikaa5238c2019-04-18 14:17:51 -07001978 List<DisplayResolveInfo> driList,
arangelov5fc9e7d2020-01-07 17:59:14 +00001979 @Nullable List<AppTarget> appTargets, UserHandle userHandle) {
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001980 if (appTargets != null && appTargets.size() != resultList.size()) {
1981 throw new RuntimeException("resultList and appTargets must have the same size."
1982 + " resultList.size()=" + resultList.size()
1983 + " appTargets.size()=" + appTargets.size());
1984 }
1985
1986 for (int i = resultList.size() - 1; i >= 0; i--) {
1987 final String packageName = resultList.get(i).getTargetComponent().getPackageName();
1988 if (!isPackageEnabled(packageName)) {
1989 resultList.remove(i);
1990 if (appTargets != null) {
1991 appTargets.remove(i);
1992 }
1993 }
1994 }
1995
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07001996 // If |appTargets| is not null, results are from AppPredictionService and already sorted.
1997 final int shortcutType = (appTargets == null ? TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER :
1998 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
1999
George Hodulik69d4a082019-01-18 11:27:03 -08002000 // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
2001 // for direct share targets. After ShareSheet is refactored we should use the
2002 // ShareShortcutInfos directly.
2003 boolean resultMessageSent = false;
2004 for (int i = 0; i < driList.size(); i++) {
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002005 List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
George Hodulik69d4a082019-01-18 11:27:03 -08002006 for (int j = 0; j < resultList.size(); j++) {
2007 if (driList.get(i).getResolvedComponentName().equals(
2008 resultList.get(j).getTargetComponent())) {
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002009 matchingShortcuts.add(resultList.get(j));
George Hodulik69d4a082019-01-18 11:27:03 -08002010 }
2011 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002012 if (matchingShortcuts.isEmpty()) {
George Hodulik69d4a082019-01-18 11:27:03 -08002013 continue;
2014 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002015 List<ChooserTarget> chooserTargets = convertToChooserTarget(
2016 matchingShortcuts, resultList, appTargets, shortcutType);
2017
arangelov5fc9e7d2020-01-07 17:59:14 +00002018
2019
George Hodulik69d4a082019-01-18 11:27:03 -08002020 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04002021 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
arangelov5fc9e7d2020-01-07 17:59:14 +00002022 msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null, userHandle);
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07002023 msg.arg1 = shortcutType;
George Hodulik69d4a082019-01-18 11:27:03 -08002024 mChooserHandler.sendMessage(msg);
2025 resultMessageSent = true;
2026 }
2027
2028 if (resultMessageSent) {
George Hodulik145b3a52019-03-27 11:18:43 -07002029 sendShortcutManagerShareTargetResultCompleted();
George Hodulik69d4a082019-01-18 11:27:03 -08002030 }
2031 }
2032
George Hodulik145b3a52019-03-27 11:18:43 -07002033 private void sendShortcutManagerShareTargetResultCompleted() {
2034 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04002035 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
George Hodulik145b3a52019-03-27 11:18:43 -07002036 mChooserHandler.sendMessage(msg);
2037 }
2038
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07002039 private boolean isPackageEnabled(String packageName) {
2040 if (TextUtils.isEmpty(packageName)) {
2041 return false;
2042 }
2043 ApplicationInfo appInfo;
2044 try {
2045 appInfo = getPackageManager().getApplicationInfo(packageName, 0);
2046 } catch (NameNotFoundException e) {
2047 return false;
2048 }
2049
2050 if (appInfo != null && appInfo.enabled
2051 && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
2052 return true;
2053 }
2054 return false;
2055 }
2056
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002057 /**
2058 * Converts a list of ShareShortcutInfos to ChooserTargets.
2059 * @param matchingShortcuts List of shortcuts, all from the same package, that match the current
2060 * share intent filter.
2061 * @param allShortcuts List of all the shortcuts from all the packages on the device that are
2062 * returned for the current sharing action.
2063 * @param allAppTargets List of AppTargets. Null if the results are not from prediction service.
2064 * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or
2065 * TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
2066 * @return A list of ChooserTargets sorted by score in descending order.
2067 */
2068 @VisibleForTesting
2069 @NonNull
2070 public List<ChooserTarget> convertToChooserTarget(
2071 @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts,
2072 @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts,
2073 @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) {
2074 // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted
2075 // list instead of the actual rank value when converting a rank to a score.
2076 List<Integer> scoreList = new ArrayList<>();
2077 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
2078 for (int i = 0; i < matchingShortcuts.size(); i++) {
2079 int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank();
2080 if (!scoreList.contains(shortcutRank)) {
2081 scoreList.add(shortcutRank);
2082 }
2083 }
2084 Collections.sort(scoreList);
2085 }
2086
2087 List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size());
2088 for (int i = 0; i < matchingShortcuts.size(); i++) {
2089 ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo();
2090 int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i));
2091
2092 float score;
2093 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
2094 // Incoming results are ordered. Create a score based on index in the original list.
2095 score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f);
2096 } else {
2097 // Create a score based on the rank of the shortcut.
2098 int rankIndex = scoreList.indexOf(shortcutInfo.getRank());
2099 score = Math.max(1.0f - (0.01f * rankIndex), 0.0f);
2100 }
2101
2102 Bundle extras = new Bundle();
2103 extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
2104 ChooserTarget chooserTarget = new ChooserTarget(shortcutInfo.getShortLabel(),
2105 null, // Icon will be loaded later if this target is selected to be shown.
2106 score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);
2107
2108 chooserTargetList.add(chooserTarget);
2109 if (mDirectShareAppTargetCache != null && allAppTargets != null) {
2110 mDirectShareAppTargetCache.put(chooserTarget,
2111 allAppTargets.get(indexInAllShortcuts));
2112 }
arangelov5fc9e7d2020-01-07 17:59:14 +00002113 if (mDirectShareShortcutInfoCache != null) {
2114 mDirectShareShortcutInfoCache.put(chooserTarget, shortcutInfo);
2115 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002116 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002117 // Sort ChooserTargets by score in descending order
2118 Comparator<ChooserTarget> byScore =
2119 (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore());
2120 Collections.sort(chooserTargetList, byScore);
2121 return chooserTargetList;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002122 }
2123
Adam Powell24428412015-04-01 17:19:56 -07002124 private String convertServiceName(String packageName, String serviceName) {
2125 if (TextUtils.isEmpty(serviceName)) {
2126 return null;
2127 }
2128
2129 final String fullName;
2130 if (serviceName.startsWith(".")) {
2131 // Relative to the app package. Prepend the app package name.
2132 fullName = packageName + serviceName;
2133 } else if (serviceName.indexOf('.') >= 0) {
2134 // Fully qualified package name.
2135 fullName = serviceName;
2136 } else {
2137 fullName = null;
2138 }
2139 return fullName;
2140 }
2141
2142 void unbindRemainingServices() {
2143 if (DEBUG) {
2144 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
2145 }
2146 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
2147 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
2148 if (DEBUG) Log.d(TAG, "unbinding " + conn);
2149 unbindService(conn);
Adam Powell9761ab22015-09-08 17:01:49 -07002150 conn.destroy();
Adam Powell24428412015-04-01 17:19:56 -07002151 }
Matt Pietalab73a882019-06-05 07:04:55 -04002152 mServicesRequested.clear();
Adam Powell24428412015-04-01 17:19:56 -07002153 mServiceConnections.clear();
Adam Powell24428412015-04-01 17:19:56 -07002154 }
2155
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07002156 private void logDirectShareTargetReceived(int logCategory) {
2157 final long queryTime =
2158 logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
2159 ? mQueriedSharingShortcutsTimeMs : mQueriedTargetServicesTimeMs;
2160 final int apiLatency = (int) (System.currentTimeMillis() - queryTime);
2161 getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
2162 }
2163
Kang Li9fa2a2c2017-01-06 13:33:24 -08002164 void updateModelAndChooserCounts(TargetInfo info) {
Kang Li53b43142016-11-14 14:38:25 -08002165 if (info != null) {
George Hodulik145b3a52019-03-27 11:18:43 -07002166 sendClickToAppPredictor(info);
Kang Li53b43142016-11-14 14:38:25 -08002167 final ResolveInfo ri = info.getResolveInfo();
Kang Li64b018e2017-01-05 17:30:06 -08002168 Intent targetIntent = getTargetIntent();
2169 if (ri != null && ri.activityInfo != null && targetIntent != null) {
arangelov38a6fce2019-12-02 18:21:22 +00002170 ChooserListAdapter currentListAdapter =
arangelova3912cf2019-12-13 14:34:45 +00002171 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
arangelov38a6fce2019-12-02 18:21:22 +00002172 if (currentListAdapter != null) {
2173 currentListAdapter.updateModel(info.getResolvedComponentName());
arangelov4872e682020-04-16 15:57:38 +01002174 currentListAdapter.updateChooserCounts(ri.activityInfo.packageName,
Kang Li9fa2a2c2017-01-06 13:33:24 -08002175 targetIntent.getAction());
Kang Li0cef910d2017-01-05 09:14:36 -08002176 }
Kang Li53b43142016-11-14 14:38:25 -08002177 if (DEBUG) {
Kang Li64b018e2017-01-05 17:30:06 -08002178 Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
Kang Li64b018e2017-01-05 17:30:06 -08002179 Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
Kang Li53b43142016-11-14 14:38:25 -08002180 }
George Hodulikf2b0d342019-01-25 12:43:54 -08002181 } else if (DEBUG) {
Kang Li53b43142016-11-14 14:38:25 -08002182 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
2183 }
2184 }
Kang Li9082f5b2016-12-02 10:56:21 -08002185 mIsSuccessfullySelected = true;
Kang Li53b43142016-11-14 14:38:25 -08002186 }
2187
George Hodulikf2b0d342019-01-25 12:43:54 -08002188 private void sendClickToAppPredictor(TargetInfo targetInfo) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002189 AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
2190 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
George Hodulikaa5238c2019-04-18 14:17:51 -07002191 if (directShareAppPredictor == null) {
George Hodulik145b3a52019-03-27 11:18:43 -07002192 return;
2193 }
George Hodulikf2b0d342019-01-25 12:43:54 -08002194 if (!(targetInfo instanceof ChooserTargetInfo)) {
2195 return;
2196 }
2197 ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
George Hodulikaa5238c2019-04-18 14:17:51 -07002198 AppTarget appTarget = null;
2199 if (mDirectShareAppTargetCache != null) {
2200 appTarget = mDirectShareAppTargetCache.get(chooserTarget);
George Hodulikf2b0d342019-01-25 12:43:54 -08002201 }
George Hodulikaa5238c2019-04-18 14:17:51 -07002202 // This is a direct share click that was provided by the APS
2203 if (appTarget != null) {
2204 directShareAppPredictor.notifyAppTargetEvent(
2205 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05002206 .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE)
George Hodulikaa5238c2019-04-18 14:17:51 -07002207 .build());
George Hodulikf2b0d342019-01-25 12:43:54 -08002208 }
George Hodulikf2b0d342019-01-25 12:43:54 -08002209 }
2210
George Hodulik145b3a52019-03-27 11:18:43 -07002211 @Nullable
arangelov5fc9e7d2020-01-07 17:59:14 +00002212 private AppPredictor createAppPredictor(UserHandle userHandle) {
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -07002213 if (!mIsAppPredictorComponentAvailable) {
2214 return null;
2215 }
arangelov5fc9e7d2020-01-07 17:59:14 +00002216
arangelove5b369c2020-03-12 17:36:05 +00002217 if (getPersonalProfileUserHandle().equals(userHandle)) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002218 if (mPersonalAppPredictor != null) {
2219 return mPersonalAppPredictor;
2220 }
2221 } else {
2222 if (mWorkAppPredictor != null) {
2223 return mWorkAppPredictor;
2224 }
George Hodulik145b3a52019-03-27 11:18:43 -07002225 }
arangelov5fc9e7d2020-01-07 17:59:14 +00002226
2227 // TODO(b/148230574): Currently AppPredictor fetches only the same-profile app targets.
2228 // Make AppPredictor work cross-profile.
2229 Context contextAsUser = createContextAsUser(userHandle, 0 /* flags */);
2230 final IntentFilter filter = getTargetIntentFilter();
2231 Bundle extras = new Bundle();
2232 extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
2233 AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(contextAsUser)
2234 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
2235 .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
2236 .setExtras(extras)
2237 .build();
2238 AppPredictionManager appPredictionManager =
2239 contextAsUser
2240 .getSystemService(AppPredictionManager.class);
2241 AppPredictor appPredictionSession = appPredictionManager.createAppPredictionSession(
2242 appPredictionContext);
arangelove5b369c2020-03-12 17:36:05 +00002243 if (getPersonalProfileUserHandle().equals(userHandle)) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002244 mPersonalAppPredictor = appPredictionSession;
2245 } else {
2246 mWorkAppPredictor = appPredictionSession;
2247 }
2248 return appPredictionSession;
George Hodulik145b3a52019-03-27 11:18:43 -07002249 }
2250
2251 /**
2252 * This will return an app predictor if it is enabled for direct share sorting
2253 * and if one exists. Otherwise, it returns null.
arangelov5fc9e7d2020-01-07 17:59:14 +00002254 * @param userHandle
George Hodulik145b3a52019-03-27 11:18:43 -07002255 */
2256 @Nullable
arangelov5fc9e7d2020-01-07 17:59:14 +00002257 private AppPredictor getAppPredictorForDirectShareIfEnabled(UserHandle userHandle) {
arangelovb0802dc2019-10-18 18:03:44 +01002258 return ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS
arangelov5fc9e7d2020-01-07 17:59:14 +00002259 && !ActivityManager.isLowRamDeviceStatic() ? createAppPredictor(userHandle) : null;
George Hodulik145b3a52019-03-27 11:18:43 -07002260 }
2261
George Hodulikc681ce42019-04-12 17:10:31 -07002262 /**
2263 * This will return an app predictor if it is enabled for share activity sorting
2264 * and if one exists. Otherwise, it returns null.
2265 */
2266 @Nullable
arangelov5fc9e7d2020-01-07 17:59:14 +00002267 private AppPredictor getAppPredictorForShareActivitiesIfEnabled(UserHandle userHandle) {
2268 return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? createAppPredictor(userHandle) : null;
George Hodulikc681ce42019-04-12 17:10:31 -07002269 }
2270
Adam Powell2ed547e2015-04-29 18:45:04 -07002271 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
2272 if (mRefinementResultReceiver != null) {
2273 mRefinementResultReceiver.destroy();
2274 mRefinementResultReceiver = null;
2275 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002276 if (selectedTarget == null) {
2277 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
2278 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
2279 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
2280 + " cannot match refined source intent " + matchingIntent);
Kang Li53b43142016-11-14 14:38:25 -08002281 } else {
2282 TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
2283 if (super.onTargetSelected(clonedTarget, false)) {
Kang Li9fa2a2c2017-01-06 13:33:24 -08002284 updateModelAndChooserCounts(clonedTarget);
Kang Li53b43142016-11-14 14:38:25 -08002285 finish();
2286 return;
2287 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002288 }
2289 onRefinementCanceled();
2290 }
2291
2292 void onRefinementCanceled() {
2293 if (mRefinementResultReceiver != null) {
2294 mRefinementResultReceiver.destroy();
2295 mRefinementResultReceiver = null;
2296 }
2297 finish();
2298 }
2299
2300 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
2301 final List<Intent> targetIntents = target.getAllSourceIntents();
2302 for (int i = 0, N = targetIntents.size(); i < N; i++) {
2303 final Intent targetIntent = targetIntents.get(i);
2304 if (targetIntent.filterEquals(matchingIntent)) {
2305 return true;
2306 }
2307 }
2308 return false;
2309 }
2310
arangelov5fc9e7d2020-01-07 17:59:14 +00002311 void filterServiceTargets(Context contextAsUser, String packageName,
2312 List<ChooserTarget> targets) {
Adam Powell666d82a2015-07-15 20:14:57 -07002313 if (targets == null) {
2314 return;
2315 }
2316
arangelov5fc9e7d2020-01-07 17:59:14 +00002317 final PackageManager pm = contextAsUser.getPackageManager();
Adam Powell666d82a2015-07-15 20:14:57 -07002318 for (int i = targets.size() - 1; i >= 0; i--) {
2319 final ChooserTarget target = targets.get(i);
2320 final ComponentName targetName = target.getComponentName();
2321 if (packageName != null && packageName.equals(targetName.getPackageName())) {
2322 // Anything from the original target's package is fine.
2323 continue;
2324 }
2325
2326 boolean remove;
2327 try {
2328 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
2329 remove = !ai.exported || ai.permission != null;
2330 } catch (NameNotFoundException e) {
2331 Log.e(TAG, "Target " + target + " returned by " + packageName
2332 + " component not found");
2333 remove = true;
2334 }
2335
2336 if (remove) {
2337 targets.remove(i);
2338 }
2339 }
2340 }
2341
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002342 /**
2343 * Sort intents alphabetically based on display label.
2344 */
arangelovb0802dc2019-10-18 18:03:44 +01002345 static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002346 Collator mCollator;
2347 AzInfoComparator(Context context) {
2348 mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
2349 }
2350
2351 @Override
arangelovb0802dc2019-10-18 18:03:44 +01002352 public int compare(
2353 DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002354 return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
2355 }
2356 }
2357
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -05002358 protected MetricsLogger getMetricsLogger() {
2359 if (mMetricsLogger == null) {
2360 mMetricsLogger = new MetricsLogger();
2361 }
2362 return mMetricsLogger;
2363 }
2364
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05002365 protected ChooserActivityLogger getChooserActivityLogger() {
2366 if (mChooserActivityLogger == null) {
2367 mChooserActivityLogger = new ChooserActivityLoggerImpl();
2368 }
2369 return mChooserActivityLogger;
2370 }
2371
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002372 public class ChooserListController extends ResolverListController {
2373 public ChooserListController(Context context,
2374 PackageManager pm,
2375 Intent targetIntent,
2376 String referrerPackageName,
George Hodulikc681ce42019-04-12 17:10:31 -07002377 int launchedFromUid,
arangelov38a6fce2019-12-02 18:21:22 +00002378 UserHandle userId,
George Hodulikc681ce42019-04-12 17:10:31 -07002379 AbstractResolverComparator resolverComparator) {
arangelov38a6fce2019-12-02 18:21:22 +00002380 super(context, pm, targetIntent, referrerPackageName, launchedFromUid, userId,
George Hodulikc681ce42019-04-12 17:10:31 -07002381 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002382 }
2383
2384 @Override
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002385 boolean isComponentFiltered(ComponentName name) {
2386 if (mFilteredComponentNames == null) {
2387 return false;
2388 }
2389 for (ComponentName filteredComponentName : mFilteredComponentNames) {
2390 if (name.equals(filteredComponentName)) {
2391 return true;
2392 }
2393 }
2394 return false;
2395 }
Alison Cichowlas1fd47152019-11-14 19:50:55 -05002396
2397 @Override
2398 public boolean isComponentPinned(ComponentName name) {
2399 return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
2400 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002401 }
2402
arangelov38a6fce2019-12-02 18:21:22 +00002403 @VisibleForTesting
2404 public ChooserGridAdapter createChooserGridAdapter(Context context,
2405 List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
2406 boolean filterLastUsed, boolean useLayoutForBrowsables, UserHandle userHandle) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002407 ChooserListAdapter chooserListAdapter = new ChooserListAdapter(context, payloadIntents,
2408 initialIntents, rList,
2409 filterLastUsed, createListController(userHandle), useLayoutForBrowsables,
2410 this, this);
2411 AppPredictor.Callback appPredictorCallback = createAppPredictorCallback(chooserListAdapter);
2412 AppPredictor appPredictor = setupAppPredictorForUser(userHandle, appPredictorCallback);
2413 chooserListAdapter.setAppPredictor(appPredictor);
2414 chooserListAdapter.setAppPredictorCallback(appPredictorCallback);
2415 return new ChooserGridAdapter(chooserListAdapter);
Adam Powell24428412015-04-01 17:19:56 -07002416 }
2417
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002418 @VisibleForTesting
arangelov38a6fce2019-12-02 18:21:22 +00002419 protected ResolverListController createListController(UserHandle userHandle) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002420 AppPredictor appPredictor = getAppPredictorForShareActivitiesIfEnabled(userHandle);
George Hodulikc681ce42019-04-12 17:10:31 -07002421 AbstractResolverComparator resolverComparator;
2422 if (appPredictor != null) {
2423 resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
arangelov5fc9e7d2020-01-07 17:59:14 +00002424 getReferrerPackageName(), appPredictor, userHandle);
George Hodulikc681ce42019-04-12 17:10:31 -07002425 } else {
2426 resolverComparator =
2427 new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
2428 getReferrerPackageName(), null);
2429 }
2430
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002431 return new ChooserListController(
2432 this,
2433 mPm,
2434 getTargetIntent(),
2435 getReferrerPackageName(),
George Hodulikc681ce42019-04-12 17:10:31 -07002436 mLaunchedFromUid,
arangelov38a6fce2019-12-02 18:21:22 +00002437 userHandle,
George Hodulikc681ce42019-04-12 17:10:31 -07002438 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002439 }
2440
Matt Pietal26038402019-01-08 07:29:34 -05002441 @VisibleForTesting
2442 protected Bitmap loadThumbnail(Uri uri, Size size) {
2443 if (uri == null || size == null) {
2444 return null;
2445 }
2446
2447 try {
Alison Cichowlas07acade2019-12-20 11:26:23 -05002448 return getContentResolver().loadThumbnail(uri, size, null);
Matt Pietal46d828c2019-02-05 08:07:07 -05002449 } catch (IOException | NullPointerException | SecurityException ex) {
Matt Pietal62532e52019-05-07 09:51:37 -04002450 logContentPreviewWarning(uri);
Matt Pietal26038402019-01-08 07:29:34 -05002451 }
2452 return null;
2453 }
2454
arangelovb0802dc2019-10-18 18:03:44 +01002455 static final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
2456 public Drawable getDisplayIcon(Context context) {
Mike Digmanac1d88c2019-04-18 15:15:55 -07002457 AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
arangelovb0802dc2019-10-18 18:03:44 +01002458 context.getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
Mike Digmanac1d88c2019-04-18 15:15:55 -07002459 avd.start(); // Start animation after generation
2460 return avd;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002461 }
2462 }
2463
arangelovb0802dc2019-10-18 18:03:44 +01002464 static final class EmptyTargetInfo extends NotSelectableTargetInfo {
2465 public Drawable getDisplayIcon(Context context) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002466 return null;
2467 }
2468 }
2469
Matt Pietal5b648562019-03-12 07:40:26 -04002470 private void handleScroll(View view, int x, int y, int oldx, int oldy) {
arangelov38a6fce2019-12-02 18:21:22 +00002471 if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
2472 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().handleScroll(view, y, oldy);
Matt Pietal5b648562019-03-12 07:40:26 -04002473 }
2474 }
2475
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002476 /*
2477 * Need to dynamically adjust how many icons can fit per row before we add them,
2478 * which also means setting the correct offset to initially show the content
2479 * preview area + 2 rows of targets
2480 */
2481 private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
2482 int oldTop, int oldRight, int oldBottom) {
arangelov38a6fce2019-12-02 18:21:22 +00002483 if (mChooserMultiProfilePagerAdapter == null) {
2484 return;
2485 }
arangelovcf268642020-01-15 15:09:51 +00002486 RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
arangelov38a6fce2019-12-02 18:21:22 +00002487 ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
2488 if (gridAdapter == null || recyclerView == null) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002489 return;
2490 }
2491
Matt Pietalab73a882019-06-05 07:04:55 -04002492 final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
arangelov19e1fea2020-03-10 18:09:39 +00002493 boolean isLayoutUpdated = gridAdapter.consumeLayoutRequest()
arangelov38a6fce2019-12-02 18:21:22 +00002494 || gridAdapter.calculateChooserTargetWidth(availableWidth)
2495 || recyclerView.getAdapter() == null
arangelov19e1fea2020-03-10 18:09:39 +00002496 || availableWidth != mCurrAvailableWidth;
2497 if (isLayoutUpdated
2498 || mLastNumberOfChildren != recyclerView.getChildCount()) {
Matt Pietalab73a882019-06-05 07:04:55 -04002499 mCurrAvailableWidth = availableWidth;
arangelov2731fa72020-03-18 20:24:03 +00002500 if (isLayoutUpdated) {
2501 // It is very important we call setAdapter from here. Otherwise in some cases
2502 // the resolver list doesn't get populated, such as b/150922090, b/150918223
2503 // and b/150936654
2504 recyclerView.setAdapter(gridAdapter);
2505 ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount(
2506 gridAdapter.getMaxTargetsPerRow());
2507 }
2508
arangelovc7581392020-04-07 17:10:36 +01002509 UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle();
2510 int currentProfile = getProfileForUser(currentUserHandle);
2511 int initialProfile = findSelectedProfile();
2512 if (currentProfile != initialProfile) {
arangelov19e1fea2020-03-10 18:09:39 +00002513 return;
arangelov2731fa72020-03-18 20:24:03 +00002514 }
2515
2516 if (mLastNumberOfChildren == recyclerView.getChildCount()) {
arangelova30787b2020-03-11 18:33:26 +00002517 return;
arangelov19e1fea2020-03-10 18:09:39 +00002518 }
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002519
2520 getMainThreadHandler().post(() -> {
arangelov38a6fce2019-12-02 18:21:22 +00002521 if (mResolverDrawerLayout == null || gridAdapter == null) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002522 return;
2523 }
2524
Matt Pietal800136a2019-05-08 07:46:39 -04002525 final int bottomInset = mSystemWindowInsets != null
2526 ? mSystemWindowInsets.bottom : 0;
2527 int offset = bottomInset;
arangelovc4dbdbd2020-02-18 20:54:16 +00002528 int rowsToShow = gridAdapter.getContentPreviewRowCount()
2529 + gridAdapter.getProfileRowCount()
arangelov38a6fce2019-12-02 18:21:22 +00002530 + gridAdapter.getServiceTargetRowCount()
2531 + gridAdapter.getCallerAndRankedTargetRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002532
2533 // then this is most likely not a SEND_* action, so check
2534 // the app target count
2535 if (rowsToShow == 0) {
arangelov38a6fce2019-12-02 18:21:22 +00002536 rowsToShow = gridAdapter.getRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002537 }
2538
2539 // still zero? then use a default height and leave, which
2540 // can happen when there are no targets to show
arangelov590fba32020-02-11 18:05:42 +00002541 if (rowsToShow == 0 && !shouldShowStickyContentPreview()) {
Matt Pietal800136a2019-05-08 07:46:39 -04002542 offset += getResources().getDimensionPixelSize(
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002543 R.dimen.chooser_max_collapsed_height);
2544 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2545 return;
2546 }
2547
arangelovc4dbdbd2020-02-18 20:54:16 +00002548 View stickyContentPreview = findViewById(R.id.content_preview_container);
2549 if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) {
2550 offset += stickyContentPreview.getHeight();
arangelova3912cf2019-12-13 14:34:45 +00002551 }
2552
arangelovf163a882020-02-18 17:18:47 +00002553 if (shouldShowTabs()) {
arangelova69e6762020-02-05 21:25:15 +00002554 offset += findViewById(R.id.tabs).getHeight();
2555 }
2556
arangelov2a4d0b12020-03-04 18:15:04 +00002557 View tabDivider = findViewById(R.id.resolver_tab_divider);
2558 if (tabDivider.getVisibility() == View.VISIBLE) {
2559 offset += tabDivider.getHeight();
2560 }
2561
arangelova30787b2020-03-11 18:33:26 +00002562 if (recyclerView.getVisibility() == View.VISIBLE) {
2563 int directShareHeight = 0;
2564 rowsToShow = Math.min(4, rowsToShow);
2565 mLastNumberOfChildren = recyclerView.getChildCount();
2566 for (int i = 0, childCount = recyclerView.getChildCount();
2567 i < childCount && rowsToShow > 0; i++) {
2568 View child = recyclerView.getChildAt(i);
2569 if (((GridLayoutManager.LayoutParams)
2570 child.getLayoutParams()).getSpanIndex() != 0) {
2571 continue;
2572 }
2573 int height = child.getHeight();
2574 offset += height;
2575
2576 if (gridAdapter.getTargetType(
2577 recyclerView.getChildAdapterPosition(child))
2578 == ChooserListAdapter.TARGET_SERVICE) {
2579 directShareHeight = height;
2580 }
2581 rowsToShow--;
Zhen Zhangbde7b462019-11-11 11:49:33 -08002582 }
Matt Pietal394ebd02019-05-03 07:36:21 -04002583
arangelova30787b2020-03-11 18:33:26 +00002584 boolean isExpandable = getResources().getConfiguration().orientation
2585 == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
2586 if (directShareHeight != 0 && isSendAction(getTargetIntent())
2587 && isExpandable) {
2588 // make sure to leave room for direct share 4->8 expansion
2589 int requiredExpansionHeight =
2590 (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
2591 int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
2592 int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
2593 - requiredExpansionHeight - topInset - bottomInset;
2594
2595 offset = Math.min(offset, minHeight);
Matt Pietal394ebd02019-05-03 07:36:21 -04002596 }
arangelova30787b2020-03-11 18:33:26 +00002597 } else {
2598 ViewGroup currentEmptyStateView = getCurrentEmptyStateView();
2599 if (currentEmptyStateView.getVisibility() == View.VISIBLE) {
2600 offset += currentEmptyStateView.getHeight();
2601 }
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002602 }
2603
Matt Pietal399e8c72019-04-04 15:49:48 -04002604 mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top));
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002605 });
2606 }
2607 }
2608
arangelovc7581392020-04-07 17:10:36 +01002609 /**
2610 * Returns {@link #PROFILE_PERSONAL}, {@link #PROFILE_WORK}, or -1 if the given user handle
2611 * does not match either the personal or work user handle.
2612 **/
2613 private int getProfileForUser(UserHandle currentUserHandle) {
2614 if (currentUserHandle == getPersonalProfileUserHandle()) {
2615 return PROFILE_PERSONAL;
2616 } else if (currentUserHandle == getWorkProfileUserHandle()) {
2617 return PROFILE_WORK;
2618 }
2619 return -1;
2620 }
2621
arangelova30787b2020-03-11 18:33:26 +00002622 private ViewGroup getCurrentEmptyStateView() {
2623 int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage();
2624 return mChooserMultiProfilePagerAdapter.getItem(currentPage).getEmptyStateView();
2625 }
2626
Adam Powella182e452015-07-06 16:57:56 -07002627 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
2628 @Override
2629 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
2630 // Descending order
Adam Powell77a533f2015-10-16 10:47:32 -07002631 return (int) Math.signum(rhs.getScore() - lhs.getScore());
Adam Powella182e452015-07-06 16:57:56 -07002632 }
2633 }
2634
arangelovb0802dc2019-10-18 18:03:44 +01002635 @Override // ResolverListCommunicator
arangelov7981b122020-01-16 10:58:27 +00002636 public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
arangelovb0802dc2019-10-18 18:03:44 +01002637 mServicesRequested.clear();
arangelova3912cf2019-12-13 14:34:45 +00002638 mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged();
arangelov7981b122020-01-16 10:58:27 +00002639 super.onHandlePackagesChanged(listAdapter);
arangelovb0802dc2019-10-18 18:03:44 +01002640 }
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002641
arangelovb0802dc2019-10-18 18:03:44 +01002642 @Override // SelectableTargetInfoCommunicator
2643 public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) {
arangelova3912cf2019-12-13 14:34:45 +00002644 return mChooserMultiProfilePagerAdapter.getActiveListAdapter().makePresentationGetter(info);
arangelovb0802dc2019-10-18 18:03:44 +01002645 }
2646
2647 @Override // SelectableTargetInfoCommunicator
2648 public Intent getReferrerFillInIntent() {
2649 return mReferrerFillInIntent;
2650 }
2651
2652 @Override // ChooserListCommunicator
2653 public int getMaxRankedTargets() {
arangelov38a6fce2019-12-02 18:21:22 +00002654 return mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() == null
Zhen Zhangbde7b462019-11-11 11:49:33 -08002655 ? ChooserGridAdapter.MAX_TARGETS_PER_ROW_PORTRAIT
arangelov38a6fce2019-12-02 18:21:22 +00002656 : mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().getMaxTargetsPerRow();
arangelovb0802dc2019-10-18 18:03:44 +01002657 }
2658
2659 @Override // ChooserListCommunicator
arangelov5fc9e7d2020-01-07 17:59:14 +00002660 public void sendListViewUpdateMessage(UserHandle userHandle) {
2661 Message msg = Message.obtain();
2662 msg.what = ChooserHandler.LIST_VIEW_UPDATE_MESSAGE;
2663 msg.obj = userHandle;
2664 mChooserHandler.sendMessageDelayed(msg, LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
arangelovb0802dc2019-10-18 18:03:44 +01002665 }
2666
2667 @Override
arangelova3912cf2019-12-13 14:34:45 +00002668 public void onListRebuilt(ResolverListAdapter listAdapter) {
arangelovcf268642020-01-15 15:09:51 +00002669 setupScrollListener();
2670
arangelova3912cf2019-12-13 14:34:45 +00002671 ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
arangelovc4dbdbd2020-02-18 20:54:16 +00002672 if (chooserListAdapter.getUserHandle()
arangelove5b369c2020-03-12 17:36:05 +00002673 .equals(mChooserMultiProfilePagerAdapter.getCurrentUserHandle())) {
arangelovc4dbdbd2020-02-18 20:54:16 +00002674 mChooserMultiProfilePagerAdapter.getActiveAdapterView()
2675 .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter());
2676 mChooserMultiProfilePagerAdapter
2677 .setupListAdapter(mChooserMultiProfilePagerAdapter.getCurrentPage());
2678 }
2679
arangelova3912cf2019-12-13 14:34:45 +00002680 if (chooserListAdapter.mDisplayList == null
2681 || chooserListAdapter.mDisplayList.isEmpty()) {
2682 chooserListAdapter.notifyDataSetChanged();
arangelovb0802dc2019-10-18 18:03:44 +01002683 } else {
2684 new AsyncTask<Void, Void, Void>() {
2685 @Override
2686 protected Void doInBackground(Void... voids) {
arangelova3912cf2019-12-13 14:34:45 +00002687 chooserListAdapter.updateAlphabeticalList();
arangelovb0802dc2019-10-18 18:03:44 +01002688 return null;
2689 }
2690 @Override
2691 protected void onPostExecute(Void aVoid) {
arangelova3912cf2019-12-13 14:34:45 +00002692 chooserListAdapter.notifyDataSetChanged();
arangelovb0802dc2019-10-18 18:03:44 +01002693 }
2694 }.execute();
2695 }
2696
2697 // don't support direct share on low ram devices
2698 if (ActivityManager.isLowRamDeviceStatic()) {
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05002699 getChooserActivityLogger().logSharesheetAppLoadComplete();
arangelovb0802dc2019-10-18 18:03:44 +01002700 return;
2701 }
2702
2703 if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
2704 || ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
2705 if (DEBUG) {
2706 Log.d(TAG, "querying direct share targets from ShortcutManager");
2707 }
2708
arangelova3912cf2019-12-13 14:34:45 +00002709 queryDirectShareTargets(chooserListAdapter, false);
arangelovb0802dc2019-10-18 18:03:44 +01002710 }
2711 if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
2712 if (DEBUG) {
2713 Log.d(TAG, "List built querying services");
2714 }
2715
arangelova3912cf2019-12-13 14:34:45 +00002716 queryTargetServices(chooserListAdapter);
arangelovb0802dc2019-10-18 18:03:44 +01002717 }
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05002718
2719 getChooserActivityLogger().logSharesheetAppLoadComplete();
arangelovb0802dc2019-10-18 18:03:44 +01002720 }
2721
arangelovcf268642020-01-15 15:09:51 +00002722 private void setupScrollListener() {
arangelovf163a882020-02-18 17:18:47 +00002723 if (mResolverDrawerLayout == null || shouldShowTabs()) {
arangelovcf268642020-01-15 15:09:51 +00002724 return;
2725 }
2726 final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
2727 final float defaultElevation = chooserHeader.getElevation();
2728 final float chooserHeaderScrollElevation =
2729 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
2730
2731 mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener(
2732 new RecyclerView.OnScrollListener() {
2733 public void onScrollStateChanged(RecyclerView view, int scrollState) {
2734 }
2735
2736 public void onScrolled(RecyclerView view, int dx, int dy) {
2737 if (view.getChildCount() > 0) {
2738 View child = view.getLayoutManager().findViewByPosition(0);
2739 if (child == null || child.getTop() < 0) {
2740 chooserHeader.setElevation(chooserHeaderScrollElevation);
2741 return;
2742 }
2743 }
2744
2745 chooserHeader.setElevation(defaultElevation);
2746 }
2747 });
2748 }
2749
arangelovb0802dc2019-10-18 18:03:44 +01002750 @Override // ChooserListCommunicator
2751 public boolean isSendAction(Intent targetIntent) {
Matt Pietal95574b02019-03-13 08:12:25 -04002752 if (targetIntent == null) {
2753 return false;
2754 }
2755
2756 String action = targetIntent.getAction();
2757 if (action == null) {
2758 return false;
2759 }
2760
2761 if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
2762 return true;
2763 }
2764
2765 return false;
2766 }
2767
arangelov590fba32020-02-11 18:05:42 +00002768 /**
2769 * The sticky content preview is shown only when we have a tabbed view. It's shown above
2770 * the tabs so it is not part of the scrollable list. If we are not in tabbed view,
2771 * we instead show the content preview as a regular list item.
2772 */
2773 private boolean shouldShowStickyContentPreview() {
arangelovc4dbdbd2020-02-18 20:54:16 +00002774 return shouldShowStickyContentPreviewNoOrientationCheck()
arangelovc5fb8392020-03-25 13:30:28 +00002775 && !getResources().getBoolean(R.bool.resolver_landscape_phone);
arangelova3912cf2019-12-13 14:34:45 +00002776 }
2777
arangelovc4dbdbd2020-02-18 20:54:16 +00002778 private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
2779 return shouldShowTabs()
2780 && mMultiProfilePagerAdapter.getListAdapterForUserHandle(
2781 UserHandle.of(UserHandle.myUserId())).getCount() > 0
2782 && isSendAction(getTargetIntent());
2783 }
2784
arangelov590fba32020-02-11 18:05:42 +00002785 private void updateStickyContentPreview() {
arangelovc4dbdbd2020-02-18 20:54:16 +00002786 if (shouldShowStickyContentPreviewNoOrientationCheck()) {
2787 // The sticky content preview is only shown when we show the work and personal tabs.
2788 // We don't show it in landscape as otherwise there is no room for scrolling.
2789 // If the sticky content preview will be shown at some point with orientation change,
2790 // then always preload it to avoid subsequent resizing of the share sheet.
2791 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
2792 if (contentPreviewContainer.getChildCount() == 0) {
2793 ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer);
2794 contentPreviewContainer.addView(contentPreviewView);
2795 }
2796 }
arangelov590fba32020-02-11 18:05:42 +00002797 if (shouldShowStickyContentPreview()) {
2798 showStickyContentPreview();
arangelova3912cf2019-12-13 14:34:45 +00002799 } else {
arangelov590fba32020-02-11 18:05:42 +00002800 hideStickyContentPreview();
arangelova3912cf2019-12-13 14:34:45 +00002801 }
2802 }
2803
arangelov590fba32020-02-11 18:05:42 +00002804 private void showStickyContentPreview() {
arangelovc4dbdbd2020-02-18 20:54:16 +00002805 if (isStickyContentPreviewShowing()) {
2806 return;
2807 }
arangelova3912cf2019-12-13 14:34:45 +00002808 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
2809 contentPreviewContainer.setVisibility(View.VISIBLE);
arangelovc4dbdbd2020-02-18 20:54:16 +00002810 }
2811
2812 private boolean isStickyContentPreviewShowing() {
2813 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
2814 return contentPreviewContainer.getVisibility() == View.VISIBLE;
arangelova3912cf2019-12-13 14:34:45 +00002815 }
2816
arangelov590fba32020-02-11 18:05:42 +00002817 private void hideStickyContentPreview() {
arangelovc4dbdbd2020-02-18 20:54:16 +00002818 if (!isStickyContentPreviewShowing()) {
2819 return;
2820 }
arangelova3912cf2019-12-13 14:34:45 +00002821 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
arangelova3912cf2019-12-13 14:34:45 +00002822 contentPreviewContainer.setVisibility(View.GONE);
2823 }
2824
2825 private void logActionShareWithPreview() {
2826 Intent targetIntent = getTargetIntent();
2827 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
2828 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
2829 .setSubtype(previewType));
2830 }
2831
Zhen Zhangbde7b462019-11-11 11:49:33 -08002832 /**
2833 * Used to bind types of individual item including
2834 * {@link ChooserGridAdapter#VIEW_TYPE_NORMAL},
arangelov590fba32020-02-11 18:05:42 +00002835 * {@link ChooserGridAdapter#VIEW_TYPE_CONTENT_PREVIEW},
Zhen Zhangbde7b462019-11-11 11:49:33 -08002836 * {@link ChooserGridAdapter#VIEW_TYPE_PROFILE},
2837 * and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}.
2838 */
2839 final class ItemViewHolder extends RecyclerView.ViewHolder {
2840 ResolverListAdapter.ViewHolder mWrappedViewHolder;
2841 int mListPosition = ChooserListAdapter.NO_POSITION;
2842
2843 ItemViewHolder(View itemView, boolean isClickable) {
2844 super(itemView);
2845 mWrappedViewHolder = new ResolverListAdapter.ViewHolder(itemView);
2846 if (isClickable) {
2847 itemView.setOnClickListener(v -> startSelected(mListPosition,
2848 false/* always */, true/* filterd */));
2849 itemView.setOnLongClickListener(v -> {
2850 showTargetDetails(
arangelova3912cf2019-12-13 14:34:45 +00002851 mChooserMultiProfilePagerAdapter.getActiveListAdapter()
Alison Cichowlas19ee2922019-12-16 19:43:12 -05002852 .targetInfoForPosition(mListPosition, /* filtered */ true));
Zhen Zhangbde7b462019-11-11 11:49:33 -08002853 return true;
2854 });
2855 }
2856 }
2857 }
2858
2859 /**
Matt Pietal9236adc2019-12-12 09:24:23 -05002860 * Add a footer to the list, to support scrolling behavior below the navbar.
arangelov38a6fce2019-12-02 18:21:22 +00002861 */
Matt Pietal9236adc2019-12-12 09:24:23 -05002862 final class FooterViewHolder extends RecyclerView.ViewHolder {
2863 FooterViewHolder(View itemView) {
2864 super(itemView);
2865 }
arangelov38a6fce2019-12-02 18:21:22 +00002866 }
2867
2868 /**
2869 * Intentionally override the {@link ResolverActivity} implementation as we only need that
2870 * implementation for the intent resolver case.
2871 */
2872 @Override
2873 public void onButtonClick(View v) {}
2874
2875 /**
2876 * Intentionally override the {@link ResolverActivity} implementation as we only need that
2877 * implementation for the intent resolver case.
2878 */
2879 @Override
2880 protected void resetButtonBar() {}
2881
arangelov4bf17472020-02-17 20:21:46 +00002882 @Override
2883 protected String getMetricsCategory() {
2884 return METRICS_CATEGORY_CHOOSER;
2885 }
2886
arangelov38a6fce2019-12-02 18:21:22 +00002887 /**
Zhen Zhangbde7b462019-11-11 11:49:33 -08002888 * Adapter for all types of items and targets in ShareSheet.
2889 * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the
2890 * row level by this adapter but not on the item level. Individual targets within the row are
2891 * handled by {@link ChooserListAdapter}
2892 */
arangelovcf268642020-01-15 15:09:51 +00002893 @VisibleForTesting
2894 public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
Adam Powell7d758002015-05-06 17:49:36 -07002895 private ChooserListAdapter mChooserListAdapter;
2896 private final LayoutInflater mLayoutInflater;
Adam Powell7d758002015-05-06 17:49:36 -07002897
Matt Pietal5b648562019-03-12 07:40:26 -04002898 private DirectShareViewHolder mDirectShareViewHolder;
Matt Pietalab986b52019-04-10 10:14:32 -04002899 private int mChooserTargetWidth = 0;
Mike Digman849a9d12019-04-29 11:20:48 -07002900 private boolean mShowAzLabelIfPoss;
Matt Pietal5b648562019-03-12 07:40:26 -04002901
arangelov590fba32020-02-11 18:05:42 +00002902 private boolean mHideContentPreview = false;
Matt Pietale7cacab2019-05-23 07:21:36 -04002903 private boolean mLayoutRequested = false;
2904
Matt Pietal2025a1b2020-01-07 10:08:07 -05002905 private int mFooterHeight = 0;
Matt Pietal9236adc2019-12-12 09:24:23 -05002906
Matt Pietal5b648562019-03-12 07:40:26 -04002907 private static final int VIEW_TYPE_DIRECT_SHARE = 0;
2908 private static final int VIEW_TYPE_NORMAL = 1;
arangelov590fba32020-02-11 18:05:42 +00002909 private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
2910 private static final int VIEW_TYPE_PROFILE = 3;
2911 private static final int VIEW_TYPE_AZ_LABEL = 4;
2912 private static final int VIEW_TYPE_CALLER_AND_RANK = 5;
2913 private static final int VIEW_TYPE_FOOTER = 6;
Matt Pietal5b648562019-03-12 07:40:26 -04002914
Matt Pietalfaedea82019-03-21 10:36:54 -04002915 private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
2916 private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
2917
Mike Digman849a9d12019-04-29 11:20:48 -07002918 private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
2919
Zhen Zhangbde7b462019-11-11 11:49:33 -08002920 ChooserGridAdapter(ChooserListAdapter wrappedAdapter) {
2921 super();
Adam Powell7d758002015-05-06 17:49:36 -07002922 mChooserListAdapter = wrappedAdapter;
2923 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
2924
Mike Digman849a9d12019-04-29 11:20:48 -07002925 mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
2926
Adam Powell7d758002015-05-06 17:49:36 -07002927 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
2928 @Override
2929 public void onChanged() {
2930 super.onChanged();
2931 notifyDataSetChanged();
2932 }
2933
2934 @Override
2935 public void onInvalidated() {
2936 super.onInvalidated();
Zhen Zhangbde7b462019-11-11 11:49:33 -08002937 notifyDataSetChanged();
Adam Powell7d758002015-05-06 17:49:36 -07002938 }
2939 });
2940 }
2941
Matt Pietal9236adc2019-12-12 09:24:23 -05002942 public void setFooterHeight(int height) {
Matt Pietal2025a1b2020-01-07 10:08:07 -05002943 mFooterHeight = height;
Matt Pietal9236adc2019-12-12 09:24:23 -05002944 }
2945
Matt Pietalfaedea82019-03-21 10:36:54 -04002946 /**
Matt Pietalab986b52019-04-10 10:14:32 -04002947 * Calculate the chooser target width to maximize space per item
Matt Pietalfaedea82019-03-21 10:36:54 -04002948 *
2949 * @param width The new row width to use for recalculation
Matt Pietalab986b52019-04-10 10:14:32 -04002950 * @return true if the view width has changed
Matt Pietalfaedea82019-03-21 10:36:54 -04002951 */
Matt Pietalab986b52019-04-10 10:14:32 -04002952 public boolean calculateChooserTargetWidth(int width) {
Matt Pietalab986b52019-04-10 10:14:32 -04002953 if (width == 0) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002954 return false;
2955 }
2956
Zhen Zhangbde7b462019-11-11 11:49:33 -08002957 int newWidth = width / getMaxTargetsPerRow();
Matt Pietalab986b52019-04-10 10:14:32 -04002958 if (newWidth != mChooserTargetWidth) {
2959 mChooserTargetWidth = newWidth;
Matt Pietalfaedea82019-03-21 10:36:54 -04002960 return true;
2961 }
2962
2963 return false;
2964 }
2965
arangelov38a6fce2019-12-02 18:21:22 +00002966 int getMaxTargetsPerRow() {
Matt Pietalfaedea82019-03-21 10:36:54 -04002967 int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002968 if (shouldDisplayLandscape(getResources().getConfiguration().orientation)) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002969 maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
2970 }
Matt Pietalab986b52019-04-10 10:14:32 -04002971 return maxTargets;
Matt Pietal5b648562019-03-12 07:40:26 -04002972 }
2973
arangelov590fba32020-02-11 18:05:42 +00002974 /**
2975 * Hides the list item content preview.
2976 * <p>Not to be confused with the sticky content preview which is above the
2977 * personal and work tabs.
2978 */
2979 public void hideContentPreview() {
2980 mHideContentPreview = true;
2981 mLayoutRequested = true;
2982 notifyDataSetChanged();
2983 }
2984
Matt Pietale7cacab2019-05-23 07:21:36 -04002985 public boolean consumeLayoutRequest() {
2986 boolean oldValue = mLayoutRequested;
2987 mLayoutRequested = false;
2988 return oldValue;
2989 }
2990
Zhen Zhangbde7b462019-11-11 11:49:33 -08002991 public int getRowCount() {
Adam Powell7d758002015-05-06 17:49:36 -07002992 return (int) (
arangelov590fba32020-02-11 18:05:42 +00002993 getContentPreviewRowCount()
2994 + getProfileRowCount()
Matt Pietal26038402019-01-08 07:29:34 -05002995 + getServiceTargetRowCount()
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002996 + getCallerAndRankedTargetRowCount()
Mike Digmanae730b12019-04-25 11:10:31 -07002997 + getAzLabelRowCount()
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002998 + Math.ceil(
2999 (float) mChooserListAdapter.getAlphaTargetCount()
3000 / getMaxTargetsPerRow())
Adam Powell7d758002015-05-06 17:49:36 -07003001 );
3002 }
3003
arangelov590fba32020-02-11 18:05:42 +00003004 /**
3005 * Returns either {@code 0} or {@code 1} depending on whether we want to show the list item
3006 * content preview. Not to be confused with the sticky content preview which is above the
3007 * personal and work tabs.
3008 */
3009 public int getContentPreviewRowCount() {
3010 // For the tabbed case we show the sticky content preview above the tabs,
3011 // please refer to shouldShowStickyContentPreview
arangelovf163a882020-02-18 17:18:47 +00003012 if (shouldShowTabs()) {
arangelov590fba32020-02-11 18:05:42 +00003013 return 0;
3014 }
3015 if (!isSendAction(getTargetIntent())) {
3016 return 0;
3017 }
3018
3019 if (mHideContentPreview || mChooserListAdapter == null
3020 || mChooserListAdapter.getCount() == 0) {
3021 return 0;
3022 }
3023
3024 return 1;
3025 }
3026
Matt Pietal74c6ed02019-04-18 13:38:46 -04003027 public int getProfileRowCount() {
arangelovf163a882020-02-18 17:18:47 +00003028 if (shouldShowTabs()) {
arangelov590fba32020-02-11 18:05:42 +00003029 return 0;
3030 }
Matt Pietal74c6ed02019-04-18 13:38:46 -04003031 return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
3032 }
3033
Matt Pietal9236adc2019-12-12 09:24:23 -05003034 public int getFooterRowCount() {
3035 return 1;
3036 }
3037
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04003038 public int getCallerAndRankedTargetRowCount() {
Adam Powell63b31692015-09-28 10:45:00 -07003039 return (int) Math.ceil(
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04003040 ((float) mChooserListAdapter.getCallerTargetCount()
3041 + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow());
Adam Powell63b31692015-09-28 10:45:00 -07003042 }
3043
Matt Pietal5b648562019-03-12 07:40:26 -04003044 // There can be at most one row in the listview, that is internally
3045 // a ViewGroup with 2 rows
Adam Powell63b31692015-09-28 10:45:00 -07003046 public int getServiceTargetRowCount() {
arangelovb0802dc2019-10-18 18:03:44 +01003047 if (isSendAction(getTargetIntent())
3048 && !ActivityManager.isLowRamDeviceStatic()) {
Matt Pietal95574b02019-03-13 08:12:25 -04003049 return 1;
3050 }
3051 return 0;
Adam Powell63b31692015-09-28 10:45:00 -07003052 }
3053
Mike Digmanae730b12019-04-25 11:10:31 -07003054 public int getAzLabelRowCount() {
3055 // Only show a label if the a-z list is showing
Mike Digman849a9d12019-04-29 11:20:48 -07003056 return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
Mike Digmanae730b12019-04-25 11:10:31 -07003057 }
3058
Adam Powell7d758002015-05-06 17:49:36 -07003059 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08003060 public int getItemCount() {
3061 return (int) (
arangelov590fba32020-02-11 18:05:42 +00003062 getContentPreviewRowCount()
3063 + getProfileRowCount()
Zhen Zhangbde7b462019-11-11 11:49:33 -08003064 + getServiceTargetRowCount()
3065 + getCallerAndRankedTargetRowCount()
3066 + getAzLabelRowCount()
3067 + mChooserListAdapter.getAlphaTargetCount()
Matt Pietal9236adc2019-12-12 09:24:23 -05003068 + getFooterRowCount()
Zhen Zhangbde7b462019-11-11 11:49:33 -08003069 );
Adam Powell7d758002015-05-06 17:49:36 -07003070 }
3071
3072 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08003073 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
3074 switch (viewType) {
arangelov590fba32020-02-11 18:05:42 +00003075 case VIEW_TYPE_CONTENT_PREVIEW:
3076 return new ItemViewHolder(createContentPreviewView(parent), false);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003077 case VIEW_TYPE_PROFILE:
3078 return new ItemViewHolder(createProfileView(parent), false);
3079 case VIEW_TYPE_AZ_LABEL:
3080 return new ItemViewHolder(createAzLabelView(parent), false);
3081 case VIEW_TYPE_NORMAL:
3082 return new ItemViewHolder(mChooserListAdapter.createView(parent), true);
3083 case VIEW_TYPE_DIRECT_SHARE:
3084 case VIEW_TYPE_CALLER_AND_RANK:
3085 return createItemGroupViewHolder(viewType, parent);
Matt Pietal9236adc2019-12-12 09:24:23 -05003086 case VIEW_TYPE_FOOTER:
Matt Pietal2025a1b2020-01-07 10:08:07 -05003087 Space sp = new Space(parent.getContext());
3088 sp.setLayoutParams(new RecyclerView.LayoutParams(
3089 LayoutParams.MATCH_PARENT, mFooterHeight));
3090 return new FooterViewHolder(sp);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003091 default:
3092 // Since we catch all possible viewTypes above, no chance this is being called.
3093 return null;
3094 }
Adam Powell7d758002015-05-06 17:49:36 -07003095 }
3096
3097 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08003098 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Matt Pietal5b648562019-03-12 07:40:26 -04003099 int viewType = getItemViewType(position);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003100 switch (viewType) {
3101 case VIEW_TYPE_DIRECT_SHARE:
3102 case VIEW_TYPE_CALLER_AND_RANK:
3103 bindItemGroupViewHolder(position, (ItemGroupViewHolder) holder);
3104 break;
3105 case VIEW_TYPE_NORMAL:
3106 bindItemViewHolder(position, (ItemViewHolder) holder);
3107 break;
3108 default:
Matt Pietal1ef88002019-03-13 10:43:18 -04003109 }
Adam Powell7d758002015-05-06 17:49:36 -07003110 }
3111
Matt Pietal5b648562019-03-12 07:40:26 -04003112 @Override
3113 public int getItemViewType(int position) {
Mike Digmanae730b12019-04-25 11:10:31 -07003114 int count;
Matt Pietal1ef88002019-03-13 10:43:18 -04003115
arangelov590fba32020-02-11 18:05:42 +00003116 int countSum = (count = getContentPreviewRowCount());
3117 if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
3118
3119 countSum += (count = getProfileRowCount());
Mike Digmanae730b12019-04-25 11:10:31 -07003120 if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
Adam Powell63b31692015-09-28 10:45:00 -07003121
Mike Digmanae730b12019-04-25 11:10:31 -07003122 countSum += (count = getServiceTargetRowCount());
3123 if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
3124
3125 countSum += (count = getCallerAndRankedTargetRowCount());
Zhen Zhangbde7b462019-11-11 11:49:33 -08003126 if (count > 0 && position < countSum) return VIEW_TYPE_CALLER_AND_RANK;
Mike Digmanae730b12019-04-25 11:10:31 -07003127
3128 countSum += (count = getAzLabelRowCount());
3129 if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
Matt Pietal5b648562019-03-12 07:40:26 -04003130
Matt Pietal9236adc2019-12-12 09:24:23 -05003131 if (position == getItemCount() - 1) return VIEW_TYPE_FOOTER;
3132
Matt Pietal5b648562019-03-12 07:40:26 -04003133 return VIEW_TYPE_NORMAL;
3134 }
3135
Zhen Zhangbde7b462019-11-11 11:49:33 -08003136 public int getTargetType(int position) {
3137 return mChooserListAdapter.getPositionTargetType(getListPosition(position));
Matt Pietal1ef88002019-03-13 10:43:18 -04003138 }
3139
Zhen Zhangbde7b462019-11-11 11:49:33 -08003140 private View createProfileView(ViewGroup parent) {
3141 View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false);
Matt Pietal74c6ed02019-04-18 13:38:46 -04003142 profileRow.setBackground(
3143 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
3144 mProfileView = profileRow.findViewById(R.id.profile_button);
3145 mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
arangelovb0802dc2019-10-18 18:03:44 +01003146 updateProfileViewButton();
Matt Pietal74c6ed02019-04-18 13:38:46 -04003147 return profileRow;
3148 }
3149
Mike Digmanae730b12019-04-25 11:10:31 -07003150 private View createAzLabelView(ViewGroup parent) {
3151 return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
3152 }
3153
Zhen Zhangbde7b462019-11-11 11:49:33 -08003154 private ItemGroupViewHolder loadViewsIntoGroup(ItemGroupViewHolder holder) {
Matt Pietal5b648562019-03-12 07:40:26 -04003155 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietalab986b52019-04-10 10:14:32 -04003156 final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
3157 MeasureSpec.EXACTLY);
Matt Pietal5b648562019-03-12 07:40:26 -04003158 int columnCount = holder.getColumnCount();
3159
Mike Digmanba232682019-03-27 14:55:26 -07003160 final boolean isDirectShare = holder instanceof DirectShareViewHolder;
3161
Matt Pietal5b648562019-03-12 07:40:26 -04003162 for (int i = 0; i < columnCount; i++) {
Mike Digmanba232682019-03-27 14:55:26 -07003163 final View v = mChooserListAdapter.createView(holder.getRowByIndex(i));
Adam Powell4eb98712015-10-14 13:10:18 -07003164 final int column = i;
Adam Powell63b31692015-09-28 10:45:00 -07003165 v.setOnClickListener(new OnClickListener() {
3166 @Override
3167 public void onClick(View v) {
Matt Pietal5b648562019-03-12 07:40:26 -04003168 startSelected(holder.getItemIndex(column), false, true);
Adam Powell63b31692015-09-28 10:45:00 -07003169 }
3170 });
3171 v.setOnLongClickListener(new OnLongClickListener() {
3172 @Override
3173 public boolean onLongClick(View v) {
Adam Powell23882512016-01-29 10:21:00 -08003174 showTargetDetails(
Alison Cichowlas19ee2922019-12-16 19:43:12 -05003175 mChooserListAdapter.targetInfoForPosition(
Matt Pietal5b648562019-03-12 07:40:26 -04003176 holder.getItemIndex(column), true));
Adam Powell63b31692015-09-28 10:45:00 -07003177 return true;
3178 }
3179 });
Zhen Zhangbde7b462019-11-11 11:49:33 -08003180 holder.addView(i, v);
Adam Powell63b31692015-09-28 10:45:00 -07003181
Mike Digmanba232682019-03-27 14:55:26 -07003182 // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
3183 // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be
3184 // done before measuring.
3185 if (isDirectShare) {
3186 final ViewHolder vh = (ViewHolder) v.getTag();
3187 vh.text.setLines(2);
3188 vh.text.setHorizontallyScrolling(false);
3189 vh.text2.setVisibility(View.GONE);
Adam Powell63b31692015-09-28 10:45:00 -07003190 }
Mike Digmanba232682019-03-27 14:55:26 -07003191
3192 // Force height to be a given so we don't have visual disruption during scaling.
Matt Pietalab986b52019-04-10 10:14:32 -04003193 v.measure(exactSpec, spec);
3194 setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight());
Adam Powell63b31692015-09-28 10:45:00 -07003195 }
3196
Matt Pietal5b648562019-03-12 07:40:26 -04003197 final ViewGroup viewGroup = holder.getViewGroup();
3198
Mike Digmanba232682019-03-27 14:55:26 -07003199 // Pre-measure and fix height so we can scale later.
Adam Powell63b31692015-09-28 10:45:00 -07003200 holder.measure();
Matt Pietalab986b52019-04-10 10:14:32 -04003201 setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight());
Mike Digmanba232682019-03-27 14:55:26 -07003202
3203 if (isDirectShare) {
3204 DirectShareViewHolder dsvh = (DirectShareViewHolder) holder;
Matt Pietalab986b52019-04-10 10:14:32 -04003205 setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
3206 setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
Adam Powell7d758002015-05-06 17:49:36 -07003207 }
Matt Pietal5b648562019-03-12 07:40:26 -04003208
3209 viewGroup.setTag(holder);
3210
Adam Powell7d758002015-05-06 17:49:36 -07003211 return holder;
3212 }
3213
Matt Pietalab986b52019-04-10 10:14:32 -04003214 private void setViewBounds(View view, int widthPx, int heightPx) {
Mike Digmanba232682019-03-27 14:55:26 -07003215 LayoutParams lp = view.getLayoutParams();
3216 if (lp == null) {
Matt Pietalab986b52019-04-10 10:14:32 -04003217 lp = new LayoutParams(widthPx, heightPx);
Mike Digmanba232682019-03-27 14:55:26 -07003218 view.setLayoutParams(lp);
3219 } else {
3220 lp.height = heightPx;
Matt Pietalab986b52019-04-10 10:14:32 -04003221 lp.width = widthPx;
Mike Digmanba232682019-03-27 14:55:26 -07003222 }
3223 }
3224
Zhen Zhangbde7b462019-11-11 11:49:33 -08003225 ItemGroupViewHolder createItemGroupViewHolder(int viewType, ViewGroup parent) {
Matt Pietal5b648562019-03-12 07:40:26 -04003226 if (viewType == VIEW_TYPE_DIRECT_SHARE) {
3227 ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate(
3228 R.layout.chooser_row_direct_share, parent, false);
3229 ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3230 parentGroup, false);
3231 ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3232 parentGroup, false);
3233 parentGroup.addView(row1);
3234 parentGroup.addView(row2);
3235
3236 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
3237 Lists.newArrayList(row1, row2), getMaxTargetsPerRow());
Zhen Zhangbde7b462019-11-11 11:49:33 -08003238 loadViewsIntoGroup(mDirectShareViewHolder);
Matt Pietal5b648562019-03-12 07:40:26 -04003239
3240 return mDirectShareViewHolder;
3241 } else {
3242 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
3243 false);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003244 ItemGroupViewHolder holder = new SingleRowViewHolder(row, getMaxTargetsPerRow());
3245 loadViewsIntoGroup(holder);
Matt Pietal5b648562019-03-12 07:40:26 -04003246
3247 return holder;
3248 }
3249 }
3250
Matt Pietaldadc0d12019-04-16 12:53:28 -04003251 /**
Mike Digmanae730b12019-04-25 11:10:31 -07003252 * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
3253 * showing on top of the AZ list if the AZ label is visible. All other types are placed into
3254 * their own row as determined by their target type, and dividers are added in the list to
3255 * separate each type.
Matt Pietaldadc0d12019-04-16 12:53:28 -04003256 */
3257 int getRowType(int rowPosition) {
Mike Digmanae730b12019-04-25 11:10:31 -07003258 // Merge caller and ranked standard into a single row
Matt Pietaldadc0d12019-04-16 12:53:28 -04003259 int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
3260 if (positionType == ChooserListAdapter.TARGET_CALLER) {
3261 return ChooserListAdapter.TARGET_STANDARD;
3262 }
3263
Mike Digmanae730b12019-04-25 11:10:31 -07003264 // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
3265 // row type the same as the suggestion row type
3266 if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
3267 return ChooserListAdapter.TARGET_STANDARD;
3268 }
3269
Matt Pietaldadc0d12019-04-16 12:53:28 -04003270 return positionType;
3271 }
3272
Zhen Zhangbde7b462019-11-11 11:49:33 -08003273 void bindItemViewHolder(int position, ItemViewHolder holder) {
3274 View v = holder.itemView;
3275 int listPosition = getListPosition(position);
3276 holder.mListPosition = listPosition;
3277 mChooserListAdapter.bindView(listPosition, v);
3278 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003279
Zhen Zhangbde7b462019-11-11 11:49:33 -08003280 void bindItemGroupViewHolder(int position, ItemGroupViewHolder holder) {
3281 final ViewGroup viewGroup = (ViewGroup) holder.itemView;
3282 int start = getListPosition(position);
3283 int startType = getRowType(start);
arangelov934c64c2020-02-14 13:48:23 +00003284 if (viewGroup.getForeground() == null && position > 0) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08003285 viewGroup.setForeground(
Matt Pietal74c6ed02019-04-18 13:38:46 -04003286 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003287 }
3288
Matt Pietalfaedea82019-03-21 10:36:54 -04003289 int columnCount = holder.getColumnCount();
Matt Pietal5b648562019-03-12 07:40:26 -04003290 int end = start + columnCount - 1;
Matt Pietaldadc0d12019-04-16 12:53:28 -04003291 while (getRowType(end) != startType && end >= start) {
Adam Powell7d758002015-05-06 17:49:36 -07003292 end--;
3293 }
3294
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003295 if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08003296 final TextView textView = viewGroup.findViewById(R.id.chooser_row_text_option);
Adam Powell63b31692015-09-28 10:45:00 -07003297
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003298 if (textView.getVisibility() != View.VISIBLE) {
3299 textView.setAlpha(0.0f);
3300 textView.setVisibility(View.VISIBLE);
3301 textView.setText(R.string.chooser_no_direct_share_targets);
3302
3303 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
3304 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3305
3306 float translationInPx = getResources().getDimensionPixelSize(
3307 R.dimen.chooser_row_text_option_translate);
3308 textView.setTranslationY(translationInPx);
3309 ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
3310 0.0f);
3311 translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3312
3313 AnimatorSet animSet = new AnimatorSet();
3314 animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3315 animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3316 animSet.playTogether(fadeAnim, translateAnim);
3317 animSet.start();
3318 }
Adam Powell7d758002015-05-06 17:49:36 -07003319 }
3320
Matt Pietal5b648562019-03-12 07:40:26 -04003321 for (int i = 0; i < columnCount; i++) {
3322 final View v = holder.getView(i);
arangelov38a6fce2019-12-02 18:21:22 +00003323
Adam Powell7d758002015-05-06 17:49:36 -07003324 if (start + i <= end) {
Matt Pietalfaedea82019-03-21 10:36:54 -04003325 holder.setViewVisibility(i, View.VISIBLE);
Matt Pietal5b648562019-03-12 07:40:26 -04003326 holder.setItemIndex(i, start + i);
3327 mChooserListAdapter.bindView(holder.getItemIndex(i), v);
Adam Powell7d758002015-05-06 17:49:36 -07003328 } else {
Matt Pietalfaedea82019-03-21 10:36:54 -04003329 holder.setViewVisibility(i, View.INVISIBLE);
Adam Powell7d758002015-05-06 17:49:36 -07003330 }
3331 }
3332 }
3333
Zhen Zhangbde7b462019-11-11 11:49:33 -08003334 int getListPosition(int position) {
arangelov590fba32020-02-11 18:05:42 +00003335 position -= getContentPreviewRowCount() + getProfileRowCount();
Matt Pietal1ef88002019-03-13 10:43:18 -04003336
Matt Pietal5b648562019-03-12 07:40:26 -04003337 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
3338 final int serviceRows = (int) Math.ceil((float) serviceCount
3339 / ChooserListAdapter.MAX_SERVICE_TARGETS);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003340 if (position < serviceRows) {
3341 return position * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07003342 }
3343
Zhen Zhangbde7b462019-11-11 11:49:33 -08003344 position -= serviceRows;
3345
Matt Pietaldadc0d12019-04-16 12:53:28 -04003346 final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
3347 + mChooserListAdapter.getRankedTargetCount();
3348 final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
Zhen Zhangbde7b462019-11-11 11:49:33 -08003349 if (position < callerAndRankedRows) {
3350 return serviceCount + position * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07003351 }
3352
Zhen Zhangbde7b462019-11-11 11:49:33 -08003353 position -= getAzLabelRowCount() + callerAndRankedRows;
Mike Digmanae730b12019-04-25 11:10:31 -07003354
Zhen Zhangbde7b462019-11-11 11:49:33 -08003355 return callerAndRankedCount + serviceCount + position;
Matt Pietal5b648562019-03-12 07:40:26 -04003356 }
3357
3358 public void handleScroll(View v, int y, int oldy) {
Matt Pietale54dcc2e2019-05-02 12:59:38 -04003359 // Only expand direct share area if there is a minimum number of shortcuts,
3360 // which will help reduce the amount of visible shuffling due to older-style
3361 // direct share targets.
3362 int orientation = getResources().getConfiguration().orientation;
3363 boolean canExpandDirectShare =
3364 mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
Matt Pietal3e4b56f2019-05-31 12:06:17 -04003365 && orientation == Configuration.ORIENTATION_PORTRAIT
3366 && !isInMultiWindowMode();
Matt Pietale54dcc2e2019-05-02 12:59:38 -04003367
3368 if (mDirectShareViewHolder != null && canExpandDirectShare) {
arangelov38a6fce2019-12-02 18:21:22 +00003369 mDirectShareViewHolder.handleScroll(
arangelovcf268642020-01-15 15:09:51 +00003370 mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy,
arangelov38a6fce2019-12-02 18:21:22 +00003371 getMaxTargetsPerRow());
Matt Pietal5b648562019-03-12 07:40:26 -04003372 }
Adam Powell7d758002015-05-06 17:49:36 -07003373 }
arangelov38a6fce2019-12-02 18:21:22 +00003374
3375 public ChooserListAdapter getListAdapter() {
3376 return mChooserListAdapter;
3377 }
3378
arangelov38a6fce2019-12-02 18:21:22 +00003379 boolean shouldCellSpan(int position) {
3380 return getItemViewType(position) == VIEW_TYPE_NORMAL;
3381 }
Adam Powell7d758002015-05-06 17:49:36 -07003382 }
3383
Zhen Zhangbde7b462019-11-11 11:49:33 -08003384 /**
3385 * Used to bind types for group of items including:
3386 * {@link ChooserGridAdapter#VIEW_TYPE_DIRECT_SHARE},
3387 * and {@link ChooserGridAdapter#VIEW_TYPE_CALLER_AND_RANK}.
3388 */
3389 abstract class ItemGroupViewHolder extends RecyclerView.ViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04003390 protected int mMeasuredRowHeight;
3391 private int[] mItemIndices;
3392 protected final View[] mCells;
Matt Pietal5b648562019-03-12 07:40:26 -04003393 private final int mColumnCount;
Adam Powell63b31692015-09-28 10:45:00 -07003394
Zhen Zhangbde7b462019-11-11 11:49:33 -08003395 ItemGroupViewHolder(int cellCount, View itemView) {
3396 super(itemView);
Matt Pietal5b648562019-03-12 07:40:26 -04003397 this.mCells = new View[cellCount];
3398 this.mItemIndices = new int[cellCount];
Matt Pietal5b648562019-03-12 07:40:26 -04003399 this.mColumnCount = cellCount;
3400 }
3401
3402 abstract ViewGroup addView(int index, View v);
3403
3404 abstract ViewGroup getViewGroup();
3405
Mike Digmanba232682019-03-27 14:55:26 -07003406 abstract ViewGroup getRowByIndex(int index);
3407
3408 abstract ViewGroup getRow(int rowNumber);
Matt Pietal5b648562019-03-12 07:40:26 -04003409
Matt Pietalfaedea82019-03-21 10:36:54 -04003410 abstract void setViewVisibility(int i, int visibility);
3411
Matt Pietal5b648562019-03-12 07:40:26 -04003412 public int getColumnCount() {
3413 return mColumnCount;
3414 }
3415
Adam Powell63b31692015-09-28 10:45:00 -07003416 public void measure() {
3417 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietal5b648562019-03-12 07:40:26 -04003418 getViewGroup().measure(spec, spec);
3419 mMeasuredRowHeight = getViewGroup().getMeasuredHeight();
3420 }
3421
3422 public int getMeasuredRowHeight() {
3423 return mMeasuredRowHeight;
3424 }
3425
Matt Pietal5b648562019-03-12 07:40:26 -04003426 public void setItemIndex(int itemIndex, int listIndex) {
3427 mItemIndices[itemIndex] = listIndex;
3428 }
3429
3430 public int getItemIndex(int itemIndex) {
3431 return mItemIndices[itemIndex];
3432 }
3433
3434 public View getView(int index) {
3435 return mCells[index];
3436 }
3437 }
3438
Zhen Zhangbde7b462019-11-11 11:49:33 -08003439 class SingleRowViewHolder extends ItemGroupViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04003440 private final ViewGroup mRow;
3441
3442 SingleRowViewHolder(ViewGroup row, int cellCount) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08003443 super(cellCount, row);
Matt Pietal5b648562019-03-12 07:40:26 -04003444
3445 this.mRow = row;
3446 }
3447
3448 public ViewGroup getViewGroup() {
3449 return mRow;
3450 }
3451
Mike Digmanba232682019-03-27 14:55:26 -07003452 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04003453 return mRow;
3454 }
3455
Mike Digmanba232682019-03-27 14:55:26 -07003456 public ViewGroup getRow(int rowNumber) {
3457 if (rowNumber == 0) return mRow;
3458 return null;
3459 }
3460
Matt Pietal5b648562019-03-12 07:40:26 -04003461 public ViewGroup addView(int index, View v) {
3462 mRow.addView(v);
3463 mCells[index] = v;
3464
Matt Pietal5b648562019-03-12 07:40:26 -04003465 return mRow;
3466 }
Matt Pietalfaedea82019-03-21 10:36:54 -04003467
3468 public void setViewVisibility(int i, int visibility) {
3469 getView(i).setVisibility(visibility);
3470 }
Matt Pietal5b648562019-03-12 07:40:26 -04003471 }
3472
Zhen Zhangbde7b462019-11-11 11:49:33 -08003473 class DirectShareViewHolder extends ItemGroupViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04003474 private final ViewGroup mParent;
3475 private final List<ViewGroup> mRows;
3476 private int mCellCountPerRow;
3477
3478 private boolean mHideDirectShareExpansion = false;
3479 private int mDirectShareMinHeight = 0;
3480 private int mDirectShareCurrHeight = 0;
3481 private int mDirectShareMaxHeight = 0;
3482
Matt Pietalfaedea82019-03-21 10:36:54 -04003483 private final boolean[] mCellVisibility;
3484
Matt Pietal5b648562019-03-12 07:40:26 -04003485 DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08003486 super(rows.size() * cellCountPerRow, parent);
Matt Pietal5b648562019-03-12 07:40:26 -04003487
3488 this.mParent = parent;
3489 this.mRows = rows;
3490 this.mCellCountPerRow = cellCountPerRow;
Matt Pietalfaedea82019-03-21 10:36:54 -04003491 this.mCellVisibility = new boolean[rows.size() * cellCountPerRow];
Matt Pietal5b648562019-03-12 07:40:26 -04003492 }
3493
3494 public ViewGroup addView(int index, View v) {
Mike Digmanba232682019-03-27 14:55:26 -07003495 ViewGroup row = getRowByIndex(index);
Matt Pietal5b648562019-03-12 07:40:26 -04003496 row.addView(v);
3497 mCells[index] = v;
3498
Matt Pietal5b648562019-03-12 07:40:26 -04003499 return row;
3500 }
3501
3502 public ViewGroup getViewGroup() {
3503 return mParent;
3504 }
3505
Mike Digmanba232682019-03-27 14:55:26 -07003506 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04003507 return mRows.get(index / mCellCountPerRow);
3508 }
3509
Mike Digmanba232682019-03-27 14:55:26 -07003510 public ViewGroup getRow(int rowNumber) {
3511 return mRows.get(rowNumber);
3512 }
3513
Matt Pietal5b648562019-03-12 07:40:26 -04003514 public void measure() {
3515 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3516 getRow(0).measure(spec, spec);
3517 getRow(1).measure(spec, spec);
3518
Matt Pietal5b648562019-03-12 07:40:26 -04003519 mDirectShareMinHeight = getRow(0).getMeasuredHeight();
3520 mDirectShareCurrHeight = mDirectShareCurrHeight > 0
Zhen Zhangbde7b462019-11-11 11:49:33 -08003521 ? mDirectShareCurrHeight : mDirectShareMinHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04003522 mDirectShareMaxHeight = 2 * mDirectShareMinHeight;
3523 }
3524
3525 public int getMeasuredRowHeight() {
3526 return mDirectShareCurrHeight;
3527 }
3528
Matt Pietala9c8e502019-04-10 14:27:35 -04003529 public int getMinRowHeight() {
3530 return mDirectShareMinHeight;
3531 }
3532
Matt Pietalfaedea82019-03-21 10:36:54 -04003533 public void setViewVisibility(int i, int visibility) {
3534 final View v = getView(i);
3535 if (visibility == View.VISIBLE) {
3536 mCellVisibility[i] = true;
3537 v.setVisibility(visibility);
3538 v.setAlpha(1.0f);
3539 } else if (visibility == View.INVISIBLE && mCellVisibility[i]) {
3540 mCellVisibility[i] = false;
3541
3542 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
3543 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3544 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
3545 fadeAnim.addListener(new AnimatorListenerAdapter() {
3546 public void onAnimationEnd(Animator animation) {
3547 v.setVisibility(View.INVISIBLE);
3548 }
3549 });
3550 fadeAnim.start();
3551 }
3552 }
3553
Zhen Zhangbde7b462019-11-11 11:49:33 -08003554 public void handleScroll(RecyclerView view, int y, int oldy, int maxTargetsPerRow) {
Matt Pietala9c8e502019-04-10 14:27:35 -04003555 // only exit early if fully collapsed, otherwise onListRebuilt() with shifting
3556 // targets can lock us into an expanded mode
3557 boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight;
3558 if (notExpanded) {
3559 if (mHideDirectShareExpansion) {
3560 return;
3561 }
Matt Pietal5b648562019-03-12 07:40:26 -04003562
Matt Pietala9c8e502019-04-10 14:27:35 -04003563 // only expand if we have more than maxTargetsPerRow, and delay that decision
3564 // until they start to scroll
arangelova3912cf2019-12-13 14:34:45 +00003565 if (mChooserMultiProfilePagerAdapter.getActiveListAdapter()
arangelov38a6fce2019-12-02 18:21:22 +00003566 .getSelectableServiceTargetCount() <= maxTargetsPerRow) {
Matt Pietala9c8e502019-04-10 14:27:35 -04003567 mHideDirectShareExpansion = true;
3568 return;
3569 }
Matt Pietal5b648562019-03-12 07:40:26 -04003570 }
3571
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003572 int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal5b648562019-03-12 07:40:26 -04003573
3574 int prevHeight = mDirectShareCurrHeight;
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003575 int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
3576 newHeight = Math.max(newHeight, mDirectShareMinHeight);
3577 yDiff = newHeight - prevHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04003578
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003579 if (view == null || view.getChildCount() == 0 || yDiff == 0) {
Matt Pietal5b648562019-03-12 07:40:26 -04003580 return;
3581 }
3582
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003583 // locate the item to expand, and offset the rows below that one
3584 boolean foundExpansion = false;
3585 for (int i = 0; i < view.getChildCount(); i++) {
3586 View child = view.getChildAt(i);
Matt Pietal1ef88002019-03-13 10:43:18 -04003587
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003588 if (foundExpansion) {
3589 child.offsetTopAndBottom(yDiff);
3590 } else {
3591 if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
3592 int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
3593 MeasureSpec.EXACTLY);
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003594 int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003595 MeasureSpec.EXACTLY);
3596 child.measure(widthSpec, heightSpec);
3597 child.getLayoutParams().height = child.getMeasuredHeight();
3598 child.layout(child.getLeft(), child.getTop(), child.getRight(),
3599 child.getTop() + child.getMeasuredHeight());
Matt Pietal5b648562019-03-12 07:40:26 -04003600
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003601 foundExpansion = true;
3602 }
3603 }
Matt Pietal5b648562019-03-12 07:40:26 -04003604 }
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003605
3606 if (foundExpansion) {
3607 mDirectShareCurrHeight = newHeight;
3608 }
Adam Powell63b31692015-09-28 10:45:00 -07003609 }
3610 }
3611
Adam Powell9761ab22015-09-08 17:01:49 -07003612 static class ChooserTargetServiceConnection implements ServiceConnection {
Adam Powell52c39212016-04-07 15:14:18 -07003613 private DisplayResolveInfo mOriginalTarget;
Adam Powell9761ab22015-09-08 17:01:49 -07003614 private ComponentName mConnectedComponent;
3615 private ChooserActivity mChooserActivity;
arangelov5fc9e7d2020-01-07 17:59:14 +00003616 private final UserHandle mUserHandle;
Adam Powell9761ab22015-09-08 17:01:49 -07003617 private final Object mLock = new Object();
Adam Powell24428412015-04-01 17:19:56 -07003618
3619 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
3620 @Override
3621 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
Adam Powell9761ab22015-09-08 17:01:49 -07003622 synchronized (mLock) {
3623 if (mChooserActivity == null) {
3624 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
3625 + mConnectedComponent + "; ignoring...");
3626 return;
3627 }
arangelov5fc9e7d2020-01-07 17:59:14 +00003628 Context contextAsUser =
3629 mChooserActivity.createContextAsUser(mUserHandle, 0 /* flags */);
3630 mChooserActivity.filterServiceTargets(contextAsUser,
Adam Powell9761ab22015-09-08 17:01:49 -07003631 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
3632 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04003633 msg.what = ChooserHandler.CHOOSER_TARGET_SERVICE_RESULT;
Adam Powell9761ab22015-09-08 17:01:49 -07003634 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
arangelov5fc9e7d2020-01-07 17:59:14 +00003635 ChooserTargetServiceConnection.this, mUserHandle);
Adam Powell9761ab22015-09-08 17:01:49 -07003636 mChooserActivity.mChooserHandler.sendMessage(msg);
3637 }
Adam Powell24428412015-04-01 17:19:56 -07003638 }
3639 };
3640
Adam Powell9761ab22015-09-08 17:01:49 -07003641 public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
arangelov5fc9e7d2020-01-07 17:59:14 +00003642 DisplayResolveInfo dri, UserHandle userHandle) {
Adam Powell9761ab22015-09-08 17:01:49 -07003643 mChooserActivity = chooserActivity;
Adam Powell24428412015-04-01 17:19:56 -07003644 mOriginalTarget = dri;
arangelov5fc9e7d2020-01-07 17:59:14 +00003645 mUserHandle = userHandle;
Adam Powell24428412015-04-01 17:19:56 -07003646 }
3647
3648 @Override
3649 public void onServiceConnected(ComponentName name, IBinder service) {
3650 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003651 synchronized (mLock) {
3652 if (mChooserActivity == null) {
3653 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
3654 return;
3655 }
3656
3657 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
3658 try {
3659 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
3660 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
3661 } catch (RemoteException e) {
3662 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
3663 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003664 mChooserActivity.mServiceConnections.remove(this);
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003665 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003666 }
Adam Powell24428412015-04-01 17:19:56 -07003667 }
3668 }
3669
3670 @Override
3671 public void onServiceDisconnected(ComponentName name) {
3672 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003673 synchronized (mLock) {
3674 if (mChooserActivity == null) {
3675 Log.e(TAG,
3676 "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
3677 return;
3678 }
3679
3680 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003681 mChooserActivity.mServiceConnections.remove(this);
3682 if (mChooserActivity.mServiceConnections.isEmpty()) {
Adam Powell9761ab22015-09-08 17:01:49 -07003683 mChooserActivity.sendVoiceChoicesIfNeeded();
3684 }
3685 mConnectedComponent = null;
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003686 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003687 }
3688 }
3689
3690 public void destroy() {
3691 synchronized (mLock) {
3692 mChooserActivity = null;
Adam Powell52c39212016-04-07 15:14:18 -07003693 mOriginalTarget = null;
Adam Powell4c470d62015-06-19 17:46:17 -07003694 }
Adam Powell24428412015-04-01 17:19:56 -07003695 }
3696
3697 @Override
3698 public String toString() {
Adam Powell9761ab22015-09-08 17:01:49 -07003699 return "ChooserTargetServiceConnection{service="
3700 + mConnectedComponent + ", activity="
Adam Powell52c39212016-04-07 15:14:18 -07003701 + (mOriginalTarget != null
3702 ? mOriginalTarget.getResolveInfo().activityInfo.toString()
3703 : "<connection destroyed>") + "}";
Adam Powell24428412015-04-01 17:19:56 -07003704 }
Song Hue2deffd2020-03-09 15:22:29 -07003705
3706 public ComponentName getComponentName() {
3707 return mOriginalTarget.getResolveInfo().activityInfo.getComponentName();
3708 }
Adam Powell24428412015-04-01 17:19:56 -07003709 }
3710
3711 static class ServiceResultInfo {
3712 public final DisplayResolveInfo originalTarget;
3713 public final List<ChooserTarget> resultTargets;
3714 public final ChooserTargetServiceConnection connection;
arangelov5fc9e7d2020-01-07 17:59:14 +00003715 public final UserHandle userHandle;
Adam Powell24428412015-04-01 17:19:56 -07003716
3717 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
arangelov5fc9e7d2020-01-07 17:59:14 +00003718 ChooserTargetServiceConnection c, UserHandle userHandle) {
Adam Powell24428412015-04-01 17:19:56 -07003719 originalTarget = ot;
3720 resultTargets = rt;
3721 connection = c;
arangelov5fc9e7d2020-01-07 17:59:14 +00003722 this.userHandle = userHandle;
Adam Powell24428412015-04-01 17:19:56 -07003723 }
3724 }
Adam Powell2ed547e2015-04-29 18:45:04 -07003725
3726 static class RefinementResultReceiver extends ResultReceiver {
3727 private ChooserActivity mChooserActivity;
3728 private TargetInfo mSelectedTarget;
3729
3730 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
3731 Handler handler) {
3732 super(handler);
3733 mChooserActivity = host;
3734 mSelectedTarget = target;
3735 }
3736
3737 @Override
3738 protected void onReceiveResult(int resultCode, Bundle resultData) {
3739 if (mChooserActivity == null) {
3740 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
3741 return;
3742 }
3743 if (resultData == null) {
3744 Log.e(TAG, "RefinementResultReceiver received null resultData");
3745 return;
3746 }
3747
3748 switch (resultCode) {
3749 case RESULT_CANCELED:
3750 mChooserActivity.onRefinementCanceled();
3751 break;
3752 case RESULT_OK:
3753 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
3754 if (intentParcelable instanceof Intent) {
3755 mChooserActivity.onRefinementResult(mSelectedTarget,
3756 (Intent) intentParcelable);
3757 } else {
3758 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
3759 + " in resultData with key Intent.EXTRA_INTENT");
3760 }
3761 break;
3762 default:
3763 Log.w(TAG, "Unknown result code " + resultCode
3764 + " sent to RefinementResultReceiver");
3765 break;
3766 }
3767 }
3768
3769 public void destroy() {
3770 mChooserActivity = null;
3771 mSelectedTarget = null;
3772 }
3773 }
Adam Powell63b31692015-09-28 10:45:00 -07003774
Matt Pietal26038402019-01-08 07:29:34 -05003775 /**
3776 * Used internally to round image corners while obeying view padding.
3777 */
3778 public static class RoundedRectImageView extends ImageView {
3779 private int mRadius = 0;
3780 private Path mPath = new Path();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003781 private Paint mOverlayPaint = new Paint(0);
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003782 private Paint mRoundRectPaint = new Paint(0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003783 private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
3784 private String mExtraImageCount = null;
Matt Pietal26038402019-01-08 07:29:34 -05003785
3786 public RoundedRectImageView(Context context) {
3787 super(context);
3788 }
3789
3790 public RoundedRectImageView(Context context, AttributeSet attrs) {
3791 this(context, attrs, 0);
3792 }
3793
3794 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
3795 this(context, attrs, defStyleAttr, 0);
3796 }
3797
3798 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
3799 int defStyleRes) {
3800 super(context, attrs, defStyleAttr, defStyleRes);
3801 mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003802
3803 mOverlayPaint.setColor(0x99000000);
3804 mOverlayPaint.setStyle(Paint.Style.FILL);
3805
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003806 mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider));
3807 mRoundRectPaint.setStyle(Paint.Style.STROKE);
3808 mRoundRectPaint.setStrokeWidth(context.getResources()
3809 .getDimensionPixelSize(R.dimen.chooser_preview_image_border));
3810
Matt Pietal0ea391b2019-01-30 10:44:15 -05003811 mTextPaint.setColor(Color.WHITE);
3812 mTextPaint.setTextSize(context.getResources()
3813 .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
3814 mTextPaint.setTextAlign(Paint.Align.CENTER);
Matt Pietal26038402019-01-08 07:29:34 -05003815 }
3816
3817 private void updatePath(int width, int height) {
3818 mPath.reset();
3819
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003820 int imageWidth = width - getPaddingRight() - getPaddingLeft();
3821 int imageHeight = height - getPaddingBottom() - getPaddingTop();
Matt Pietal26038402019-01-08 07:29:34 -05003822 mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
3823 mRadius, Path.Direction.CW);
3824 }
3825
3826 /**
3827 * Sets the corner radius on all corners
3828 *
3829 * param radius 0 for no radius, &gt; 0 for a visible corner radius
3830 */
3831 public void setRadius(int radius) {
3832 mRadius = radius;
3833 updatePath(getWidth(), getHeight());
3834 }
3835
Matt Pietal0ea391b2019-01-30 10:44:15 -05003836 /**
3837 * Display an overlay with extra image count on 3rd image
3838 */
3839 public void setExtraImageCount(int count) {
3840 if (count > 0) {
3841 this.mExtraImageCount = "+" + count;
3842 } else {
3843 this.mExtraImageCount = null;
3844 }
3845 }
3846
Matt Pietal26038402019-01-08 07:29:34 -05003847 @Override
3848 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
3849 super.onSizeChanged(width, height, oldWidth, oldHeight);
3850 updatePath(width, height);
3851 }
3852
3853 @Override
3854 protected void onDraw(Canvas canvas) {
3855 if (mRadius != 0) {
3856 canvas.clipPath(mPath);
3857 }
3858
3859 super.onDraw(canvas);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003860
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003861 int x = getPaddingLeft();
3862 int y = getPaddingRight();
3863 int width = getWidth() - getPaddingRight() - getPaddingLeft();
3864 int height = getHeight() - getPaddingBottom() - getPaddingTop();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003865 if (mExtraImageCount != null) {
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003866 canvas.drawRect(x, y, width, height, mOverlayPaint);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003867
3868 int xPos = canvas.getWidth() / 2;
3869 int yPos = (int) ((canvas.getHeight() / 2.0f)
3870 - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
3871
3872 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
3873 }
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003874
3875 canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
Matt Pietal26038402019-01-08 07:29:34 -05003876 }
3877 }
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05003878
3879 @Override
3880 protected void maybeLogProfileChange() {
3881 getChooserActivityLogger().logShareheetProfileChanged();
3882 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003883}