blob: 89e3d6b6460faf8827a634e958c79ed38657bc11 [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
21import android.annotation.IntDef;
Adam Powell0b3c1122014-10-09 12:50:14 -070022import android.app.Activity;
Ng Zhi And3ec5fc2018-05-10 09:13:00 -070023import android.app.ActivityManager;
George Hodulik69d4a082019-01-18 11:27:03 -080024import android.app.prediction.AppPredictionContext;
25import android.app.prediction.AppPredictionManager;
26import android.app.prediction.AppPredictor;
27import android.app.prediction.AppTarget;
George Hodulikf2b0d342019-01-25 12:43:54 -080028import android.app.prediction.AppTargetEvent;
29import android.app.prediction.AppTargetId;
Matt Pietal26038402019-01-08 07:29:34 -050030import android.content.ClipData;
Matt Pietal1fa7d802019-01-30 10:44:15 -050031import android.content.ClipboardManager;
Adam Powell0b3c1122014-10-09 12:50:14 -070032import android.content.ComponentName;
Matt Pietal0ea391b2019-01-30 10:44:15 -050033import android.content.ContentResolver;
Adam Powell24428412015-04-01 17:19:56 -070034import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.content.Intent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080036import android.content.IntentFilter;
Adam Powell0b3c1122014-10-09 12:50:14 -070037import android.content.IntentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -070038import android.content.IntentSender.SendIntentException;
Adam Powell24428412015-04-01 17:19:56 -070039import android.content.ServiceConnection;
Adam Powell23882512016-01-29 10:21:00 -080040import android.content.SharedPreferences;
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +000041import android.content.pm.ActivityInfo;
Adam Powell7d758002015-05-06 17:49:36 -070042import android.content.pm.LabeledIntent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080043import android.content.pm.LauncherApps;
Adam Powell24428412015-04-01 17:19:56 -070044import android.content.pm.PackageManager;
45import android.content.pm.PackageManager.NameNotFoundException;
46import android.content.pm.ResolveInfo;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080047import android.content.pm.ShortcutInfo;
48import android.content.pm.ShortcutManager;
Matt Pietal18bbd822019-02-12 15:21:36 -050049import android.content.res.Configuration;
Matt Pietal46d828c2019-02-05 08:07:07 -050050import android.database.Cursor;
Adam Powell7d758002015-05-06 17:49:36 -070051import android.database.DataSetObserver;
Matt Pietal26038402019-01-08 07:29:34 -050052import android.graphics.Bitmap;
53import android.graphics.Canvas;
Adam Powell63b31692015-09-28 10:45:00 -070054import android.graphics.Color;
Matt Pietal0ea391b2019-01-30 10:44:15 -050055import android.graphics.Paint;
Matt Pietal26038402019-01-08 07:29:34 -050056import android.graphics.Path;
Adam Powell24428412015-04-01 17:19:56 -070057import android.graphics.drawable.Drawable;
Adam Powell13036be2015-05-12 14:43:56 -070058import android.graphics.drawable.Icon;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -050059import android.metrics.LogMaker;
Matt Pietal26038402019-01-08 07:29:34 -050060import android.net.Uri;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080061import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062import android.os.Bundle;
Adam Powell23882512016-01-29 10:21:00 -080063import android.os.Environment;
Adam Powell24428412015-04-01 17:19:56 -070064import android.os.Handler;
65import android.os.IBinder;
66import android.os.Message;
Dianne Hackborneb034652009-09-07 00:49:58 -070067import android.os.Parcelable;
Adam Powell52c39212016-04-07 15:14:18 -070068import android.os.Process;
Adam Powell24428412015-04-01 17:19:56 -070069import android.os.RemoteException;
Adam Powell2ed547e2015-04-29 18:45:04 -070070import android.os.ResultReceiver;
Adam Powell24428412015-04-01 17:19:56 -070071import android.os.UserHandle;
Adam Powell7d758002015-05-06 17:49:36 -070072import android.os.UserManager;
Jeff Sharkey8212ae02016-02-10 14:46:43 -070073import android.os.storage.StorageManager;
Matt Pietal46d828c2019-02-05 08:07:07 -050074import android.provider.DocumentsContract;
Matt Pietalf38e9d22019-02-15 10:01:03 -050075import android.provider.Downloads;
Matt Pietal46d828c2019-02-05 08:07:07 -050076import android.provider.OpenableColumns;
Adam Powell24428412015-04-01 17:19:56 -070077import android.service.chooser.ChooserTarget;
78import android.service.chooser.ChooserTargetService;
79import android.service.chooser.IChooserTargetResult;
80import android.service.chooser.IChooserTargetService;
81import android.text.TextUtils;
Matt Pietal26038402019-01-08 07:29:34 -050082import android.util.AttributeSet;
Dianne Hackborneb034652009-09-07 00:49:58 -070083import android.util.Log;
Matt Pietal26038402019-01-08 07:29:34 -050084import android.util.Size;
Adam Powell0b3c1122014-10-09 12:50:14 -070085import android.util.Slog;
Adam Powell7d758002015-05-06 17:49:36 -070086import android.view.LayoutInflater;
Adam Powell24428412015-04-01 17:19:56 -070087import android.view.View;
Adam Powell63b31692015-09-28 10:45:00 -070088import android.view.View.MeasureSpec;
Adam Powell7d758002015-05-06 17:49:36 -070089import android.view.View.OnClickListener;
Adam Powell98b7f892015-06-19 12:38:45 -070090import android.view.View.OnLongClickListener;
Adam Powell24428412015-04-01 17:19:56 -070091import android.view.ViewGroup;
Adam Powell63b31692015-09-28 10:45:00 -070092import android.view.ViewGroup.LayoutParams;
Adam Powell7d758002015-05-06 17:49:36 -070093import android.widget.AbsListView;
94import android.widget.BaseAdapter;
Matt Pietal26038402019-01-08 07:29:34 -050095import android.widget.ImageView;
Jason Monk027dcfa2017-06-27 18:37:35 -040096import android.widget.LinearLayout;
Adam Powell7d758002015-05-06 17:49:36 -070097import android.widget.ListView;
Jason Monk027dcfa2017-06-27 18:37:35 -040098import android.widget.Space;
Matt Pietal26038402019-01-08 07:29:34 -050099import android.widget.TextView;
Matt Pietal1fa7d802019-01-30 10:44:15 -0500100import android.widget.Toast;
Jason Monk027dcfa2017-06-27 18:37:35 -0400101
Adam Powell7d758002015-05-06 17:49:36 -0700102import com.android.internal.R;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800103import com.android.internal.annotations.VisibleForTesting;
Adam Powell98b7f892015-06-19 12:38:45 -0700104import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +0100105import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500106import com.android.internal.util.ImageUtils;
Alison Cichowlas3e340502018-08-07 17:15:01 -0400107
Adam Powell52c39212016-04-07 15:14:18 -0700108import com.google.android.collect.Lists;
Adam Powell24428412015-04-01 17:19:56 -0700109
Adam Powell23882512016-01-29 10:21:00 -0800110import java.io.File;
Matt Pietal26038402019-01-08 07:29:34 -0500111import java.io.IOException;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500112import java.lang.annotation.Retention;
Adam Powell24428412015-04-01 17:19:56 -0700113import java.util.ArrayList;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800114import java.util.Arrays;
Adam Powella182e452015-07-06 16:57:56 -0700115import java.util.Collections;
116import java.util.Comparator;
Adam Powell24428412015-04-01 17:19:56 -0700117import java.util.List;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118
119public class ChooserActivity extends ResolverActivity {
Adam Powell0b3c1122014-10-09 12:50:14 -0700120 private static final String TAG = "ChooserActivity";
121
Jorim Jaggif631ef72017-02-24 13:49:47 +0100122 /**
123 * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
124 * in onStop when launched in a new task. If this extra is set to true, we do not finish
125 * ourselves when onStop gets called.
126 */
127 public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
128 = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
129
Mehdi Alizadeh3c335a22019-01-17 16:03:19 -0800130 private static final boolean DEBUG = false;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800131
George Hodulik69d4a082019-01-18 11:27:03 -0800132
133 /**
134 * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
135 * {@link AppPredictionManager} will be queried for direct share targets.
136 */
137 // TODO(b/123089490): Replace with system flag
138 private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = false;
139 // TODO(b/123088566) Share these in a better way.
140 private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
George Hodulikf2b0d342019-01-25 12:43:54 -0800141 public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
George Hodulik69d4a082019-01-18 11:27:03 -0800142 private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
143 public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
144 private AppPredictor mAppPredictor;
145 private AppPredictor.Callback mAppPredictorCallback;
146
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800147 /**
148 * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
149 * binding to every ChooserTargetService implementation.
150 */
151 // TODO(b/121287573): Replace with a system flag (setprop?)
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800152 private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
153 private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
154
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800155 // TODO(b/121287224): Re-evaluate this limit
156 private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
Adam Powell24428412015-04-01 17:19:56 -0700157
Adam Powell2ed547e2015-04-29 18:45:04 -0700158 private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
Adam Powell3f23dfc2017-10-02 10:41:03 -0700159 private static final int WATCHDOG_TIMEOUT_MILLIS = 2000;
Adam Powell24428412015-04-01 17:19:56 -0700160
Adam Powelle49d9392014-07-17 18:45:19 -0700161 private Bundle mReplacementExtras;
Adam Powell0b3c1122014-10-09 12:50:14 -0700162 private IntentSender mChosenComponentSender;
Adam Powell2ed547e2015-04-29 18:45:04 -0700163 private IntentSender mRefinementIntentSender;
164 private RefinementResultReceiver mRefinementResultReceiver;
Adam Powell52c39212016-04-07 15:14:18 -0700165 private ChooserTarget[] mCallerChooserTargets;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800166 private ComponentName[] mFilteredComponentNames;
Adam Powelle49d9392014-07-17 18:45:19 -0700167
Adam Powell13036be2015-05-12 14:43:56 -0700168 private Intent mReferrerFillInIntent;
169
Kang Li9082f5b2016-12-02 10:56:21 -0800170 private long mChooserShownTime;
Kang Li64b018e2017-01-05 17:30:06 -0800171 protected boolean mIsSuccessfullySelected;
Kang Li9082f5b2016-12-02 10:56:21 -0800172
Adam Powell7d758002015-05-06 17:49:36 -0700173 private ChooserListAdapter mChooserListAdapter;
Adam Powell63b31692015-09-28 10:45:00 -0700174 private ChooserRowAdapter mChooserRowAdapter;
Adam Powell24428412015-04-01 17:19:56 -0700175
Adam Powell23882512016-01-29 10:21:00 -0800176 private SharedPreferences mPinnedSharedPrefs;
177 private static final float PINNED_TARGET_SCORE_BOOST = 1000.f;
Adam Powell52c39212016-04-07 15:14:18 -0700178 private static final float CALLER_TARGET_SCORE_BOOST = 900.f;
Adam Powell23882512016-01-29 10:21:00 -0800179 private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
180 private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
181
Adam Powell24428412015-04-01 17:19:56 -0700182 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
183
184 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
185 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800186 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 3;
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800187 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4;
Adam Powell24428412015-04-01 17:19:56 -0700188
Matt Pietal0ea391b2019-01-30 10:44:15 -0500189 @Retention(SOURCE)
190 @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
191 private @interface ContentPreviewType {
192 }
193
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500194 // Starting at 1 since 0 is considered "undefined" for some of the database transformations
195 // of tron logs.
196 private static final int CONTENT_PREVIEW_IMAGE = 1;
197 private static final int CONTENT_PREVIEW_FILE = 2;
198 private static final int CONTENT_PREVIEW_TEXT = 3;
199 protected MetricsLogger mMetricsLogger;
Matt Pietal0ea391b2019-01-30 10:44:15 -0500200
Adam Powell13036be2015-05-12 14:43:56 -0700201 private final Handler mChooserHandler = new Handler() {
Adam Powell24428412015-04-01 17:19:56 -0700202 @Override
203 public void handleMessage(Message msg) {
204 switch (msg.what) {
205 case CHOOSER_TARGET_SERVICE_RESULT:
206 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
207 if (isDestroyed()) break;
208 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
209 if (!mServiceConnections.contains(sri.connection)) {
210 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
211 + " returned after being removed from active connections."
212 + " Have you considered returning results faster?");
213 break;
214 }
Adam Powella182e452015-07-06 16:57:56 -0700215 if (sri.resultTargets != null) {
216 mChooserListAdapter.addServiceResults(sri.originalTarget,
217 sri.resultTargets);
218 }
Adam Powell24428412015-04-01 17:19:56 -0700219 unbindService(sri.connection);
Adam Powell9761ab22015-09-08 17:01:49 -0700220 sri.connection.destroy();
Adam Powell24428412015-04-01 17:19:56 -0700221 mServiceConnections.remove(sri.connection);
Adam Powell4c470d62015-06-19 17:46:17 -0700222 if (mServiceConnections.isEmpty()) {
223 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
224 sendVoiceChoicesIfNeeded();
Adam Powell565943f2016-02-11 10:29:05 -0800225 mChooserListAdapter.setShowServiceTargets(true);
Adam Powell4c470d62015-06-19 17:46:17 -0700226 }
Adam Powell24428412015-04-01 17:19:56 -0700227 break;
228
229 case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT:
230 if (DEBUG) {
231 Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
232 }
233 unbindRemainingServices();
Adam Powell4c470d62015-06-19 17:46:17 -0700234 sendVoiceChoicesIfNeeded();
Adam Powell565943f2016-02-11 10:29:05 -0800235 mChooserListAdapter.setShowServiceTargets(true);
Adam Powell24428412015-04-01 17:19:56 -0700236 break;
237
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800238 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
239 if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
240 if (isDestroyed()) break;
241 final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
242 if (resultInfo.resultTargets != null) {
243 mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
244 resultInfo.resultTargets);
245 }
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -0800246 break;
247
248 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800249 sendVoiceChoicesIfNeeded();
250 mChooserListAdapter.setShowServiceTargets(true);
251 break;
252
Adam Powell24428412015-04-01 17:19:56 -0700253 default:
254 super.handleMessage(msg);
255 }
256 }
257 };
258
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 @Override
260 protected void onCreate(Bundle savedInstanceState) {
Kang Li9082f5b2016-12-02 10:56:21 -0800261 final long intentReceivedTime = System.currentTimeMillis();
262 mIsSuccessfullySelected = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263 Intent intent = getIntent();
Dianne Hackborneb034652009-09-07 00:49:58 -0700264 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
265 if (!(targetParcelable instanceof Intent)) {
Christopher Tate9d6376a2014-02-12 13:14:10 -0800266 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
Dianne Hackborneb034652009-09-07 00:49:58 -0700267 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800268 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700269 return;
270 }
Adam Powell24428412015-04-01 17:19:56 -0700271 Intent target = (Intent) targetParcelable;
Craig Mautner411d2aed2014-05-08 09:07:43 -0700272 if (target != null) {
Adam Powelle49d9392014-07-17 18:45:19 -0700273 modifyTargetIntent(target);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700274 }
Adam Powell2ed547e2015-04-29 18:45:04 -0700275 Parcelable[] targetsParcelable
276 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
277 if (targetsParcelable != null) {
278 final boolean offset = target == null;
279 Intent[] additionalTargets =
280 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
281 for (int i = 0; i < targetsParcelable.length; i++) {
282 if (!(targetsParcelable[i] instanceof Intent)) {
283 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
284 + targetsParcelable[i]);
285 finish();
286 super.onCreate(null);
287 return;
288 }
289 final Intent additionalTarget = (Intent) targetsParcelable[i];
290 if (i == 0 && target == null) {
291 target = additionalTarget;
292 modifyTargetIntent(target);
293 } else {
294 additionalTargets[offset ? i - 1 : i] = additionalTarget;
295 modifyTargetIntent(additionalTarget);
296 }
297 }
298 setAdditionalTargets(additionalTargets);
299 }
300
Adam Powelle49d9392014-07-17 18:45:19 -0700301 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
Matt Pietal26038402019-01-08 07:29:34 -0500302
303 // Do not allow the title to be changed when sharing content
304 CharSequence title = null;
305 if (target != null) {
306 String targetAction = target.getAction();
307 if (!(Intent.ACTION_SEND.equals(targetAction) || Intent.ACTION_SEND_MULTIPLE.equals(
308 targetAction))) {
309 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
310 } else {
311 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
312 + " preview title by using EXTRA_TITLE property of the wrapped"
313 + " EXTRA_INTENT.");
314 }
315 }
316
Adam Powell278902c2014-07-12 18:33:22 -0700317 int defaultTitleRes = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318 if (title == null) {
Adam Powell278902c2014-07-12 18:33:22 -0700319 defaultTitleRes = com.android.internal.R.string.chooseActivity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800320 }
Matt Pietal26038402019-01-08 07:29:34 -0500321
Dianne Hackborneb034652009-09-07 00:49:58 -0700322 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
323 Intent[] initialIntents = null;
324 if (pa != null) {
325 initialIntents = new Intent[pa.length];
Matt Pietal26038402019-01-08 07:29:34 -0500326 for (int i = 0; i < pa.length; i++) {
Dianne Hackborneb034652009-09-07 00:49:58 -0700327 if (!(pa[i] instanceof Intent)) {
Adam Powell2ed547e2015-04-29 18:45:04 -0700328 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
Dianne Hackborneb034652009-09-07 00:49:58 -0700329 finish();
Christopher Tate9d6376a2014-02-12 13:14:10 -0800330 super.onCreate(null);
Dianne Hackborneb034652009-09-07 00:49:58 -0700331 return;
332 }
Craig Mautner411d2aed2014-05-08 09:07:43 -0700333 final Intent in = (Intent) pa[i];
Adam Powelle49d9392014-07-17 18:45:19 -0700334 modifyTargetIntent(in);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700335 initialIntents[i] = in;
Dianne Hackborneb034652009-09-07 00:49:58 -0700336 }
337 }
Adam Powell24428412015-04-01 17:19:56 -0700338
Adam Powell13036be2015-05-12 14:43:56 -0700339 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
340
Adam Powell0b3c1122014-10-09 12:50:14 -0700341 mChosenComponentSender = intent.getParcelableExtra(
342 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
Adam Powell2ed547e2015-04-29 18:45:04 -0700343 mRefinementIntentSender = intent.getParcelableExtra(
344 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
Dianne Hackborn028ceeb2014-08-17 17:45:48 -0700345 setSafeForwardingMode(true);
Adam Powell23882512016-01-29 10:21:00 -0800346
Adam Powell52c39212016-04-07 15:14:18 -0700347 pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
348 if (pa != null) {
349 ComponentName[] names = new ComponentName[pa.length];
350 for (int i = 0; i < pa.length; i++) {
351 if (!(pa[i] instanceof ComponentName)) {
352 Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
353 names = null;
354 break;
355 }
356 names[i] = (ComponentName) pa[i];
357 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800358 mFilteredComponentNames = names;
Adam Powell52c39212016-04-07 15:14:18 -0700359 }
360
361 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
362 if (pa != null) {
363 ChooserTarget[] targets = new ChooserTarget[pa.length];
364 for (int i = 0; i < pa.length; i++) {
365 if (!(pa[i] instanceof ChooserTarget)) {
366 Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
367 targets = null;
368 break;
369 }
370 targets[i] = (ChooserTarget) pa[i];
371 }
372 mCallerChooserTargets = targets;
373 }
374
Adam Powell23882512016-01-29 10:21:00 -0800375 mPinnedSharedPrefs = getPinnedSharedPrefs(this);
Jorim Jaggif631ef72017-02-24 13:49:47 +0100376 setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
Adam Powell278902c2014-07-12 18:33:22 -0700377 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
378 null, false);
Adam Powell98b7f892015-06-19 12:38:45 -0700379
Kang Li9082f5b2016-12-02 10:56:21 -0800380 mChooserShownTime = System.currentTimeMillis();
381 final long systemCost = mChooserShownTime - intentReceivedTime;
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500382
383 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500384 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
385 MetricsEvent.PARENT_PROFILE)
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500386 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
387 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
George Hodulik69d4a082019-01-18 11:27:03 -0800388
389 if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
390 final IntentFilter filter = getTargetIntentFilter();
391 Bundle extras = new Bundle();
392 extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
393 AppPredictionManager appPredictionManager =
394 getSystemService(AppPredictionManager.class);
395 mAppPredictor = appPredictionManager.createAppPredictionSession(
396 new AppPredictionContext.Builder(this)
397 .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
398 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
399 .setExtras(extras)
400 .build());
401 mAppPredictorCallback = resultList -> {
402 final List<DisplayResolveInfo> driList =
403 getDisplayResolveInfos(mChooserListAdapter);
404 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
405 new ArrayList<>();
406 for (AppTarget appTarget : resultList) {
407 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
408 appTarget.getShortcutInfo(),
409 new ComponentName(
410 appTarget.getPackageName(), appTarget.getClassName())));
411 }
412 sendShareShortcutInfoList(shareShortcutInfos, driList);
413 };
414 mAppPredictor.registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
415 }
416
Kang Li9082f5b2016-12-02 10:56:21 -0800417 if (DEBUG) {
418 Log.d(TAG, "System Time Cost is " + systemCost);
419 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800420 }
Adam Powelle49d9392014-07-17 18:45:19 -0700421
Matt Pietal26038402019-01-08 07:29:34 -0500422 /**
Susi Kharraz-Post0446fab2019-02-21 09:42:31 -0500423 * Check if the profile currently used is a work profile.
424 * @return true if it is work profile, false if it is parent profile (or no work profile is
425 * set up)
426 */
427 protected boolean isWorkProfile() {
428 return ((UserManager) getSystemService(Context.USER_SERVICE))
429 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
430 }
431
432 /**
Matt Pietal26038402019-01-08 07:29:34 -0500433 * Override method to add content preview area, specific to the chooser activity.
434 */
435 @Override
436 public void setHeader() {
437 super.setHeader();
438
439 Intent targetIntent = getTargetIntent();
440 if (targetIntent == null) {
441 return;
442 }
443
Matt Pietal26038402019-01-08 07:29:34 -0500444 String action = targetIntent.getAction();
445 if (!(Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action))) {
Matt Pietal26038402019-01-08 07:29:34 -0500446 return;
447 }
448
Matt Pietal46d828c2019-02-05 08:07:07 -0500449 if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) {
450 return;
451 }
452
Matt Pietal0ea391b2019-01-30 10:44:15 -0500453 int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -0500454
455 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
456 .setSubtype(previewType));
Matt Pietal0ea391b2019-01-30 10:44:15 -0500457 displayContentPreview(previewType, targetIntent);
Matt Pietal26038402019-01-08 07:29:34 -0500458 }
459
Matt Pietal46d828c2019-02-05 08:07:07 -0500460 private void onCopyButtonClicked(View v) {
461 Intent targetIntent = getTargetIntent();
462 if (targetIntent == null) {
463 finish();
464 } else {
465 final String action = targetIntent.getAction();
466
467 ClipData clipData = null;
468 if (Intent.ACTION_SEND.equals(action)) {
469 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
470 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
471
472 if (extraText != null) {
473 clipData = ClipData.newPlainText(null, extraText);
474 } else if (extraStream != null) {
475 clipData = ClipData.newUri(getContentResolver(), null, extraStream);
476 } else {
477 Log.w(TAG, "No data available to copy to clipboard");
478 return;
479 }
480 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
481 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
482 Intent.EXTRA_STREAM);
483 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
484 for (int i = 1; i < streams.size(); i++) {
485 clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
486 }
487 } else {
488 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
489 // so warn about unexpected action
490 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
491 return;
492 }
493
494 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
495 Context.CLIPBOARD_SERVICE);
496 clipboardManager.setPrimaryClip(clipData);
497 Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
498
499 finish();
500 }
501 }
502
Matt Pietal18bbd822019-02-12 15:21:36 -0500503 @Override
504 public void onConfigurationChanged(Configuration newConfig) {
505 super.onConfigurationChanged(newConfig);
506
507 int width = -1;
508 if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
509 width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
510 }
511
512 updateLayoutWidth(R.id.content_preview_text_layout, width);
513 updateLayoutWidth(R.id.content_preview_title_layout, width);
514 updateLayoutWidth(R.id.content_preview_file_layout, width);
515 }
516
517 private void updateLayoutWidth(int layoutResourceId, int width) {
518 View view = findViewById(layoutResourceId);
519 LayoutParams params = view.getLayoutParams();
520 params.width = width;
521 view.setLayoutParams(params);
522 }
523
Matt Pietal0ea391b2019-01-30 10:44:15 -0500524 private void displayContentPreview(@ContentPreviewType int previewType, Intent targetIntent) {
525 switch (previewType) {
526 case CONTENT_PREVIEW_TEXT:
527 displayTextContentPreview(targetIntent);
528 break;
529 case CONTENT_PREVIEW_IMAGE:
530 displayImageContentPreview(targetIntent);
531 break;
532 case CONTENT_PREVIEW_FILE:
533 displayFileContentPreview(targetIntent);
534 break;
535 default:
536 Log.e(TAG, "Unexpected content preview type: " + previewType);
537 }
538 }
539
540 private void displayTextContentPreview(Intent targetIntent) {
541 ViewGroup contentPreviewLayout = findViewById(R.id.content_preview_text_area);
542 contentPreviewLayout.setVisibility(View.VISIBLE);
543
Matt Pietal46d828c2019-02-05 08:07:07 -0500544 findViewById(R.id.copy_button).setOnClickListener(this::onCopyButtonClicked);
545
Matt Pietal26038402019-01-08 07:29:34 -0500546 CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
Matt Pietal26038402019-01-08 07:29:34 -0500547 if (sharingText == null) {
Matt Pietal1fa7d802019-01-30 10:44:15 -0500548 findViewById(R.id.content_preview_text_layout).setVisibility(View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500549 } else {
Matt Pietal1fa7d802019-01-30 10:44:15 -0500550 TextView textView = findViewById(R.id.content_preview_text);
551 textView.setText(sharingText);
Matt Pietal26038402019-01-08 07:29:34 -0500552 }
553
554 String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
Matt Pietal46d828c2019-02-05 08:07:07 -0500555 if (TextUtils.isEmpty(previewTitle)) {
Matt Pietal1fa7d802019-01-30 10:44:15 -0500556 findViewById(R.id.content_preview_title_layout).setVisibility(View.GONE);
Matt Pietal26038402019-01-08 07:29:34 -0500557 } else {
Matt Pietal1fa7d802019-01-30 10:44:15 -0500558 TextView previewTitleView = findViewById(R.id.content_preview_title);
Matt Pietal26038402019-01-08 07:29:34 -0500559 previewTitleView.setText(previewTitle);
Matt Pietal26038402019-01-08 07:29:34 -0500560
Matt Pietal1fa7d802019-01-30 10:44:15 -0500561 ClipData previewData = targetIntent.getClipData();
562 Uri previewThumbnail = null;
563 if (previewData != null) {
564 if (previewData.getItemCount() > 0) {
565 ClipData.Item previewDataItem = previewData.getItemAt(0);
566 previewThumbnail = previewDataItem.getUri();
567 }
Matt Pietal26038402019-01-08 07:29:34 -0500568 }
Matt Pietal26038402019-01-08 07:29:34 -0500569
Matt Pietal1fa7d802019-01-30 10:44:15 -0500570 ImageView previewThumbnailView = findViewById(R.id.content_preview_thumbnail);
571 if (previewThumbnail == null) {
Matt Pietal26038402019-01-08 07:29:34 -0500572 previewThumbnailView.setVisibility(View.GONE);
573 } else {
Matt Pietal1fa7d802019-01-30 10:44:15 -0500574 Bitmap bmp = loadThumbnail(previewThumbnail, new Size(100, 100));
575 if (bmp == null) {
576 previewThumbnailView.setVisibility(View.GONE);
577 } else {
578 previewThumbnailView.setImageBitmap(bmp);
579 }
Matt Pietal26038402019-01-08 07:29:34 -0500580 }
581 }
582 }
583
Matt Pietal0ea391b2019-01-30 10:44:15 -0500584 private void displayImageContentPreview(Intent targetIntent) {
585 ViewGroup contentPreviewLayout = findViewById(R.id.content_preview_image_area);
586 contentPreviewLayout.setVisibility(View.VISIBLE);
587
588 String action = targetIntent.getAction();
589 if (Intent.ACTION_SEND.equals(action)) {
590 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
591 loadUriIntoView(R.id.content_preview_image_1_large, uri);
592 } else {
593 ContentResolver resolver = getContentResolver();
594
595 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
596 List<Uri> imageUris = new ArrayList<>();
597 for (Uri uri : uris) {
598 if (isImageType(resolver.getType(uri))) {
599 imageUris.add(uri);
600 }
601 }
602
603 if (imageUris.size() == 0) {
604 Log.i(TAG, "Attempted to display image preview area with zero"
605 + " available images detected in EXTRA_STREAM list");
Matt Pietal46d828c2019-02-05 08:07:07 -0500606 contentPreviewLayout.setVisibility(View.GONE);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500607 return;
608 }
609
610 loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0));
611
612 if (imageUris.size() == 2) {
613 loadUriIntoView(R.id.content_preview_image_2_large, imageUris.get(1));
614 } else if (imageUris.size() > 2) {
615 loadUriIntoView(R.id.content_preview_image_2_small, imageUris.get(1));
616 RoundedRectImageView imageView = loadUriIntoView(
617 R.id.content_preview_image_3_small, imageUris.get(2));
618
619 if (imageUris.size() > 3) {
620 imageView.setExtraImageCount(imageUris.size() - 3);
621 }
622 }
623 }
624 }
625
Matt Pietal46d828c2019-02-05 08:07:07 -0500626 private static class FileInfo {
627 public final String name;
628 public final boolean hasThumbnail;
629
630 FileInfo(String name, boolean hasThumbnail) {
631 this.name = name;
632 this.hasThumbnail = hasThumbnail;
633 }
634 }
635
Matt Pietalf38e9d22019-02-15 10:01:03 -0500636 /**
637 * Wrapping the ContentResolver call to expose for easier mocking,
638 * and to avoid mocking Android core classes.
639 */
640 @VisibleForTesting
641 public Cursor queryResolver(ContentResolver resolver, Uri uri) {
642 return resolver.query(uri, null, null, null, null);
643 }
644
Matt Pietal46d828c2019-02-05 08:07:07 -0500645 private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
646 String fileName = null;
647 boolean hasThumbnail = false;
Matt Pietal3087bca2019-02-14 12:19:16 -0500648
Matt Pietalf38e9d22019-02-15 10:01:03 -0500649 try (Cursor cursor = queryResolver(resolver, uri)) {
650 if (cursor != null && cursor.getCount() > 0) {
651 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
652 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
653 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
654
655 cursor.moveToFirst();
656 if (nameIndex != -1) {
657 fileName = cursor.getString(nameIndex);
658 } else if (titleIndex != -1) {
659 fileName = cursor.getString(titleIndex);
660 }
661
662 if (flagsIndex != -1) {
663 hasThumbnail = (cursor.getInt(flagsIndex)
664 & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
665 }
666 }
Matt Pietal3087bca2019-02-14 12:19:16 -0500667 } catch (SecurityException e) {
668 Log.w(TAG, "Error loading file preview", e);
669 }
670
Matt Pietal46d828c2019-02-05 08:07:07 -0500671 if (TextUtils.isEmpty(fileName)) {
672 fileName = uri.getPath();
673 int index = fileName.lastIndexOf('/');
674 if (index != -1) {
675 fileName = fileName.substring(index + 1);
676 }
677 }
678
679 return new FileInfo(fileName, hasThumbnail);
680 }
681
Matt Pietal0ea391b2019-01-30 10:44:15 -0500682 private void displayFileContentPreview(Intent targetIntent) {
Matt Pietal46d828c2019-02-05 08:07:07 -0500683 ViewGroup contentPreviewLayout = findViewById(R.id.content_preview_file_area);
684 contentPreviewLayout.setVisibility(View.VISIBLE);
685
686 // TODO(b/120417119): Disable file copy until after moving to sysui,
687 // due to permissions issues
688 findViewById(R.id.file_copy_button).setVisibility(View.GONE);
689
Matt Pietal3087bca2019-02-14 12:19:16 -0500690 String action = targetIntent.getAction();
691 if (Intent.ACTION_SEND.equals(action)) {
692 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
693 loadFileUriIntoView(uri);
694 } else {
695 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
696 int uriCount = uris.size();
Matt Pietal46d828c2019-02-05 08:07:07 -0500697
Matt Pietal3087bca2019-02-14 12:19:16 -0500698 if (uriCount == 0) {
699 contentPreviewLayout.setVisibility(View.GONE);
700 Log.i(TAG,
701 "Appears to be no uris available in EXTRA_STREAM, removing "
702 + "preview area");
703 return;
704 } else if (uriCount == 1) {
705 loadFileUriIntoView(uris.get(0));
Matt Pietal46d828c2019-02-05 08:07:07 -0500706 } else {
Matt Pietal3087bca2019-02-14 12:19:16 -0500707 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
708 int remUriCount = uriCount - 1;
Matt Pietalacabc572019-02-14 11:02:05 -0500709 String fileName = getResources().getQuantityString(R.plurals.file_count,
Matt Pietal3087bca2019-02-14 12:19:16 -0500710 remUriCount, fileInfo.name, remUriCount);
Matt Pietalacabc572019-02-14 11:02:05 -0500711
Matt Pietal3087bca2019-02-14 12:19:16 -0500712 TextView fileNameView = findViewById(R.id.content_preview_filename);
Matt Pietalacabc572019-02-14 11:02:05 -0500713 fileNameView.setText(fileName);
Matt Pietal3087bca2019-02-14 12:19:16 -0500714
Matt Pietal46d828c2019-02-05 08:07:07 -0500715 ImageView fileIconView = findViewById(R.id.content_preview_file_icon);
716 fileIconView.setVisibility(View.VISIBLE);
Matt Pietalacabc572019-02-14 11:02:05 -0500717 fileIconView.setImageResource(R.drawable.ic_file_copy);
Matt Pietal46d828c2019-02-05 08:07:07 -0500718 }
Matt Pietal3087bca2019-02-14 12:19:16 -0500719 }
720 }
721
722 private void loadFileUriIntoView(Uri uri) {
723 FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
724
725 TextView fileNameView = findViewById(R.id.content_preview_filename);
726 fileNameView.setText(fileInfo.name);
727
728 if (fileInfo.hasThumbnail) {
729 loadUriIntoView(R.id.content_preview_file_thumbnail, uri);
730 } else {
731 ImageView fileIconView = findViewById(R.id.content_preview_file_icon);
732 fileIconView.setVisibility(View.VISIBLE);
733 fileIconView.setImageResource(R.drawable.ic_doc_generic);
Matt Pietal46d828c2019-02-05 08:07:07 -0500734 }
Matt Pietal0ea391b2019-01-30 10:44:15 -0500735 }
736
737 private RoundedRectImageView loadUriIntoView(int imageResourceId, Uri uri) {
738 RoundedRectImageView imageView = findViewById(imageResourceId);
Matt Pietal0ea391b2019-01-30 10:44:15 -0500739 Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
Matt Pietal46d828c2019-02-05 08:07:07 -0500740 if (bmp != null) {
741 imageView.setVisibility(View.VISIBLE);
742 imageView.setImageBitmap(bmp);
743 }
Matt Pietal0ea391b2019-01-30 10:44:15 -0500744
745 return imageView;
746 }
747
748 @VisibleForTesting
749 protected boolean isImageType(String mimeType) {
750 return mimeType != null && mimeType.startsWith("image/");
751 }
752
753 @ContentPreviewType
754 private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
755 if (uri == null) {
756 return CONTENT_PREVIEW_TEXT;
757 }
758
759 String mimeType = resolver.getType(uri);
760 return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
761 }
762
763 /**
764 * In {@link android.content.Intent#getType}, the app may specify a very general
765 * mime-type that broadly covers all data being shared, such as {@literal *}/*
766 * when sending an image and text. We therefore should inspect each item for the
767 * the preferred type, in order of IMAGE, FILE, TEXT.
768 */
769 @ContentPreviewType
770 private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
771 String action = targetIntent.getAction();
772 if (Intent.ACTION_SEND.equals(action)) {
773 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
774 return findPreferredContentPreview(uri, resolver);
775 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
776 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
777 if (uris == null || uris.isEmpty()) {
778 return CONTENT_PREVIEW_TEXT;
779 }
780
781 for (Uri uri : uris) {
782 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_IMAGE) {
783 return CONTENT_PREVIEW_IMAGE;
784 }
785 }
786
787 return CONTENT_PREVIEW_FILE;
788 }
789
790 return CONTENT_PREVIEW_TEXT;
791 }
792
Adam Powell23882512016-01-29 10:21:00 -0800793 static SharedPreferences getPinnedSharedPrefs(Context context) {
794 // The code below is because in the android:ui process, no one can hear you scream.
795 // The package info in the context isn't initialized in the way it is for normal apps,
796 // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
797 // build the path manually below using the same policy that appears in ContextImpl.
798 // This fails silently under the hood if there's a problem, so if we find ourselves in
799 // the case where we don't have access to credential encrypted storage we just won't
800 // have our pinned target info.
801 final File prefsFile = new File(new File(
Jeff Sharkey8212ae02016-02-10 14:46:43 -0700802 Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
Adam Powell23882512016-01-29 10:21:00 -0800803 context.getUserId(), context.getPackageName()),
804 "shared_prefs"),
805 PINNED_SHARED_PREFS_NAME + ".xml");
806 return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
807 }
808
Adam Powell0b3c1122014-10-09 12:50:14 -0700809 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -0700810 protected void onDestroy() {
811 super.onDestroy();
812 if (mRefinementResultReceiver != null) {
813 mRefinementResultReceiver.destroy();
814 mRefinementResultReceiver = null;
815 }
Adam Powell9761ab22015-09-08 17:01:49 -0700816 unbindRemainingServices();
817 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
George Hodulik69d4a082019-01-18 11:27:03 -0800818 if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
819 mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
820 mAppPredictor.destroy();
821 }
Adam Powell2ed547e2015-04-29 18:45:04 -0700822 }
823
824 @Override
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +0000825 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
826 Intent result = defIntent;
Adam Powelle49d9392014-07-17 18:45:19 -0700827 if (mReplacementExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +0000828 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
Adam Powelle49d9392014-07-17 18:45:19 -0700829 if (replExtras != null) {
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +0000830 result = new Intent(defIntent);
Adam Powelle49d9392014-07-17 18:45:19 -0700831 result.putExtras(replExtras);
Adam Powelle49d9392014-07-17 18:45:19 -0700832 }
833 }
Nicolas Prevot741abfc2015-08-11 12:03:51 +0100834 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +0000835 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
836 result = Intent.createChooser(result,
837 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
Hakan Seyalioglu7317e8a2016-12-12 16:15:38 -0800838
839 // Don't auto-launch single intents if the intent is being forwarded. This is done
840 // because automatically launching a resolving application as a response to the user
841 // action of switching accounts is pretty unexpected.
842 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
Nicolas Prevot0e2b73f2014-10-27 10:06:11 +0000843 }
844 return result;
Adam Powelle49d9392014-07-17 18:45:19 -0700845 }
846
Adam Powell0b3c1122014-10-09 12:50:14 -0700847 @Override
Adam Powell23882512016-01-29 10:21:00 -0800848 public void onActivityStarted(TargetInfo cti) {
Adam Powell0b3c1122014-10-09 12:50:14 -0700849 if (mChosenComponentSender != null) {
Adam Powell24428412015-04-01 17:19:56 -0700850 final ComponentName target = cti.getResolvedComponentName();
Adam Powell0b3c1122014-10-09 12:50:14 -0700851 if (target != null) {
852 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
853 try {
854 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
855 } catch (IntentSender.SendIntentException e) {
856 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
857 + "the chosen component: " + e);
858 }
859 }
860 }
861 }
862
Adam Powell24428412015-04-01 17:19:56 -0700863 @Override
Hakan Seyalioglu13405c52017-01-31 19:01:31 -0800864 public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
Adam Powell7d758002015-05-06 17:49:36 -0700865 final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
866 mChooserListAdapter = (ChooserListAdapter) adapter;
Adam Powell52c39212016-04-07 15:14:18 -0700867 if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
868 mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets));
869 }
Adam Powell63b31692015-09-28 10:45:00 -0700870 mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
871 mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView));
872 adapterView.setAdapter(mChooserRowAdapter);
Adam Powell7d758002015-05-06 17:49:36 -0700873 if (listView != null) {
874 listView.setItemsCanFocus(true);
875 }
876 }
877
878 @Override
Adam Powell23882512016-01-29 10:21:00 -0800879 public int getLayoutResource() {
Adam Powell7d758002015-05-06 17:49:36 -0700880 return R.layout.chooser_grid;
Adam Powell24428412015-04-01 17:19:56 -0700881 }
882
883 @Override
Adam Powell23882512016-01-29 10:21:00 -0800884 public boolean shouldGetActivityMetadata() {
Adam Powell24428412015-04-01 17:19:56 -0700885 return true;
886 }
887
Adam Powell9761ab22015-09-08 17:01:49 -0700888 @Override
Ben Lin145b0ca2016-10-14 14:23:40 -0700889 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
Hakan Seyalioglu13405c52017-01-31 19:01:31 -0800890 // Note that this is only safe because the Intent handled by the ChooserActivity is
891 // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
892 // method can not be replaced in the ResolverActivity whole hog.
Ben Lin145b0ca2016-10-14 14:23:40 -0700893 return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE,
894 super.shouldAutoLaunchSingleChoice(target));
895 }
896
897 @Override
Adam Powell23882512016-01-29 10:21:00 -0800898 public void showTargetDetails(ResolveInfo ri) {
sanryhuang296ca9e2018-03-31 11:17:13 +0800899 if (ri == null) {
900 return;
901 }
902
Adam Powell23882512016-01-29 10:21:00 -0800903 ComponentName name = ri.activityInfo.getComponentName();
904 boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
905 ResolverTargetActionsDialogFragment f =
906 new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
907 name, pinned);
908 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
909 }
910
Adam Powelle49d9392014-07-17 18:45:19 -0700911 private void modifyTargetIntent(Intent in) {
912 final String action = in.getAction();
913 if (Intent.ACTION_SEND.equals(action) ||
914 Intent.ACTION_SEND_MULTIPLE.equals(action)) {
915 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
Dianne Hackborn13420f22014-07-18 15:43:56 -0700916 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Adam Powelle49d9392014-07-17 18:45:19 -0700917 }
918 }
Adam Powell24428412015-04-01 17:19:56 -0700919
Adam Powell2ed547e2015-04-29 18:45:04 -0700920 @Override
921 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
922 if (mRefinementIntentSender != null) {
923 final Intent fillIn = new Intent();
924 final List<Intent> sourceIntents = target.getAllSourceIntents();
925 if (!sourceIntents.isEmpty()) {
926 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
927 if (sourceIntents.size() > 1) {
928 final Intent[] alts = new Intent[sourceIntents.size() - 1];
929 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
930 alts[i - 1] = sourceIntents.get(i);
931 }
932 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
933 }
934 if (mRefinementResultReceiver != null) {
935 mRefinementResultReceiver.destroy();
936 }
937 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
938 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
939 mRefinementResultReceiver);
940 try {
941 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
942 return false;
943 } catch (SendIntentException e) {
944 Log.e(TAG, "Refinement IntentSender failed to send", e);
945 }
946 }
947 }
Kang Li9fa2a2c2017-01-06 13:33:24 -0800948 updateModelAndChooserCounts(target);
Adam Powell2ed547e2015-04-29 18:45:04 -0700949 return super.onTargetSelected(target, alwaysCheck);
950 }
951
Adam Powell98b7f892015-06-19 12:38:45 -0700952 @Override
Adam Powell23882512016-01-29 10:21:00 -0800953 public void startSelected(int which, boolean always, boolean filtered) {
Kang Li9082f5b2016-12-02 10:56:21 -0800954 final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
Adam Powell98b7f892015-06-19 12:38:45 -0700955 super.startSelected(which, always, filtered);
956
957 if (mChooserListAdapter != null) {
958 // Log the index of which type of target the user picked.
959 // Lower values mean the ranking was better.
960 int cat = 0;
961 int value = which;
962 switch (mChooserListAdapter.getPositionTargetType(which)) {
963 case ChooserListAdapter.TARGET_CALLER:
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500964 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
Adam Powell98b7f892015-06-19 12:38:45 -0700965 break;
966 case ChooserListAdapter.TARGET_SERVICE:
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500967 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
Adam Powell98b7f892015-06-19 12:38:45 -0700968 value -= mChooserListAdapter.getCallerTargetCount();
969 break;
970 case ChooserListAdapter.TARGET_STANDARD:
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500971 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
Adam Powell98b7f892015-06-19 12:38:45 -0700972 value -= mChooserListAdapter.getCallerTargetCount()
973 + mChooserListAdapter.getServiceTargetCount();
974 break;
975 }
976
977 if (cat != 0) {
978 MetricsLogger.action(this, cat, value);
979 }
Kang Li9082f5b2016-12-02 10:56:21 -0800980
981 if (mIsSuccessfullySelected) {
982 if (DEBUG) {
983 Log.d(TAG, "User Selection Time Cost is " + selectionCost);
984 Log.d(TAG, "position of selected app/service/caller is " +
985 Integer.toString(value));
986 }
987 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
988 (int) selectionCost);
989 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
990 }
Adam Powell98b7f892015-06-19 12:38:45 -0700991 }
992 }
993
Adam Powell24428412015-04-01 17:19:56 -0700994 void queryTargetServices(ChooserListAdapter adapter) {
995 final PackageManager pm = getPackageManager();
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -0800996 ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
Adam Powell24428412015-04-01 17:19:56 -0700997 int targetsToQuery = 0;
998 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
999 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
Adam Powell3a09c522015-10-21 13:21:28 -07001000 if (adapter.getScore(dri) == 0) {
1001 // A score of 0 means the app hasn't been used in some time;
1002 // don't query it as it's not likely to be relevant.
1003 continue;
1004 }
Adam Powell24428412015-04-01 17:19:56 -07001005 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -08001006 if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
1007 && sm.hasShareTargets(ai.packageName)) {
1008 // Share targets will be queried from ShortcutManager
1009 continue;
1010 }
Adam Powell24428412015-04-01 17:19:56 -07001011 final Bundle md = ai.metaData;
1012 final String serviceName = md != null ? convertServiceName(ai.packageName,
1013 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
1014 if (serviceName != null) {
1015 final ComponentName serviceComponent = new ComponentName(
1016 ai.packageName, serviceName);
1017 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
1018 .setComponent(serviceComponent);
1019
1020 if (DEBUG) {
1021 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
1022 }
1023
1024 try {
1025 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
1026 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
1027 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
1028 + " permission " + ChooserTargetService.BIND_PERMISSION
1029 + " - this service will not be queried for ChooserTargets."
1030 + " add android:permission=\""
1031 + ChooserTargetService.BIND_PERMISSION + "\""
1032 + " to the <service> tag for " + serviceComponent
1033 + " in the manifest.");
1034 continue;
1035 }
1036 } catch (NameNotFoundException e) {
Adam Powell52c39212016-04-07 15:14:18 -07001037 Log.e(TAG, "Could not look up service " + serviceComponent
1038 + "; component name not found");
Adam Powell24428412015-04-01 17:19:56 -07001039 continue;
1040 }
1041
Adam Powell9761ab22015-09-08 17:01:49 -07001042 final ChooserTargetServiceConnection conn =
1043 new ChooserTargetServiceConnection(this, dri);
Adam Powell52c39212016-04-07 15:14:18 -07001044
1045 // Explicitly specify Process.myUserHandle instead of calling bindService
1046 // to avoid the warning from calling from the system process without an explicit
1047 // user handle
1048 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
1049 Process.myUserHandle())) {
Adam Powell24428412015-04-01 17:19:56 -07001050 if (DEBUG) {
1051 Log.d(TAG, "Binding service connection for target " + dri
1052 + " intent " + serviceIntent);
1053 }
1054 mServiceConnections.add(conn);
1055 targetsToQuery++;
1056 }
1057 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001058 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
Matt Pietal26038402019-01-08 07:29:34 -05001059 if (DEBUG) {
1060 Log.d(TAG, "queryTargets hit query target limit "
1061 + QUERY_TARGET_SERVICE_LIMIT);
1062 }
Adam Powell24428412015-04-01 17:19:56 -07001063 break;
1064 }
1065 }
1066
1067 if (!mServiceConnections.isEmpty()) {
Matt Pietal26038402019-01-08 07:29:34 -05001068 if (DEBUG) {
1069 Log.d(TAG, "queryTargets setting watchdog timer for "
1070 + WATCHDOG_TIMEOUT_MILLIS + "ms");
1071 }
Adam Powell13036be2015-05-12 14:43:56 -07001072 mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT,
Adam Powell24428412015-04-01 17:19:56 -07001073 WATCHDOG_TIMEOUT_MILLIS);
Adam Powell4c470d62015-06-19 17:46:17 -07001074 } else {
1075 sendVoiceChoicesIfNeeded();
Adam Powell24428412015-04-01 17:19:56 -07001076 }
1077 }
1078
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001079 private IntentFilter getTargetIntentFilter() {
1080 try {
1081 final Intent intent = getTargetIntent();
1082 String dataString = intent.getDataString();
1083 if (TextUtils.isEmpty(dataString)) {
1084 dataString = intent.getType();
1085 }
1086 return new IntentFilter(intent.getAction(), dataString);
1087 } catch (Exception e) {
1088 Log.e(TAG, "failed to get target intent filter " + e);
1089 return null;
1090 }
1091 }
1092
George Hodulik69d4a082019-01-18 11:27:03 -08001093 private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001094 // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
1095 // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
George Hodulik69d4a082019-01-18 11:27:03 -08001096 List<DisplayResolveInfo> driList = new ArrayList<>();
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001097 int targetsToQuery = 0;
1098 for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
1099 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
1100 if (adapter.getScore(dri) == 0) {
1101 // A score of 0 means the app hasn't been used in some time;
1102 // don't query it as it's not likely to be relevant.
1103 continue;
1104 }
1105 driList.add(dri);
1106 targetsToQuery++;
1107 // TODO(b/121287224): Do we need this here? (similar to queryTargetServices)
1108 if (targetsToQuery >= SHARE_TARGET_QUERY_PACKAGE_LIMIT) {
1109 if (DEBUG) {
1110 Log.d(TAG, "queryTargets hit query target limit "
1111 + SHARE_TARGET_QUERY_PACKAGE_LIMIT);
1112 }
1113 break;
1114 }
1115 }
George Hodulik69d4a082019-01-18 11:27:03 -08001116 return driList;
1117 }
1118
1119 private void queryDirectShareTargets(ChooserListAdapter adapter) {
1120 if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
1121 mAppPredictor.requestPredictionUpdate();
1122 return;
1123 }
1124 final IntentFilter filter = getTargetIntentFilter();
1125 if (filter == null) {
1126 return;
1127 }
1128 final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001129
1130 AsyncTask.execute(() -> {
1131 ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
1132 List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
George Hodulik69d4a082019-01-18 11:27:03 -08001133 sendShareShortcutInfoList(resultList, driList);
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001134 });
1135 }
1136
George Hodulik69d4a082019-01-18 11:27:03 -08001137 private void sendShareShortcutInfoList(
1138 List<ShortcutManager.ShareShortcutInfo> resultList,
1139 List<DisplayResolveInfo> driList) {
1140 // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
1141 // for direct share targets. After ShareSheet is refactored we should use the
1142 // ShareShortcutInfos directly.
1143 boolean resultMessageSent = false;
1144 for (int i = 0; i < driList.size(); i++) {
1145 List<ChooserTarget> chooserTargets = new ArrayList<>();
1146 for (int j = 0; j < resultList.size(); j++) {
1147 if (driList.get(i).getResolvedComponentName().equals(
1148 resultList.get(j).getTargetComponent())) {
1149 chooserTargets.add(convertToChooserTarget(resultList.get(j)));
1150 }
1151 }
1152 if (chooserTargets.isEmpty()) {
1153 continue;
1154 }
1155 final Message msg = Message.obtain();
1156 msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
1157 msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
1158 mChooserHandler.sendMessage(msg);
1159 resultMessageSent = true;
1160 }
1161
1162 if (resultMessageSent) {
1163 final Message msg = Message.obtain();
1164 msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
1165 mChooserHandler.sendMessage(msg);
1166 }
1167 }
1168
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001169 private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut) {
1170 ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
1171 Bundle extras = new Bundle();
1172 extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
1173 return new ChooserTarget(
1174 // The name of this target.
1175 shortcutInfo.getShortLabel(),
1176 // Don't load the icon until it is selected to be shown
1177 null,
1178 // The ranking score for this target (0.0-1.0); the system will omit items with low
1179 // scores when there are too many Direct Share items.
1180 0.5f,
1181 // The name of the component to be launched if this target is chosen.
1182 shareShortcut.getTargetComponent().clone(),
1183 // The extra values here will be merged into the Intent when this target is chosen.
1184 extras);
1185 }
1186
Adam Powell24428412015-04-01 17:19:56 -07001187 private String convertServiceName(String packageName, String serviceName) {
1188 if (TextUtils.isEmpty(serviceName)) {
1189 return null;
1190 }
1191
1192 final String fullName;
1193 if (serviceName.startsWith(".")) {
1194 // Relative to the app package. Prepend the app package name.
1195 fullName = packageName + serviceName;
1196 } else if (serviceName.indexOf('.') >= 0) {
1197 // Fully qualified package name.
1198 fullName = serviceName;
1199 } else {
1200 fullName = null;
1201 }
1202 return fullName;
1203 }
1204
1205 void unbindRemainingServices() {
1206 if (DEBUG) {
1207 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
1208 }
1209 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
1210 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
1211 if (DEBUG) Log.d(TAG, "unbinding " + conn);
1212 unbindService(conn);
Adam Powell9761ab22015-09-08 17:01:49 -07001213 conn.destroy();
Adam Powell24428412015-04-01 17:19:56 -07001214 }
1215 mServiceConnections.clear();
Adam Powell13036be2015-05-12 14:43:56 -07001216 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
Adam Powell24428412015-04-01 17:19:56 -07001217 }
1218
Adam Powell23882512016-01-29 10:21:00 -08001219 public void onSetupVoiceInteraction() {
Adam Powell4c470d62015-06-19 17:46:17 -07001220 // Do nothing. We'll send the voice stuff ourselves.
1221 }
1222
Kang Li9fa2a2c2017-01-06 13:33:24 -08001223 void updateModelAndChooserCounts(TargetInfo info) {
Kang Li53b43142016-11-14 14:38:25 -08001224 if (info != null) {
George Hodulikf2b0d342019-01-25 12:43:54 -08001225 if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
1226 sendClickToAppPredictor(info);
1227 }
Kang Li53b43142016-11-14 14:38:25 -08001228 final ResolveInfo ri = info.getResolveInfo();
Kang Li64b018e2017-01-05 17:30:06 -08001229 Intent targetIntent = getTargetIntent();
1230 if (ri != null && ri.activityInfo != null && targetIntent != null) {
Kang Li0cef910d2017-01-05 09:14:36 -08001231 if (mAdapter != null) {
1232 mAdapter.updateModel(info.getResolvedComponentName());
Kang Li9fa2a2c2017-01-06 13:33:24 -08001233 mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
1234 targetIntent.getAction());
Kang Li0cef910d2017-01-05 09:14:36 -08001235 }
Kang Li53b43142016-11-14 14:38:25 -08001236 if (DEBUG) {
Kang Li64b018e2017-01-05 17:30:06 -08001237 Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
Kang Li64b018e2017-01-05 17:30:06 -08001238 Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
Kang Li53b43142016-11-14 14:38:25 -08001239 }
George Hodulikf2b0d342019-01-25 12:43:54 -08001240 } else if (DEBUG) {
Kang Li53b43142016-11-14 14:38:25 -08001241 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
1242 }
1243 }
Kang Li9082f5b2016-12-02 10:56:21 -08001244 mIsSuccessfullySelected = true;
Kang Li53b43142016-11-14 14:38:25 -08001245 }
1246
George Hodulikf2b0d342019-01-25 12:43:54 -08001247 private void sendClickToAppPredictor(TargetInfo targetInfo) {
1248 if (!(targetInfo instanceof ChooserTargetInfo)) {
1249 return;
1250 }
1251 ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
1252 ComponentName componentName = chooserTarget.getComponentName();
1253 Bundle extras = chooserTarget.getIntentExtras();
1254 if (extras == null) {
1255 return;
1256 }
1257 String shortcutId = extras.getString(Intent.EXTRA_SHORTCUT_ID);
1258 if (shortcutId == null) {
1259 return;
1260 }
1261 mAppPredictor.notifyAppTargetEvent(
1262 new AppTargetEvent.Builder(
1263 new AppTarget(
1264 new AppTargetId(shortcutId),
1265 componentName.getPackageName(),
1266 componentName.getClassName(),
1267 getUser()),
1268 AppTargetEvent.ACTION_LAUNCH
1269 ).setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
1270 .build());
1271 }
1272
Adam Powell2ed547e2015-04-29 18:45:04 -07001273 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
1274 if (mRefinementResultReceiver != null) {
1275 mRefinementResultReceiver.destroy();
1276 mRefinementResultReceiver = null;
1277 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001278 if (selectedTarget == null) {
1279 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
1280 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
1281 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
1282 + " cannot match refined source intent " + matchingIntent);
Kang Li53b43142016-11-14 14:38:25 -08001283 } else {
1284 TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
1285 if (super.onTargetSelected(clonedTarget, false)) {
Kang Li9fa2a2c2017-01-06 13:33:24 -08001286 updateModelAndChooserCounts(clonedTarget);
Kang Li53b43142016-11-14 14:38:25 -08001287 finish();
1288 return;
1289 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001290 }
1291 onRefinementCanceled();
1292 }
1293
1294 void onRefinementCanceled() {
1295 if (mRefinementResultReceiver != null) {
1296 mRefinementResultReceiver.destroy();
1297 mRefinementResultReceiver = null;
1298 }
1299 finish();
1300 }
1301
1302 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
1303 final List<Intent> targetIntents = target.getAllSourceIntents();
1304 for (int i = 0, N = targetIntents.size(); i < N; i++) {
1305 final Intent targetIntent = targetIntents.get(i);
1306 if (targetIntent.filterEquals(matchingIntent)) {
1307 return true;
1308 }
1309 }
1310 return false;
1311 }
1312
Adam Powell666d82a2015-07-15 20:14:57 -07001313 void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
1314 if (targets == null) {
1315 return;
1316 }
1317
1318 final PackageManager pm = getPackageManager();
1319 for (int i = targets.size() - 1; i >= 0; i--) {
1320 final ChooserTarget target = targets.get(i);
1321 final ComponentName targetName = target.getComponentName();
1322 if (packageName != null && packageName.equals(targetName.getPackageName())) {
1323 // Anything from the original target's package is fine.
1324 continue;
1325 }
1326
1327 boolean remove;
1328 try {
1329 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
1330 remove = !ai.exported || ai.permission != null;
1331 } catch (NameNotFoundException e) {
1332 Log.e(TAG, "Target " + target + " returned by " + packageName
1333 + " component not found");
1334 remove = true;
1335 }
1336
1337 if (remove) {
1338 targets.remove(i);
1339 }
1340 }
1341 }
1342
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -05001343 protected MetricsLogger getMetricsLogger() {
1344 if (mMetricsLogger == null) {
1345 mMetricsLogger = new MetricsLogger();
1346 }
1347 return mMetricsLogger;
1348 }
1349
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001350 public class ChooserListController extends ResolverListController {
1351 public ChooserListController(Context context,
1352 PackageManager pm,
1353 Intent targetIntent,
1354 String referrerPackageName,
1355 int launchedFromUid) {
1356 super(context, pm, targetIntent, referrerPackageName, launchedFromUid);
1357 }
1358
1359 @Override
1360 boolean isComponentPinned(ComponentName name) {
1361 return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
1362 }
1363
1364 @Override
1365 boolean isComponentFiltered(ComponentName name) {
1366 if (mFilteredComponentNames == null) {
1367 return false;
1368 }
1369 for (ComponentName filteredComponentName : mFilteredComponentNames) {
1370 if (name.equals(filteredComponentName)) {
1371 return true;
1372 }
1373 }
1374 return false;
1375 }
1376
1377 @Override
1378 public float getScore(DisplayResolveInfo target) {
1379 if (target == null) {
1380 return CALLER_TARGET_SCORE_BOOST;
1381 }
1382 float score = super.getScore(target);
1383 if (target.isPinned()) {
1384 score += PINNED_TARGET_SCORE_BOOST;
1385 }
1386 return score;
1387 }
1388 }
1389
Adam Powell24428412015-04-01 17:19:56 -07001390 @Override
Adam Powell23882512016-01-29 10:21:00 -08001391 public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
Adam Powell7d758002015-05-06 17:49:36 -07001392 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
1393 boolean filterLastUsed) {
1394 final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001395 initialIntents, rList, launchedFromUid, filterLastUsed, createListController());
Adam Powell24428412015-04-01 17:19:56 -07001396 return adapter;
1397 }
1398
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001399 @VisibleForTesting
1400 protected ResolverListController createListController() {
1401 return new ChooserListController(
1402 this,
1403 mPm,
1404 getTargetIntent(),
1405 getReferrerPackageName(),
1406 mLaunchedFromUid);
1407 }
1408
Matt Pietal26038402019-01-08 07:29:34 -05001409 @VisibleForTesting
1410 protected Bitmap loadThumbnail(Uri uri, Size size) {
1411 if (uri == null || size == null) {
1412 return null;
1413 }
1414
1415 try {
Matt Pietal46d828c2019-02-05 08:07:07 -05001416 return ImageUtils.loadThumbnail(getContentResolver(), uri, size);
1417 } catch (IOException | NullPointerException | SecurityException ex) {
Matt Pietal26038402019-01-08 07:29:34 -05001418 Log.w(TAG, "Error loading preview thumbnail for uri: " + uri.toString(), ex);
1419 }
1420 return null;
1421 }
1422
Adam Powell2ed547e2015-04-29 18:45:04 -07001423 final class ChooserTargetInfo implements TargetInfo {
1424 private final DisplayResolveInfo mSourceInfo;
Adam Powell0ccc0e92015-04-23 17:19:37 -07001425 private final ResolveInfo mBackupResolveInfo;
Adam Powell24428412015-04-01 17:19:56 -07001426 private final ChooserTarget mChooserTarget;
Adam Powell7d758002015-05-06 17:49:36 -07001427 private Drawable mBadgeIcon = null;
Alan Viverettece5d92c2015-07-31 16:46:56 -04001428 private CharSequence mBadgeContentDescription;
Adam Powell13036be2015-05-12 14:43:56 -07001429 private Drawable mDisplayIcon;
Adam Powell2ed547e2015-04-29 18:45:04 -07001430 private final Intent mFillInIntent;
1431 private final int mFillInFlags;
Adam Powella182e452015-07-06 16:57:56 -07001432 private final float mModifiedScore;
Adam Powell24428412015-04-01 17:19:56 -07001433
Adam Powella182e452015-07-06 16:57:56 -07001434 public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
1435 float modifiedScore) {
Adam Powell24428412015-04-01 17:19:56 -07001436 mSourceInfo = sourceInfo;
1437 mChooserTarget = chooserTarget;
Adam Powella182e452015-07-06 16:57:56 -07001438 mModifiedScore = modifiedScore;
Adam Powell7d758002015-05-06 17:49:36 -07001439 if (sourceInfo != null) {
1440 final ResolveInfo ri = sourceInfo.getResolveInfo();
1441 if (ri != null) {
1442 final ActivityInfo ai = ri.activityInfo;
1443 if (ai != null && ai.applicationInfo != null) {
Alan Viverettece5d92c2015-07-31 16:46:56 -04001444 final PackageManager pm = getPackageManager();
1445 mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
1446 mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
Adam Powell7d758002015-05-06 17:49:36 -07001447 }
1448 }
1449 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001450 // TODO(b/121287224): do this in the background thread, and only for selected targets
1451 mDisplayIcon = getChooserTargetIconDrawable(chooserTarget);
Adam Powell0ccc0e92015-04-23 17:19:37 -07001452
1453 if (sourceInfo != null) {
1454 mBackupResolveInfo = null;
1455 } else {
1456 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
1457 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001458
1459 mFillInIntent = null;
1460 mFillInFlags = 0;
1461 }
1462
1463 private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) {
1464 mSourceInfo = other.mSourceInfo;
1465 mBackupResolveInfo = other.mBackupResolveInfo;
1466 mChooserTarget = other.mChooserTarget;
Adam Powell7d758002015-05-06 17:49:36 -07001467 mBadgeIcon = other.mBadgeIcon;
Alan Viverettece5d92c2015-07-31 16:46:56 -04001468 mBadgeContentDescription = other.mBadgeContentDescription;
Adam Powell2ed547e2015-04-29 18:45:04 -07001469 mDisplayIcon = other.mDisplayIcon;
1470 mFillInIntent = fillInIntent;
1471 mFillInFlags = flags;
Adam Powella182e452015-07-06 16:57:56 -07001472 mModifiedScore = other.mModifiedScore;
1473 }
1474
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001475 /**
1476 * Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
1477 * the call to LauncherApps#getShortcuts(ShortcutQuery).
1478 */
1479 // TODO(121287224): Refactor code to apply the suggestion above
1480 private Drawable getChooserTargetIconDrawable(ChooserTarget target) {
1481 final Icon icon = target.getIcon();
1482 if (icon != null) {
1483 return icon.loadDrawable(ChooserActivity.this);
1484 }
1485 if (!USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
1486 return null;
1487 }
1488
1489 Bundle extras = target.getIntentExtras();
1490 if (extras == null || !extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
1491 return null;
1492 }
1493 CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
1494 LauncherApps launcherApps = (LauncherApps) getSystemService(
1495 Context.LAUNCHER_APPS_SERVICE);
1496 final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
1497 q.setPackage(target.getComponentName().getPackageName());
1498 q.setShortcutIds(Arrays.asList(shortcutId.toString()));
1499 q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
1500 final List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(q, getUser());
1501 if (shortcuts != null && shortcuts.size() > 0) {
1502 return launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
1503 }
1504
1505 return null;
1506 }
1507
Adam Powella182e452015-07-06 16:57:56 -07001508 public float getModifiedScore() {
1509 return mModifiedScore;
Adam Powell24428412015-04-01 17:19:56 -07001510 }
1511
1512 @Override
1513 public Intent getResolvedIntent() {
Adam Powell7d758002015-05-06 17:49:36 -07001514 if (mSourceInfo != null) {
Adam Powell0ccc0e92015-04-23 17:19:37 -07001515 return mSourceInfo.getResolvedIntent();
1516 }
Adam Powell52c39212016-04-07 15:14:18 -07001517
1518 final Intent targetIntent = new Intent(getTargetIntent());
1519 targetIntent.setComponent(mChooserTarget.getComponentName());
1520 targetIntent.putExtras(mChooserTarget.getIntentExtras());
1521 return targetIntent;
Adam Powell24428412015-04-01 17:19:56 -07001522 }
1523
1524 @Override
1525 public ComponentName getResolvedComponentName() {
Adam Powell0ccc0e92015-04-23 17:19:37 -07001526 if (mSourceInfo != null) {
1527 return mSourceInfo.getResolvedComponentName();
1528 } else if (mBackupResolveInfo != null) {
1529 return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
1530 mBackupResolveInfo.activityInfo.name);
1531 }
1532 return null;
1533 }
1534
Adam Powell666d82a2015-07-15 20:14:57 -07001535 private Intent getBaseIntentToSend() {
Adam Powell52c39212016-04-07 15:14:18 -07001536 Intent result = getResolvedIntent();
Adam Powell2ed547e2015-04-29 18:45:04 -07001537 if (result == null) {
Adam Powell666d82a2015-07-15 20:14:57 -07001538 Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
Adam Powell13036be2015-05-12 14:43:56 -07001539 } else {
Adam Powell2ed547e2015-04-29 18:45:04 -07001540 result = new Intent(result);
Adam Powell13036be2015-05-12 14:43:56 -07001541 if (mFillInIntent != null) {
1542 result.fillIn(mFillInIntent, mFillInFlags);
1543 }
1544 result.fillIn(mReferrerFillInIntent, 0);
Adam Powell2ed547e2015-04-29 18:45:04 -07001545 }
1546 return result;
Adam Powell24428412015-04-01 17:19:56 -07001547 }
1548
1549 @Override
1550 public boolean start(Activity activity, Bundle options) {
Adam Powell666d82a2015-07-15 20:14:57 -07001551 throw new RuntimeException("ChooserTargets should be started as caller.");
Adam Powell24428412015-04-01 17:19:56 -07001552 }
1553
1554 @Override
Alison Cichowlas3e340502018-08-07 17:15:01 -04001555 public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
Adam Powell666d82a2015-07-15 20:14:57 -07001556 final Intent intent = getBaseIntentToSend();
Adam Powell2ed547e2015-04-29 18:45:04 -07001557 if (intent == null) {
1558 return false;
1559 }
Adam Powell666d82a2015-07-15 20:14:57 -07001560 intent.setComponent(mChooserTarget.getComponentName());
Makoto Onuki99302b52017-03-29 12:42:26 -07001561 intent.putExtras(mChooserTarget.getIntentExtras());
Adam Powell52c39212016-04-07 15:14:18 -07001562
1563 // Important: we will ignore the target security checks in ActivityManager
1564 // if and only if the ChooserTarget's target package is the same package
1565 // where we got the ChooserTargetService that provided it. This lets a
1566 // ChooserTargetService provide a non-exported or permission-guarded target
1567 // to the chooser for the user to pick.
1568 //
1569 // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
1570 // so we'll obey the caller's normal security checks.
1571 final boolean ignoreTargetSecurity = mSourceInfo != null
1572 && mSourceInfo.getResolvedComponentName().getPackageName()
1573 .equals(mChooserTarget.getComponentName().getPackageName());
Alison Cichowlas3e340502018-08-07 17:15:01 -04001574 return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
Adam Powell24428412015-04-01 17:19:56 -07001575 }
1576
1577 @Override
1578 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
Adam Powell666d82a2015-07-15 20:14:57 -07001579 throw new RuntimeException("ChooserTargets should be started as caller.");
Adam Powell24428412015-04-01 17:19:56 -07001580 }
1581
1582 @Override
1583 public ResolveInfo getResolveInfo() {
Adam Powell0ccc0e92015-04-23 17:19:37 -07001584 return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
Adam Powell24428412015-04-01 17:19:56 -07001585 }
1586
1587 @Override
1588 public CharSequence getDisplayLabel() {
1589 return mChooserTarget.getTitle();
1590 }
1591
1592 @Override
1593 public CharSequence getExtendedInfo() {
Adam Powell00f4aad2015-09-17 13:38:16 -07001594 // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
1595 return null;
Adam Powell24428412015-04-01 17:19:56 -07001596 }
1597
1598 @Override
1599 public Drawable getDisplayIcon() {
1600 return mDisplayIcon;
1601 }
Adam Powell2ed547e2015-04-29 18:45:04 -07001602
1603 @Override
Adam Powell7d758002015-05-06 17:49:36 -07001604 public Drawable getBadgeIcon() {
1605 return mBadgeIcon;
1606 }
1607
1608 @Override
Alan Viverettece5d92c2015-07-31 16:46:56 -04001609 public CharSequence getBadgeContentDescription() {
1610 return mBadgeContentDescription;
1611 }
1612
George Hodulikf2b0d342019-01-25 12:43:54 -08001613 public ChooserTarget getChooserTarget() {
1614 return mChooserTarget;
1615 }
1616
Alan Viverettece5d92c2015-07-31 16:46:56 -04001617 @Override
Adam Powell2ed547e2015-04-29 18:45:04 -07001618 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
1619 return new ChooserTargetInfo(this, fillInIntent, flags);
1620 }
1621
1622 @Override
1623 public List<Intent> getAllSourceIntents() {
1624 final List<Intent> results = new ArrayList<>();
1625 if (mSourceInfo != null) {
1626 // We only queried the service for the first one in our sourceinfo.
1627 results.add(mSourceInfo.getAllSourceIntents().get(0));
1628 }
1629 return results;
1630 }
Adam Powell23882512016-01-29 10:21:00 -08001631
1632 @Override
1633 public boolean isPinned() {
1634 return mSourceInfo != null ? mSourceInfo.isPinned() : false;
1635 }
Adam Powell24428412015-04-01 17:19:56 -07001636 }
1637
1638 public class ChooserListAdapter extends ResolveListAdapter {
Adam Powell7d758002015-05-06 17:49:36 -07001639 public static final int TARGET_BAD = -1;
1640 public static final int TARGET_CALLER = 0;
1641 public static final int TARGET_SERVICE = 1;
1642 public static final int TARGET_STANDARD = 2;
1643
Dan Sandler62aad002018-05-23 02:13:51 -04001644 private static final int MAX_SERVICE_TARGETS = 4;
Dan Sandlerf5e17692018-06-04 22:13:40 -04001645 private static final int MAX_TARGETS_PER_SERVICE = 2;
Adam Powella182e452015-07-06 16:57:56 -07001646
Adam Powell24428412015-04-01 17:19:56 -07001647 private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
Adam Powell7d758002015-05-06 17:49:36 -07001648 private final List<TargetInfo> mCallerTargets = new ArrayList<>();
Adam Powell565943f2016-02-11 10:29:05 -08001649 private boolean mShowServiceTargets;
Adam Powell24428412015-04-01 17:19:56 -07001650
Adam Powella182e452015-07-06 16:57:56 -07001651 private float mLateFee = 1.f;
1652
Dan Sandlerf5e17692018-06-04 22:13:40 -04001653 private boolean mTargetsNeedPruning = false;
1654
Adam Powella182e452015-07-06 16:57:56 -07001655 private final BaseChooserTargetComparator mBaseTargetComparator
1656 = new BaseChooserTargetComparator();
1657
Adam Powell7d758002015-05-06 17:49:36 -07001658 public ChooserListAdapter(Context context, List<Intent> payloadIntents,
1659 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001660 boolean filterLastUsed, ResolverListController resolverListController) {
Adam Powell7d758002015-05-06 17:49:36 -07001661 // Don't send the initial intents through the shared ResolverActivity path,
1662 // we want to separate them into a different section.
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001663 super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed,
1664 resolverListController);
Adam Powell0ccc0e92015-04-23 17:19:37 -07001665
Adam Powell7d758002015-05-06 17:49:36 -07001666 if (initialIntents != null) {
1667 final PackageManager pm = getPackageManager();
1668 for (int i = 0; i < initialIntents.length; i++) {
1669 final Intent ii = initialIntents[i];
1670 if (ii == null) {
1671 continue;
1672 }
Adam Powell86100d12016-05-12 16:13:17 -07001673
1674 // We reimplement Intent#resolveActivityInfo here because if we have an
1675 // implicit intent, we want the ResolveInfo returned by PackageManager
1676 // instead of one we reconstruct ourselves. The ResolveInfo returned might
1677 // have extra metadata and resolvePackageName set and we want to respect that.
1678 ResolveInfo ri = null;
1679 ActivityInfo ai = null;
1680 final ComponentName cn = ii.getComponent();
1681 if (cn != null) {
1682 try {
1683 ai = pm.getActivityInfo(ii.getComponent(), 0);
1684 ri = new ResolveInfo();
1685 ri.activityInfo = ai;
1686 } catch (PackageManager.NameNotFoundException ignored) {
1687 // ai will == null below
1688 }
1689 }
1690 if (ai == null) {
1691 ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
1692 ai = ri != null ? ri.activityInfo : null;
1693 }
Adam Powell7d758002015-05-06 17:49:36 -07001694 if (ai == null) {
1695 Log.w(TAG, "No activity found for " + ii);
1696 continue;
1697 }
Adam Powell7d758002015-05-06 17:49:36 -07001698 UserManager userManager =
1699 (UserManager) getSystemService(Context.USER_SERVICE);
Adam Powell7d758002015-05-06 17:49:36 -07001700 if (ii instanceof LabeledIntent) {
Matt Pietal26038402019-01-08 07:29:34 -05001701 LabeledIntent li = (LabeledIntent) ii;
Adam Powell7d758002015-05-06 17:49:36 -07001702 ri.resolvePackageName = li.getSourcePackage();
1703 ri.labelRes = li.getLabelResource();
1704 ri.nonLocalizedLabel = li.getNonLocalizedLabel();
1705 ri.icon = li.getIconResource();
Sudheer Shanka9ded7602015-05-19 21:17:25 +01001706 ri.iconResourceId = ri.icon;
1707 }
1708 if (userManager.isManagedProfile()) {
1709 ri.noResourceId = true;
1710 ri.icon = 0;
Adam Powell7d758002015-05-06 17:49:36 -07001711 }
1712 mCallerTargets.add(new DisplayResolveInfo(ii, ri,
1713 ri.loadLabel(pm), null, ii));
Adam Powelld974c7b2015-04-28 15:41:46 -07001714 }
Adam Powell0ccc0e92015-04-23 17:19:37 -07001715 }
Adam Powell24428412015-04-01 17:19:56 -07001716 }
1717
1718 @Override
1719 public boolean showsExtendedInfo(TargetInfo info) {
Adam Powell00f4aad2015-09-17 13:38:16 -07001720 // We have badges so we don't need this text shown.
1721 return false;
Adam Powell24428412015-04-01 17:19:56 -07001722 }
1723
1724 @Override
Adam Powell23882512016-01-29 10:21:00 -08001725 public boolean isComponentPinned(ComponentName name) {
1726 return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
1727 }
1728
1729 @Override
Adam Powell7d758002015-05-06 17:49:36 -07001730 public View onCreateView(ViewGroup parent) {
Adam Powell24428412015-04-01 17:19:56 -07001731 return mInflater.inflate(
1732 com.android.internal.R.layout.resolve_grid_item, parent, false);
1733 }
1734
1735 @Override
1736 public void onListRebuilt() {
Ng Zhi And3ec5fc2018-05-10 09:13:00 -07001737 // don't support direct share on low ram devices
1738 if (ActivityManager.isLowRamDeviceStatic()) {
1739 return;
1740 }
1741
Adam Powell24428412015-04-01 17:19:56 -07001742 if (mServiceTargets != null) {
Dan Sandlerf5e17692018-06-04 22:13:40 -04001743 if (getDisplayInfoCount() == 0) {
1744 // b/109676071: When packages change, onListRebuilt() is called before
1745 // ResolverActivity.mDisplayList is re-populated; pruning now would cause the
1746 // list to disappear briefly, so instead we detect this case (the
1747 // set of targets suddenly dropping to zero) and remember to prune later.
1748 mTargetsNeedPruning = true;
1749 }
Adam Powell24428412015-04-01 17:19:56 -07001750 }
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -08001751
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001752 if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
1753 if (DEBUG) {
1754 Log.d(TAG, "querying direct share targets from ShortcutManager");
1755 }
1756 queryDirectShareTargets(this);
Mehdi Alizadeh8e248ad2019-01-17 11:41:49 -08001757 }
1758 if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
1759 if (DEBUG) {
1760 Log.d(TAG, "List built querying services");
1761 }
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -08001762 queryTargetServices(this);
1763 }
Adam Powell24428412015-04-01 17:19:56 -07001764 }
1765
1766 @Override
Adam Powellc6d5e3a2015-04-23 12:22:20 -07001767 public boolean shouldGetResolvedFilter() {
1768 return true;
1769 }
1770
1771 @Override
Adam Powell24428412015-04-01 17:19:56 -07001772 public int getCount() {
Adam Powella182e452015-07-06 16:57:56 -07001773 return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
Adam Powell24428412015-04-01 17:19:56 -07001774 }
1775
Adam Powell50077352015-05-26 18:01:55 -07001776 @Override
1777 public int getUnfilteredCount() {
Adam Powella182e452015-07-06 16:57:56 -07001778 return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
Adam Powell50077352015-05-26 18:01:55 -07001779 }
1780
1781 public int getCallerTargetCount() {
Adam Powell7d758002015-05-06 17:49:36 -07001782 return mCallerTargets.size();
1783 }
1784
Adam Powell50077352015-05-26 18:01:55 -07001785 public int getServiceTargetCount() {
Adam Powell565943f2016-02-11 10:29:05 -08001786 if (!mShowServiceTargets) {
1787 return 0;
1788 }
Adam Powella182e452015-07-06 16:57:56 -07001789 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
Adam Powell7d758002015-05-06 17:49:36 -07001790 }
1791
1792 public int getStandardTargetCount() {
1793 return super.getCount();
1794 }
1795
1796 public int getPositionTargetType(int position) {
1797 int offset = 0;
1798
Adam Powella182e452015-07-06 16:57:56 -07001799 final int callerTargetCount = getCallerTargetCount();
Adam Powell7d758002015-05-06 17:49:36 -07001800 if (position < callerTargetCount) {
1801 return TARGET_CALLER;
1802 }
1803 offset += callerTargetCount;
1804
Adam Powella182e452015-07-06 16:57:56 -07001805 final int serviceTargetCount = getServiceTargetCount();
Adam Powell7d758002015-05-06 17:49:36 -07001806 if (position - offset < serviceTargetCount) {
1807 return TARGET_SERVICE;
1808 }
1809 offset += serviceTargetCount;
1810
1811 final int standardTargetCount = super.getCount();
1812 if (position - offset < standardTargetCount) {
1813 return TARGET_STANDARD;
1814 }
1815
1816 return TARGET_BAD;
1817 }
1818
Adam Powell24428412015-04-01 17:19:56 -07001819 @Override
1820 public TargetInfo getItem(int position) {
Adam Powell50077352015-05-26 18:01:55 -07001821 return targetInfoForPosition(position, true);
1822 }
1823
1824 @Override
1825 public TargetInfo targetInfoForPosition(int position, boolean filtered) {
Adam Powell24428412015-04-01 17:19:56 -07001826 int offset = 0;
Adam Powell0ccc0e92015-04-23 17:19:37 -07001827
Adam Powella182e452015-07-06 16:57:56 -07001828 final int callerTargetCount = getCallerTargetCount();
Adam Powell0ccc0e92015-04-23 17:19:37 -07001829 if (position < callerTargetCount) {
1830 return mCallerTargets.get(position);
Adam Powell24428412015-04-01 17:19:56 -07001831 }
Adam Powell0ccc0e92015-04-23 17:19:37 -07001832 offset += callerTargetCount;
1833
Adam Powella182e452015-07-06 16:57:56 -07001834 final int serviceTargetCount = getServiceTargetCount();
Adam Powell0ccc0e92015-04-23 17:19:37 -07001835 if (position - offset < serviceTargetCount) {
1836 return mServiceTargets.get(position - offset);
1837 }
1838 offset += serviceTargetCount;
1839
Adam Powell50077352015-05-26 18:01:55 -07001840 return filtered ? super.getItem(position - offset)
1841 : getDisplayInfoAt(position - offset);
Adam Powell24428412015-04-01 17:19:56 -07001842 }
1843
1844 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
Matt Pietal26038402019-01-08 07:29:34 -05001845 if (DEBUG) {
1846 Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
1847 + " targets");
1848 }
Dan Sandlerf5e17692018-06-04 22:13:40 -04001849
1850 if (mTargetsNeedPruning && targets.size() > 0) {
1851 // First proper update since we got an onListRebuilt() with (transient) 0 items.
1852 // Clear out the target list and rebuild.
1853 mServiceTargets.clear();
1854 mTargetsNeedPruning = false;
1855 }
1856
Adam Powella182e452015-07-06 16:57:56 -07001857 final float parentScore = getScore(origTarget);
1858 Collections.sort(targets, mBaseTargetComparator);
1859 float lastScore = 0;
Adam Powellbba00302016-02-03 16:01:09 -08001860 for (int i = 0, N = Math.min(targets.size(), MAX_TARGETS_PER_SERVICE); i < N; i++) {
Adam Powella182e452015-07-06 16:57:56 -07001861 final ChooserTarget target = targets.get(i);
1862 float targetScore = target.getScore();
1863 targetScore *= parentScore;
1864 targetScore *= mLateFee;
1865 if (i > 0 && targetScore >= lastScore) {
1866 // Apply a decay so that the top app can't crowd out everything else.
1867 // This incents ChooserTargetServices to define what's truly better.
1868 targetScore = lastScore * 0.95f;
1869 }
1870 insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore));
1871
1872 if (DEBUG) {
1873 Log.d(TAG, " => " + target.toString() + " score=" + targetScore
1874 + " base=" + target.getScore()
1875 + " lastScore=" + lastScore
1876 + " parentScore=" + parentScore
1877 + " lateFee=" + mLateFee);
1878 }
1879
1880 lastScore = targetScore;
Adam Powell24428412015-04-01 17:19:56 -07001881 }
1882
Adam Powella182e452015-07-06 16:57:56 -07001883 mLateFee *= 0.95f;
Adam Powell24428412015-04-01 17:19:56 -07001884
1885 notifyDataSetChanged();
1886 }
1887
Adam Powell565943f2016-02-11 10:29:05 -08001888 /**
1889 * Set to true to reveal all service targets at once.
1890 */
1891 public void setShowServiceTargets(boolean show) {
Adam Powell08adbfe2017-05-10 07:48:30 -07001892 if (show != mShowServiceTargets) {
1893 mShowServiceTargets = show;
1894 notifyDataSetChanged();
Susi Kharraz-Post7e2115d2019-02-01 16:51:22 -05001895 getMetricsLogger().write(
1896 new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN_DIRECT_TARGET));
Adam Powell08adbfe2017-05-10 07:48:30 -07001897 }
Adam Powell565943f2016-02-11 10:29:05 -08001898 }
1899
Adam Powella182e452015-07-06 16:57:56 -07001900 private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
1901 final float newScore = chooserTargetInfo.getModifiedScore();
1902 for (int i = 0, N = mServiceTargets.size(); i < N; i++) {
1903 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
1904 if (newScore > serviceTarget.getModifiedScore()) {
1905 mServiceTargets.add(i, chooserTargetInfo);
1906 return;
1907 }
1908 }
1909 mServiceTargets.add(chooserTargetInfo);
1910 }
Adam Powell24428412015-04-01 17:19:56 -07001911 }
1912
Adam Powella182e452015-07-06 16:57:56 -07001913 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
1914 @Override
1915 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
1916 // Descending order
Adam Powell77a533f2015-10-16 10:47:32 -07001917 return (int) Math.signum(rhs.getScore() - lhs.getScore());
Adam Powella182e452015-07-06 16:57:56 -07001918 }
1919 }
1920
Adam Powell7d758002015-05-06 17:49:36 -07001921 class ChooserRowAdapter extends BaseAdapter {
1922 private ChooserListAdapter mChooserListAdapter;
1923 private final LayoutInflater mLayoutInflater;
1924 private final int mColumnCount = 4;
Adam Powell08adbfe2017-05-10 07:48:30 -07001925 private int mAnimationCount = 0;
Adam Powell7d758002015-05-06 17:49:36 -07001926
1927 public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
1928 mChooserListAdapter = wrappedAdapter;
1929 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
1930
1931 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
1932 @Override
1933 public void onChanged() {
1934 super.onChanged();
1935 notifyDataSetChanged();
1936 }
1937
1938 @Override
1939 public void onInvalidated() {
1940 super.onInvalidated();
1941 notifyDataSetInvalidated();
1942 }
1943 });
1944 }
1945
1946 @Override
1947 public int getCount() {
1948 return (int) (
Adam Powell63b31692015-09-28 10:45:00 -07001949 getCallerTargetRowCount()
Matt Pietal26038402019-01-08 07:29:34 -05001950 + getServiceTargetRowCount()
1951 + Math.ceil(
1952 (float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
Adam Powell7d758002015-05-06 17:49:36 -07001953 );
1954 }
1955
Adam Powell63b31692015-09-28 10:45:00 -07001956 public int getCallerTargetRowCount() {
1957 return (int) Math.ceil(
1958 (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount);
1959 }
1960
Dan Sandler62aad002018-05-23 02:13:51 -04001961 // There can be at most one row of service targets.
Adam Powell63b31692015-09-28 10:45:00 -07001962 public int getServiceTargetRowCount() {
Dan Sandler62aad002018-05-23 02:13:51 -04001963 return (int) mChooserListAdapter.getServiceTargetCount() == 0 ? 0 : 1;
Adam Powell63b31692015-09-28 10:45:00 -07001964 }
1965
Adam Powell7d758002015-05-06 17:49:36 -07001966 @Override
1967 public Object getItem(int position) {
1968 // We have nothing useful to return here.
1969 return position;
1970 }
1971
1972 @Override
1973 public long getItemId(int position) {
1974 return position;
1975 }
1976
1977 @Override
1978 public View getView(int position, View convertView, ViewGroup parent) {
Adam Powell63b31692015-09-28 10:45:00 -07001979 final RowViewHolder holder;
Adam Powell7d758002015-05-06 17:49:36 -07001980 if (convertView == null) {
1981 holder = createViewHolder(parent);
1982 } else {
Adam Powell63b31692015-09-28 10:45:00 -07001983 holder = (RowViewHolder) convertView.getTag();
Adam Powell7d758002015-05-06 17:49:36 -07001984 }
1985 bindViewHolder(position, holder);
1986
Adam Powell63b31692015-09-28 10:45:00 -07001987 return holder.row;
Adam Powell7d758002015-05-06 17:49:36 -07001988 }
1989
Adam Powell63b31692015-09-28 10:45:00 -07001990 RowViewHolder createViewHolder(ViewGroup parent) {
Adam Powell7d758002015-05-06 17:49:36 -07001991 final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
1992 parent, false);
Adam Powell63b31692015-09-28 10:45:00 -07001993 final RowViewHolder holder = new RowViewHolder(row, mColumnCount);
1994 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1995
Adam Powell7d758002015-05-06 17:49:36 -07001996 for (int i = 0; i < mColumnCount; i++) {
Adam Powell63b31692015-09-28 10:45:00 -07001997 final View v = mChooserListAdapter.createView(row);
Adam Powell4eb98712015-10-14 13:10:18 -07001998 final int column = i;
Adam Powell63b31692015-09-28 10:45:00 -07001999 v.setOnClickListener(new OnClickListener() {
2000 @Override
2001 public void onClick(View v) {
Adam Powell4eb98712015-10-14 13:10:18 -07002002 startSelected(holder.itemIndices[column], false, true);
Adam Powell63b31692015-09-28 10:45:00 -07002003 }
2004 });
2005 v.setOnLongClickListener(new OnLongClickListener() {
2006 @Override
2007 public boolean onLongClick(View v) {
Adam Powell23882512016-01-29 10:21:00 -08002008 showTargetDetails(
Adam Powell4eb98712015-10-14 13:10:18 -07002009 mChooserListAdapter.resolveInfoForPosition(
2010 holder.itemIndices[column], true));
Adam Powell63b31692015-09-28 10:45:00 -07002011 return true;
2012 }
2013 });
2014 row.addView(v);
2015 holder.cells[i] = v;
2016
2017 // Force height to be a given so we don't have visual disruption during scaling.
2018 LayoutParams lp = v.getLayoutParams();
2019 v.measure(spec, spec);
2020 if (lp == null) {
2021 lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight());
2022 row.setLayoutParams(lp);
2023 } else {
2024 lp.height = v.getMeasuredHeight();
2025 }
Jason Monk027dcfa2017-06-27 18:37:35 -04002026 if (i != (mColumnCount - 1)) {
2027 row.addView(new Space(ChooserActivity.this),
2028 new LinearLayout.LayoutParams(0, 0, 1));
2029 }
Adam Powell63b31692015-09-28 10:45:00 -07002030 }
2031
2032 // Pre-measure so we can scale later.
2033 holder.measure();
2034 LayoutParams lp = row.getLayoutParams();
2035 if (lp == null) {
2036 lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.measuredRowHeight);
2037 row.setLayoutParams(lp);
2038 } else {
2039 lp.height = holder.measuredRowHeight;
Adam Powell7d758002015-05-06 17:49:36 -07002040 }
2041 row.setTag(holder);
Adam Powell7d758002015-05-06 17:49:36 -07002042 return holder;
2043 }
2044
Adam Powell63b31692015-09-28 10:45:00 -07002045 void bindViewHolder(int rowPosition, RowViewHolder holder) {
Adam Powell7d758002015-05-06 17:49:36 -07002046 final int start = getFirstRowPosition(rowPosition);
2047 final int startType = mChooserListAdapter.getPositionTargetType(start);
2048
2049 int end = start + mColumnCount - 1;
2050 while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
2051 end--;
2052 }
2053
Adam Powell7d758002015-05-06 17:49:36 -07002054 if (startType == ChooserListAdapter.TARGET_SERVICE) {
Jason Monk027dcfa2017-06-27 18:37:35 -04002055 int nextStartType = mChooserListAdapter.getPositionTargetType(
2056 getFirstRowPosition(rowPosition + 1));
2057 int serviceSpacing = holder.row.getContext().getResources()
2058 .getDimensionPixelSize(R.dimen.chooser_service_spacing);
Beverly3aee1782017-09-21 10:01:41 -04002059 if (rowPosition == 0 && nextStartType != ChooserListAdapter.TARGET_SERVICE) {
2060 // if the row is the only row for target service
2061 setVertPadding(holder, 0, 0);
Jason Monk027dcfa2017-06-27 18:37:35 -04002062 } else {
Beverly3aee1782017-09-21 10:01:41 -04002063 int top = rowPosition == 0 ? serviceSpacing : 0;
2064 if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
2065 setVertPadding(holder, top, serviceSpacing);
2066 } else {
2067 setVertPadding(holder, top, 0);
2068 }
Jason Monk027dcfa2017-06-27 18:37:35 -04002069 }
Adam Powell7d758002015-05-06 17:49:36 -07002070 } else {
Adam Powell63b31692015-09-28 10:45:00 -07002071 holder.row.setBackgroundColor(Color.TRANSPARENT);
Jason Monk027dcfa2017-06-27 18:37:35 -04002072 int lastStartType = mChooserListAdapter.getPositionTargetType(
2073 getFirstRowPosition(rowPosition - 1));
2074 if (lastStartType == ChooserListAdapter.TARGET_SERVICE || rowPosition == 0) {
2075 int serviceSpacing = holder.row.getContext().getResources()
2076 .getDimensionPixelSize(R.dimen.chooser_service_spacing);
2077 setVertPadding(holder, serviceSpacing, 0);
2078 } else {
2079 setVertPadding(holder, 0, 0);
2080 }
Adam Powell63b31692015-09-28 10:45:00 -07002081 }
2082
2083 final int oldHeight = holder.row.getLayoutParams().height;
Dan Sandler62aad002018-05-23 02:13:51 -04002084 holder.row.getLayoutParams().height = Math.max(1, holder.measuredRowHeight);
Adam Powell63b31692015-09-28 10:45:00 -07002085 if (holder.row.getLayoutParams().height != oldHeight) {
2086 holder.row.requestLayout();
Adam Powell7d758002015-05-06 17:49:36 -07002087 }
2088
2089 for (int i = 0; i < mColumnCount; i++) {
Adam Powell63b31692015-09-28 10:45:00 -07002090 final View v = holder.cells[i];
Adam Powell7d758002015-05-06 17:49:36 -07002091 if (start + i <= end) {
2092 v.setVisibility(View.VISIBLE);
Adam Powell4eb98712015-10-14 13:10:18 -07002093 holder.itemIndices[i] = start + i;
2094 mChooserListAdapter.bindView(holder.itemIndices[i], v);
Adam Powell7d758002015-05-06 17:49:36 -07002095 } else {
Jason Monk027dcfa2017-06-27 18:37:35 -04002096 v.setVisibility(View.INVISIBLE);
Adam Powell7d758002015-05-06 17:49:36 -07002097 }
2098 }
2099 }
2100
Jason Monk027dcfa2017-06-27 18:37:35 -04002101 private void setVertPadding(RowViewHolder holder, int top, int bottom) {
2102 holder.row.setPadding(holder.row.getPaddingLeft(), top,
2103 holder.row.getPaddingRight(), bottom);
2104 }
2105
Adam Powell7d758002015-05-06 17:49:36 -07002106 int getFirstRowPosition(int row) {
Adam Powell50077352015-05-26 18:01:55 -07002107 final int callerCount = mChooserListAdapter.getCallerTargetCount();
Adam Powell7d758002015-05-06 17:49:36 -07002108 final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
2109
2110 if (row < callerRows) {
2111 return row * mColumnCount;
2112 }
2113
Adam Powell50077352015-05-26 18:01:55 -07002114 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
Adam Powell7d758002015-05-06 17:49:36 -07002115 final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
2116
2117 if (row < callerRows + serviceRows) {
2118 return callerCount + (row - callerRows) * mColumnCount;
2119 }
2120
2121 return callerCount + serviceCount
2122 + (row - callerRows - serviceRows) * mColumnCount;
2123 }
2124 }
2125
Adam Powell63b31692015-09-28 10:45:00 -07002126 static class RowViewHolder {
2127 final View[] cells;
2128 final ViewGroup row;
2129 int measuredRowHeight;
Adam Powell4eb98712015-10-14 13:10:18 -07002130 int[] itemIndices;
Adam Powell63b31692015-09-28 10:45:00 -07002131
2132 public RowViewHolder(ViewGroup row, int cellCount) {
2133 this.row = row;
2134 this.cells = new View[cellCount];
Adam Powell4eb98712015-10-14 13:10:18 -07002135 this.itemIndices = new int[cellCount];
Adam Powell63b31692015-09-28 10:45:00 -07002136 }
2137
2138 public void measure() {
2139 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
2140 row.measure(spec, spec);
2141 measuredRowHeight = row.getMeasuredHeight();
2142 }
2143 }
2144
Adam Powell9761ab22015-09-08 17:01:49 -07002145 static class ChooserTargetServiceConnection implements ServiceConnection {
Adam Powell52c39212016-04-07 15:14:18 -07002146 private DisplayResolveInfo mOriginalTarget;
Adam Powell9761ab22015-09-08 17:01:49 -07002147 private ComponentName mConnectedComponent;
2148 private ChooserActivity mChooserActivity;
2149 private final Object mLock = new Object();
Adam Powell24428412015-04-01 17:19:56 -07002150
2151 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
2152 @Override
2153 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
Adam Powell9761ab22015-09-08 17:01:49 -07002154 synchronized (mLock) {
2155 if (mChooserActivity == null) {
2156 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
2157 + mConnectedComponent + "; ignoring...");
2158 return;
2159 }
2160 mChooserActivity.filterServiceTargets(
2161 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
2162 final Message msg = Message.obtain();
2163 msg.what = CHOOSER_TARGET_SERVICE_RESULT;
2164 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
2165 ChooserTargetServiceConnection.this);
2166 mChooserActivity.mChooserHandler.sendMessage(msg);
2167 }
Adam Powell24428412015-04-01 17:19:56 -07002168 }
2169 };
2170
Adam Powell9761ab22015-09-08 17:01:49 -07002171 public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
2172 DisplayResolveInfo dri) {
2173 mChooserActivity = chooserActivity;
Adam Powell24428412015-04-01 17:19:56 -07002174 mOriginalTarget = dri;
2175 }
2176
2177 @Override
2178 public void onServiceConnected(ComponentName name, IBinder service) {
2179 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07002180 synchronized (mLock) {
2181 if (mChooserActivity == null) {
2182 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
2183 return;
2184 }
2185
2186 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
2187 try {
2188 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
2189 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
2190 } catch (RemoteException e) {
2191 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
2192 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07002193 mChooserActivity.mServiceConnections.remove(this);
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04002194 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07002195 }
Adam Powell24428412015-04-01 17:19:56 -07002196 }
2197 }
2198
2199 @Override
2200 public void onServiceDisconnected(ComponentName name) {
2201 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
Adam Powell9761ab22015-09-08 17:01:49 -07002202 synchronized (mLock) {
2203 if (mChooserActivity == null) {
2204 Log.e(TAG,
2205 "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
2206 return;
2207 }
2208
2209 mChooserActivity.unbindService(this);
Adam Powell9761ab22015-09-08 17:01:49 -07002210 mChooserActivity.mServiceConnections.remove(this);
2211 if (mChooserActivity.mServiceConnections.isEmpty()) {
2212 mChooserActivity.mChooserHandler.removeMessages(
2213 CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
2214 mChooserActivity.sendVoiceChoicesIfNeeded();
2215 }
2216 mConnectedComponent = null;
Dan Sandlerfcd7fae2017-09-25 17:40:04 -04002217 destroy();
Adam Powell9761ab22015-09-08 17:01:49 -07002218 }
2219 }
2220
2221 public void destroy() {
2222 synchronized (mLock) {
2223 mChooserActivity = null;
Adam Powell52c39212016-04-07 15:14:18 -07002224 mOriginalTarget = null;
Adam Powell4c470d62015-06-19 17:46:17 -07002225 }
Adam Powell24428412015-04-01 17:19:56 -07002226 }
2227
2228 @Override
2229 public String toString() {
Adam Powell9761ab22015-09-08 17:01:49 -07002230 return "ChooserTargetServiceConnection{service="
2231 + mConnectedComponent + ", activity="
Adam Powell52c39212016-04-07 15:14:18 -07002232 + (mOriginalTarget != null
2233 ? mOriginalTarget.getResolveInfo().activityInfo.toString()
2234 : "<connection destroyed>") + "}";
Adam Powell24428412015-04-01 17:19:56 -07002235 }
2236 }
2237
2238 static class ServiceResultInfo {
2239 public final DisplayResolveInfo originalTarget;
2240 public final List<ChooserTarget> resultTargets;
2241 public final ChooserTargetServiceConnection connection;
2242
2243 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
2244 ChooserTargetServiceConnection c) {
2245 originalTarget = ot;
2246 resultTargets = rt;
2247 connection = c;
2248 }
2249 }
Adam Powell2ed547e2015-04-29 18:45:04 -07002250
2251 static class RefinementResultReceiver extends ResultReceiver {
2252 private ChooserActivity mChooserActivity;
2253 private TargetInfo mSelectedTarget;
2254
2255 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
2256 Handler handler) {
2257 super(handler);
2258 mChooserActivity = host;
2259 mSelectedTarget = target;
2260 }
2261
2262 @Override
2263 protected void onReceiveResult(int resultCode, Bundle resultData) {
2264 if (mChooserActivity == null) {
2265 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
2266 return;
2267 }
2268 if (resultData == null) {
2269 Log.e(TAG, "RefinementResultReceiver received null resultData");
2270 return;
2271 }
2272
2273 switch (resultCode) {
2274 case RESULT_CANCELED:
2275 mChooserActivity.onRefinementCanceled();
2276 break;
2277 case RESULT_OK:
2278 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
2279 if (intentParcelable instanceof Intent) {
2280 mChooserActivity.onRefinementResult(mSelectedTarget,
2281 (Intent) intentParcelable);
2282 } else {
2283 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
2284 + " in resultData with key Intent.EXTRA_INTENT");
2285 }
2286 break;
2287 default:
2288 Log.w(TAG, "Unknown result code " + resultCode
2289 + " sent to RefinementResultReceiver");
2290 break;
2291 }
2292 }
2293
2294 public void destroy() {
2295 mChooserActivity = null;
2296 mSelectedTarget = null;
2297 }
2298 }
Adam Powell63b31692015-09-28 10:45:00 -07002299
2300 class OffsetDataSetObserver extends DataSetObserver {
2301 private final AbsListView mListView;
2302 private int mCachedViewType = -1;
2303 private View mCachedView;
2304
2305 public OffsetDataSetObserver(AbsListView listView) {
2306 mListView = listView;
2307 }
2308
2309 @Override
2310 public void onChanged() {
2311 if (mResolverDrawerLayout == null) {
2312 return;
2313 }
2314
2315 final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount();
2316 int offset = 0;
Matt Pietal26038402019-01-08 07:29:34 -05002317 for (int i = 0; i < chooserTargetRows; i++) {
Adam Powell63b31692015-09-28 10:45:00 -07002318 final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i;
2319 final int vt = mChooserRowAdapter.getItemViewType(pos);
2320 if (vt != mCachedViewType) {
2321 mCachedView = null;
2322 }
2323 final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView);
2324 int height = ((RowViewHolder) (v.getTag())).measuredRowHeight;
2325
Dan Sandler62aad002018-05-23 02:13:51 -04002326 offset += (int) (height);
Adam Powell63b31692015-09-28 10:45:00 -07002327
2328 if (vt >= 0) {
2329 mCachedViewType = vt;
2330 mCachedView = v;
2331 } else {
2332 mCachedViewType = -1;
2333 }
2334 }
2335
2336 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2337 }
2338 }
Matt Pietal26038402019-01-08 07:29:34 -05002339
2340
2341 /**
2342 * Used internally to round image corners while obeying view padding.
2343 */
2344 public static class RoundedRectImageView extends ImageView {
2345 private int mRadius = 0;
2346 private Path mPath = new Path();
Matt Pietal0ea391b2019-01-30 10:44:15 -05002347 private Paint mOverlayPaint = new Paint(0);
2348 private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
2349 private String mExtraImageCount = null;
Matt Pietal26038402019-01-08 07:29:34 -05002350
2351 public RoundedRectImageView(Context context) {
2352 super(context);
2353 }
2354
2355 public RoundedRectImageView(Context context, AttributeSet attrs) {
2356 this(context, attrs, 0);
2357 }
2358
2359 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
2360 this(context, attrs, defStyleAttr, 0);
2361 }
2362
2363 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
2364 int defStyleRes) {
2365 super(context, attrs, defStyleAttr, defStyleRes);
2366 mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
Matt Pietal0ea391b2019-01-30 10:44:15 -05002367
2368 mOverlayPaint.setColor(0x99000000);
2369 mOverlayPaint.setStyle(Paint.Style.FILL);
2370
2371 mTextPaint.setColor(Color.WHITE);
2372 mTextPaint.setTextSize(context.getResources()
2373 .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
2374 mTextPaint.setTextAlign(Paint.Align.CENTER);
Matt Pietal26038402019-01-08 07:29:34 -05002375 }
2376
2377 private void updatePath(int width, int height) {
2378 mPath.reset();
2379
Matt Pietal1fa7d802019-01-30 10:44:15 -05002380 int imageWidth = width - getPaddingRight();
2381 int imageHeight = height - getPaddingBottom();
Matt Pietal26038402019-01-08 07:29:34 -05002382 mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
2383 mRadius, Path.Direction.CW);
2384 }
2385
2386 /**
2387 * Sets the corner radius on all corners
2388 *
2389 * param radius 0 for no radius, &gt; 0 for a visible corner radius
2390 */
2391 public void setRadius(int radius) {
2392 mRadius = radius;
2393 updatePath(getWidth(), getHeight());
2394 }
2395
Matt Pietal0ea391b2019-01-30 10:44:15 -05002396 /**
2397 * Display an overlay with extra image count on 3rd image
2398 */
2399 public void setExtraImageCount(int count) {
2400 if (count > 0) {
2401 this.mExtraImageCount = "+" + count;
2402 } else {
2403 this.mExtraImageCount = null;
2404 }
2405 }
2406
Matt Pietal26038402019-01-08 07:29:34 -05002407 @Override
2408 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
2409 super.onSizeChanged(width, height, oldWidth, oldHeight);
2410 updatePath(width, height);
2411 }
2412
Matt Pietal0ea391b2019-01-30 10:44:15 -05002413
Matt Pietal26038402019-01-08 07:29:34 -05002414 @Override
2415 protected void onDraw(Canvas canvas) {
2416 if (mRadius != 0) {
2417 canvas.clipPath(mPath);
2418 }
2419
2420 super.onDraw(canvas);
Matt Pietal0ea391b2019-01-30 10:44:15 -05002421
2422 if (mExtraImageCount != null) {
2423 canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mOverlayPaint);
2424
2425 int xPos = canvas.getWidth() / 2;
2426 int yPos = (int) ((canvas.getHeight() / 2.0f)
2427 - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
2428
2429 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
2430 }
Matt Pietal26038402019-01-08 07:29:34 -05002431 }
2432 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002433}