blob: a30c3c52f42c41ba8a9d920ed9d4ced4d0f8d460 [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;
Song Hu5aab2d32020-04-30 10:07:15 -070036import android.app.prediction.AppTargetId;
Artur Satayeved5a6ae2019-12-10 17:47:54 +000037import android.compat.annotation.UnsupportedAppUsage;
Matt Pietal26038402019-01-08 07:29:34 -050038import android.content.ClipData;
Matt Pietal1fa7d802019-01-30 10:44:15 -050039import android.content.ClipboardManager;
Adam Powell0b3c1122014-10-09 12:50:14 -070040import android.content.ComponentName;
Matt Pietal0ea391b2019-01-30 10:44:15 -050041import android.content.ContentResolver;
Adam Powell24428412015-04-01 17:19:56 -070042import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.content.Intent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080044import android.content.IntentFilter;
Adam Powell0b3c1122014-10-09 12:50:14 -070045import android.content.IntentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -070046import android.content.IntentSender.SendIntentException;
Adam Powell24428412015-04-01 17:19:56 -070047import android.content.ServiceConnection;
Alison Cichowlas1fd47152019-11-14 19:50:55 -050048import android.content.SharedPreferences;
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +000049import android.content.pm.ActivityInfo;
Matt Pietala4b30072019-04-04 13:44:36 -040050import android.content.pm.ApplicationInfo;
Adam Powell24428412015-04-01 17:19:56 -070051import android.content.pm.PackageManager;
52import android.content.pm.PackageManager.NameNotFoundException;
53import android.content.pm.ResolveInfo;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080054import android.content.pm.ShortcutInfo;
55import android.content.pm.ShortcutManager;
Matt Pietal18bbd822019-02-12 15:21:36 -050056import android.content.res.Configuration;
Dan Sandlere3d19932019-11-22 11:58:58 -050057import android.content.res.Resources;
Matt Pietal46d828c2019-02-05 08:07:07 -050058import android.database.Cursor;
Adam Powell7d758002015-05-06 17:49:36 -070059import android.database.DataSetObserver;
Matt Pietal26038402019-01-08 07:29:34 -050060import android.graphics.Bitmap;
61import android.graphics.Canvas;
Adam Powell63b31692015-09-28 10:45:00 -070062import android.graphics.Color;
Matt Pietal0ea391b2019-01-30 10:44:15 -050063import android.graphics.Paint;
Matt Pietal26038402019-01-08 07:29:34 -050064import android.graphics.Path;
Mike Digmanac1d88c2019-04-18 15:15:55 -070065import android.graphics.drawable.AnimatedVectorDrawable;
Adam Powell24428412015-04-01 17:19:56 -070066import android.graphics.drawable.Drawable;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -050067import android.metrics.LogMaker;
Matt Pietal26038402019-01-08 07:29:34 -050068import android.net.Uri;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080069import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070import android.os.Bundle;
Alison Cichowlas1fd47152019-11-14 19:50:55 -050071import android.os.Environment;
Adam Powell24428412015-04-01 17:19:56 -070072import android.os.Handler;
73import android.os.IBinder;
74import android.os.Message;
Dianne Hackborneb034652009-09-07 00:49:58 -070075import android.os.Parcelable;
Katsiaryna Naliuka66cd0562020-01-16 18:01:30 +010076import android.os.PatternMatcher;
Adam Powell24428412015-04-01 17:19:56 -070077import android.os.RemoteException;
Adam Powell2ed547e2015-04-29 18:45:04 -070078import android.os.ResultReceiver;
Adam Powell24428412015-04-01 17:19:56 -070079import android.os.UserHandle;
Adam Powell7d758002015-05-06 17:49:36 -070080import android.os.UserManager;
Alison Cichowlas1fd47152019-11-14 19:50:55 -050081import android.os.storage.StorageManager;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -040082import android.provider.DeviceConfig;
Matt Pietal46d828c2019-02-05 08:07:07 -050083import android.provider.DocumentsContract;
Matt Pietalf38e9d22019-02-15 10:01:03 -050084import android.provider.Downloads;
Matt Pietal46d828c2019-02-05 08:07:07 -050085import android.provider.OpenableColumns;
Dan Sandlere3d19932019-11-22 11:58:58 -050086import android.provider.Settings;
Adam Powell24428412015-04-01 17:19:56 -070087import android.service.chooser.ChooserTarget;
88import android.service.chooser.ChooserTargetService;
89import android.service.chooser.IChooserTargetResult;
90import android.service.chooser.IChooserTargetService;
91import android.text.TextUtils;
Matt Pietal26038402019-01-08 07:29:34 -050092import android.util.AttributeSet;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -040093import android.util.HashedStringCache;
Dianne Hackborneb034652009-09-07 00:49:58 -070094import android.util.Log;
arangelov5fc9e7d2020-01-07 17:59:14 +000095import android.util.Pair;
Matt Pietal26038402019-01-08 07:29:34 -050096import android.util.Size;
Adam Powell0b3c1122014-10-09 12:50:14 -070097import android.util.Slog;
Adam Powell7d758002015-05-06 17:49:36 -070098import android.view.LayoutInflater;
Adam Powell24428412015-04-01 17:19:56 -070099import android.view.View;
Adam Powell63b31692015-09-28 10:45:00 -0700100import android.view.View.MeasureSpec;
Adam Powell7d758002015-05-06 17:49:36 -0700101import android.view.View.OnClickListener;
Adam Powell24428412015-04-01 17:19:56 -0700102import android.view.ViewGroup;
Adam Powell63b31692015-09-28 10:45:00 -0700103import android.view.ViewGroup.LayoutParams;
Kholoud Mohamed85a28ec2020-05-26 11:39:23 +0100104import android.view.ViewTreeObserver;
arangelov28bb4fe2020-05-29 13:29:23 +0100105import android.view.WindowInsets;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500106import android.view.animation.AccelerateInterpolator;
107import android.view.animation.DecelerateInterpolator;
Dan Sandlere3d19932019-11-22 11:58:58 -0500108import android.widget.Button;
Matt Pietal26038402019-01-08 07:29:34 -0500109import android.widget.ImageView;
Matt Pietal9236adc2019-12-12 09:24:23 -0500110import android.widget.Space;
Matt Pietal26038402019-01-08 07:29:34 -0500111import android.widget.TextView;
Matt Pietal1fa7d802019-01-30 10:44:15 -0500112import android.widget.Toast;
Jason Monk027dcfa2017-06-27 18:37:35 -0400113
Adam Powell7d758002015-05-06 17:49:36 -0700114import com.android.internal.R;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800115import com.android.internal.annotations.VisibleForTesting;
arangelovb0802dc2019-10-18 18:03:44 +0100116import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
117import com.android.internal.app.ResolverListAdapter.ViewHolder;
118import com.android.internal.app.chooser.ChooserTargetInfo;
119import com.android.internal.app.chooser.DisplayResolveInfo;
Alison Cichowlas19ee2922019-12-16 19:43:12 -0500120import com.android.internal.app.chooser.MultiDisplayResolveInfo;
arangelovb0802dc2019-10-18 18:03:44 +0100121import com.android.internal.app.chooser.NotSelectableTargetInfo;
122import com.android.internal.app.chooser.SelectableTargetInfo;
123import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
124import com.android.internal.app.chooser.TargetInfo;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400125import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
Matt Pietalab73a882019-06-05 07:04:55 -0400126import com.android.internal.content.PackageMonitor;
Adam Powell98b7f892015-06-19 12:38:45 -0700127import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +0100128import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500129import com.android.internal.util.FrameworkStatsLog;
Zhen Zhangbde7b462019-11-11 11:49:33 -0800130import com.android.internal.widget.GridLayoutManager;
131import com.android.internal.widget.RecyclerView;
Mike Digman849a9d12019-04-29 11:20:48 -0700132import com.android.internal.widget.ResolverDrawerLayout;
arangelovd85a74a2020-06-02 11:50:19 +0100133import com.android.internal.widget.ViewPager;
Alison Cichowlas3e340502018-08-07 17:15:01 -0400134
Adam Powell52c39212016-04-07 15:14:18 -0700135import com.google.android.collect.Lists;
Adam Powell24428412015-04-01 17:19:56 -0700136
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500137import java.io.File;
Matt Pietal26038402019-01-08 07:29:34 -0500138import java.io.IOException;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500139import java.lang.annotation.Retention;
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700140import java.lang.annotation.RetentionPolicy;
Dan Sandlere3d19932019-11-22 11:58:58 -0500141import java.net.URISyntaxException;
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400142import java.text.Collator;
Adam Powell24428412015-04-01 17:19:56 -0700143import java.util.ArrayList;
Adam Powella182e452015-07-06 16:57:56 -0700144import java.util.Collections;
145import java.util.Comparator;
George Hodulikaa5238c2019-04-18 14:17:51 -0700146import java.util.HashMap;
Matt Pietalab73a882019-06-05 07:04:55 -0400147import java.util.HashSet;
Adam Powell24428412015-04-01 17:19:56 -0700148import java.util.List;
George Hodulikaa5238c2019-04-18 14:17:51 -0700149import java.util.Map;
Matt Pietalab73a882019-06-05 07:04:55 -0400150import java.util.Set;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400152/**
153 * The Chooser Activity handles intent resolution specifically for sharing intents -
154 * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
155 *
156 */
arangelovb0802dc2019-10-18 18:03:44 +0100157public class ChooserActivity extends ResolverActivity implements
158 ChooserListAdapter.ChooserListCommunicator,
159 SelectableTargetInfoCommunicator {
Adam Powell0b3c1122014-10-09 12:50:14 -0700160 private static final String TAG = "ChooserActivity";
arangelov5fc9e7d2020-01-07 17:59:14 +0000161 private AppPredictor mPersonalAppPredictor;
162 private AppPredictor mWorkAppPredictor;
arangelov706316d2020-05-06 14:25:06 +0100163 private boolean mShouldDisplayLandscape;
Adam Powell0b3c1122014-10-09 12:50:14 -0700164
Artur Satayev751e5512019-11-15 19:12:49 +0000165 @UnsupportedAppUsage
166 public ChooserActivity() {
167 }
Jorim Jaggif631ef72017-02-24 13:49:47 +0100168 /**
169 * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
170 * in onStop when launched in a new task. If this extra is set to true, we do not finish
171 * ourselves when onStop gets called.
172 */
173 public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
174 = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
175
Mike Digman849a9d12019-04-29 11:20:48 -0700176 private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
177
Dan Sandlere3d19932019-11-22 11:58:58 -0500178 private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label";
179 private static final String CHIP_ICON_METADATA_KEY = "android.service.chooser.chip_icon";
180
arangelov5fc9e7d2020-01-07 17:59:14 +0000181 private static final boolean DEBUG = true;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800182
George Hodulik1428beb2019-05-01 17:04:23 -0700183 private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
George Hodulik69d4a082019-01-18 11:27:03 -0800184 // TODO(b/123088566) Share these in a better way.
185 private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500186 public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share";
Song Hu5aab2d32020-04-30 10:07:15 -0700187 public static final String CHOOSER_TARGET = "chooser_target";
188 private static final String SHORTCUT_TARGET = "shortcut_target";
George Hodulik69d4a082019-01-18 11:27:03 -0800189 private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
190 public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700191
arangelovb0802dc2019-10-18 18:03:44 +0100192 @VisibleForTesting
193 public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
194
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700195 private boolean mIsAppPredictorComponentAvailable;
George Hodulikaa5238c2019-04-18 14:17:51 -0700196 private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
arangelov5fc9e7d2020-01-07 17:59:14 +0000197 private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache;
Song Huc6297c82020-06-15 23:30:53 -0700198 private Map<ComponentName, ComponentName> mChooserTargetComponentNameCache;
George Hodulik69d4a082019-01-18 11:27:03 -0800199
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700200 public static final int TARGET_TYPE_DEFAULT = 0;
201 public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
202 public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
203 public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
204
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500205 public static final int SELECTION_TYPE_SERVICE = 1;
206 public static final int SELECTION_TYPE_APP = 2;
207 public static final int SELECTION_TYPE_STANDARD = 3;
208 public static final int SELECTION_TYPE_COPY = 4;
209
arangelovd85a74a2020-06-02 11:50:19 +0100210 private static final int SCROLL_STATUS_IDLE = 0;
211 private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1;
212 private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2;
213
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500214 // 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 Hu5aab2d32020-04-30 10:07:15 -0700250 private boolean mChooserTargetRankingEnabled = DeviceConfig.getBoolean(
251 DeviceConfig.NAMESPACE_SYSTEMUI,
252 SystemUiDeviceConfigFlags.CHOOSER_TARGET_RANKING_ENABLED,
Song Hu60c131e2020-05-05 10:27:52 -0700253 true);
Song Hue2deffd2020-03-09 15:22:29 -0700254
Adam Powelle49d9392014-07-17 18:45:19 -0700255 private Bundle mReplacementExtras;
Adam Powell0b3c1122014-10-09 12:50:14 -0700256 private IntentSender mChosenComponentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -0700257 private IntentSender mRefinementIntentSender;
258 private RefinementResultReceiver mRefinementResultReceiver;
Adam Powell52c39212016-04-07 15:14:18 -0700259 private ChooserTarget[] mCallerChooserTargets;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800260 private ComponentName[] mFilteredComponentNames;
Adam Powelle49d9392014-07-17 18:45:19 -0700261
Adam Powell13036be2015-05-12 14:43:56 -0700262 private Intent mReferrerFillInIntent;
263
Kang Li9082f5b2016-12-02 10:56:21 -0800264 private long mChooserShownTime;
Kang Li64b018e2017-01-05 17:30:06 -0800265 protected boolean mIsSuccessfullySelected;
Kang Li9082f5b2016-12-02 10:56:21 -0800266
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700267 private long mQueriedTargetServicesTimeMs;
268 private long mQueriedSharingShortcutsTimeMs;
269
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500270 private int mChooserRowServiceSpacing;
Adam Powell24428412015-04-01 17:19:56 -0700271
Matt Pietalab73a882019-06-05 07:04:55 -0400272 private int mCurrAvailableWidth = 0;
arangelovc4dbdbd2020-02-18 20:54:16 +0000273 private int mLastNumberOfChildren = -1;
Matt Pietalab73a882019-06-05 07:04:55 -0400274
Adam Powell23882512016-01-29 10:21:00 -0800275 private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400276 // TODO: Update to handle landscape instead of using static value
277 private static final int MAX_RANKED_TARGETS = 4;
Adam Powell23882512016-01-29 10:21:00 -0800278
Adam Powell24428412015-04-01 17:19:56 -0700279 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
arangelov5fc9e7d2020-01-07 17:59:14 +0000280 private final Set<Pair<ComponentName, UserHandle>> mServicesRequested = new HashSet<>();
Matt Pietalaf044ae2019-03-29 06:53:53 -0400281
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -0400282 private static final int MAX_LOG_RANK_POSITION = 12;
283
Matt Pietal4e2e3632019-04-05 08:32:47 -0400284 private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400285 private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
Matt Pietal4e2e3632019-04-05 08:32:47 -0400286
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500287 private SharedPreferences mPinnedSharedPrefs;
288 private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
289
Matt Pietal0ea391b2019-01-30 10:44:15 -0500290 @Retention(SOURCE)
291 @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
292 private @interface ContentPreviewType {
293 }
294
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500295 // Starting at 1 since 0 is considered "undefined" for some of the database transformations
296 // of tron logs.
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500297 protected static final int CONTENT_PREVIEW_IMAGE = 1;
298 protected static final int CONTENT_PREVIEW_FILE = 2;
299 protected static final int CONTENT_PREVIEW_TEXT = 3;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500300 protected MetricsLogger mMetricsLogger;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500301
Matt Pietale7cacab2019-05-23 07:21:36 -0400302 private ContentPreviewCoordinator mPreviewCoord;
arangelovd85a74a2020-06-02 11:50:19 +0100303 private int mScrollStatus = SCROLL_STATUS_IDLE;
Matt Pietale7cacab2019-05-23 07:21:36 -0400304
arangelov38a6fce2019-12-02 18:21:22 +0000305 @VisibleForTesting
306 protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
307
Matt Pietale7cacab2019-05-23 07:21:36 -0400308 private class ContentPreviewCoordinator {
Matt Pietale7cacab2019-05-23 07:21:36 -0400309 private static final int IMAGE_FADE_IN_MILLIS = 150;
310 private static final int IMAGE_LOAD_TIMEOUT = 1;
311 private static final int IMAGE_LOAD_INTO_VIEW = 2;
312
Matt Pietalab73a882019-06-05 07:04:55 -0400313 private final int mImageLoadTimeoutMillis =
314 getResources().getInteger(R.integer.config_shortAnimTime);
315
Matt Pietale7cacab2019-05-23 07:21:36 -0400316 private final View mParentView;
317 private boolean mHideParentOnFail;
318 private boolean mAtLeastOneLoaded = false;
319
320 class LoadUriTask {
321 public final Uri mUri;
322 public final int mImageResourceId;
323 public final int mExtraCount;
324 public final Bitmap mBmp;
325
326 LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) {
327 this.mImageResourceId = imageResourceId;
328 this.mUri = uri;
329 this.mExtraCount = extraCount;
330 this.mBmp = bmp;
331 }
332 }
333
334 // If at least one image loads within the timeout period, allow other
335 // loads to continue. Otherwise terminate and optionally hide
336 // the parent area
337 private final Handler mHandler = new Handler() {
338 @Override
339 public void handleMessage(Message msg) {
340 switch (msg.what) {
341 case IMAGE_LOAD_TIMEOUT:
342 maybeHideContentPreview();
343 break;
344
345 case IMAGE_LOAD_INTO_VIEW:
346 if (isFinishing()) break;
347
348 LoadUriTask task = (LoadUriTask) msg.obj;
349 RoundedRectImageView imageView = mParentView.findViewById(
350 task.mImageResourceId);
351 if (task.mBmp == null) {
352 imageView.setVisibility(View.GONE);
353 maybeHideContentPreview();
354 return;
355 }
356
357 mAtLeastOneLoaded = true;
358 imageView.setVisibility(View.VISIBLE);
359 imageView.setAlpha(0.0f);
360 imageView.setImageBitmap(task.mBmp);
361
362 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f,
363 1.0f);
364 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
365 fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS);
366 fadeAnim.start();
367
368 if (task.mExtraCount > 0) {
369 imageView.setExtraImageCount(task.mExtraCount);
370 }
371 }
372 }
373 };
374
375 ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) {
376 super();
377
378 this.mParentView = parentView;
379 this.mHideParentOnFail = hideParentOnFail;
380 }
381
382 private void loadUriIntoView(final int imageResourceId, final Uri uri,
383 final int extraImages) {
Matt Pietalab73a882019-06-05 07:04:55 -0400384 mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, mImageLoadTimeoutMillis);
Matt Pietale7cacab2019-05-23 07:21:36 -0400385
386 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
Alison Cichowlas07acade2019-12-20 11:26:23 -0500387 int size = getResources().getDimensionPixelSize(
388 R.dimen.chooser_preview_image_max_dimen);
389 final Bitmap bmp = loadThumbnail(uri, new Size(size, size));
Matt Pietale7cacab2019-05-23 07:21:36 -0400390 final Message msg = Message.obtain();
391 msg.what = IMAGE_LOAD_INTO_VIEW;
392 msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
393 mHandler.sendMessage(msg);
394 });
395 }
396
397 private void cancelLoads() {
398 mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW);
399 mHandler.removeMessages(IMAGE_LOAD_TIMEOUT);
400 }
401
402 private void maybeHideContentPreview() {
403 if (!mAtLeastOneLoaded && mHideParentOnFail) {
404 Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
Matt Pietalab73a882019-06-05 07:04:55 -0400405 + " within " + mImageLoadTimeoutMillis + "ms.");
Matt Pietale7cacab2019-05-23 07:21:36 -0400406 collapseParentView();
arangelovf163a882020-02-18 17:18:47 +0000407 if (shouldShowTabs()) {
arangelov590fba32020-02-11 18:05:42 +0000408 hideStickyContentPreview();
409 } else if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
410 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().hideContentPreview();
411 }
Matt Pietale7cacab2019-05-23 07:21:36 -0400412 mHideParentOnFail = false;
413 }
414 }
415
416 private void collapseParentView() {
417 // This will effectively hide the content preview row by forcing the height
418 // to zero. It is faster than forcing a relayout of the listview
419 final View v = mParentView;
420 int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY);
421 int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
422 v.measure(widthSpec, heightSpec);
423 v.getLayoutParams().height = 0;
424 v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop());
425 v.invalidate();
426 }
427 }
428
Matt Pietalab73a882019-06-05 07:04:55 -0400429 private final ChooserHandler mChooserHandler = new ChooserHandler();
430
431 private class ChooserHandler extends Handler {
432 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
433 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT = 2;
434 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT = 3;
435 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
436 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
437 private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
Song Hu5aab2d32020-04-30 10:07:15 -0700438 private static final int CHOOSER_TARGET_RANKING_SCORE = 7;
Matt Pietalab73a882019-06-05 07:04:55 -0400439
440 private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
441 private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;
442
Song Hue2deffd2020-03-09 15:22:29 -0700443 private static final int DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS = 1500;
444 private int mDirectShareTimeout = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
445 SystemUiDeviceConfigFlags.SHARE_SHEET_DIRECT_SHARE_TIMEOUT,
446 DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS);
447
Matt Pietalab73a882019-06-05 07:04:55 -0400448 private boolean mMinTimeoutPassed = false;
449
450 private void removeAllMessages() {
451 removeMessages(LIST_VIEW_UPDATE_MESSAGE);
452 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
453 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
454 removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
455 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
456 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
Song Hu5aab2d32020-04-30 10:07:15 -0700457 removeMessages(CHOOSER_TARGET_RANKING_SCORE);
Matt Pietalab73a882019-06-05 07:04:55 -0400458 }
459
460 private void restartServiceRequestTimer() {
461 mMinTimeoutPassed = false;
462 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
463 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
464
465 if (DEBUG) {
466 Log.d(TAG, "queryTargets setting watchdog timer for "
Song Hue2deffd2020-03-09 15:22:29 -0700467 + mDirectShareTimeout + "-"
Matt Pietalab73a882019-06-05 07:04:55 -0400468 + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
469 }
470
471 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
472 WATCHDOG_TIMEOUT_MIN_MILLIS);
473 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
Song Hue2deffd2020-03-09 15:22:29 -0700474 mAppendDirectShareEnabled ? mDirectShareTimeout : WATCHDOG_TIMEOUT_MAX_MILLIS);
Matt Pietalab73a882019-06-05 07:04:55 -0400475 }
476
477 private void maybeStopServiceRequestTimer() {
478 // Set a minimum timeout threshold, to ensure both apis, sharing shortcuts
479 // and older-style direct share services, have had time to load, otherwise
480 // just checking mServiceConnections could force us to end prematurely
481 if (mMinTimeoutPassed && mServiceConnections.isEmpty()) {
482 logDirectShareTargetReceived(
483 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
484 sendVoiceChoicesIfNeeded();
arangelova3912cf2019-12-13 14:34:45 +0000485 mChooserMultiProfilePagerAdapter.getActiveListAdapter()
arangelov38a6fce2019-12-02 18:21:22 +0000486 .completeServiceTargetLoading();
Matt Pietalab73a882019-06-05 07:04:55 -0400487 }
488 }
489
Adam Powell24428412015-04-01 17:19:56 -0700490 @Override
491 public void handleMessage(Message msg) {
arangelova3912cf2019-12-13 14:34:45 +0000492 if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == null || isDestroyed()) {
Matt Pietalaf044ae2019-03-29 06:53:53 -0400493 return;
494 }
495
Adam Powell24428412015-04-01 17:19:56 -0700496 switch (msg.what) {
497 case CHOOSER_TARGET_SERVICE_RESULT:
498 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
Adam Powell24428412015-04-01 17:19:56 -0700499 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
500 if (!mServiceConnections.contains(sri.connection)) {
501 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
Song Hue2deffd2020-03-09 15:22:29 -0700502 + sri.originalTarget.getResolveInfo().activityInfo.packageName
Adam Powell24428412015-04-01 17:19:56 -0700503 + " returned after being removed from active connections."
504 + " Have you considered returning results faster?");
505 break;
506 }
Adam Powella182e452015-07-06 16:57:56 -0700507 if (sri.resultTargets != null) {
arangelov5fc9e7d2020-01-07 17:59:14 +0000508 ChooserListAdapter adapterForUserHandle =
509 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
510 sri.userHandle);
511 if (adapterForUserHandle != null) {
512 adapterForUserHandle.addServiceResults(sri.originalTarget,
513 sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET,
Song Hue2deffd2020-03-09 15:22:29 -0700514 /* directShareShortcutInfoCache */ null, mServiceConnections);
Song Huc6297c82020-06-15 23:30:53 -0700515 if (!sri.resultTargets.isEmpty() && sri.originalTarget != null) {
516 mChooserTargetComponentNameCache.put(
517 sri.resultTargets.get(0).getComponentName(),
518 sri.originalTarget.getResolvedComponentName());
519 }
arangelov5fc9e7d2020-01-07 17:59:14 +0000520 }
Adam Powella182e452015-07-06 16:57:56 -0700521 }
Adam Powell24428412015-04-01 17:19:56 -0700522 unbindService(sri.connection);
Adam Powell9761ab22015-09-08 17:01:49 -0700523 sri.connection.destroy();
Adam Powell24428412015-04-01 17:19:56 -0700524 mServiceConnections.remove(sri.connection);
Matt Pietalab73a882019-06-05 07:04:55 -0400525 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700526 break;
527
Matt Pietalab73a882019-06-05 07:04:55 -0400528 case CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT:
529 mMinTimeoutPassed = true;
530 maybeStopServiceRequestTimer();
531 break;
Matt Pietalaf044ae2019-03-29 06:53:53 -0400532
Matt Pietalab73a882019-06-05 07:04:55 -0400533 case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
Song Hue2deffd2020-03-09 15:22:29 -0700534 mMinTimeoutPassed = true;
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500535 if (!mServiceConnections.isEmpty()) {
536 getChooserActivityLogger().logSharesheetDirectLoadTimeout();
537 }
Adam Powell24428412015-04-01 17:19:56 -0700538 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -0400539 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700540 break;
541
Matt Pietalaf044ae2019-03-29 06:53:53 -0400542 case LIST_VIEW_UPDATE_MESSAGE:
543 if (DEBUG) {
544 Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
545 }
546
arangelov5fc9e7d2020-01-07 17:59:14 +0000547 UserHandle userHandle = (UserHandle) msg.obj;
548 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(userHandle)
549 .refreshListView();
Matt Pietalaf044ae2019-03-29 06:53:53 -0400550 break;
551
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800552 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
553 if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800554 final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
555 if (resultInfo.resultTargets != null) {
arangelov5fc9e7d2020-01-07 17:59:14 +0000556 ChooserListAdapter adapterForUserHandle =
557 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
558 resultInfo.userHandle);
559 if (adapterForUserHandle != null) {
560 adapterForUserHandle.addServiceResults(
561 resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1,
Song Hue2deffd2020-03-09 15:22:29 -0700562 mDirectShareShortcutInfoCache, mServiceConnections);
arangelov5fc9e7d2020-01-07 17:59:14 +0000563 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800564 }
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800565 break;
566
567 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700568 logDirectShareTargetReceived(
569 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800570 sendVoiceChoicesIfNeeded();
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500571 getChooserActivityLogger().logSharesheetDirectLoadComplete();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800572 break;
573
Song Hu5aab2d32020-04-30 10:07:15 -0700574 case CHOOSER_TARGET_RANKING_SCORE:
575 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_RANKING_SCORE");
576 final ChooserTargetRankingInfo scoreInfo = (ChooserTargetRankingInfo) msg.obj;
577 ChooserListAdapter adapterForUserHandle =
578 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
579 scoreInfo.userHandle);
580 if (adapterForUserHandle != null) {
581 adapterForUserHandle.addChooserTargetRankingScore(scoreInfo.scores);
582 }
583 break;
584
Adam Powell24428412015-04-01 17:19:56 -0700585 default:
586 super.handleMessage(msg);
587 }
588 }
589 };
590
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800591 @Override
592 protected void onCreate(Bundle savedInstanceState) {
Kang Li9082f5b2016-12-02 10:56:21 -0800593 final long intentReceivedTime = System.currentTimeMillis();
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500594 getChooserActivityLogger().logSharesheetTriggered();
Mehdi Alizadeh5cc5f712019-09-06 12:17:52 -0700595 // This is the only place this value is being set. Effectively final.
596 mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
597
Kang Li9082f5b2016-12-02 10:56:21 -0800598 mIsSuccessfullySelected = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800599 Intent intent = getIntent();
Dianne Hackborneb034652009-09-07 00:49:58 -0700600 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
Dan Sandlere3d19932019-11-22 11:58:58 -0500601 if (targetParcelable instanceof Uri) {
602 try {
603 targetParcelable = Intent.parseUri(targetParcelable.toString(),
604 Intent.URI_INTENT_SCHEME);
605 } catch (URISyntaxException ex) {
606 // doesn't parse as an intent; let the next test fail and error out
607 }
608 }
609
Dianne Hackborneb034652009-09-07 00:49:58 -0700610 if (!(targetParcelable instanceof Intent)) {
Christopher Tate9d6376a2014-02-12 13:14:10 -0800611 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
Dianne Hackborneb034652009-09-07 00:49:58 -0700612 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800613 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700614 return;
615 }
Adam Powell24428412015-04-01 17:19:56 -0700616 Intent target = (Intent) targetParcelable;
Craig Mautner411d2aed2014-05-08 09:07:43 -0700617 if (target != null) {
Adam Powelle49d9392014-07-17 18:45:19 -0700618 modifyTargetIntent(target);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700619 }
Adam Powell2ed547e2015-04-29 18:45:04 -0700620 Parcelable[] targetsParcelable
621 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
622 if (targetsParcelable != null) {
623 final boolean offset = target == null;
624 Intent[] additionalTargets =
625 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
626 for (int i = 0; i < targetsParcelable.length; i++) {
627 if (!(targetsParcelable[i] instanceof Intent)) {
628 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
629 + targetsParcelable[i]);
630 finish();
631 super.onCreate(null);
632 return;
633 }
634 final Intent additionalTarget = (Intent) targetsParcelable[i];
635 if (i == 0 && target == null) {
636 target = additionalTarget;
637 modifyTargetIntent(target);
638 } else {
639 additionalTargets[offset ? i - 1 : i] = additionalTarget;
640 modifyTargetIntent(additionalTarget);
641 }
642 }
643 setAdditionalTargets(additionalTargets);
644 }
645
Adam Powelle49d9392014-07-17 18:45:19 -0700646 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
Matt Pietal26038402019-01-08 07:29:34 -0500647
648 // Do not allow the title to be changed when sharing content
649 CharSequence title = null;
650 if (target != null) {
Matt Pietal95574b02019-03-13 08:12:25 -0400651 if (!isSendAction(target)) {
Matt Pietal26038402019-01-08 07:29:34 -0500652 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
653 } else {
654 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
655 + " preview title by using EXTRA_TITLE property of the wrapped"
656 + " EXTRA_INTENT.");
657 }
658 }
659
Adam Powell278902c2014-07-12 18:33:22 -0700660 int defaultTitleRes = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800661 if (title == null) {
Adam Powell278902c2014-07-12 18:33:22 -0700662 defaultTitleRes = com.android.internal.R.string.chooseActivity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800663 }
Matt Pietal26038402019-01-08 07:29:34 -0500664
Dianne Hackborneb034652009-09-07 00:49:58 -0700665 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
666 Intent[] initialIntents = null;
667 if (pa != null) {
Matt Pietal4e2e3632019-04-05 08:32:47 -0400668 int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS);
669 initialIntents = new Intent[count];
670 for (int i = 0; i < count; i++) {
Dianne Hackborneb034652009-09-07 00:49:58 -0700671 if (!(pa[i] instanceof Intent)) {
Adam Powell2ed547e2015-04-29 18:45:04 -0700672 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
Dianne Hackborneb034652009-09-07 00:49:58 -0700673 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800674 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700675 return;
676 }
Craig Mautner411d2aed2014-05-08 09:07:43 -0700677 final Intent in = (Intent) pa[i];
Adam Powelle49d9392014-07-17 18:45:19 -0700678 modifyTargetIntent(in);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700679 initialIntents[i] = in;
Dianne Hackborneb034652009-09-07 00:49:58 -0700680 }
681 }
Adam Powell24428412015-04-01 17:19:56 -0700682
Adam Powell13036be2015-05-12 14:43:56 -0700683 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
684
Adam Powell0b3c1122014-10-09 12:50:14 -0700685 mChosenComponentSender = intent.getParcelableExtra(
686 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
Adam Powell2ed547e2015-04-29 18:45:04 -0700687 mRefinementIntentSender = intent.getParcelableExtra(
688 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
Dianne Hackborn028ceeb2014-08-17 17:45:48 -0700689 setSafeForwardingMode(true);
Adam Powell23882512016-01-29 10:21:00 -0800690
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500691 mPinnedSharedPrefs = getPinnedSharedPrefs(this);
692
Adam Powell52c39212016-04-07 15:14:18 -0700693 pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
Alison Cichowlas3d88dcc2020-06-19 10:14:51 -0400694
695
696 // Exclude out Nearby from main list if chip is present, to avoid duplication
697 ComponentName nearbySharingComponent = getNearbySharingComponent();
698 boolean hasNearby = nearbySharingComponent != null;
699
Adam Powell52c39212016-04-07 15:14:18 -0700700 if (pa != null) {
Alison Cichowlas3d88dcc2020-06-19 10:14:51 -0400701 ComponentName[] names = new ComponentName[pa.length + (hasNearby ? 1 : 0)];
Adam Powell52c39212016-04-07 15:14:18 -0700702 for (int i = 0; i < pa.length; i++) {
703 if (!(pa[i] instanceof ComponentName)) {
704 Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
705 names = null;
706 break;
707 }
708 names[i] = (ComponentName) pa[i];
709 }
Alison Cichowlas3d88dcc2020-06-19 10:14:51 -0400710 if (hasNearby) {
711 names[names.length - 1] = nearbySharingComponent;
712 }
713
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800714 mFilteredComponentNames = names;
Alison Cichowlas3d88dcc2020-06-19 10:14:51 -0400715 } else if (hasNearby) {
716 mFilteredComponentNames = new ComponentName[1];
717 mFilteredComponentNames[0] = nearbySharingComponent;
Adam Powell52c39212016-04-07 15:14:18 -0700718 }
719
720 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
721 if (pa != null) {
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400722 int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS);
723 ChooserTarget[] targets = new ChooserTarget[count];
724 for (int i = 0; i < count; i++) {
Adam Powell52c39212016-04-07 15:14:18 -0700725 if (!(pa[i] instanceof ChooserTarget)) {
726 Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
727 targets = null;
728 break;
729 }
730 targets[i] = (ChooserTarget) pa[i];
731 }
732 mCallerChooserTargets = targets;
733 }
734
arangelov706316d2020-05-06 14:25:06 +0100735 mShouldDisplayLandscape = shouldDisplayLandscape(
736 getResources().getConfiguration().orientation);
Jorim Jaggif631ef72017-02-24 13:49:47 +0100737 setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
Adam Powell278902c2014-07-12 18:33:22 -0700738 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
739 null, false);
Adam Powell98b7f892015-06-19 12:38:45 -0700740
Kang Li9082f5b2016-12-02 10:56:21 -0800741 mChooserShownTime = System.currentTimeMillis();
742 final long systemCost = mChooserShownTime - intentReceivedTime;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500743
744 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500745 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
746 MetricsEvent.PARENT_PROFILE)
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500747 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
748 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
George Hodulik69d4a082019-01-18 11:27:03 -0800749
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500750 mChooserRowServiceSpacing = getResources()
751 .getDimensionPixelSize(R.dimen.chooser_service_spacing);
752
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400753 if (mResolverDrawerLayout != null) {
754 mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
755
756 // expand/shrink direct share 4 -> 8 viewgroup
757 if (isSendAction(target)) {
758 mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
759 }
Matt Pietalb1d629d2019-04-23 11:35:53 -0400760
Mike Digman849a9d12019-04-29 11:20:48 -0700761 mResolverDrawerLayout.setOnCollapsedChangedListener(
762 new ResolverDrawerLayout.OnCollapsedChangedListener() {
763
764 // Only consider one expansion per activity creation
765 private boolean mWrittenOnce = false;
766
767 @Override
768 public void onCollapsedChanged(boolean isCollapsed) {
769 if (!isCollapsed && !mWrittenOnce) {
770 incrementNumSheetExpansions();
771 mWrittenOnce = true;
772 }
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500773 getChooserActivityLogger()
774 .logSharesheetExpansionChanged(isCollapsed);
Mike Digman849a9d12019-04-29 11:20:48 -0700775 }
776 });
Matt Pietal5b648562019-03-12 07:40:26 -0400777 }
778
Kang Li9082f5b2016-12-02 10:56:21 -0800779 if (DEBUG) {
780 Log.d(TAG, "System Time Cost is " + systemCost);
781 }
arangelov5fc9e7d2020-01-07 17:59:14 +0000782
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -0500783 getChooserActivityLogger().logShareStarted(
784 FrameworkStatsLog.SHARESHEET_STARTED,
785 getReferrerPackageName(),
786 target.getType(),
787 initialIntents == null ? 0 : initialIntents.length,
788 mCallerChooserTargets == null ? 0 : mCallerChooserTargets.length,
789 isWorkProfile(),
790 findPreferredContentPreview(getTargetIntent(), getContentResolver()),
791 target.getAction()
792 );
arangelov5fc9e7d2020-01-07 17:59:14 +0000793 mDirectShareShortcutInfoCache = new HashMap<>();
Song Huc6297c82020-06-15 23:30:53 -0700794 mChooserTargetComponentNameCache = new HashMap<>();
arangelov5fc9e7d2020-01-07 17:59:14 +0000795 }
796
Mike Digmand45a8b52020-05-13 12:32:19 -0700797 @Override
798 protected int appliedThemeResId() {
799 return R.style.Theme_DeviceDefault_Chooser;
800 }
801
arangelov5fc9e7d2020-01-07 17:59:14 +0000802 private AppPredictor setupAppPredictorForUser(UserHandle userHandle,
803 AppPredictor.Callback appPredictorCallback) {
804 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
805 if (appPredictor == null) {
806 return null;
807 }
808 mDirectShareAppTargetCache = new HashMap<>();
809 appPredictor.registerPredictionUpdates(this.getMainExecutor(), appPredictorCallback);
810 return appPredictor;
811 }
812
813 private AppPredictor.Callback createAppPredictorCallback(
814 ChooserListAdapter chooserListAdapter) {
815 return resultList -> {
arangelov5fc9e7d2020-01-07 17:59:14 +0000816 if (isFinishing() || isDestroyed()) {
817 return;
818 }
819 if (chooserListAdapter.getCount() == 0) {
820 return;
821 }
822 if (resultList.isEmpty()) {
823 // APS may be disabled, so try querying targets ourselves.
arangelov5fc9e7d2020-01-07 17:59:14 +0000824 queryDirectShareTargets(chooserListAdapter, true);
825 return;
826 }
827 final List<DisplayResolveInfo> driList =
828 getDisplayResolveInfos(chooserListAdapter);
829 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
830 new ArrayList<>();
Song Hu5aab2d32020-04-30 10:07:15 -0700831
832 // Separate ChooserTargets ranking scores and ranked Shortcuts.
833 List<AppTarget> shortcutResults = new ArrayList<>();
834 List<AppTarget> chooserTargetScores = new ArrayList<>();
arangelov5fc9e7d2020-01-07 17:59:14 +0000835 for (AppTarget appTarget : resultList) {
836 if (appTarget.getShortcutInfo() == null) {
837 continue;
838 }
Song Hu5aab2d32020-04-30 10:07:15 -0700839 if (appTarget.getShortcutInfo().getId().equals(CHOOSER_TARGET)) {
840 chooserTargetScores.add(appTarget);
841 } else {
842 shortcutResults.add(appTarget);
843 }
844 }
845 resultList = shortcutResults;
846 if (mChooserTargetRankingEnabled) {
847 sendChooserTargetRankingScore(chooserTargetScores,
848 chooserListAdapter.getUserHandle());
849 }
850 for (AppTarget appTarget : resultList) {
arangelov5fc9e7d2020-01-07 17:59:14 +0000851 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
852 appTarget.getShortcutInfo(),
853 new ComponentName(
854 appTarget.getPackageName(), appTarget.getClassName())));
855 }
856 sendShareShortcutInfoList(shareShortcutInfos, driList, resultList,
857 chooserListAdapter.getUserHandle());
858 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800859 }
Adam Powelle49d9392014-07-17 18:45:19 -0700860
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500861 static SharedPreferences getPinnedSharedPrefs(Context context) {
862 // The code below is because in the android:ui process, no one can hear you scream.
863 // The package info in the context isn't initialized in the way it is for normal apps,
864 // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
865 // build the path manually below using the same policy that appears in ContextImpl.
866 // This fails silently under the hood if there's a problem, so if we find ourselves in
867 // the case where we don't have access to credential encrypted storage we just won't
868 // have our pinned target info.
869 final File prefsFile = new File(new File(
870 Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
871 context.getUserId(), context.getPackageName()),
872 "shared_prefs"),
873 PINNED_SHARED_PREFS_NAME + ".xml");
874 return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
875 }
876
arangelov38a6fce2019-12-02 18:21:22 +0000877 @Override
878 protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
879 Intent[] initialIntents,
880 List<ResolveInfo> rList,
881 boolean filterLastUsed) {
arangelovf163a882020-02-18 17:18:47 +0000882 if (shouldShowTabs()) {
arangelov38a6fce2019-12-02 18:21:22 +0000883 mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles(
884 initialIntents, rList, filterLastUsed);
885 } else {
886 mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile(
887 initialIntents, rList, filterLastUsed);
888 }
889 return mChooserMultiProfilePagerAdapter;
890 }
891
892 private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
893 Intent[] initialIntents,
894 List<ResolveInfo> rList,
895 boolean filterLastUsed) {
896 ChooserGridAdapter adapter = createChooserGridAdapter(
897 /* context */ this,
898 /* payloadIntents */ mIntents,
899 initialIntents,
900 rList,
901 filterLastUsed,
arangelov38a6fce2019-12-02 18:21:22 +0000902 /* userHandle */ UserHandle.of(UserHandle.myUserId()));
903 return new ChooserMultiProfilePagerAdapter(
904 /* context */ this,
arangelov7981b122020-01-16 10:58:27 +0000905 adapter,
906 getPersonalProfileUserHandle(),
arangelov993d63b2020-04-29 15:48:49 +0100907 /* workProfileUserHandle= */ null,
908 isSendAction(getTargetIntent()));
arangelov38a6fce2019-12-02 18:21:22 +0000909 }
910
911 private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
912 Intent[] initialIntents,
913 List<ResolveInfo> rList,
914 boolean filterLastUsed) {
arangelovacc82432020-04-28 12:55:55 +0100915 int selectedProfile = findSelectedProfile();
arangelov38a6fce2019-12-02 18:21:22 +0000916 ChooserGridAdapter personalAdapter = createChooserGridAdapter(
917 /* context */ this,
918 /* payloadIntents */ mIntents,
arangelovacc82432020-04-28 12:55:55 +0100919 selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
arangelov38a6fce2019-12-02 18:21:22 +0000920 rList,
921 filterLastUsed,
arangelov38a6fce2019-12-02 18:21:22 +0000922 /* userHandle */ getPersonalProfileUserHandle());
923 ChooserGridAdapter workAdapter = createChooserGridAdapter(
924 /* context */ this,
925 /* payloadIntents */ mIntents,
arangelovacc82432020-04-28 12:55:55 +0100926 selectedProfile == PROFILE_WORK ? initialIntents : null,
arangelov38a6fce2019-12-02 18:21:22 +0000927 rList,
928 filterLastUsed,
arangelov38a6fce2019-12-02 18:21:22 +0000929 /* userHandle */ getWorkProfileUserHandle());
930 return new ChooserMultiProfilePagerAdapter(
931 /* context */ this,
932 personalAdapter,
933 workAdapter,
arangelovc7581392020-04-07 17:10:36 +0100934 selectedProfile,
arangelov7981b122020-01-16 10:58:27 +0000935 getPersonalProfileUserHandle(),
arangelov993d63b2020-04-29 15:48:49 +0100936 getWorkProfileUserHandle(),
937 isSendAction(getTargetIntent()));
arangelov38a6fce2019-12-02 18:21:22 +0000938 }
939
arangelovc7581392020-04-07 17:10:36 +0100940 private int findSelectedProfile() {
arangeloveb7793a2020-05-11 16:57:33 +0100941 int selectedProfile = getSelectedProfileExtra();
942 if (selectedProfile == -1) {
arangelovc7581392020-04-07 17:10:36 +0100943 selectedProfile = getProfileForUser(getUser());
944 }
945 return selectedProfile;
946 }
947
arangelov38a6fce2019-12-02 18:21:22 +0000948 @Override
949 protected boolean postRebuildList(boolean rebuildCompleted) {
arangelov590fba32020-02-11 18:05:42 +0000950 updateStickyContentPreview();
951 if (shouldShowStickyContentPreview()
952 || mChooserMultiProfilePagerAdapter
953 .getCurrentRootAdapter().getContentPreviewRowCount() != 0) {
954 logActionShareWithPreview();
955 }
arangelov38a6fce2019-12-02 18:21:22 +0000956 return postRebuildListInternal(rebuildCompleted);
957 }
958
Matt Pietal26038402019-01-08 07:29:34 -0500959 /**
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700960 * Returns true if app prediction service is defined and the component exists on device.
961 */
Mehdi Alizadehe870e972019-09-11 17:54:15 -0700962 @VisibleForTesting
963 public boolean isAppPredictionServiceAvailable() {
964 if (getPackageManager().getAppPredictionServicePackageName() == null) {
965 // Default AppPredictionService is not defined.
966 return false;
967 }
968
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700969 final String appPredictionServiceName =
970 getString(R.string.config_defaultAppPredictionService);
971 if (appPredictionServiceName == null) {
972 return false;
973 }
974 final ComponentName appPredictionComponentName =
975 ComponentName.unflattenFromString(appPredictionServiceName);
976 if (appPredictionComponentName == null) {
977 return false;
978 }
979
980 // Check if the app prediction component actually exists on the device.
981 Intent intent = new Intent();
982 intent.setComponent(appPredictionComponentName);
983 if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) {
984 Log.e(TAG, "App prediction service is defined, but does not exist: "
985 + appPredictionServiceName);
986 return false;
987 }
988 return true;
989 }
990
991 /**
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500992 * Check if the profile currently used is a work profile.
993 * @return true if it is work profile, false if it is parent profile (or no work profile is
994 * set up)
995 */
996 protected boolean isWorkProfile() {
arangelov38a6fce2019-12-02 18:21:22 +0000997 return getSystemService(UserManager.class)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500998 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
999 }
1000
Matt Pietalab73a882019-06-05 07:04:55 -04001001 @Override
arangelov7981b122020-01-16 10:58:27 +00001002 protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) {
Matt Pietalab73a882019-06-05 07:04:55 -04001003 return new PackageMonitor() {
1004 @Override
1005 public void onSomePackagesChanged() {
arangelov7981b122020-01-16 10:58:27 +00001006 handlePackagesChanged(listAdapter);
Matt Pietalab73a882019-06-05 07:04:55 -04001007 }
1008 };
1009 }
1010
Alison Cichowlasbc290812019-12-17 19:42:29 -05001011 /**
1012 * Update UI to reflect changes in data.
1013 */
1014 public void handlePackagesChanged() {
arangelov7981b122020-01-16 10:58:27 +00001015 handlePackagesChanged(/* listAdapter */ null);
1016 }
1017
1018 /**
1019 * Update UI to reflect changes in data.
Alison Cichowlasc3913622020-06-23 13:27:09 -04001020 * <p>If {@code listAdapter} is {@code null}, both profile list adapters are updated if
1021 * available.
arangelov7981b122020-01-16 10:58:27 +00001022 */
1023 private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) {
Alison Cichowlasd5c946d2020-06-21 10:14:51 -04001024 // Refresh pinned items
1025 mPinnedSharedPrefs = getPinnedSharedPrefs(this);
arangelov7981b122020-01-16 10:58:27 +00001026 if (listAdapter == null) {
1027 mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
Alison Cichowlasc3913622020-06-23 13:27:09 -04001028 if (mChooserMultiProfilePagerAdapter.getCount() > 1) {
1029 mChooserMultiProfilePagerAdapter.getInactiveListAdapter().handlePackagesChanged();
1030 }
arangelov7981b122020-01-16 10:58:27 +00001031 } else {
1032 listAdapter.handlePackagesChanged();
1033 }
Alison Cichowlasbc290812019-12-17 19:42:29 -05001034 updateProfileViewButton();
1035 }
1036
Matt Pietal46d828c2019-02-05 08:07:07 -05001037 private void onCopyButtonClicked(View v) {
1038 Intent targetIntent = getTargetIntent();
1039 if (targetIntent == null) {
1040 finish();
1041 } else {
1042 final String action = targetIntent.getAction();
1043
1044 ClipData clipData = null;
1045 if (Intent.ACTION_SEND.equals(action)) {
1046 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
1047 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1048
1049 if (extraText != null) {
1050 clipData = ClipData.newPlainText(null, extraText);
1051 } else if (extraStream != null) {
1052 clipData = ClipData.newUri(getContentResolver(), null, extraStream);
1053 } else {
1054 Log.w(TAG, "No data available to copy to clipboard");
1055 return;
1056 }
1057 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1058 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
1059 Intent.EXTRA_STREAM);
1060 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
1061 for (int i = 1; i < streams.size(); i++) {
1062 clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
1063 }
1064 } else {
1065 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
1066 // so warn about unexpected action
1067 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
1068 return;
1069 }
1070
1071 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
1072 Context.CLIPBOARD_SERVICE);
1073 clipboardManager.setPrimaryClip(clipData);
1074 Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
1075
Alison Cichowlasaa7f79f2019-09-12 15:57:26 -04001076 // Log share completion via copy
1077 LogMaker targetLogMaker = new LogMaker(
1078 MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
1079 getMetricsLogger().write(targetLogMaker);
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05001080 getChooserActivityLogger().logShareTargetSelected(
1081 SELECTION_TYPE_COPY,
1082 "",
1083 -1);
Alison Cichowlasaa7f79f2019-09-12 15:57:26 -04001084
Matt Pietal46d828c2019-02-05 08:07:07 -05001085 finish();
1086 }
1087 }
1088
Matt Pietal18bbd822019-02-12 15:21:36 -05001089 @Override
1090 public void onConfigurationChanged(Configuration newConfig) {
1091 super.onConfigurationChanged(newConfig);
arangelov5f416b92020-06-17 13:12:09 +01001092 ViewPager viewPager = findViewById(R.id.profile_pager);
1093 if (shouldShowTabs() && viewPager.isLayoutRtl()) {
1094 mMultiProfilePagerAdapter.setupViewPager(viewPager);
1095 }
Matt Pietal18bbd822019-02-12 15:21:36 -05001096
arangelov706316d2020-05-06 14:25:06 +01001097 mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation);
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001098 adjustPreviewWidth(newConfig.orientation, null);
arangelovc4dbdbd2020-02-18 20:54:16 +00001099 updateStickyContentPreview();
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001100 }
1101
1102 private boolean shouldDisplayLandscape(int orientation) {
1103 // Sharesheet fixes the # of items per row and therefore can not correctly lay out
1104 // when in the restricted size of multi-window mode. In the future, would be nice
1105 // to use minimum dp size requirements instead
1106 return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
1107 }
1108
1109 private void adjustPreviewWidth(int orientation, View parent) {
Matt Pietal18bbd822019-02-12 15:21:36 -05001110 int width = -1;
arangelov706316d2020-05-06 14:25:06 +01001111 if (mShouldDisplayLandscape) {
Matt Pietal18bbd822019-02-12 15:21:36 -05001112 width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
1113 }
1114
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001115 parent = parent == null ? getWindow().getDecorView() : parent;
1116
1117 updateLayoutWidth(R.id.content_preview_text_layout, width, parent);
1118 updateLayoutWidth(R.id.content_preview_title_layout, width, parent);
1119 updateLayoutWidth(R.id.content_preview_file_layout, width, parent);
Matt Pietal18bbd822019-02-12 15:21:36 -05001120 }
1121
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001122 private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
1123 View view = parent.findViewById(layoutResourceId);
Matt Pietal1ef88002019-03-13 10:43:18 -04001124 if (view != null && view.getLayoutParams() != null) {
1125 LayoutParams params = view.getLayoutParams();
1126 params.width = width;
1127 view.setLayoutParams(params);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001128 }
1129 }
1130
arangelova3912cf2019-12-13 14:34:45 +00001131 private ViewGroup createContentPreviewView(ViewGroup parent) {
1132 Intent targetIntent = getTargetIntent();
1133 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
1134 return displayContentPreview(previewType, targetIntent, getLayoutInflater(), parent);
1135 }
1136
Dan Sandlere3d19932019-11-22 11:58:58 -05001137 private ComponentName getNearbySharingComponent() {
1138 String nearbyComponent = Settings.Secure.getString(
1139 getContentResolver(),
1140 Settings.Secure.NEARBY_SHARING_COMPONENT);
1141 if (TextUtils.isEmpty(nearbyComponent)) {
1142 nearbyComponent = getString(R.string.config_defaultNearbySharingComponent);
1143 }
1144 if (TextUtils.isEmpty(nearbyComponent)) {
1145 return null;
1146 }
1147 return ComponentName.unflattenFromString(nearbyComponent);
1148 }
1149
1150 private TargetInfo getNearbySharingTarget(Intent originalIntent) {
1151 final ComponentName cn = getNearbySharingComponent();
1152 if (cn == null) return null;
1153
Dan Sandlerb18b59f2020-06-05 12:35:56 -04001154 final Intent resolveIntent = new Intent(originalIntent);
Dan Sandlere3d19932019-11-22 11:58:58 -05001155 resolveIntent.setComponent(cn);
1156 final ResolveInfo ri = getPackageManager().resolveActivity(
1157 resolveIntent, PackageManager.GET_META_DATA);
1158 if (ri == null || ri.activityInfo == null) {
1159 Log.e(TAG, "Device-specified nearby sharing component (" + cn
1160 + ") not available");
1161 return null;
1162 }
1163
1164 // Allow the nearby sharing component to provide a more appropriate icon and label
1165 // for the chip.
1166 CharSequence name = null;
1167 Drawable icon = null;
1168 final Bundle metaData = ri.activityInfo.metaData;
1169 if (metaData != null) {
1170 try {
1171 final Resources pkgRes = getPackageManager().getResourcesForActivity(cn);
1172 final int nameResId = metaData.getInt(CHIP_LABEL_METADATA_KEY);
1173 name = pkgRes.getString(nameResId);
1174 final int resId = metaData.getInt(CHIP_ICON_METADATA_KEY);
1175 icon = pkgRes.getDrawable(resId);
1176 } catch (Resources.NotFoundException ex) {
1177 } catch (NameNotFoundException ex) {
1178 }
1179 }
1180 if (TextUtils.isEmpty(name)) {
1181 name = ri.loadLabel(getPackageManager());
1182 }
1183 if (icon == null) {
1184 icon = ri.loadIcon(getPackageManager());
1185 }
1186
1187 final DisplayResolveInfo dri = new DisplayResolveInfo(
1188 originalIntent, ri, name, "", resolveIntent, null);
1189 dri.setDisplayIcon(icon);
1190 return dri;
1191 }
1192
1193 private Button createActionButton(Drawable icon, CharSequence title, View.OnClickListener r) {
1194 Button b = (Button) LayoutInflater.from(this).inflate(R.layout.chooser_action_button, null);
1195 if (icon != null) {
1196 final int size = getResources()
1197 .getDimensionPixelSize(R.dimen.chooser_action_button_icon_size);
1198 icon.setBounds(0, 0, size, size);
1199 b.setCompoundDrawablesRelative(icon, null, null, null);
1200 }
1201 b.setText(title);
1202 b.setOnClickListener(r);
1203 return b;
1204 }
1205
1206 private Button createCopyButton() {
1207 final Button b = createActionButton(
1208 getDrawable(R.drawable.ic_menu_copy_material),
1209 getString(R.string.copy), this::onCopyButtonClicked);
1210 b.setId(R.id.chooser_copy_button);
1211 return b;
1212 }
1213
1214 private @Nullable Button createNearbyButton(Intent originalIntent) {
1215 final TargetInfo ti = getNearbySharingTarget(originalIntent);
1216 if (ti == null) return null;
1217
1218 return createActionButton(
1219 ti.getDisplayIcon(this),
1220 ti.getDisplayLabel(),
1221 (View unused) -> {
1222 safelyStartActivity(ti);
1223 finish();
1224 }
1225 );
1226 }
1227
1228 private void addActionButton(ViewGroup parent, Button b) {
1229 if (b == null) return;
1230 final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(
1231 LayoutParams.WRAP_CONTENT,
1232 LayoutParams.WRAP_CONTENT
1233 );
1234 final int gap = getResources().getDimensionPixelSize(R.dimen.resolver_icon_margin) / 2;
1235 lp.setMarginsRelative(gap, 0, gap, 0);
1236 parent.addView(b, lp);
1237 }
1238
Matt Pietal1ef88002019-03-13 10:43:18 -04001239 private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
Zhen Zhangbde7b462019-11-11 11:49:33 -08001240 Intent targetIntent, LayoutInflater layoutInflater, ViewGroup parent) {
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001241 ViewGroup layout = null;
1242
Matt Pietal1ef88002019-03-13 10:43:18 -04001243 switch (previewType) {
1244 case CONTENT_PREVIEW_TEXT:
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001245 layout = displayTextContentPreview(targetIntent, layoutInflater, parent);
1246 break;
Matt Pietal1ef88002019-03-13 10:43:18 -04001247 case CONTENT_PREVIEW_IMAGE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001248 layout = displayImageContentPreview(targetIntent, layoutInflater, parent);
1249 break;
Matt Pietal1ef88002019-03-13 10:43:18 -04001250 case CONTENT_PREVIEW_FILE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001251 layout = displayFileContentPreview(targetIntent, layoutInflater, parent);
1252 break;
Matt Pietal1ef88002019-03-13 10:43:18 -04001253 default:
1254 Log.e(TAG, "Unexpected content preview type: " + previewType);
1255 }
Matt Pietal0ea391b2019-01-30 10:44:15 -05001256
Matt Pietal3e4b56f2019-05-31 12:06:17 -04001257 if (layout != null) {
1258 adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
1259 }
1260
1261 return layout;
Matt Pietal1ef88002019-03-13 10:43:18 -04001262 }
1263
1264 private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001265 ViewGroup parent) {
1266 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1267 R.layout.chooser_grid_preview_text, parent, false);
Matt Pietal1ef88002019-03-13 10:43:18 -04001268
Dan Sandlere3d19932019-11-22 11:58:58 -05001269 final ViewGroup actionRow =
1270 (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
1271 addActionButton(actionRow, createCopyButton());
1272 addActionButton(actionRow, createNearbyButton(targetIntent));
Matt Pietal46d828c2019-02-05 08:07:07 -05001273
Matt Pietal26038402019-01-08 07:29:34 -05001274 CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
Matt Pietal26038402019-01-08 07:29:34 -05001275 if (sharingText == null) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001276 contentPreviewLayout.findViewById(R.id.content_preview_text_layout).setVisibility(
1277 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -05001278 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -04001279 TextView textView = contentPreviewLayout.findViewById(R.id.content_preview_text);
Matt Pietal1fa7d802019-01-30 10:44:15 -05001280 textView.setText(sharingText);
Matt Pietal26038402019-01-08 07:29:34 -05001281 }
1282
1283 String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
Matt Pietal46d828c2019-02-05 08:07:07 -05001284 if (TextUtils.isEmpty(previewTitle)) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001285 contentPreviewLayout.findViewById(R.id.content_preview_title_layout).setVisibility(
1286 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -05001287 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -04001288 TextView previewTitleView = contentPreviewLayout.findViewById(
1289 R.id.content_preview_title);
Matt Pietal26038402019-01-08 07:29:34 -05001290 previewTitleView.setText(previewTitle);
Matt Pietal26038402019-01-08 07:29:34 -05001291
Matt Pietal1fa7d802019-01-30 10:44:15 -05001292 ClipData previewData = targetIntent.getClipData();
1293 Uri previewThumbnail = null;
1294 if (previewData != null) {
1295 if (previewData.getItemCount() > 0) {
1296 ClipData.Item previewDataItem = previewData.getItemAt(0);
1297 previewThumbnail = previewDataItem.getUri();
1298 }
Matt Pietal26038402019-01-08 07:29:34 -05001299 }
Matt Pietal26038402019-01-08 07:29:34 -05001300
Matt Pietal1ef88002019-03-13 10:43:18 -04001301 ImageView previewThumbnailView = contentPreviewLayout.findViewById(
1302 R.id.content_preview_thumbnail);
Matt Pietal1fa7d802019-01-30 10:44:15 -05001303 if (previewThumbnail == null) {
Matt Pietal26038402019-01-08 07:29:34 -05001304 previewThumbnailView.setVisibility(View.GONE);
1305 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -04001306 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
1307 mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
Matt Pietal26038402019-01-08 07:29:34 -05001308 }
1309 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001310
1311 return contentPreviewLayout;
Matt Pietal26038402019-01-08 07:29:34 -05001312 }
1313
Matt Pietal1ef88002019-03-13 10:43:18 -04001314 private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001315 ViewGroup parent) {
1316 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1317 R.layout.chooser_grid_preview_image, parent, false);
Dan Sandlerb18b59f2020-06-05 12:35:56 -04001318
1319 final ViewGroup actionRow =
1320 (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
1321 //TODO: addActionButton(actionRow, createCopyButton());
1322 addActionButton(actionRow, createNearbyButton(targetIntent));
1323
Matt Pietale7cacab2019-05-23 07:21:36 -04001324 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001325
1326 String action = targetIntent.getAction();
1327 if (Intent.ACTION_SEND.equals(action)) {
1328 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietale7cacab2019-05-23 07:21:36 -04001329 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001330 } else {
1331 ContentResolver resolver = getContentResolver();
1332
1333 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1334 List<Uri> imageUris = new ArrayList<>();
1335 for (Uri uri : uris) {
1336 if (isImageType(resolver.getType(uri))) {
1337 imageUris.add(uri);
1338 }
1339 }
1340
1341 if (imageUris.size() == 0) {
1342 Log.i(TAG, "Attempted to display image preview area with zero"
1343 + " available images detected in EXTRA_STREAM list");
Matt Pietal46d828c2019-02-05 08:07:07 -05001344 contentPreviewLayout.setVisibility(View.GONE);
Matt Pietal1ef88002019-03-13 10:43:18 -04001345 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001346 }
1347
Matt Pietale7cacab2019-05-23 07:21:36 -04001348 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001349
1350 if (imageUris.size() == 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001351 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
1352 imageUris.get(1), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001353 } else if (imageUris.size() > 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001354 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
1355 imageUris.get(1), 0);
1356 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
1357 imageUris.get(2), imageUris.size() - 3);
Matt Pietal0ea391b2019-01-30 10:44:15 -05001358 }
1359 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001360
1361 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001362 }
1363
Matt Pietal46d828c2019-02-05 08:07:07 -05001364 private static class FileInfo {
1365 public final String name;
1366 public final boolean hasThumbnail;
1367
1368 FileInfo(String name, boolean hasThumbnail) {
1369 this.name = name;
1370 this.hasThumbnail = hasThumbnail;
1371 }
1372 }
1373
Matt Pietalf38e9d22019-02-15 10:01:03 -05001374 /**
1375 * Wrapping the ContentResolver call to expose for easier mocking,
1376 * and to avoid mocking Android core classes.
1377 */
1378 @VisibleForTesting
1379 public Cursor queryResolver(ContentResolver resolver, Uri uri) {
1380 return resolver.query(uri, null, null, null, null);
1381 }
1382
Matt Pietal46d828c2019-02-05 08:07:07 -05001383 private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
1384 String fileName = null;
1385 boolean hasThumbnail = false;
Matt Pietal3087bca2019-02-14 12:19:16 -05001386
Matt Pietalf38e9d22019-02-15 10:01:03 -05001387 try (Cursor cursor = queryResolver(resolver, uri)) {
1388 if (cursor != null && cursor.getCount() > 0) {
1389 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
1390 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
1391 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
1392
1393 cursor.moveToFirst();
1394 if (nameIndex != -1) {
1395 fileName = cursor.getString(nameIndex);
1396 } else if (titleIndex != -1) {
1397 fileName = cursor.getString(titleIndex);
1398 }
1399
1400 if (flagsIndex != -1) {
1401 hasThumbnail = (cursor.getInt(flagsIndex)
1402 & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
1403 }
1404 }
Matt Pietal73a873f2019-03-15 08:46:20 -04001405 } catch (SecurityException | NullPointerException e) {
Matt Pietal62532e52019-05-07 09:51:37 -04001406 logContentPreviewWarning(uri);
Matt Pietal3087bca2019-02-14 12:19:16 -05001407 }
1408
Matt Pietal46d828c2019-02-05 08:07:07 -05001409 if (TextUtils.isEmpty(fileName)) {
1410 fileName = uri.getPath();
1411 int index = fileName.lastIndexOf('/');
1412 if (index != -1) {
1413 fileName = fileName.substring(index + 1);
1414 }
1415 }
1416
1417 return new FileInfo(fileName, hasThumbnail);
1418 }
1419
Matt Pietal62532e52019-05-07 09:51:37 -04001420 private void logContentPreviewWarning(Uri uri) {
1421 // The ContentResolver already logs the exception. Log something more informative.
1422 Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
1423 + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
1424 + "and set your Intent's clipData and flags in accordance with that method's "
1425 + "documentation");
1426 }
1427
Matt Pietal1ef88002019-03-13 10:43:18 -04001428 private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001429 ViewGroup parent) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001430
Matt Pietale7cacab2019-05-23 07:21:36 -04001431 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1432 R.layout.chooser_grid_preview_file, parent, false);
Matt Pietal46d828c2019-02-05 08:07:07 -05001433
Dan Sandlerb18b59f2020-06-05 12:35:56 -04001434 final ViewGroup actionRow =
1435 (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
1436 //TODO(b/120417119): addActionButton(actionRow, createCopyButton());
1437 addActionButton(actionRow, createNearbyButton(targetIntent));
1438
Matt Pietal46d828c2019-02-05 08:07:07 -05001439
Matt Pietal3087bca2019-02-14 12:19:16 -05001440 String action = targetIntent.getAction();
1441 if (Intent.ACTION_SEND.equals(action)) {
1442 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietal1ef88002019-03-13 10:43:18 -04001443 loadFileUriIntoView(uri, contentPreviewLayout);
Matt Pietal3087bca2019-02-14 12:19:16 -05001444 } else {
1445 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1446 int uriCount = uris.size();
Matt Pietal46d828c2019-02-05 08:07:07 -05001447
Matt Pietal3087bca2019-02-14 12:19:16 -05001448 if (uriCount == 0) {
1449 contentPreviewLayout.setVisibility(View.GONE);
1450 Log.i(TAG,
1451 "Appears to be no uris available in EXTRA_STREAM, removing "
1452 + "preview area");
Matt Pietal1ef88002019-03-13 10:43:18 -04001453 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001454 } else if (uriCount == 1) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001455 loadFileUriIntoView(uris.get(0), contentPreviewLayout);
Matt Pietal46d828c2019-02-05 08:07:07 -05001456 } else {
Matt Pietal3087bca2019-02-14 12:19:16 -05001457 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
1458 int remUriCount = uriCount - 1;
Matt Pietalacabc572019-02-14 11:02:05 -05001459 String fileName = getResources().getQuantityString(R.plurals.file_count,
Matt Pietal3087bca2019-02-14 12:19:16 -05001460 remUriCount, fileInfo.name, remUriCount);
Matt Pietalacabc572019-02-14 11:02:05 -05001461
Matt Pietal1ef88002019-03-13 10:43:18 -04001462 TextView fileNameView = contentPreviewLayout.findViewById(
1463 R.id.content_preview_filename);
Matt Pietalacabc572019-02-14 11:02:05 -05001464 fileNameView.setText(fileName);
Matt Pietal3087bca2019-02-14 12:19:16 -05001465
Matt Pietale7cacab2019-05-23 07:21:36 -04001466 View thumbnailView = contentPreviewLayout.findViewById(
1467 R.id.content_preview_file_thumbnail);
1468 thumbnailView.setVisibility(View.GONE);
1469
Matt Pietal1ef88002019-03-13 10:43:18 -04001470 ImageView fileIconView = contentPreviewLayout.findViewById(
1471 R.id.content_preview_file_icon);
Matt Pietal46d828c2019-02-05 08:07:07 -05001472 fileIconView.setVisibility(View.VISIBLE);
Matt Pietalacabc572019-02-14 11:02:05 -05001473 fileIconView.setImageResource(R.drawable.ic_file_copy);
Matt Pietal46d828c2019-02-05 08:07:07 -05001474 }
Matt Pietal3087bca2019-02-14 12:19:16 -05001475 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001476
1477 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001478 }
1479
Matt Pietale7cacab2019-05-23 07:21:36 -04001480 private void loadFileUriIntoView(final Uri uri, final View parent) {
Matt Pietal3087bca2019-02-14 12:19:16 -05001481 FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
1482
Matt Pietal1ef88002019-03-13 10:43:18 -04001483 TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
Matt Pietal3087bca2019-02-14 12:19:16 -05001484 fileNameView.setText(fileInfo.name);
1485
1486 if (fileInfo.hasThumbnail) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001487 mPreviewCoord = new ContentPreviewCoordinator(parent, false);
1488 mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
Matt Pietal3087bca2019-02-14 12:19:16 -05001489 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -04001490 View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
1491 thumbnailView.setVisibility(View.GONE);
1492
Matt Pietal1ef88002019-03-13 10:43:18 -04001493 ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
Matt Pietal3087bca2019-02-14 12:19:16 -05001494 fileIconView.setVisibility(View.VISIBLE);
Matt Pietal832cdbf2019-04-05 13:20:31 -04001495 fileIconView.setImageResource(R.drawable.chooser_file_generic);
Matt Pietal46d828c2019-02-05 08:07:07 -05001496 }
Matt Pietal0ea391b2019-01-30 10:44:15 -05001497 }
1498
Matt Pietal0ea391b2019-01-30 10:44:15 -05001499 @VisibleForTesting
1500 protected boolean isImageType(String mimeType) {
1501 return mimeType != null && mimeType.startsWith("image/");
1502 }
1503
1504 @ContentPreviewType
1505 private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
1506 if (uri == null) {
1507 return CONTENT_PREVIEW_TEXT;
1508 }
1509
1510 String mimeType = resolver.getType(uri);
1511 return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
1512 }
1513
1514 /**
1515 * In {@link android.content.Intent#getType}, the app may specify a very general
1516 * mime-type that broadly covers all data being shared, such as {@literal *}/*
1517 * when sending an image and text. We therefore should inspect each item for the
1518 * the preferred type, in order of IMAGE, FILE, TEXT.
1519 */
1520 @ContentPreviewType
1521 private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
1522 String action = targetIntent.getAction();
1523 if (Intent.ACTION_SEND.equals(action)) {
1524 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1525 return findPreferredContentPreview(uri, resolver);
1526 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1527 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1528 if (uris == null || uris.isEmpty()) {
1529 return CONTENT_PREVIEW_TEXT;
1530 }
1531
1532 for (Uri uri : uris) {
Matt Pietal832cdbf2019-04-05 13:20:31 -04001533 // Defaulting to file preview when there are mixed image/file types is
1534 // preferable, as it shows the user the correct number of items being shared
1535 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_FILE) {
1536 return CONTENT_PREVIEW_FILE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001537 }
1538 }
1539
Matt Pietal832cdbf2019-04-05 13:20:31 -04001540 return CONTENT_PREVIEW_IMAGE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001541 }
1542
1543 return CONTENT_PREVIEW_TEXT;
1544 }
1545
Mike Digman849a9d12019-04-29 11:20:48 -07001546 private int getNumSheetExpansions() {
1547 return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0);
1548 }
1549
1550 private void incrementNumSheetExpansions() {
1551 getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS,
1552 getNumSheetExpansions() + 1).apply();
1553 }
1554
Adam Powell0b3c1122014-10-09 12:50:14 -07001555 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07001556 protected void onDestroy() {
1557 super.onDestroy();
1558 if (mRefinementResultReceiver != null) {
1559 mRefinementResultReceiver.destroy();
1560 mRefinementResultReceiver = null;
1561 }
Adam Powell9761ab22015-09-08 17:01:49 -07001562 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -04001563 mChooserHandler.removeAllMessages();
Matt Pietale7cacab2019-05-23 07:21:36 -04001564
1565 if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
1566
arangelov5fc9e7d2020-01-07 17:59:14 +00001567 mChooserMultiProfilePagerAdapter.getActiveListAdapter().destroyAppPredictor();
1568 if (mChooserMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
1569 mChooserMultiProfilePagerAdapter.getInactiveListAdapter().destroyAppPredictor();
George Hodulik69d4a082019-01-18 11:27:03 -08001570 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001571 }
1572
arangelovb0802dc2019-10-18 18:03:44 +01001573 @Override // ResolverListCommunicator
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001574 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
1575 Intent result = defIntent;
Adam Powelle49d9392014-07-17 18:45:19 -07001576 if (mReplacementExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001577 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
Adam Powelle49d9392014-07-17 18:45:19 -07001578 if (replExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001579 result = new Intent(defIntent);
Adam Powelle49d9392014-07-17 18:45:19 -07001580 result.putExtras(replExtras);
Adam Powelle49d9392014-07-17 18:45:19 -07001581 }
1582 }
Nicolas Prevot741abfc2015-08-11 12:03:51 +01001583 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001584 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
1585 result = Intent.createChooser(result,
1586 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
Hakan Seyalioglu7317e8a2016-12-12 16:15:38 -08001587
1588 // Don't auto-launch single intents if the intent is being forwarded. This is done
1589 // because automatically launching a resolving application as a response to the user
1590 // action of switching accounts is pretty unexpected.
1591 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001592 }
1593 return result;
Adam Powelle49d9392014-07-17 18:45:19 -07001594 }
1595
Adam Powell0b3c1122014-10-09 12:50:14 -07001596 @Override
Adam Powell23882512016-01-29 10:21:00 -08001597 public void onActivityStarted(TargetInfo cti) {
Adam Powell0b3c1122014-10-09 12:50:14 -07001598 if (mChosenComponentSender != null) {
Adam Powell24428412015-04-01 17:19:56 -07001599 final ComponentName target = cti.getResolvedComponentName();
Adam Powell0b3c1122014-10-09 12:50:14 -07001600 if (target != null) {
1601 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
1602 try {
1603 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
1604 } catch (IntentSender.SendIntentException e) {
1605 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
1606 + "the chosen component: " + e);
1607 }
1608 }
1609 }
1610 }
1611
Adam Powell24428412015-04-01 17:19:56 -07001612 @Override
arangelovcf268642020-01-15 15:09:51 +00001613 public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
Adam Powell52c39212016-04-07 15:14:18 -07001614 if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
arangelova3912cf2019-12-13 14:34:45 +00001615 mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
arangelov38a6fce2019-12-02 18:21:22 +00001616 /* origTarget */ null,
1617 Lists.newArrayList(mCallerChooserTargets),
arangelov5fc9e7d2020-01-07 17:59:14 +00001618 TARGET_TYPE_DEFAULT,
Song Hue2deffd2020-03-09 15:22:29 -07001619 /* directShareShortcutInfoCache */ null, mServiceConnections);
Adam Powell52c39212016-04-07 15:14:18 -07001620 }
arangelovb0802dc2019-10-18 18:03:44 +01001621 }
1622
1623 @Override
Adam Powell23882512016-01-29 10:21:00 -08001624 public int getLayoutResource() {
Adam Powell7d758002015-05-06 17:49:36 -07001625 return R.layout.chooser_grid;
Adam Powell24428412015-04-01 17:19:56 -07001626 }
1627
arangelovb0802dc2019-10-18 18:03:44 +01001628 @Override // ResolverListCommunicator
Adam Powell23882512016-01-29 10:21:00 -08001629 public boolean shouldGetActivityMetadata() {
Adam Powell24428412015-04-01 17:19:56 -07001630 return true;
1631 }
1632
Adam Powell9761ab22015-09-08 17:01:49 -07001633 @Override
Ben Lin145b0ca2016-10-14 14:23:40 -07001634 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
Hakan Seyalioglu13405c52017-01-31 19:01:31 -08001635 // Note that this is only safe because the Intent handled by the ChooserActivity is
1636 // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
1637 // method can not be replaced in the ResolverActivity whole hog.
Matt Pietala4b30072019-04-04 13:44:36 -04001638 if (!super.shouldAutoLaunchSingleChoice(target)) {
1639 return false;
1640 }
1641
1642 return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
Ben Lin145b0ca2016-10-14 14:23:40 -07001643 }
1644
Mike Digman64c70432020-05-21 11:17:51 -07001645 private void showTargetDetails(DisplayResolveInfo ti) {
1646 if (ti == null) return;
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001647
Mike Digman64c70432020-05-21 11:17:51 -07001648 List<DisplayResolveInfo> targetList;
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001649
1650 // For multiple targets, include info on all targets
1651 if (ti instanceof MultiDisplayResolveInfo) {
1652 MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) ti;
Mike Digman64c70432020-05-21 11:17:51 -07001653 targetList = mti.getTargets();
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001654 } else {
Mike Digman64c70432020-05-21 11:17:51 -07001655 targetList = Collections.singletonList(ti);
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001656 }
1657
Mike Digmandc294792020-05-27 10:31:16 -07001658 ChooserTargetActionsDialogFragment f = new ChooserTargetActionsDialogFragment(
Mike Digman64c70432020-05-21 11:17:51 -07001659 targetList, mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
1660
Adam Powell23882512016-01-29 10:21:00 -08001661 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1662 }
1663
Adam Powelle49d9392014-07-17 18:45:19 -07001664 private void modifyTargetIntent(Intent in) {
Matt Pietal95574b02019-03-13 08:12:25 -04001665 if (isSendAction(in)) {
Adam Powelle49d9392014-07-17 18:45:19 -07001666 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
Dianne Hackborn13420f22014-07-18 15:43:56 -07001667 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Adam Powelle49d9392014-07-17 18:45:19 -07001668 }
1669 }
Adam Powell24428412015-04-01 17:19:56 -07001670
Adam Powell2ed547e2015-04-29 18:45:04 -07001671 @Override
1672 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
1673 if (mRefinementIntentSender != null) {
1674 final Intent fillIn = new Intent();
1675 final List<Intent> sourceIntents = target.getAllSourceIntents();
1676 if (!sourceIntents.isEmpty()) {
1677 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
1678 if (sourceIntents.size() > 1) {
1679 final Intent[] alts = new Intent[sourceIntents.size() - 1];
1680 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
1681 alts[i - 1] = sourceIntents.get(i);
1682 }
1683 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
1684 }
1685 if (mRefinementResultReceiver != null) {
1686 mRefinementResultReceiver.destroy();
1687 }
1688 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
1689 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
1690 mRefinementResultReceiver);
1691 try {
1692 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
1693 return false;
1694 } catch (SendIntentException e) {
1695 Log.e(TAG, "Refinement IntentSender failed to send", e);
1696 }
1697 }
1698 }
Kang Li9fa2a2c2017-01-06 13:33:24 -08001699 updateModelAndChooserCounts(target);
Adam Powell2ed547e2015-04-29 18:45:04 -07001700 return super.onTargetSelected(target, alwaysCheck);
1701 }
1702
Adam Powell98b7f892015-06-19 12:38:45 -07001703 @Override
Adam Powell23882512016-01-29 10:21:00 -08001704 public void startSelected(int which, boolean always, boolean filtered) {
arangelov38a6fce2019-12-02 18:21:22 +00001705 ChooserListAdapter currentListAdapter =
arangelova3912cf2019-12-13 14:34:45 +00001706 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
arangelov38a6fce2019-12-02 18:21:22 +00001707 TargetInfo targetInfo = currentListAdapter
1708 .targetInfoForPosition(which, filtered);
Matt Pietala4b30072019-04-04 13:44:36 -04001709 if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
1710 return;
1711 }
1712
Kang Li9082f5b2016-12-02 10:56:21 -08001713 final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001714
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001715 if (targetInfo instanceof MultiDisplayResolveInfo) {
1716 MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
Alison Cichowlase12616a2019-12-20 19:05:27 -05001717 if (!mti.hasSelected()) {
Alison Cichowlase12616a2019-12-20 19:05:27 -05001718 ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment(
Mike Digmandc294792020-05-27 10:31:16 -07001719 mti, which,
1720 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001721
Alison Cichowlase12616a2019-12-20 19:05:27 -05001722 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1723 return;
1724 }
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001725 }
1726
Adam Powell98b7f892015-06-19 12:38:45 -07001727 super.startSelected(which, always, filtered);
1728
Alison Cichowlas19ee2922019-12-16 19:43:12 -05001729
arangelov38a6fce2019-12-02 18:21:22 +00001730 if (currentListAdapter.getCount() > 0) {
Adam Powell98b7f892015-06-19 12:38:45 -07001731 // Log the index of which type of target the user picked.
1732 // Lower values mean the ranking was better.
1733 int cat = 0;
1734 int value = which;
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001735 int directTargetAlsoRanked = -1;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001736 int numCallerProvided = 0;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001737 HashedStringCache.HashResult directTargetHashed = null;
arangelov38a6fce2019-12-02 18:21:22 +00001738 switch (currentListAdapter.getPositionTargetType(which)) {
Adam Powell98b7f892015-06-19 12:38:45 -07001739 case ChooserListAdapter.TARGET_SERVICE:
Chris Wrenf6e9228b2016-01-26 18:04:35 -05001740 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001741 // Log the package name + target name to answer the question if most users
1742 // share to mostly the same person or to a bunch of different people.
arangelov38a6fce2019-12-02 18:21:22 +00001743 ChooserTarget target = currentListAdapter.getChooserTargetForValue(value);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001744 directTargetHashed = HashedStringCache.getInstance().hashString(
1745 this,
1746 TAG,
1747 target.getComponentName().getPackageName()
1748 + target.getTitle().toString(),
1749 mMaxHashSaltDays);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001750 directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001751
1752 if (mCallerChooserTargets != null) {
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001753 numCallerProvided = mCallerChooserTargets.length;
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001754 }
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05001755 getChooserActivityLogger().logShareTargetSelected(
1756 SELECTION_TYPE_SERVICE,
1757 targetInfo.getResolveInfo().activityInfo.processName,
1758 value
1759 );
Adam Powell98b7f892015-06-19 12:38:45 -07001760 break;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001761 case ChooserListAdapter.TARGET_CALLER:
Adam Powell98b7f892015-06-19 12:38:45 -07001762 case ChooserListAdapter.TARGET_STANDARD:
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001763 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
arangelov38a6fce2019-12-02 18:21:22 +00001764 value -= currentListAdapter.getSelectableServiceTargetCount();
1765 numCallerProvided = currentListAdapter.getCallerTargetCount();
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05001766 getChooserActivityLogger().logShareTargetSelected(
1767 SELECTION_TYPE_APP,
1768 targetInfo.getResolveInfo().activityInfo.processName,
1769 value
1770 );
Adam Powell98b7f892015-06-19 12:38:45 -07001771 break;
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04001772 case ChooserListAdapter.TARGET_STANDARD_AZ:
1773 // A-Z targets are unranked standard targets; we use -1 to mark that they
1774 // are from the alphabetical pool.
1775 value = -1;
1776 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05001777 getChooserActivityLogger().logShareTargetSelected(
1778 SELECTION_TYPE_STANDARD,
1779 targetInfo.getResolveInfo().activityInfo.processName,
1780 value
1781 );
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04001782 break;
Adam Powell98b7f892015-06-19 12:38:45 -07001783 }
1784
1785 if (cat != 0) {
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001786 LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
1787 if (directTargetHashed != null) {
1788 targetLogMaker.addTaggedData(
1789 MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
1790 targetLogMaker.addTaggedData(
1791 MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
1792 directTargetHashed.saltGeneration);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001793 targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
1794 directTargetAlsoRanked);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001795 }
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001796 targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED,
1797 numCallerProvided);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001798 getMetricsLogger().write(targetLogMaker);
Adam Powell98b7f892015-06-19 12:38:45 -07001799 }
Kang Li9082f5b2016-12-02 10:56:21 -08001800
1801 if (mIsSuccessfullySelected) {
1802 if (DEBUG) {
1803 Log.d(TAG, "User Selection Time Cost is " + selectionCost);
1804 Log.d(TAG, "position of selected app/service/caller is " +
1805 Integer.toString(value));
1806 }
1807 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
1808 (int) selectionCost);
1809 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
1810 }
Adam Powell98b7f892015-06-19 12:38:45 -07001811 }
1812 }
1813
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001814 private int getRankedPosition(SelectableTargetInfo targetInfo) {
1815 String targetPackageName =
1816 targetInfo.getChooserTarget().getComponentName().getPackageName();
arangelov38a6fce2019-12-02 18:21:22 +00001817 ChooserListAdapter currentListAdapter =
arangelova3912cf2019-12-13 14:34:45 +00001818 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
arangelov38a6fce2019-12-02 18:21:22 +00001819 int maxRankedResults = Math.min(currentListAdapter.mDisplayList.size(),
1820 MAX_LOG_RANK_POSITION);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001821
1822 for (int i = 0; i < maxRankedResults; i++) {
arangelov38a6fce2019-12-02 18:21:22 +00001823 if (currentListAdapter.mDisplayList.get(i)
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001824 .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
1825 return i;
1826 }
1827 }
1828 return -1;
1829 }
1830
Matt Pietal9236adc2019-12-12 09:24:23 -05001831 @Override
1832 protected boolean shouldAddFooterView() {
1833 // To accommodate for window insets
1834 return true;
1835 }
1836
1837 @Override
1838 protected void applyFooterView(int height) {
1839 int count = mChooserMultiProfilePagerAdapter.getItemCount();
1840
1841 for (int i = 0; i < count; i++) {
1842 mChooserMultiProfilePagerAdapter.getAdapterForIndex(i).setFooterHeight(height);
1843 }
1844 }
1845
arangelovf32a4382020-06-04 19:05:22 +01001846 @VisibleForTesting
1847 protected void queryTargetServices(ChooserListAdapter adapter) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001848 mQueriedTargetServicesTimeMs = System.currentTimeMillis();
1849
arangelov5fc9e7d2020-01-07 17:59:14 +00001850 Context selectedProfileContext = createContextAsUser(
1851 adapter.getUserHandle(), 0 /* flags */);
1852 final PackageManager pm = selectedProfileContext.getPackageManager();
1853 ShortcutManager sm = selectedProfileContext.getSystemService(ShortcutManager.class);
Adam Powell24428412015-04-01 17:19:56 -07001854 int targetsToQuery = 0;
Matt Pietalab73a882019-06-05 07:04:55 -04001855
Adam Powell24428412015-04-01 17:19:56 -07001856 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
1857 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
Adam Powell3a09c522015-10-21 13:21:28 -07001858 if (adapter.getScore(dri) == 0) {
1859 // A score of 0 means the app hasn't been used in some time;
1860 // don't query it as it's not likely to be relevant.
1861 continue;
1862 }
Adam Powell24428412015-04-01 17:19:56 -07001863 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
arangelovb0802dc2019-10-18 18:03:44 +01001864 if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001865 && sm.hasShareTargets(ai.packageName)) {
1866 // Share targets will be queried from ShortcutManager
1867 continue;
1868 }
Adam Powell24428412015-04-01 17:19:56 -07001869 final Bundle md = ai.metaData;
1870 final String serviceName = md != null ? convertServiceName(ai.packageName,
1871 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
1872 if (serviceName != null) {
1873 final ComponentName serviceComponent = new ComponentName(
1874 ai.packageName, serviceName);
Matt Pietalab73a882019-06-05 07:04:55 -04001875
arangelov5fc9e7d2020-01-07 17:59:14 +00001876 UserHandle userHandle = adapter.getUserHandle();
1877 Pair<ComponentName, UserHandle> requestedItem =
1878 new Pair<>(serviceComponent, userHandle);
1879 if (mServicesRequested.contains(requestedItem)) {
Matt Pietalab73a882019-06-05 07:04:55 -04001880 continue;
1881 }
arangelov5fc9e7d2020-01-07 17:59:14 +00001882 mServicesRequested.add(requestedItem);
Matt Pietalab73a882019-06-05 07:04:55 -04001883
Adam Powell24428412015-04-01 17:19:56 -07001884 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
1885 .setComponent(serviceComponent);
1886
1887 if (DEBUG) {
1888 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
1889 }
1890
1891 try {
1892 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
1893 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
1894 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
1895 + " permission " + ChooserTargetService.BIND_PERMISSION
1896 + " - this service will not be queried for ChooserTargets."
1897 + " add android:permission=\""
1898 + ChooserTargetService.BIND_PERMISSION + "\""
1899 + " to the <service> tag for " + serviceComponent
1900 + " in the manifest.");
1901 continue;
1902 }
1903 } catch (NameNotFoundException e) {
Adam Powell52c39212016-04-07 15:14:18 -07001904 Log.e(TAG, "Could not look up service " + serviceComponent
1905 + "; component name not found");
Adam Powell24428412015-04-01 17:19:56 -07001906 continue;
1907 }
1908
Adam Powell9761ab22015-09-08 17:01:49 -07001909 final ChooserTargetServiceConnection conn =
arangelov5fc9e7d2020-01-07 17:59:14 +00001910 new ChooserTargetServiceConnection(this, dri,
1911 adapter.getUserHandle());
Adam Powell52c39212016-04-07 15:14:18 -07001912
arangelov5fc9e7d2020-01-07 17:59:14 +00001913 // Explicitly specify the user handle instead of calling bindService
Adam Powell52c39212016-04-07 15:14:18 -07001914 // to avoid the warning from calling from the system process without an explicit
1915 // user handle
1916 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
arangelov5fc9e7d2020-01-07 17:59:14 +00001917 adapter.getUserHandle())) {
Adam Powell24428412015-04-01 17:19:56 -07001918 if (DEBUG) {
1919 Log.d(TAG, "Binding service connection for target " + dri
1920 + " intent " + serviceIntent);
1921 }
1922 mServiceConnections.add(conn);
1923 targetsToQuery++;
1924 }
1925 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001926 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
Matt Pietal26038402019-01-08 07:29:34 -05001927 if (DEBUG) {
1928 Log.d(TAG, "queryTargets hit query target limit "
1929 + QUERY_TARGET_SERVICE_LIMIT);
1930 }
Adam Powell24428412015-04-01 17:19:56 -07001931 break;
1932 }
1933 }
1934
Matt Pietalab73a882019-06-05 07:04:55 -04001935 mChooserHandler.restartServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -07001936 }
1937
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001938 private IntentFilter getTargetIntentFilter() {
1939 try {
1940 final Intent intent = getTargetIntent();
1941 String dataString = intent.getDataString();
Katsiaryna Naliuka66cd0562020-01-16 18:01:30 +01001942 if (!TextUtils.isEmpty(dataString)) {
1943 return new IntentFilter(intent.getAction(), dataString);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001944 }
Katsiaryna Naliukae2c0b5f2020-02-18 19:26:21 +01001945 if (intent.getType() == null) {
1946 Log.e(TAG, "Failed to get target intent filter: intent data and type are null");
1947 return null;
1948 }
Katsiaryna Naliuka66cd0562020-01-16 18:01:30 +01001949 IntentFilter intentFilter = new IntentFilter(intent.getAction(), intent.getType());
1950 List<Uri> contentUris = new ArrayList<>();
1951 if (Intent.ACTION_SEND.equals(intent.getAction())) {
1952 Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
1953 if (uri != null) {
1954 contentUris.add(uri);
1955 }
1956 } else {
1957 List<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1958 if (uris != null) {
1959 contentUris.addAll(uris);
1960 }
1961 }
1962 for (Uri uri : contentUris) {
1963 intentFilter.addDataScheme(uri.getScheme());
1964 intentFilter.addDataAuthority(uri.getAuthority(), null);
1965 intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
1966 }
1967 return intentFilter;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001968 } catch (Exception e) {
Katsiaryna Naliukae2c0b5f2020-02-18 19:26:21 +01001969 Log.e(TAG, "Failed to get target intent filter", e);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001970 return null;
1971 }
1972 }
1973
George Hodulik69d4a082019-01-18 11:27:03 -08001974 private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001975 // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
1976 // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
George Hodulik69d4a082019-01-18 11:27:03 -08001977 List<DisplayResolveInfo> driList = new ArrayList<>();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001978 int targetsToQuery = 0;
1979 for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
1980 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
1981 if (adapter.getScore(dri) == 0) {
1982 // A score of 0 means the app hasn't been used in some time;
1983 // don't query it as it's not likely to be relevant.
1984 continue;
1985 }
1986 driList.add(dri);
1987 targetsToQuery++;
1988 // TODO(b/121287224): Do we need this here? (similar to queryTargetServices)
1989 if (targetsToQuery >= SHARE_TARGET_QUERY_PACKAGE_LIMIT) {
1990 if (DEBUG) {
1991 Log.d(TAG, "queryTargets hit query target limit "
1992 + SHARE_TARGET_QUERY_PACKAGE_LIMIT);
1993 }
1994 break;
1995 }
1996 }
George Hodulik69d4a082019-01-18 11:27:03 -08001997 return driList;
1998 }
1999
arangelovf32a4382020-06-04 19:05:22 +01002000 @VisibleForTesting
2001 protected void queryDirectShareTargets(
George Hodulik3f399f22019-04-26 16:17:54 -07002002 ChooserListAdapter adapter, boolean skipAppPredictionService) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07002003 mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
arangelov5fc9e7d2020-01-07 17:59:14 +00002004 UserHandle userHandle = adapter.getUserHandle();
George Hodulik3f399f22019-04-26 16:17:54 -07002005 if (!skipAppPredictionService) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002006 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
George Hodulik3f399f22019-04-26 16:17:54 -07002007 if (appPredictor != null) {
2008 appPredictor.requestPredictionUpdate();
2009 return;
2010 }
George Hodulik69d4a082019-01-18 11:27:03 -08002011 }
George Hodulik145b3a52019-03-27 11:18:43 -07002012 // Default to just querying ShortcutManager if AppPredictor not present.
George Hodulik69d4a082019-01-18 11:27:03 -08002013 final IntentFilter filter = getTargetIntentFilter();
2014 if (filter == null) {
2015 return;
2016 }
2017 final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002018
2019 AsyncTask.execute(() -> {
arangelov5fc9e7d2020-01-07 17:59:14 +00002020 Context selectedProfileContext = createContextAsUser(userHandle, 0 /* flags */);
2021 ShortcutManager sm = (ShortcutManager) selectedProfileContext
2022 .getSystemService(Context.SHORTCUT_SERVICE);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002023 List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
arangelov5fc9e7d2020-01-07 17:59:14 +00002024 sendShareShortcutInfoList(resultList, driList, null, userHandle);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002025 });
2026 }
2027
Song Hu5aab2d32020-04-30 10:07:15 -07002028 private void sendChooserTargetRankingScore(List<AppTarget> chooserTargetScores,
2029 UserHandle userHandle) {
2030 final Message msg = Message.obtain();
2031 msg.what = ChooserHandler.CHOOSER_TARGET_RANKING_SCORE;
2032 msg.obj = new ChooserTargetRankingInfo(chooserTargetScores, userHandle);
2033 mChooserHandler.sendMessage(msg);
2034 }
2035
George Hodulik69d4a082019-01-18 11:27:03 -08002036 private void sendShareShortcutInfoList(
2037 List<ShortcutManager.ShareShortcutInfo> resultList,
George Hodulikaa5238c2019-04-18 14:17:51 -07002038 List<DisplayResolveInfo> driList,
arangelov5fc9e7d2020-01-07 17:59:14 +00002039 @Nullable List<AppTarget> appTargets, UserHandle userHandle) {
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07002040 if (appTargets != null && appTargets.size() != resultList.size()) {
2041 throw new RuntimeException("resultList and appTargets must have the same size."
2042 + " resultList.size()=" + resultList.size()
2043 + " appTargets.size()=" + appTargets.size());
2044 }
2045
2046 for (int i = resultList.size() - 1; i >= 0; i--) {
2047 final String packageName = resultList.get(i).getTargetComponent().getPackageName();
2048 if (!isPackageEnabled(packageName)) {
2049 resultList.remove(i);
2050 if (appTargets != null) {
2051 appTargets.remove(i);
2052 }
2053 }
2054 }
2055
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07002056 // If |appTargets| is not null, results are from AppPredictionService and already sorted.
2057 final int shortcutType = (appTargets == null ? TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER :
2058 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
2059
George Hodulik69d4a082019-01-18 11:27:03 -08002060 // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
2061 // for direct share targets. After ShareSheet is refactored we should use the
2062 // ShareShortcutInfos directly.
2063 boolean resultMessageSent = false;
2064 for (int i = 0; i < driList.size(); i++) {
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002065 List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
George Hodulik69d4a082019-01-18 11:27:03 -08002066 for (int j = 0; j < resultList.size(); j++) {
2067 if (driList.get(i).getResolvedComponentName().equals(
2068 resultList.get(j).getTargetComponent())) {
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002069 matchingShortcuts.add(resultList.get(j));
George Hodulik69d4a082019-01-18 11:27:03 -08002070 }
2071 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002072 if (matchingShortcuts.isEmpty()) {
George Hodulik69d4a082019-01-18 11:27:03 -08002073 continue;
2074 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002075 List<ChooserTarget> chooserTargets = convertToChooserTarget(
2076 matchingShortcuts, resultList, appTargets, shortcutType);
2077
arangelov5fc9e7d2020-01-07 17:59:14 +00002078
2079
George Hodulik69d4a082019-01-18 11:27:03 -08002080 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04002081 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
arangelov5fc9e7d2020-01-07 17:59:14 +00002082 msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null, userHandle);
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07002083 msg.arg1 = shortcutType;
George Hodulik69d4a082019-01-18 11:27:03 -08002084 mChooserHandler.sendMessage(msg);
2085 resultMessageSent = true;
2086 }
2087
2088 if (resultMessageSent) {
George Hodulik145b3a52019-03-27 11:18:43 -07002089 sendShortcutManagerShareTargetResultCompleted();
George Hodulik69d4a082019-01-18 11:27:03 -08002090 }
2091 }
2092
George Hodulik145b3a52019-03-27 11:18:43 -07002093 private void sendShortcutManagerShareTargetResultCompleted() {
2094 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04002095 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
George Hodulik145b3a52019-03-27 11:18:43 -07002096 mChooserHandler.sendMessage(msg);
2097 }
2098
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07002099 private boolean isPackageEnabled(String packageName) {
2100 if (TextUtils.isEmpty(packageName)) {
2101 return false;
2102 }
2103 ApplicationInfo appInfo;
2104 try {
2105 appInfo = getPackageManager().getApplicationInfo(packageName, 0);
2106 } catch (NameNotFoundException e) {
2107 return false;
2108 }
2109
2110 if (appInfo != null && appInfo.enabled
2111 && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
2112 return true;
2113 }
2114 return false;
2115 }
2116
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002117 /**
2118 * Converts a list of ShareShortcutInfos to ChooserTargets.
2119 * @param matchingShortcuts List of shortcuts, all from the same package, that match the current
2120 * share intent filter.
2121 * @param allShortcuts List of all the shortcuts from all the packages on the device that are
2122 * returned for the current sharing action.
2123 * @param allAppTargets List of AppTargets. Null if the results are not from prediction service.
2124 * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or
2125 * TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
2126 * @return A list of ChooserTargets sorted by score in descending order.
2127 */
2128 @VisibleForTesting
2129 @NonNull
2130 public List<ChooserTarget> convertToChooserTarget(
2131 @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts,
2132 @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts,
2133 @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) {
2134 // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted
2135 // list instead of the actual rank value when converting a rank to a score.
2136 List<Integer> scoreList = new ArrayList<>();
2137 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
2138 for (int i = 0; i < matchingShortcuts.size(); i++) {
2139 int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank();
2140 if (!scoreList.contains(shortcutRank)) {
2141 scoreList.add(shortcutRank);
2142 }
2143 }
2144 Collections.sort(scoreList);
2145 }
2146
2147 List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size());
2148 for (int i = 0; i < matchingShortcuts.size(); i++) {
2149 ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo();
2150 int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i));
2151
2152 float score;
2153 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
2154 // Incoming results are ordered. Create a score based on index in the original list.
2155 score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f);
2156 } else {
2157 // Create a score based on the rank of the shortcut.
2158 int rankIndex = scoreList.indexOf(shortcutInfo.getRank());
2159 score = Math.max(1.0f - (0.01f * rankIndex), 0.0f);
2160 }
2161
2162 Bundle extras = new Bundle();
2163 extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
Mike Digmaned447062020-05-20 10:51:20 -07002164
2165 ChooserTarget chooserTarget = new ChooserTarget(
Mike Digmaneb257d12020-05-21 16:04:43 -07002166 shortcutInfo.getLabel(),
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002167 null, // Icon will be loaded later if this target is selected to be shown.
2168 score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);
2169
2170 chooserTargetList.add(chooserTarget);
2171 if (mDirectShareAppTargetCache != null && allAppTargets != null) {
2172 mDirectShareAppTargetCache.put(chooserTarget,
2173 allAppTargets.get(indexInAllShortcuts));
2174 }
arangelov5fc9e7d2020-01-07 17:59:14 +00002175 if (mDirectShareShortcutInfoCache != null) {
2176 mDirectShareShortcutInfoCache.put(chooserTarget, shortcutInfo);
2177 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002178 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07002179 // Sort ChooserTargets by score in descending order
2180 Comparator<ChooserTarget> byScore =
2181 (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore());
2182 Collections.sort(chooserTargetList, byScore);
2183 return chooserTargetList;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002184 }
2185
Adam Powell24428412015-04-01 17:19:56 -07002186 private String convertServiceName(String packageName, String serviceName) {
2187 if (TextUtils.isEmpty(serviceName)) {
2188 return null;
2189 }
2190
2191 final String fullName;
2192 if (serviceName.startsWith(".")) {
2193 // Relative to the app package. Prepend the app package name.
2194 fullName = packageName + serviceName;
2195 } else if (serviceName.indexOf('.') >= 0) {
2196 // Fully qualified package name.
2197 fullName = serviceName;
2198 } else {
2199 fullName = null;
2200 }
2201 return fullName;
2202 }
2203
2204 void unbindRemainingServices() {
2205 if (DEBUG) {
2206 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
2207 }
2208 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
2209 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
2210 if (DEBUG) Log.d(TAG, "unbinding " + conn);
2211 unbindService(conn);
Adam Powell9761ab22015-09-08 17:01:49 -07002212 conn.destroy();
Adam Powell24428412015-04-01 17:19:56 -07002213 }
Matt Pietalab73a882019-06-05 07:04:55 -04002214 mServicesRequested.clear();
Adam Powell24428412015-04-01 17:19:56 -07002215 mServiceConnections.clear();
Adam Powell24428412015-04-01 17:19:56 -07002216 }
2217
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07002218 private void logDirectShareTargetReceived(int logCategory) {
2219 final long queryTime =
2220 logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
2221 ? mQueriedSharingShortcutsTimeMs : mQueriedTargetServicesTimeMs;
2222 final int apiLatency = (int) (System.currentTimeMillis() - queryTime);
2223 getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
2224 }
2225
Kang Li9fa2a2c2017-01-06 13:33:24 -08002226 void updateModelAndChooserCounts(TargetInfo info) {
Song Hub6039992020-06-01 11:35:28 -07002227 if (info != null && info instanceof MultiDisplayResolveInfo) {
2228 info = ((MultiDisplayResolveInfo) info).getSelectedTarget();
2229 }
Kang Li53b43142016-11-14 14:38:25 -08002230 if (info != null) {
George Hodulik145b3a52019-03-27 11:18:43 -07002231 sendClickToAppPredictor(info);
Kang Li53b43142016-11-14 14:38:25 -08002232 final ResolveInfo ri = info.getResolveInfo();
Kang Li64b018e2017-01-05 17:30:06 -08002233 Intent targetIntent = getTargetIntent();
2234 if (ri != null && ri.activityInfo != null && targetIntent != null) {
arangelov38a6fce2019-12-02 18:21:22 +00002235 ChooserListAdapter currentListAdapter =
arangelova3912cf2019-12-13 14:34:45 +00002236 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
arangelov38a6fce2019-12-02 18:21:22 +00002237 if (currentListAdapter != null) {
Song Hu5aab2d32020-04-30 10:07:15 -07002238 sendImpressionToAppPredictor(info, currentListAdapter);
arangelov38a6fce2019-12-02 18:21:22 +00002239 currentListAdapter.updateModel(info.getResolvedComponentName());
arangelov4872e682020-04-16 15:57:38 +01002240 currentListAdapter.updateChooserCounts(ri.activityInfo.packageName,
Kang Li9fa2a2c2017-01-06 13:33:24 -08002241 targetIntent.getAction());
Kang Li0cef910d2017-01-05 09:14:36 -08002242 }
Kang Li53b43142016-11-14 14:38:25 -08002243 if (DEBUG) {
Kang Li64b018e2017-01-05 17:30:06 -08002244 Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
Kang Li64b018e2017-01-05 17:30:06 -08002245 Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
Kang Li53b43142016-11-14 14:38:25 -08002246 }
George Hodulikf2b0d342019-01-25 12:43:54 -08002247 } else if (DEBUG) {
Kang Li53b43142016-11-14 14:38:25 -08002248 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
2249 }
2250 }
Kang Li9082f5b2016-12-02 10:56:21 -08002251 mIsSuccessfullySelected = true;
Kang Li53b43142016-11-14 14:38:25 -08002252 }
2253
Song Hu5aab2d32020-04-30 10:07:15 -07002254 private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) {
2255 if (!mChooserTargetRankingEnabled) {
2256 return;
2257 }
2258 AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
2259 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
2260 if (directShareAppPredictor == null) {
2261 return;
2262 }
2263 // Send DS target impression info to AppPredictor, only when user chooses app share.
2264 if (targetInfo instanceof ChooserTargetInfo) {
2265 return;
2266 }
2267 List<ChooserTargetInfo> surfacedTargetInfo = adapter.getSurfacedTargetInfo();
2268 List<AppTargetId> targetIds = new ArrayList<>();
2269 for (ChooserTargetInfo chooserTargetInfo : surfacedTargetInfo) {
2270 ChooserTarget chooserTarget = chooserTargetInfo.getChooserTarget();
Song Huc6297c82020-06-15 23:30:53 -07002271 ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault(
2272 chooserTarget.getComponentName(), chooserTarget.getComponentName());
Song Hu5aab2d32020-04-30 10:07:15 -07002273 if (mDirectShareShortcutInfoCache.containsKey(chooserTarget)) {
2274 String shortcutId = mDirectShareShortcutInfoCache.get(chooserTarget).getId();
2275 targetIds.add(new AppTargetId(
Song Huc6297c82020-06-15 23:30:53 -07002276 String.format("%s/%s/%s", shortcutId, componentName.flattenToString(),
2277 SHORTCUT_TARGET)));
Song Hu5aab2d32020-04-30 10:07:15 -07002278 } else {
2279 String titleHash = ChooserUtil.md5(chooserTarget.getTitle().toString());
2280 targetIds.add(new AppTargetId(
Song Huc6297c82020-06-15 23:30:53 -07002281 String.format("%s/%s/%s", titleHash, componentName.flattenToString(),
2282 CHOOSER_TARGET)));
Song Hu5aab2d32020-04-30 10:07:15 -07002283 }
2284 }
2285 directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds);
2286 }
2287
George Hodulikf2b0d342019-01-25 12:43:54 -08002288 private void sendClickToAppPredictor(TargetInfo targetInfo) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002289 AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
2290 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
George Hodulikaa5238c2019-04-18 14:17:51 -07002291 if (directShareAppPredictor == null) {
George Hodulik145b3a52019-03-27 11:18:43 -07002292 return;
2293 }
George Hodulikf2b0d342019-01-25 12:43:54 -08002294 if (!(targetInfo instanceof ChooserTargetInfo)) {
2295 return;
2296 }
2297 ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
George Hodulikaa5238c2019-04-18 14:17:51 -07002298 AppTarget appTarget = null;
2299 if (mDirectShareAppTargetCache != null) {
2300 appTarget = mDirectShareAppTargetCache.get(chooserTarget);
George Hodulikf2b0d342019-01-25 12:43:54 -08002301 }
Song Hu5aab2d32020-04-30 10:07:15 -07002302 if (mChooserTargetRankingEnabled && appTarget == null) {
2303 // Send ChooserTarget sharing info to AppPredictor.
Song Huc6297c82020-06-15 23:30:53 -07002304 ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault(
2305 chooserTarget.getComponentName(), chooserTarget.getComponentName());
Song Hu5aab2d32020-04-30 10:07:15 -07002306 try {
2307 appTarget = new AppTarget.Builder(
2308 new AppTargetId(componentName.flattenToString()),
2309 new ShortcutInfo.Builder(
2310 createPackageContextAsUser(
2311 componentName.getPackageName(),
2312 0 /* flags */,
2313 getUser()),
2314 CHOOSER_TARGET)
2315 .setActivity(componentName)
2316 .setShortLabel(ChooserUtil.md5(chooserTarget.getTitle().toString()))
2317 .build())
2318 .setClassName(componentName.getClassName())
2319 .build();
2320 } catch (NameNotFoundException e) {
2321 Log.e(TAG, "Could not look up service " + componentName
2322 + "; component name not found");
2323 }
2324 }
George Hodulikaa5238c2019-04-18 14:17:51 -07002325 // This is a direct share click that was provided by the APS
2326 if (appTarget != null) {
2327 directShareAppPredictor.notifyAppTargetEvent(
2328 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05002329 .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE)
George Hodulikaa5238c2019-04-18 14:17:51 -07002330 .build());
George Hodulikf2b0d342019-01-25 12:43:54 -08002331 }
George Hodulikf2b0d342019-01-25 12:43:54 -08002332 }
2333
George Hodulik145b3a52019-03-27 11:18:43 -07002334 @Nullable
arangelov5fc9e7d2020-01-07 17:59:14 +00002335 private AppPredictor createAppPredictor(UserHandle userHandle) {
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -07002336 if (!mIsAppPredictorComponentAvailable) {
2337 return null;
2338 }
arangelov5fc9e7d2020-01-07 17:59:14 +00002339
arangelove5b369c2020-03-12 17:36:05 +00002340 if (getPersonalProfileUserHandle().equals(userHandle)) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002341 if (mPersonalAppPredictor != null) {
2342 return mPersonalAppPredictor;
2343 }
2344 } else {
2345 if (mWorkAppPredictor != null) {
2346 return mWorkAppPredictor;
2347 }
George Hodulik145b3a52019-03-27 11:18:43 -07002348 }
arangelov5fc9e7d2020-01-07 17:59:14 +00002349
2350 // TODO(b/148230574): Currently AppPredictor fetches only the same-profile app targets.
2351 // Make AppPredictor work cross-profile.
2352 Context contextAsUser = createContextAsUser(userHandle, 0 /* flags */);
2353 final IntentFilter filter = getTargetIntentFilter();
2354 Bundle extras = new Bundle();
2355 extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
2356 AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(contextAsUser)
2357 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
2358 .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
2359 .setExtras(extras)
2360 .build();
2361 AppPredictionManager appPredictionManager =
2362 contextAsUser
2363 .getSystemService(AppPredictionManager.class);
2364 AppPredictor appPredictionSession = appPredictionManager.createAppPredictionSession(
2365 appPredictionContext);
arangelove5b369c2020-03-12 17:36:05 +00002366 if (getPersonalProfileUserHandle().equals(userHandle)) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002367 mPersonalAppPredictor = appPredictionSession;
2368 } else {
2369 mWorkAppPredictor = appPredictionSession;
2370 }
2371 return appPredictionSession;
George Hodulik145b3a52019-03-27 11:18:43 -07002372 }
2373
2374 /**
2375 * This will return an app predictor if it is enabled for direct share sorting
2376 * and if one exists. Otherwise, it returns null.
arangelov5fc9e7d2020-01-07 17:59:14 +00002377 * @param userHandle
George Hodulik145b3a52019-03-27 11:18:43 -07002378 */
2379 @Nullable
arangelov5fc9e7d2020-01-07 17:59:14 +00002380 private AppPredictor getAppPredictorForDirectShareIfEnabled(UserHandle userHandle) {
arangelovb0802dc2019-10-18 18:03:44 +01002381 return ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS
arangelov5fc9e7d2020-01-07 17:59:14 +00002382 && !ActivityManager.isLowRamDeviceStatic() ? createAppPredictor(userHandle) : null;
George Hodulik145b3a52019-03-27 11:18:43 -07002383 }
2384
George Hodulikc681ce42019-04-12 17:10:31 -07002385 /**
2386 * This will return an app predictor if it is enabled for share activity sorting
2387 * and if one exists. Otherwise, it returns null.
2388 */
2389 @Nullable
arangelov5fc9e7d2020-01-07 17:59:14 +00002390 private AppPredictor getAppPredictorForShareActivitiesIfEnabled(UserHandle userHandle) {
2391 return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? createAppPredictor(userHandle) : null;
George Hodulikc681ce42019-04-12 17:10:31 -07002392 }
2393
Adam Powell2ed547e2015-04-29 18:45:04 -07002394 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
2395 if (mRefinementResultReceiver != null) {
2396 mRefinementResultReceiver.destroy();
2397 mRefinementResultReceiver = null;
2398 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002399 if (selectedTarget == null) {
2400 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
2401 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
2402 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
2403 + " cannot match refined source intent " + matchingIntent);
Kang Li53b43142016-11-14 14:38:25 -08002404 } else {
2405 TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
2406 if (super.onTargetSelected(clonedTarget, false)) {
Kang Li9fa2a2c2017-01-06 13:33:24 -08002407 updateModelAndChooserCounts(clonedTarget);
Kang Li53b43142016-11-14 14:38:25 -08002408 finish();
2409 return;
2410 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002411 }
2412 onRefinementCanceled();
2413 }
2414
2415 void onRefinementCanceled() {
2416 if (mRefinementResultReceiver != null) {
2417 mRefinementResultReceiver.destroy();
2418 mRefinementResultReceiver = null;
2419 }
2420 finish();
2421 }
2422
2423 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
2424 final List<Intent> targetIntents = target.getAllSourceIntents();
2425 for (int i = 0, N = targetIntents.size(); i < N; i++) {
2426 final Intent targetIntent = targetIntents.get(i);
2427 if (targetIntent.filterEquals(matchingIntent)) {
2428 return true;
2429 }
2430 }
2431 return false;
2432 }
2433
arangelov5fc9e7d2020-01-07 17:59:14 +00002434 void filterServiceTargets(Context contextAsUser, String packageName,
2435 List<ChooserTarget> targets) {
Adam Powell666d82a2015-07-15 20:14:57 -07002436 if (targets == null) {
2437 return;
2438 }
2439
arangelov5fc9e7d2020-01-07 17:59:14 +00002440 final PackageManager pm = contextAsUser.getPackageManager();
Adam Powell666d82a2015-07-15 20:14:57 -07002441 for (int i = targets.size() - 1; i >= 0; i--) {
2442 final ChooserTarget target = targets.get(i);
2443 final ComponentName targetName = target.getComponentName();
2444 if (packageName != null && packageName.equals(targetName.getPackageName())) {
2445 // Anything from the original target's package is fine.
2446 continue;
2447 }
2448
2449 boolean remove;
2450 try {
2451 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
2452 remove = !ai.exported || ai.permission != null;
2453 } catch (NameNotFoundException e) {
2454 Log.e(TAG, "Target " + target + " returned by " + packageName
2455 + " component not found");
2456 remove = true;
2457 }
2458
2459 if (remove) {
2460 targets.remove(i);
2461 }
2462 }
2463 }
2464
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002465 /**
2466 * Sort intents alphabetically based on display label.
2467 */
arangelovb0802dc2019-10-18 18:03:44 +01002468 static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002469 Collator mCollator;
2470 AzInfoComparator(Context context) {
2471 mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
2472 }
2473
2474 @Override
arangelovb0802dc2019-10-18 18:03:44 +01002475 public int compare(
2476 DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002477 return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
2478 }
2479 }
2480
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -05002481 protected MetricsLogger getMetricsLogger() {
2482 if (mMetricsLogger == null) {
2483 mMetricsLogger = new MetricsLogger();
2484 }
2485 return mMetricsLogger;
2486 }
2487
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05002488 protected ChooserActivityLogger getChooserActivityLogger() {
2489 if (mChooserActivityLogger == null) {
2490 mChooserActivityLogger = new ChooserActivityLoggerImpl();
2491 }
2492 return mChooserActivityLogger;
2493 }
2494
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002495 public class ChooserListController extends ResolverListController {
2496 public ChooserListController(Context context,
2497 PackageManager pm,
2498 Intent targetIntent,
2499 String referrerPackageName,
George Hodulikc681ce42019-04-12 17:10:31 -07002500 int launchedFromUid,
arangelov38a6fce2019-12-02 18:21:22 +00002501 UserHandle userId,
George Hodulikc681ce42019-04-12 17:10:31 -07002502 AbstractResolverComparator resolverComparator) {
arangelov38a6fce2019-12-02 18:21:22 +00002503 super(context, pm, targetIntent, referrerPackageName, launchedFromUid, userId,
George Hodulikc681ce42019-04-12 17:10:31 -07002504 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002505 }
2506
2507 @Override
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002508 boolean isComponentFiltered(ComponentName name) {
2509 if (mFilteredComponentNames == null) {
2510 return false;
2511 }
2512 for (ComponentName filteredComponentName : mFilteredComponentNames) {
2513 if (name.equals(filteredComponentName)) {
2514 return true;
2515 }
2516 }
2517 return false;
2518 }
Alison Cichowlas1fd47152019-11-14 19:50:55 -05002519
2520 @Override
2521 public boolean isComponentPinned(ComponentName name) {
2522 return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
2523 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002524 }
2525
arangelov38a6fce2019-12-02 18:21:22 +00002526 @VisibleForTesting
2527 public ChooserGridAdapter createChooserGridAdapter(Context context,
2528 List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
Narayan Kamath75418682020-05-07 13:42:05 +01002529 boolean filterLastUsed, UserHandle userHandle) {
arangelov1b11cc12020-04-27 17:23:04 +01002530 ChooserListAdapter chooserListAdapter = createChooserListAdapter(context, payloadIntents,
2531 initialIntents, rList, filterLastUsed,
Narayan Kamath75418682020-05-07 13:42:05 +01002532 createListController(userHandle));
arangelov5fc9e7d2020-01-07 17:59:14 +00002533 AppPredictor.Callback appPredictorCallback = createAppPredictorCallback(chooserListAdapter);
2534 AppPredictor appPredictor = setupAppPredictorForUser(userHandle, appPredictorCallback);
2535 chooserListAdapter.setAppPredictor(appPredictor);
2536 chooserListAdapter.setAppPredictorCallback(appPredictorCallback);
2537 return new ChooserGridAdapter(chooserListAdapter);
Adam Powell24428412015-04-01 17:19:56 -07002538 }
2539
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002540 @VisibleForTesting
arangelov1b11cc12020-04-27 17:23:04 +01002541 public ChooserListAdapter createChooserListAdapter(Context context,
2542 List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
Narayan Kamath75418682020-05-07 13:42:05 +01002543 boolean filterLastUsed, ResolverListController resolverListController) {
arangelov1b11cc12020-04-27 17:23:04 +01002544 return new ChooserListAdapter(context, payloadIntents, initialIntents, rList,
Narayan Kamath75418682020-05-07 13:42:05 +01002545 filterLastUsed, resolverListController, this,
2546 this, context.getPackageManager());
arangelov1b11cc12020-04-27 17:23:04 +01002547 }
2548
2549 @VisibleForTesting
arangelov38a6fce2019-12-02 18:21:22 +00002550 protected ResolverListController createListController(UserHandle userHandle) {
arangelov5fc9e7d2020-01-07 17:59:14 +00002551 AppPredictor appPredictor = getAppPredictorForShareActivitiesIfEnabled(userHandle);
George Hodulikc681ce42019-04-12 17:10:31 -07002552 AbstractResolverComparator resolverComparator;
2553 if (appPredictor != null) {
2554 resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
arangelov5fc9e7d2020-01-07 17:59:14 +00002555 getReferrerPackageName(), appPredictor, userHandle);
George Hodulikc681ce42019-04-12 17:10:31 -07002556 } else {
2557 resolverComparator =
2558 new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
2559 getReferrerPackageName(), null);
2560 }
2561
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002562 return new ChooserListController(
2563 this,
2564 mPm,
2565 getTargetIntent(),
2566 getReferrerPackageName(),
George Hodulikc681ce42019-04-12 17:10:31 -07002567 mLaunchedFromUid,
arangelov38a6fce2019-12-02 18:21:22 +00002568 userHandle,
George Hodulikc681ce42019-04-12 17:10:31 -07002569 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002570 }
2571
Matt Pietal26038402019-01-08 07:29:34 -05002572 @VisibleForTesting
2573 protected Bitmap loadThumbnail(Uri uri, Size size) {
2574 if (uri == null || size == null) {
2575 return null;
2576 }
2577
2578 try {
Alison Cichowlas07acade2019-12-20 11:26:23 -05002579 return getContentResolver().loadThumbnail(uri, size, null);
Matt Pietal46d828c2019-02-05 08:07:07 -05002580 } catch (IOException | NullPointerException | SecurityException ex) {
Matt Pietal62532e52019-05-07 09:51:37 -04002581 logContentPreviewWarning(uri);
Matt Pietal26038402019-01-08 07:29:34 -05002582 }
2583 return null;
2584 }
2585
arangelovb0802dc2019-10-18 18:03:44 +01002586 static final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
2587 public Drawable getDisplayIcon(Context context) {
Mike Digmanac1d88c2019-04-18 15:15:55 -07002588 AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
arangelovb0802dc2019-10-18 18:03:44 +01002589 context.getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
Mike Digmanac1d88c2019-04-18 15:15:55 -07002590 avd.start(); // Start animation after generation
2591 return avd;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002592 }
2593 }
2594
arangelovb0802dc2019-10-18 18:03:44 +01002595 static final class EmptyTargetInfo extends NotSelectableTargetInfo {
2596 public Drawable getDisplayIcon(Context context) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002597 return null;
2598 }
2599 }
2600
Matt Pietal5b648562019-03-12 07:40:26 -04002601 private void handleScroll(View view, int x, int y, int oldx, int oldy) {
arangelov38a6fce2019-12-02 18:21:22 +00002602 if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
2603 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().handleScroll(view, y, oldy);
Matt Pietal5b648562019-03-12 07:40:26 -04002604 }
2605 }
2606
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002607 /*
2608 * Need to dynamically adjust how many icons can fit per row before we add them,
2609 * which also means setting the correct offset to initially show the content
2610 * preview area + 2 rows of targets
2611 */
2612 private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
2613 int oldTop, int oldRight, int oldBottom) {
arangelov38a6fce2019-12-02 18:21:22 +00002614 if (mChooserMultiProfilePagerAdapter == null) {
2615 return;
2616 }
arangelovcf268642020-01-15 15:09:51 +00002617 RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
arangelov38a6fce2019-12-02 18:21:22 +00002618 ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
2619 if (gridAdapter == null || recyclerView == null) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002620 return;
2621 }
2622
Matt Pietalab73a882019-06-05 07:04:55 -04002623 final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
arangelov19e1fea2020-03-10 18:09:39 +00002624 boolean isLayoutUpdated = gridAdapter.consumeLayoutRequest()
arangelov38a6fce2019-12-02 18:21:22 +00002625 || gridAdapter.calculateChooserTargetWidth(availableWidth)
2626 || recyclerView.getAdapter() == null
arangelov19e1fea2020-03-10 18:09:39 +00002627 || availableWidth != mCurrAvailableWidth;
2628 if (isLayoutUpdated
2629 || mLastNumberOfChildren != recyclerView.getChildCount()) {
Matt Pietalab73a882019-06-05 07:04:55 -04002630 mCurrAvailableWidth = availableWidth;
arangelov2731fa72020-03-18 20:24:03 +00002631 if (isLayoutUpdated) {
2632 // It is very important we call setAdapter from here. Otherwise in some cases
2633 // the resolver list doesn't get populated, such as b/150922090, b/150918223
2634 // and b/150936654
2635 recyclerView.setAdapter(gridAdapter);
2636 ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount(
2637 gridAdapter.getMaxTargetsPerRow());
2638 }
2639
arangelovc7581392020-04-07 17:10:36 +01002640 UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle();
2641 int currentProfile = getProfileForUser(currentUserHandle);
2642 int initialProfile = findSelectedProfile();
2643 if (currentProfile != initialProfile) {
arangelov19e1fea2020-03-10 18:09:39 +00002644 return;
arangelov2731fa72020-03-18 20:24:03 +00002645 }
2646
2647 if (mLastNumberOfChildren == recyclerView.getChildCount()) {
arangelova30787b2020-03-11 18:33:26 +00002648 return;
arangelov19e1fea2020-03-10 18:09:39 +00002649 }
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002650
2651 getMainThreadHandler().post(() -> {
arangelov38a6fce2019-12-02 18:21:22 +00002652 if (mResolverDrawerLayout == null || gridAdapter == null) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002653 return;
2654 }
2655
Matt Pietal800136a2019-05-08 07:46:39 -04002656 final int bottomInset = mSystemWindowInsets != null
2657 ? mSystemWindowInsets.bottom : 0;
2658 int offset = bottomInset;
arangelovc4dbdbd2020-02-18 20:54:16 +00002659 int rowsToShow = gridAdapter.getContentPreviewRowCount()
2660 + gridAdapter.getProfileRowCount()
arangelov38a6fce2019-12-02 18:21:22 +00002661 + gridAdapter.getServiceTargetRowCount()
2662 + gridAdapter.getCallerAndRankedTargetRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002663
2664 // then this is most likely not a SEND_* action, so check
2665 // the app target count
2666 if (rowsToShow == 0) {
arangelov38a6fce2019-12-02 18:21:22 +00002667 rowsToShow = gridAdapter.getRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002668 }
2669
2670 // still zero? then use a default height and leave, which
2671 // can happen when there are no targets to show
arangelov590fba32020-02-11 18:05:42 +00002672 if (rowsToShow == 0 && !shouldShowStickyContentPreview()) {
Matt Pietal800136a2019-05-08 07:46:39 -04002673 offset += getResources().getDimensionPixelSize(
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002674 R.dimen.chooser_max_collapsed_height);
2675 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2676 return;
2677 }
2678
arangelovc4dbdbd2020-02-18 20:54:16 +00002679 View stickyContentPreview = findViewById(R.id.content_preview_container);
2680 if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) {
2681 offset += stickyContentPreview.getHeight();
arangelova3912cf2019-12-13 14:34:45 +00002682 }
2683
arangelovf163a882020-02-18 17:18:47 +00002684 if (shouldShowTabs()) {
arangelova69e6762020-02-05 21:25:15 +00002685 offset += findViewById(R.id.tabs).getHeight();
2686 }
2687
arangelov2a4d0b12020-03-04 18:15:04 +00002688 View tabDivider = findViewById(R.id.resolver_tab_divider);
2689 if (tabDivider.getVisibility() == View.VISIBLE) {
2690 offset += tabDivider.getHeight();
2691 }
2692
arangelova30787b2020-03-11 18:33:26 +00002693 if (recyclerView.getVisibility() == View.VISIBLE) {
2694 int directShareHeight = 0;
2695 rowsToShow = Math.min(4, rowsToShow);
arangelov416a1b32020-06-04 13:57:06 +01002696 boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow);
arangelova30787b2020-03-11 18:33:26 +00002697 mLastNumberOfChildren = recyclerView.getChildCount();
2698 for (int i = 0, childCount = recyclerView.getChildCount();
2699 i < childCount && rowsToShow > 0; i++) {
2700 View child = recyclerView.getChildAt(i);
2701 if (((GridLayoutManager.LayoutParams)
2702 child.getLayoutParams()).getSpanIndex() != 0) {
2703 continue;
2704 }
2705 int height = child.getHeight();
2706 offset += height;
arangelov416a1b32020-06-04 13:57:06 +01002707 if (shouldShowExtraRow) {
2708 offset += height;
2709 }
arangelova30787b2020-03-11 18:33:26 +00002710
2711 if (gridAdapter.getTargetType(
2712 recyclerView.getChildAdapterPosition(child))
2713 == ChooserListAdapter.TARGET_SERVICE) {
2714 directShareHeight = height;
2715 }
2716 rowsToShow--;
Zhen Zhangbde7b462019-11-11 11:49:33 -08002717 }
Matt Pietal394ebd02019-05-03 07:36:21 -04002718
arangelova30787b2020-03-11 18:33:26 +00002719 boolean isExpandable = getResources().getConfiguration().orientation
2720 == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
2721 if (directShareHeight != 0 && isSendAction(getTargetIntent())
2722 && isExpandable) {
2723 // make sure to leave room for direct share 4->8 expansion
2724 int requiredExpansionHeight =
2725 (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
2726 int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
2727 int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
2728 - requiredExpansionHeight - topInset - bottomInset;
2729
2730 offset = Math.min(offset, minHeight);
Matt Pietal394ebd02019-05-03 07:36:21 -04002731 }
arangelova30787b2020-03-11 18:33:26 +00002732 } else {
arangelov28bb4fe2020-05-29 13:29:23 +01002733 ViewGroup currentEmptyStateView = getActiveEmptyStateView();
arangelova30787b2020-03-11 18:33:26 +00002734 if (currentEmptyStateView.getVisibility() == View.VISIBLE) {
2735 offset += currentEmptyStateView.getHeight();
2736 }
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002737 }
2738
Matt Pietal399e8c72019-04-04 15:49:48 -04002739 mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top));
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002740 });
2741 }
2742 }
2743
arangelovc7581392020-04-07 17:10:36 +01002744 /**
arangelov416a1b32020-06-04 13:57:06 +01002745 * If we have a tabbed view and are showing 1 row in the current profile and an empty
2746 * state screen in the other profile, to prevent cropping of the empty state screen we show
2747 * a second row in the current profile.
2748 */
2749 private boolean shouldShowExtraRow(int rowsToShow) {
2750 return shouldShowTabs()
2751 && rowsToShow == 1
2752 && mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen(
2753 mChooserMultiProfilePagerAdapter.getInactiveListAdapter());
2754 }
2755
2756 /**
arangelovc7581392020-04-07 17:10:36 +01002757 * Returns {@link #PROFILE_PERSONAL}, {@link #PROFILE_WORK}, or -1 if the given user handle
2758 * does not match either the personal or work user handle.
2759 **/
2760 private int getProfileForUser(UserHandle currentUserHandle) {
arangelov47906052020-04-20 14:56:23 +01002761 if (currentUserHandle.equals(getPersonalProfileUserHandle())) {
arangelovc7581392020-04-07 17:10:36 +01002762 return PROFILE_PERSONAL;
arangelov47906052020-04-20 14:56:23 +01002763 } else if (currentUserHandle.equals(getWorkProfileUserHandle())) {
arangelovc7581392020-04-07 17:10:36 +01002764 return PROFILE_WORK;
2765 }
arangelov47906052020-04-20 14:56:23 +01002766 Log.e(TAG, "User " + currentUserHandle + " does not belong to a personal or work profile.");
arangelovc7581392020-04-07 17:10:36 +01002767 return -1;
2768 }
2769
arangelov28bb4fe2020-05-29 13:29:23 +01002770 private ViewGroup getActiveEmptyStateView() {
arangelova30787b2020-03-11 18:33:26 +00002771 int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage();
2772 return mChooserMultiProfilePagerAdapter.getItem(currentPage).getEmptyStateView();
2773 }
2774
Adam Powella182e452015-07-06 16:57:56 -07002775 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
2776 @Override
2777 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
2778 // Descending order
Adam Powell77a533f2015-10-16 10:47:32 -07002779 return (int) Math.signum(rhs.getScore() - lhs.getScore());
Adam Powella182e452015-07-06 16:57:56 -07002780 }
2781 }
2782
arangelovb0802dc2019-10-18 18:03:44 +01002783 @Override // ResolverListCommunicator
arangelov7981b122020-01-16 10:58:27 +00002784 public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
arangelovb0802dc2019-10-18 18:03:44 +01002785 mServicesRequested.clear();
arangelova3912cf2019-12-13 14:34:45 +00002786 mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged();
arangelov7981b122020-01-16 10:58:27 +00002787 super.onHandlePackagesChanged(listAdapter);
arangelovb0802dc2019-10-18 18:03:44 +01002788 }
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002789
arangelovb0802dc2019-10-18 18:03:44 +01002790 @Override // SelectableTargetInfoCommunicator
2791 public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) {
arangelova3912cf2019-12-13 14:34:45 +00002792 return mChooserMultiProfilePagerAdapter.getActiveListAdapter().makePresentationGetter(info);
arangelovb0802dc2019-10-18 18:03:44 +01002793 }
2794
2795 @Override // SelectableTargetInfoCommunicator
2796 public Intent getReferrerFillInIntent() {
2797 return mReferrerFillInIntent;
2798 }
2799
2800 @Override // ChooserListCommunicator
2801 public int getMaxRankedTargets() {
arangelov38a6fce2019-12-02 18:21:22 +00002802 return mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() == null
Zhen Zhangbde7b462019-11-11 11:49:33 -08002803 ? ChooserGridAdapter.MAX_TARGETS_PER_ROW_PORTRAIT
arangelov38a6fce2019-12-02 18:21:22 +00002804 : mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().getMaxTargetsPerRow();
arangelovb0802dc2019-10-18 18:03:44 +01002805 }
2806
2807 @Override // ChooserListCommunicator
arangelov5fc9e7d2020-01-07 17:59:14 +00002808 public void sendListViewUpdateMessage(UserHandle userHandle) {
2809 Message msg = Message.obtain();
2810 msg.what = ChooserHandler.LIST_VIEW_UPDATE_MESSAGE;
2811 msg.obj = userHandle;
2812 mChooserHandler.sendMessageDelayed(msg, LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
arangelovb0802dc2019-10-18 18:03:44 +01002813 }
2814
2815 @Override
arangelova3912cf2019-12-13 14:34:45 +00002816 public void onListRebuilt(ResolverListAdapter listAdapter) {
arangelovcf268642020-01-15 15:09:51 +00002817 setupScrollListener();
Kholoud Mohamed85a28ec2020-05-26 11:39:23 +01002818 maybeSetupGlobalLayoutListener();
arangelovcf268642020-01-15 15:09:51 +00002819
arangelova3912cf2019-12-13 14:34:45 +00002820 ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
arangelovc4dbdbd2020-02-18 20:54:16 +00002821 if (chooserListAdapter.getUserHandle()
arangelove5b369c2020-03-12 17:36:05 +00002822 .equals(mChooserMultiProfilePagerAdapter.getCurrentUserHandle())) {
arangelovc4dbdbd2020-02-18 20:54:16 +00002823 mChooserMultiProfilePagerAdapter.getActiveAdapterView()
2824 .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter());
2825 mChooserMultiProfilePagerAdapter
2826 .setupListAdapter(mChooserMultiProfilePagerAdapter.getCurrentPage());
2827 }
2828
arangelova3912cf2019-12-13 14:34:45 +00002829 if (chooserListAdapter.mDisplayList == null
2830 || chooserListAdapter.mDisplayList.isEmpty()) {
2831 chooserListAdapter.notifyDataSetChanged();
arangelovb0802dc2019-10-18 18:03:44 +01002832 } else {
Matt Pietal0aefc0c2020-06-16 14:04:52 -04002833 chooserListAdapter.updateAlphabeticalList();
arangelovb0802dc2019-10-18 18:03:44 +01002834 }
2835
2836 // don't support direct share on low ram devices
2837 if (ActivityManager.isLowRamDeviceStatic()) {
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05002838 getChooserActivityLogger().logSharesheetAppLoadComplete();
arangelovb0802dc2019-10-18 18:03:44 +01002839 return;
2840 }
2841
arangelov9e811f92020-06-01 17:37:28 +01002842 // no need to query direct share for work profile when its turned off
arangelov24156fa2020-06-10 19:21:32 +01002843 if (isQuietModeEnabled(chooserListAdapter.getUserHandle())) {
arangelov9e811f92020-06-01 17:37:28 +01002844 getChooserActivityLogger().logSharesheetAppLoadComplete();
2845 return;
2846 }
2847
arangelovb0802dc2019-10-18 18:03:44 +01002848 if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
2849 || ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
2850 if (DEBUG) {
2851 Log.d(TAG, "querying direct share targets from ShortcutManager");
2852 }
2853
arangelova3912cf2019-12-13 14:34:45 +00002854 queryDirectShareTargets(chooserListAdapter, false);
arangelovb0802dc2019-10-18 18:03:44 +01002855 }
2856 if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
2857 if (DEBUG) {
2858 Log.d(TAG, "List built querying services");
2859 }
2860
arangelova3912cf2019-12-13 14:34:45 +00002861 queryTargetServices(chooserListAdapter);
arangelovb0802dc2019-10-18 18:03:44 +01002862 }
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05002863
2864 getChooserActivityLogger().logSharesheetAppLoadComplete();
arangelovb0802dc2019-10-18 18:03:44 +01002865 }
2866
arangelov24156fa2020-06-10 19:21:32 +01002867 @VisibleForTesting
2868 protected boolean isQuietModeEnabled(UserHandle userHandle) {
2869 UserManager userManager = getSystemService(UserManager.class);
2870 return userManager.isQuietModeEnabled(userHandle);
2871 }
2872
arangelovcf268642020-01-15 15:09:51 +00002873 private void setupScrollListener() {
arangelovde5ebdc2020-04-22 19:12:39 +01002874 if (mResolverDrawerLayout == null) {
arangelovcf268642020-01-15 15:09:51 +00002875 return;
2876 }
arangelovde5ebdc2020-04-22 19:12:39 +01002877 int elevatedViewResId = shouldShowTabs() ? R.id.resolver_tab_divider : R.id.chooser_header;
2878 final View elevatedView = mResolverDrawerLayout.findViewById(elevatedViewResId);
2879 final float defaultElevation = elevatedView.getElevation();
arangelovcf268642020-01-15 15:09:51 +00002880 final float chooserHeaderScrollElevation =
2881 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
arangelovcf268642020-01-15 15:09:51 +00002882 mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener(
2883 new RecyclerView.OnScrollListener() {
2884 public void onScrollStateChanged(RecyclerView view, int scrollState) {
arangelovd85a74a2020-06-02 11:50:19 +01002885 if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
2886 if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) {
2887 mScrollStatus = SCROLL_STATUS_IDLE;
2888 setHorizontalScrollingEnabled(true);
2889 }
2890 } else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) {
2891 if (mScrollStatus == SCROLL_STATUS_IDLE) {
2892 mScrollStatus = SCROLL_STATUS_SCROLLING_VERTICAL;
2893 setHorizontalScrollingEnabled(false);
2894 }
2895 }
arangelovcf268642020-01-15 15:09:51 +00002896 }
2897
2898 public void onScrolled(RecyclerView view, int dx, int dy) {
2899 if (view.getChildCount() > 0) {
2900 View child = view.getLayoutManager().findViewByPosition(0);
2901 if (child == null || child.getTop() < 0) {
arangelovde5ebdc2020-04-22 19:12:39 +01002902 elevatedView.setElevation(chooserHeaderScrollElevation);
arangelovcf268642020-01-15 15:09:51 +00002903 return;
2904 }
2905 }
2906
arangelovde5ebdc2020-04-22 19:12:39 +01002907 elevatedView.setElevation(defaultElevation);
arangelovcf268642020-01-15 15:09:51 +00002908 }
2909 });
2910 }
2911
Kholoud Mohamed85a28ec2020-05-26 11:39:23 +01002912 private void maybeSetupGlobalLayoutListener() {
2913 if (shouldShowTabs()) {
2914 return;
2915 }
2916 final View recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
2917 recyclerView.getViewTreeObserver()
2918 .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
2919 @Override
2920 public void onGlobalLayout() {
2921 // Fixes an issue were the accessibility border disappears on list creation.
2922 recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
2923 final TextView titleView = findViewById(R.id.title);
2924 if (titleView != null) {
2925 titleView.setFocusable(true);
2926 titleView.setFocusableInTouchMode(true);
2927 titleView.requestFocus();
2928 titleView.requestAccessibilityFocus();
2929 }
2930 }
2931 });
2932 }
2933
arangelovb0802dc2019-10-18 18:03:44 +01002934 @Override // ChooserListCommunicator
2935 public boolean isSendAction(Intent targetIntent) {
Matt Pietal95574b02019-03-13 08:12:25 -04002936 if (targetIntent == null) {
2937 return false;
2938 }
2939
2940 String action = targetIntent.getAction();
2941 if (action == null) {
2942 return false;
2943 }
2944
2945 if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
2946 return true;
2947 }
2948
2949 return false;
2950 }
2951
arangelov590fba32020-02-11 18:05:42 +00002952 /**
2953 * The sticky content preview is shown only when we have a tabbed view. It's shown above
2954 * the tabs so it is not part of the scrollable list. If we are not in tabbed view,
2955 * we instead show the content preview as a regular list item.
2956 */
2957 private boolean shouldShowStickyContentPreview() {
arangelovc4dbdbd2020-02-18 20:54:16 +00002958 return shouldShowStickyContentPreviewNoOrientationCheck()
arangelovc5fb8392020-03-25 13:30:28 +00002959 && !getResources().getBoolean(R.bool.resolver_landscape_phone);
arangelova3912cf2019-12-13 14:34:45 +00002960 }
2961
arangelovc4dbdbd2020-02-18 20:54:16 +00002962 private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
2963 return shouldShowTabs()
2964 && mMultiProfilePagerAdapter.getListAdapterForUserHandle(
2965 UserHandle.of(UserHandle.myUserId())).getCount() > 0
2966 && isSendAction(getTargetIntent());
2967 }
2968
arangelov590fba32020-02-11 18:05:42 +00002969 private void updateStickyContentPreview() {
arangelovc4dbdbd2020-02-18 20:54:16 +00002970 if (shouldShowStickyContentPreviewNoOrientationCheck()) {
2971 // The sticky content preview is only shown when we show the work and personal tabs.
2972 // We don't show it in landscape as otherwise there is no room for scrolling.
2973 // If the sticky content preview will be shown at some point with orientation change,
2974 // then always preload it to avoid subsequent resizing of the share sheet.
2975 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
2976 if (contentPreviewContainer.getChildCount() == 0) {
2977 ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer);
2978 contentPreviewContainer.addView(contentPreviewView);
2979 }
2980 }
arangelov590fba32020-02-11 18:05:42 +00002981 if (shouldShowStickyContentPreview()) {
2982 showStickyContentPreview();
arangelova3912cf2019-12-13 14:34:45 +00002983 } else {
arangelov590fba32020-02-11 18:05:42 +00002984 hideStickyContentPreview();
arangelova3912cf2019-12-13 14:34:45 +00002985 }
2986 }
2987
arangelov590fba32020-02-11 18:05:42 +00002988 private void showStickyContentPreview() {
arangelovc4dbdbd2020-02-18 20:54:16 +00002989 if (isStickyContentPreviewShowing()) {
2990 return;
2991 }
arangelova3912cf2019-12-13 14:34:45 +00002992 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
2993 contentPreviewContainer.setVisibility(View.VISIBLE);
arangelovc4dbdbd2020-02-18 20:54:16 +00002994 }
2995
2996 private boolean isStickyContentPreviewShowing() {
2997 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
2998 return contentPreviewContainer.getVisibility() == View.VISIBLE;
arangelova3912cf2019-12-13 14:34:45 +00002999 }
3000
arangelov590fba32020-02-11 18:05:42 +00003001 private void hideStickyContentPreview() {
arangelovc4dbdbd2020-02-18 20:54:16 +00003002 if (!isStickyContentPreviewShowing()) {
3003 return;
3004 }
arangelova3912cf2019-12-13 14:34:45 +00003005 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
arangelova3912cf2019-12-13 14:34:45 +00003006 contentPreviewContainer.setVisibility(View.GONE);
3007 }
3008
3009 private void logActionShareWithPreview() {
3010 Intent targetIntent = getTargetIntent();
3011 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
3012 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
3013 .setSubtype(previewType));
3014 }
3015
arangelov706316d2020-05-06 14:25:06 +01003016 class ViewHolderBase extends RecyclerView.ViewHolder {
3017 private int mViewType;
3018
3019 ViewHolderBase(View itemView, int viewType) {
3020 super(itemView);
3021 this.mViewType = viewType;
3022 }
3023
3024 int getViewType() {
3025 return mViewType;
3026 }
3027 }
3028
Zhen Zhangbde7b462019-11-11 11:49:33 -08003029 /**
3030 * Used to bind types of individual item including
3031 * {@link ChooserGridAdapter#VIEW_TYPE_NORMAL},
arangelov590fba32020-02-11 18:05:42 +00003032 * {@link ChooserGridAdapter#VIEW_TYPE_CONTENT_PREVIEW},
Zhen Zhangbde7b462019-11-11 11:49:33 -08003033 * {@link ChooserGridAdapter#VIEW_TYPE_PROFILE},
3034 * and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}.
3035 */
arangelov706316d2020-05-06 14:25:06 +01003036 final class ItemViewHolder extends ViewHolderBase {
Zhen Zhangbde7b462019-11-11 11:49:33 -08003037 ResolverListAdapter.ViewHolder mWrappedViewHolder;
3038 int mListPosition = ChooserListAdapter.NO_POSITION;
3039
arangelov706316d2020-05-06 14:25:06 +01003040 ItemViewHolder(View itemView, boolean isClickable, int viewType) {
3041 super(itemView, viewType);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003042 mWrappedViewHolder = new ResolverListAdapter.ViewHolder(itemView);
3043 if (isClickable) {
3044 itemView.setOnClickListener(v -> startSelected(mListPosition,
3045 false/* always */, true/* filterd */));
Mike Digman64c70432020-05-21 11:17:51 -07003046
Mike Digmandc294792020-05-27 10:31:16 -07003047 itemView.setOnLongClickListener(v -> {
3048 final TargetInfo ti = mChooserMultiProfilePagerAdapter.getActiveListAdapter()
3049 .targetInfoForPosition(mListPosition, /* filtered */ true);
Mike Digman64c70432020-05-21 11:17:51 -07003050
Mike Digmandc294792020-05-27 10:31:16 -07003051 // This should always be the case for ItemViewHolder, check for sanity
3052 if (ti instanceof DisplayResolveInfo) {
Mike Digman64c70432020-05-21 11:17:51 -07003053 showTargetDetails((DisplayResolveInfo) ti);
Mike Digmandc294792020-05-27 10:31:16 -07003054 }
3055 return true;
3056 });
Zhen Zhangbde7b462019-11-11 11:49:33 -08003057 }
3058 }
3059 }
3060
3061 /**
Matt Pietal9236adc2019-12-12 09:24:23 -05003062 * Add a footer to the list, to support scrolling behavior below the navbar.
arangelov38a6fce2019-12-02 18:21:22 +00003063 */
arangelov706316d2020-05-06 14:25:06 +01003064 final class FooterViewHolder extends ViewHolderBase {
3065 FooterViewHolder(View itemView, int viewType) {
3066 super(itemView, viewType);
Matt Pietal9236adc2019-12-12 09:24:23 -05003067 }
arangelov38a6fce2019-12-02 18:21:22 +00003068 }
3069
3070 /**
3071 * Intentionally override the {@link ResolverActivity} implementation as we only need that
3072 * implementation for the intent resolver case.
3073 */
3074 @Override
3075 public void onButtonClick(View v) {}
3076
3077 /**
3078 * Intentionally override the {@link ResolverActivity} implementation as we only need that
3079 * implementation for the intent resolver case.
3080 */
3081 @Override
3082 protected void resetButtonBar() {}
3083
arangelov4bf17472020-02-17 20:21:46 +00003084 @Override
3085 protected String getMetricsCategory() {
3086 return METRICS_CATEGORY_CHOOSER;
3087 }
3088
arangelov2b15d712020-04-22 14:54:31 +01003089 @Override
3090 protected void onProfileTabSelected() {
3091 ChooserGridAdapter currentRootAdapter =
3092 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
3093 currentRootAdapter.updateDirectShareExpansion();
3094 }
3095
arangelov28bb4fe2020-05-29 13:29:23 +01003096 @Override
3097 protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
3098 if (shouldShowTabs()) {
3099 mChooserMultiProfilePagerAdapter
3100 .setEmptyStateBottomOffset(insets.getSystemWindowInsetBottom());
3101 mChooserMultiProfilePagerAdapter.setupContainerPadding(
3102 getActiveEmptyStateView().findViewById(R.id.resolver_empty_state_container));
3103 }
3104 return super.onApplyWindowInsets(v, insets);
3105 }
3106
arangelovd85a74a2020-06-02 11:50:19 +01003107 private void setHorizontalScrollingEnabled(boolean enabled) {
3108 ResolverViewPager viewPager = findViewById(R.id.profile_pager);
3109 viewPager.setSwipingEnabled(enabled);
3110 }
3111
3112 private void setVerticalScrollEnabled(boolean enabled) {
3113 ChooserGridLayoutManager layoutManager =
3114 (ChooserGridLayoutManager) mChooserMultiProfilePagerAdapter.getActiveAdapterView()
3115 .getLayoutManager();
3116 layoutManager.setVerticalScrollEnabled(enabled);
3117 }
3118
3119 @Override
3120 void onHorizontalSwipeStateChanged(int state) {
3121 if (state == ViewPager.SCROLL_STATE_DRAGGING) {
3122 if (mScrollStatus == SCROLL_STATUS_IDLE) {
3123 mScrollStatus = SCROLL_STATUS_SCROLLING_HORIZONTAL;
3124 setVerticalScrollEnabled(false);
3125 }
3126 } else if (state == ViewPager.SCROLL_STATE_IDLE) {
arangelov0e383d72020-06-11 15:52:13 +01003127 if (mScrollStatus == SCROLL_STATUS_SCROLLING_HORIZONTAL) {
arangelovd85a74a2020-06-02 11:50:19 +01003128 mScrollStatus = SCROLL_STATUS_IDLE;
3129 setVerticalScrollEnabled(true);
3130 }
3131 }
3132 }
3133
arangelov38a6fce2019-12-02 18:21:22 +00003134 /**
Zhen Zhangbde7b462019-11-11 11:49:33 -08003135 * Adapter for all types of items and targets in ShareSheet.
3136 * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the
3137 * row level by this adapter but not on the item level. Individual targets within the row are
3138 * handled by {@link ChooserListAdapter}
3139 */
arangelovcf268642020-01-15 15:09:51 +00003140 @VisibleForTesting
3141 public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
Adam Powell7d758002015-05-06 17:49:36 -07003142 private ChooserListAdapter mChooserListAdapter;
3143 private final LayoutInflater mLayoutInflater;
Adam Powell7d758002015-05-06 17:49:36 -07003144
Matt Pietal5b648562019-03-12 07:40:26 -04003145 private DirectShareViewHolder mDirectShareViewHolder;
Matt Pietalab986b52019-04-10 10:14:32 -04003146 private int mChooserTargetWidth = 0;
Mike Digman849a9d12019-04-29 11:20:48 -07003147 private boolean mShowAzLabelIfPoss;
Matt Pietal5b648562019-03-12 07:40:26 -04003148
arangelov590fba32020-02-11 18:05:42 +00003149 private boolean mHideContentPreview = false;
Matt Pietale7cacab2019-05-23 07:21:36 -04003150 private boolean mLayoutRequested = false;
3151
Matt Pietal2025a1b2020-01-07 10:08:07 -05003152 private int mFooterHeight = 0;
Matt Pietal9236adc2019-12-12 09:24:23 -05003153
Matt Pietal5b648562019-03-12 07:40:26 -04003154 private static final int VIEW_TYPE_DIRECT_SHARE = 0;
3155 private static final int VIEW_TYPE_NORMAL = 1;
arangelov590fba32020-02-11 18:05:42 +00003156 private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
3157 private static final int VIEW_TYPE_PROFILE = 3;
3158 private static final int VIEW_TYPE_AZ_LABEL = 4;
3159 private static final int VIEW_TYPE_CALLER_AND_RANK = 5;
3160 private static final int VIEW_TYPE_FOOTER = 6;
Matt Pietal5b648562019-03-12 07:40:26 -04003161
Matt Pietalfaedea82019-03-21 10:36:54 -04003162 private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
3163 private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
3164
Mike Digman849a9d12019-04-29 11:20:48 -07003165 private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
3166
Zhen Zhangbde7b462019-11-11 11:49:33 -08003167 ChooserGridAdapter(ChooserListAdapter wrappedAdapter) {
3168 super();
Adam Powell7d758002015-05-06 17:49:36 -07003169 mChooserListAdapter = wrappedAdapter;
3170 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
3171
Mike Digman849a9d12019-04-29 11:20:48 -07003172 mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
3173
Adam Powell7d758002015-05-06 17:49:36 -07003174 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
3175 @Override
3176 public void onChanged() {
3177 super.onChanged();
3178 notifyDataSetChanged();
3179 }
3180
3181 @Override
3182 public void onInvalidated() {
3183 super.onInvalidated();
Zhen Zhangbde7b462019-11-11 11:49:33 -08003184 notifyDataSetChanged();
Adam Powell7d758002015-05-06 17:49:36 -07003185 }
3186 });
3187 }
3188
Matt Pietal9236adc2019-12-12 09:24:23 -05003189 public void setFooterHeight(int height) {
Matt Pietal2025a1b2020-01-07 10:08:07 -05003190 mFooterHeight = height;
Matt Pietal9236adc2019-12-12 09:24:23 -05003191 }
3192
Matt Pietalfaedea82019-03-21 10:36:54 -04003193 /**
Matt Pietalab986b52019-04-10 10:14:32 -04003194 * Calculate the chooser target width to maximize space per item
Matt Pietalfaedea82019-03-21 10:36:54 -04003195 *
3196 * @param width The new row width to use for recalculation
Matt Pietalab986b52019-04-10 10:14:32 -04003197 * @return true if the view width has changed
Matt Pietalfaedea82019-03-21 10:36:54 -04003198 */
Matt Pietalab986b52019-04-10 10:14:32 -04003199 public boolean calculateChooserTargetWidth(int width) {
Matt Pietalab986b52019-04-10 10:14:32 -04003200 if (width == 0) {
Matt Pietalfaedea82019-03-21 10:36:54 -04003201 return false;
3202 }
3203
Zhen Zhangbde7b462019-11-11 11:49:33 -08003204 int newWidth = width / getMaxTargetsPerRow();
Matt Pietalab986b52019-04-10 10:14:32 -04003205 if (newWidth != mChooserTargetWidth) {
3206 mChooserTargetWidth = newWidth;
Matt Pietalfaedea82019-03-21 10:36:54 -04003207 return true;
3208 }
3209
3210 return false;
3211 }
3212
arangelov38a6fce2019-12-02 18:21:22 +00003213 int getMaxTargetsPerRow() {
Matt Pietalfaedea82019-03-21 10:36:54 -04003214 int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
arangelov706316d2020-05-06 14:25:06 +01003215 if (mShouldDisplayLandscape) {
Matt Pietalfaedea82019-03-21 10:36:54 -04003216 maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
3217 }
Matt Pietalab986b52019-04-10 10:14:32 -04003218 return maxTargets;
Matt Pietal5b648562019-03-12 07:40:26 -04003219 }
3220
arangelov590fba32020-02-11 18:05:42 +00003221 /**
3222 * Hides the list item content preview.
3223 * <p>Not to be confused with the sticky content preview which is above the
3224 * personal and work tabs.
3225 */
3226 public void hideContentPreview() {
3227 mHideContentPreview = true;
3228 mLayoutRequested = true;
3229 notifyDataSetChanged();
3230 }
3231
Matt Pietale7cacab2019-05-23 07:21:36 -04003232 public boolean consumeLayoutRequest() {
3233 boolean oldValue = mLayoutRequested;
3234 mLayoutRequested = false;
3235 return oldValue;
3236 }
3237
Zhen Zhangbde7b462019-11-11 11:49:33 -08003238 public int getRowCount() {
Adam Powell7d758002015-05-06 17:49:36 -07003239 return (int) (
arangelov590fba32020-02-11 18:05:42 +00003240 getContentPreviewRowCount()
3241 + getProfileRowCount()
Matt Pietal26038402019-01-08 07:29:34 -05003242 + getServiceTargetRowCount()
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04003243 + getCallerAndRankedTargetRowCount()
Mike Digmanae730b12019-04-25 11:10:31 -07003244 + getAzLabelRowCount()
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04003245 + Math.ceil(
3246 (float) mChooserListAdapter.getAlphaTargetCount()
3247 / getMaxTargetsPerRow())
Adam Powell7d758002015-05-06 17:49:36 -07003248 );
3249 }
3250
arangelov590fba32020-02-11 18:05:42 +00003251 /**
3252 * Returns either {@code 0} or {@code 1} depending on whether we want to show the list item
3253 * content preview. Not to be confused with the sticky content preview which is above the
3254 * personal and work tabs.
3255 */
3256 public int getContentPreviewRowCount() {
3257 // For the tabbed case we show the sticky content preview above the tabs,
3258 // please refer to shouldShowStickyContentPreview
arangelovf163a882020-02-18 17:18:47 +00003259 if (shouldShowTabs()) {
arangelov590fba32020-02-11 18:05:42 +00003260 return 0;
3261 }
3262 if (!isSendAction(getTargetIntent())) {
3263 return 0;
3264 }
3265
3266 if (mHideContentPreview || mChooserListAdapter == null
3267 || mChooserListAdapter.getCount() == 0) {
3268 return 0;
3269 }
3270
3271 return 1;
3272 }
3273
Matt Pietal74c6ed02019-04-18 13:38:46 -04003274 public int getProfileRowCount() {
arangelovf163a882020-02-18 17:18:47 +00003275 if (shouldShowTabs()) {
arangelov590fba32020-02-11 18:05:42 +00003276 return 0;
3277 }
Matt Pietal74c6ed02019-04-18 13:38:46 -04003278 return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
3279 }
3280
Matt Pietal9236adc2019-12-12 09:24:23 -05003281 public int getFooterRowCount() {
3282 return 1;
3283 }
3284
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04003285 public int getCallerAndRankedTargetRowCount() {
Adam Powell63b31692015-09-28 10:45:00 -07003286 return (int) Math.ceil(
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04003287 ((float) mChooserListAdapter.getCallerTargetCount()
3288 + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow());
Adam Powell63b31692015-09-28 10:45:00 -07003289 }
3290
Matt Pietal5b648562019-03-12 07:40:26 -04003291 // There can be at most one row in the listview, that is internally
3292 // a ViewGroup with 2 rows
Adam Powell63b31692015-09-28 10:45:00 -07003293 public int getServiceTargetRowCount() {
arangelovb0802dc2019-10-18 18:03:44 +01003294 if (isSendAction(getTargetIntent())
3295 && !ActivityManager.isLowRamDeviceStatic()) {
Matt Pietal95574b02019-03-13 08:12:25 -04003296 return 1;
3297 }
3298 return 0;
Adam Powell63b31692015-09-28 10:45:00 -07003299 }
3300
Mike Digmanae730b12019-04-25 11:10:31 -07003301 public int getAzLabelRowCount() {
3302 // Only show a label if the a-z list is showing
Mike Digman849a9d12019-04-29 11:20:48 -07003303 return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
Mike Digmanae730b12019-04-25 11:10:31 -07003304 }
3305
Adam Powell7d758002015-05-06 17:49:36 -07003306 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08003307 public int getItemCount() {
3308 return (int) (
arangelov590fba32020-02-11 18:05:42 +00003309 getContentPreviewRowCount()
3310 + getProfileRowCount()
Zhen Zhangbde7b462019-11-11 11:49:33 -08003311 + getServiceTargetRowCount()
3312 + getCallerAndRankedTargetRowCount()
3313 + getAzLabelRowCount()
3314 + mChooserListAdapter.getAlphaTargetCount()
Matt Pietal9236adc2019-12-12 09:24:23 -05003315 + getFooterRowCount()
Zhen Zhangbde7b462019-11-11 11:49:33 -08003316 );
Adam Powell7d758002015-05-06 17:49:36 -07003317 }
3318
3319 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08003320 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
3321 switch (viewType) {
arangelov590fba32020-02-11 18:05:42 +00003322 case VIEW_TYPE_CONTENT_PREVIEW:
arangelov706316d2020-05-06 14:25:06 +01003323 return new ItemViewHolder(createContentPreviewView(parent), false, viewType);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003324 case VIEW_TYPE_PROFILE:
arangelov706316d2020-05-06 14:25:06 +01003325 return new ItemViewHolder(createProfileView(parent), false, viewType);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003326 case VIEW_TYPE_AZ_LABEL:
arangelov706316d2020-05-06 14:25:06 +01003327 return new ItemViewHolder(createAzLabelView(parent), false, viewType);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003328 case VIEW_TYPE_NORMAL:
arangelov706316d2020-05-06 14:25:06 +01003329 return new ItemViewHolder(
3330 mChooserListAdapter.createView(parent), true, viewType);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003331 case VIEW_TYPE_DIRECT_SHARE:
3332 case VIEW_TYPE_CALLER_AND_RANK:
3333 return createItemGroupViewHolder(viewType, parent);
Matt Pietal9236adc2019-12-12 09:24:23 -05003334 case VIEW_TYPE_FOOTER:
Matt Pietal2025a1b2020-01-07 10:08:07 -05003335 Space sp = new Space(parent.getContext());
3336 sp.setLayoutParams(new RecyclerView.LayoutParams(
3337 LayoutParams.MATCH_PARENT, mFooterHeight));
arangelov706316d2020-05-06 14:25:06 +01003338 return new FooterViewHolder(sp, viewType);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003339 default:
3340 // Since we catch all possible viewTypes above, no chance this is being called.
3341 return null;
3342 }
Adam Powell7d758002015-05-06 17:49:36 -07003343 }
3344
3345 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -08003346 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
arangelov706316d2020-05-06 14:25:06 +01003347 int viewType = ((ViewHolderBase) holder).getViewType();
Zhen Zhangbde7b462019-11-11 11:49:33 -08003348 switch (viewType) {
3349 case VIEW_TYPE_DIRECT_SHARE:
3350 case VIEW_TYPE_CALLER_AND_RANK:
3351 bindItemGroupViewHolder(position, (ItemGroupViewHolder) holder);
3352 break;
3353 case VIEW_TYPE_NORMAL:
3354 bindItemViewHolder(position, (ItemViewHolder) holder);
3355 break;
3356 default:
Matt Pietal1ef88002019-03-13 10:43:18 -04003357 }
Adam Powell7d758002015-05-06 17:49:36 -07003358 }
3359
Matt Pietal5b648562019-03-12 07:40:26 -04003360 @Override
3361 public int getItemViewType(int position) {
Mike Digmanae730b12019-04-25 11:10:31 -07003362 int count;
Matt Pietal1ef88002019-03-13 10:43:18 -04003363
arangelov590fba32020-02-11 18:05:42 +00003364 int countSum = (count = getContentPreviewRowCount());
3365 if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
3366
3367 countSum += (count = getProfileRowCount());
Mike Digmanae730b12019-04-25 11:10:31 -07003368 if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
Adam Powell63b31692015-09-28 10:45:00 -07003369
Mike Digmanae730b12019-04-25 11:10:31 -07003370 countSum += (count = getServiceTargetRowCount());
3371 if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
3372
3373 countSum += (count = getCallerAndRankedTargetRowCount());
Zhen Zhangbde7b462019-11-11 11:49:33 -08003374 if (count > 0 && position < countSum) return VIEW_TYPE_CALLER_AND_RANK;
Mike Digmanae730b12019-04-25 11:10:31 -07003375
3376 countSum += (count = getAzLabelRowCount());
3377 if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
Matt Pietal5b648562019-03-12 07:40:26 -04003378
Matt Pietal9236adc2019-12-12 09:24:23 -05003379 if (position == getItemCount() - 1) return VIEW_TYPE_FOOTER;
3380
Matt Pietal5b648562019-03-12 07:40:26 -04003381 return VIEW_TYPE_NORMAL;
3382 }
3383
Zhen Zhangbde7b462019-11-11 11:49:33 -08003384 public int getTargetType(int position) {
3385 return mChooserListAdapter.getPositionTargetType(getListPosition(position));
Matt Pietal1ef88002019-03-13 10:43:18 -04003386 }
3387
Zhen Zhangbde7b462019-11-11 11:49:33 -08003388 private View createProfileView(ViewGroup parent) {
3389 View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false);
Matt Pietal74c6ed02019-04-18 13:38:46 -04003390 profileRow.setBackground(
3391 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
3392 mProfileView = profileRow.findViewById(R.id.profile_button);
3393 mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
arangelovb0802dc2019-10-18 18:03:44 +01003394 updateProfileViewButton();
Matt Pietal74c6ed02019-04-18 13:38:46 -04003395 return profileRow;
3396 }
3397
Mike Digmanae730b12019-04-25 11:10:31 -07003398 private View createAzLabelView(ViewGroup parent) {
3399 return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
3400 }
3401
Zhen Zhangbde7b462019-11-11 11:49:33 -08003402 private ItemGroupViewHolder loadViewsIntoGroup(ItemGroupViewHolder holder) {
Matt Pietal5b648562019-03-12 07:40:26 -04003403 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietalab986b52019-04-10 10:14:32 -04003404 final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
3405 MeasureSpec.EXACTLY);
Matt Pietal5b648562019-03-12 07:40:26 -04003406 int columnCount = holder.getColumnCount();
3407
Mike Digmanba232682019-03-27 14:55:26 -07003408 final boolean isDirectShare = holder instanceof DirectShareViewHolder;
3409
Matt Pietal5b648562019-03-12 07:40:26 -04003410 for (int i = 0; i < columnCount; i++) {
Mike Digmanba232682019-03-27 14:55:26 -07003411 final View v = mChooserListAdapter.createView(holder.getRowByIndex(i));
Adam Powell4eb98712015-10-14 13:10:18 -07003412 final int column = i;
Adam Powell63b31692015-09-28 10:45:00 -07003413 v.setOnClickListener(new OnClickListener() {
3414 @Override
3415 public void onClick(View v) {
Matt Pietal5b648562019-03-12 07:40:26 -04003416 startSelected(holder.getItemIndex(column), false, true);
Adam Powell63b31692015-09-28 10:45:00 -07003417 }
3418 });
Mike Digman64c70432020-05-21 11:17:51 -07003419
3420 // Direct Share targets should not show any menu
3421 if (!isDirectShare) {
Mike Digmandc294792020-05-27 10:31:16 -07003422 v.setOnLongClickListener(v1 -> {
3423 final TargetInfo ti = mChooserListAdapter.targetInfoForPosition(
3424 holder.getItemIndex(column), true);
3425 // This should always be the case for non-DS targets, check for sanity
3426 if (ti instanceof DisplayResolveInfo) {
Mike Digman64c70432020-05-21 11:17:51 -07003427 showTargetDetails((DisplayResolveInfo) ti);
Mike Digmandc294792020-05-27 10:31:16 -07003428 }
3429 return true;
3430 });
Mike Digman64c70432020-05-21 11:17:51 -07003431 }
3432
Zhen Zhangbde7b462019-11-11 11:49:33 -08003433 holder.addView(i, v);
Adam Powell63b31692015-09-28 10:45:00 -07003434
Mike Digmanba232682019-03-27 14:55:26 -07003435 // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
3436 // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be
3437 // done before measuring.
3438 if (isDirectShare) {
3439 final ViewHolder vh = (ViewHolder) v.getTag();
3440 vh.text.setLines(2);
3441 vh.text.setHorizontallyScrolling(false);
3442 vh.text2.setVisibility(View.GONE);
Adam Powell63b31692015-09-28 10:45:00 -07003443 }
Mike Digmanba232682019-03-27 14:55:26 -07003444
3445 // Force height to be a given so we don't have visual disruption during scaling.
Matt Pietalab986b52019-04-10 10:14:32 -04003446 v.measure(exactSpec, spec);
3447 setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight());
Adam Powell63b31692015-09-28 10:45:00 -07003448 }
3449
Matt Pietal5b648562019-03-12 07:40:26 -04003450 final ViewGroup viewGroup = holder.getViewGroup();
3451
Mike Digmanba232682019-03-27 14:55:26 -07003452 // Pre-measure and fix height so we can scale later.
Adam Powell63b31692015-09-28 10:45:00 -07003453 holder.measure();
Matt Pietalab986b52019-04-10 10:14:32 -04003454 setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight());
Mike Digmanba232682019-03-27 14:55:26 -07003455
3456 if (isDirectShare) {
3457 DirectShareViewHolder dsvh = (DirectShareViewHolder) holder;
Matt Pietalab986b52019-04-10 10:14:32 -04003458 setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
3459 setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
Adam Powell7d758002015-05-06 17:49:36 -07003460 }
Matt Pietal5b648562019-03-12 07:40:26 -04003461
3462 viewGroup.setTag(holder);
Adam Powell7d758002015-05-06 17:49:36 -07003463 return holder;
3464 }
3465
Matt Pietalab986b52019-04-10 10:14:32 -04003466 private void setViewBounds(View view, int widthPx, int heightPx) {
Mike Digmanba232682019-03-27 14:55:26 -07003467 LayoutParams lp = view.getLayoutParams();
3468 if (lp == null) {
Matt Pietalab986b52019-04-10 10:14:32 -04003469 lp = new LayoutParams(widthPx, heightPx);
Mike Digmanba232682019-03-27 14:55:26 -07003470 view.setLayoutParams(lp);
3471 } else {
3472 lp.height = heightPx;
Matt Pietalab986b52019-04-10 10:14:32 -04003473 lp.width = widthPx;
Mike Digmanba232682019-03-27 14:55:26 -07003474 }
3475 }
3476
Zhen Zhangbde7b462019-11-11 11:49:33 -08003477 ItemGroupViewHolder createItemGroupViewHolder(int viewType, ViewGroup parent) {
Matt Pietal5b648562019-03-12 07:40:26 -04003478 if (viewType == VIEW_TYPE_DIRECT_SHARE) {
3479 ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate(
3480 R.layout.chooser_row_direct_share, parent, false);
3481 ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3482 parentGroup, false);
3483 ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3484 parentGroup, false);
3485 parentGroup.addView(row1);
3486 parentGroup.addView(row2);
3487
3488 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
arangelov706316d2020-05-06 14:25:06 +01003489 Lists.newArrayList(row1, row2), getMaxTargetsPerRow(), viewType);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003490 loadViewsIntoGroup(mDirectShareViewHolder);
Matt Pietal5b648562019-03-12 07:40:26 -04003491
3492 return mDirectShareViewHolder;
3493 } else {
3494 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
3495 false);
arangelov706316d2020-05-06 14:25:06 +01003496 ItemGroupViewHolder holder =
3497 new SingleRowViewHolder(row, getMaxTargetsPerRow(), viewType);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003498 loadViewsIntoGroup(holder);
Matt Pietal5b648562019-03-12 07:40:26 -04003499
3500 return holder;
3501 }
3502 }
3503
Matt Pietaldadc0d12019-04-16 12:53:28 -04003504 /**
Mike Digmanae730b12019-04-25 11:10:31 -07003505 * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
3506 * showing on top of the AZ list if the AZ label is visible. All other types are placed into
3507 * their own row as determined by their target type, and dividers are added in the list to
3508 * separate each type.
Matt Pietaldadc0d12019-04-16 12:53:28 -04003509 */
3510 int getRowType(int rowPosition) {
Mike Digmanae730b12019-04-25 11:10:31 -07003511 // Merge caller and ranked standard into a single row
Matt Pietaldadc0d12019-04-16 12:53:28 -04003512 int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
3513 if (positionType == ChooserListAdapter.TARGET_CALLER) {
3514 return ChooserListAdapter.TARGET_STANDARD;
3515 }
3516
Mike Digmanae730b12019-04-25 11:10:31 -07003517 // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
3518 // row type the same as the suggestion row type
3519 if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
3520 return ChooserListAdapter.TARGET_STANDARD;
3521 }
3522
Matt Pietaldadc0d12019-04-16 12:53:28 -04003523 return positionType;
3524 }
3525
Zhen Zhangbde7b462019-11-11 11:49:33 -08003526 void bindItemViewHolder(int position, ItemViewHolder holder) {
3527 View v = holder.itemView;
3528 int listPosition = getListPosition(position);
3529 holder.mListPosition = listPosition;
3530 mChooserListAdapter.bindView(listPosition, v);
3531 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003532
Zhen Zhangbde7b462019-11-11 11:49:33 -08003533 void bindItemGroupViewHolder(int position, ItemGroupViewHolder holder) {
3534 final ViewGroup viewGroup = (ViewGroup) holder.itemView;
3535 int start = getListPosition(position);
3536 int startType = getRowType(start);
arangelov934c64c2020-02-14 13:48:23 +00003537 if (viewGroup.getForeground() == null && position > 0) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08003538 viewGroup.setForeground(
Matt Pietal74c6ed02019-04-18 13:38:46 -04003539 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003540 }
3541
Matt Pietalfaedea82019-03-21 10:36:54 -04003542 int columnCount = holder.getColumnCount();
Matt Pietal5b648562019-03-12 07:40:26 -04003543 int end = start + columnCount - 1;
Matt Pietaldadc0d12019-04-16 12:53:28 -04003544 while (getRowType(end) != startType && end >= start) {
Adam Powell7d758002015-05-06 17:49:36 -07003545 end--;
3546 }
3547
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003548 if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
Zhen Zhangbde7b462019-11-11 11:49:33 -08003549 final TextView textView = viewGroup.findViewById(R.id.chooser_row_text_option);
Adam Powell63b31692015-09-28 10:45:00 -07003550
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003551 if (textView.getVisibility() != View.VISIBLE) {
3552 textView.setAlpha(0.0f);
3553 textView.setVisibility(View.VISIBLE);
3554 textView.setText(R.string.chooser_no_direct_share_targets);
3555
3556 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
3557 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3558
3559 float translationInPx = getResources().getDimensionPixelSize(
3560 R.dimen.chooser_row_text_option_translate);
3561 textView.setTranslationY(translationInPx);
3562 ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
3563 0.0f);
3564 translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3565
3566 AnimatorSet animSet = new AnimatorSet();
3567 animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3568 animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3569 animSet.playTogether(fadeAnim, translateAnim);
3570 animSet.start();
3571 }
Adam Powell7d758002015-05-06 17:49:36 -07003572 }
3573
Matt Pietal5b648562019-03-12 07:40:26 -04003574 for (int i = 0; i < columnCount; i++) {
3575 final View v = holder.getView(i);
arangelov38a6fce2019-12-02 18:21:22 +00003576
Adam Powell7d758002015-05-06 17:49:36 -07003577 if (start + i <= end) {
Matt Pietalfaedea82019-03-21 10:36:54 -04003578 holder.setViewVisibility(i, View.VISIBLE);
Matt Pietal5b648562019-03-12 07:40:26 -04003579 holder.setItemIndex(i, start + i);
3580 mChooserListAdapter.bindView(holder.getItemIndex(i), v);
Adam Powell7d758002015-05-06 17:49:36 -07003581 } else {
Matt Pietalfaedea82019-03-21 10:36:54 -04003582 holder.setViewVisibility(i, View.INVISIBLE);
Adam Powell7d758002015-05-06 17:49:36 -07003583 }
3584 }
3585 }
3586
Zhen Zhangbde7b462019-11-11 11:49:33 -08003587 int getListPosition(int position) {
arangelov590fba32020-02-11 18:05:42 +00003588 position -= getContentPreviewRowCount() + getProfileRowCount();
Matt Pietal1ef88002019-03-13 10:43:18 -04003589
Matt Pietal5b648562019-03-12 07:40:26 -04003590 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
3591 final int serviceRows = (int) Math.ceil((float) serviceCount
3592 / ChooserListAdapter.MAX_SERVICE_TARGETS);
Zhen Zhangbde7b462019-11-11 11:49:33 -08003593 if (position < serviceRows) {
3594 return position * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07003595 }
3596
Zhen Zhangbde7b462019-11-11 11:49:33 -08003597 position -= serviceRows;
3598
Matt Pietaldadc0d12019-04-16 12:53:28 -04003599 final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
3600 + mChooserListAdapter.getRankedTargetCount();
3601 final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
Zhen Zhangbde7b462019-11-11 11:49:33 -08003602 if (position < callerAndRankedRows) {
3603 return serviceCount + position * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07003604 }
3605
Zhen Zhangbde7b462019-11-11 11:49:33 -08003606 position -= getAzLabelRowCount() + callerAndRankedRows;
Mike Digmanae730b12019-04-25 11:10:31 -07003607
Zhen Zhangbde7b462019-11-11 11:49:33 -08003608 return callerAndRankedCount + serviceCount + position;
Matt Pietal5b648562019-03-12 07:40:26 -04003609 }
3610
3611 public void handleScroll(View v, int y, int oldy) {
arangelov2b15d712020-04-22 14:54:31 +01003612 boolean canExpandDirectShare = canExpandDirectShare();
Matt Pietale54dcc2e2019-05-02 12:59:38 -04003613 if (mDirectShareViewHolder != null && canExpandDirectShare) {
arangelov38a6fce2019-12-02 18:21:22 +00003614 mDirectShareViewHolder.handleScroll(
arangelovcf268642020-01-15 15:09:51 +00003615 mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy,
arangelov38a6fce2019-12-02 18:21:22 +00003616 getMaxTargetsPerRow());
Matt Pietal5b648562019-03-12 07:40:26 -04003617 }
Adam Powell7d758002015-05-06 17:49:36 -07003618 }
arangelov38a6fce2019-12-02 18:21:22 +00003619
arangelov2b15d712020-04-22 14:54:31 +01003620 /**
Song Hub548df92020-05-16 08:42:54 -07003621 * Only expand direct share area if there is a minimum number of targets.
arangelov2b15d712020-04-22 14:54:31 +01003622 */
3623 private boolean canExpandDirectShare() {
Matt Pietal185f7152020-06-10 11:40:55 -04003624 // Do not enable until we have confirmed more apps are using sharing shortcuts
3625 // Check git history for enablement logic
3626 return false;
arangelov2b15d712020-04-22 14:54:31 +01003627 }
3628
arangelov38a6fce2019-12-02 18:21:22 +00003629 public ChooserListAdapter getListAdapter() {
3630 return mChooserListAdapter;
3631 }
3632
arangelov38a6fce2019-12-02 18:21:22 +00003633 boolean shouldCellSpan(int position) {
3634 return getItemViewType(position) == VIEW_TYPE_NORMAL;
3635 }
arangelov2b15d712020-04-22 14:54:31 +01003636
3637 void updateDirectShareExpansion() {
3638 if (mDirectShareViewHolder == null || !canExpandDirectShare()) {
3639 return;
3640 }
3641 RecyclerView activeAdapterView =
3642 mChooserMultiProfilePagerAdapter.getActiveAdapterView();
3643 if (mResolverDrawerLayout.isCollapsed()) {
3644 mDirectShareViewHolder.collapse(activeAdapterView);
3645 } else {
3646 mDirectShareViewHolder.expand(activeAdapterView);
3647 }
3648 }
Adam Powell7d758002015-05-06 17:49:36 -07003649 }
3650
Zhen Zhangbde7b462019-11-11 11:49:33 -08003651 /**
3652 * Used to bind types for group of items including:
3653 * {@link ChooserGridAdapter#VIEW_TYPE_DIRECT_SHARE},
3654 * and {@link ChooserGridAdapter#VIEW_TYPE_CALLER_AND_RANK}.
3655 */
arangelov706316d2020-05-06 14:25:06 +01003656 abstract class ItemGroupViewHolder extends ViewHolderBase {
Matt Pietal5b648562019-03-12 07:40:26 -04003657 protected int mMeasuredRowHeight;
3658 private int[] mItemIndices;
3659 protected final View[] mCells;
Matt Pietal5b648562019-03-12 07:40:26 -04003660 private final int mColumnCount;
Adam Powell63b31692015-09-28 10:45:00 -07003661
arangelov706316d2020-05-06 14:25:06 +01003662 ItemGroupViewHolder(int cellCount, View itemView, int viewType) {
3663 super(itemView, viewType);
Matt Pietal5b648562019-03-12 07:40:26 -04003664 this.mCells = new View[cellCount];
3665 this.mItemIndices = new int[cellCount];
Matt Pietal5b648562019-03-12 07:40:26 -04003666 this.mColumnCount = cellCount;
3667 }
3668
3669 abstract ViewGroup addView(int index, View v);
3670
3671 abstract ViewGroup getViewGroup();
3672
Mike Digmanba232682019-03-27 14:55:26 -07003673 abstract ViewGroup getRowByIndex(int index);
3674
3675 abstract ViewGroup getRow(int rowNumber);
Matt Pietal5b648562019-03-12 07:40:26 -04003676
Matt Pietalfaedea82019-03-21 10:36:54 -04003677 abstract void setViewVisibility(int i, int visibility);
3678
Matt Pietal5b648562019-03-12 07:40:26 -04003679 public int getColumnCount() {
3680 return mColumnCount;
3681 }
3682
Adam Powell63b31692015-09-28 10:45:00 -07003683 public void measure() {
3684 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietal5b648562019-03-12 07:40:26 -04003685 getViewGroup().measure(spec, spec);
3686 mMeasuredRowHeight = getViewGroup().getMeasuredHeight();
3687 }
3688
3689 public int getMeasuredRowHeight() {
3690 return mMeasuredRowHeight;
3691 }
3692
Matt Pietal5b648562019-03-12 07:40:26 -04003693 public void setItemIndex(int itemIndex, int listIndex) {
3694 mItemIndices[itemIndex] = listIndex;
3695 }
3696
3697 public int getItemIndex(int itemIndex) {
3698 return mItemIndices[itemIndex];
3699 }
3700
3701 public View getView(int index) {
3702 return mCells[index];
3703 }
3704 }
3705
Zhen Zhangbde7b462019-11-11 11:49:33 -08003706 class SingleRowViewHolder extends ItemGroupViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04003707 private final ViewGroup mRow;
3708
arangelov706316d2020-05-06 14:25:06 +01003709 SingleRowViewHolder(ViewGroup row, int cellCount, int viewType) {
3710 super(cellCount, row, viewType);
Matt Pietal5b648562019-03-12 07:40:26 -04003711
3712 this.mRow = row;
3713 }
3714
3715 public ViewGroup getViewGroup() {
3716 return mRow;
3717 }
3718
Mike Digmanba232682019-03-27 14:55:26 -07003719 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04003720 return mRow;
3721 }
3722
Mike Digmanba232682019-03-27 14:55:26 -07003723 public ViewGroup getRow(int rowNumber) {
3724 if (rowNumber == 0) return mRow;
3725 return null;
3726 }
3727
Matt Pietal5b648562019-03-12 07:40:26 -04003728 public ViewGroup addView(int index, View v) {
3729 mRow.addView(v);
3730 mCells[index] = v;
3731
Matt Pietal5b648562019-03-12 07:40:26 -04003732 return mRow;
3733 }
Matt Pietalfaedea82019-03-21 10:36:54 -04003734
3735 public void setViewVisibility(int i, int visibility) {
3736 getView(i).setVisibility(visibility);
3737 }
Matt Pietal5b648562019-03-12 07:40:26 -04003738 }
3739
Zhen Zhangbde7b462019-11-11 11:49:33 -08003740 class DirectShareViewHolder extends ItemGroupViewHolder {
Matt Pietal5b648562019-03-12 07:40:26 -04003741 private final ViewGroup mParent;
3742 private final List<ViewGroup> mRows;
3743 private int mCellCountPerRow;
3744
3745 private boolean mHideDirectShareExpansion = false;
3746 private int mDirectShareMinHeight = 0;
3747 private int mDirectShareCurrHeight = 0;
3748 private int mDirectShareMaxHeight = 0;
3749
Matt Pietalfaedea82019-03-21 10:36:54 -04003750 private final boolean[] mCellVisibility;
3751
arangelov706316d2020-05-06 14:25:06 +01003752 DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow,
3753 int viewType) {
3754 super(rows.size() * cellCountPerRow, parent, viewType);
Matt Pietal5b648562019-03-12 07:40:26 -04003755
3756 this.mParent = parent;
3757 this.mRows = rows;
3758 this.mCellCountPerRow = cellCountPerRow;
Matt Pietalfaedea82019-03-21 10:36:54 -04003759 this.mCellVisibility = new boolean[rows.size() * cellCountPerRow];
Matt Pietal5b648562019-03-12 07:40:26 -04003760 }
3761
3762 public ViewGroup addView(int index, View v) {
Mike Digmanba232682019-03-27 14:55:26 -07003763 ViewGroup row = getRowByIndex(index);
Matt Pietal5b648562019-03-12 07:40:26 -04003764 row.addView(v);
3765 mCells[index] = v;
3766
Matt Pietal5b648562019-03-12 07:40:26 -04003767 return row;
3768 }
3769
3770 public ViewGroup getViewGroup() {
3771 return mParent;
3772 }
3773
Mike Digmanba232682019-03-27 14:55:26 -07003774 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04003775 return mRows.get(index / mCellCountPerRow);
3776 }
3777
Mike Digmanba232682019-03-27 14:55:26 -07003778 public ViewGroup getRow(int rowNumber) {
3779 return mRows.get(rowNumber);
3780 }
3781
Matt Pietal5b648562019-03-12 07:40:26 -04003782 public void measure() {
3783 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3784 getRow(0).measure(spec, spec);
3785 getRow(1).measure(spec, spec);
3786
Matt Pietal5b648562019-03-12 07:40:26 -04003787 mDirectShareMinHeight = getRow(0).getMeasuredHeight();
3788 mDirectShareCurrHeight = mDirectShareCurrHeight > 0
Zhen Zhangbde7b462019-11-11 11:49:33 -08003789 ? mDirectShareCurrHeight : mDirectShareMinHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04003790 mDirectShareMaxHeight = 2 * mDirectShareMinHeight;
3791 }
3792
3793 public int getMeasuredRowHeight() {
3794 return mDirectShareCurrHeight;
3795 }
3796
Matt Pietala9c8e502019-04-10 14:27:35 -04003797 public int getMinRowHeight() {
3798 return mDirectShareMinHeight;
3799 }
3800
Matt Pietalfaedea82019-03-21 10:36:54 -04003801 public void setViewVisibility(int i, int visibility) {
3802 final View v = getView(i);
3803 if (visibility == View.VISIBLE) {
3804 mCellVisibility[i] = true;
3805 v.setVisibility(visibility);
3806 v.setAlpha(1.0f);
3807 } else if (visibility == View.INVISIBLE && mCellVisibility[i]) {
3808 mCellVisibility[i] = false;
3809
3810 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
3811 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3812 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
3813 fadeAnim.addListener(new AnimatorListenerAdapter() {
3814 public void onAnimationEnd(Animator animation) {
3815 v.setVisibility(View.INVISIBLE);
3816 }
3817 });
3818 fadeAnim.start();
3819 }
3820 }
3821
Zhen Zhangbde7b462019-11-11 11:49:33 -08003822 public void handleScroll(RecyclerView view, int y, int oldy, int maxTargetsPerRow) {
Matt Pietala9c8e502019-04-10 14:27:35 -04003823 // only exit early if fully collapsed, otherwise onListRebuilt() with shifting
3824 // targets can lock us into an expanded mode
3825 boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight;
3826 if (notExpanded) {
3827 if (mHideDirectShareExpansion) {
3828 return;
3829 }
Matt Pietal5b648562019-03-12 07:40:26 -04003830
Matt Pietala9c8e502019-04-10 14:27:35 -04003831 // only expand if we have more than maxTargetsPerRow, and delay that decision
3832 // until they start to scroll
Song Hub548df92020-05-16 08:42:54 -07003833 ChooserListAdapter adapter =
3834 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
3835 int validTargets =
3836 mAppendDirectShareEnabled ? adapter.getNumServiceTargetsForExpand()
3837 : adapter.getSelectableServiceTargetCount();
3838 if (validTargets <= maxTargetsPerRow) {
Matt Pietala9c8e502019-04-10 14:27:35 -04003839 mHideDirectShareExpansion = true;
3840 return;
3841 }
Matt Pietal5b648562019-03-12 07:40:26 -04003842 }
3843
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003844 int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal5b648562019-03-12 07:40:26 -04003845
3846 int prevHeight = mDirectShareCurrHeight;
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003847 int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
3848 newHeight = Math.max(newHeight, mDirectShareMinHeight);
3849 yDiff = newHeight - prevHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04003850
arangelov2b15d712020-04-22 14:54:31 +01003851 updateDirectShareRowHeight(view, yDiff, newHeight);
3852 }
3853
3854 void expand(RecyclerView view) {
3855 updateDirectShareRowHeight(view, mDirectShareMaxHeight - mDirectShareCurrHeight,
3856 mDirectShareMaxHeight);
3857 }
3858
3859 void collapse(RecyclerView view) {
3860 updateDirectShareRowHeight(view, mDirectShareMinHeight - mDirectShareCurrHeight,
3861 mDirectShareMinHeight);
3862 }
3863
3864 private void updateDirectShareRowHeight(RecyclerView view, int yDiff, int newHeight) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003865 if (view == null || view.getChildCount() == 0 || yDiff == 0) {
Matt Pietal5b648562019-03-12 07:40:26 -04003866 return;
3867 }
3868
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003869 // locate the item to expand, and offset the rows below that one
3870 boolean foundExpansion = false;
3871 for (int i = 0; i < view.getChildCount(); i++) {
3872 View child = view.getChildAt(i);
Matt Pietal1ef88002019-03-13 10:43:18 -04003873
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003874 if (foundExpansion) {
3875 child.offsetTopAndBottom(yDiff);
3876 } else {
3877 if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
3878 int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
3879 MeasureSpec.EXACTLY);
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003880 int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003881 MeasureSpec.EXACTLY);
3882 child.measure(widthSpec, heightSpec);
3883 child.getLayoutParams().height = child.getMeasuredHeight();
3884 child.layout(child.getLeft(), child.getTop(), child.getRight(),
3885 child.getTop() + child.getMeasuredHeight());
Matt Pietal5b648562019-03-12 07:40:26 -04003886
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003887 foundExpansion = true;
3888 }
3889 }
Matt Pietal5b648562019-03-12 07:40:26 -04003890 }
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003891
3892 if (foundExpansion) {
3893 mDirectShareCurrHeight = newHeight;
3894 }
Adam Powell63b31692015-09-28 10:45:00 -07003895 }
3896 }
3897
Adam Powell9761ab22015-09-08 17:01:49 -07003898 static class ChooserTargetServiceConnection implements ServiceConnection {
Adam Powell52c39212016-04-07 15:14:18 -07003899 private DisplayResolveInfo mOriginalTarget;
Adam Powell9761ab22015-09-08 17:01:49 -07003900 private ComponentName mConnectedComponent;
3901 private ChooserActivity mChooserActivity;
arangelov5fc9e7d2020-01-07 17:59:14 +00003902 private final UserHandle mUserHandle;
Adam Powell9761ab22015-09-08 17:01:49 -07003903 private final Object mLock = new Object();
Adam Powell24428412015-04-01 17:19:56 -07003904
3905 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
3906 @Override
3907 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
Adam Powell9761ab22015-09-08 17:01:49 -07003908 synchronized (mLock) {
3909 if (mChooserActivity == null) {
3910 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
3911 + mConnectedComponent + "; ignoring...");
3912 return;
3913 }
arangelov5fc9e7d2020-01-07 17:59:14 +00003914 Context contextAsUser =
3915 mChooserActivity.createContextAsUser(mUserHandle, 0 /* flags */);
3916 mChooserActivity.filterServiceTargets(contextAsUser,
Adam Powell9761ab22015-09-08 17:01:49 -07003917 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
3918 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04003919 msg.what = ChooserHandler.CHOOSER_TARGET_SERVICE_RESULT;
Adam Powell9761ab22015-09-08 17:01:49 -07003920 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
arangelov5fc9e7d2020-01-07 17:59:14 +00003921 ChooserTargetServiceConnection.this, mUserHandle);
Adam Powell9761ab22015-09-08 17:01:49 -07003922 mChooserActivity.mChooserHandler.sendMessage(msg);
3923 }
Adam Powell24428412015-04-01 17:19:56 -07003924 }
3925 };
3926
Adam Powell9761ab22015-09-08 17:01:49 -07003927 public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
arangelov5fc9e7d2020-01-07 17:59:14 +00003928 DisplayResolveInfo dri, UserHandle userHandle) {
Adam Powell9761ab22015-09-08 17:01:49 -07003929 mChooserActivity = chooserActivity;
Adam Powell24428412015-04-01 17:19:56 -07003930 mOriginalTarget = dri;
arangelov5fc9e7d2020-01-07 17:59:14 +00003931 mUserHandle = userHandle;
Adam Powell24428412015-04-01 17:19:56 -07003932 }
3933
3934 @Override
3935 public void onServiceConnected(ComponentName name, IBinder service) {
3936 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003937 synchronized (mLock) {
3938 if (mChooserActivity == null) {
3939 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
3940 return;
3941 }
3942
3943 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
3944 try {
3945 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
3946 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
3947 } catch (RemoteException e) {
3948 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
3949 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003950 mChooserActivity.mServiceConnections.remove(this);
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003951 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003952 }
Adam Powell24428412015-04-01 17:19:56 -07003953 }
3954 }
3955
3956 @Override
3957 public void onServiceDisconnected(ComponentName name) {
3958 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003959 synchronized (mLock) {
3960 if (mChooserActivity == null) {
3961 Log.e(TAG,
3962 "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
3963 return;
3964 }
3965
3966 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003967 mChooserActivity.mServiceConnections.remove(this);
3968 if (mChooserActivity.mServiceConnections.isEmpty()) {
Adam Powell9761ab22015-09-08 17:01:49 -07003969 mChooserActivity.sendVoiceChoicesIfNeeded();
3970 }
3971 mConnectedComponent = null;
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003972 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003973 }
3974 }
3975
3976 public void destroy() {
3977 synchronized (mLock) {
3978 mChooserActivity = null;
Adam Powell52c39212016-04-07 15:14:18 -07003979 mOriginalTarget = null;
Adam Powell4c470d62015-06-19 17:46:17 -07003980 }
Adam Powell24428412015-04-01 17:19:56 -07003981 }
3982
3983 @Override
3984 public String toString() {
Adam Powell9761ab22015-09-08 17:01:49 -07003985 return "ChooserTargetServiceConnection{service="
3986 + mConnectedComponent + ", activity="
Adam Powell52c39212016-04-07 15:14:18 -07003987 + (mOriginalTarget != null
3988 ? mOriginalTarget.getResolveInfo().activityInfo.toString()
3989 : "<connection destroyed>") + "}";
Adam Powell24428412015-04-01 17:19:56 -07003990 }
Song Hue2deffd2020-03-09 15:22:29 -07003991
3992 public ComponentName getComponentName() {
3993 return mOriginalTarget.getResolveInfo().activityInfo.getComponentName();
3994 }
Adam Powell24428412015-04-01 17:19:56 -07003995 }
3996
3997 static class ServiceResultInfo {
3998 public final DisplayResolveInfo originalTarget;
3999 public final List<ChooserTarget> resultTargets;
4000 public final ChooserTargetServiceConnection connection;
arangelov5fc9e7d2020-01-07 17:59:14 +00004001 public final UserHandle userHandle;
Adam Powell24428412015-04-01 17:19:56 -07004002
4003 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
arangelov5fc9e7d2020-01-07 17:59:14 +00004004 ChooserTargetServiceConnection c, UserHandle userHandle) {
Adam Powell24428412015-04-01 17:19:56 -07004005 originalTarget = ot;
4006 resultTargets = rt;
4007 connection = c;
arangelov5fc9e7d2020-01-07 17:59:14 +00004008 this.userHandle = userHandle;
Adam Powell24428412015-04-01 17:19:56 -07004009 }
4010 }
Adam Powell2ed547e2015-04-29 18:45:04 -07004011
Song Hu5aab2d32020-04-30 10:07:15 -07004012 static class ChooserTargetRankingInfo {
4013 public final List<AppTarget> scores;
4014 public final UserHandle userHandle;
4015
4016 ChooserTargetRankingInfo(List<AppTarget> chooserTargetScores,
4017 UserHandle userHandle) {
4018 this.scores = chooserTargetScores;
4019 this.userHandle = userHandle;
4020 }
4021 }
4022
Adam Powell2ed547e2015-04-29 18:45:04 -07004023 static class RefinementResultReceiver extends ResultReceiver {
4024 private ChooserActivity mChooserActivity;
4025 private TargetInfo mSelectedTarget;
4026
4027 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
4028 Handler handler) {
4029 super(handler);
4030 mChooserActivity = host;
4031 mSelectedTarget = target;
4032 }
4033
4034 @Override
4035 protected void onReceiveResult(int resultCode, Bundle resultData) {
4036 if (mChooserActivity == null) {
4037 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
4038 return;
4039 }
4040 if (resultData == null) {
4041 Log.e(TAG, "RefinementResultReceiver received null resultData");
4042 return;
4043 }
4044
4045 switch (resultCode) {
4046 case RESULT_CANCELED:
4047 mChooserActivity.onRefinementCanceled();
4048 break;
4049 case RESULT_OK:
4050 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
4051 if (intentParcelable instanceof Intent) {
4052 mChooserActivity.onRefinementResult(mSelectedTarget,
4053 (Intent) intentParcelable);
4054 } else {
4055 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
4056 + " in resultData with key Intent.EXTRA_INTENT");
4057 }
4058 break;
4059 default:
4060 Log.w(TAG, "Unknown result code " + resultCode
4061 + " sent to RefinementResultReceiver");
4062 break;
4063 }
4064 }
4065
4066 public void destroy() {
4067 mChooserActivity = null;
4068 mSelectedTarget = null;
4069 }
4070 }
Adam Powell63b31692015-09-28 10:45:00 -07004071
Matt Pietal26038402019-01-08 07:29:34 -05004072 /**
4073 * Used internally to round image corners while obeying view padding.
4074 */
4075 public static class RoundedRectImageView extends ImageView {
4076 private int mRadius = 0;
4077 private Path mPath = new Path();
Matt Pietal0ea391b2019-01-30 10:44:15 -05004078 private Paint mOverlayPaint = new Paint(0);
Matt Pietal9aaf00c2019-04-09 10:09:12 -04004079 private Paint mRoundRectPaint = new Paint(0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05004080 private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
4081 private String mExtraImageCount = null;
Matt Pietal26038402019-01-08 07:29:34 -05004082
4083 public RoundedRectImageView(Context context) {
4084 super(context);
4085 }
4086
4087 public RoundedRectImageView(Context context, AttributeSet attrs) {
4088 this(context, attrs, 0);
4089 }
4090
4091 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
4092 this(context, attrs, defStyleAttr, 0);
4093 }
4094
4095 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
4096 int defStyleRes) {
4097 super(context, attrs, defStyleAttr, defStyleRes);
4098 mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
Matt Pietal0ea391b2019-01-30 10:44:15 -05004099
4100 mOverlayPaint.setColor(0x99000000);
4101 mOverlayPaint.setStyle(Paint.Style.FILL);
4102
Matt Pietal9aaf00c2019-04-09 10:09:12 -04004103 mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider));
4104 mRoundRectPaint.setStyle(Paint.Style.STROKE);
4105 mRoundRectPaint.setStrokeWidth(context.getResources()
4106 .getDimensionPixelSize(R.dimen.chooser_preview_image_border));
4107
Matt Pietal0ea391b2019-01-30 10:44:15 -05004108 mTextPaint.setColor(Color.WHITE);
4109 mTextPaint.setTextSize(context.getResources()
4110 .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
4111 mTextPaint.setTextAlign(Paint.Align.CENTER);
Matt Pietal26038402019-01-08 07:29:34 -05004112 }
4113
4114 private void updatePath(int width, int height) {
4115 mPath.reset();
4116
Matt Pietal9aaf00c2019-04-09 10:09:12 -04004117 int imageWidth = width - getPaddingRight() - getPaddingLeft();
4118 int imageHeight = height - getPaddingBottom() - getPaddingTop();
Matt Pietal26038402019-01-08 07:29:34 -05004119 mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
4120 mRadius, Path.Direction.CW);
4121 }
4122
4123 /**
4124 * Sets the corner radius on all corners
4125 *
4126 * param radius 0 for no radius, &gt; 0 for a visible corner radius
4127 */
4128 public void setRadius(int radius) {
4129 mRadius = radius;
4130 updatePath(getWidth(), getHeight());
4131 }
4132
Matt Pietal0ea391b2019-01-30 10:44:15 -05004133 /**
4134 * Display an overlay with extra image count on 3rd image
4135 */
4136 public void setExtraImageCount(int count) {
4137 if (count > 0) {
4138 this.mExtraImageCount = "+" + count;
4139 } else {
4140 this.mExtraImageCount = null;
4141 }
4142 }
4143
Matt Pietal26038402019-01-08 07:29:34 -05004144 @Override
4145 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
4146 super.onSizeChanged(width, height, oldWidth, oldHeight);
4147 updatePath(width, height);
4148 }
4149
4150 @Override
4151 protected void onDraw(Canvas canvas) {
4152 if (mRadius != 0) {
4153 canvas.clipPath(mPath);
4154 }
4155
4156 super.onDraw(canvas);
Matt Pietal0ea391b2019-01-30 10:44:15 -05004157
Matt Pietal9aaf00c2019-04-09 10:09:12 -04004158 int x = getPaddingLeft();
4159 int y = getPaddingRight();
4160 int width = getWidth() - getPaddingRight() - getPaddingLeft();
4161 int height = getHeight() - getPaddingBottom() - getPaddingTop();
Matt Pietal0ea391b2019-01-30 10:44:15 -05004162 if (mExtraImageCount != null) {
Matt Pietal9aaf00c2019-04-09 10:09:12 -04004163 canvas.drawRect(x, y, width, height, mOverlayPaint);
Matt Pietal0ea391b2019-01-30 10:44:15 -05004164
4165 int xPos = canvas.getWidth() / 2;
4166 int yPos = (int) ((canvas.getHeight() / 2.0f)
4167 - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
4168
4169 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
4170 }
Matt Pietal9aaf00c2019-04-09 10:09:12 -04004171
4172 canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
Matt Pietal26038402019-01-08 07:29:34 -05004173 }
4174 }
Susi Kharraz-Postdde9c3a2020-03-03 07:55:40 -05004175
4176 @Override
4177 protected void maybeLogProfileChange() {
4178 getChooserActivityLogger().logShareheetProfileChanged();
4179 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004180}