blob: 102ba5c2ec5e8a141d835386f645a352cc1dfe23 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Adam Powelle7c74cc2016-01-28 16:42:27 +000017package com.android.internal.app;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080018
Matt Pietal0ea391b2019-01-30 10:44:15 -050019import static java.lang.annotation.RetentionPolicy.SOURCE;
20
Matt Pietalcdfcd9a2019-03-05 08:31:47 -050021import android.animation.Animator;
22import android.animation.AnimatorListenerAdapter;
23import android.animation.AnimatorSet;
24import android.animation.ObjectAnimator;
25import android.animation.ValueAnimator;
Matt Pietal0ea391b2019-01-30 10:44:15 -050026import android.annotation.IntDef;
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -070027import android.annotation.NonNull;
George Hodulik145b3a52019-03-27 11:18:43 -070028import android.annotation.Nullable;
Artur Satayevfc46be72019-11-04 17:50:59 +000029import android.annotation.UnsupportedAppUsage;
Adam Powell0b3c1122014-10-09 12:50:14 -070030import android.app.Activity;
Ng Zhi And3ec5fc2018-05-10 09:13:00 -070031import android.app.ActivityManager;
George Hodulik69d4a082019-01-18 11:27:03 -080032import android.app.prediction.AppPredictionContext;
33import android.app.prediction.AppPredictionManager;
34import android.app.prediction.AppPredictor;
35import android.app.prediction.AppTarget;
George Hodulikf2b0d342019-01-25 12:43:54 -080036import android.app.prediction.AppTargetEvent;
Matt Pietal26038402019-01-08 07:29:34 -050037import android.content.ClipData;
Matt Pietal1fa7d802019-01-30 10:44:15 -050038import android.content.ClipboardManager;
Adam Powell0b3c1122014-10-09 12:50:14 -070039import android.content.ComponentName;
Matt Pietal0ea391b2019-01-30 10:44:15 -050040import android.content.ContentResolver;
Adam Powell24428412015-04-01 17:19:56 -070041import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.content.Intent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080043import android.content.IntentFilter;
Adam Powell0b3c1122014-10-09 12:50:14 -070044import android.content.IntentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -070045import android.content.IntentSender.SendIntentException;
Adam Powell24428412015-04-01 17:19:56 -070046import android.content.ServiceConnection;
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +000047import android.content.pm.ActivityInfo;
Matt Pietala4b30072019-04-04 13:44:36 -040048import android.content.pm.ApplicationInfo;
Adam Powell7d758002015-05-06 17:49:36 -070049import android.content.pm.LabeledIntent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080050import android.content.pm.LauncherApps;
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;
Matt Pietal46d828c2019-02-05 08:07:07 -050057import android.database.Cursor;
Adam Powell7d758002015-05-06 17:49:36 -070058import android.database.DataSetObserver;
Matt Pietal26038402019-01-08 07:29:34 -050059import android.graphics.Bitmap;
60import android.graphics.Canvas;
Adam Powell63b31692015-09-28 10:45:00 -070061import android.graphics.Color;
Matt Pietal0ea391b2019-01-30 10:44:15 -050062import android.graphics.Paint;
Matt Pietal26038402019-01-08 07:29:34 -050063import android.graphics.Path;
Mike Digmanac1d88c2019-04-18 15:15:55 -070064import android.graphics.drawable.AnimatedVectorDrawable;
Mike Digman9c4ae502019-03-19 17:02:25 -070065import android.graphics.drawable.BitmapDrawable;
Adam Powell24428412015-04-01 17:19:56 -070066import android.graphics.drawable.Drawable;
Adam Powell13036be2015-05-12 14:43:56 -070067import android.graphics.drawable.Icon;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -050068import android.metrics.LogMaker;
Matt Pietal26038402019-01-08 07:29:34 -050069import android.net.Uri;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080070import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071import android.os.Bundle;
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;
Adam Powell52c39212016-04-07 15:14:18 -070076import android.os.Process;
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;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -040081import android.provider.DeviceConfig;
Matt Pietal46d828c2019-02-05 08:07:07 -050082import android.provider.DocumentsContract;
Matt Pietalf38e9d22019-02-15 10:01:03 -050083import android.provider.Downloads;
Matt Pietal46d828c2019-02-05 08:07:07 -050084import android.provider.OpenableColumns;
Adam Powell24428412015-04-01 17:19:56 -070085import android.service.chooser.ChooserTarget;
86import android.service.chooser.ChooserTargetService;
87import android.service.chooser.IChooserTargetResult;
88import android.service.chooser.IChooserTargetService;
Matt Pietal9d501432019-04-12 10:05:29 -040089import android.text.SpannableStringBuilder;
Adam Powell24428412015-04-01 17:19:56 -070090import android.text.TextUtils;
Matt Pietal26038402019-01-08 07:29:34 -050091import android.util.AttributeSet;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -040092import android.util.HashedStringCache;
Dianne Hackborneb034652009-09-07 00:49:58 -070093import android.util.Log;
Matt Pietal26038402019-01-08 07:29:34 -050094import android.util.Size;
Adam Powell0b3c1122014-10-09 12:50:14 -070095import android.util.Slog;
Adam Powell7d758002015-05-06 17:49:36 -070096import android.view.LayoutInflater;
Adam Powell24428412015-04-01 17:19:56 -070097import android.view.View;
Adam Powell63b31692015-09-28 10:45:00 -070098import android.view.View.MeasureSpec;
Adam Powell7d758002015-05-06 17:49:36 -070099import android.view.View.OnClickListener;
Adam Powell98b7f892015-06-19 12:38:45 -0700100import android.view.View.OnLongClickListener;
Adam Powell24428412015-04-01 17:19:56 -0700101import android.view.ViewGroup;
Adam Powell63b31692015-09-28 10:45:00 -0700102import android.view.ViewGroup.LayoutParams;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500103import android.view.animation.AccelerateInterpolator;
104import android.view.animation.DecelerateInterpolator;
Adam Powell7d758002015-05-06 17:49:36 -0700105import android.widget.AbsListView;
106import android.widget.BaseAdapter;
Matt Pietal26038402019-01-08 07:29:34 -0500107import android.widget.ImageView;
Adam Powell7d758002015-05-06 17:49:36 -0700108import android.widget.ListView;
Matt Pietal26038402019-01-08 07:29:34 -0500109import android.widget.TextView;
Matt Pietal1fa7d802019-01-30 10:44:15 -0500110import android.widget.Toast;
Jason Monk027dcfa2017-06-27 18:37:35 -0400111
Adam Powell7d758002015-05-06 17:49:36 -0700112import com.android.internal.R;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800113import com.android.internal.annotations.VisibleForTesting;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400114import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
Matt Pietalab73a882019-06-05 07:04:55 -0400115import com.android.internal.content.PackageMonitor;
Adam Powell98b7f892015-06-19 12:38:45 -0700116import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +0100117import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500118import com.android.internal.util.ImageUtils;
Mike Digman849a9d12019-04-29 11:20:48 -0700119import com.android.internal.widget.ResolverDrawerLayout;
Alison Cichowlas3e340502018-08-07 17:15:01 -0400120
Adam Powell52c39212016-04-07 15:14:18 -0700121import com.google.android.collect.Lists;
Adam Powell24428412015-04-01 17:19:56 -0700122
Matt Pietal26038402019-01-08 07:29:34 -0500123import java.io.IOException;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500124import java.lang.annotation.Retention;
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700125import java.lang.annotation.RetentionPolicy;
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400126import java.text.Collator;
Adam Powell24428412015-04-01 17:19:56 -0700127import java.util.ArrayList;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800128import java.util.Arrays;
Adam Powella182e452015-07-06 16:57:56 -0700129import java.util.Collections;
130import java.util.Comparator;
George Hodulikaa5238c2019-04-18 14:17:51 -0700131import java.util.HashMap;
Matt Pietalab73a882019-06-05 07:04:55 -0400132import java.util.HashSet;
Adam Powell24428412015-04-01 17:19:56 -0700133import java.util.List;
George Hodulikaa5238c2019-04-18 14:17:51 -0700134import java.util.Map;
Matt Pietalab73a882019-06-05 07:04:55 -0400135import java.util.Set;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400137/**
138 * The Chooser Activity handles intent resolution specifically for sharing intents -
139 * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
140 *
141 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142public class ChooserActivity extends ResolverActivity {
Adam Powell0b3c1122014-10-09 12:50:14 -0700143 private static final String TAG = "ChooserActivity";
144
Artur Satayevfc46be72019-11-04 17:50:59 +0000145 @UnsupportedAppUsage
146 public ChooserActivity() {
147 }
George Hodulik145b3a52019-03-27 11:18:43 -0700148
Jorim Jaggif631ef72017-02-24 13:49:47 +0100149 /**
150 * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
151 * in onStop when launched in a new task. If this extra is set to true, we do not finish
152 * ourselves when onStop gets called.
153 */
154 public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
155 = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
156
Mike Digman849a9d12019-04-29 11:20:48 -0700157 private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
158
Mehdi Alizadeh3c335a22019-01-17 16:03:19 -0800159 private static final boolean DEBUG = false;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800160
George Hodulik69d4a082019-01-18 11:27:03 -0800161 /**
162 * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
163 * {@link AppPredictionManager} will be queried for direct share targets.
164 */
165 // TODO(b/123089490): Replace with system flag
George Hodulik1428beb2019-05-01 17:04:23 -0700166 private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = true;
167 private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
George Hodulik69d4a082019-01-18 11:27:03 -0800168 // TODO(b/123088566) Share these in a better way.
169 private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
George Hodulikf2b0d342019-01-25 12:43:54 -0800170 public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
George Hodulik69d4a082019-01-18 11:27:03 -0800171 private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
172 public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700173
174 private boolean mIsAppPredictorComponentAvailable;
George Hodulik69d4a082019-01-18 11:27:03 -0800175 private AppPredictor mAppPredictor;
176 private AppPredictor.Callback mAppPredictorCallback;
George Hodulikaa5238c2019-04-18 14:17:51 -0700177 private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
George Hodulik69d4a082019-01-18 11:27:03 -0800178
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800179 /**
180 * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
181 * binding to every ChooserTargetService implementation.
182 */
183 // TODO(b/121287573): Replace with a system flag (setprop?)
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800184 private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
185 private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
186
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700187 public static final int TARGET_TYPE_DEFAULT = 0;
188 public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
189 public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
190 public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
191
192 @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
193 TARGET_TYPE_DEFAULT,
194 TARGET_TYPE_CHOOSER_TARGET,
195 TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
196 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
197 })
198 @Retention(RetentionPolicy.SOURCE)
199 public @interface ShareTargetType {}
200
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500201 /**
202 * The transition time between placeholders for direct share to a message
203 * indicating that non are available.
204 */
205 private static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200;
206
Matt Pietal394ebd02019-05-03 07:36:21 -0400207 private static final float DIRECT_SHARE_EXPANSION_RATE = 0.78f;
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400208
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800209 // TODO(b/121287224): Re-evaluate this limit
210 private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
Adam Powell24428412015-04-01 17:19:56 -0700211
Adam Powell2ed547e2015-04-29 18:45:04 -0700212 private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
Adam Powell24428412015-04-01 17:19:56 -0700213
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400214 private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7;
215 private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
216 SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
217 DEFAULT_SALT_EXPIRATION_DAYS);
218
Adam Powelle49d9392014-07-17 18:45:19 -0700219 private Bundle mReplacementExtras;
Adam Powell0b3c1122014-10-09 12:50:14 -0700220 private IntentSender mChosenComponentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -0700221 private IntentSender mRefinementIntentSender;
222 private RefinementResultReceiver mRefinementResultReceiver;
Adam Powell52c39212016-04-07 15:14:18 -0700223 private ChooserTarget[] mCallerChooserTargets;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800224 private ComponentName[] mFilteredComponentNames;
Adam Powelle49d9392014-07-17 18:45:19 -0700225
Adam Powell13036be2015-05-12 14:43:56 -0700226 private Intent mReferrerFillInIntent;
227
Kang Li9082f5b2016-12-02 10:56:21 -0800228 private long mChooserShownTime;
Kang Li64b018e2017-01-05 17:30:06 -0800229 protected boolean mIsSuccessfullySelected;
Kang Li9082f5b2016-12-02 10:56:21 -0800230
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700231 private long mQueriedTargetServicesTimeMs;
232 private long mQueriedSharingShortcutsTimeMs;
233
Adam Powell7d758002015-05-06 17:49:36 -0700234 private ChooserListAdapter mChooserListAdapter;
Adam Powell63b31692015-09-28 10:45:00 -0700235 private ChooserRowAdapter mChooserRowAdapter;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500236 private int mChooserRowServiceSpacing;
Adam Powell24428412015-04-01 17:19:56 -0700237
Matt Pietalab73a882019-06-05 07:04:55 -0400238 private int mCurrAvailableWidth = 0;
239
Matt Pietalfbfa0492019-04-01 11:29:56 -0400240 /** {@link ChooserActivity#getBaseScore} */
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700241 public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
Matt Pietalfbfa0492019-04-01 11:29:56 -0400242 /** {@link ChooserActivity#getBaseScore} */
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700243 public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
Adam Powell23882512016-01-29 10:21:00 -0800244 private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400245 // TODO: Update to handle landscape instead of using static value
246 private static final int MAX_RANKED_TARGETS = 4;
Adam Powell23882512016-01-29 10:21:00 -0800247
Adam Powell24428412015-04-01 17:19:56 -0700248 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
Matt Pietalab73a882019-06-05 07:04:55 -0400249 private final Set<ComponentName> mServicesRequested = new HashSet<>();
Matt Pietalaf044ae2019-03-29 06:53:53 -0400250
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -0400251 private static final int MAX_LOG_RANK_POSITION = 12;
252
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -0400253 @VisibleForTesting
254 public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
Matt Pietalaf044ae2019-03-29 06:53:53 -0400255
Matt Pietal4e2e3632019-04-05 08:32:47 -0400256 private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400257 private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
Matt Pietal4e2e3632019-04-05 08:32:47 -0400258
Matt Pietalaf044ae2019-03-29 06:53:53 -0400259 private boolean mListViewDataChanged = false;
Adam Powell24428412015-04-01 17:19:56 -0700260
Matt Pietal0ea391b2019-01-30 10:44:15 -0500261 @Retention(SOURCE)
262 @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
263 private @interface ContentPreviewType {
264 }
265
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500266 // Starting at 1 since 0 is considered "undefined" for some of the database transformations
267 // of tron logs.
268 private static final int CONTENT_PREVIEW_IMAGE = 1;
269 private static final int CONTENT_PREVIEW_FILE = 2;
270 private static final int CONTENT_PREVIEW_TEXT = 3;
271 protected MetricsLogger mMetricsLogger;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500272
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400273 // Sorted list of DisplayResolveInfos for the alphabetical app section.
274 private List<ResolverActivity.DisplayResolveInfo> mSortedList = new ArrayList<>();
275
Matt Pietale7cacab2019-05-23 07:21:36 -0400276 private ContentPreviewCoordinator mPreviewCoord;
277
278 private class ContentPreviewCoordinator {
Matt Pietale7cacab2019-05-23 07:21:36 -0400279 private static final int IMAGE_FADE_IN_MILLIS = 150;
280 private static final int IMAGE_LOAD_TIMEOUT = 1;
281 private static final int IMAGE_LOAD_INTO_VIEW = 2;
282
Matt Pietalab73a882019-06-05 07:04:55 -0400283 private final int mImageLoadTimeoutMillis =
284 getResources().getInteger(R.integer.config_shortAnimTime);
285
Matt Pietale7cacab2019-05-23 07:21:36 -0400286 private final View mParentView;
287 private boolean mHideParentOnFail;
288 private boolean mAtLeastOneLoaded = false;
289
290 class LoadUriTask {
291 public final Uri mUri;
292 public final int mImageResourceId;
293 public final int mExtraCount;
294 public final Bitmap mBmp;
295
296 LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) {
297 this.mImageResourceId = imageResourceId;
298 this.mUri = uri;
299 this.mExtraCount = extraCount;
300 this.mBmp = bmp;
301 }
302 }
303
304 // If at least one image loads within the timeout period, allow other
305 // loads to continue. Otherwise terminate and optionally hide
306 // the parent area
307 private final Handler mHandler = new Handler() {
308 @Override
309 public void handleMessage(Message msg) {
310 switch (msg.what) {
311 case IMAGE_LOAD_TIMEOUT:
312 maybeHideContentPreview();
313 break;
314
315 case IMAGE_LOAD_INTO_VIEW:
316 if (isFinishing()) break;
317
318 LoadUriTask task = (LoadUriTask) msg.obj;
319 RoundedRectImageView imageView = mParentView.findViewById(
320 task.mImageResourceId);
321 if (task.mBmp == null) {
322 imageView.setVisibility(View.GONE);
323 maybeHideContentPreview();
324 return;
325 }
326
327 mAtLeastOneLoaded = true;
328 imageView.setVisibility(View.VISIBLE);
329 imageView.setAlpha(0.0f);
330 imageView.setImageBitmap(task.mBmp);
331
332 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f,
333 1.0f);
334 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
335 fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS);
336 fadeAnim.start();
337
338 if (task.mExtraCount > 0) {
339 imageView.setExtraImageCount(task.mExtraCount);
340 }
341 }
342 }
343 };
344
345 ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) {
346 super();
347
348 this.mParentView = parentView;
349 this.mHideParentOnFail = hideParentOnFail;
350 }
351
352 private void loadUriIntoView(final int imageResourceId, final Uri uri,
353 final int extraImages) {
Matt Pietalab73a882019-06-05 07:04:55 -0400354 mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, mImageLoadTimeoutMillis);
Matt Pietale7cacab2019-05-23 07:21:36 -0400355
356 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
357 final Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
358 final Message msg = Message.obtain();
359 msg.what = IMAGE_LOAD_INTO_VIEW;
360 msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
361 mHandler.sendMessage(msg);
362 });
363 }
364
365 private void cancelLoads() {
366 mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW);
367 mHandler.removeMessages(IMAGE_LOAD_TIMEOUT);
368 }
369
370 private void maybeHideContentPreview() {
371 if (!mAtLeastOneLoaded && mHideParentOnFail) {
372 Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
Matt Pietalab73a882019-06-05 07:04:55 -0400373 + " within " + mImageLoadTimeoutMillis + "ms.");
Matt Pietale7cacab2019-05-23 07:21:36 -0400374 collapseParentView();
375 if (mChooserRowAdapter != null) {
376 mChooserRowAdapter.hideContentPreview();
377 }
378 mHideParentOnFail = false;
379 }
380 }
381
382 private void collapseParentView() {
383 // This will effectively hide the content preview row by forcing the height
384 // to zero. It is faster than forcing a relayout of the listview
385 final View v = mParentView;
386 int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY);
387 int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
388 v.measure(widthSpec, heightSpec);
389 v.getLayoutParams().height = 0;
390 v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop());
391 v.invalidate();
392 }
393 }
394
Matt Pietalab73a882019-06-05 07:04:55 -0400395 private final ChooserHandler mChooserHandler = new ChooserHandler();
396
397 private class ChooserHandler extends Handler {
398 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
399 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT = 2;
400 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT = 3;
401 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
402 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
403 private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
404
405 private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
406 private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;
407
408 private boolean mMinTimeoutPassed = false;
409
410 private void removeAllMessages() {
411 removeMessages(LIST_VIEW_UPDATE_MESSAGE);
412 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
413 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
414 removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
415 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
416 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
417 }
418
419 private void restartServiceRequestTimer() {
420 mMinTimeoutPassed = false;
421 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
422 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
423
424 if (DEBUG) {
425 Log.d(TAG, "queryTargets setting watchdog timer for "
426 + WATCHDOG_TIMEOUT_MIN_MILLIS + "-"
427 + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
428 }
429
430 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
431 WATCHDOG_TIMEOUT_MIN_MILLIS);
432 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
433 WATCHDOG_TIMEOUT_MAX_MILLIS);
434 }
435
436 private void maybeStopServiceRequestTimer() {
437 // Set a minimum timeout threshold, to ensure both apis, sharing shortcuts
438 // and older-style direct share services, have had time to load, otherwise
439 // just checking mServiceConnections could force us to end prematurely
440 if (mMinTimeoutPassed && mServiceConnections.isEmpty()) {
441 logDirectShareTargetReceived(
442 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
443 sendVoiceChoicesIfNeeded();
444 mChooserListAdapter.completeServiceTargetLoading();
445 }
446 }
447
Adam Powell24428412015-04-01 17:19:56 -0700448 @Override
449 public void handleMessage(Message msg) {
Matt Pietalaf044ae2019-03-29 06:53:53 -0400450 if (mChooserListAdapter == null || isDestroyed()) {
451 return;
452 }
453
Adam Powell24428412015-04-01 17:19:56 -0700454 switch (msg.what) {
455 case CHOOSER_TARGET_SERVICE_RESULT:
456 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
Adam Powell24428412015-04-01 17:19:56 -0700457 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
458 if (!mServiceConnections.contains(sri.connection)) {
459 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
460 + " returned after being removed from active connections."
461 + " Have you considered returning results faster?");
462 break;
463 }
Adam Powella182e452015-07-06 16:57:56 -0700464 if (sri.resultTargets != null) {
465 mChooserListAdapter.addServiceResults(sri.originalTarget,
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700466 sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET);
Adam Powella182e452015-07-06 16:57:56 -0700467 }
Adam Powell24428412015-04-01 17:19:56 -0700468 unbindService(sri.connection);
Adam Powell9761ab22015-09-08 17:01:49 -0700469 sri.connection.destroy();
Adam Powell24428412015-04-01 17:19:56 -0700470 mServiceConnections.remove(sri.connection);
Matt Pietalab73a882019-06-05 07:04:55 -0400471 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700472 break;
473
Matt Pietalab73a882019-06-05 07:04:55 -0400474 case CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT:
475 mMinTimeoutPassed = true;
476 maybeStopServiceRequestTimer();
477 break;
Matt Pietalaf044ae2019-03-29 06:53:53 -0400478
Matt Pietalab73a882019-06-05 07:04:55 -0400479 case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
Adam Powell24428412015-04-01 17:19:56 -0700480 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -0400481 maybeStopServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -0700482 break;
483
Matt Pietalaf044ae2019-03-29 06:53:53 -0400484 case LIST_VIEW_UPDATE_MESSAGE:
485 if (DEBUG) {
486 Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
487 }
488
489 mChooserListAdapter.refreshListView();
490 break;
491
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800492 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
493 if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800494 final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
495 if (resultInfo.resultTargets != null) {
496 mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
Mehdi Alizadeh06955f62019-09-11 17:23:10 -0700497 resultInfo.resultTargets, msg.arg1);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800498 }
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800499 break;
500
501 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -0700502 logDirectShareTargetReceived(
503 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800504 sendVoiceChoicesIfNeeded();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800505 break;
506
Adam Powell24428412015-04-01 17:19:56 -0700507 default:
508 super.handleMessage(msg);
509 }
510 }
511 };
512
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513 @Override
514 protected void onCreate(Bundle savedInstanceState) {
Kang Li9082f5b2016-12-02 10:56:21 -0800515 final long intentReceivedTime = System.currentTimeMillis();
Mehdi Alizadeh5cc5f712019-09-06 12:17:52 -0700516 // This is the only place this value is being set. Effectively final.
517 mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
518
Kang Li9082f5b2016-12-02 10:56:21 -0800519 mIsSuccessfullySelected = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 Intent intent = getIntent();
Dianne Hackborneb034652009-09-07 00:49:58 -0700521 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
522 if (!(targetParcelable instanceof Intent)) {
Christopher Tate9d6376a2014-02-12 13:14:10 -0800523 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
Dianne Hackborneb034652009-09-07 00:49:58 -0700524 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800525 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700526 return;
527 }
Adam Powell24428412015-04-01 17:19:56 -0700528 Intent target = (Intent) targetParcelable;
Craig Mautner411d2aed2014-05-08 09:07:43 -0700529 if (target != null) {
Adam Powelle49d9392014-07-17 18:45:19 -0700530 modifyTargetIntent(target);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700531 }
Adam Powell2ed547e2015-04-29 18:45:04 -0700532 Parcelable[] targetsParcelable
533 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
534 if (targetsParcelable != null) {
535 final boolean offset = target == null;
536 Intent[] additionalTargets =
537 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
538 for (int i = 0; i < targetsParcelable.length; i++) {
539 if (!(targetsParcelable[i] instanceof Intent)) {
540 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
541 + targetsParcelable[i]);
542 finish();
543 super.onCreate(null);
544 return;
545 }
546 final Intent additionalTarget = (Intent) targetsParcelable[i];
547 if (i == 0 && target == null) {
548 target = additionalTarget;
549 modifyTargetIntent(target);
550 } else {
551 additionalTargets[offset ? i - 1 : i] = additionalTarget;
552 modifyTargetIntent(additionalTarget);
553 }
554 }
555 setAdditionalTargets(additionalTargets);
556 }
557
Adam Powelle49d9392014-07-17 18:45:19 -0700558 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
Matt Pietal26038402019-01-08 07:29:34 -0500559
560 // Do not allow the title to be changed when sharing content
561 CharSequence title = null;
562 if (target != null) {
Matt Pietal95574b02019-03-13 08:12:25 -0400563 if (!isSendAction(target)) {
Matt Pietal26038402019-01-08 07:29:34 -0500564 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
565 } else {
566 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
567 + " preview title by using EXTRA_TITLE property of the wrapped"
568 + " EXTRA_INTENT.");
569 }
570 }
571
Adam Powell278902c2014-07-12 18:33:22 -0700572 int defaultTitleRes = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800573 if (title == null) {
Adam Powell278902c2014-07-12 18:33:22 -0700574 defaultTitleRes = com.android.internal.R.string.chooseActivity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 }
Matt Pietal26038402019-01-08 07:29:34 -0500576
Dianne Hackborneb034652009-09-07 00:49:58 -0700577 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
578 Intent[] initialIntents = null;
579 if (pa != null) {
Matt Pietal4e2e3632019-04-05 08:32:47 -0400580 int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS);
581 initialIntents = new Intent[count];
582 for (int i = 0; i < count; i++) {
Dianne Hackborneb034652009-09-07 00:49:58 -0700583 if (!(pa[i] instanceof Intent)) {
Adam Powell2ed547e2015-04-29 18:45:04 -0700584 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
Dianne Hackborneb034652009-09-07 00:49:58 -0700585 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800586 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700587 return;
588 }
Craig Mautner411d2aed2014-05-08 09:07:43 -0700589 final Intent in = (Intent) pa[i];
Adam Powelle49d9392014-07-17 18:45:19 -0700590 modifyTargetIntent(in);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700591 initialIntents[i] = in;
Dianne Hackborneb034652009-09-07 00:49:58 -0700592 }
593 }
Adam Powell24428412015-04-01 17:19:56 -0700594
Adam Powell13036be2015-05-12 14:43:56 -0700595 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
596
Adam Powell0b3c1122014-10-09 12:50:14 -0700597 mChosenComponentSender = intent.getParcelableExtra(
598 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
Adam Powell2ed547e2015-04-29 18:45:04 -0700599 mRefinementIntentSender = intent.getParcelableExtra(
600 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
Dianne Hackborn028ceeb2014-08-17 17:45:48 -0700601 setSafeForwardingMode(true);
Adam Powell23882512016-01-29 10:21:00 -0800602
Adam Powell52c39212016-04-07 15:14:18 -0700603 pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
604 if (pa != null) {
605 ComponentName[] names = new ComponentName[pa.length];
606 for (int i = 0; i < pa.length; i++) {
607 if (!(pa[i] instanceof ComponentName)) {
608 Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
609 names = null;
610 break;
611 }
612 names[i] = (ComponentName) pa[i];
613 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800614 mFilteredComponentNames = names;
Adam Powell52c39212016-04-07 15:14:18 -0700615 }
616
617 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
618 if (pa != null) {
Matt Pietal9a6b23d2019-04-19 14:47:14 -0400619 int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS);
620 ChooserTarget[] targets = new ChooserTarget[count];
621 for (int i = 0; i < count; i++) {
Adam Powell52c39212016-04-07 15:14:18 -0700622 if (!(pa[i] instanceof ChooserTarget)) {
623 Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
624 targets = null;
625 break;
626 }
627 targets[i] = (ChooserTarget) pa[i];
628 }
629 mCallerChooserTargets = targets;
630 }
631
Jorim Jaggif631ef72017-02-24 13:49:47 +0100632 setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
Adam Powell278902c2014-07-12 18:33:22 -0700633 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
634 null, false);
Adam Powell98b7f892015-06-19 12:38:45 -0700635
Kang Li9082f5b2016-12-02 10:56:21 -0800636 mChooserShownTime = System.currentTimeMillis();
637 final long systemCost = mChooserShownTime - intentReceivedTime;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500638
639 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500640 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
641 MetricsEvent.PARENT_PROFILE)
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500642 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
643 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
George Hodulik69d4a082019-01-18 11:27:03 -0800644
George Hodulik145b3a52019-03-27 11:18:43 -0700645 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
646 if (appPredictor != null) {
George Hodulikaa5238c2019-04-18 14:17:51 -0700647 mDirectShareAppTargetCache = new HashMap<>();
George Hodulik69d4a082019-01-18 11:27:03 -0800648 mAppPredictorCallback = resultList -> {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500649 if (isFinishing() || isDestroyed()) {
650 return;
651 }
George Hodulik0dd5fbe2019-03-06 12:00:26 -0800652 // May be null if there are no apps to perform share/open action.
653 if (mChooserListAdapter == null) {
654 return;
655 }
George Hodulik3f399f22019-04-26 16:17:54 -0700656 if (resultList.isEmpty()) {
657 // APS may be disabled, so try querying targets ourselves.
658 queryDirectShareTargets(mChooserListAdapter, true);
659 return;
660 }
George Hodulik69d4a082019-01-18 11:27:03 -0800661 final List<DisplayResolveInfo> driList =
662 getDisplayResolveInfos(mChooserListAdapter);
663 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
664 new ArrayList<>();
665 for (AppTarget appTarget : resultList) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500666 if (appTarget.getShortcutInfo() == null) {
667 continue;
668 }
George Hodulik69d4a082019-01-18 11:27:03 -0800669 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
670 appTarget.getShortcutInfo(),
671 new ComponentName(
672 appTarget.getPackageName(), appTarget.getClassName())));
673 }
George Hodulikaa5238c2019-04-18 14:17:51 -0700674 sendShareShortcutInfoList(shareShortcutInfos, driList, resultList);
George Hodulik69d4a082019-01-18 11:27:03 -0800675 };
George Hodulik145b3a52019-03-27 11:18:43 -0700676 appPredictor
677 .registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
George Hodulik69d4a082019-01-18 11:27:03 -0800678 }
679
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500680 mChooserRowServiceSpacing = getResources()
681 .getDimensionPixelSize(R.dimen.chooser_service_spacing);
682
Matt Pietalfe28f9a2019-03-22 07:59:58 -0400683 if (mResolverDrawerLayout != null) {
684 mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
685
686 // expand/shrink direct share 4 -> 8 viewgroup
687 if (isSendAction(target)) {
688 mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
689 }
Matt Pietalb1d629d2019-04-23 11:35:53 -0400690
691 final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
692 final float defaultElevation = chooserHeader.getElevation();
693 final float chooserHeaderScrollElevation =
694 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
695
696 mAdapterView.setOnScrollListener(new AbsListView.OnScrollListener() {
697 public void onScrollStateChanged(AbsListView view, int scrollState) {
698 }
699
700 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
701 int totalItemCount) {
702 if (view.getChildCount() > 0) {
703 if (firstVisibleItem > 0 || view.getChildAt(0).getTop() < 0) {
704 chooserHeader.setElevation(chooserHeaderScrollElevation);
705 return;
706 }
707 }
708
709 chooserHeader.setElevation(defaultElevation);
710 }
711 });
Mike Digman849a9d12019-04-29 11:20:48 -0700712
713 mResolverDrawerLayout.setOnCollapsedChangedListener(
714 new ResolverDrawerLayout.OnCollapsedChangedListener() {
715
716 // Only consider one expansion per activity creation
717 private boolean mWrittenOnce = false;
718
719 @Override
720 public void onCollapsedChanged(boolean isCollapsed) {
721 if (!isCollapsed && !mWrittenOnce) {
722 incrementNumSheetExpansions();
723 mWrittenOnce = true;
724 }
725 }
726 });
Matt Pietal5b648562019-03-12 07:40:26 -0400727 }
728
Kang Li9082f5b2016-12-02 10:56:21 -0800729 if (DEBUG) {
730 Log.d(TAG, "System Time Cost is " + systemCost);
731 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800732 }
Adam Powelle49d9392014-07-17 18:45:19 -0700733
Matt Pietal26038402019-01-08 07:29:34 -0500734 /**
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700735 * Returns true if app prediction service is defined and the component exists on device.
736 */
Mehdi Alizadehe870e972019-09-11 17:54:15 -0700737 @VisibleForTesting
738 public boolean isAppPredictionServiceAvailable() {
739 if (getPackageManager().getAppPredictionServicePackageName() == null) {
740 // Default AppPredictionService is not defined.
741 return false;
742 }
743
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -0700744 final String appPredictionServiceName =
745 getString(R.string.config_defaultAppPredictionService);
746 if (appPredictionServiceName == null) {
747 return false;
748 }
749 final ComponentName appPredictionComponentName =
750 ComponentName.unflattenFromString(appPredictionServiceName);
751 if (appPredictionComponentName == null) {
752 return false;
753 }
754
755 // Check if the app prediction component actually exists on the device.
756 Intent intent = new Intent();
757 intent.setComponent(appPredictionComponentName);
758 if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) {
759 Log.e(TAG, "App prediction service is defined, but does not exist: "
760 + appPredictionServiceName);
761 return false;
762 }
763 return true;
764 }
765
766 /**
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500767 * Check if the profile currently used is a work profile.
768 * @return true if it is work profile, false if it is parent profile (or no work profile is
769 * set up)
770 */
771 protected boolean isWorkProfile() {
772 return ((UserManager) getSystemService(Context.USER_SERVICE))
773 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
774 }
775
Matt Pietalab73a882019-06-05 07:04:55 -0400776 @Override
777 protected PackageMonitor createPackageMonitor() {
778 return new PackageMonitor() {
779 @Override
780 public void onSomePackagesChanged() {
781 mAdapter.handlePackagesChanged();
782 bindProfileView();
783 }
784 };
785 }
786
Matt Pietal46d828c2019-02-05 08:07:07 -0500787 private void onCopyButtonClicked(View v) {
788 Intent targetIntent = getTargetIntent();
789 if (targetIntent == null) {
790 finish();
791 } else {
792 final String action = targetIntent.getAction();
793
794 ClipData clipData = null;
795 if (Intent.ACTION_SEND.equals(action)) {
796 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
797 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
798
799 if (extraText != null) {
800 clipData = ClipData.newPlainText(null, extraText);
801 } else if (extraStream != null) {
802 clipData = ClipData.newUri(getContentResolver(), null, extraStream);
803 } else {
804 Log.w(TAG, "No data available to copy to clipboard");
805 return;
806 }
807 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
808 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
809 Intent.EXTRA_STREAM);
810 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
811 for (int i = 1; i < streams.size(); i++) {
812 clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
813 }
814 } else {
815 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
816 // so warn about unexpected action
817 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
818 return;
819 }
820
821 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
822 Context.CLIPBOARD_SERVICE);
823 clipboardManager.setPrimaryClip(clipData);
824 Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
825
Alison Cichowlasaa7f79f2019-09-12 15:57:26 -0400826 // Log share completion via copy
827 LogMaker targetLogMaker = new LogMaker(
828 MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
829 getMetricsLogger().write(targetLogMaker);
830
Matt Pietal46d828c2019-02-05 08:07:07 -0500831 finish();
832 }
833 }
834
Matt Pietal18bbd822019-02-12 15:21:36 -0500835 @Override
836 public void onConfigurationChanged(Configuration newConfig) {
837 super.onConfigurationChanged(newConfig);
838
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400839 adjustPreviewWidth(newConfig.orientation, null);
840 }
841
842 private boolean shouldDisplayLandscape(int orientation) {
843 // Sharesheet fixes the # of items per row and therefore can not correctly lay out
844 // when in the restricted size of multi-window mode. In the future, would be nice
845 // to use minimum dp size requirements instead
846 return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
847 }
848
849 private void adjustPreviewWidth(int orientation, View parent) {
Matt Pietal18bbd822019-02-12 15:21:36 -0500850 int width = -1;
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400851 if (shouldDisplayLandscape(orientation)) {
Matt Pietal18bbd822019-02-12 15:21:36 -0500852 width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
853 }
854
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400855 parent = parent == null ? getWindow().getDecorView() : parent;
856
857 updateLayoutWidth(R.id.content_preview_text_layout, width, parent);
858 updateLayoutWidth(R.id.content_preview_title_layout, width, parent);
859 updateLayoutWidth(R.id.content_preview_file_layout, width, parent);
Matt Pietal18bbd822019-02-12 15:21:36 -0500860 }
861
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400862 private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
863 View view = parent.findViewById(layoutResourceId);
Matt Pietal1ef88002019-03-13 10:43:18 -0400864 if (view != null && view.getLayoutParams() != null) {
865 LayoutParams params = view.getLayoutParams();
866 params.width = width;
867 view.setLayoutParams(params);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500868 }
869 }
870
Matt Pietal1ef88002019-03-13 10:43:18 -0400871 private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
872 Intent targetIntent, LayoutInflater layoutInflater, ViewGroup convertView,
873 ViewGroup parent) {
Matt Pietale7cacab2019-05-23 07:21:36 -0400874 if (convertView != null) return convertView;
875
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400876 ViewGroup layout = null;
877
Matt Pietal1ef88002019-03-13 10:43:18 -0400878 switch (previewType) {
879 case CONTENT_PREVIEW_TEXT:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400880 layout = displayTextContentPreview(targetIntent, layoutInflater, parent);
881 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400882 case CONTENT_PREVIEW_IMAGE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400883 layout = displayImageContentPreview(targetIntent, layoutInflater, parent);
884 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400885 case CONTENT_PREVIEW_FILE:
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400886 layout = displayFileContentPreview(targetIntent, layoutInflater, parent);
887 break;
Matt Pietal1ef88002019-03-13 10:43:18 -0400888 default:
889 Log.e(TAG, "Unexpected content preview type: " + previewType);
890 }
Matt Pietal0ea391b2019-01-30 10:44:15 -0500891
Matt Pietal3e4b56f2019-05-31 12:06:17 -0400892 if (layout != null) {
893 adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
894 }
895
896 return layout;
Matt Pietal1ef88002019-03-13 10:43:18 -0400897 }
898
899 private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -0400900 ViewGroup parent) {
901 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
902 R.layout.chooser_grid_preview_text, parent, false);
Matt Pietal1ef88002019-03-13 10:43:18 -0400903
904 contentPreviewLayout.findViewById(R.id.copy_button).setOnClickListener(
905 this::onCopyButtonClicked);
Matt Pietal46d828c2019-02-05 08:07:07 -0500906
Matt Pietal26038402019-01-08 07:29:34 -0500907 CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
Matt Pietal26038402019-01-08 07:29:34 -0500908 if (sharingText == null) {
Matt Pietal1ef88002019-03-13 10:43:18 -0400909 contentPreviewLayout.findViewById(R.id.content_preview_text_layout).setVisibility(
910 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500911 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -0400912 TextView textView = contentPreviewLayout.findViewById(R.id.content_preview_text);
Matt Pietal1fa7d802019-01-30 10:44:15 -0500913 textView.setText(sharingText);
Matt Pietal26038402019-01-08 07:29:34 -0500914 }
915
916 String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
Matt Pietal46d828c2019-02-05 08:07:07 -0500917 if (TextUtils.isEmpty(previewTitle)) {
Matt Pietal1ef88002019-03-13 10:43:18 -0400918 contentPreviewLayout.findViewById(R.id.content_preview_title_layout).setVisibility(
919 View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500920 } else {
Matt Pietal1ef88002019-03-13 10:43:18 -0400921 TextView previewTitleView = contentPreviewLayout.findViewById(
922 R.id.content_preview_title);
Matt Pietal26038402019-01-08 07:29:34 -0500923 previewTitleView.setText(previewTitle);
Matt Pietal26038402019-01-08 07:29:34 -0500924
Matt Pietal1fa7d802019-01-30 10:44:15 -0500925 ClipData previewData = targetIntent.getClipData();
926 Uri previewThumbnail = null;
927 if (previewData != null) {
928 if (previewData.getItemCount() > 0) {
929 ClipData.Item previewDataItem = previewData.getItemAt(0);
930 previewThumbnail = previewDataItem.getUri();
931 }
Matt Pietal26038402019-01-08 07:29:34 -0500932 }
Matt Pietal26038402019-01-08 07:29:34 -0500933
Matt Pietal1ef88002019-03-13 10:43:18 -0400934 ImageView previewThumbnailView = contentPreviewLayout.findViewById(
935 R.id.content_preview_thumbnail);
Matt Pietal1fa7d802019-01-30 10:44:15 -0500936 if (previewThumbnail == null) {
Matt Pietal26038402019-01-08 07:29:34 -0500937 previewThumbnailView.setVisibility(View.GONE);
938 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -0400939 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
940 mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
Matt Pietal26038402019-01-08 07:29:34 -0500941 }
942 }
Matt Pietal1ef88002019-03-13 10:43:18 -0400943
944 return contentPreviewLayout;
Matt Pietal26038402019-01-08 07:29:34 -0500945 }
946
Matt Pietal1ef88002019-03-13 10:43:18 -0400947 private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -0400948 ViewGroup parent) {
949 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
950 R.layout.chooser_grid_preview_image, parent, false);
951 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500952
953 String action = targetIntent.getAction();
954 if (Intent.ACTION_SEND.equals(action)) {
955 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietale7cacab2019-05-23 07:21:36 -0400956 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500957 } else {
958 ContentResolver resolver = getContentResolver();
959
960 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
961 List<Uri> imageUris = new ArrayList<>();
962 for (Uri uri : uris) {
963 if (isImageType(resolver.getType(uri))) {
964 imageUris.add(uri);
965 }
966 }
967
968 if (imageUris.size() == 0) {
969 Log.i(TAG, "Attempted to display image preview area with zero"
970 + " available images detected in EXTRA_STREAM list");
Matt Pietal46d828c2019-02-05 08:07:07 -0500971 contentPreviewLayout.setVisibility(View.GONE);
Matt Pietal1ef88002019-03-13 10:43:18 -0400972 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500973 }
974
Matt Pietale7cacab2019-05-23 07:21:36 -0400975 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500976
977 if (imageUris.size() == 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -0400978 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
979 imageUris.get(1), 0);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500980 } else if (imageUris.size() > 2) {
Matt Pietale7cacab2019-05-23 07:21:36 -0400981 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
982 imageUris.get(1), 0);
983 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
984 imageUris.get(2), imageUris.size() - 3);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500985 }
986 }
Matt Pietal1ef88002019-03-13 10:43:18 -0400987
988 return contentPreviewLayout;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500989 }
990
Matt Pietal46d828c2019-02-05 08:07:07 -0500991 private static class FileInfo {
992 public final String name;
993 public final boolean hasThumbnail;
994
995 FileInfo(String name, boolean hasThumbnail) {
996 this.name = name;
997 this.hasThumbnail = hasThumbnail;
998 }
999 }
1000
Matt Pietalf38e9d22019-02-15 10:01:03 -05001001 /**
1002 * Wrapping the ContentResolver call to expose for easier mocking,
1003 * and to avoid mocking Android core classes.
1004 */
1005 @VisibleForTesting
1006 public Cursor queryResolver(ContentResolver resolver, Uri uri) {
1007 return resolver.query(uri, null, null, null, null);
1008 }
1009
Matt Pietal46d828c2019-02-05 08:07:07 -05001010 private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
1011 String fileName = null;
1012 boolean hasThumbnail = false;
Matt Pietal3087bca2019-02-14 12:19:16 -05001013
Matt Pietalf38e9d22019-02-15 10:01:03 -05001014 try (Cursor cursor = queryResolver(resolver, uri)) {
1015 if (cursor != null && cursor.getCount() > 0) {
1016 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
1017 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
1018 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
1019
1020 cursor.moveToFirst();
1021 if (nameIndex != -1) {
1022 fileName = cursor.getString(nameIndex);
1023 } else if (titleIndex != -1) {
1024 fileName = cursor.getString(titleIndex);
1025 }
1026
1027 if (flagsIndex != -1) {
1028 hasThumbnail = (cursor.getInt(flagsIndex)
1029 & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
1030 }
1031 }
Matt Pietal73a873f2019-03-15 08:46:20 -04001032 } catch (SecurityException | NullPointerException e) {
Matt Pietal62532e52019-05-07 09:51:37 -04001033 logContentPreviewWarning(uri);
Matt Pietal3087bca2019-02-14 12:19:16 -05001034 }
1035
Matt Pietal46d828c2019-02-05 08:07:07 -05001036 if (TextUtils.isEmpty(fileName)) {
1037 fileName = uri.getPath();
1038 int index = fileName.lastIndexOf('/');
1039 if (index != -1) {
1040 fileName = fileName.substring(index + 1);
1041 }
1042 }
1043
1044 return new FileInfo(fileName, hasThumbnail);
1045 }
1046
Matt Pietal62532e52019-05-07 09:51:37 -04001047 private void logContentPreviewWarning(Uri uri) {
1048 // The ContentResolver already logs the exception. Log something more informative.
1049 Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
1050 + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
1051 + "and set your Intent's clipData and flags in accordance with that method's "
1052 + "documentation");
1053 }
1054
Matt Pietal1ef88002019-03-13 10:43:18 -04001055 private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
Matt Pietale7cacab2019-05-23 07:21:36 -04001056 ViewGroup parent) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001057
Matt Pietale7cacab2019-05-23 07:21:36 -04001058 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1059 R.layout.chooser_grid_preview_file, parent, false);
Matt Pietal46d828c2019-02-05 08:07:07 -05001060
1061 // TODO(b/120417119): Disable file copy until after moving to sysui,
1062 // due to permissions issues
Matt Pietal1ef88002019-03-13 10:43:18 -04001063 contentPreviewLayout.findViewById(R.id.file_copy_button).setVisibility(View.GONE);
Matt Pietal46d828c2019-02-05 08:07:07 -05001064
Matt Pietal3087bca2019-02-14 12:19:16 -05001065 String action = targetIntent.getAction();
1066 if (Intent.ACTION_SEND.equals(action)) {
1067 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
Matt Pietal1ef88002019-03-13 10:43:18 -04001068 loadFileUriIntoView(uri, contentPreviewLayout);
Matt Pietal3087bca2019-02-14 12:19:16 -05001069 } else {
1070 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1071 int uriCount = uris.size();
Matt Pietal46d828c2019-02-05 08:07:07 -05001072
Matt Pietal3087bca2019-02-14 12:19:16 -05001073 if (uriCount == 0) {
1074 contentPreviewLayout.setVisibility(View.GONE);
1075 Log.i(TAG,
1076 "Appears to be no uris available in EXTRA_STREAM, removing "
1077 + "preview area");
Matt Pietal1ef88002019-03-13 10:43:18 -04001078 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001079 } else if (uriCount == 1) {
Matt Pietal1ef88002019-03-13 10:43:18 -04001080 loadFileUriIntoView(uris.get(0), contentPreviewLayout);
Matt Pietal46d828c2019-02-05 08:07:07 -05001081 } else {
Matt Pietal3087bca2019-02-14 12:19:16 -05001082 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
1083 int remUriCount = uriCount - 1;
Matt Pietalacabc572019-02-14 11:02:05 -05001084 String fileName = getResources().getQuantityString(R.plurals.file_count,
Matt Pietal3087bca2019-02-14 12:19:16 -05001085 remUriCount, fileInfo.name, remUriCount);
Matt Pietalacabc572019-02-14 11:02:05 -05001086
Matt Pietal1ef88002019-03-13 10:43:18 -04001087 TextView fileNameView = contentPreviewLayout.findViewById(
1088 R.id.content_preview_filename);
Matt Pietalacabc572019-02-14 11:02:05 -05001089 fileNameView.setText(fileName);
Matt Pietal3087bca2019-02-14 12:19:16 -05001090
Matt Pietale7cacab2019-05-23 07:21:36 -04001091 View thumbnailView = contentPreviewLayout.findViewById(
1092 R.id.content_preview_file_thumbnail);
1093 thumbnailView.setVisibility(View.GONE);
1094
Matt Pietal1ef88002019-03-13 10:43:18 -04001095 ImageView fileIconView = contentPreviewLayout.findViewById(
1096 R.id.content_preview_file_icon);
Matt Pietal46d828c2019-02-05 08:07:07 -05001097 fileIconView.setVisibility(View.VISIBLE);
Matt Pietalacabc572019-02-14 11:02:05 -05001098 fileIconView.setImageResource(R.drawable.ic_file_copy);
Matt Pietal46d828c2019-02-05 08:07:07 -05001099 }
Matt Pietal3087bca2019-02-14 12:19:16 -05001100 }
Matt Pietal1ef88002019-03-13 10:43:18 -04001101
1102 return contentPreviewLayout;
Matt Pietal3087bca2019-02-14 12:19:16 -05001103 }
1104
Matt Pietale7cacab2019-05-23 07:21:36 -04001105 private void loadFileUriIntoView(final Uri uri, final View parent) {
Matt Pietal3087bca2019-02-14 12:19:16 -05001106 FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
1107
Matt Pietal1ef88002019-03-13 10:43:18 -04001108 TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
Matt Pietal3087bca2019-02-14 12:19:16 -05001109 fileNameView.setText(fileInfo.name);
1110
1111 if (fileInfo.hasThumbnail) {
Matt Pietale7cacab2019-05-23 07:21:36 -04001112 mPreviewCoord = new ContentPreviewCoordinator(parent, false);
1113 mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
Matt Pietal3087bca2019-02-14 12:19:16 -05001114 } else {
Matt Pietale7cacab2019-05-23 07:21:36 -04001115 View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
1116 thumbnailView.setVisibility(View.GONE);
1117
Matt Pietal1ef88002019-03-13 10:43:18 -04001118 ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
Matt Pietal3087bca2019-02-14 12:19:16 -05001119 fileIconView.setVisibility(View.VISIBLE);
Matt Pietal832cdbf2019-04-05 13:20:31 -04001120 fileIconView.setImageResource(R.drawable.chooser_file_generic);
Matt Pietal46d828c2019-02-05 08:07:07 -05001121 }
Matt Pietal0ea391b2019-01-30 10:44:15 -05001122 }
1123
Matt Pietal0ea391b2019-01-30 10:44:15 -05001124 @VisibleForTesting
1125 protected boolean isImageType(String mimeType) {
1126 return mimeType != null && mimeType.startsWith("image/");
1127 }
1128
1129 @ContentPreviewType
1130 private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
1131 if (uri == null) {
1132 return CONTENT_PREVIEW_TEXT;
1133 }
1134
1135 String mimeType = resolver.getType(uri);
1136 return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
1137 }
1138
1139 /**
1140 * In {@link android.content.Intent#getType}, the app may specify a very general
1141 * mime-type that broadly covers all data being shared, such as {@literal *}/*
1142 * when sending an image and text. We therefore should inspect each item for the
1143 * the preferred type, in order of IMAGE, FILE, TEXT.
1144 */
1145 @ContentPreviewType
1146 private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
1147 String action = targetIntent.getAction();
1148 if (Intent.ACTION_SEND.equals(action)) {
1149 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1150 return findPreferredContentPreview(uri, resolver);
1151 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1152 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1153 if (uris == null || uris.isEmpty()) {
1154 return CONTENT_PREVIEW_TEXT;
1155 }
1156
1157 for (Uri uri : uris) {
Matt Pietal832cdbf2019-04-05 13:20:31 -04001158 // Defaulting to file preview when there are mixed image/file types is
1159 // preferable, as it shows the user the correct number of items being shared
1160 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_FILE) {
1161 return CONTENT_PREVIEW_FILE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001162 }
1163 }
1164
Matt Pietal832cdbf2019-04-05 13:20:31 -04001165 return CONTENT_PREVIEW_IMAGE;
Matt Pietal0ea391b2019-01-30 10:44:15 -05001166 }
1167
1168 return CONTENT_PREVIEW_TEXT;
1169 }
1170
Mike Digman849a9d12019-04-29 11:20:48 -07001171 private int getNumSheetExpansions() {
1172 return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0);
1173 }
1174
1175 private void incrementNumSheetExpansions() {
1176 getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS,
1177 getNumSheetExpansions() + 1).apply();
1178 }
1179
Adam Powell0b3c1122014-10-09 12:50:14 -07001180 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07001181 protected void onDestroy() {
1182 super.onDestroy();
1183 if (mRefinementResultReceiver != null) {
1184 mRefinementResultReceiver.destroy();
1185 mRefinementResultReceiver = null;
1186 }
Adam Powell9761ab22015-09-08 17:01:49 -07001187 unbindRemainingServices();
Matt Pietalab73a882019-06-05 07:04:55 -04001188 mChooserHandler.removeAllMessages();
Matt Pietale7cacab2019-05-23 07:21:36 -04001189
1190 if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
1191
George Hodulik145b3a52019-03-27 11:18:43 -07001192 if (mAppPredictor != null) {
George Hodulik69d4a082019-01-18 11:27:03 -08001193 mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
1194 mAppPredictor.destroy();
1195 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001196 }
1197
1198 @Override
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001199 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
1200 Intent result = defIntent;
Adam Powelle49d9392014-07-17 18:45:19 -07001201 if (mReplacementExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001202 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
Adam Powelle49d9392014-07-17 18:45:19 -07001203 if (replExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001204 result = new Intent(defIntent);
Adam Powelle49d9392014-07-17 18:45:19 -07001205 result.putExtras(replExtras);
Adam Powelle49d9392014-07-17 18:45:19 -07001206 }
1207 }
Nicolas Prevot741abfc2015-08-11 12:03:51 +01001208 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001209 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
1210 result = Intent.createChooser(result,
1211 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
Hakan Seyalioglu7317e8a2016-12-12 16:15:38 -08001212
1213 // Don't auto-launch single intents if the intent is being forwarded. This is done
1214 // because automatically launching a resolving application as a response to the user
1215 // action of switching accounts is pretty unexpected.
1216 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +00001217 }
1218 return result;
Adam Powelle49d9392014-07-17 18:45:19 -07001219 }
1220
Adam Powell0b3c1122014-10-09 12:50:14 -07001221 @Override
Adam Powell23882512016-01-29 10:21:00 -08001222 public void onActivityStarted(TargetInfo cti) {
Adam Powell0b3c1122014-10-09 12:50:14 -07001223 if (mChosenComponentSender != null) {
Adam Powell24428412015-04-01 17:19:56 -07001224 final ComponentName target = cti.getResolvedComponentName();
Adam Powell0b3c1122014-10-09 12:50:14 -07001225 if (target != null) {
1226 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
1227 try {
1228 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
1229 } catch (IntentSender.SendIntentException e) {
1230 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
1231 + "the chosen component: " + e);
1232 }
1233 }
1234 }
1235 }
1236
Adam Powell24428412015-04-01 17:19:56 -07001237 @Override
Hakan Seyalioglu13405c52017-01-31 19:01:31 -08001238 public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
Adam Powell7d758002015-05-06 17:49:36 -07001239 final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
1240 mChooserListAdapter = (ChooserListAdapter) adapter;
Adam Powell52c39212016-04-07 15:14:18 -07001241 if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
Matt Pietalfbfa0492019-04-01 11:29:56 -04001242 mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets),
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07001243 TARGET_TYPE_DEFAULT);
Adam Powell52c39212016-04-07 15:14:18 -07001244 }
Adam Powell63b31692015-09-28 10:45:00 -07001245 mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
Adam Powell7d758002015-05-06 17:49:36 -07001246 if (listView != null) {
1247 listView.setItemsCanFocus(true);
1248 }
1249 }
1250
1251 @Override
Adam Powell23882512016-01-29 10:21:00 -08001252 public int getLayoutResource() {
Adam Powell7d758002015-05-06 17:49:36 -07001253 return R.layout.chooser_grid;
Adam Powell24428412015-04-01 17:19:56 -07001254 }
1255
1256 @Override
Adam Powell23882512016-01-29 10:21:00 -08001257 public boolean shouldGetActivityMetadata() {
Adam Powell24428412015-04-01 17:19:56 -07001258 return true;
1259 }
1260
Adam Powell9761ab22015-09-08 17:01:49 -07001261 @Override
Ben Lin145b0ca2016-10-14 14:23:40 -07001262 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
Hakan Seyalioglu13405c52017-01-31 19:01:31 -08001263 // Note that this is only safe because the Intent handled by the ChooserActivity is
1264 // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
1265 // method can not be replaced in the ResolverActivity whole hog.
Matt Pietala4b30072019-04-04 13:44:36 -04001266 if (!super.shouldAutoLaunchSingleChoice(target)) {
1267 return false;
1268 }
1269
1270 return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
Ben Lin145b0ca2016-10-14 14:23:40 -07001271 }
1272
1273 @Override
Adam Powell23882512016-01-29 10:21:00 -08001274 public void showTargetDetails(ResolveInfo ri) {
sanryhuang296ca9e2018-03-31 11:17:13 +08001275 if (ri == null) {
1276 return;
1277 }
1278
Adam Powell23882512016-01-29 10:21:00 -08001279 ComponentName name = ri.activityInfo.getComponentName();
Adam Powell23882512016-01-29 10:21:00 -08001280 ResolverTargetActionsDialogFragment f =
1281 new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
Matt Pietaldf634cc2019-03-13 09:55:28 -04001282 name);
Adam Powell23882512016-01-29 10:21:00 -08001283 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1284 }
1285
Adam Powelle49d9392014-07-17 18:45:19 -07001286 private void modifyTargetIntent(Intent in) {
Matt Pietal95574b02019-03-13 08:12:25 -04001287 if (isSendAction(in)) {
Adam Powelle49d9392014-07-17 18:45:19 -07001288 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
Dianne Hackborn13420f22014-07-18 15:43:56 -07001289 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Adam Powelle49d9392014-07-17 18:45:19 -07001290 }
1291 }
Adam Powell24428412015-04-01 17:19:56 -07001292
Adam Powell2ed547e2015-04-29 18:45:04 -07001293 @Override
1294 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
1295 if (mRefinementIntentSender != null) {
1296 final Intent fillIn = new Intent();
1297 final List<Intent> sourceIntents = target.getAllSourceIntents();
1298 if (!sourceIntents.isEmpty()) {
1299 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
1300 if (sourceIntents.size() > 1) {
1301 final Intent[] alts = new Intent[sourceIntents.size() - 1];
1302 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
1303 alts[i - 1] = sourceIntents.get(i);
1304 }
1305 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
1306 }
1307 if (mRefinementResultReceiver != null) {
1308 mRefinementResultReceiver.destroy();
1309 }
1310 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
1311 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
1312 mRefinementResultReceiver);
1313 try {
1314 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
1315 return false;
1316 } catch (SendIntentException e) {
1317 Log.e(TAG, "Refinement IntentSender failed to send", e);
1318 }
1319 }
1320 }
Kang Li9fa2a2c2017-01-06 13:33:24 -08001321 updateModelAndChooserCounts(target);
Adam Powell2ed547e2015-04-29 18:45:04 -07001322 return super.onTargetSelected(target, alwaysCheck);
1323 }
1324
Adam Powell98b7f892015-06-19 12:38:45 -07001325 @Override
Adam Powell23882512016-01-29 10:21:00 -08001326 public void startSelected(int which, boolean always, boolean filtered) {
Matt Pietala4b30072019-04-04 13:44:36 -04001327 TargetInfo targetInfo = mChooserListAdapter.targetInfoForPosition(which, filtered);
1328 if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
1329 return;
1330 }
1331
Kang Li9082f5b2016-12-02 10:56:21 -08001332 final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
Adam Powell98b7f892015-06-19 12:38:45 -07001333 super.startSelected(which, always, filtered);
1334
1335 if (mChooserListAdapter != null) {
1336 // Log the index of which type of target the user picked.
1337 // Lower values mean the ranking was better.
1338 int cat = 0;
1339 int value = which;
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001340 int directTargetAlsoRanked = -1;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001341 int numCallerProvided = 0;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001342 HashedStringCache.HashResult directTargetHashed = null;
Adam Powell98b7f892015-06-19 12:38:45 -07001343 switch (mChooserListAdapter.getPositionTargetType(which)) {
Adam Powell98b7f892015-06-19 12:38:45 -07001344 case ChooserListAdapter.TARGET_SERVICE:
Chris Wrenf6e9228b2016-01-26 18:04:35 -05001345 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001346 // Log the package name + target name to answer the question if most users
1347 // share to mostly the same person or to a bunch of different people.
1348 ChooserTarget target =
1349 mChooserListAdapter.mServiceTargets.get(value).getChooserTarget();
1350 directTargetHashed = HashedStringCache.getInstance().hashString(
1351 this,
1352 TAG,
1353 target.getComponentName().getPackageName()
1354 + target.getTitle().toString(),
1355 mMaxHashSaltDays);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001356 directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001357
1358 if (mCallerChooserTargets != null) {
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001359 numCallerProvided = mCallerChooserTargets.length;
Matt Pietal9a6b23d2019-04-19 14:47:14 -04001360 }
Adam Powell98b7f892015-06-19 12:38:45 -07001361 break;
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001362 case ChooserListAdapter.TARGET_CALLER:
Adam Powell98b7f892015-06-19 12:38:45 -07001363 case ChooserListAdapter.TARGET_STANDARD:
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001364 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
1365 value -= mChooserListAdapter.getSelectableServiceTargetCount();
1366 numCallerProvided = mChooserListAdapter.getCallerTargetCount();
Adam Powell98b7f892015-06-19 12:38:45 -07001367 break;
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04001368 case ChooserListAdapter.TARGET_STANDARD_AZ:
1369 // A-Z targets are unranked standard targets; we use -1 to mark that they
1370 // are from the alphabetical pool.
1371 value = -1;
1372 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
1373 break;
Adam Powell98b7f892015-06-19 12:38:45 -07001374 }
1375
1376 if (cat != 0) {
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001377 LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
1378 if (directTargetHashed != null) {
1379 targetLogMaker.addTaggedData(
1380 MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
1381 targetLogMaker.addTaggedData(
1382 MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
1383 directTargetHashed.saltGeneration);
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001384 targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
1385 directTargetAlsoRanked);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001386 }
Susi Kharraz-Post4bcca522019-04-23 15:07:10 -04001387 targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED,
1388 numCallerProvided);
Susi Kharraz-Post14cbfcd2019-04-01 11:07:59 -04001389 getMetricsLogger().write(targetLogMaker);
Adam Powell98b7f892015-06-19 12:38:45 -07001390 }
Kang Li9082f5b2016-12-02 10:56:21 -08001391
1392 if (mIsSuccessfullySelected) {
1393 if (DEBUG) {
1394 Log.d(TAG, "User Selection Time Cost is " + selectionCost);
1395 Log.d(TAG, "position of selected app/service/caller is " +
1396 Integer.toString(value));
1397 }
1398 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
1399 (int) selectionCost);
1400 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
1401 }
Adam Powell98b7f892015-06-19 12:38:45 -07001402 }
1403 }
1404
Susi Kharraz-Post8c14f772019-04-17 16:33:41 -04001405 private int getRankedPosition(SelectableTargetInfo targetInfo) {
1406 String targetPackageName =
1407 targetInfo.getChooserTarget().getComponentName().getPackageName();
1408 int maxRankedResults = Math.min(mChooserListAdapter.mDisplayList.size(),
1409 MAX_LOG_RANK_POSITION);
1410
1411 for (int i = 0; i < maxRankedResults; i++) {
1412 if (mChooserListAdapter.mDisplayList.get(i)
1413 .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
1414 return i;
1415 }
1416 }
1417 return -1;
1418 }
1419
Adam Powell24428412015-04-01 17:19:56 -07001420 void queryTargetServices(ChooserListAdapter adapter) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001421 mQueriedTargetServicesTimeMs = System.currentTimeMillis();
1422
Adam Powell24428412015-04-01 17:19:56 -07001423 final PackageManager pm = getPackageManager();
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001424 ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
Adam Powell24428412015-04-01 17:19:56 -07001425 int targetsToQuery = 0;
Matt Pietalab73a882019-06-05 07:04:55 -04001426
Adam Powell24428412015-04-01 17:19:56 -07001427 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
1428 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
Adam Powell3a09c522015-10-21 13:21:28 -07001429 if (adapter.getScore(dri) == 0) {
1430 // A score of 0 means the app hasn't been used in some time;
1431 // don't query it as it's not likely to be relevant.
1432 continue;
1433 }
Adam Powell24428412015-04-01 17:19:56 -07001434 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001435 if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
1436 && sm.hasShareTargets(ai.packageName)) {
1437 // Share targets will be queried from ShortcutManager
1438 continue;
1439 }
Adam Powell24428412015-04-01 17:19:56 -07001440 final Bundle md = ai.metaData;
1441 final String serviceName = md != null ? convertServiceName(ai.packageName,
1442 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
1443 if (serviceName != null) {
1444 final ComponentName serviceComponent = new ComponentName(
1445 ai.packageName, serviceName);
Matt Pietalab73a882019-06-05 07:04:55 -04001446
1447 if (mServicesRequested.contains(serviceComponent)) {
1448 continue;
1449 }
1450 mServicesRequested.add(serviceComponent);
1451
Adam Powell24428412015-04-01 17:19:56 -07001452 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
1453 .setComponent(serviceComponent);
1454
1455 if (DEBUG) {
1456 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
1457 }
1458
1459 try {
1460 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
1461 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
1462 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
1463 + " permission " + ChooserTargetService.BIND_PERMISSION
1464 + " - this service will not be queried for ChooserTargets."
1465 + " add android:permission=\""
1466 + ChooserTargetService.BIND_PERMISSION + "\""
1467 + " to the <service> tag for " + serviceComponent
1468 + " in the manifest.");
1469 continue;
1470 }
1471 } catch (NameNotFoundException e) {
Adam Powell52c39212016-04-07 15:14:18 -07001472 Log.e(TAG, "Could not look up service " + serviceComponent
1473 + "; component name not found");
Adam Powell24428412015-04-01 17:19:56 -07001474 continue;
1475 }
1476
Adam Powell9761ab22015-09-08 17:01:49 -07001477 final ChooserTargetServiceConnection conn =
1478 new ChooserTargetServiceConnection(this, dri);
Adam Powell52c39212016-04-07 15:14:18 -07001479
1480 // Explicitly specify Process.myUserHandle instead of calling bindService
1481 // to avoid the warning from calling from the system process without an explicit
1482 // user handle
1483 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
1484 Process.myUserHandle())) {
Adam Powell24428412015-04-01 17:19:56 -07001485 if (DEBUG) {
1486 Log.d(TAG, "Binding service connection for target " + dri
1487 + " intent " + serviceIntent);
1488 }
1489 mServiceConnections.add(conn);
1490 targetsToQuery++;
1491 }
1492 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001493 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
Matt Pietal26038402019-01-08 07:29:34 -05001494 if (DEBUG) {
1495 Log.d(TAG, "queryTargets hit query target limit "
1496 + QUERY_TARGET_SERVICE_LIMIT);
1497 }
Adam Powell24428412015-04-01 17:19:56 -07001498 break;
1499 }
1500 }
1501
Matt Pietalab73a882019-06-05 07:04:55 -04001502 mChooserHandler.restartServiceRequestTimer();
Adam Powell24428412015-04-01 17:19:56 -07001503 }
1504
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001505 private IntentFilter getTargetIntentFilter() {
1506 try {
1507 final Intent intent = getTargetIntent();
1508 String dataString = intent.getDataString();
1509 if (TextUtils.isEmpty(dataString)) {
1510 dataString = intent.getType();
1511 }
1512 return new IntentFilter(intent.getAction(), dataString);
1513 } catch (Exception e) {
1514 Log.e(TAG, "failed to get target intent filter " + e);
1515 return null;
1516 }
1517 }
1518
George Hodulik69d4a082019-01-18 11:27:03 -08001519 private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001520 // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
1521 // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
George Hodulik69d4a082019-01-18 11:27:03 -08001522 List<DisplayResolveInfo> driList = new ArrayList<>();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001523 int targetsToQuery = 0;
1524 for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
1525 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
1526 if (adapter.getScore(dri) == 0) {
1527 // A score of 0 means the app hasn't been used in some time;
1528 // don't query it as it's not likely to be relevant.
1529 continue;
1530 }
1531 driList.add(dri);
1532 targetsToQuery++;
1533 // TODO(b/121287224): Do we need this here? (similar to queryTargetServices)
1534 if (targetsToQuery >= SHARE_TARGET_QUERY_PACKAGE_LIMIT) {
1535 if (DEBUG) {
1536 Log.d(TAG, "queryTargets hit query target limit "
1537 + SHARE_TARGET_QUERY_PACKAGE_LIMIT);
1538 }
1539 break;
1540 }
1541 }
George Hodulik69d4a082019-01-18 11:27:03 -08001542 return driList;
1543 }
1544
George Hodulik3f399f22019-04-26 16:17:54 -07001545 private void queryDirectShareTargets(
1546 ChooserListAdapter adapter, boolean skipAppPredictionService) {
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001547 mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
George Hodulik3f399f22019-04-26 16:17:54 -07001548 if (!skipAppPredictionService) {
1549 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
1550 if (appPredictor != null) {
1551 appPredictor.requestPredictionUpdate();
1552 return;
1553 }
George Hodulik69d4a082019-01-18 11:27:03 -08001554 }
George Hodulik145b3a52019-03-27 11:18:43 -07001555 // Default to just querying ShortcutManager if AppPredictor not present.
George Hodulik69d4a082019-01-18 11:27:03 -08001556 final IntentFilter filter = getTargetIntentFilter();
1557 if (filter == null) {
1558 return;
1559 }
1560 final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001561
1562 AsyncTask.execute(() -> {
1563 ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
1564 List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
George Hodulikaa5238c2019-04-18 14:17:51 -07001565 sendShareShortcutInfoList(resultList, driList, null);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001566 });
1567 }
1568
George Hodulik69d4a082019-01-18 11:27:03 -08001569 private void sendShareShortcutInfoList(
1570 List<ShortcutManager.ShareShortcutInfo> resultList,
George Hodulikaa5238c2019-04-18 14:17:51 -07001571 List<DisplayResolveInfo> driList,
1572 @Nullable List<AppTarget> appTargets) {
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001573 if (appTargets != null && appTargets.size() != resultList.size()) {
1574 throw new RuntimeException("resultList and appTargets must have the same size."
1575 + " resultList.size()=" + resultList.size()
1576 + " appTargets.size()=" + appTargets.size());
1577 }
1578
1579 for (int i = resultList.size() - 1; i >= 0; i--) {
1580 final String packageName = resultList.get(i).getTargetComponent().getPackageName();
1581 if (!isPackageEnabled(packageName)) {
1582 resultList.remove(i);
1583 if (appTargets != null) {
1584 appTargets.remove(i);
1585 }
1586 }
1587 }
1588
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07001589 // If |appTargets| is not null, results are from AppPredictionService and already sorted.
1590 final int shortcutType = (appTargets == null ? TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER :
1591 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
1592
George Hodulik69d4a082019-01-18 11:27:03 -08001593 // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
1594 // for direct share targets. After ShareSheet is refactored we should use the
1595 // ShareShortcutInfos directly.
1596 boolean resultMessageSent = false;
1597 for (int i = 0; i < driList.size(); i++) {
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001598 List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
George Hodulik69d4a082019-01-18 11:27:03 -08001599 for (int j = 0; j < resultList.size(); j++) {
1600 if (driList.get(i).getResolvedComponentName().equals(
1601 resultList.get(j).getTargetComponent())) {
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001602 matchingShortcuts.add(resultList.get(j));
George Hodulik69d4a082019-01-18 11:27:03 -08001603 }
1604 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001605 if (matchingShortcuts.isEmpty()) {
George Hodulik69d4a082019-01-18 11:27:03 -08001606 continue;
1607 }
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001608 List<ChooserTarget> chooserTargets = convertToChooserTarget(
1609 matchingShortcuts, resultList, appTargets, shortcutType);
1610
George Hodulik69d4a082019-01-18 11:27:03 -08001611 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04001612 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
George Hodulik69d4a082019-01-18 11:27:03 -08001613 msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07001614 msg.arg1 = shortcutType;
George Hodulik69d4a082019-01-18 11:27:03 -08001615 mChooserHandler.sendMessage(msg);
1616 resultMessageSent = true;
1617 }
1618
1619 if (resultMessageSent) {
George Hodulik145b3a52019-03-27 11:18:43 -07001620 sendShortcutManagerShareTargetResultCompleted();
George Hodulik69d4a082019-01-18 11:27:03 -08001621 }
1622 }
1623
George Hodulik145b3a52019-03-27 11:18:43 -07001624 private void sendShortcutManagerShareTargetResultCompleted() {
1625 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04001626 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
George Hodulik145b3a52019-03-27 11:18:43 -07001627 mChooserHandler.sendMessage(msg);
1628 }
1629
Mehdi Alizadeh3e3216f2019-05-27 17:56:51 -07001630 private boolean isPackageEnabled(String packageName) {
1631 if (TextUtils.isEmpty(packageName)) {
1632 return false;
1633 }
1634 ApplicationInfo appInfo;
1635 try {
1636 appInfo = getPackageManager().getApplicationInfo(packageName, 0);
1637 } catch (NameNotFoundException e) {
1638 return false;
1639 }
1640
1641 if (appInfo != null && appInfo.enabled
1642 && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
1643 return true;
1644 }
1645 return false;
1646 }
1647
Mehdi Alizadeh707c0cf2019-09-03 18:11:48 -07001648 /**
1649 * Converts a list of ShareShortcutInfos to ChooserTargets.
1650 * @param matchingShortcuts List of shortcuts, all from the same package, that match the current
1651 * share intent filter.
1652 * @param allShortcuts List of all the shortcuts from all the packages on the device that are
1653 * returned for the current sharing action.
1654 * @param allAppTargets List of AppTargets. Null if the results are not from prediction service.
1655 * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or
1656 * TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
1657 * @return A list of ChooserTargets sorted by score in descending order.
1658 */
1659 @VisibleForTesting
1660 @NonNull
1661 public List<ChooserTarget> convertToChooserTarget(
1662 @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts,
1663 @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts,
1664 @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) {
1665 // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted
1666 // list instead of the actual rank value when converting a rank to a score.
1667 List<Integer> scoreList = new ArrayList<>();
1668 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
1669 for (int i = 0; i < matchingShortcuts.size(); i++) {
1670 int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank();
1671 if (!scoreList.contains(shortcutRank)) {
1672 scoreList.add(shortcutRank);
1673 }
1674 }
1675 Collections.sort(scoreList);
1676 }
1677
1678 List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size());
1679 for (int i = 0; i < matchingShortcuts.size(); i++) {
1680 ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo();
1681 int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i));
1682
1683 float score;
1684 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
1685 // Incoming results are ordered. Create a score based on index in the original list.
1686 score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f);
1687 } else {
1688 // Create a score based on the rank of the shortcut.
1689 int rankIndex = scoreList.indexOf(shortcutInfo.getRank());
1690 score = Math.max(1.0f - (0.01f * rankIndex), 0.0f);
1691 }
1692
1693 Bundle extras = new Bundle();
1694 extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
1695 ChooserTarget chooserTarget = new ChooserTarget(shortcutInfo.getShortLabel(),
1696 null, // Icon will be loaded later if this target is selected to be shown.
1697 score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);
1698
1699 chooserTargetList.add(chooserTarget);
1700 if (mDirectShareAppTargetCache != null && allAppTargets != null) {
1701 mDirectShareAppTargetCache.put(chooserTarget,
1702 allAppTargets.get(indexInAllShortcuts));
1703 }
1704 }
1705
1706 // Sort ChooserTargets by score in descending order
1707 Comparator<ChooserTarget> byScore =
1708 (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore());
1709 Collections.sort(chooserTargetList, byScore);
1710 return chooserTargetList;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001711 }
1712
Adam Powell24428412015-04-01 17:19:56 -07001713 private String convertServiceName(String packageName, String serviceName) {
1714 if (TextUtils.isEmpty(serviceName)) {
1715 return null;
1716 }
1717
1718 final String fullName;
1719 if (serviceName.startsWith(".")) {
1720 // Relative to the app package. Prepend the app package name.
1721 fullName = packageName + serviceName;
1722 } else if (serviceName.indexOf('.') >= 0) {
1723 // Fully qualified package name.
1724 fullName = serviceName;
1725 } else {
1726 fullName = null;
1727 }
1728 return fullName;
1729 }
1730
1731 void unbindRemainingServices() {
1732 if (DEBUG) {
1733 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
1734 }
1735 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
1736 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
1737 if (DEBUG) Log.d(TAG, "unbinding " + conn);
1738 unbindService(conn);
Adam Powell9761ab22015-09-08 17:01:49 -07001739 conn.destroy();
Adam Powell24428412015-04-01 17:19:56 -07001740 }
Matt Pietalab73a882019-06-05 07:04:55 -04001741 mServicesRequested.clear();
Adam Powell24428412015-04-01 17:19:56 -07001742 mServiceConnections.clear();
Adam Powell24428412015-04-01 17:19:56 -07001743 }
1744
Mehdi Alizadeh97fb3ed2019-04-25 14:52:02 -07001745 private void logDirectShareTargetReceived(int logCategory) {
1746 final long queryTime =
1747 logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
1748 ? mQueriedSharingShortcutsTimeMs : mQueriedTargetServicesTimeMs;
1749 final int apiLatency = (int) (System.currentTimeMillis() - queryTime);
1750 getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
1751 }
1752
Kang Li9fa2a2c2017-01-06 13:33:24 -08001753 void updateModelAndChooserCounts(TargetInfo info) {
Kang Li53b43142016-11-14 14:38:25 -08001754 if (info != null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001755 sendClickToAppPredictor(info);
Kang Li53b43142016-11-14 14:38:25 -08001756 final ResolveInfo ri = info.getResolveInfo();
Kang Li64b018e2017-01-05 17:30:06 -08001757 Intent targetIntent = getTargetIntent();
1758 if (ri != null && ri.activityInfo != null && targetIntent != null) {
Kang Li0cef910d2017-01-05 09:14:36 -08001759 if (mAdapter != null) {
1760 mAdapter.updateModel(info.getResolvedComponentName());
Kang Li9fa2a2c2017-01-06 13:33:24 -08001761 mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
1762 targetIntent.getAction());
Kang Li0cef910d2017-01-05 09:14:36 -08001763 }
Kang Li53b43142016-11-14 14:38:25 -08001764 if (DEBUG) {
Kang Li64b018e2017-01-05 17:30:06 -08001765 Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
Kang Li64b018e2017-01-05 17:30:06 -08001766 Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
Kang Li53b43142016-11-14 14:38:25 -08001767 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001768 } else if (DEBUG) {
Kang Li53b43142016-11-14 14:38:25 -08001769 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
1770 }
1771 }
Kang Li9082f5b2016-12-02 10:56:21 -08001772 mIsSuccessfullySelected = true;
Kang Li53b43142016-11-14 14:38:25 -08001773 }
1774
George Hodulikf2b0d342019-01-25 12:43:54 -08001775 private void sendClickToAppPredictor(TargetInfo targetInfo) {
George Hodulikaa5238c2019-04-18 14:17:51 -07001776 AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled();
1777 if (directShareAppPredictor == null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001778 return;
1779 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001780 if (!(targetInfo instanceof ChooserTargetInfo)) {
1781 return;
1782 }
1783 ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
George Hodulikaa5238c2019-04-18 14:17:51 -07001784 AppTarget appTarget = null;
1785 if (mDirectShareAppTargetCache != null) {
1786 appTarget = mDirectShareAppTargetCache.get(chooserTarget);
George Hodulikf2b0d342019-01-25 12:43:54 -08001787 }
George Hodulikaa5238c2019-04-18 14:17:51 -07001788 // This is a direct share click that was provided by the APS
1789 if (appTarget != null) {
1790 directShareAppPredictor.notifyAppTargetEvent(
1791 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
1792 .setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
1793 .build());
George Hodulikf2b0d342019-01-25 12:43:54 -08001794 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001795 }
1796
George Hodulik145b3a52019-03-27 11:18:43 -07001797 @Nullable
1798 private AppPredictor getAppPredictor() {
Mehdi Alizadeha1c18a82019-08-15 16:31:38 -07001799 if (!mIsAppPredictorComponentAvailable) {
1800 return null;
1801 }
Mehdi Alizadehe870e972019-09-11 17:54:15 -07001802 if (mAppPredictor == null) {
George Hodulik145b3a52019-03-27 11:18:43 -07001803 final IntentFilter filter = getTargetIntentFilter();
1804 Bundle extras = new Bundle();
1805 extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
1806 AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(this)
1807 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
1808 .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
1809 .setExtras(extras)
1810 .build();
1811 AppPredictionManager appPredictionManager
1812 = getSystemService(AppPredictionManager.class);
1813 mAppPredictor = appPredictionManager.createAppPredictionSession(appPredictionContext);
1814 }
1815 return mAppPredictor;
1816 }
1817
1818 /**
1819 * This will return an app predictor if it is enabled for direct share sorting
1820 * and if one exists. Otherwise, it returns null.
1821 */
1822 @Nullable
1823 private AppPredictor getAppPredictorForDirectShareIfEnabled() {
Matt Pietal030bd842019-05-29 07:14:14 -04001824 return USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS && !ActivityManager.isLowRamDeviceStatic()
1825 ? getAppPredictor() : null;
George Hodulik145b3a52019-03-27 11:18:43 -07001826 }
1827
George Hodulikc681ce42019-04-12 17:10:31 -07001828 /**
1829 * This will return an app predictor if it is enabled for share activity sorting
1830 * and if one exists. Otherwise, it returns null.
1831 */
1832 @Nullable
1833 private AppPredictor getAppPredictorForShareActivitesIfEnabled() {
1834 return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? getAppPredictor() : null;
1835 }
1836
Adam Powell2ed547e2015-04-29 18:45:04 -07001837 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
1838 if (mRefinementResultReceiver != null) {
1839 mRefinementResultReceiver.destroy();
1840 mRefinementResultReceiver = null;
1841 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001842 if (selectedTarget == null) {
1843 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
1844 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
1845 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
1846 + " cannot match refined source intent " + matchingIntent);
Kang Li53b43142016-11-14 14:38:25 -08001847 } else {
1848 TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
1849 if (super.onTargetSelected(clonedTarget, false)) {
Kang Li9fa2a2c2017-01-06 13:33:24 -08001850 updateModelAndChooserCounts(clonedTarget);
Kang Li53b43142016-11-14 14:38:25 -08001851 finish();
1852 return;
1853 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001854 }
1855 onRefinementCanceled();
1856 }
1857
1858 void onRefinementCanceled() {
1859 if (mRefinementResultReceiver != null) {
1860 mRefinementResultReceiver.destroy();
1861 mRefinementResultReceiver = null;
1862 }
1863 finish();
1864 }
1865
1866 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
1867 final List<Intent> targetIntents = target.getAllSourceIntents();
1868 for (int i = 0, N = targetIntents.size(); i < N; i++) {
1869 final Intent targetIntent = targetIntents.get(i);
1870 if (targetIntent.filterEquals(matchingIntent)) {
1871 return true;
1872 }
1873 }
1874 return false;
1875 }
1876
Adam Powell666d82a2015-07-15 20:14:57 -07001877 void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
1878 if (targets == null) {
1879 return;
1880 }
1881
1882 final PackageManager pm = getPackageManager();
1883 for (int i = targets.size() - 1; i >= 0; i--) {
1884 final ChooserTarget target = targets.get(i);
1885 final ComponentName targetName = target.getComponentName();
1886 if (packageName != null && packageName.equals(targetName.getPackageName())) {
1887 // Anything from the original target's package is fine.
1888 continue;
1889 }
1890
1891 boolean remove;
1892 try {
1893 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
1894 remove = !ai.exported || ai.permission != null;
1895 } catch (NameNotFoundException e) {
1896 Log.e(TAG, "Target " + target + " returned by " + packageName
1897 + " component not found");
1898 remove = true;
1899 }
1900
1901 if (remove) {
1902 targets.remove(i);
1903 }
1904 }
1905 }
1906
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04001907 private void updateAlphabeticalList() {
Alison Cichowlas13314612019-04-11 15:20:39 -04001908 mSortedList.clear();
1909 mSortedList.addAll(getDisplayList());
1910 Collections.sort(mSortedList, new AzInfoComparator(ChooserActivity.this));
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04001911 }
1912
1913 /**
1914 * Sort intents alphabetically based on display label.
1915 */
1916 class AzInfoComparator implements Comparator<ResolverActivity.DisplayResolveInfo> {
1917 Collator mCollator;
1918 AzInfoComparator(Context context) {
1919 mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
1920 }
1921
1922 @Override
1923 public int compare(ResolverActivity.DisplayResolveInfo lhsp,
1924 ResolverActivity.DisplayResolveInfo rhsp) {
1925 return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
1926 }
1927 }
1928
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -05001929 protected MetricsLogger getMetricsLogger() {
1930 if (mMetricsLogger == null) {
1931 mMetricsLogger = new MetricsLogger();
1932 }
1933 return mMetricsLogger;
1934 }
1935
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001936 public class ChooserListController extends ResolverListController {
1937 public ChooserListController(Context context,
1938 PackageManager pm,
1939 Intent targetIntent,
1940 String referrerPackageName,
George Hodulikc681ce42019-04-12 17:10:31 -07001941 int launchedFromUid,
1942 AbstractResolverComparator resolverComparator) {
1943 super(context, pm, targetIntent, referrerPackageName, launchedFromUid,
1944 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001945 }
1946
1947 @Override
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001948 boolean isComponentFiltered(ComponentName name) {
1949 if (mFilteredComponentNames == null) {
1950 return false;
1951 }
1952 for (ComponentName filteredComponentName : mFilteredComponentNames) {
1953 if (name.equals(filteredComponentName)) {
1954 return true;
1955 }
1956 }
1957 return false;
1958 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001959 }
1960
Adam Powell24428412015-04-01 17:19:56 -07001961 @Override
Adam Powell23882512016-01-29 10:21:00 -08001962 public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
Adam Powell7d758002015-05-06 17:49:36 -07001963 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
1964 boolean filterLastUsed) {
1965 final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001966 initialIntents, rList, launchedFromUid, filterLastUsed, createListController());
Adam Powell24428412015-04-01 17:19:56 -07001967 return adapter;
1968 }
1969
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001970 @VisibleForTesting
1971 protected ResolverListController createListController() {
George Hodulikc681ce42019-04-12 17:10:31 -07001972 AppPredictor appPredictor = getAppPredictorForShareActivitesIfEnabled();
1973 AbstractResolverComparator resolverComparator;
1974 if (appPredictor != null) {
1975 resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
George Hodulik3f399f22019-04-26 16:17:54 -07001976 getReferrerPackageName(), appPredictor, getUser());
George Hodulikc681ce42019-04-12 17:10:31 -07001977 } else {
1978 resolverComparator =
1979 new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
1980 getReferrerPackageName(), null);
1981 }
1982
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001983 return new ChooserListController(
1984 this,
1985 mPm,
1986 getTargetIntent(),
1987 getReferrerPackageName(),
George Hodulikc681ce42019-04-12 17:10:31 -07001988 mLaunchedFromUid,
1989 resolverComparator);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001990 }
1991
Matt Pietal26038402019-01-08 07:29:34 -05001992 @VisibleForTesting
1993 protected Bitmap loadThumbnail(Uri uri, Size size) {
1994 if (uri == null || size == null) {
1995 return null;
1996 }
1997
1998 try {
Matt Pietal46d828c2019-02-05 08:07:07 -05001999 return ImageUtils.loadThumbnail(getContentResolver(), uri, size);
2000 } catch (IOException | NullPointerException | SecurityException ex) {
Matt Pietal62532e52019-05-07 09:51:37 -04002001 logContentPreviewWarning(uri);
Matt Pietal26038402019-01-08 07:29:34 -05002002 }
2003 return null;
2004 }
2005
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002006 interface ChooserTargetInfo extends TargetInfo {
2007 float getModifiedScore();
2008
2009 ChooserTarget getChooserTarget();
Matt Pietal9d501432019-04-12 10:05:29 -04002010
2011 /**
2012 * Do not label as 'equals', since this doesn't quite work
2013 * as intended with java 8.
2014 */
2015 default boolean isSimilar(ChooserTargetInfo other) {
2016 if (other == null) return false;
2017
2018 ChooserTarget ct1 = getChooserTarget();
2019 ChooserTarget ct2 = other.getChooserTarget();
2020
2021 // If either is null, there is not enough info to make an informed decision
2022 // about equality, so just exit
2023 if (ct1 == null || ct2 == null) return false;
2024
2025 if (ct1.getComponentName().equals(ct2.getComponentName())
2026 && TextUtils.equals(getDisplayLabel(), other.getDisplayLabel())
2027 && TextUtils.equals(getExtendedInfo(), other.getExtendedInfo())) {
2028 return true;
2029 }
2030
2031 return false;
2032 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002033 }
2034
2035 /**
2036 * Distinguish between targets that selectable by the user, vs those that are
2037 * placeholders for the system while information is loading in an async manner.
2038 */
2039 abstract class NotSelectableTargetInfo implements ChooserTargetInfo {
2040
2041 public Intent getResolvedIntent() {
2042 return null;
2043 }
2044
2045 public ComponentName getResolvedComponentName() {
2046 return null;
2047 }
2048
2049 public boolean start(Activity activity, Bundle options) {
2050 return false;
2051 }
2052
2053 public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
2054 return false;
2055 }
2056
2057 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
2058 return false;
2059 }
2060
2061 public ResolveInfo getResolveInfo() {
2062 return null;
2063 }
2064
2065 public CharSequence getDisplayLabel() {
2066 return null;
2067 }
2068
2069 public CharSequence getExtendedInfo() {
2070 return null;
2071 }
2072
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002073 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
2074 return null;
2075 }
2076
2077 public List<Intent> getAllSourceIntents() {
2078 return null;
2079 }
2080
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002081 public float getModifiedScore() {
Matt Pietalfbfa0492019-04-01 11:29:56 -04002082 return -0.1f;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002083 }
2084
2085 public ChooserTarget getChooserTarget() {
2086 return null;
2087 }
Matt Pietala4b30072019-04-04 13:44:36 -04002088
2089 public boolean isSuspended() {
2090 return false;
2091 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002092 }
2093
2094 final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
2095 public Drawable getDisplayIcon() {
Mike Digmanac1d88c2019-04-18 15:15:55 -07002096 AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
2097 getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
2098 avd.start(); // Start animation after generation
2099 return avd;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002100 }
2101 }
2102
2103
2104 final class EmptyTargetInfo extends NotSelectableTargetInfo {
2105 public Drawable getDisplayIcon() {
2106 return null;
2107 }
2108 }
2109
2110 final class SelectableTargetInfo implements ChooserTargetInfo {
Adam Powell2ed547e2015-04-29 18:45:04 -07002111 private final DisplayResolveInfo mSourceInfo;
Adam Powell0ccc0e92015-04-23 17:19:37 -07002112 private final ResolveInfo mBackupResolveInfo;
Adam Powell24428412015-04-01 17:19:56 -07002113 private final ChooserTarget mChooserTarget;
Matt Pietal9d501432019-04-12 10:05:29 -04002114 private final String mDisplayLabel;
Adam Powell7d758002015-05-06 17:49:36 -07002115 private Drawable mBadgeIcon = null;
Alan Viverettece5d92c2015-07-31 16:46:56 -04002116 private CharSequence mBadgeContentDescription;
Adam Powell13036be2015-05-12 14:43:56 -07002117 private Drawable mDisplayIcon;
Adam Powell2ed547e2015-04-29 18:45:04 -07002118 private final Intent mFillInIntent;
2119 private final int mFillInFlags;
Adam Powella182e452015-07-06 16:57:56 -07002120 private final float mModifiedScore;
Matt Pietalab986b52019-04-10 10:14:32 -04002121 private boolean mIsSuspended = false;
Adam Powell24428412015-04-01 17:19:56 -07002122
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002123 SelectableTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
Adam Powella182e452015-07-06 16:57:56 -07002124 float modifiedScore) {
Adam Powell24428412015-04-01 17:19:56 -07002125 mSourceInfo = sourceInfo;
2126 mChooserTarget = chooserTarget;
Adam Powella182e452015-07-06 16:57:56 -07002127 mModifiedScore = modifiedScore;
Adam Powell7d758002015-05-06 17:49:36 -07002128 if (sourceInfo != null) {
2129 final ResolveInfo ri = sourceInfo.getResolveInfo();
2130 if (ri != null) {
2131 final ActivityInfo ai = ri.activityInfo;
2132 if (ai != null && ai.applicationInfo != null) {
Alan Viverettece5d92c2015-07-31 16:46:56 -04002133 final PackageManager pm = getPackageManager();
2134 mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
2135 mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
Matt Pietalab986b52019-04-10 10:14:32 -04002136 mIsSuspended =
2137 (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
Adam Powell7d758002015-05-06 17:49:36 -07002138 }
2139 }
2140 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002141 // TODO(b/121287224): do this in the background thread, and only for selected targets
2142 mDisplayIcon = getChooserTargetIconDrawable(chooserTarget);
Adam Powell0ccc0e92015-04-23 17:19:37 -07002143
2144 if (sourceInfo != null) {
2145 mBackupResolveInfo = null;
2146 } else {
2147 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
2148 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002149
2150 mFillInIntent = null;
2151 mFillInFlags = 0;
Matt Pietal9d501432019-04-12 10:05:29 -04002152
2153 mDisplayLabel = sanitizeDisplayLabel(chooserTarget.getTitle());
Adam Powell2ed547e2015-04-29 18:45:04 -07002154 }
2155
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002156 private SelectableTargetInfo(SelectableTargetInfo other, Intent fillInIntent, int flags) {
Adam Powell2ed547e2015-04-29 18:45:04 -07002157 mSourceInfo = other.mSourceInfo;
2158 mBackupResolveInfo = other.mBackupResolveInfo;
2159 mChooserTarget = other.mChooserTarget;
Adam Powell7d758002015-05-06 17:49:36 -07002160 mBadgeIcon = other.mBadgeIcon;
Alan Viverettece5d92c2015-07-31 16:46:56 -04002161 mBadgeContentDescription = other.mBadgeContentDescription;
Adam Powell2ed547e2015-04-29 18:45:04 -07002162 mDisplayIcon = other.mDisplayIcon;
2163 mFillInIntent = fillInIntent;
2164 mFillInFlags = flags;
Adam Powella182e452015-07-06 16:57:56 -07002165 mModifiedScore = other.mModifiedScore;
Matt Pietal9d501432019-04-12 10:05:29 -04002166
2167 mDisplayLabel = sanitizeDisplayLabel(mChooserTarget.getTitle());
2168 }
2169
2170 private String sanitizeDisplayLabel(CharSequence label) {
2171 SpannableStringBuilder sb = new SpannableStringBuilder(label);
2172 sb.clearSpans();
2173 return sb.toString();
Adam Powella182e452015-07-06 16:57:56 -07002174 }
2175
Matt Pietala4b30072019-04-04 13:44:36 -04002176 public boolean isSuspended() {
2177 return mIsSuspended;
2178 }
2179
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002180 /**
2181 * Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
2182 * the call to LauncherApps#getShortcuts(ShortcutQuery).
2183 */
2184 // TODO(121287224): Refactor code to apply the suggestion above
2185 private Drawable getChooserTargetIconDrawable(ChooserTarget target) {
Mike Digman9c4ae502019-03-19 17:02:25 -07002186 Drawable directShareIcon = null;
2187
2188 // First get the target drawable and associated activity info
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002189 final Icon icon = target.getIcon();
2190 if (icon != null) {
Mike Digman9c4ae502019-03-19 17:02:25 -07002191 directShareIcon = icon.loadDrawable(ChooserActivity.this);
2192 } else if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
2193 Bundle extras = target.getIntentExtras();
2194 if (extras != null && extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
2195 CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
2196 LauncherApps launcherApps = (LauncherApps) getSystemService(
2197 Context.LAUNCHER_APPS_SERVICE);
2198 final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
2199 q.setPackage(target.getComponentName().getPackageName());
2200 q.setShortcutIds(Arrays.asList(shortcutId.toString()));
2201 q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
2202 final List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(q, getUser());
2203 if (shortcuts != null && shortcuts.size() > 0) {
2204 directShareIcon = launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
2205 }
2206 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002207 }
2208
Mike Digman9c4ae502019-03-19 17:02:25 -07002209 if (directShareIcon == null) return null;
2210
2211 ActivityInfo info = null;
2212 try {
2213 info = mPm.getActivityInfo(target.getComponentName(), 0);
2214 } catch (NameNotFoundException error) {
2215 Log.e(TAG, "Could not find activity associated with ChooserTarget");
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002216 }
2217
Mike Digman9c4ae502019-03-19 17:02:25 -07002218 if (info == null) return null;
2219
2220 // Now fetch app icon and raster with no badging even in work profile
Mike Digmanb2e5e712019-04-19 15:49:10 -07002221 Bitmap appIcon = makePresentationGetter(info).getIconBitmap(
2222 UserHandle.getUserHandleForUid(UserHandle.myUserId()));
Mike Digman9c4ae502019-03-19 17:02:25 -07002223
2224 // Raster target drawable with appIcon as a badge
2225 SimpleIconFactory sif = SimpleIconFactory.obtain(ChooserActivity.this);
2226 Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
2227 sif.recycle();
2228
2229 return new BitmapDrawable(getResources(), directShareBadgedIcon);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002230 }
2231
Adam Powella182e452015-07-06 16:57:56 -07002232 public float getModifiedScore() {
2233 return mModifiedScore;
Adam Powell24428412015-04-01 17:19:56 -07002234 }
2235
2236 @Override
2237 public Intent getResolvedIntent() {
Adam Powell7d758002015-05-06 17:49:36 -07002238 if (mSourceInfo != null) {
Adam Powell0ccc0e92015-04-23 17:19:37 -07002239 return mSourceInfo.getResolvedIntent();
2240 }
Adam Powell52c39212016-04-07 15:14:18 -07002241
2242 final Intent targetIntent = new Intent(getTargetIntent());
2243 targetIntent.setComponent(mChooserTarget.getComponentName());
2244 targetIntent.putExtras(mChooserTarget.getIntentExtras());
2245 return targetIntent;
Adam Powell24428412015-04-01 17:19:56 -07002246 }
2247
2248 @Override
2249 public ComponentName getResolvedComponentName() {
Adam Powell0ccc0e92015-04-23 17:19:37 -07002250 if (mSourceInfo != null) {
2251 return mSourceInfo.getResolvedComponentName();
2252 } else if (mBackupResolveInfo != null) {
2253 return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
2254 mBackupResolveInfo.activityInfo.name);
2255 }
2256 return null;
2257 }
2258
Adam Powell666d82a2015-07-15 20:14:57 -07002259 private Intent getBaseIntentToSend() {
Adam Powell52c39212016-04-07 15:14:18 -07002260 Intent result = getResolvedIntent();
Adam Powell2ed547e2015-04-29 18:45:04 -07002261 if (result == null) {
Adam Powell666d82a2015-07-15 20:14:57 -07002262 Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
Adam Powell13036be2015-05-12 14:43:56 -07002263 } else {
Adam Powell2ed547e2015-04-29 18:45:04 -07002264 result = new Intent(result);
Adam Powell13036be2015-05-12 14:43:56 -07002265 if (mFillInIntent != null) {
2266 result.fillIn(mFillInIntent, mFillInFlags);
2267 }
2268 result.fillIn(mReferrerFillInIntent, 0);
Adam Powell2ed547e2015-04-29 18:45:04 -07002269 }
2270 return result;
Adam Powell24428412015-04-01 17:19:56 -07002271 }
2272
2273 @Override
2274 public boolean start(Activity activity, Bundle options) {
Adam Powell666d82a2015-07-15 20:14:57 -07002275 throw new RuntimeException("ChooserTargets should be started as caller.");
Adam Powell24428412015-04-01 17:19:56 -07002276 }
2277
2278 @Override
Alison Cichowlas3e340502018-08-07 17:15:01 -04002279 public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
Adam Powell666d82a2015-07-15 20:14:57 -07002280 final Intent intent = getBaseIntentToSend();
Adam Powell2ed547e2015-04-29 18:45:04 -07002281 if (intent == null) {
2282 return false;
2283 }
Adam Powell666d82a2015-07-15 20:14:57 -07002284 intent.setComponent(mChooserTarget.getComponentName());
Makoto Onuki99302b52017-03-29 12:42:26 -07002285 intent.putExtras(mChooserTarget.getIntentExtras());
Adam Powell52c39212016-04-07 15:14:18 -07002286
2287 // Important: we will ignore the target security checks in ActivityManager
2288 // if and only if the ChooserTarget's target package is the same package
2289 // where we got the ChooserTargetService that provided it. This lets a
2290 // ChooserTargetService provide a non-exported or permission-guarded target
2291 // to the chooser for the user to pick.
2292 //
2293 // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
2294 // so we'll obey the caller's normal security checks.
2295 final boolean ignoreTargetSecurity = mSourceInfo != null
2296 && mSourceInfo.getResolvedComponentName().getPackageName()
2297 .equals(mChooserTarget.getComponentName().getPackageName());
Alison Cichowlas3e340502018-08-07 17:15:01 -04002298 return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
Adam Powell24428412015-04-01 17:19:56 -07002299 }
2300
2301 @Override
2302 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
Adam Powell666d82a2015-07-15 20:14:57 -07002303 throw new RuntimeException("ChooserTargets should be started as caller.");
Adam Powell24428412015-04-01 17:19:56 -07002304 }
2305
2306 @Override
2307 public ResolveInfo getResolveInfo() {
Adam Powell0ccc0e92015-04-23 17:19:37 -07002308 return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
Adam Powell24428412015-04-01 17:19:56 -07002309 }
2310
2311 @Override
2312 public CharSequence getDisplayLabel() {
Matt Pietal9d501432019-04-12 10:05:29 -04002313 return mDisplayLabel;
Adam Powell24428412015-04-01 17:19:56 -07002314 }
2315
2316 @Override
2317 public CharSequence getExtendedInfo() {
Adam Powell00f4aad2015-09-17 13:38:16 -07002318 // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
2319 return null;
Adam Powell24428412015-04-01 17:19:56 -07002320 }
2321
2322 @Override
2323 public Drawable getDisplayIcon() {
2324 return mDisplayIcon;
2325 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002326
George Hodulikf2b0d342019-01-25 12:43:54 -08002327 public ChooserTarget getChooserTarget() {
2328 return mChooserTarget;
2329 }
2330
Alan Viverettece5d92c2015-07-31 16:46:56 -04002331 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07002332 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002333 return new SelectableTargetInfo(this, fillInIntent, flags);
Adam Powell2ed547e2015-04-29 18:45:04 -07002334 }
2335
2336 @Override
2337 public List<Intent> getAllSourceIntents() {
2338 final List<Intent> results = new ArrayList<>();
2339 if (mSourceInfo != null) {
2340 // We only queried the service for the first one in our sourceinfo.
2341 results.add(mSourceInfo.getAllSourceIntents().get(0));
2342 }
2343 return results;
2344 }
Adam Powell24428412015-04-01 17:19:56 -07002345 }
2346
Matt Pietal5b648562019-03-12 07:40:26 -04002347 private void handleScroll(View view, int x, int y, int oldx, int oldy) {
2348 if (mChooserRowAdapter != null) {
2349 mChooserRowAdapter.handleScroll(view, y, oldy);
2350 }
2351 }
2352
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002353 /*
2354 * Need to dynamically adjust how many icons can fit per row before we add them,
2355 * which also means setting the correct offset to initially show the content
2356 * preview area + 2 rows of targets
2357 */
2358 private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
2359 int oldTop, int oldRight, int oldBottom) {
2360 if (mChooserRowAdapter == null || mAdapterView == null) {
2361 return;
2362 }
2363
Matt Pietalab73a882019-06-05 07:04:55 -04002364 final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
Matt Pietale7cacab2019-05-23 07:21:36 -04002365 if (mChooserRowAdapter.consumeLayoutRequest()
2366 || mChooserRowAdapter.calculateChooserTargetWidth(availableWidth)
Matt Pietalab73a882019-06-05 07:04:55 -04002367 || mAdapterView.getAdapter() == null
2368 || availableWidth != mCurrAvailableWidth) {
2369 mCurrAvailableWidth = availableWidth;
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002370 mAdapterView.setAdapter(mChooserRowAdapter);
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002371
2372 getMainThreadHandler().post(() -> {
2373 if (mResolverDrawerLayout == null || mChooserRowAdapter == null) {
2374 return;
2375 }
2376
Matt Pietal800136a2019-05-08 07:46:39 -04002377 final int bottomInset = mSystemWindowInsets != null
2378 ? mSystemWindowInsets.bottom : 0;
2379 int offset = bottomInset;
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002380 int rowsToShow = mChooserRowAdapter.getContentPreviewRowCount()
Matt Pietal74c6ed02019-04-18 13:38:46 -04002381 + mChooserRowAdapter.getProfileRowCount()
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002382 + mChooserRowAdapter.getServiceTargetRowCount()
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002383 + mChooserRowAdapter.getCallerAndRankedTargetRowCount();
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002384
2385 // then this is most likely not a SEND_* action, so check
2386 // the app target count
2387 if (rowsToShow == 0) {
2388 rowsToShow = mChooserRowAdapter.getCount();
2389 }
2390
2391 // still zero? then use a default height and leave, which
2392 // can happen when there are no targets to show
2393 if (rowsToShow == 0) {
Matt Pietal800136a2019-05-08 07:46:39 -04002394 offset += getResources().getDimensionPixelSize(
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002395 R.dimen.chooser_max_collapsed_height);
2396 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2397 return;
2398 }
2399
Matt Pietal394ebd02019-05-03 07:36:21 -04002400 int directShareHeight = 0;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002401 rowsToShow = Math.min(4, rowsToShow);
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002402 for (int i = 0; i < Math.min(rowsToShow, mAdapterView.getChildCount()); i++) {
Matt Pietal394ebd02019-05-03 07:36:21 -04002403 View child = mAdapterView.getChildAt(i);
2404 int height = child.getHeight();
2405 offset += height;
2406
2407 if (child.getTag() != null
2408 && (child.getTag() instanceof DirectShareViewHolder)) {
2409 directShareHeight = height;
2410 }
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002411 }
2412
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002413 boolean isExpandable = getResources().getConfiguration().orientation
2414 == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
2415 if (directShareHeight != 0 && isSendAction(getTargetIntent()) && isExpandable) {
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002416 // make sure to leave room for direct share 4->8 expansion
Matt Pietal394ebd02019-05-03 07:36:21 -04002417 int requiredExpansionHeight =
2418 (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal800136a2019-05-08 07:46:39 -04002419 int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
Matt Pietal394ebd02019-05-03 07:36:21 -04002420 int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
Matt Pietal800136a2019-05-08 07:46:39 -04002421 - requiredExpansionHeight - topInset - bottomInset;
Matt Pietal394ebd02019-05-03 07:36:21 -04002422
2423 offset = Math.min(offset, minHeight);
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002424 }
2425
Matt Pietal399e8c72019-04-04 15:49:48 -04002426 mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top));
Matt Pietalfe28f9a2019-03-22 07:59:58 -04002427 });
2428 }
2429 }
2430
Adam Powell24428412015-04-01 17:19:56 -07002431 public class ChooserListAdapter extends ResolveListAdapter {
Adam Powell7d758002015-05-06 17:49:36 -07002432 public static final int TARGET_BAD = -1;
2433 public static final int TARGET_CALLER = 0;
2434 public static final int TARGET_SERVICE = 1;
2435 public static final int TARGET_STANDARD = 2;
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002436 public static final int TARGET_STANDARD_AZ = 3;
Adam Powell7d758002015-05-06 17:49:36 -07002437
Matt Pietal5b648562019-03-12 07:40:26 -04002438 private static final int MAX_SUGGESTED_APP_TARGETS = 4;
Matt Pietal791b1c32019-04-30 13:36:49 -04002439 private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
Adam Powella182e452015-07-06 16:57:56 -07002440
Matt Pietal5b648562019-03-12 07:40:26 -04002441 private static final int MAX_SERVICE_TARGETS = 8;
2442
Matt Pietal3ed20a72019-06-24 12:14:52 -04002443 private final int mMaxShortcutTargetsPerApp =
2444 getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp);
2445
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002446 private int mNumShortcutResults = 0;
2447
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002448 // Reserve spots for incoming direct share targets by adding placeholders
2449 private ChooserTargetInfo mPlaceHolderTargetInfo = new PlaceHolderTargetInfo();
Matt Pietal5b648562019-03-12 07:40:26 -04002450 private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
Adam Powell7d758002015-05-06 17:49:36 -07002451 private final List<TargetInfo> mCallerTargets = new ArrayList<>();
Dan Sandlerf5e17692018-06-04 22:13:40 -04002452
Adam Powella182e452015-07-06 16:57:56 -07002453 private final BaseChooserTargetComparator mBaseTargetComparator
2454 = new BaseChooserTargetComparator();
2455
Adam Powell7d758002015-05-06 17:49:36 -07002456 public ChooserListAdapter(Context context, List<Intent> payloadIntents,
2457 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002458 boolean filterLastUsed, ResolverListController resolverListController) {
Adam Powell7d758002015-05-06 17:49:36 -07002459 // Don't send the initial intents through the shared ResolverActivity path,
2460 // we want to separate them into a different section.
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08002461 super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed,
2462 resolverListController);
Adam Powell0ccc0e92015-04-23 17:19:37 -07002463
Matt Pietal5b648562019-03-12 07:40:26 -04002464 createPlaceHolders();
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002465
Adam Powell7d758002015-05-06 17:49:36 -07002466 if (initialIntents != null) {
2467 final PackageManager pm = getPackageManager();
2468 for (int i = 0; i < initialIntents.length; i++) {
2469 final Intent ii = initialIntents[i];
2470 if (ii == null) {
2471 continue;
2472 }
Adam Powell86100d12016-05-12 16:13:17 -07002473
2474 // We reimplement Intent#resolveActivityInfo here because if we have an
2475 // implicit intent, we want the ResolveInfo returned by PackageManager
2476 // instead of one we reconstruct ourselves. The ResolveInfo returned might
2477 // have extra metadata and resolvePackageName set and we want to respect that.
2478 ResolveInfo ri = null;
2479 ActivityInfo ai = null;
2480 final ComponentName cn = ii.getComponent();
2481 if (cn != null) {
2482 try {
2483 ai = pm.getActivityInfo(ii.getComponent(), 0);
2484 ri = new ResolveInfo();
2485 ri.activityInfo = ai;
2486 } catch (PackageManager.NameNotFoundException ignored) {
2487 // ai will == null below
2488 }
2489 }
2490 if (ai == null) {
2491 ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
2492 ai = ri != null ? ri.activityInfo : null;
2493 }
Adam Powell7d758002015-05-06 17:49:36 -07002494 if (ai == null) {
2495 Log.w(TAG, "No activity found for " + ii);
2496 continue;
2497 }
Adam Powell7d758002015-05-06 17:49:36 -07002498 UserManager userManager =
2499 (UserManager) getSystemService(Context.USER_SERVICE);
Adam Powell7d758002015-05-06 17:49:36 -07002500 if (ii instanceof LabeledIntent) {
Matt Pietal26038402019-01-08 07:29:34 -05002501 LabeledIntent li = (LabeledIntent) ii;
Adam Powell7d758002015-05-06 17:49:36 -07002502 ri.resolvePackageName = li.getSourcePackage();
2503 ri.labelRes = li.getLabelResource();
2504 ri.nonLocalizedLabel = li.getNonLocalizedLabel();
2505 ri.icon = li.getIconResource();
Sudheer Shanka9ded7602015-05-19 21:17:25 +01002506 ri.iconResourceId = ri.icon;
2507 }
2508 if (userManager.isManagedProfile()) {
2509 ri.noResourceId = true;
2510 ri.icon = 0;
Adam Powell7d758002015-05-06 17:49:36 -07002511 }
Mike Digmanba232682019-03-27 14:55:26 -07002512 ResolveInfoPresentationGetter getter = makePresentationGetter(ri);
Adam Powell7d758002015-05-06 17:49:36 -07002513 mCallerTargets.add(new DisplayResolveInfo(ii, ri,
Mike Digmanba232682019-03-27 14:55:26 -07002514 getter.getLabel(), getter.getSubLabel(), ii));
Adam Powelld974c7b2015-04-28 15:41:46 -07002515 }
Adam Powell0ccc0e92015-04-23 17:19:37 -07002516 }
Adam Powell24428412015-04-01 17:19:56 -07002517 }
2518
Matt Pietalaf044ae2019-03-29 06:53:53 -04002519 @Override
Matt Pietalab73a882019-06-05 07:04:55 -04002520 public void handlePackagesChanged() {
2521 if (DEBUG) {
2522 Log.d(TAG, "clearing queryTargets on package change");
2523 }
2524 createPlaceHolders();
2525 mServicesRequested.clear();
2526 notifyDataSetChanged();
2527
2528 super.handlePackagesChanged();
2529 }
2530
2531 @Override
Matt Pietalaf044ae2019-03-29 06:53:53 -04002532 public void notifyDataSetChanged() {
2533 if (!mListViewDataChanged) {
Matt Pietalab73a882019-06-05 07:04:55 -04002534 mChooserHandler.sendEmptyMessageDelayed(ChooserHandler.LIST_VIEW_UPDATE_MESSAGE,
Matt Pietalaf044ae2019-03-29 06:53:53 -04002535 LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
2536 mListViewDataChanged = true;
2537 }
2538 }
2539
2540 private void refreshListView() {
2541 if (mListViewDataChanged) {
2542 super.notifyDataSetChanged();
2543 }
2544 mListViewDataChanged = false;
2545 }
2546
2547
Matt Pietal5b648562019-03-12 07:40:26 -04002548 private void createPlaceHolders() {
Matt Pietalab73a882019-06-05 07:04:55 -04002549 mNumShortcutResults = 0;
Matt Pietal5b648562019-03-12 07:40:26 -04002550 mServiceTargets.clear();
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002551 for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
Matt Pietal5b648562019-03-12 07:40:26 -04002552 mServiceTargets.add(mPlaceHolderTargetInfo);
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002553 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002554 }
2555
Adam Powell24428412015-04-01 17:19:56 -07002556 @Override
Adam Powell7d758002015-05-06 17:49:36 -07002557 public View onCreateView(ViewGroup parent) {
Adam Powell24428412015-04-01 17:19:56 -07002558 return mInflater.inflate(
2559 com.android.internal.R.layout.resolve_grid_item, parent, false);
2560 }
2561
2562 @Override
Mike Digmanbd7e0df2019-04-23 14:52:45 -07002563 protected void onBindView(View view, TargetInfo info) {
2564 super.onBindView(view, info);
2565
Mike Digman4b83c212019-05-03 10:17:35 -07002566 // If target is loading, show a special placeholder shape in the label, make unclickable
Mike Digmanbd7e0df2019-04-23 14:52:45 -07002567 final ViewHolder holder = (ViewHolder) view.getTag();
2568 if (info instanceof PlaceHolderTargetInfo) {
2569 final int maxWidth = getResources().getDimensionPixelSize(
2570 R.dimen.chooser_direct_share_label_placeholder_max_width);
2571 holder.text.setMaxWidth(maxWidth);
2572 holder.text.setBackground(getResources().getDrawable(
2573 R.drawable.chooser_direct_share_label_placeholder, getTheme()));
Mike Digman4b83c212019-05-03 10:17:35 -07002574 // Prevent rippling by removing background containing ripple
2575 holder.itemView.setBackground(null);
Mike Digmanbd7e0df2019-04-23 14:52:45 -07002576 } else {
2577 holder.text.setMaxWidth(Integer.MAX_VALUE);
2578 holder.text.setBackground(null);
Mike Digman4b83c212019-05-03 10:17:35 -07002579 holder.itemView.setBackground(holder.defaultItemViewBackground);
Mike Digmanbd7e0df2019-04-23 14:52:45 -07002580 }
2581 }
2582
2583 @Override
Adam Powell24428412015-04-01 17:19:56 -07002584 public void onListRebuilt() {
Matt Pietal030bd842019-05-29 07:14:14 -04002585 updateAlphabeticalList();
2586
Ng Zhi And3ec5fc2018-05-10 09:13:00 -07002587 // don't support direct share on low ram devices
2588 if (ActivityManager.isLowRamDeviceStatic()) {
2589 return;
2590 }
2591
George Hodulik145b3a52019-03-27 11:18:43 -07002592 if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
2593 || USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002594 if (DEBUG) {
2595 Log.d(TAG, "querying direct share targets from ShortcutManager");
2596 }
Matt Pietalaf044ae2019-03-29 06:53:53 -04002597
George Hodulik3f399f22019-04-26 16:17:54 -07002598 queryDirectShareTargets(this, false);
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -08002599 }
2600 if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
2601 if (DEBUG) {
2602 Log.d(TAG, "List built querying services");
2603 }
Matt Pietalaf044ae2019-03-29 06:53:53 -04002604
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08002605 queryTargetServices(this);
2606 }
Adam Powell24428412015-04-01 17:19:56 -07002607 }
2608
2609 @Override
Adam Powellc6d5e3a2015-04-23 12:22:20 -07002610 public boolean shouldGetResolvedFilter() {
2611 return true;
2612 }
2613
2614 @Override
Adam Powell24428412015-04-01 17:19:56 -07002615 public int getCount() {
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002616 return getRankedTargetCount() + getAlphaTargetCount()
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002617 + getSelectableServiceTargetCount() + getCallerTargetCount();
Adam Powell24428412015-04-01 17:19:56 -07002618 }
2619
Adam Powell50077352015-05-26 18:01:55 -07002620 @Override
2621 public int getUnfilteredCount() {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002622 int appTargets = super.getUnfilteredCount();
Alison Cichowlas13314612019-04-11 15:20:39 -04002623 if (appTargets > getMaxRankedTargets()) {
2624 appTargets = appTargets + getMaxRankedTargets();
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002625 }
2626 return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
Adam Powell50077352015-05-26 18:01:55 -07002627 }
2628
Alison Cichowlas13314612019-04-11 15:20:39 -04002629
Adam Powell50077352015-05-26 18:01:55 -07002630 public int getCallerTargetCount() {
Matt Pietal5b648562019-03-12 07:40:26 -04002631 return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS);
Adam Powell7d758002015-05-06 17:49:36 -07002632 }
2633
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002634 /**
2635 * Filter out placeholders and non-selectable service targets
2636 */
2637 public int getSelectableServiceTargetCount() {
2638 int count = 0;
2639 for (ChooserTargetInfo info : mServiceTargets) {
2640 if (info instanceof SelectableTargetInfo) {
2641 count++;
2642 }
Adam Powell565943f2016-02-11 10:29:05 -08002643 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002644 return count;
2645 }
2646
2647 public int getServiceTargetCount() {
Matt Pietal6e88b512019-06-10 10:20:15 -04002648 if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
Matt Pietal95574b02019-03-13 08:12:25 -04002649 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
2650 }
2651
2652 return 0;
Adam Powell7d758002015-05-06 17:49:36 -07002653 }
2654
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002655 int getAlphaTargetCount() {
2656 int standardCount = super.getCount();
Alison Cichowlas13314612019-04-11 15:20:39 -04002657 return standardCount > getMaxRankedTargets() ? standardCount : 0;
Adam Powell7d758002015-05-06 17:49:36 -07002658 }
2659
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002660 int getRankedTargetCount() {
Alison Cichowlas13314612019-04-11 15:20:39 -04002661 int spacesAvailable = getMaxRankedTargets() - getCallerTargetCount();
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002662 return Math.min(spacesAvailable, super.getCount());
2663 }
2664
Alison Cichowlas13314612019-04-11 15:20:39 -04002665 private int getMaxRankedTargets() {
2666 return mChooserRowAdapter == null ? 4 : mChooserRowAdapter.getMaxTargetsPerRow();
2667 }
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002668
Adam Powell7d758002015-05-06 17:49:36 -07002669 public int getPositionTargetType(int position) {
2670 int offset = 0;
2671
Adam Powella182e452015-07-06 16:57:56 -07002672 final int serviceTargetCount = getServiceTargetCount();
Matt Pietal5b648562019-03-12 07:40:26 -04002673 if (position < serviceTargetCount) {
Adam Powell7d758002015-05-06 17:49:36 -07002674 return TARGET_SERVICE;
2675 }
2676 offset += serviceTargetCount;
2677
Matt Pietal5b648562019-03-12 07:40:26 -04002678 final int callerTargetCount = getCallerTargetCount();
2679 if (position - offset < callerTargetCount) {
2680 return TARGET_CALLER;
2681 }
2682 offset += callerTargetCount;
2683
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002684 final int rankedTargetCount = getRankedTargetCount();
2685 if (position - offset < rankedTargetCount) {
Adam Powell7d758002015-05-06 17:49:36 -07002686 return TARGET_STANDARD;
2687 }
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002688 offset += rankedTargetCount;
2689
2690 final int standardTargetCount = getAlphaTargetCount();
2691 if (position - offset < standardTargetCount) {
2692 return TARGET_STANDARD_AZ;
2693 }
Adam Powell7d758002015-05-06 17:49:36 -07002694
2695 return TARGET_BAD;
2696 }
2697
Adam Powell24428412015-04-01 17:19:56 -07002698 @Override
2699 public TargetInfo getItem(int position) {
Adam Powell50077352015-05-26 18:01:55 -07002700 return targetInfoForPosition(position, true);
2701 }
2702
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002703
2704 /**
2705 * Find target info for a given position.
2706 * Since ChooserActivity displays several sections of content, determine which
2707 * section provides this item.
2708 */
Adam Powell50077352015-05-26 18:01:55 -07002709 @Override
2710 public TargetInfo targetInfoForPosition(int position, boolean filtered) {
Adam Powell24428412015-04-01 17:19:56 -07002711 int offset = 0;
Adam Powell0ccc0e92015-04-23 17:19:37 -07002712
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002713 // Direct share targets
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002714 final int serviceTargetCount = filtered ? getServiceTargetCount() :
2715 getSelectableServiceTargetCount();
Matt Pietal5b648562019-03-12 07:40:26 -04002716 if (position < serviceTargetCount) {
2717 return mServiceTargets.get(position);
Adam Powell0ccc0e92015-04-23 17:19:37 -07002718 }
2719 offset += serviceTargetCount;
2720
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002721 // Targets provided by calling app
Matt Pietal5b648562019-03-12 07:40:26 -04002722 final int callerTargetCount = getCallerTargetCount();
2723 if (position - offset < callerTargetCount) {
2724 return mCallerTargets.get(position - offset);
2725 }
2726 offset += callerTargetCount;
2727
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002728 // Ranked standard app targets
2729 final int rankedTargetCount = getRankedTargetCount();
2730 if (position - offset < rankedTargetCount) {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002731 return filtered ? super.getItem(position - offset)
2732 : getDisplayResolveInfo(position - offset);
2733 }
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002734 offset += rankedTargetCount;
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002735
2736 // Alphabetical complete app target list.
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04002737 if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) {
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002738 return mSortedList.get(position - offset);
2739 }
2740
2741 return null;
Adam Powell24428412015-04-01 17:19:56 -07002742 }
2743
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002744
Matt Pietalfbfa0492019-04-01 11:29:56 -04002745 /**
2746 * Evaluate targets for inclusion in the direct share area. May not be included
2747 * if score is too low.
2748 */
2749 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07002750 @ShareTargetType int targetType) {
Matt Pietal26038402019-01-08 07:29:34 -05002751 if (DEBUG) {
2752 Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
2753 + " targets");
2754 }
Dan Sandlerf5e17692018-06-04 22:13:40 -04002755
Matt Pietalfbfa0492019-04-01 11:29:56 -04002756 if (targets.size() == 0) {
2757 return;
2758 }
2759
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07002760 final float baseScore = getBaseScore(origTarget, targetType);
Adam Powella182e452015-07-06 16:57:56 -07002761 Collections.sort(targets, mBaseTargetComparator);
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002762
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07002763 final boolean isShortcutResult =
2764 (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
2765 || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
Matt Pietal3ed20a72019-06-24 12:14:52 -04002766 final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
Matt Pietal791b1c32019-04-30 13:36:49 -04002767 : MAX_CHOOSER_TARGETS_PER_APP;
Adam Powella182e452015-07-06 16:57:56 -07002768 float lastScore = 0;
Matt Pietalfbfa0492019-04-01 11:29:56 -04002769 boolean shouldNotify = false;
Matt Pietal791b1c32019-04-30 13:36:49 -04002770 for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
Adam Powella182e452015-07-06 16:57:56 -07002771 final ChooserTarget target = targets.get(i);
2772 float targetScore = target.getScore();
Matt Pietalfbfa0492019-04-01 11:29:56 -04002773 targetScore *= baseScore;
Adam Powella182e452015-07-06 16:57:56 -07002774 if (i > 0 && targetScore >= lastScore) {
2775 // Apply a decay so that the top app can't crowd out everything else.
2776 // This incents ChooserTargetServices to define what's truly better.
2777 targetScore = lastScore * 0.95f;
2778 }
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002779 boolean isInserted = insertServiceTarget(
Matt Pietalfbfa0492019-04-01 11:29:56 -04002780 new SelectableTargetInfo(origTarget, target, targetScore));
Adam Powella182e452015-07-06 16:57:56 -07002781
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002782 if (isInserted && isShortcutResult) {
2783 mNumShortcutResults++;
2784 }
2785
2786 shouldNotify |= isInserted;
2787
Adam Powella182e452015-07-06 16:57:56 -07002788 if (DEBUG) {
2789 Log.d(TAG, " => " + target.toString() + " score=" + targetScore
2790 + " base=" + target.getScore()
2791 + " lastScore=" + lastScore
Matt Pietalfbfa0492019-04-01 11:29:56 -04002792 + " baseScore=" + baseScore);
Adam Powella182e452015-07-06 16:57:56 -07002793 }
2794
2795 lastScore = targetScore;
Adam Powell24428412015-04-01 17:19:56 -07002796 }
2797
Matt Pietalfbfa0492019-04-01 11:29:56 -04002798 if (shouldNotify) {
2799 notifyDataSetChanged();
2800 }
2801 }
Adam Powell24428412015-04-01 17:19:56 -07002802
Matt Pietale54dcc2e2019-05-02 12:59:38 -04002803 private int getNumShortcutResults() {
2804 return mNumShortcutResults;
2805 }
2806
Matt Pietalfbfa0492019-04-01 11:29:56 -04002807 /**
Matt Pietal39d181d2019-05-08 14:48:30 -04002808 * Use the scoring system along with artificial boosts to create up to 4 distinct buckets:
Matt Pietalfbfa0492019-04-01 11:29:56 -04002809 * <ol>
2810 * <li>App-supplied targets
Matt Pietal39d181d2019-05-08 14:48:30 -04002811 * <li>Shortcuts ranked via App Prediction Manager
2812 * <li>Shortcuts ranked via legacy heuristics
Matt Pietalfbfa0492019-04-01 11:29:56 -04002813 * <li>Legacy direct share targets
2814 * </ol>
2815 */
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07002816 public float getBaseScore(DisplayResolveInfo target, @ShareTargetType int targetType) {
Matt Pietalfbfa0492019-04-01 11:29:56 -04002817 if (target == null) {
2818 return CALLER_TARGET_SCORE_BOOST;
2819 }
2820
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07002821 if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
Matt Pietalfbfa0492019-04-01 11:29:56 -04002822 return SHORTCUT_TARGET_SCORE_BOOST;
2823 }
2824
2825 float score = super.getScore(target);
Mehdi Alizadeh06955f62019-09-11 17:23:10 -07002826 if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
Matt Pietalfbfa0492019-04-01 11:29:56 -04002827 return score * SHORTCUT_TARGET_SCORE_BOOST;
2828 }
2829
2830 return score;
Adam Powell24428412015-04-01 17:19:56 -07002831 }
2832
Adam Powell565943f2016-02-11 10:29:05 -08002833 /**
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002834 * Calling this marks service target loading complete, and will attempt to no longer
2835 * update the direct share area.
2836 */
2837 public void completeServiceTargetLoading() {
2838 mServiceTargets.removeIf(o -> o instanceof PlaceHolderTargetInfo);
2839
2840 if (mServiceTargets.isEmpty()) {
2841 mServiceTargets.add(new EmptyTargetInfo());
2842 }
2843 notifyDataSetChanged();
2844 }
2845
Matt Pietalfbfa0492019-04-01 11:29:56 -04002846 private boolean insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002847 // Avoid inserting any potentially late results
2848 if (mServiceTargets.size() == 1
2849 && mServiceTargets.get(0) instanceof EmptyTargetInfo) {
Matt Pietalfbfa0492019-04-01 11:29:56 -04002850 return false;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002851 }
2852
Matt Pietal9d501432019-04-12 10:05:29 -04002853 // Check for duplicates and abort if found
2854 for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
2855 if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
2856 return false;
2857 }
2858 }
2859
Matt Pietalfbfa0492019-04-01 11:29:56 -04002860 int currentSize = mServiceTargets.size();
Matt Pietal9d501432019-04-12 10:05:29 -04002861 final float newScore = chooserTargetInfo.getModifiedScore();
Matt Pietalfbfa0492019-04-01 11:29:56 -04002862 for (int i = 0; i < Math.min(currentSize, MAX_SERVICE_TARGETS); i++) {
Adam Powella182e452015-07-06 16:57:56 -07002863 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002864 if (serviceTarget == null) {
2865 mServiceTargets.set(i, chooserTargetInfo);
Matt Pietalfbfa0492019-04-01 11:29:56 -04002866 return true;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002867 } else if (newScore > serviceTarget.getModifiedScore()) {
Adam Powella182e452015-07-06 16:57:56 -07002868 mServiceTargets.add(i, chooserTargetInfo);
Matt Pietalfbfa0492019-04-01 11:29:56 -04002869 return true;
Adam Powella182e452015-07-06 16:57:56 -07002870 }
2871 }
Matt Pietalfbfa0492019-04-01 11:29:56 -04002872
2873 if (currentSize < MAX_SERVICE_TARGETS) {
2874 mServiceTargets.add(chooserTargetInfo);
2875 return true;
2876 }
2877
2878 return false;
Adam Powella182e452015-07-06 16:57:56 -07002879 }
Adam Powell24428412015-04-01 17:19:56 -07002880 }
2881
Adam Powella182e452015-07-06 16:57:56 -07002882 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
2883 @Override
2884 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
2885 // Descending order
Adam Powell77a533f2015-10-16 10:47:32 -07002886 return (int) Math.signum(rhs.getScore() - lhs.getScore());
Adam Powella182e452015-07-06 16:57:56 -07002887 }
2888 }
2889
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04002890
Matt Pietal95574b02019-03-13 08:12:25 -04002891 private boolean isSendAction(Intent targetIntent) {
2892 if (targetIntent == null) {
2893 return false;
2894 }
2895
2896 String action = targetIntent.getAction();
2897 if (action == null) {
2898 return false;
2899 }
2900
2901 if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
2902 return true;
2903 }
2904
2905 return false;
2906 }
2907
Adam Powell7d758002015-05-06 17:49:36 -07002908 class ChooserRowAdapter extends BaseAdapter {
2909 private ChooserListAdapter mChooserListAdapter;
2910 private final LayoutInflater mLayoutInflater;
Adam Powell7d758002015-05-06 17:49:36 -07002911
Matt Pietal5b648562019-03-12 07:40:26 -04002912 private DirectShareViewHolder mDirectShareViewHolder;
Matt Pietalab986b52019-04-10 10:14:32 -04002913 private int mChooserTargetWidth = 0;
Mike Digman849a9d12019-04-29 11:20:48 -07002914 private boolean mShowAzLabelIfPoss;
Matt Pietal5b648562019-03-12 07:40:26 -04002915
Matt Pietale7cacab2019-05-23 07:21:36 -04002916 private boolean mHideContentPreview = false;
2917 private boolean mLayoutRequested = false;
2918
Matt Pietal5b648562019-03-12 07:40:26 -04002919 private static final int VIEW_TYPE_DIRECT_SHARE = 0;
2920 private static final int VIEW_TYPE_NORMAL = 1;
Matt Pietal1ef88002019-03-13 10:43:18 -04002921 private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
Matt Pietal74c6ed02019-04-18 13:38:46 -04002922 private static final int VIEW_TYPE_PROFILE = 3;
Mike Digmanae730b12019-04-25 11:10:31 -07002923 private static final int VIEW_TYPE_AZ_LABEL = 4;
Matt Pietal5b648562019-03-12 07:40:26 -04002924
Matt Pietalfaedea82019-03-21 10:36:54 -04002925 private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
2926 private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
2927
Mike Digman849a9d12019-04-29 11:20:48 -07002928 private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
2929
Adam Powell7d758002015-05-06 17:49:36 -07002930 public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
2931 mChooserListAdapter = wrappedAdapter;
2932 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
2933
Mike Digman849a9d12019-04-29 11:20:48 -07002934 mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
2935
Adam Powell7d758002015-05-06 17:49:36 -07002936 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
2937 @Override
2938 public void onChanged() {
2939 super.onChanged();
2940 notifyDataSetChanged();
2941 }
2942
2943 @Override
2944 public void onInvalidated() {
2945 super.onInvalidated();
2946 notifyDataSetInvalidated();
2947 }
2948 });
2949 }
2950
Matt Pietalfaedea82019-03-21 10:36:54 -04002951 /**
Matt Pietalab986b52019-04-10 10:14:32 -04002952 * Calculate the chooser target width to maximize space per item
Matt Pietalfaedea82019-03-21 10:36:54 -04002953 *
2954 * @param width The new row width to use for recalculation
Matt Pietalab986b52019-04-10 10:14:32 -04002955 * @return true if the view width has changed
Matt Pietalfaedea82019-03-21 10:36:54 -04002956 */
Matt Pietalab986b52019-04-10 10:14:32 -04002957 public boolean calculateChooserTargetWidth(int width) {
Matt Pietalab986b52019-04-10 10:14:32 -04002958 if (width == 0) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002959 return false;
2960 }
2961
Matt Pietal9d501432019-04-12 10:05:29 -04002962 int newWidth = width / getMaxTargetsPerRow();
Matt Pietalab986b52019-04-10 10:14:32 -04002963 if (newWidth != mChooserTargetWidth) {
2964 mChooserTargetWidth = newWidth;
Matt Pietalfaedea82019-03-21 10:36:54 -04002965 return true;
2966 }
2967
2968 return false;
2969 }
2970
Matt Pietal5b648562019-03-12 07:40:26 -04002971 private int getMaxTargetsPerRow() {
Matt Pietalfaedea82019-03-21 10:36:54 -04002972 int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
Matt Pietal3e4b56f2019-05-31 12:06:17 -04002973 if (shouldDisplayLandscape(getResources().getConfiguration().orientation)) {
Matt Pietalfaedea82019-03-21 10:36:54 -04002974 maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
2975 }
2976
Matt Pietalab986b52019-04-10 10:14:32 -04002977 return maxTargets;
Matt Pietal5b648562019-03-12 07:40:26 -04002978 }
2979
Matt Pietale7cacab2019-05-23 07:21:36 -04002980 public void hideContentPreview() {
2981 mHideContentPreview = true;
2982 mLayoutRequested = true;
2983 notifyDataSetChanged();
2984 }
2985
2986 public boolean consumeLayoutRequest() {
2987 boolean oldValue = mLayoutRequested;
2988 mLayoutRequested = false;
2989 return oldValue;
2990 }
2991
Adam Powell7d758002015-05-06 17:49:36 -07002992 @Override
Matt Pietal8a8cfc42019-04-30 07:59:14 -04002993 public boolean areAllItemsEnabled() {
2994 return false;
2995 }
2996
2997 @Override
2998 public boolean isEnabled(int position) {
2999 int viewType = getItemViewType(position);
Matt Pietal9462c0b2019-05-16 09:26:33 -04003000 if (viewType == VIEW_TYPE_CONTENT_PREVIEW || viewType == VIEW_TYPE_AZ_LABEL) {
Matt Pietal8a8cfc42019-04-30 07:59:14 -04003001 return false;
3002 }
3003 return true;
3004 }
3005
3006 @Override
Adam Powell7d758002015-05-06 17:49:36 -07003007 public int getCount() {
3008 return (int) (
Matt Pietal1ef88002019-03-13 10:43:18 -04003009 getContentPreviewRowCount()
Matt Pietal74c6ed02019-04-18 13:38:46 -04003010 + getProfileRowCount()
Matt Pietal26038402019-01-08 07:29:34 -05003011 + getServiceTargetRowCount()
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04003012 + getCallerAndRankedTargetRowCount()
Mike Digmanae730b12019-04-25 11:10:31 -07003013 + getAzLabelRowCount()
Alison Cichowlas1c8816c2019-04-03 17:43:22 -04003014 + Math.ceil(
3015 (float) mChooserListAdapter.getAlphaTargetCount()
3016 / getMaxTargetsPerRow())
Adam Powell7d758002015-05-06 17:49:36 -07003017 );
3018 }
3019
Matt Pietal1ef88002019-03-13 10:43:18 -04003020 public int getContentPreviewRowCount() {
3021 if (!isSendAction(getTargetIntent())) {
3022 return 0;
3023 }
3024
Matt Pietale7cacab2019-05-23 07:21:36 -04003025 if (mHideContentPreview || mChooserListAdapter == null
3026 || mChooserListAdapter.getCount() == 0) {
Matt Pietal1ef88002019-03-13 10:43:18 -04003027 return 0;
3028 }
3029
3030 return 1;
3031 }
3032
Matt Pietal74c6ed02019-04-18 13:38:46 -04003033 public int getProfileRowCount() {
3034 return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
3035 }
3036
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04003037 public int getCallerAndRankedTargetRowCount() {
Adam Powell63b31692015-09-28 10:45:00 -07003038 return (int) Math.ceil(
Alison Cichowlasd0a075b2019-04-10 20:18:59 -04003039 ((float) mChooserListAdapter.getCallerTargetCount()
3040 + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow());
Adam Powell63b31692015-09-28 10:45:00 -07003041 }
3042
Matt Pietal5b648562019-03-12 07:40:26 -04003043 // There can be at most one row in the listview, that is internally
3044 // a ViewGroup with 2 rows
Adam Powell63b31692015-09-28 10:45:00 -07003045 public int getServiceTargetRowCount() {
Matt Pietal030bd842019-05-29 07:14:14 -04003046 if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
Matt Pietal95574b02019-03-13 08:12:25 -04003047 return 1;
3048 }
3049 return 0;
Adam Powell63b31692015-09-28 10:45:00 -07003050 }
3051
Mike Digmanae730b12019-04-25 11:10:31 -07003052 public int getAzLabelRowCount() {
3053 // Only show a label if the a-z list is showing
Mike Digman849a9d12019-04-29 11:20:48 -07003054 return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
Mike Digmanae730b12019-04-25 11:10:31 -07003055 }
3056
Adam Powell7d758002015-05-06 17:49:36 -07003057 @Override
3058 public Object getItem(int position) {
3059 // We have nothing useful to return here.
3060 return position;
3061 }
3062
3063 @Override
3064 public long getItemId(int position) {
3065 return position;
3066 }
3067
3068 @Override
3069 public View getView(int position, View convertView, ViewGroup parent) {
Adam Powell63b31692015-09-28 10:45:00 -07003070 final RowViewHolder holder;
Matt Pietal5b648562019-03-12 07:40:26 -04003071 int viewType = getItemViewType(position);
3072
Matt Pietal1ef88002019-03-13 10:43:18 -04003073 if (viewType == VIEW_TYPE_CONTENT_PREVIEW) {
3074 return createContentPreviewView(convertView, parent);
3075 }
3076
Matt Pietal74c6ed02019-04-18 13:38:46 -04003077 if (viewType == VIEW_TYPE_PROFILE) {
3078 return createProfileView(convertView, parent);
3079 }
3080
Mike Digmanae730b12019-04-25 11:10:31 -07003081 if (viewType == VIEW_TYPE_AZ_LABEL) {
3082 return createAzLabelView(parent);
3083 }
3084
Adam Powell7d758002015-05-06 17:49:36 -07003085 if (convertView == null) {
Matt Pietal5b648562019-03-12 07:40:26 -04003086 holder = createViewHolder(viewType, parent);
Adam Powell7d758002015-05-06 17:49:36 -07003087 } else {
Adam Powell63b31692015-09-28 10:45:00 -07003088 holder = (RowViewHolder) convertView.getTag();
Adam Powell7d758002015-05-06 17:49:36 -07003089 }
Adam Powell7d758002015-05-06 17:49:36 -07003090
Matt Pietalfaedea82019-03-21 10:36:54 -04003091 bindViewHolder(position, holder);
Matt Pietal5b648562019-03-12 07:40:26 -04003092
3093 return holder.getViewGroup();
Adam Powell7d758002015-05-06 17:49:36 -07003094 }
3095
Matt Pietal5b648562019-03-12 07:40:26 -04003096 @Override
3097 public int getItemViewType(int position) {
Mike Digmanae730b12019-04-25 11:10:31 -07003098 int count;
Matt Pietal1ef88002019-03-13 10:43:18 -04003099
Mike Digmanae730b12019-04-25 11:10:31 -07003100 int countSum = (count = getContentPreviewRowCount());
3101 if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
Matt Pietal74c6ed02019-04-18 13:38:46 -04003102
Mike Digmanae730b12019-04-25 11:10:31 -07003103 countSum += (count = getProfileRowCount());
3104 if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
Adam Powell63b31692015-09-28 10:45:00 -07003105
Mike Digmanae730b12019-04-25 11:10:31 -07003106 countSum += (count = getServiceTargetRowCount());
3107 if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
3108
3109 countSum += (count = getCallerAndRankedTargetRowCount());
3110 if (count > 0 && position < countSum) return VIEW_TYPE_NORMAL;
3111
3112 countSum += (count = getAzLabelRowCount());
3113 if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
Matt Pietal5b648562019-03-12 07:40:26 -04003114
3115 return VIEW_TYPE_NORMAL;
3116 }
3117
3118 @Override
3119 public int getViewTypeCount() {
Mike Digmanae730b12019-04-25 11:10:31 -07003120 return 5;
Matt Pietal1ef88002019-03-13 10:43:18 -04003121 }
3122
3123 private ViewGroup createContentPreviewView(View convertView, ViewGroup parent) {
3124 Intent targetIntent = getTargetIntent();
3125 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
3126
3127 if (convertView == null) {
3128 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
3129 .setSubtype(previewType));
3130 }
3131
3132 return displayContentPreview(previewType, targetIntent, mLayoutInflater,
3133 (ViewGroup) convertView, parent);
Matt Pietal5b648562019-03-12 07:40:26 -04003134 }
3135
Matt Pietal74c6ed02019-04-18 13:38:46 -04003136 private View createProfileView(View convertView, ViewGroup parent) {
3137 View profileRow = convertView != null ? convertView : mLayoutInflater.inflate(
3138 R.layout.chooser_profile_row, parent, false);
3139 profileRow.setBackground(
3140 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
3141 mProfileView = profileRow.findViewById(R.id.profile_button);
3142 mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
3143 bindProfileView();
3144 return profileRow;
3145 }
3146
Mike Digmanae730b12019-04-25 11:10:31 -07003147 private View createAzLabelView(ViewGroup parent) {
3148 return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
3149 }
3150
Matt Pietal5b648562019-03-12 07:40:26 -04003151 private RowViewHolder loadViewsIntoRow(RowViewHolder holder) {
3152 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietalab986b52019-04-10 10:14:32 -04003153 final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
3154 MeasureSpec.EXACTLY);
Matt Pietal5b648562019-03-12 07:40:26 -04003155 int columnCount = holder.getColumnCount();
3156
Mike Digmanba232682019-03-27 14:55:26 -07003157 final boolean isDirectShare = holder instanceof DirectShareViewHolder;
3158
Matt Pietal5b648562019-03-12 07:40:26 -04003159 for (int i = 0; i < columnCount; i++) {
Mike Digmanba232682019-03-27 14:55:26 -07003160 final View v = mChooserListAdapter.createView(holder.getRowByIndex(i));
Adam Powell4eb98712015-10-14 13:10:18 -07003161 final int column = i;
Adam Powell63b31692015-09-28 10:45:00 -07003162 v.setOnClickListener(new OnClickListener() {
3163 @Override
3164 public void onClick(View v) {
Matt Pietal5b648562019-03-12 07:40:26 -04003165 startSelected(holder.getItemIndex(column), false, true);
Adam Powell63b31692015-09-28 10:45:00 -07003166 }
3167 });
3168 v.setOnLongClickListener(new OnLongClickListener() {
3169 @Override
3170 public boolean onLongClick(View v) {
Adam Powell23882512016-01-29 10:21:00 -08003171 showTargetDetails(
Adam Powell4eb98712015-10-14 13:10:18 -07003172 mChooserListAdapter.resolveInfoForPosition(
Matt Pietal5b648562019-03-12 07:40:26 -04003173 holder.getItemIndex(column), true));
Adam Powell63b31692015-09-28 10:45:00 -07003174 return true;
3175 }
3176 });
Matt Pietal5b648562019-03-12 07:40:26 -04003177 ViewGroup row = holder.addView(i, v);
Adam Powell63b31692015-09-28 10:45:00 -07003178
Mike Digmanba232682019-03-27 14:55:26 -07003179 // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
3180 // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be
3181 // done before measuring.
3182 if (isDirectShare) {
3183 final ViewHolder vh = (ViewHolder) v.getTag();
3184 vh.text.setLines(2);
3185 vh.text.setHorizontallyScrolling(false);
3186 vh.text2.setVisibility(View.GONE);
Adam Powell63b31692015-09-28 10:45:00 -07003187 }
Mike Digmanba232682019-03-27 14:55:26 -07003188
3189 // Force height to be a given so we don't have visual disruption during scaling.
Matt Pietalab986b52019-04-10 10:14:32 -04003190 v.measure(exactSpec, spec);
3191 setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight());
Adam Powell63b31692015-09-28 10:45:00 -07003192 }
3193
Matt Pietal5b648562019-03-12 07:40:26 -04003194 final ViewGroup viewGroup = holder.getViewGroup();
3195
Mike Digmanba232682019-03-27 14:55:26 -07003196 // Pre-measure and fix height so we can scale later.
Adam Powell63b31692015-09-28 10:45:00 -07003197 holder.measure();
Matt Pietalab986b52019-04-10 10:14:32 -04003198 setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight());
Mike Digmanba232682019-03-27 14:55:26 -07003199
3200 if (isDirectShare) {
3201 DirectShareViewHolder dsvh = (DirectShareViewHolder) holder;
Matt Pietalab986b52019-04-10 10:14:32 -04003202 setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
3203 setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
Adam Powell7d758002015-05-06 17:49:36 -07003204 }
Matt Pietal5b648562019-03-12 07:40:26 -04003205
3206 viewGroup.setTag(holder);
3207
Adam Powell7d758002015-05-06 17:49:36 -07003208 return holder;
3209 }
3210
Matt Pietalab986b52019-04-10 10:14:32 -04003211 private void setViewBounds(View view, int widthPx, int heightPx) {
Mike Digmanba232682019-03-27 14:55:26 -07003212 LayoutParams lp = view.getLayoutParams();
3213 if (lp == null) {
Matt Pietalab986b52019-04-10 10:14:32 -04003214 lp = new LayoutParams(widthPx, heightPx);
Mike Digmanba232682019-03-27 14:55:26 -07003215 view.setLayoutParams(lp);
3216 } else {
3217 lp.height = heightPx;
Matt Pietalab986b52019-04-10 10:14:32 -04003218 lp.width = widthPx;
Mike Digmanba232682019-03-27 14:55:26 -07003219 }
3220 }
3221
Matt Pietal5b648562019-03-12 07:40:26 -04003222 RowViewHolder createViewHolder(int viewType, ViewGroup parent) {
3223 if (viewType == VIEW_TYPE_DIRECT_SHARE) {
3224 ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate(
3225 R.layout.chooser_row_direct_share, parent, false);
3226 ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3227 parentGroup, false);
3228 ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3229 parentGroup, false);
3230 parentGroup.addView(row1);
3231 parentGroup.addView(row2);
3232
3233 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
3234 Lists.newArrayList(row1, row2), getMaxTargetsPerRow());
3235 loadViewsIntoRow(mDirectShareViewHolder);
3236
3237 return mDirectShareViewHolder;
3238 } else {
3239 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
3240 false);
3241 RowViewHolder holder = new SingleRowViewHolder(row, getMaxTargetsPerRow());
3242 loadViewsIntoRow(holder);
3243
3244 return holder;
3245 }
3246 }
3247
Matt Pietaldadc0d12019-04-16 12:53:28 -04003248 /**
Mike Digmanae730b12019-04-25 11:10:31 -07003249 * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
3250 * showing on top of the AZ list if the AZ label is visible. All other types are placed into
3251 * their own row as determined by their target type, and dividers are added in the list to
3252 * separate each type.
Matt Pietaldadc0d12019-04-16 12:53:28 -04003253 */
3254 int getRowType(int rowPosition) {
Mike Digmanae730b12019-04-25 11:10:31 -07003255 // Merge caller and ranked standard into a single row
Matt Pietaldadc0d12019-04-16 12:53:28 -04003256 int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
3257 if (positionType == ChooserListAdapter.TARGET_CALLER) {
3258 return ChooserListAdapter.TARGET_STANDARD;
3259 }
3260
Mike Digmanae730b12019-04-25 11:10:31 -07003261 // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
3262 // row type the same as the suggestion row type
3263 if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
3264 return ChooserListAdapter.TARGET_STANDARD;
3265 }
3266
Matt Pietaldadc0d12019-04-16 12:53:28 -04003267 return positionType;
3268 }
3269
Matt Pietalfaedea82019-03-21 10:36:54 -04003270 void bindViewHolder(int rowPosition, RowViewHolder holder) {
Adam Powell7d758002015-05-06 17:49:36 -07003271 final int start = getFirstRowPosition(rowPosition);
Matt Pietaldadc0d12019-04-16 12:53:28 -04003272 final int startType = getRowType(start);
3273 final int lastStartType = getRowType(getFirstRowPosition(rowPosition - 1));
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003274
Matt Pietal5b648562019-03-12 07:40:26 -04003275 final ViewGroup row = holder.getViewGroup();
3276
Matt Pietal74c6ed02019-04-18 13:38:46 -04003277 if (startType != lastStartType
3278 || rowPosition == getContentPreviewRowCount() + getProfileRowCount()) {
Matt Pietal84a55bd2019-05-01 12:32:47 -04003279 row.setForeground(
Matt Pietal74c6ed02019-04-18 13:38:46 -04003280 getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003281 } else {
Matt Pietal84a55bd2019-05-01 12:32:47 -04003282 row.setForeground(null);
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003283 }
3284
Matt Pietalfaedea82019-03-21 10:36:54 -04003285 int columnCount = holder.getColumnCount();
Matt Pietal5b648562019-03-12 07:40:26 -04003286 int end = start + columnCount - 1;
Matt Pietaldadc0d12019-04-16 12:53:28 -04003287 while (getRowType(end) != startType && end >= start) {
Adam Powell7d758002015-05-06 17:49:36 -07003288 end--;
3289 }
3290
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003291 if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
Matt Pietal5b648562019-03-12 07:40:26 -04003292 final TextView textView = row.findViewById(R.id.chooser_row_text_option);
Adam Powell63b31692015-09-28 10:45:00 -07003293
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05003294 if (textView.getVisibility() != View.VISIBLE) {
3295 textView.setAlpha(0.0f);
3296 textView.setVisibility(View.VISIBLE);
3297 textView.setText(R.string.chooser_no_direct_share_targets);
3298
3299 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
3300 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3301
3302 float translationInPx = getResources().getDimensionPixelSize(
3303 R.dimen.chooser_row_text_option_translate);
3304 textView.setTranslationY(translationInPx);
3305 ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
3306 0.0f);
3307 translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3308
3309 AnimatorSet animSet = new AnimatorSet();
3310 animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3311 animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3312 animSet.playTogether(fadeAnim, translateAnim);
3313 animSet.start();
3314 }
Adam Powell7d758002015-05-06 17:49:36 -07003315 }
3316
Matt Pietal5b648562019-03-12 07:40:26 -04003317 for (int i = 0; i < columnCount; i++) {
3318 final View v = holder.getView(i);
Adam Powell7d758002015-05-06 17:49:36 -07003319 if (start + i <= end) {
Matt Pietalfaedea82019-03-21 10:36:54 -04003320 holder.setViewVisibility(i, View.VISIBLE);
Matt Pietal5b648562019-03-12 07:40:26 -04003321 holder.setItemIndex(i, start + i);
3322 mChooserListAdapter.bindView(holder.getItemIndex(i), v);
Adam Powell7d758002015-05-06 17:49:36 -07003323 } else {
Matt Pietalfaedea82019-03-21 10:36:54 -04003324 holder.setViewVisibility(i, View.INVISIBLE);
Adam Powell7d758002015-05-06 17:49:36 -07003325 }
3326 }
3327 }
3328
3329 int getFirstRowPosition(int row) {
Matt Pietal74c6ed02019-04-18 13:38:46 -04003330 row -= getContentPreviewRowCount() + getProfileRowCount();
Matt Pietal1ef88002019-03-13 10:43:18 -04003331
Matt Pietal5b648562019-03-12 07:40:26 -04003332 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
3333 final int serviceRows = (int) Math.ceil((float) serviceCount
3334 / ChooserListAdapter.MAX_SERVICE_TARGETS);
3335 if (row < serviceRows) {
3336 return row * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07003337 }
3338
Matt Pietaldadc0d12019-04-16 12:53:28 -04003339 final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
3340 + mChooserListAdapter.getRankedTargetCount();
3341 final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
3342 if (row < callerAndRankedRows + serviceRows) {
Matt Pietal5b648562019-03-12 07:40:26 -04003343 return serviceCount + (row - serviceRows) * getMaxTargetsPerRow();
Adam Powell7d758002015-05-06 17:49:36 -07003344 }
3345
Mike Digmanae730b12019-04-25 11:10:31 -07003346 row -= getAzLabelRowCount();
3347
Matt Pietaldadc0d12019-04-16 12:53:28 -04003348 return callerAndRankedCount + serviceCount
3349 + (row - callerAndRankedRows - serviceRows) * getMaxTargetsPerRow();
Matt Pietal5b648562019-03-12 07:40:26 -04003350 }
3351
3352 public void handleScroll(View v, int y, int oldy) {
Matt Pietale54dcc2e2019-05-02 12:59:38 -04003353 // Only expand direct share area if there is a minimum number of shortcuts,
3354 // which will help reduce the amount of visible shuffling due to older-style
3355 // direct share targets.
3356 int orientation = getResources().getConfiguration().orientation;
3357 boolean canExpandDirectShare =
3358 mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
Matt Pietal3e4b56f2019-05-31 12:06:17 -04003359 && orientation == Configuration.ORIENTATION_PORTRAIT
3360 && !isInMultiWindowMode();
Matt Pietale54dcc2e2019-05-02 12:59:38 -04003361
3362 if (mDirectShareViewHolder != null && canExpandDirectShare) {
Matt Pietal5b648562019-03-12 07:40:26 -04003363 mDirectShareViewHolder.handleScroll(mAdapterView, y, oldy, getMaxTargetsPerRow());
3364 }
Adam Powell7d758002015-05-06 17:49:36 -07003365 }
3366 }
3367
Matt Pietal5b648562019-03-12 07:40:26 -04003368 abstract class RowViewHolder {
3369 protected int mMeasuredRowHeight;
3370 private int[] mItemIndices;
3371 protected final View[] mCells;
Matt Pietal5b648562019-03-12 07:40:26 -04003372 private final int mColumnCount;
Adam Powell63b31692015-09-28 10:45:00 -07003373
Matt Pietal5b648562019-03-12 07:40:26 -04003374 RowViewHolder(int cellCount) {
3375 this.mCells = new View[cellCount];
3376 this.mItemIndices = new int[cellCount];
Matt Pietal5b648562019-03-12 07:40:26 -04003377 this.mColumnCount = cellCount;
3378 }
3379
3380 abstract ViewGroup addView(int index, View v);
3381
3382 abstract ViewGroup getViewGroup();
3383
Mike Digmanba232682019-03-27 14:55:26 -07003384 abstract ViewGroup getRowByIndex(int index);
3385
3386 abstract ViewGroup getRow(int rowNumber);
Matt Pietal5b648562019-03-12 07:40:26 -04003387
Matt Pietalfaedea82019-03-21 10:36:54 -04003388 abstract void setViewVisibility(int i, int visibility);
3389
Matt Pietal5b648562019-03-12 07:40:26 -04003390 public int getColumnCount() {
3391 return mColumnCount;
3392 }
3393
Adam Powell63b31692015-09-28 10:45:00 -07003394 public void measure() {
3395 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Matt Pietal5b648562019-03-12 07:40:26 -04003396 getViewGroup().measure(spec, spec);
3397 mMeasuredRowHeight = getViewGroup().getMeasuredHeight();
3398 }
3399
3400 public int getMeasuredRowHeight() {
3401 return mMeasuredRowHeight;
3402 }
3403
Matt Pietal5b648562019-03-12 07:40:26 -04003404 public void setItemIndex(int itemIndex, int listIndex) {
3405 mItemIndices[itemIndex] = listIndex;
3406 }
3407
3408 public int getItemIndex(int itemIndex) {
3409 return mItemIndices[itemIndex];
3410 }
3411
3412 public View getView(int index) {
3413 return mCells[index];
3414 }
3415 }
3416
3417 class SingleRowViewHolder extends RowViewHolder {
3418 private final ViewGroup mRow;
3419
3420 SingleRowViewHolder(ViewGroup row, int cellCount) {
3421 super(cellCount);
3422
3423 this.mRow = row;
3424 }
3425
3426 public ViewGroup getViewGroup() {
3427 return mRow;
3428 }
3429
Mike Digmanba232682019-03-27 14:55:26 -07003430 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04003431 return mRow;
3432 }
3433
Mike Digmanba232682019-03-27 14:55:26 -07003434 public ViewGroup getRow(int rowNumber) {
3435 if (rowNumber == 0) return mRow;
3436 return null;
3437 }
3438
Matt Pietal5b648562019-03-12 07:40:26 -04003439 public ViewGroup addView(int index, View v) {
3440 mRow.addView(v);
3441 mCells[index] = v;
3442
Matt Pietal5b648562019-03-12 07:40:26 -04003443 return mRow;
3444 }
Matt Pietalfaedea82019-03-21 10:36:54 -04003445
3446 public void setViewVisibility(int i, int visibility) {
3447 getView(i).setVisibility(visibility);
3448 }
Matt Pietal5b648562019-03-12 07:40:26 -04003449 }
3450
3451 class DirectShareViewHolder extends RowViewHolder {
3452 private final ViewGroup mParent;
3453 private final List<ViewGroup> mRows;
3454 private int mCellCountPerRow;
3455
3456 private boolean mHideDirectShareExpansion = false;
3457 private int mDirectShareMinHeight = 0;
3458 private int mDirectShareCurrHeight = 0;
3459 private int mDirectShareMaxHeight = 0;
3460
Matt Pietalfaedea82019-03-21 10:36:54 -04003461 private final boolean[] mCellVisibility;
3462
Matt Pietal5b648562019-03-12 07:40:26 -04003463 DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow) {
3464 super(rows.size() * cellCountPerRow);
3465
3466 this.mParent = parent;
3467 this.mRows = rows;
3468 this.mCellCountPerRow = cellCountPerRow;
Matt Pietalfaedea82019-03-21 10:36:54 -04003469 this.mCellVisibility = new boolean[rows.size() * cellCountPerRow];
Matt Pietal5b648562019-03-12 07:40:26 -04003470 }
3471
3472 public ViewGroup addView(int index, View v) {
Mike Digmanba232682019-03-27 14:55:26 -07003473 ViewGroup row = getRowByIndex(index);
Matt Pietal5b648562019-03-12 07:40:26 -04003474 row.addView(v);
3475 mCells[index] = v;
3476
Matt Pietal5b648562019-03-12 07:40:26 -04003477 return row;
3478 }
3479
3480 public ViewGroup getViewGroup() {
3481 return mParent;
3482 }
3483
Mike Digmanba232682019-03-27 14:55:26 -07003484 public ViewGroup getRowByIndex(int index) {
Matt Pietal5b648562019-03-12 07:40:26 -04003485 return mRows.get(index / mCellCountPerRow);
3486 }
3487
Mike Digmanba232682019-03-27 14:55:26 -07003488 public ViewGroup getRow(int rowNumber) {
3489 return mRows.get(rowNumber);
3490 }
3491
Matt Pietal5b648562019-03-12 07:40:26 -04003492 public void measure() {
3493 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3494 getRow(0).measure(spec, spec);
3495 getRow(1).measure(spec, spec);
3496
Matt Pietal5b648562019-03-12 07:40:26 -04003497 mDirectShareMinHeight = getRow(0).getMeasuredHeight();
3498 mDirectShareCurrHeight = mDirectShareCurrHeight > 0
3499 ? mDirectShareCurrHeight : mDirectShareMinHeight;
3500 mDirectShareMaxHeight = 2 * mDirectShareMinHeight;
3501 }
3502
3503 public int getMeasuredRowHeight() {
3504 return mDirectShareCurrHeight;
3505 }
3506
Matt Pietala9c8e502019-04-10 14:27:35 -04003507 public int getMinRowHeight() {
3508 return mDirectShareMinHeight;
3509 }
3510
Matt Pietalfaedea82019-03-21 10:36:54 -04003511 public void setViewVisibility(int i, int visibility) {
3512 final View v = getView(i);
3513 if (visibility == View.VISIBLE) {
3514 mCellVisibility[i] = true;
3515 v.setVisibility(visibility);
3516 v.setAlpha(1.0f);
3517 } else if (visibility == View.INVISIBLE && mCellVisibility[i]) {
3518 mCellVisibility[i] = false;
3519
3520 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
3521 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3522 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
3523 fadeAnim.addListener(new AnimatorListenerAdapter() {
3524 public void onAnimationEnd(Animator animation) {
3525 v.setVisibility(View.INVISIBLE);
3526 }
3527 });
3528 fadeAnim.start();
3529 }
3530 }
3531
Matt Pietal5b648562019-03-12 07:40:26 -04003532 public void handleScroll(AbsListView view, int y, int oldy, int maxTargetsPerRow) {
Matt Pietala9c8e502019-04-10 14:27:35 -04003533 // only exit early if fully collapsed, otherwise onListRebuilt() with shifting
3534 // targets can lock us into an expanded mode
3535 boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight;
3536 if (notExpanded) {
3537 if (mHideDirectShareExpansion) {
3538 return;
3539 }
Matt Pietal5b648562019-03-12 07:40:26 -04003540
Matt Pietala9c8e502019-04-10 14:27:35 -04003541 // only expand if we have more than maxTargetsPerRow, and delay that decision
3542 // until they start to scroll
3543 if (mChooserListAdapter.getSelectableServiceTargetCount() <= maxTargetsPerRow) {
3544 mHideDirectShareExpansion = true;
3545 return;
3546 }
Matt Pietal5b648562019-03-12 07:40:26 -04003547 }
3548
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003549 int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
Matt Pietal5b648562019-03-12 07:40:26 -04003550
3551 int prevHeight = mDirectShareCurrHeight;
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003552 int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
3553 newHeight = Math.max(newHeight, mDirectShareMinHeight);
3554 yDiff = newHeight - prevHeight;
Matt Pietal5b648562019-03-12 07:40:26 -04003555
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003556 if (view == null || view.getChildCount() == 0 || yDiff == 0) {
Matt Pietal5b648562019-03-12 07:40:26 -04003557 return;
3558 }
3559
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003560 // locate the item to expand, and offset the rows below that one
3561 boolean foundExpansion = false;
3562 for (int i = 0; i < view.getChildCount(); i++) {
3563 View child = view.getChildAt(i);
Matt Pietal1ef88002019-03-13 10:43:18 -04003564
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003565 if (foundExpansion) {
3566 child.offsetTopAndBottom(yDiff);
3567 } else {
3568 if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
3569 int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
3570 MeasureSpec.EXACTLY);
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003571 int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003572 MeasureSpec.EXACTLY);
3573 child.measure(widthSpec, heightSpec);
3574 child.getLayoutParams().height = child.getMeasuredHeight();
3575 child.layout(child.getLeft(), child.getTop(), child.getRight(),
3576 child.getTop() + child.getMeasuredHeight());
Matt Pietal5b648562019-03-12 07:40:26 -04003577
Matt Pietalfe28f9a2019-03-22 07:59:58 -04003578 foundExpansion = true;
3579 }
3580 }
Matt Pietal5b648562019-03-12 07:40:26 -04003581 }
Matt Pietalc6d3ac22019-04-25 14:38:30 -04003582
3583 if (foundExpansion) {
3584 mDirectShareCurrHeight = newHeight;
3585 }
Adam Powell63b31692015-09-28 10:45:00 -07003586 }
3587 }
3588
Adam Powell9761ab22015-09-08 17:01:49 -07003589 static class ChooserTargetServiceConnection implements ServiceConnection {
Adam Powell52c39212016-04-07 15:14:18 -07003590 private DisplayResolveInfo mOriginalTarget;
Adam Powell9761ab22015-09-08 17:01:49 -07003591 private ComponentName mConnectedComponent;
3592 private ChooserActivity mChooserActivity;
3593 private final Object mLock = new Object();
Adam Powell24428412015-04-01 17:19:56 -07003594
3595 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
3596 @Override
3597 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
Adam Powell9761ab22015-09-08 17:01:49 -07003598 synchronized (mLock) {
3599 if (mChooserActivity == null) {
3600 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
3601 + mConnectedComponent + "; ignoring...");
3602 return;
3603 }
3604 mChooserActivity.filterServiceTargets(
3605 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
3606 final Message msg = Message.obtain();
Matt Pietalab73a882019-06-05 07:04:55 -04003607 msg.what = ChooserHandler.CHOOSER_TARGET_SERVICE_RESULT;
Adam Powell9761ab22015-09-08 17:01:49 -07003608 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
3609 ChooserTargetServiceConnection.this);
3610 mChooserActivity.mChooserHandler.sendMessage(msg);
3611 }
Adam Powell24428412015-04-01 17:19:56 -07003612 }
3613 };
3614
Adam Powell9761ab22015-09-08 17:01:49 -07003615 public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
3616 DisplayResolveInfo dri) {
3617 mChooserActivity = chooserActivity;
Adam Powell24428412015-04-01 17:19:56 -07003618 mOriginalTarget = dri;
3619 }
3620
3621 @Override
3622 public void onServiceConnected(ComponentName name, IBinder service) {
3623 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003624 synchronized (mLock) {
3625 if (mChooserActivity == null) {
3626 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
3627 return;
3628 }
3629
3630 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
3631 try {
3632 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
3633 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
3634 } catch (RemoteException e) {
3635 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
3636 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003637 mChooserActivity.mServiceConnections.remove(this);
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003638 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003639 }
Adam Powell24428412015-04-01 17:19:56 -07003640 }
3641 }
3642
3643 @Override
3644 public void onServiceDisconnected(ComponentName name) {
3645 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07003646 synchronized (mLock) {
3647 if (mChooserActivity == null) {
3648 Log.e(TAG,
3649 "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
3650 return;
3651 }
3652
3653 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07003654 mChooserActivity.mServiceConnections.remove(this);
3655 if (mChooserActivity.mServiceConnections.isEmpty()) {
Adam Powell9761ab22015-09-08 17:01:49 -07003656 mChooserActivity.sendVoiceChoicesIfNeeded();
3657 }
3658 mConnectedComponent = null;
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04003659 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07003660 }
3661 }
3662
3663 public void destroy() {
3664 synchronized (mLock) {
3665 mChooserActivity = null;
Adam Powell52c39212016-04-07 15:14:18 -07003666 mOriginalTarget = null;
Adam Powell4c470d62015-06-19 17:46:17 -07003667 }
Adam Powell24428412015-04-01 17:19:56 -07003668 }
3669
3670 @Override
3671 public String toString() {
Adam Powell9761ab22015-09-08 17:01:49 -07003672 return "ChooserTargetServiceConnection{service="
3673 + mConnectedComponent + ", activity="
Adam Powell52c39212016-04-07 15:14:18 -07003674 + (mOriginalTarget != null
3675 ? mOriginalTarget.getResolveInfo().activityInfo.toString()
3676 : "<connection destroyed>") + "}";
Adam Powell24428412015-04-01 17:19:56 -07003677 }
3678 }
3679
3680 static class ServiceResultInfo {
3681 public final DisplayResolveInfo originalTarget;
3682 public final List<ChooserTarget> resultTargets;
3683 public final ChooserTargetServiceConnection connection;
3684
3685 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
3686 ChooserTargetServiceConnection c) {
3687 originalTarget = ot;
3688 resultTargets = rt;
3689 connection = c;
3690 }
3691 }
Adam Powell2ed547e2015-04-29 18:45:04 -07003692
3693 static class RefinementResultReceiver extends ResultReceiver {
3694 private ChooserActivity mChooserActivity;
3695 private TargetInfo mSelectedTarget;
3696
3697 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
3698 Handler handler) {
3699 super(handler);
3700 mChooserActivity = host;
3701 mSelectedTarget = target;
3702 }
3703
3704 @Override
3705 protected void onReceiveResult(int resultCode, Bundle resultData) {
3706 if (mChooserActivity == null) {
3707 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
3708 return;
3709 }
3710 if (resultData == null) {
3711 Log.e(TAG, "RefinementResultReceiver received null resultData");
3712 return;
3713 }
3714
3715 switch (resultCode) {
3716 case RESULT_CANCELED:
3717 mChooserActivity.onRefinementCanceled();
3718 break;
3719 case RESULT_OK:
3720 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
3721 if (intentParcelable instanceof Intent) {
3722 mChooserActivity.onRefinementResult(mSelectedTarget,
3723 (Intent) intentParcelable);
3724 } else {
3725 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
3726 + " in resultData with key Intent.EXTRA_INTENT");
3727 }
3728 break;
3729 default:
3730 Log.w(TAG, "Unknown result code " + resultCode
3731 + " sent to RefinementResultReceiver");
3732 break;
3733 }
3734 }
3735
3736 public void destroy() {
3737 mChooserActivity = null;
3738 mSelectedTarget = null;
3739 }
3740 }
Adam Powell63b31692015-09-28 10:45:00 -07003741
Matt Pietal26038402019-01-08 07:29:34 -05003742 /**
3743 * Used internally to round image corners while obeying view padding.
3744 */
3745 public static class RoundedRectImageView extends ImageView {
3746 private int mRadius = 0;
3747 private Path mPath = new Path();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003748 private Paint mOverlayPaint = new Paint(0);
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003749 private Paint mRoundRectPaint = new Paint(0);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003750 private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
3751 private String mExtraImageCount = null;
Matt Pietal26038402019-01-08 07:29:34 -05003752
3753 public RoundedRectImageView(Context context) {
3754 super(context);
3755 }
3756
3757 public RoundedRectImageView(Context context, AttributeSet attrs) {
3758 this(context, attrs, 0);
3759 }
3760
3761 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
3762 this(context, attrs, defStyleAttr, 0);
3763 }
3764
3765 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
3766 int defStyleRes) {
3767 super(context, attrs, defStyleAttr, defStyleRes);
3768 mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003769
3770 mOverlayPaint.setColor(0x99000000);
3771 mOverlayPaint.setStyle(Paint.Style.FILL);
3772
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003773 mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider));
3774 mRoundRectPaint.setStyle(Paint.Style.STROKE);
3775 mRoundRectPaint.setStrokeWidth(context.getResources()
3776 .getDimensionPixelSize(R.dimen.chooser_preview_image_border));
3777
Matt Pietal0ea391b2019-01-30 10:44:15 -05003778 mTextPaint.setColor(Color.WHITE);
3779 mTextPaint.setTextSize(context.getResources()
3780 .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
3781 mTextPaint.setTextAlign(Paint.Align.CENTER);
Matt Pietal26038402019-01-08 07:29:34 -05003782 }
3783
3784 private void updatePath(int width, int height) {
3785 mPath.reset();
3786
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003787 int imageWidth = width - getPaddingRight() - getPaddingLeft();
3788 int imageHeight = height - getPaddingBottom() - getPaddingTop();
Matt Pietal26038402019-01-08 07:29:34 -05003789 mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
3790 mRadius, Path.Direction.CW);
3791 }
3792
3793 /**
3794 * Sets the corner radius on all corners
3795 *
3796 * param radius 0 for no radius, &gt; 0 for a visible corner radius
3797 */
3798 public void setRadius(int radius) {
3799 mRadius = radius;
3800 updatePath(getWidth(), getHeight());
3801 }
3802
Matt Pietal0ea391b2019-01-30 10:44:15 -05003803 /**
3804 * Display an overlay with extra image count on 3rd image
3805 */
3806 public void setExtraImageCount(int count) {
3807 if (count > 0) {
3808 this.mExtraImageCount = "+" + count;
3809 } else {
3810 this.mExtraImageCount = null;
3811 }
3812 }
3813
Matt Pietal26038402019-01-08 07:29:34 -05003814 @Override
3815 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
3816 super.onSizeChanged(width, height, oldWidth, oldHeight);
3817 updatePath(width, height);
3818 }
3819
3820 @Override
3821 protected void onDraw(Canvas canvas) {
3822 if (mRadius != 0) {
3823 canvas.clipPath(mPath);
3824 }
3825
3826 super.onDraw(canvas);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003827
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003828 int x = getPaddingLeft();
3829 int y = getPaddingRight();
3830 int width = getWidth() - getPaddingRight() - getPaddingLeft();
3831 int height = getHeight() - getPaddingBottom() - getPaddingTop();
Matt Pietal0ea391b2019-01-30 10:44:15 -05003832 if (mExtraImageCount != null) {
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003833 canvas.drawRect(x, y, width, height, mOverlayPaint);
Matt Pietal0ea391b2019-01-30 10:44:15 -05003834
3835 int xPos = canvas.getWidth() / 2;
3836 int yPos = (int) ((canvas.getHeight() / 2.0f)
3837 - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
3838
3839 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
3840 }
Matt Pietal9aaf00c2019-04-09 10:09:12 -04003841
3842 canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
Matt Pietal26038402019-01-08 07:29:34 -05003843 }
3844 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003845}