blob: 91928b55ec60918909d164fa059e9bbf48b50610 [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;
Adam Powell0b3c1122014-10-09 12:50:14 -070027import android.app.Activity;
Ng Zhi And3ec5fc2018-05-10 09:13:00 -070028import android.app.ActivityManager;
George Hodulik69d4a082019-01-18 11:27:03 -080029import android.app.prediction.AppPredictionContext;
30import android.app.prediction.AppPredictionManager;
31import android.app.prediction.AppPredictor;
32import android.app.prediction.AppTarget;
George Hodulikf2b0d342019-01-25 12:43:54 -080033import android.app.prediction.AppTargetEvent;
34import android.app.prediction.AppTargetId;
Matt Pietal26038402019-01-08 07:29:34 -050035import android.content.ClipData;
Matt Pietal1fa7d802019-01-30 10:44:15 -050036import android.content.ClipboardManager;
Adam Powell0b3c1122014-10-09 12:50:14 -070037import android.content.ComponentName;
Matt Pietal0ea391b2019-01-30 10:44:15 -050038import android.content.ContentResolver;
Adam Powell24428412015-04-01 17:19:56 -070039import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.content.Intent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080041import android.content.IntentFilter;
Adam Powell0b3c1122014-10-09 12:50:14 -070042import android.content.IntentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -070043import android.content.IntentSender.SendIntentException;
Adam Powell24428412015-04-01 17:19:56 -070044import android.content.ServiceConnection;
Adam Powell23882512016-01-29 10:21:00 -080045import android.content.SharedPreferences;
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +000046import android.content.pm.ActivityInfo;
Adam Powell7d758002015-05-06 17:49:36 -070047import android.content.pm.LabeledIntent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080048import android.content.pm.LauncherApps;
Adam Powell24428412015-04-01 17:19:56 -070049import android.content.pm.PackageManager;
50import android.content.pm.PackageManager.NameNotFoundException;
51import android.content.pm.ResolveInfo;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080052import android.content.pm.ShortcutInfo;
53import android.content.pm.ShortcutManager;
Matt Pietal18bbd822019-02-12 15:21:36 -050054import android.content.res.Configuration;
Matt Pietal46d828c2019-02-05 08:07:07 -050055import android.database.Cursor;
Adam Powell7d758002015-05-06 17:49:36 -070056import android.database.DataSetObserver;
Matt Pietal26038402019-01-08 07:29:34 -050057import android.graphics.Bitmap;
58import android.graphics.Canvas;
Adam Powell63b31692015-09-28 10:45:00 -070059import android.graphics.Color;
Matt Pietal0ea391b2019-01-30 10:44:15 -050060import android.graphics.Paint;
Matt Pietal26038402019-01-08 07:29:34 -050061import android.graphics.Path;
Adam Powell24428412015-04-01 17:19:56 -070062import android.graphics.drawable.Drawable;
Adam Powell13036be2015-05-12 14:43:56 -070063import android.graphics.drawable.Icon;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -050064import android.metrics.LogMaker;
Matt Pietal26038402019-01-08 07:29:34 -050065import android.net.Uri;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080066import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067import android.os.Bundle;
Adam Powell23882512016-01-29 10:21:00 -080068import android.os.Environment;
Adam Powell24428412015-04-01 17:19:56 -070069import android.os.Handler;
70import android.os.IBinder;
71import android.os.Message;
Dianne Hackborneb034652009-09-07 00:49:58 -070072import android.os.Parcelable;
Adam Powell52c39212016-04-07 15:14:18 -070073import android.os.Process;
Adam Powell24428412015-04-01 17:19:56 -070074import android.os.RemoteException;
Adam Powell2ed547e2015-04-29 18:45:04 -070075import android.os.ResultReceiver;
Adam Powell24428412015-04-01 17:19:56 -070076import android.os.UserHandle;
Adam Powell7d758002015-05-06 17:49:36 -070077import android.os.UserManager;
Jeff Sharkey8212ae02016-02-10 14:46:43 -070078import android.os.storage.StorageManager;
Matt Pietal46d828c2019-02-05 08:07:07 -050079import android.provider.DocumentsContract;
Matt Pietalf38e9d22019-02-15 10:01:03 -050080import android.provider.Downloads;
Matt Pietal46d828c2019-02-05 08:07:07 -050081import android.provider.OpenableColumns;
Adam Powell24428412015-04-01 17:19:56 -070082import android.service.chooser.ChooserTarget;
83import android.service.chooser.ChooserTargetService;
84import android.service.chooser.IChooserTargetResult;
85import android.service.chooser.IChooserTargetService;
86import android.text.TextUtils;
Matt Pietal26038402019-01-08 07:29:34 -050087import android.util.AttributeSet;
Dianne Hackborneb034652009-09-07 00:49:58 -070088import android.util.Log;
Matt Pietal26038402019-01-08 07:29:34 -050089import android.util.Size;
Adam Powell0b3c1122014-10-09 12:50:14 -070090import android.util.Slog;
Adam Powell7d758002015-05-06 17:49:36 -070091import android.view.LayoutInflater;
Adam Powell24428412015-04-01 17:19:56 -070092import android.view.View;
Adam Powell63b31692015-09-28 10:45:00 -070093import android.view.View.MeasureSpec;
Adam Powell7d758002015-05-06 17:49:36 -070094import android.view.View.OnClickListener;
Adam Powell98b7f892015-06-19 12:38:45 -070095import android.view.View.OnLongClickListener;
Adam Powell24428412015-04-01 17:19:56 -070096import android.view.ViewGroup;
Adam Powell63b31692015-09-28 10:45:00 -070097import android.view.ViewGroup.LayoutParams;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -050098import android.view.animation.AccelerateInterpolator;
99import android.view.animation.DecelerateInterpolator;
Adam Powell7d758002015-05-06 17:49:36 -0700100import android.widget.AbsListView;
101import android.widget.BaseAdapter;
Matt Pietal26038402019-01-08 07:29:34 -0500102import android.widget.ImageView;
Jason Monk027dcfa2017-06-27 18:37:35 -0400103import android.widget.LinearLayout;
Adam Powell7d758002015-05-06 17:49:36 -0700104import android.widget.ListView;
Jason Monk027dcfa2017-06-27 18:37:35 -0400105import android.widget.Space;
Matt Pietal26038402019-01-08 07:29:34 -0500106import android.widget.TextView;
Matt Pietal1fa7d802019-01-30 10:44:15 -0500107import android.widget.Toast;
Jason Monk027dcfa2017-06-27 18:37:35 -0400108
Adam Powell7d758002015-05-06 17:49:36 -0700109import com.android.internal.R;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800110import com.android.internal.annotations.VisibleForTesting;
Adam Powell98b7f892015-06-19 12:38:45 -0700111import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +0100112import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500113import com.android.internal.util.ImageUtils;
Alison Cichowlas3e340502018-08-07 17:15:01 -0400114
Adam Powell52c39212016-04-07 15:14:18 -0700115import com.google.android.collect.Lists;
Adam Powell24428412015-04-01 17:19:56 -0700116
Adam Powell23882512016-01-29 10:21:00 -0800117import java.io.File;
Matt Pietal26038402019-01-08 07:29:34 -0500118import java.io.IOException;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500119import java.lang.annotation.Retention;
Adam Powell24428412015-04-01 17:19:56 -0700120import java.util.ArrayList;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800121import java.util.Arrays;
Adam Powella182e452015-07-06 16:57:56 -0700122import java.util.Collections;
123import java.util.Comparator;
Adam Powell24428412015-04-01 17:19:56 -0700124import java.util.List;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125
126public class ChooserActivity extends ResolverActivity {
Adam Powell0b3c1122014-10-09 12:50:14 -0700127 private static final String TAG = "ChooserActivity";
128
Jorim Jaggif631ef72017-02-24 13:49:47 +0100129 /**
130 * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
131 * in onStop when launched in a new task. If this extra is set to true, we do not finish
132 * ourselves when onStop gets called.
133 */
134 public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
135 = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
136
Mehdi Alizadeh3c335a22019-01-17 16:03:19 -0800137 private static final boolean DEBUG = false;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800138
George Hodulik69d4a082019-01-18 11:27:03 -0800139
140 /**
141 * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
142 * {@link AppPredictionManager} will be queried for direct share targets.
143 */
144 // TODO(b/123089490): Replace with system flag
George Hodulik2bd58812019-03-06 19:22:10 +0000145 private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = false;
George Hodulik69d4a082019-01-18 11:27:03 -0800146 // TODO(b/123088566) Share these in a better way.
147 private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
George Hodulikf2b0d342019-01-25 12:43:54 -0800148 public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
George Hodulik69d4a082019-01-18 11:27:03 -0800149 private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
150 public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
151 private AppPredictor mAppPredictor;
152 private AppPredictor.Callback mAppPredictorCallback;
153
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800154 /**
155 * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
156 * binding to every ChooserTargetService implementation.
157 */
158 // TODO(b/121287573): Replace with a system flag (setprop?)
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800159 private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
160 private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
161
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500162 /**
163 * The transition time between placeholders for direct share to a message
164 * indicating that non are available.
165 */
166 private static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200;
167
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800168 // TODO(b/121287224): Re-evaluate this limit
169 private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
Adam Powell24428412015-04-01 17:19:56 -0700170
Adam Powell2ed547e2015-04-29 18:45:04 -0700171 private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500172 private static final int WATCHDOG_TIMEOUT_MILLIS = 3000;
Adam Powell24428412015-04-01 17:19:56 -0700173
Adam Powelle49d9392014-07-17 18:45:19 -0700174 private Bundle mReplacementExtras;
Adam Powell0b3c1122014-10-09 12:50:14 -0700175 private IntentSender mChosenComponentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -0700176 private IntentSender mRefinementIntentSender;
177 private RefinementResultReceiver mRefinementResultReceiver;
Adam Powell52c39212016-04-07 15:14:18 -0700178 private ChooserTarget[] mCallerChooserTargets;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800179 private ComponentName[] mFilteredComponentNames;
Adam Powelle49d9392014-07-17 18:45:19 -0700180
Adam Powell13036be2015-05-12 14:43:56 -0700181 private Intent mReferrerFillInIntent;
182
Kang Li9082f5b2016-12-02 10:56:21 -0800183 private long mChooserShownTime;
Kang Li64b018e2017-01-05 17:30:06 -0800184 protected boolean mIsSuccessfullySelected;
Kang Li9082f5b2016-12-02 10:56:21 -0800185
Adam Powell7d758002015-05-06 17:49:36 -0700186 private ChooserListAdapter mChooserListAdapter;
Adam Powell63b31692015-09-28 10:45:00 -0700187 private ChooserRowAdapter mChooserRowAdapter;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500188 private Drawable mChooserRowLayer;
189 private int mChooserRowServiceSpacing;
Adam Powell24428412015-04-01 17:19:56 -0700190
Adam Powell23882512016-01-29 10:21:00 -0800191 private SharedPreferences mPinnedSharedPrefs;
192 private static final float PINNED_TARGET_SCORE_BOOST = 1000.f;
Adam Powell52c39212016-04-07 15:14:18 -0700193 private static final float CALLER_TARGET_SCORE_BOOST = 900.f;
Adam Powell23882512016-01-29 10:21:00 -0800194 private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
195 private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
196
Adam Powell24428412015-04-01 17:19:56 -0700197 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
198
199 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
200 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800201 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 3;
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800202 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4;
Adam Powell24428412015-04-01 17:19:56 -0700203
Matt Pietal0ea391b2019-01-30 10:44:15 -0500204 @Retention(SOURCE)
205 @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
206 private @interface ContentPreviewType {
207 }
208
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500209 // Starting at 1 since 0 is considered "undefined" for some of the database transformations
210 // of tron logs.
211 private static final int CONTENT_PREVIEW_IMAGE = 1;
212 private static final int CONTENT_PREVIEW_FILE = 2;
213 private static final int CONTENT_PREVIEW_TEXT = 3;
214 protected MetricsLogger mMetricsLogger;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500215
Adam Powell13036be2015-05-12 14:43:56 -0700216 private final Handler mChooserHandler = new Handler() {
Adam Powell24428412015-04-01 17:19:56 -0700217 @Override
218 public void handleMessage(Message msg) {
219 switch (msg.what) {
220 case CHOOSER_TARGET_SERVICE_RESULT:
221 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
222 if (isDestroyed()) break;
223 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
224 if (!mServiceConnections.contains(sri.connection)) {
225 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
226 + " returned after being removed from active connections."
227 + " Have you considered returning results faster?");
228 break;
229 }
Adam Powella182e452015-07-06 16:57:56 -0700230 if (sri.resultTargets != null) {
231 mChooserListAdapter.addServiceResults(sri.originalTarget,
232 sri.resultTargets);
233 }
Adam Powell24428412015-04-01 17:19:56 -0700234 unbindService(sri.connection);
Adam Powell9761ab22015-09-08 17:01:49 -0700235 sri.connection.destroy();
Adam Powell24428412015-04-01 17:19:56 -0700236 mServiceConnections.remove(sri.connection);
Adam Powell4c470d62015-06-19 17:46:17 -0700237 if (mServiceConnections.isEmpty()) {
Adam Powell4c470d62015-06-19 17:46:17 -0700238 sendVoiceChoicesIfNeeded();
Adam Powell565943f2016-02-11 10:29:05 -0800239 mChooserListAdapter.setShowServiceTargets(true);
Adam Powell4c470d62015-06-19 17:46:17 -0700240 }
Adam Powell24428412015-04-01 17:19:56 -0700241 break;
242
243 case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT:
244 if (DEBUG) {
245 Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
246 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500247 if (isDestroyed()) {
248 break;
249 }
Adam Powell24428412015-04-01 17:19:56 -0700250 unbindRemainingServices();
Adam Powell4c470d62015-06-19 17:46:17 -0700251 sendVoiceChoicesIfNeeded();
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500252 mChooserListAdapter.completeServiceTargetLoading();
Adam Powell565943f2016-02-11 10:29:05 -0800253 mChooserListAdapter.setShowServiceTargets(true);
Adam Powell24428412015-04-01 17:19:56 -0700254 break;
255
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800256 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
257 if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
258 if (isDestroyed()) break;
259 final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
260 if (resultInfo.resultTargets != null) {
261 mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
262 resultInfo.resultTargets);
263 }
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800264 break;
265
266 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800267 sendVoiceChoicesIfNeeded();
268 mChooserListAdapter.setShowServiceTargets(true);
269 break;
270
Adam Powell24428412015-04-01 17:19:56 -0700271 default:
272 super.handleMessage(msg);
273 }
274 }
275 };
276
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800277 @Override
278 protected void onCreate(Bundle savedInstanceState) {
Kang Li9082f5b2016-12-02 10:56:21 -0800279 final long intentReceivedTime = System.currentTimeMillis();
280 mIsSuccessfullySelected = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800281 Intent intent = getIntent();
Dianne Hackborneb034652009-09-07 00:49:58 -0700282 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
283 if (!(targetParcelable instanceof Intent)) {
Christopher Tate9d6376a2014-02-12 13:14:10 -0800284 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
Dianne Hackborneb034652009-09-07 00:49:58 -0700285 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800286 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700287 return;
288 }
Adam Powell24428412015-04-01 17:19:56 -0700289 Intent target = (Intent) targetParcelable;
Craig Mautner411d2aed2014-05-08 09:07:43 -0700290 if (target != null) {
Adam Powelle49d9392014-07-17 18:45:19 -0700291 modifyTargetIntent(target);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700292 }
Adam Powell2ed547e2015-04-29 18:45:04 -0700293 Parcelable[] targetsParcelable
294 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
295 if (targetsParcelable != null) {
296 final boolean offset = target == null;
297 Intent[] additionalTargets =
298 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
299 for (int i = 0; i < targetsParcelable.length; i++) {
300 if (!(targetsParcelable[i] instanceof Intent)) {
301 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
302 + targetsParcelable[i]);
303 finish();
304 super.onCreate(null);
305 return;
306 }
307 final Intent additionalTarget = (Intent) targetsParcelable[i];
308 if (i == 0 && target == null) {
309 target = additionalTarget;
310 modifyTargetIntent(target);
311 } else {
312 additionalTargets[offset ? i - 1 : i] = additionalTarget;
313 modifyTargetIntent(additionalTarget);
314 }
315 }
316 setAdditionalTargets(additionalTargets);
317 }
318
Adam Powelle49d9392014-07-17 18:45:19 -0700319 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
Matt Pietal26038402019-01-08 07:29:34 -0500320
321 // Do not allow the title to be changed when sharing content
322 CharSequence title = null;
323 if (target != null) {
324 String targetAction = target.getAction();
325 if (!(Intent.ACTION_SEND.equals(targetAction) || Intent.ACTION_SEND_MULTIPLE.equals(
326 targetAction))) {
327 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
328 } else {
329 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
330 + " preview title by using EXTRA_TITLE property of the wrapped"
331 + " EXTRA_INTENT.");
332 }
333 }
334
Adam Powell278902c2014-07-12 18:33:22 -0700335 int defaultTitleRes = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 if (title == null) {
Adam Powell278902c2014-07-12 18:33:22 -0700337 defaultTitleRes = com.android.internal.R.string.chooseActivity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800338 }
Matt Pietal26038402019-01-08 07:29:34 -0500339
Dianne Hackborneb034652009-09-07 00:49:58 -0700340 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
341 Intent[] initialIntents = null;
342 if (pa != null) {
343 initialIntents = new Intent[pa.length];
Matt Pietal26038402019-01-08 07:29:34 -0500344 for (int i = 0; i < pa.length; i++) {
Dianne Hackborneb034652009-09-07 00:49:58 -0700345 if (!(pa[i] instanceof Intent)) {
Adam Powell2ed547e2015-04-29 18:45:04 -0700346 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
Dianne Hackborneb034652009-09-07 00:49:58 -0700347 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800348 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700349 return;
350 }
Craig Mautner411d2aed2014-05-08 09:07:43 -0700351 final Intent in = (Intent) pa[i];
Adam Powelle49d9392014-07-17 18:45:19 -0700352 modifyTargetIntent(in);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700353 initialIntents[i] = in;
Dianne Hackborneb034652009-09-07 00:49:58 -0700354 }
355 }
Adam Powell24428412015-04-01 17:19:56 -0700356
Adam Powell13036be2015-05-12 14:43:56 -0700357 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
358
Adam Powell0b3c1122014-10-09 12:50:14 -0700359 mChosenComponentSender = intent.getParcelableExtra(
360 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
Adam Powell2ed547e2015-04-29 18:45:04 -0700361 mRefinementIntentSender = intent.getParcelableExtra(
362 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
Dianne Hackborn028ceeb2014-08-17 17:45:48 -0700363 setSafeForwardingMode(true);
Adam Powell23882512016-01-29 10:21:00 -0800364
Adam Powell52c39212016-04-07 15:14:18 -0700365 pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
366 if (pa != null) {
367 ComponentName[] names = new ComponentName[pa.length];
368 for (int i = 0; i < pa.length; i++) {
369 if (!(pa[i] instanceof ComponentName)) {
370 Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
371 names = null;
372 break;
373 }
374 names[i] = (ComponentName) pa[i];
375 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800376 mFilteredComponentNames = names;
Adam Powell52c39212016-04-07 15:14:18 -0700377 }
378
379 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
380 if (pa != null) {
381 ChooserTarget[] targets = new ChooserTarget[pa.length];
382 for (int i = 0; i < pa.length; i++) {
383 if (!(pa[i] instanceof ChooserTarget)) {
384 Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
385 targets = null;
386 break;
387 }
388 targets[i] = (ChooserTarget) pa[i];
389 }
390 mCallerChooserTargets = targets;
391 }
392
Adam Powell23882512016-01-29 10:21:00 -0800393 mPinnedSharedPrefs = getPinnedSharedPrefs(this);
Jorim Jaggif631ef72017-02-24 13:49:47 +0100394 setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
Adam Powell278902c2014-07-12 18:33:22 -0700395 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
396 null, false);
Adam Powell98b7f892015-06-19 12:38:45 -0700397
Kang Li9082f5b2016-12-02 10:56:21 -0800398 mChooserShownTime = System.currentTimeMillis();
399 final long systemCost = mChooserShownTime - intentReceivedTime;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500400
401 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500402 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
403 MetricsEvent.PARENT_PROFILE)
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500404 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
405 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
George Hodulik69d4a082019-01-18 11:27:03 -0800406
407 if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
408 final IntentFilter filter = getTargetIntentFilter();
409 Bundle extras = new Bundle();
410 extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
411 AppPredictionManager appPredictionManager =
412 getSystemService(AppPredictionManager.class);
413 mAppPredictor = appPredictionManager.createAppPredictionSession(
414 new AppPredictionContext.Builder(this)
415 .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
416 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
417 .setExtras(extras)
418 .build());
419 mAppPredictorCallback = resultList -> {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500420 if (isFinishing() || isDestroyed()) {
421 return;
422 }
George Hodulik0dd5fbe2019-03-06 12:00:26 -0800423 // May be null if there are no apps to perform share/open action.
424 if (mChooserListAdapter == null) {
425 return;
426 }
George Hodulik69d4a082019-01-18 11:27:03 -0800427 final List<DisplayResolveInfo> driList =
428 getDisplayResolveInfos(mChooserListAdapter);
429 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
430 new ArrayList<>();
431 for (AppTarget appTarget : resultList) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500432 if (appTarget.getShortcutInfo() == null) {
433 continue;
434 }
George Hodulik69d4a082019-01-18 11:27:03 -0800435 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
436 appTarget.getShortcutInfo(),
437 new ComponentName(
438 appTarget.getPackageName(), appTarget.getClassName())));
439 }
440 sendShareShortcutInfoList(shareShortcutInfos, driList);
441 };
442 mAppPredictor.registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
443 }
444
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500445 mChooserRowLayer = getResources().getDrawable(R.drawable.chooser_row_layer_list, null);
446 mChooserRowServiceSpacing = getResources()
447 .getDimensionPixelSize(R.dimen.chooser_service_spacing);
448
Kang Li9082f5b2016-12-02 10:56:21 -0800449 if (DEBUG) {
450 Log.d(TAG, "System Time Cost is " + systemCost);
451 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800452 }
Adam Powelle49d9392014-07-17 18:45:19 -0700453
Matt Pietal26038402019-01-08 07:29:34 -0500454 /**
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500455 * Check if the profile currently used is a work profile.
456 * @return true if it is work profile, false if it is parent profile (or no work profile is
457 * set up)
458 */
459 protected boolean isWorkProfile() {
460 return ((UserManager) getSystemService(Context.USER_SERVICE))
461 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
462 }
463
464 /**
Matt Pietal26038402019-01-08 07:29:34 -0500465 * Override method to add content preview area, specific to the chooser activity.
466 */
467 @Override
468 public void setHeader() {
469 super.setHeader();
470
471 Intent targetIntent = getTargetIntent();
472 if (targetIntent == null) {
473 return;
474 }
475
Matt Pietal26038402019-01-08 07:29:34 -0500476 String action = targetIntent.getAction();
477 if (!(Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action))) {
Matt Pietal26038402019-01-08 07:29:34 -0500478 return;
479 }
480
Matt Pietal46d828c2019-02-05 08:07:07 -0500481 if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) {
482 return;
483 }
484
Matt Pietal0ea391b2019-01-30 10:44:15 -0500485 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500486
487 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
488 .setSubtype(previewType));
Matt Pietal0ea391b2019-01-30 10:44:15 -0500489 displayContentPreview(previewType, targetIntent);
Matt Pietal26038402019-01-08 07:29:34 -0500490 }
491
Matt Pietal46d828c2019-02-05 08:07:07 -0500492 private void onCopyButtonClicked(View v) {
493 Intent targetIntent = getTargetIntent();
494 if (targetIntent == null) {
495 finish();
496 } else {
497 final String action = targetIntent.getAction();
498
499 ClipData clipData = null;
500 if (Intent.ACTION_SEND.equals(action)) {
501 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
502 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
503
504 if (extraText != null) {
505 clipData = ClipData.newPlainText(null, extraText);
506 } else if (extraStream != null) {
507 clipData = ClipData.newUri(getContentResolver(), null, extraStream);
508 } else {
509 Log.w(TAG, "No data available to copy to clipboard");
510 return;
511 }
512 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
513 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
514 Intent.EXTRA_STREAM);
515 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
516 for (int i = 1; i < streams.size(); i++) {
517 clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
518 }
519 } else {
520 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
521 // so warn about unexpected action
522 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
523 return;
524 }
525
526 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
527 Context.CLIPBOARD_SERVICE);
528 clipboardManager.setPrimaryClip(clipData);
529 Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
530
531 finish();
532 }
533 }
534
Matt Pietal18bbd822019-02-12 15:21:36 -0500535 @Override
536 public void onConfigurationChanged(Configuration newConfig) {
537 super.onConfigurationChanged(newConfig);
538
539 int width = -1;
540 if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
541 width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
542 }
543
544 updateLayoutWidth(R.id.content_preview_text_layout, width);
545 updateLayoutWidth(R.id.content_preview_title_layout, width);
546 updateLayoutWidth(R.id.content_preview_file_layout, width);
547 }
548
549 private void updateLayoutWidth(int layoutResourceId, int width) {
550 View view = findViewById(layoutResourceId);
551 LayoutParams params = view.getLayoutParams();
552 params.width = width;
553 view.setLayoutParams(params);
554 }
555
Matt Pietal0ea391b2019-01-30 10:44:15 -0500556 private void displayContentPreview(@ContentPreviewType int previewType, Intent targetIntent) {
557 switch (previewType) {
558 case CONTENT_PREVIEW_TEXT:
559 displayTextContentPreview(targetIntent);
560 break;
561 case CONTENT_PREVIEW_IMAGE:
562 displayImageContentPreview(targetIntent);
563 break;
564 case CONTENT_PREVIEW_FILE:
565 displayFileContentPreview(targetIntent);
566 break;
567 default:
568 Log.e(TAG, "Unexpected content preview type: " + previewType);
569 }
570 }
571
572 private void displayTextContentPreview(Intent targetIntent) {
573 ViewGroup contentPreviewLayout = findViewById(R.id.content_preview_text_area);
574 contentPreviewLayout.setVisibility(View.VISIBLE);
575
Matt Pietal46d828c2019-02-05 08:07:07 -0500576 findViewById(R.id.copy_button).setOnClickListener(this::onCopyButtonClicked);
577
Matt Pietal26038402019-01-08 07:29:34 -0500578 CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
Matt Pietal26038402019-01-08 07:29:34 -0500579 if (sharingText == null) {
Matt Pietal1fa7d802019-01-30 10:44:15 -0500580 findViewById(R.id.content_preview_text_layout).setVisibility(View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500581 } else {
Matt Pietal1fa7d802019-01-30 10:44:15 -0500582 TextView textView = findViewById(R.id.content_preview_text);
583 textView.setText(sharingText);
Matt Pietal26038402019-01-08 07:29:34 -0500584 }
585
586 String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
Matt Pietal46d828c2019-02-05 08:07:07 -0500587 if (TextUtils.isEmpty(previewTitle)) {
Matt Pietal1fa7d802019-01-30 10:44:15 -0500588 findViewById(R.id.content_preview_title_layout).setVisibility(View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500589 } else {
Matt Pietal1fa7d802019-01-30 10:44:15 -0500590 TextView previewTitleView = findViewById(R.id.content_preview_title);
Matt Pietal26038402019-01-08 07:29:34 -0500591 previewTitleView.setText(previewTitle);
Matt Pietal26038402019-01-08 07:29:34 -0500592
Matt Pietal1fa7d802019-01-30 10:44:15 -0500593 ClipData previewData = targetIntent.getClipData();
594 Uri previewThumbnail = null;
595 if (previewData != null) {
596 if (previewData.getItemCount() > 0) {
597 ClipData.Item previewDataItem = previewData.getItemAt(0);
598 previewThumbnail = previewDataItem.getUri();
599 }
Matt Pietal26038402019-01-08 07:29:34 -0500600 }
Matt Pietal26038402019-01-08 07:29:34 -0500601
Matt Pietal1fa7d802019-01-30 10:44:15 -0500602 ImageView previewThumbnailView = findViewById(R.id.content_preview_thumbnail);
603 if (previewThumbnail == null) {
Matt Pietal26038402019-01-08 07:29:34 -0500604 previewThumbnailView.setVisibility(View.GONE);
605 } else {
Matt Pietal1fa7d802019-01-30 10:44:15 -0500606 Bitmap bmp = loadThumbnail(previewThumbnail, new Size(100, 100));
607 if (bmp == null) {
608 previewThumbnailView.setVisibility(View.GONE);
609 } else {
610 previewThumbnailView.setImageBitmap(bmp);
611 }
Matt Pietal26038402019-01-08 07:29:34 -0500612 }
613 }
614 }
615
Matt Pietal0ea391b2019-01-30 10:44:15 -0500616 private void displayImageContentPreview(Intent targetIntent) {
617 ViewGroup contentPreviewLayout = findViewById(R.id.content_preview_image_area);
618 contentPreviewLayout.setVisibility(View.VISIBLE);
619
620 String action = targetIntent.getAction();
621 if (Intent.ACTION_SEND.equals(action)) {
622 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
623 loadUriIntoView(R.id.content_preview_image_1_large, uri);
624 } else {
625 ContentResolver resolver = getContentResolver();
626
627 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
628 List<Uri> imageUris = new ArrayList<>();
629 for (Uri uri : uris) {
630 if (isImageType(resolver.getType(uri))) {
631 imageUris.add(uri);
632 }
633 }
634
635 if (imageUris.size() == 0) {
636 Log.i(TAG, "Attempted to display image preview area with zero"
637 + " available images detected in EXTRA_STREAM list");
Matt Pietal46d828c2019-02-05 08:07:07 -0500638 contentPreviewLayout.setVisibility(View.GONE);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500639 return;
640 }
641
642 loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0));
643
644 if (imageUris.size() == 2) {
645 loadUriIntoView(R.id.content_preview_image_2_large, imageUris.get(1));
646 } else if (imageUris.size() > 2) {
647 loadUriIntoView(R.id.content_preview_image_2_small, imageUris.get(1));
648 RoundedRectImageView imageView = loadUriIntoView(
649 R.id.content_preview_image_3_small, imageUris.get(2));
650
651 if (imageUris.size() > 3) {
652 imageView.setExtraImageCount(imageUris.size() - 3);
653 }
654 }
655 }
656 }
657
Matt Pietal46d828c2019-02-05 08:07:07 -0500658 private static class FileInfo {
659 public final String name;
660 public final boolean hasThumbnail;
661
662 FileInfo(String name, boolean hasThumbnail) {
663 this.name = name;
664 this.hasThumbnail = hasThumbnail;
665 }
666 }
667
Matt Pietalf38e9d22019-02-15 10:01:03 -0500668 /**
669 * Wrapping the ContentResolver call to expose for easier mocking,
670 * and to avoid mocking Android core classes.
671 */
672 @VisibleForTesting
673 public Cursor queryResolver(ContentResolver resolver, Uri uri) {
674 return resolver.query(uri, null, null, null, null);
675 }
676
Matt Pietal46d828c2019-02-05 08:07:07 -0500677 private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
678 String fileName = null;
679 boolean hasThumbnail = false;
Matt Pietal3087bca2019-02-14 12:19:16 -0500680
Matt Pietalf38e9d22019-02-15 10:01:03 -0500681 try (Cursor cursor = queryResolver(resolver, uri)) {
682 if (cursor != null && cursor.getCount() > 0) {
683 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
684 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
685 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
686
687 cursor.moveToFirst();
688 if (nameIndex != -1) {
689 fileName = cursor.getString(nameIndex);
690 } else if (titleIndex != -1) {
691 fileName = cursor.getString(titleIndex);
692 }
693
694 if (flagsIndex != -1) {
695 hasThumbnail = (cursor.getInt(flagsIndex)
696 & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
697 }
698 }
Matt Pietal3087bca2019-02-14 12:19:16 -0500699 } catch (SecurityException e) {
700 Log.w(TAG, "Error loading file preview", e);
701 }
702
Matt Pietal46d828c2019-02-05 08:07:07 -0500703 if (TextUtils.isEmpty(fileName)) {
704 fileName = uri.getPath();
705 int index = fileName.lastIndexOf('/');
706 if (index != -1) {
707 fileName = fileName.substring(index + 1);
708 }
709 }
710
711 return new FileInfo(fileName, hasThumbnail);
712 }
713
Matt Pietal0ea391b2019-01-30 10:44:15 -0500714 private void displayFileContentPreview(Intent targetIntent) {
Matt Pietal46d828c2019-02-05 08:07:07 -0500715 ViewGroup contentPreviewLayout = findViewById(R.id.content_preview_file_area);
716 contentPreviewLayout.setVisibility(View.VISIBLE);
717
718 // TODO(b/120417119): Disable file copy until after moving to sysui,
719 // due to permissions issues
720 findViewById(R.id.file_copy_button).setVisibility(View.GONE);
721
Matt Pietal3087bca2019-02-14 12:19:16 -0500722 String action = targetIntent.getAction();
723 if (Intent.ACTION_SEND.equals(action)) {
724 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
725 loadFileUriIntoView(uri);
726 } else {
727 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
728 int uriCount = uris.size();
Matt Pietal46d828c2019-02-05 08:07:07 -0500729
Matt Pietal3087bca2019-02-14 12:19:16 -0500730 if (uriCount == 0) {
731 contentPreviewLayout.setVisibility(View.GONE);
732 Log.i(TAG,
733 "Appears to be no uris available in EXTRA_STREAM, removing "
734 + "preview area");
735 return;
736 } else if (uriCount == 1) {
737 loadFileUriIntoView(uris.get(0));
Matt Pietal46d828c2019-02-05 08:07:07 -0500738 } else {
Matt Pietal3087bca2019-02-14 12:19:16 -0500739 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
740 int remUriCount = uriCount - 1;
Matt Pietalacabc572019-02-14 11:02:05 -0500741 String fileName = getResources().getQuantityString(R.plurals.file_count,
Matt Pietal3087bca2019-02-14 12:19:16 -0500742 remUriCount, fileInfo.name, remUriCount);
Matt Pietalacabc572019-02-14 11:02:05 -0500743
Matt Pietal3087bca2019-02-14 12:19:16 -0500744 TextView fileNameView = findViewById(R.id.content_preview_filename);
Matt Pietalacabc572019-02-14 11:02:05 -0500745 fileNameView.setText(fileName);
Matt Pietal3087bca2019-02-14 12:19:16 -0500746
Matt Pietal46d828c2019-02-05 08:07:07 -0500747 ImageView fileIconView = findViewById(R.id.content_preview_file_icon);
748 fileIconView.setVisibility(View.VISIBLE);
Matt Pietalacabc572019-02-14 11:02:05 -0500749 fileIconView.setImageResource(R.drawable.ic_file_copy);
Matt Pietal46d828c2019-02-05 08:07:07 -0500750 }
Matt Pietal3087bca2019-02-14 12:19:16 -0500751 }
752 }
753
754 private void loadFileUriIntoView(Uri uri) {
755 FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
756
757 TextView fileNameView = findViewById(R.id.content_preview_filename);
758 fileNameView.setText(fileInfo.name);
759
760 if (fileInfo.hasThumbnail) {
761 loadUriIntoView(R.id.content_preview_file_thumbnail, uri);
762 } else {
763 ImageView fileIconView = findViewById(R.id.content_preview_file_icon);
764 fileIconView.setVisibility(View.VISIBLE);
765 fileIconView.setImageResource(R.drawable.ic_doc_generic);
Matt Pietal46d828c2019-02-05 08:07:07 -0500766 }
Matt Pietal0ea391b2019-01-30 10:44:15 -0500767 }
768
769 private RoundedRectImageView loadUriIntoView(int imageResourceId, Uri uri) {
770 RoundedRectImageView imageView = findViewById(imageResourceId);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500771 Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
Matt Pietal46d828c2019-02-05 08:07:07 -0500772 if (bmp != null) {
773 imageView.setVisibility(View.VISIBLE);
774 imageView.setImageBitmap(bmp);
775 }
Matt Pietal0ea391b2019-01-30 10:44:15 -0500776
777 return imageView;
778 }
779
780 @VisibleForTesting
781 protected boolean isImageType(String mimeType) {
782 return mimeType != null && mimeType.startsWith("image/");
783 }
784
785 @ContentPreviewType
786 private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
787 if (uri == null) {
788 return CONTENT_PREVIEW_TEXT;
789 }
790
791 String mimeType = resolver.getType(uri);
792 return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
793 }
794
795 /**
796 * In {@link android.content.Intent#getType}, the app may specify a very general
797 * mime-type that broadly covers all data being shared, such as {@literal *}/*
798 * when sending an image and text. We therefore should inspect each item for the
799 * the preferred type, in order of IMAGE, FILE, TEXT.
800 */
801 @ContentPreviewType
802 private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
803 String action = targetIntent.getAction();
804 if (Intent.ACTION_SEND.equals(action)) {
805 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
806 return findPreferredContentPreview(uri, resolver);
807 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
808 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
809 if (uris == null || uris.isEmpty()) {
810 return CONTENT_PREVIEW_TEXT;
811 }
812
813 for (Uri uri : uris) {
814 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_IMAGE) {
815 return CONTENT_PREVIEW_IMAGE;
816 }
817 }
818
819 return CONTENT_PREVIEW_FILE;
820 }
821
822 return CONTENT_PREVIEW_TEXT;
823 }
824
Adam Powell23882512016-01-29 10:21:00 -0800825 static SharedPreferences getPinnedSharedPrefs(Context context) {
826 // The code below is because in the android:ui process, no one can hear you scream.
827 // The package info in the context isn't initialized in the way it is for normal apps,
828 // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
829 // build the path manually below using the same policy that appears in ContextImpl.
830 // This fails silently under the hood if there's a problem, so if we find ourselves in
831 // the case where we don't have access to credential encrypted storage we just won't
832 // have our pinned target info.
833 final File prefsFile = new File(new File(
Jeff Sharkey8212ae02016-02-10 14:46:43 -0700834 Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
Adam Powell23882512016-01-29 10:21:00 -0800835 context.getUserId(), context.getPackageName()),
836 "shared_prefs"),
837 PINNED_SHARED_PREFS_NAME + ".xml");
838 return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
839 }
840
Adam Powell0b3c1122014-10-09 12:50:14 -0700841 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -0700842 protected void onDestroy() {
843 super.onDestroy();
844 if (mRefinementResultReceiver != null) {
845 mRefinementResultReceiver.destroy();
846 mRefinementResultReceiver = null;
847 }
Adam Powell9761ab22015-09-08 17:01:49 -0700848 unbindRemainingServices();
849 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
George Hodulik69d4a082019-01-18 11:27:03 -0800850 if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
851 mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
852 mAppPredictor.destroy();
853 }
Adam Powell2ed547e2015-04-29 18:45:04 -0700854 }
855
856 @Override
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +0000857 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
858 Intent result = defIntent;
Adam Powelle49d9392014-07-17 18:45:19 -0700859 if (mReplacementExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +0000860 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
Adam Powelle49d9392014-07-17 18:45:19 -0700861 if (replExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +0000862 result = new Intent(defIntent);
Adam Powelle49d9392014-07-17 18:45:19 -0700863 result.putExtras(replExtras);
Adam Powelle49d9392014-07-17 18:45:19 -0700864 }
865 }
Nicolas Prevot741abfc2015-08-11 12:03:51 +0100866 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +0000867 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
868 result = Intent.createChooser(result,
869 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
Hakan Seyalioglu7317e8a2016-12-12 16:15:38 -0800870
871 // Don't auto-launch single intents if the intent is being forwarded. This is done
872 // because automatically launching a resolving application as a response to the user
873 // action of switching accounts is pretty unexpected.
874 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +0000875 }
876 return result;
Adam Powelle49d9392014-07-17 18:45:19 -0700877 }
878
Adam Powell0b3c1122014-10-09 12:50:14 -0700879 @Override
Adam Powell23882512016-01-29 10:21:00 -0800880 public void onActivityStarted(TargetInfo cti) {
Adam Powell0b3c1122014-10-09 12:50:14 -0700881 if (mChosenComponentSender != null) {
Adam Powell24428412015-04-01 17:19:56 -0700882 final ComponentName target = cti.getResolvedComponentName();
Adam Powell0b3c1122014-10-09 12:50:14 -0700883 if (target != null) {
884 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
885 try {
886 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
887 } catch (IntentSender.SendIntentException e) {
888 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
889 + "the chosen component: " + e);
890 }
891 }
892 }
893 }
894
Adam Powell24428412015-04-01 17:19:56 -0700895 @Override
Hakan Seyalioglu13405c52017-01-31 19:01:31 -0800896 public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
Adam Powell7d758002015-05-06 17:49:36 -0700897 final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
898 mChooserListAdapter = (ChooserListAdapter) adapter;
Adam Powell52c39212016-04-07 15:14:18 -0700899 if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
900 mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets));
901 }
Adam Powell63b31692015-09-28 10:45:00 -0700902 mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
903 mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView));
904 adapterView.setAdapter(mChooserRowAdapter);
Adam Powell7d758002015-05-06 17:49:36 -0700905 if (listView != null) {
906 listView.setItemsCanFocus(true);
907 }
908 }
909
910 @Override
Adam Powell23882512016-01-29 10:21:00 -0800911 public int getLayoutResource() {
Adam Powell7d758002015-05-06 17:49:36 -0700912 return R.layout.chooser_grid;
Adam Powell24428412015-04-01 17:19:56 -0700913 }
914
915 @Override
Adam Powell23882512016-01-29 10:21:00 -0800916 public boolean shouldGetActivityMetadata() {
Adam Powell24428412015-04-01 17:19:56 -0700917 return true;
918 }
919
Adam Powell9761ab22015-09-08 17:01:49 -0700920 @Override
Ben Lin145b0ca2016-10-14 14:23:40 -0700921 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
Hakan Seyalioglu13405c52017-01-31 19:01:31 -0800922 // Note that this is only safe because the Intent handled by the ChooserActivity is
923 // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
924 // method can not be replaced in the ResolverActivity whole hog.
Ben Lin145b0ca2016-10-14 14:23:40 -0700925 return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE,
926 super.shouldAutoLaunchSingleChoice(target));
927 }
928
929 @Override
Adam Powell23882512016-01-29 10:21:00 -0800930 public void showTargetDetails(ResolveInfo ri) {
sanryhuang296ca9e2018-03-31 11:17:13 +0800931 if (ri == null) {
932 return;
933 }
934
Adam Powell23882512016-01-29 10:21:00 -0800935 ComponentName name = ri.activityInfo.getComponentName();
936 boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
937 ResolverTargetActionsDialogFragment f =
938 new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
939 name, pinned);
940 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
941 }
942
Adam Powelle49d9392014-07-17 18:45:19 -0700943 private void modifyTargetIntent(Intent in) {
944 final String action = in.getAction();
945 if (Intent.ACTION_SEND.equals(action) ||
946 Intent.ACTION_SEND_MULTIPLE.equals(action)) {
947 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
Dianne Hackborn13420f22014-07-18 15:43:56 -0700948 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Adam Powelle49d9392014-07-17 18:45:19 -0700949 }
950 }
Adam Powell24428412015-04-01 17:19:56 -0700951
Adam Powell2ed547e2015-04-29 18:45:04 -0700952 @Override
953 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -0500954 if (target instanceof NotSelectableTargetInfo) {
955 return false;
956 }
957
Adam Powell2ed547e2015-04-29 18:45:04 -0700958 if (mRefinementIntentSender != null) {
959 final Intent fillIn = new Intent();
960 final List<Intent> sourceIntents = target.getAllSourceIntents();
961 if (!sourceIntents.isEmpty()) {
962 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
963 if (sourceIntents.size() > 1) {
964 final Intent[] alts = new Intent[sourceIntents.size() - 1];
965 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
966 alts[i - 1] = sourceIntents.get(i);
967 }
968 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
969 }
970 if (mRefinementResultReceiver != null) {
971 mRefinementResultReceiver.destroy();
972 }
973 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
974 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
975 mRefinementResultReceiver);
976 try {
977 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
978 return false;
979 } catch (SendIntentException e) {
980 Log.e(TAG, "Refinement IntentSender failed to send", e);
981 }
982 }
983 }
Kang Li9fa2a2c2017-01-06 13:33:24 -0800984 updateModelAndChooserCounts(target);
Adam Powell2ed547e2015-04-29 18:45:04 -0700985 return super.onTargetSelected(target, alwaysCheck);
986 }
987
Adam Powell98b7f892015-06-19 12:38:45 -0700988 @Override
Adam Powell23882512016-01-29 10:21:00 -0800989 public void startSelected(int which, boolean always, boolean filtered) {
Kang Li9082f5b2016-12-02 10:56:21 -0800990 final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
Adam Powell98b7f892015-06-19 12:38:45 -0700991 super.startSelected(which, always, filtered);
992
993 if (mChooserListAdapter != null) {
994 // Log the index of which type of target the user picked.
995 // Lower values mean the ranking was better.
996 int cat = 0;
997 int value = which;
998 switch (mChooserListAdapter.getPositionTargetType(which)) {
999 case ChooserListAdapter.TARGET_CALLER:
Chris Wrenf6e9228b2016-01-26 18:04:35 -05001000 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
Adam Powell98b7f892015-06-19 12:38:45 -07001001 break;
1002 case ChooserListAdapter.TARGET_SERVICE:
Chris Wrenf6e9228b2016-01-26 18:04:35 -05001003 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
Adam Powell98b7f892015-06-19 12:38:45 -07001004 value -= mChooserListAdapter.getCallerTargetCount();
1005 break;
1006 case ChooserListAdapter.TARGET_STANDARD:
Chris Wrenf6e9228b2016-01-26 18:04:35 -05001007 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
Adam Powell98b7f892015-06-19 12:38:45 -07001008 value -= mChooserListAdapter.getCallerTargetCount()
1009 + mChooserListAdapter.getServiceTargetCount();
1010 break;
1011 }
1012
1013 if (cat != 0) {
1014 MetricsLogger.action(this, cat, value);
1015 }
Kang Li9082f5b2016-12-02 10:56:21 -08001016
1017 if (mIsSuccessfullySelected) {
1018 if (DEBUG) {
1019 Log.d(TAG, "User Selection Time Cost is " + selectionCost);
1020 Log.d(TAG, "position of selected app/service/caller is " +
1021 Integer.toString(value));
1022 }
1023 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
1024 (int) selectionCost);
1025 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
1026 }
Adam Powell98b7f892015-06-19 12:38:45 -07001027 }
1028 }
1029
Adam Powell24428412015-04-01 17:19:56 -07001030 void queryTargetServices(ChooserListAdapter adapter) {
1031 final PackageManager pm = getPackageManager();
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001032 ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
Adam Powell24428412015-04-01 17:19:56 -07001033 int targetsToQuery = 0;
1034 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
1035 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
Adam Powell3a09c522015-10-21 13:21:28 -07001036 if (adapter.getScore(dri) == 0) {
1037 // A score of 0 means the app hasn't been used in some time;
1038 // don't query it as it's not likely to be relevant.
1039 continue;
1040 }
Adam Powell24428412015-04-01 17:19:56 -07001041 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001042 if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
1043 && sm.hasShareTargets(ai.packageName)) {
1044 // Share targets will be queried from ShortcutManager
1045 continue;
1046 }
Adam Powell24428412015-04-01 17:19:56 -07001047 final Bundle md = ai.metaData;
1048 final String serviceName = md != null ? convertServiceName(ai.packageName,
1049 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
1050 if (serviceName != null) {
1051 final ComponentName serviceComponent = new ComponentName(
1052 ai.packageName, serviceName);
1053 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
1054 .setComponent(serviceComponent);
1055
1056 if (DEBUG) {
1057 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
1058 }
1059
1060 try {
1061 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
1062 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
1063 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
1064 + " permission " + ChooserTargetService.BIND_PERMISSION
1065 + " - this service will not be queried for ChooserTargets."
1066 + " add android:permission=\""
1067 + ChooserTargetService.BIND_PERMISSION + "\""
1068 + " to the <service> tag for " + serviceComponent
1069 + " in the manifest.");
1070 continue;
1071 }
1072 } catch (NameNotFoundException e) {
Adam Powell52c39212016-04-07 15:14:18 -07001073 Log.e(TAG, "Could not look up service " + serviceComponent
1074 + "; component name not found");
Adam Powell24428412015-04-01 17:19:56 -07001075 continue;
1076 }
1077
Adam Powell9761ab22015-09-08 17:01:49 -07001078 final ChooserTargetServiceConnection conn =
1079 new ChooserTargetServiceConnection(this, dri);
Adam Powell52c39212016-04-07 15:14:18 -07001080
1081 // Explicitly specify Process.myUserHandle instead of calling bindService
1082 // to avoid the warning from calling from the system process without an explicit
1083 // user handle
1084 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
1085 Process.myUserHandle())) {
Adam Powell24428412015-04-01 17:19:56 -07001086 if (DEBUG) {
1087 Log.d(TAG, "Binding service connection for target " + dri
1088 + " intent " + serviceIntent);
1089 }
1090 mServiceConnections.add(conn);
1091 targetsToQuery++;
1092 }
1093 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001094 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
Matt Pietal26038402019-01-08 07:29:34 -05001095 if (DEBUG) {
1096 Log.d(TAG, "queryTargets hit query target limit "
1097 + QUERY_TARGET_SERVICE_LIMIT);
1098 }
Adam Powell24428412015-04-01 17:19:56 -07001099 break;
1100 }
1101 }
1102
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001103 if (DEBUG) {
1104 Log.d(TAG, "queryTargets setting watchdog timer for "
1105 + WATCHDOG_TIMEOUT_MILLIS + "ms");
1106 }
1107 mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT,
1108 WATCHDOG_TIMEOUT_MILLIS);
1109
1110 if (mServiceConnections.isEmpty()) {
Adam Powell4c470d62015-06-19 17:46:17 -07001111 sendVoiceChoicesIfNeeded();
Adam Powell24428412015-04-01 17:19:56 -07001112 }
1113 }
1114
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001115 private IntentFilter getTargetIntentFilter() {
1116 try {
1117 final Intent intent = getTargetIntent();
1118 String dataString = intent.getDataString();
1119 if (TextUtils.isEmpty(dataString)) {
1120 dataString = intent.getType();
1121 }
1122 return new IntentFilter(intent.getAction(), dataString);
1123 } catch (Exception e) {
1124 Log.e(TAG, "failed to get target intent filter " + e);
1125 return null;
1126 }
1127 }
1128
George Hodulik69d4a082019-01-18 11:27:03 -08001129 private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001130 // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
1131 // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
George Hodulik69d4a082019-01-18 11:27:03 -08001132 List<DisplayResolveInfo> driList = new ArrayList<>();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001133 int targetsToQuery = 0;
1134 for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
1135 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
1136 if (adapter.getScore(dri) == 0) {
1137 // A score of 0 means the app hasn't been used in some time;
1138 // don't query it as it's not likely to be relevant.
1139 continue;
1140 }
1141 driList.add(dri);
1142 targetsToQuery++;
1143 // TODO(b/121287224): Do we need this here? (similar to queryTargetServices)
1144 if (targetsToQuery >= SHARE_TARGET_QUERY_PACKAGE_LIMIT) {
1145 if (DEBUG) {
1146 Log.d(TAG, "queryTargets hit query target limit "
1147 + SHARE_TARGET_QUERY_PACKAGE_LIMIT);
1148 }
1149 break;
1150 }
1151 }
George Hodulik69d4a082019-01-18 11:27:03 -08001152 return driList;
1153 }
1154
1155 private void queryDirectShareTargets(ChooserListAdapter adapter) {
1156 if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
1157 mAppPredictor.requestPredictionUpdate();
1158 return;
1159 }
1160 final IntentFilter filter = getTargetIntentFilter();
1161 if (filter == null) {
1162 return;
1163 }
1164 final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001165
1166 AsyncTask.execute(() -> {
1167 ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
1168 List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
George Hodulik69d4a082019-01-18 11:27:03 -08001169 sendShareShortcutInfoList(resultList, driList);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001170 });
1171 }
1172
George Hodulik69d4a082019-01-18 11:27:03 -08001173 private void sendShareShortcutInfoList(
1174 List<ShortcutManager.ShareShortcutInfo> resultList,
1175 List<DisplayResolveInfo> driList) {
1176 // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
1177 // for direct share targets. After ShareSheet is refactored we should use the
1178 // ShareShortcutInfos directly.
1179 boolean resultMessageSent = false;
1180 for (int i = 0; i < driList.size(); i++) {
1181 List<ChooserTarget> chooserTargets = new ArrayList<>();
1182 for (int j = 0; j < resultList.size(); j++) {
1183 if (driList.get(i).getResolvedComponentName().equals(
1184 resultList.get(j).getTargetComponent())) {
1185 chooserTargets.add(convertToChooserTarget(resultList.get(j)));
1186 }
1187 }
1188 if (chooserTargets.isEmpty()) {
1189 continue;
1190 }
1191 final Message msg = Message.obtain();
1192 msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
1193 msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
1194 mChooserHandler.sendMessage(msg);
1195 resultMessageSent = true;
1196 }
1197
1198 if (resultMessageSent) {
1199 final Message msg = Message.obtain();
1200 msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
1201 mChooserHandler.sendMessage(msg);
1202 }
1203 }
1204
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001205 private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut) {
1206 ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
1207 Bundle extras = new Bundle();
1208 extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
1209 return new ChooserTarget(
1210 // The name of this target.
1211 shortcutInfo.getShortLabel(),
1212 // Don't load the icon until it is selected to be shown
1213 null,
1214 // The ranking score for this target (0.0-1.0); the system will omit items with low
1215 // scores when there are too many Direct Share items.
1216 0.5f,
1217 // The name of the component to be launched if this target is chosen.
1218 shareShortcut.getTargetComponent().clone(),
1219 // The extra values here will be merged into the Intent when this target is chosen.
1220 extras);
1221 }
1222
Adam Powell24428412015-04-01 17:19:56 -07001223 private String convertServiceName(String packageName, String serviceName) {
1224 if (TextUtils.isEmpty(serviceName)) {
1225 return null;
1226 }
1227
1228 final String fullName;
1229 if (serviceName.startsWith(".")) {
1230 // Relative to the app package. Prepend the app package name.
1231 fullName = packageName + serviceName;
1232 } else if (serviceName.indexOf('.') >= 0) {
1233 // Fully qualified package name.
1234 fullName = serviceName;
1235 } else {
1236 fullName = null;
1237 }
1238 return fullName;
1239 }
1240
1241 void unbindRemainingServices() {
1242 if (DEBUG) {
1243 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
1244 }
1245 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
1246 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
1247 if (DEBUG) Log.d(TAG, "unbinding " + conn);
1248 unbindService(conn);
Adam Powell9761ab22015-09-08 17:01:49 -07001249 conn.destroy();
Adam Powell24428412015-04-01 17:19:56 -07001250 }
1251 mServiceConnections.clear();
Adam Powell24428412015-04-01 17:19:56 -07001252 }
1253
Adam Powell23882512016-01-29 10:21:00 -08001254 public void onSetupVoiceInteraction() {
Adam Powell4c470d62015-06-19 17:46:17 -07001255 // Do nothing. We'll send the voice stuff ourselves.
1256 }
1257
Kang Li9fa2a2c2017-01-06 13:33:24 -08001258 void updateModelAndChooserCounts(TargetInfo info) {
Kang Li53b43142016-11-14 14:38:25 -08001259 if (info != null) {
George Hodulikf2b0d342019-01-25 12:43:54 -08001260 if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
1261 sendClickToAppPredictor(info);
1262 }
Kang Li53b43142016-11-14 14:38:25 -08001263 final ResolveInfo ri = info.getResolveInfo();
Kang Li64b018e2017-01-05 17:30:06 -08001264 Intent targetIntent = getTargetIntent();
1265 if (ri != null && ri.activityInfo != null && targetIntent != null) {
Kang Li0cef910d2017-01-05 09:14:36 -08001266 if (mAdapter != null) {
1267 mAdapter.updateModel(info.getResolvedComponentName());
Kang Li9fa2a2c2017-01-06 13:33:24 -08001268 mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
1269 targetIntent.getAction());
Kang Li0cef910d2017-01-05 09:14:36 -08001270 }
Kang Li53b43142016-11-14 14:38:25 -08001271 if (DEBUG) {
Kang Li64b018e2017-01-05 17:30:06 -08001272 Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
Kang Li64b018e2017-01-05 17:30:06 -08001273 Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
Kang Li53b43142016-11-14 14:38:25 -08001274 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001275 } else if (DEBUG) {
Kang Li53b43142016-11-14 14:38:25 -08001276 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
1277 }
1278 }
Kang Li9082f5b2016-12-02 10:56:21 -08001279 mIsSuccessfullySelected = true;
Kang Li53b43142016-11-14 14:38:25 -08001280 }
1281
George Hodulikf2b0d342019-01-25 12:43:54 -08001282 private void sendClickToAppPredictor(TargetInfo targetInfo) {
1283 if (!(targetInfo instanceof ChooserTargetInfo)) {
1284 return;
1285 }
1286 ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
1287 ComponentName componentName = chooserTarget.getComponentName();
1288 Bundle extras = chooserTarget.getIntentExtras();
1289 if (extras == null) {
1290 return;
1291 }
1292 String shortcutId = extras.getString(Intent.EXTRA_SHORTCUT_ID);
1293 if (shortcutId == null) {
1294 return;
1295 }
1296 mAppPredictor.notifyAppTargetEvent(
1297 new AppTargetEvent.Builder(
1298 new AppTarget(
1299 new AppTargetId(shortcutId),
1300 componentName.getPackageName(),
1301 componentName.getClassName(),
1302 getUser()),
1303 AppTargetEvent.ACTION_LAUNCH
1304 ).setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
1305 .build());
1306 }
1307
Adam Powell2ed547e2015-04-29 18:45:04 -07001308 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
1309 if (mRefinementResultReceiver != null) {
1310 mRefinementResultReceiver.destroy();
1311 mRefinementResultReceiver = null;
1312 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001313 if (selectedTarget == null) {
1314 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
1315 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
1316 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
1317 + " cannot match refined source intent " + matchingIntent);
Kang Li53b43142016-11-14 14:38:25 -08001318 } else {
1319 TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
1320 if (super.onTargetSelected(clonedTarget, false)) {
Kang Li9fa2a2c2017-01-06 13:33:24 -08001321 updateModelAndChooserCounts(clonedTarget);
Kang Li53b43142016-11-14 14:38:25 -08001322 finish();
1323 return;
1324 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001325 }
1326 onRefinementCanceled();
1327 }
1328
1329 void onRefinementCanceled() {
1330 if (mRefinementResultReceiver != null) {
1331 mRefinementResultReceiver.destroy();
1332 mRefinementResultReceiver = null;
1333 }
1334 finish();
1335 }
1336
1337 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
1338 final List<Intent> targetIntents = target.getAllSourceIntents();
1339 for (int i = 0, N = targetIntents.size(); i < N; i++) {
1340 final Intent targetIntent = targetIntents.get(i);
1341 if (targetIntent.filterEquals(matchingIntent)) {
1342 return true;
1343 }
1344 }
1345 return false;
1346 }
1347
Adam Powell666d82a2015-07-15 20:14:57 -07001348 void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
1349 if (targets == null) {
1350 return;
1351 }
1352
1353 final PackageManager pm = getPackageManager();
1354 for (int i = targets.size() - 1; i >= 0; i--) {
1355 final ChooserTarget target = targets.get(i);
1356 final ComponentName targetName = target.getComponentName();
1357 if (packageName != null && packageName.equals(targetName.getPackageName())) {
1358 // Anything from the original target's package is fine.
1359 continue;
1360 }
1361
1362 boolean remove;
1363 try {
1364 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
1365 remove = !ai.exported || ai.permission != null;
1366 } catch (NameNotFoundException e) {
1367 Log.e(TAG, "Target " + target + " returned by " + packageName
1368 + " component not found");
1369 remove = true;
1370 }
1371
1372 if (remove) {
1373 targets.remove(i);
1374 }
1375 }
1376 }
1377
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -05001378 protected MetricsLogger getMetricsLogger() {
1379 if (mMetricsLogger == null) {
1380 mMetricsLogger = new MetricsLogger();
1381 }
1382 return mMetricsLogger;
1383 }
1384
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001385 public class ChooserListController extends ResolverListController {
1386 public ChooserListController(Context context,
1387 PackageManager pm,
1388 Intent targetIntent,
1389 String referrerPackageName,
1390 int launchedFromUid) {
1391 super(context, pm, targetIntent, referrerPackageName, launchedFromUid);
1392 }
1393
1394 @Override
1395 boolean isComponentPinned(ComponentName name) {
1396 return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
1397 }
1398
1399 @Override
1400 boolean isComponentFiltered(ComponentName name) {
1401 if (mFilteredComponentNames == null) {
1402 return false;
1403 }
1404 for (ComponentName filteredComponentName : mFilteredComponentNames) {
1405 if (name.equals(filteredComponentName)) {
1406 return true;
1407 }
1408 }
1409 return false;
1410 }
1411
1412 @Override
1413 public float getScore(DisplayResolveInfo target) {
1414 if (target == null) {
1415 return CALLER_TARGET_SCORE_BOOST;
1416 }
1417 float score = super.getScore(target);
1418 if (target.isPinned()) {
1419 score += PINNED_TARGET_SCORE_BOOST;
1420 }
1421 return score;
1422 }
1423 }
1424
Adam Powell24428412015-04-01 17:19:56 -07001425 @Override
Adam Powell23882512016-01-29 10:21:00 -08001426 public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
Adam Powell7d758002015-05-06 17:49:36 -07001427 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
1428 boolean filterLastUsed) {
1429 final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001430 initialIntents, rList, launchedFromUid, filterLastUsed, createListController());
Adam Powell24428412015-04-01 17:19:56 -07001431 return adapter;
1432 }
1433
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001434 @VisibleForTesting
1435 protected ResolverListController createListController() {
1436 return new ChooserListController(
1437 this,
1438 mPm,
1439 getTargetIntent(),
1440 getReferrerPackageName(),
1441 mLaunchedFromUid);
1442 }
1443
Matt Pietal26038402019-01-08 07:29:34 -05001444 @VisibleForTesting
1445 protected Bitmap loadThumbnail(Uri uri, Size size) {
1446 if (uri == null || size == null) {
1447 return null;
1448 }
1449
1450 try {
Matt Pietal46d828c2019-02-05 08:07:07 -05001451 return ImageUtils.loadThumbnail(getContentResolver(), uri, size);
1452 } catch (IOException | NullPointerException | SecurityException ex) {
Matt Pietal26038402019-01-08 07:29:34 -05001453 Log.w(TAG, "Error loading preview thumbnail for uri: " + uri.toString(), ex);
1454 }
1455 return null;
1456 }
1457
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001458 interface ChooserTargetInfo extends TargetInfo {
1459 float getModifiedScore();
1460
1461 ChooserTarget getChooserTarget();
1462 }
1463
1464 /**
1465 * Distinguish between targets that selectable by the user, vs those that are
1466 * placeholders for the system while information is loading in an async manner.
1467 */
1468 abstract class NotSelectableTargetInfo implements ChooserTargetInfo {
1469
1470 public Intent getResolvedIntent() {
1471 return null;
1472 }
1473
1474 public ComponentName getResolvedComponentName() {
1475 return null;
1476 }
1477
1478 public boolean start(Activity activity, Bundle options) {
1479 return false;
1480 }
1481
1482 public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
1483 return false;
1484 }
1485
1486 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
1487 return false;
1488 }
1489
1490 public ResolveInfo getResolveInfo() {
1491 return null;
1492 }
1493
1494 public CharSequence getDisplayLabel() {
1495 return null;
1496 }
1497
1498 public CharSequence getExtendedInfo() {
1499 return null;
1500 }
1501
1502 public Drawable getBadgeIcon() {
1503 return null;
1504 }
1505
1506 public CharSequence getBadgeContentDescription() {
1507 return null;
1508 }
1509
1510 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
1511 return null;
1512 }
1513
1514 public List<Intent> getAllSourceIntents() {
1515 return null;
1516 }
1517
1518 public boolean isPinned() {
1519 return false;
1520 }
1521
1522 public float getModifiedScore() {
1523 return 0.1f;
1524 }
1525
1526 public ChooserTarget getChooserTarget() {
1527 return null;
1528 }
1529 }
1530
1531 final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
1532 public Drawable getDisplayIcon() {
1533 return getDrawable(R.drawable.resolver_icon_placeholder);
1534 }
1535 }
1536
1537
1538 final class EmptyTargetInfo extends NotSelectableTargetInfo {
1539 public Drawable getDisplayIcon() {
1540 return null;
1541 }
1542 }
1543
1544 final class SelectableTargetInfo implements ChooserTargetInfo {
Adam Powell2ed547e2015-04-29 18:45:04 -07001545 private final DisplayResolveInfo mSourceInfo;
Adam Powell0ccc0e92015-04-23 17:19:37 -07001546 private final ResolveInfo mBackupResolveInfo;
Adam Powell24428412015-04-01 17:19:56 -07001547 private final ChooserTarget mChooserTarget;
Adam Powell7d758002015-05-06 17:49:36 -07001548 private Drawable mBadgeIcon = null;
Alan Viverettece5d92c2015-07-31 16:46:56 -04001549 private CharSequence mBadgeContentDescription;
Adam Powell13036be2015-05-12 14:43:56 -07001550 private Drawable mDisplayIcon;
Adam Powell2ed547e2015-04-29 18:45:04 -07001551 private final Intent mFillInIntent;
1552 private final int mFillInFlags;
Adam Powella182e452015-07-06 16:57:56 -07001553 private final float mModifiedScore;
Adam Powell24428412015-04-01 17:19:56 -07001554
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001555 SelectableTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
Adam Powella182e452015-07-06 16:57:56 -07001556 float modifiedScore) {
Adam Powell24428412015-04-01 17:19:56 -07001557 mSourceInfo = sourceInfo;
1558 mChooserTarget = chooserTarget;
Adam Powella182e452015-07-06 16:57:56 -07001559 mModifiedScore = modifiedScore;
Adam Powell7d758002015-05-06 17:49:36 -07001560 if (sourceInfo != null) {
1561 final ResolveInfo ri = sourceInfo.getResolveInfo();
1562 if (ri != null) {
1563 final ActivityInfo ai = ri.activityInfo;
1564 if (ai != null && ai.applicationInfo != null) {
Alan Viverettece5d92c2015-07-31 16:46:56 -04001565 final PackageManager pm = getPackageManager();
1566 mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
1567 mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
Adam Powell7d758002015-05-06 17:49:36 -07001568 }
1569 }
1570 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001571 // TODO(b/121287224): do this in the background thread, and only for selected targets
1572 mDisplayIcon = getChooserTargetIconDrawable(chooserTarget);
Adam Powell0ccc0e92015-04-23 17:19:37 -07001573
1574 if (sourceInfo != null) {
1575 mBackupResolveInfo = null;
1576 } else {
1577 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
1578 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001579
1580 mFillInIntent = null;
1581 mFillInFlags = 0;
1582 }
1583
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001584 private SelectableTargetInfo(SelectableTargetInfo other, Intent fillInIntent, int flags) {
Adam Powell2ed547e2015-04-29 18:45:04 -07001585 mSourceInfo = other.mSourceInfo;
1586 mBackupResolveInfo = other.mBackupResolveInfo;
1587 mChooserTarget = other.mChooserTarget;
Adam Powell7d758002015-05-06 17:49:36 -07001588 mBadgeIcon = other.mBadgeIcon;
Alan Viverettece5d92c2015-07-31 16:46:56 -04001589 mBadgeContentDescription = other.mBadgeContentDescription;
Adam Powell2ed547e2015-04-29 18:45:04 -07001590 mDisplayIcon = other.mDisplayIcon;
1591 mFillInIntent = fillInIntent;
1592 mFillInFlags = flags;
Adam Powella182e452015-07-06 16:57:56 -07001593 mModifiedScore = other.mModifiedScore;
1594 }
1595
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001596 /**
1597 * Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
1598 * the call to LauncherApps#getShortcuts(ShortcutQuery).
1599 */
1600 // TODO(121287224): Refactor code to apply the suggestion above
1601 private Drawable getChooserTargetIconDrawable(ChooserTarget target) {
1602 final Icon icon = target.getIcon();
1603 if (icon != null) {
1604 return icon.loadDrawable(ChooserActivity.this);
1605 }
1606 if (!USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
1607 return null;
1608 }
1609
1610 Bundle extras = target.getIntentExtras();
1611 if (extras == null || !extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
1612 return null;
1613 }
1614 CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
1615 LauncherApps launcherApps = (LauncherApps) getSystemService(
1616 Context.LAUNCHER_APPS_SERVICE);
1617 final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
1618 q.setPackage(target.getComponentName().getPackageName());
1619 q.setShortcutIds(Arrays.asList(shortcutId.toString()));
1620 q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
1621 final List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(q, getUser());
1622 if (shortcuts != null && shortcuts.size() > 0) {
1623 return launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
1624 }
1625
1626 return null;
1627 }
1628
Adam Powella182e452015-07-06 16:57:56 -07001629 public float getModifiedScore() {
1630 return mModifiedScore;
Adam Powell24428412015-04-01 17:19:56 -07001631 }
1632
1633 @Override
1634 public Intent getResolvedIntent() {
Adam Powell7d758002015-05-06 17:49:36 -07001635 if (mSourceInfo != null) {
Adam Powell0ccc0e92015-04-23 17:19:37 -07001636 return mSourceInfo.getResolvedIntent();
1637 }
Adam Powell52c39212016-04-07 15:14:18 -07001638
1639 final Intent targetIntent = new Intent(getTargetIntent());
1640 targetIntent.setComponent(mChooserTarget.getComponentName());
1641 targetIntent.putExtras(mChooserTarget.getIntentExtras());
1642 return targetIntent;
Adam Powell24428412015-04-01 17:19:56 -07001643 }
1644
1645 @Override
1646 public ComponentName getResolvedComponentName() {
Adam Powell0ccc0e92015-04-23 17:19:37 -07001647 if (mSourceInfo != null) {
1648 return mSourceInfo.getResolvedComponentName();
1649 } else if (mBackupResolveInfo != null) {
1650 return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
1651 mBackupResolveInfo.activityInfo.name);
1652 }
1653 return null;
1654 }
1655
Adam Powell666d82a2015-07-15 20:14:57 -07001656 private Intent getBaseIntentToSend() {
Adam Powell52c39212016-04-07 15:14:18 -07001657 Intent result = getResolvedIntent();
Adam Powell2ed547e2015-04-29 18:45:04 -07001658 if (result == null) {
Adam Powell666d82a2015-07-15 20:14:57 -07001659 Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
Adam Powell13036be2015-05-12 14:43:56 -07001660 } else {
Adam Powell2ed547e2015-04-29 18:45:04 -07001661 result = new Intent(result);
Adam Powell13036be2015-05-12 14:43:56 -07001662 if (mFillInIntent != null) {
1663 result.fillIn(mFillInIntent, mFillInFlags);
1664 }
1665 result.fillIn(mReferrerFillInIntent, 0);
Adam Powell2ed547e2015-04-29 18:45:04 -07001666 }
1667 return result;
Adam Powell24428412015-04-01 17:19:56 -07001668 }
1669
1670 @Override
1671 public boolean start(Activity activity, Bundle options) {
Adam Powell666d82a2015-07-15 20:14:57 -07001672 throw new RuntimeException("ChooserTargets should be started as caller.");
Adam Powell24428412015-04-01 17:19:56 -07001673 }
1674
1675 @Override
Alison Cichowlas3e340502018-08-07 17:15:01 -04001676 public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
Adam Powell666d82a2015-07-15 20:14:57 -07001677 final Intent intent = getBaseIntentToSend();
Adam Powell2ed547e2015-04-29 18:45:04 -07001678 if (intent == null) {
1679 return false;
1680 }
Adam Powell666d82a2015-07-15 20:14:57 -07001681 intent.setComponent(mChooserTarget.getComponentName());
Makoto Onuki99302b52017-03-29 12:42:26 -07001682 intent.putExtras(mChooserTarget.getIntentExtras());
Adam Powell52c39212016-04-07 15:14:18 -07001683
1684 // Important: we will ignore the target security checks in ActivityManager
1685 // if and only if the ChooserTarget's target package is the same package
1686 // where we got the ChooserTargetService that provided it. This lets a
1687 // ChooserTargetService provide a non-exported or permission-guarded target
1688 // to the chooser for the user to pick.
1689 //
1690 // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
1691 // so we'll obey the caller's normal security checks.
1692 final boolean ignoreTargetSecurity = mSourceInfo != null
1693 && mSourceInfo.getResolvedComponentName().getPackageName()
1694 .equals(mChooserTarget.getComponentName().getPackageName());
Alison Cichowlas3e340502018-08-07 17:15:01 -04001695 return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
Adam Powell24428412015-04-01 17:19:56 -07001696 }
1697
1698 @Override
1699 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
Adam Powell666d82a2015-07-15 20:14:57 -07001700 throw new RuntimeException("ChooserTargets should be started as caller.");
Adam Powell24428412015-04-01 17:19:56 -07001701 }
1702
1703 @Override
1704 public ResolveInfo getResolveInfo() {
Adam Powell0ccc0e92015-04-23 17:19:37 -07001705 return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
Adam Powell24428412015-04-01 17:19:56 -07001706 }
1707
1708 @Override
1709 public CharSequence getDisplayLabel() {
1710 return mChooserTarget.getTitle();
1711 }
1712
1713 @Override
1714 public CharSequence getExtendedInfo() {
Adam Powell00f4aad2015-09-17 13:38:16 -07001715 // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
1716 return null;
Adam Powell24428412015-04-01 17:19:56 -07001717 }
1718
1719 @Override
1720 public Drawable getDisplayIcon() {
1721 return mDisplayIcon;
1722 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001723
1724 @Override
Adam Powell7d758002015-05-06 17:49:36 -07001725 public Drawable getBadgeIcon() {
1726 return mBadgeIcon;
1727 }
1728
1729 @Override
Alan Viverettece5d92c2015-07-31 16:46:56 -04001730 public CharSequence getBadgeContentDescription() {
1731 return mBadgeContentDescription;
1732 }
1733
George Hodulikf2b0d342019-01-25 12:43:54 -08001734 public ChooserTarget getChooserTarget() {
1735 return mChooserTarget;
1736 }
1737
Alan Viverettece5d92c2015-07-31 16:46:56 -04001738 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07001739 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001740 return new SelectableTargetInfo(this, fillInIntent, flags);
Adam Powell2ed547e2015-04-29 18:45:04 -07001741 }
1742
1743 @Override
1744 public List<Intent> getAllSourceIntents() {
1745 final List<Intent> results = new ArrayList<>();
1746 if (mSourceInfo != null) {
1747 // We only queried the service for the first one in our sourceinfo.
1748 results.add(mSourceInfo.getAllSourceIntents().get(0));
1749 }
1750 return results;
1751 }
Adam Powell23882512016-01-29 10:21:00 -08001752
1753 @Override
1754 public boolean isPinned() {
1755 return mSourceInfo != null ? mSourceInfo.isPinned() : false;
1756 }
Adam Powell24428412015-04-01 17:19:56 -07001757 }
1758
1759 public class ChooserListAdapter extends ResolveListAdapter {
Adam Powell7d758002015-05-06 17:49:36 -07001760 public static final int TARGET_BAD = -1;
1761 public static final int TARGET_CALLER = 0;
1762 public static final int TARGET_SERVICE = 1;
1763 public static final int TARGET_STANDARD = 2;
1764
Dan Sandler62aad002018-05-23 02:13:51 -04001765 private static final int MAX_SERVICE_TARGETS = 4;
Dan Sandlerf5e17692018-06-04 22:13:40 -04001766 private static final int MAX_TARGETS_PER_SERVICE = 2;
Adam Powella182e452015-07-06 16:57:56 -07001767
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001768 // Reserve spots for incoming direct share targets by adding placeholders
1769 private ChooserTargetInfo mPlaceHolderTargetInfo = new PlaceHolderTargetInfo();
1770 private List<ChooserTargetInfo> mServiceTargets;
Adam Powell7d758002015-05-06 17:49:36 -07001771 private final List<TargetInfo> mCallerTargets = new ArrayList<>();
Adam Powell565943f2016-02-11 10:29:05 -08001772 private boolean mShowServiceTargets;
Adam Powell24428412015-04-01 17:19:56 -07001773
Adam Powella182e452015-07-06 16:57:56 -07001774 private float mLateFee = 1.f;
1775
Dan Sandlerf5e17692018-06-04 22:13:40 -04001776 private boolean mTargetsNeedPruning = false;
1777
Adam Powella182e452015-07-06 16:57:56 -07001778 private final BaseChooserTargetComparator mBaseTargetComparator
1779 = new BaseChooserTargetComparator();
1780
Adam Powell7d758002015-05-06 17:49:36 -07001781 public ChooserListAdapter(Context context, List<Intent> payloadIntents,
1782 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001783 boolean filterLastUsed, ResolverListController resolverListController) {
Adam Powell7d758002015-05-06 17:49:36 -07001784 // Don't send the initial intents through the shared ResolverActivity path,
1785 // we want to separate them into a different section.
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001786 super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed,
1787 resolverListController);
Adam Powell0ccc0e92015-04-23 17:19:37 -07001788
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001789 mServiceTargets = createPlaceHolders();
1790
Adam Powell7d758002015-05-06 17:49:36 -07001791 if (initialIntents != null) {
1792 final PackageManager pm = getPackageManager();
1793 for (int i = 0; i < initialIntents.length; i++) {
1794 final Intent ii = initialIntents[i];
1795 if (ii == null) {
1796 continue;
1797 }
Adam Powell86100d12016-05-12 16:13:17 -07001798
1799 // We reimplement Intent#resolveActivityInfo here because if we have an
1800 // implicit intent, we want the ResolveInfo returned by PackageManager
1801 // instead of one we reconstruct ourselves. The ResolveInfo returned might
1802 // have extra metadata and resolvePackageName set and we want to respect that.
1803 ResolveInfo ri = null;
1804 ActivityInfo ai = null;
1805 final ComponentName cn = ii.getComponent();
1806 if (cn != null) {
1807 try {
1808 ai = pm.getActivityInfo(ii.getComponent(), 0);
1809 ri = new ResolveInfo();
1810 ri.activityInfo = ai;
1811 } catch (PackageManager.NameNotFoundException ignored) {
1812 // ai will == null below
1813 }
1814 }
1815 if (ai == null) {
1816 ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
1817 ai = ri != null ? ri.activityInfo : null;
1818 }
Adam Powell7d758002015-05-06 17:49:36 -07001819 if (ai == null) {
1820 Log.w(TAG, "No activity found for " + ii);
1821 continue;
1822 }
Adam Powell7d758002015-05-06 17:49:36 -07001823 UserManager userManager =
1824 (UserManager) getSystemService(Context.USER_SERVICE);
Adam Powell7d758002015-05-06 17:49:36 -07001825 if (ii instanceof LabeledIntent) {
Matt Pietal26038402019-01-08 07:29:34 -05001826 LabeledIntent li = (LabeledIntent) ii;
Adam Powell7d758002015-05-06 17:49:36 -07001827 ri.resolvePackageName = li.getSourcePackage();
1828 ri.labelRes = li.getLabelResource();
1829 ri.nonLocalizedLabel = li.getNonLocalizedLabel();
1830 ri.icon = li.getIconResource();
Sudheer Shanka9ded7602015-05-19 21:17:25 +01001831 ri.iconResourceId = ri.icon;
1832 }
1833 if (userManager.isManagedProfile()) {
1834 ri.noResourceId = true;
1835 ri.icon = 0;
Adam Powell7d758002015-05-06 17:49:36 -07001836 }
1837 mCallerTargets.add(new DisplayResolveInfo(ii, ri,
1838 ri.loadLabel(pm), null, ii));
Adam Powelld974c7b2015-04-28 15:41:46 -07001839 }
Adam Powell0ccc0e92015-04-23 17:19:37 -07001840 }
Adam Powell24428412015-04-01 17:19:56 -07001841 }
1842
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001843 private List<ChooserTargetInfo> createPlaceHolders() {
1844 List<ChooserTargetInfo> list = new ArrayList<>();
1845 for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
1846 list.add(mPlaceHolderTargetInfo);
1847 }
1848 return list;
1849 }
1850
Adam Powell24428412015-04-01 17:19:56 -07001851 @Override
1852 public boolean showsExtendedInfo(TargetInfo info) {
Adam Powell00f4aad2015-09-17 13:38:16 -07001853 // We have badges so we don't need this text shown.
1854 return false;
Adam Powell24428412015-04-01 17:19:56 -07001855 }
1856
1857 @Override
Adam Powell23882512016-01-29 10:21:00 -08001858 public boolean isComponentPinned(ComponentName name) {
1859 return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
1860 }
1861
1862 @Override
Adam Powell7d758002015-05-06 17:49:36 -07001863 public View onCreateView(ViewGroup parent) {
Adam Powell24428412015-04-01 17:19:56 -07001864 return mInflater.inflate(
1865 com.android.internal.R.layout.resolve_grid_item, parent, false);
1866 }
1867
1868 @Override
1869 public void onListRebuilt() {
Ng Zhi And3ec5fc2018-05-10 09:13:00 -07001870 // don't support direct share on low ram devices
1871 if (ActivityManager.isLowRamDeviceStatic()) {
1872 return;
1873 }
1874
Adam Powell24428412015-04-01 17:19:56 -07001875 if (mServiceTargets != null) {
Dan Sandlerf5e17692018-06-04 22:13:40 -04001876 if (getDisplayInfoCount() == 0) {
1877 // b/109676071: When packages change, onListRebuilt() is called before
1878 // ResolverActivity.mDisplayList is re-populated; pruning now would cause the
1879 // list to disappear briefly, so instead we detect this case (the
1880 // set of targets suddenly dropping to zero) and remember to prune later.
1881 mTargetsNeedPruning = true;
1882 }
Adam Powell24428412015-04-01 17:19:56 -07001883 }
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -08001884
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001885 if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
1886 if (DEBUG) {
1887 Log.d(TAG, "querying direct share targets from ShortcutManager");
1888 }
1889 queryDirectShareTargets(this);
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -08001890 }
1891 if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
1892 if (DEBUG) {
1893 Log.d(TAG, "List built querying services");
1894 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001895 queryTargetServices(this);
1896 }
Adam Powell24428412015-04-01 17:19:56 -07001897 }
1898
1899 @Override
Adam Powellc6d5e3a2015-04-23 12:22:20 -07001900 public boolean shouldGetResolvedFilter() {
1901 return true;
1902 }
1903
1904 @Override
Adam Powell24428412015-04-01 17:19:56 -07001905 public int getCount() {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001906 return super.getCount() + getSelectableServiceTargetCount() + getCallerTargetCount();
Adam Powell24428412015-04-01 17:19:56 -07001907 }
1908
Adam Powell50077352015-05-26 18:01:55 -07001909 @Override
1910 public int getUnfilteredCount() {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001911 return super.getUnfilteredCount() + getSelectableServiceTargetCount()
1912 + getCallerTargetCount();
Adam Powell50077352015-05-26 18:01:55 -07001913 }
1914
1915 public int getCallerTargetCount() {
Adam Powell7d758002015-05-06 17:49:36 -07001916 return mCallerTargets.size();
1917 }
1918
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001919 /**
1920 * Filter out placeholders and non-selectable service targets
1921 */
1922 public int getSelectableServiceTargetCount() {
1923 int count = 0;
1924 for (ChooserTargetInfo info : mServiceTargets) {
1925 if (info instanceof SelectableTargetInfo) {
1926 count++;
1927 }
Adam Powell565943f2016-02-11 10:29:05 -08001928 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001929 return count;
1930 }
1931
1932 public int getServiceTargetCount() {
Adam Powella182e452015-07-06 16:57:56 -07001933 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
Adam Powell7d758002015-05-06 17:49:36 -07001934 }
1935
1936 public int getStandardTargetCount() {
1937 return super.getCount();
1938 }
1939
1940 public int getPositionTargetType(int position) {
1941 int offset = 0;
1942
Adam Powella182e452015-07-06 16:57:56 -07001943 final int callerTargetCount = getCallerTargetCount();
Adam Powell7d758002015-05-06 17:49:36 -07001944 if (position < callerTargetCount) {
1945 return TARGET_CALLER;
1946 }
1947 offset += callerTargetCount;
1948
Adam Powella182e452015-07-06 16:57:56 -07001949 final int serviceTargetCount = getServiceTargetCount();
Adam Powell7d758002015-05-06 17:49:36 -07001950 if (position - offset < serviceTargetCount) {
1951 return TARGET_SERVICE;
1952 }
1953 offset += serviceTargetCount;
1954
1955 final int standardTargetCount = super.getCount();
1956 if (position - offset < standardTargetCount) {
1957 return TARGET_STANDARD;
1958 }
1959
1960 return TARGET_BAD;
1961 }
1962
Adam Powell24428412015-04-01 17:19:56 -07001963 @Override
1964 public TargetInfo getItem(int position) {
Adam Powell50077352015-05-26 18:01:55 -07001965 return targetInfoForPosition(position, true);
1966 }
1967
1968 @Override
1969 public TargetInfo targetInfoForPosition(int position, boolean filtered) {
Adam Powell24428412015-04-01 17:19:56 -07001970 int offset = 0;
Adam Powell0ccc0e92015-04-23 17:19:37 -07001971
Adam Powella182e452015-07-06 16:57:56 -07001972 final int callerTargetCount = getCallerTargetCount();
Adam Powell0ccc0e92015-04-23 17:19:37 -07001973 if (position < callerTargetCount) {
1974 return mCallerTargets.get(position);
Adam Powell24428412015-04-01 17:19:56 -07001975 }
Adam Powell0ccc0e92015-04-23 17:19:37 -07001976 offset += callerTargetCount;
1977
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001978 final int serviceTargetCount = filtered ? getServiceTargetCount() :
1979 getSelectableServiceTargetCount();
Adam Powell0ccc0e92015-04-23 17:19:37 -07001980 if (position - offset < serviceTargetCount) {
1981 return mServiceTargets.get(position - offset);
1982 }
1983 offset += serviceTargetCount;
1984
Adam Powell50077352015-05-26 18:01:55 -07001985 return filtered ? super.getItem(position - offset)
1986 : getDisplayInfoAt(position - offset);
Adam Powell24428412015-04-01 17:19:56 -07001987 }
1988
1989 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
Matt Pietal26038402019-01-08 07:29:34 -05001990 if (DEBUG) {
1991 Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
1992 + " targets");
1993 }
Dan Sandlerf5e17692018-06-04 22:13:40 -04001994
1995 if (mTargetsNeedPruning && targets.size() > 0) {
1996 // First proper update since we got an onListRebuilt() with (transient) 0 items.
1997 // Clear out the target list and rebuild.
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05001998 mServiceTargets = createPlaceHolders();
Dan Sandlerf5e17692018-06-04 22:13:40 -04001999 mTargetsNeedPruning = false;
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002000
2001 // Add back any app-supplied direct share targets that may have been
2002 // wiped by this clear
2003 if (mCallerChooserTargets != null) {
2004 addServiceResults(null, Lists.newArrayList(mCallerChooserTargets));
2005 }
Dan Sandlerf5e17692018-06-04 22:13:40 -04002006 }
2007
Adam Powella182e452015-07-06 16:57:56 -07002008 final float parentScore = getScore(origTarget);
2009 Collections.sort(targets, mBaseTargetComparator);
2010 float lastScore = 0;
Adam Powellbba00302016-02-03 16:01:09 -08002011 for (int i = 0, N = Math.min(targets.size(), MAX_TARGETS_PER_SERVICE); i < N; i++) {
Adam Powella182e452015-07-06 16:57:56 -07002012 final ChooserTarget target = targets.get(i);
2013 float targetScore = target.getScore();
2014 targetScore *= parentScore;
2015 targetScore *= mLateFee;
2016 if (i > 0 && targetScore >= lastScore) {
2017 // Apply a decay so that the top app can't crowd out everything else.
2018 // This incents ChooserTargetServices to define what's truly better.
2019 targetScore = lastScore * 0.95f;
2020 }
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002021 insertServiceTarget(new SelectableTargetInfo(origTarget, target, targetScore));
Adam Powella182e452015-07-06 16:57:56 -07002022
2023 if (DEBUG) {
2024 Log.d(TAG, " => " + target.toString() + " score=" + targetScore
2025 + " base=" + target.getScore()
2026 + " lastScore=" + lastScore
2027 + " parentScore=" + parentScore
2028 + " lateFee=" + mLateFee);
2029 }
2030
2031 lastScore = targetScore;
Adam Powell24428412015-04-01 17:19:56 -07002032 }
2033
Adam Powella182e452015-07-06 16:57:56 -07002034 mLateFee *= 0.95f;
Adam Powell24428412015-04-01 17:19:56 -07002035
2036 notifyDataSetChanged();
2037 }
2038
Adam Powell565943f2016-02-11 10:29:05 -08002039 /**
2040 * Set to true to reveal all service targets at once.
2041 */
2042 public void setShowServiceTargets(boolean show) {
Susi Kharraz-Postfcec9932019-03-01 16:46:26 -05002043 // mShowServiceTargets is only flipped once to show direct share targets. But after the
2044 // initial display the list can be re-sorted and the user will see the target list
2045 // change. This will log the initial show and the subsequent shuffle to help us get
2046 // accurate timing of the UX.
2047 if (show) {
2048 getMetricsLogger().write(
2049 new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN_DIRECT_TARGET)
2050 .setSubtype(mShowServiceTargets ? MetricsEvent.PREVIOUSLY_VISIBLE
2051 : MetricsEvent.PREVIOUSLY_HIDDEN));
2052 }
Adam Powell08adbfe2017-05-10 07:48:30 -07002053 if (show != mShowServiceTargets) {
2054 mShowServiceTargets = show;
2055 notifyDataSetChanged();
2056 }
Adam Powell565943f2016-02-11 10:29:05 -08002057 }
2058
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002059 /**
2060 * Calling this marks service target loading complete, and will attempt to no longer
2061 * update the direct share area.
2062 */
2063 public void completeServiceTargetLoading() {
2064 mServiceTargets.removeIf(o -> o instanceof PlaceHolderTargetInfo);
2065
2066 if (mServiceTargets.isEmpty()) {
2067 mServiceTargets.add(new EmptyTargetInfo());
2068 }
2069 notifyDataSetChanged();
2070 }
2071
Adam Powella182e452015-07-06 16:57:56 -07002072 private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002073 // Avoid inserting any potentially late results
2074 if (mServiceTargets.size() == 1
2075 && mServiceTargets.get(0) instanceof EmptyTargetInfo) {
2076 return;
2077 }
2078
Adam Powella182e452015-07-06 16:57:56 -07002079 final float newScore = chooserTargetInfo.getModifiedScore();
2080 for (int i = 0, N = mServiceTargets.size(); i < N; i++) {
2081 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002082 if (serviceTarget == null) {
2083 mServiceTargets.set(i, chooserTargetInfo);
2084 return;
2085 } else if (newScore > serviceTarget.getModifiedScore()) {
Adam Powella182e452015-07-06 16:57:56 -07002086 mServiceTargets.add(i, chooserTargetInfo);
2087 return;
2088 }
2089 }
2090 mServiceTargets.add(chooserTargetInfo);
2091 }
Adam Powell24428412015-04-01 17:19:56 -07002092 }
2093
Adam Powella182e452015-07-06 16:57:56 -07002094 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
2095 @Override
2096 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
2097 // Descending order
Adam Powell77a533f2015-10-16 10:47:32 -07002098 return (int) Math.signum(rhs.getScore() - lhs.getScore());
Adam Powella182e452015-07-06 16:57:56 -07002099 }
2100 }
2101
Adam Powell7d758002015-05-06 17:49:36 -07002102 class ChooserRowAdapter extends BaseAdapter {
2103 private ChooserListAdapter mChooserListAdapter;
2104 private final LayoutInflater mLayoutInflater;
2105 private final int mColumnCount = 4;
Adam Powell08adbfe2017-05-10 07:48:30 -07002106 private int mAnimationCount = 0;
Adam Powell7d758002015-05-06 17:49:36 -07002107
2108 public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
2109 mChooserListAdapter = wrappedAdapter;
2110 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
2111
2112 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
2113 @Override
2114 public void onChanged() {
2115 super.onChanged();
2116 notifyDataSetChanged();
2117 }
2118
2119 @Override
2120 public void onInvalidated() {
2121 super.onInvalidated();
2122 notifyDataSetInvalidated();
2123 }
2124 });
2125 }
2126
2127 @Override
2128 public int getCount() {
2129 return (int) (
Adam Powell63b31692015-09-28 10:45:00 -07002130 getCallerTargetRowCount()
Matt Pietal26038402019-01-08 07:29:34 -05002131 + getServiceTargetRowCount()
2132 + Math.ceil(
2133 (float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
Adam Powell7d758002015-05-06 17:49:36 -07002134 );
2135 }
2136
Adam Powell63b31692015-09-28 10:45:00 -07002137 public int getCallerTargetRowCount() {
2138 return (int) Math.ceil(
2139 (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount);
2140 }
2141
Dan Sandler62aad002018-05-23 02:13:51 -04002142 // There can be at most one row of service targets.
Adam Powell63b31692015-09-28 10:45:00 -07002143 public int getServiceTargetRowCount() {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002144 return 1;
Adam Powell63b31692015-09-28 10:45:00 -07002145 }
2146
Adam Powell7d758002015-05-06 17:49:36 -07002147 @Override
2148 public Object getItem(int position) {
2149 // We have nothing useful to return here.
2150 return position;
2151 }
2152
2153 @Override
2154 public long getItemId(int position) {
2155 return position;
2156 }
2157
2158 @Override
2159 public View getView(int position, View convertView, ViewGroup parent) {
Adam Powell63b31692015-09-28 10:45:00 -07002160 final RowViewHolder holder;
Adam Powell7d758002015-05-06 17:49:36 -07002161 if (convertView == null) {
2162 holder = createViewHolder(parent);
2163 } else {
Adam Powell63b31692015-09-28 10:45:00 -07002164 holder = (RowViewHolder) convertView.getTag();
Adam Powell7d758002015-05-06 17:49:36 -07002165 }
2166 bindViewHolder(position, holder);
2167
Adam Powell63b31692015-09-28 10:45:00 -07002168 return holder.row;
Adam Powell7d758002015-05-06 17:49:36 -07002169 }
2170
Adam Powell63b31692015-09-28 10:45:00 -07002171 RowViewHolder createViewHolder(ViewGroup parent) {
Adam Powell7d758002015-05-06 17:49:36 -07002172 final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
2173 parent, false);
Adam Powell63b31692015-09-28 10:45:00 -07002174 final RowViewHolder holder = new RowViewHolder(row, mColumnCount);
2175 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
2176
Adam Powell7d758002015-05-06 17:49:36 -07002177 for (int i = 0; i < mColumnCount; i++) {
Adam Powell63b31692015-09-28 10:45:00 -07002178 final View v = mChooserListAdapter.createView(row);
Adam Powell4eb98712015-10-14 13:10:18 -07002179 final int column = i;
Adam Powell63b31692015-09-28 10:45:00 -07002180 v.setOnClickListener(new OnClickListener() {
2181 @Override
2182 public void onClick(View v) {
Adam Powell4eb98712015-10-14 13:10:18 -07002183 startSelected(holder.itemIndices[column], false, true);
Adam Powell63b31692015-09-28 10:45:00 -07002184 }
2185 });
2186 v.setOnLongClickListener(new OnLongClickListener() {
2187 @Override
2188 public boolean onLongClick(View v) {
Adam Powell23882512016-01-29 10:21:00 -08002189 showTargetDetails(
Adam Powell4eb98712015-10-14 13:10:18 -07002190 mChooserListAdapter.resolveInfoForPosition(
2191 holder.itemIndices[column], true));
Adam Powell63b31692015-09-28 10:45:00 -07002192 return true;
2193 }
2194 });
2195 row.addView(v);
2196 holder.cells[i] = v;
2197
2198 // Force height to be a given so we don't have visual disruption during scaling.
2199 LayoutParams lp = v.getLayoutParams();
2200 v.measure(spec, spec);
2201 if (lp == null) {
2202 lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight());
2203 row.setLayoutParams(lp);
2204 } else {
2205 lp.height = v.getMeasuredHeight();
2206 }
Jason Monk027dcfa2017-06-27 18:37:35 -04002207 if (i != (mColumnCount - 1)) {
2208 row.addView(new Space(ChooserActivity.this),
2209 new LinearLayout.LayoutParams(0, 0, 1));
2210 }
Adam Powell63b31692015-09-28 10:45:00 -07002211 }
2212
2213 // Pre-measure so we can scale later.
2214 holder.measure();
2215 LayoutParams lp = row.getLayoutParams();
2216 if (lp == null) {
2217 lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.measuredRowHeight);
2218 row.setLayoutParams(lp);
2219 } else {
2220 lp.height = holder.measuredRowHeight;
Adam Powell7d758002015-05-06 17:49:36 -07002221 }
2222 row.setTag(holder);
Adam Powell7d758002015-05-06 17:49:36 -07002223 return holder;
2224 }
2225
Adam Powell63b31692015-09-28 10:45:00 -07002226 void bindViewHolder(int rowPosition, RowViewHolder holder) {
Adam Powell7d758002015-05-06 17:49:36 -07002227 final int start = getFirstRowPosition(rowPosition);
2228 final int startType = mChooserListAdapter.getPositionTargetType(start);
2229
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002230 final int lastStartType = mChooserListAdapter.getPositionTargetType(
2231 getFirstRowPosition(rowPosition - 1));
2232
2233 if (startType != lastStartType || rowPosition == 0) {
2234 holder.row.setBackground(mChooserRowLayer);
2235 setVertPadding(holder, mChooserRowServiceSpacing, 0);
2236 } else {
2237 holder.row.setBackground(null);
2238 setVertPadding(holder, 0, 0);
2239 }
2240
Adam Powell7d758002015-05-06 17:49:36 -07002241 int end = start + mColumnCount - 1;
2242 while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
2243 end--;
2244 }
2245
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002246 if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
2247 final TextView textView = holder.row.findViewById(R.id.chooser_row_text_option);
Adam Powell63b31692015-09-28 10:45:00 -07002248
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002249 if (textView.getVisibility() != View.VISIBLE) {
2250 textView.setAlpha(0.0f);
2251 textView.setVisibility(View.VISIBLE);
2252 textView.setText(R.string.chooser_no_direct_share_targets);
2253
2254 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
2255 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
2256
2257 float translationInPx = getResources().getDimensionPixelSize(
2258 R.dimen.chooser_row_text_option_translate);
2259 textView.setTranslationY(translationInPx);
2260 ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
2261 0.0f);
2262 translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
2263
2264 AnimatorSet animSet = new AnimatorSet();
2265 animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
2266 animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
2267 animSet.playTogether(fadeAnim, translateAnim);
2268 animSet.start();
2269 }
Adam Powell7d758002015-05-06 17:49:36 -07002270 }
2271
2272 for (int i = 0; i < mColumnCount; i++) {
Adam Powell63b31692015-09-28 10:45:00 -07002273 final View v = holder.cells[i];
Adam Powell7d758002015-05-06 17:49:36 -07002274 if (start + i <= end) {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002275 setCellVisibility(holder, i, View.VISIBLE);
Adam Powell4eb98712015-10-14 13:10:18 -07002276 holder.itemIndices[i] = start + i;
2277 mChooserListAdapter.bindView(holder.itemIndices[i], v);
Adam Powell7d758002015-05-06 17:49:36 -07002278 } else {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002279 setCellVisibility(holder, i, View.INVISIBLE);
Adam Powell7d758002015-05-06 17:49:36 -07002280 }
2281 }
2282 }
2283
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002284 private void setCellVisibility(RowViewHolder holder, int i, int visibility) {
2285 final View v = holder.cells[i];
2286 if (visibility == View.VISIBLE) {
2287 holder.cellVisibility[i] = true;
2288 v.setVisibility(visibility);
2289 v.setAlpha(1.0f);
2290 } else if (visibility == View.INVISIBLE && holder.cellVisibility[i]) {
2291 holder.cellVisibility[i] = false;
2292
2293 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
2294 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
2295 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
2296 fadeAnim.addListener(new AnimatorListenerAdapter() {
2297 public void onAnimationEnd(Animator animation) {
2298 v.setVisibility(View.INVISIBLE);
2299 }
2300 });
2301 fadeAnim.start();
2302 }
2303 }
2304
Jason Monk027dcfa2017-06-27 18:37:35 -04002305 private void setVertPadding(RowViewHolder holder, int top, int bottom) {
2306 holder.row.setPadding(holder.row.getPaddingLeft(), top,
2307 holder.row.getPaddingRight(), bottom);
2308 }
2309
Adam Powell7d758002015-05-06 17:49:36 -07002310 int getFirstRowPosition(int row) {
Adam Powell50077352015-05-26 18:01:55 -07002311 final int callerCount = mChooserListAdapter.getCallerTargetCount();
Adam Powell7d758002015-05-06 17:49:36 -07002312 final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
2313
2314 if (row < callerRows) {
2315 return row * mColumnCount;
2316 }
2317
Adam Powell50077352015-05-26 18:01:55 -07002318 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
Adam Powell7d758002015-05-06 17:49:36 -07002319 final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
2320
2321 if (row < callerRows + serviceRows) {
2322 return callerCount + (row - callerRows) * mColumnCount;
2323 }
2324
2325 return callerCount + serviceCount
2326 + (row - callerRows - serviceRows) * mColumnCount;
2327 }
2328 }
2329
Adam Powell63b31692015-09-28 10:45:00 -07002330 static class RowViewHolder {
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002331 public final View[] cells;
2332 public final boolean [] cellVisibility;
2333 public final ViewGroup row;
Adam Powell63b31692015-09-28 10:45:00 -07002334 int measuredRowHeight;
Adam Powell4eb98712015-10-14 13:10:18 -07002335 int[] itemIndices;
Adam Powell63b31692015-09-28 10:45:00 -07002336
2337 public RowViewHolder(ViewGroup row, int cellCount) {
2338 this.row = row;
2339 this.cells = new View[cellCount];
Matt Pietalcdfcd9a2019-03-05 08:31:47 -05002340 this.cellVisibility = new boolean[cellCount];
Adam Powell4eb98712015-10-14 13:10:18 -07002341 this.itemIndices = new int[cellCount];
Adam Powell63b31692015-09-28 10:45:00 -07002342 }
2343
2344 public void measure() {
2345 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
2346 row.measure(spec, spec);
2347 measuredRowHeight = row.getMeasuredHeight();
2348 }
2349 }
2350
Adam Powell9761ab22015-09-08 17:01:49 -07002351 static class ChooserTargetServiceConnection implements ServiceConnection {
Adam Powell52c39212016-04-07 15:14:18 -07002352 private DisplayResolveInfo mOriginalTarget;
Adam Powell9761ab22015-09-08 17:01:49 -07002353 private ComponentName mConnectedComponent;
2354 private ChooserActivity mChooserActivity;
2355 private final Object mLock = new Object();
Adam Powell24428412015-04-01 17:19:56 -07002356
2357 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
2358 @Override
2359 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
Adam Powell9761ab22015-09-08 17:01:49 -07002360 synchronized (mLock) {
2361 if (mChooserActivity == null) {
2362 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
2363 + mConnectedComponent + "; ignoring...");
2364 return;
2365 }
2366 mChooserActivity.filterServiceTargets(
2367 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
2368 final Message msg = Message.obtain();
2369 msg.what = CHOOSER_TARGET_SERVICE_RESULT;
2370 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
2371 ChooserTargetServiceConnection.this);
2372 mChooserActivity.mChooserHandler.sendMessage(msg);
2373 }
Adam Powell24428412015-04-01 17:19:56 -07002374 }
2375 };
2376
Adam Powell9761ab22015-09-08 17:01:49 -07002377 public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
2378 DisplayResolveInfo dri) {
2379 mChooserActivity = chooserActivity;
Adam Powell24428412015-04-01 17:19:56 -07002380 mOriginalTarget = dri;
2381 }
2382
2383 @Override
2384 public void onServiceConnected(ComponentName name, IBinder service) {
2385 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07002386 synchronized (mLock) {
2387 if (mChooserActivity == null) {
2388 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
2389 return;
2390 }
2391
2392 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
2393 try {
2394 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
2395 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
2396 } catch (RemoteException e) {
2397 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
2398 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07002399 mChooserActivity.mServiceConnections.remove(this);
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04002400 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07002401 }
Adam Powell24428412015-04-01 17:19:56 -07002402 }
2403 }
2404
2405 @Override
2406 public void onServiceDisconnected(ComponentName name) {
2407 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07002408 synchronized (mLock) {
2409 if (mChooserActivity == null) {
2410 Log.e(TAG,
2411 "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
2412 return;
2413 }
2414
2415 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07002416 mChooserActivity.mServiceConnections.remove(this);
2417 if (mChooserActivity.mServiceConnections.isEmpty()) {
Adam Powell9761ab22015-09-08 17:01:49 -07002418 mChooserActivity.sendVoiceChoicesIfNeeded();
2419 }
2420 mConnectedComponent = null;
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04002421 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07002422 }
2423 }
2424
2425 public void destroy() {
2426 synchronized (mLock) {
2427 mChooserActivity = null;
Adam Powell52c39212016-04-07 15:14:18 -07002428 mOriginalTarget = null;
Adam Powell4c470d62015-06-19 17:46:17 -07002429 }
Adam Powell24428412015-04-01 17:19:56 -07002430 }
2431
2432 @Override
2433 public String toString() {
Adam Powell9761ab22015-09-08 17:01:49 -07002434 return "ChooserTargetServiceConnection{service="
2435 + mConnectedComponent + ", activity="
Adam Powell52c39212016-04-07 15:14:18 -07002436 + (mOriginalTarget != null
2437 ? mOriginalTarget.getResolveInfo().activityInfo.toString()
2438 : "<connection destroyed>") + "}";
Adam Powell24428412015-04-01 17:19:56 -07002439 }
2440 }
2441
2442 static class ServiceResultInfo {
2443 public final DisplayResolveInfo originalTarget;
2444 public final List<ChooserTarget> resultTargets;
2445 public final ChooserTargetServiceConnection connection;
2446
2447 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
2448 ChooserTargetServiceConnection c) {
2449 originalTarget = ot;
2450 resultTargets = rt;
2451 connection = c;
2452 }
2453 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002454
2455 static class RefinementResultReceiver extends ResultReceiver {
2456 private ChooserActivity mChooserActivity;
2457 private TargetInfo mSelectedTarget;
2458
2459 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
2460 Handler handler) {
2461 super(handler);
2462 mChooserActivity = host;
2463 mSelectedTarget = target;
2464 }
2465
2466 @Override
2467 protected void onReceiveResult(int resultCode, Bundle resultData) {
2468 if (mChooserActivity == null) {
2469 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
2470 return;
2471 }
2472 if (resultData == null) {
2473 Log.e(TAG, "RefinementResultReceiver received null resultData");
2474 return;
2475 }
2476
2477 switch (resultCode) {
2478 case RESULT_CANCELED:
2479 mChooserActivity.onRefinementCanceled();
2480 break;
2481 case RESULT_OK:
2482 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
2483 if (intentParcelable instanceof Intent) {
2484 mChooserActivity.onRefinementResult(mSelectedTarget,
2485 (Intent) intentParcelable);
2486 } else {
2487 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
2488 + " in resultData with key Intent.EXTRA_INTENT");
2489 }
2490 break;
2491 default:
2492 Log.w(TAG, "Unknown result code " + resultCode
2493 + " sent to RefinementResultReceiver");
2494 break;
2495 }
2496 }
2497
2498 public void destroy() {
2499 mChooserActivity = null;
2500 mSelectedTarget = null;
2501 }
2502 }
Adam Powell63b31692015-09-28 10:45:00 -07002503
2504 class OffsetDataSetObserver extends DataSetObserver {
2505 private final AbsListView mListView;
2506 private int mCachedViewType = -1;
2507 private View mCachedView;
2508
2509 public OffsetDataSetObserver(AbsListView listView) {
2510 mListView = listView;
2511 }
2512
2513 @Override
2514 public void onChanged() {
2515 if (mResolverDrawerLayout == null) {
2516 return;
2517 }
2518
2519 final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount();
2520 int offset = 0;
Matt Pietal26038402019-01-08 07:29:34 -05002521 for (int i = 0; i < chooserTargetRows; i++) {
Adam Powell63b31692015-09-28 10:45:00 -07002522 final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i;
2523 final int vt = mChooserRowAdapter.getItemViewType(pos);
2524 if (vt != mCachedViewType) {
2525 mCachedView = null;
2526 }
2527 final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView);
2528 int height = ((RowViewHolder) (v.getTag())).measuredRowHeight;
2529
Dan Sandler62aad002018-05-23 02:13:51 -04002530 offset += (int) (height);
Adam Powell63b31692015-09-28 10:45:00 -07002531
2532 if (vt >= 0) {
2533 mCachedViewType = vt;
2534 mCachedView = v;
2535 } else {
2536 mCachedViewType = -1;
2537 }
2538 }
2539
2540 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2541 }
2542 }
Matt Pietal26038402019-01-08 07:29:34 -05002543
2544
2545 /**
2546 * Used internally to round image corners while obeying view padding.
2547 */
2548 public static class RoundedRectImageView extends ImageView {
2549 private int mRadius = 0;
2550 private Path mPath = new Path();
Matt Pietal0ea391b2019-01-30 10:44:15 -05002551 private Paint mOverlayPaint = new Paint(0);
2552 private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
2553 private String mExtraImageCount = null;
Matt Pietal26038402019-01-08 07:29:34 -05002554
2555 public RoundedRectImageView(Context context) {
2556 super(context);
2557 }
2558
2559 public RoundedRectImageView(Context context, AttributeSet attrs) {
2560 this(context, attrs, 0);
2561 }
2562
2563 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
2564 this(context, attrs, defStyleAttr, 0);
2565 }
2566
2567 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
2568 int defStyleRes) {
2569 super(context, attrs, defStyleAttr, defStyleRes);
2570 mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
Matt Pietal0ea391b2019-01-30 10:44:15 -05002571
2572 mOverlayPaint.setColor(0x99000000);
2573 mOverlayPaint.setStyle(Paint.Style.FILL);
2574
2575 mTextPaint.setColor(Color.WHITE);
2576 mTextPaint.setTextSize(context.getResources()
2577 .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
2578 mTextPaint.setTextAlign(Paint.Align.CENTER);
Matt Pietal26038402019-01-08 07:29:34 -05002579 }
2580
2581 private void updatePath(int width, int height) {
2582 mPath.reset();
2583
Matt Pietal1fa7d802019-01-30 10:44:15 -05002584 int imageWidth = width - getPaddingRight();
2585 int imageHeight = height - getPaddingBottom();
Matt Pietal26038402019-01-08 07:29:34 -05002586 mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
2587 mRadius, Path.Direction.CW);
2588 }
2589
2590 /**
2591 * Sets the corner radius on all corners
2592 *
2593 * param radius 0 for no radius, &gt; 0 for a visible corner radius
2594 */
2595 public void setRadius(int radius) {
2596 mRadius = radius;
2597 updatePath(getWidth(), getHeight());
2598 }
2599
Matt Pietal0ea391b2019-01-30 10:44:15 -05002600 /**
2601 * Display an overlay with extra image count on 3rd image
2602 */
2603 public void setExtraImageCount(int count) {
2604 if (count > 0) {
2605 this.mExtraImageCount = "+" + count;
2606 } else {
2607 this.mExtraImageCount = null;
2608 }
2609 }
2610
Matt Pietal26038402019-01-08 07:29:34 -05002611 @Override
2612 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
2613 super.onSizeChanged(width, height, oldWidth, oldHeight);
2614 updatePath(width, height);
2615 }
2616
Matt Pietal0ea391b2019-01-30 10:44:15 -05002617
Matt Pietal26038402019-01-08 07:29:34 -05002618 @Override
2619 protected void onDraw(Canvas canvas) {
2620 if (mRadius != 0) {
2621 canvas.clipPath(mPath);
2622 }
2623
2624 super.onDraw(canvas);
Matt Pietal0ea391b2019-01-30 10:44:15 -05002625
2626 if (mExtraImageCount != null) {
2627 canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mOverlayPaint);
2628
2629 int xPos = canvas.getWidth() / 2;
2630 int yPos = (int) ((canvas.getHeight() / 2.0f)
2631 - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
2632
2633 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
2634 }
Matt Pietal26038402019-01-08 07:29:34 -05002635 }
2636 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002637}