blob: 74ae29117b590ba9a846c35cb6d587657d4a92db [file] [log] [blame]
arangelovb0802dc2019-10-18 18:03:44 +01001/*
2 * Copyright (C) 2019 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
17package com.android.internal.app;
18
19import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
20import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
21
22import android.app.ActivityManager;
arangelov5fc9e7d2020-01-07 17:59:14 +000023import android.app.prediction.AppPredictor;
arangelovb0802dc2019-10-18 18:03:44 +010024import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.ActivityInfo;
28import android.content.pm.LabeledIntent;
29import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
arangelov5fc9e7d2020-01-07 17:59:14 +000031import android.content.pm.ShortcutInfo;
arangelovb0802dc2019-10-18 18:03:44 +010032import android.os.AsyncTask;
arangelov5fc9e7d2020-01-07 17:59:14 +000033import android.os.UserHandle;
arangelovb0802dc2019-10-18 18:03:44 +010034import android.os.UserManager;
35import android.service.chooser.ChooserTarget;
36import android.util.Log;
37import android.view.View;
38import android.view.ViewGroup;
39
40import com.android.internal.R;
41import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
42import com.android.internal.app.chooser.ChooserTargetInfo;
43import com.android.internal.app.chooser.DisplayResolveInfo;
Alison Cichowlas19ee2922019-12-16 19:43:12 -050044import com.android.internal.app.chooser.MultiDisplayResolveInfo;
arangelovb0802dc2019-10-18 18:03:44 +010045import com.android.internal.app.chooser.SelectableTargetInfo;
46import com.android.internal.app.chooser.TargetInfo;
47
48import java.util.ArrayList;
49import java.util.Collections;
Alison Cichowlas19ee2922019-12-16 19:43:12 -050050import java.util.HashMap;
arangelovb0802dc2019-10-18 18:03:44 +010051import java.util.List;
Alison Cichowlas19ee2922019-12-16 19:43:12 -050052import java.util.Map;
arangelovb0802dc2019-10-18 18:03:44 +010053
54public class ChooserListAdapter extends ResolverListAdapter {
55 private static final String TAG = "ChooserListAdapter";
56 private static final boolean DEBUG = false;
57
Alison Cichowlas19ee2922019-12-16 19:43:12 -050058 private boolean mEnableStackedApps = true;
59
Zhen Zhangbde7b462019-11-11 11:49:33 -080060 public static final int NO_POSITION = -1;
arangelovb0802dc2019-10-18 18:03:44 +010061 public static final int TARGET_BAD = -1;
62 public static final int TARGET_CALLER = 0;
63 public static final int TARGET_SERVICE = 1;
64 public static final int TARGET_STANDARD = 2;
65 public static final int TARGET_STANDARD_AZ = 3;
66
67 private static final int MAX_SUGGESTED_APP_TARGETS = 4;
68 private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
69
70 static final int MAX_SERVICE_TARGETS = 8;
71
72 /** {@link #getBaseScore} */
73 public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
74 /** {@link #getBaseScore} */
75 public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
76
77 private final int mMaxShortcutTargetsPerApp;
78 private final ChooserListCommunicator mChooserListCommunicator;
79 private final SelectableTargetInfo.SelectableTargetInfoCommunicator
80 mSelectableTargetInfoComunicator;
81
82 private int mNumShortcutResults = 0;
83
84 // Reserve spots for incoming direct share targets by adding placeholders
85 private ChooserTargetInfo
86 mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo();
87 private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
88 private final List<TargetInfo> mCallerTargets = new ArrayList<>();
89
90 private final ChooserActivity.BaseChooserTargetComparator mBaseTargetComparator =
91 new ChooserActivity.BaseChooserTargetComparator();
92 private boolean mListViewDataChanged = false;
93
94 // Sorted list of DisplayResolveInfos for the alphabetical app section.
95 private List<DisplayResolveInfo> mSortedList = new ArrayList<>();
arangelov5fc9e7d2020-01-07 17:59:14 +000096 private AppPredictor mAppPredictor;
97 private AppPredictor.Callback mAppPredictorCallback;
arangelovb0802dc2019-10-18 18:03:44 +010098
99 public ChooserListAdapter(Context context, List<Intent> payloadIntents,
100 Intent[] initialIntents, List<ResolveInfo> rList,
101 boolean filterLastUsed, ResolverListController resolverListController,
102 boolean useLayoutForBrowsables,
103 ChooserListCommunicator chooserListCommunicator,
104 SelectableTargetInfo.SelectableTargetInfoCommunicator selectableTargetInfoComunicator) {
105 // Don't send the initial intents through the shared ResolverActivity path,
106 // we want to separate them into a different section.
107 super(context, payloadIntents, null, rList, filterLastUsed,
108 resolverListController, useLayoutForBrowsables,
Paul McLean07425c82019-10-18 12:00:11 -0600109 chooserListCommunicator, false);
arangelovb0802dc2019-10-18 18:03:44 +0100110
111 createPlaceHolders();
112 mMaxShortcutTargetsPerApp =
113 context.getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp);
114 mChooserListCommunicator = chooserListCommunicator;
115 mSelectableTargetInfoComunicator = selectableTargetInfoComunicator;
116
117 if (initialIntents != null) {
118 final PackageManager pm = context.getPackageManager();
119 for (int i = 0; i < initialIntents.length; i++) {
120 final Intent ii = initialIntents[i];
121 if (ii == null) {
122 continue;
123 }
124
125 // We reimplement Intent#resolveActivityInfo here because if we have an
126 // implicit intent, we want the ResolveInfo returned by PackageManager
127 // instead of one we reconstruct ourselves. The ResolveInfo returned might
128 // have extra metadata and resolvePackageName set and we want to respect that.
129 ResolveInfo ri = null;
130 ActivityInfo ai = null;
131 final ComponentName cn = ii.getComponent();
132 if (cn != null) {
133 try {
134 ai = pm.getActivityInfo(ii.getComponent(), 0);
135 ri = new ResolveInfo();
136 ri.activityInfo = ai;
137 } catch (PackageManager.NameNotFoundException ignored) {
138 // ai will == null below
139 }
140 }
141 if (ai == null) {
142 ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
143 ai = ri != null ? ri.activityInfo : null;
144 }
145 if (ai == null) {
146 Log.w(TAG, "No activity found for " + ii);
147 continue;
148 }
149 UserManager userManager =
150 (UserManager) context.getSystemService(Context.USER_SERVICE);
151 if (ii instanceof LabeledIntent) {
152 LabeledIntent li = (LabeledIntent) ii;
153 ri.resolvePackageName = li.getSourcePackage();
154 ri.labelRes = li.getLabelResource();
155 ri.nonLocalizedLabel = li.getNonLocalizedLabel();
156 ri.icon = li.getIconResource();
157 ri.iconResourceId = ri.icon;
158 }
159 if (userManager.isManagedProfile()) {
160 ri.noResourceId = true;
161 ri.icon = 0;
162 }
163 mCallerTargets.add(new DisplayResolveInfo(ii, ri, ii, makePresentationGetter(ri)));
164 }
165 }
166 }
167
arangelov5fc9e7d2020-01-07 17:59:14 +0000168 AppPredictor getAppPredictor() {
169 return mAppPredictor;
170 }
171
arangelovb0802dc2019-10-18 18:03:44 +0100172 @Override
173 public void handlePackagesChanged() {
174 if (DEBUG) {
175 Log.d(TAG, "clearing queryTargets on package change");
176 }
177 createPlaceHolders();
arangelov7981b122020-01-16 10:58:27 +0000178 mChooserListCommunicator.onHandlePackagesChanged(this);
arangelovb0802dc2019-10-18 18:03:44 +0100179
180 }
181
182 @Override
183 public void notifyDataSetChanged() {
184 if (!mListViewDataChanged) {
arangelov5fc9e7d2020-01-07 17:59:14 +0000185 mChooserListCommunicator.sendListViewUpdateMessage(getUserHandle());
arangelovb0802dc2019-10-18 18:03:44 +0100186 mListViewDataChanged = true;
187 }
188 }
189
190 void refreshListView() {
191 if (mListViewDataChanged) {
192 super.notifyDataSetChanged();
193 }
194 mListViewDataChanged = false;
195 }
196
197
198 private void createPlaceHolders() {
199 mNumShortcutResults = 0;
200 mServiceTargets.clear();
201 for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
202 mServiceTargets.add(mPlaceHolderTargetInfo);
203 }
204 }
205
206 @Override
Zhen Zhangbde7b462019-11-11 11:49:33 -0800207 View onCreateView(ViewGroup parent) {
arangelovb0802dc2019-10-18 18:03:44 +0100208 return mInflater.inflate(
209 com.android.internal.R.layout.resolve_grid_item, parent, false);
210 }
211
212 @Override
213 protected void onBindView(View view, TargetInfo info) {
214 super.onBindView(view, info);
215
216 // If target is loading, show a special placeholder shape in the label, make unclickable
217 final ViewHolder holder = (ViewHolder) view.getTag();
218 if (info instanceof ChooserActivity.PlaceHolderTargetInfo) {
219 final int maxWidth = mContext.getResources().getDimensionPixelSize(
220 R.dimen.chooser_direct_share_label_placeholder_max_width);
221 holder.text.setMaxWidth(maxWidth);
222 holder.text.setBackground(mContext.getResources().getDrawable(
223 R.drawable.chooser_direct_share_label_placeholder, mContext.getTheme()));
224 // Prevent rippling by removing background containing ripple
225 holder.itemView.setBackground(null);
226 } else {
227 holder.text.setMaxWidth(Integer.MAX_VALUE);
228 holder.text.setBackground(null);
229 holder.itemView.setBackground(holder.defaultItemViewBackground);
230 }
231 }
232
233 void updateAlphabeticalList() {
234 mSortedList.clear();
Alison Cichowlas19ee2922019-12-16 19:43:12 -0500235 if (mEnableStackedApps) {
236 // Consolidate multiple targets from same app.
237 Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
238 for (DisplayResolveInfo info : mDisplayList) {
239 String packageName = info.getResolvedComponentName().getPackageName();
240 if (consolidated.get(packageName) != null) {
241 // create consolidated target
242 MultiDisplayResolveInfo multiDisplayResolveInfo =
243 new MultiDisplayResolveInfo(packageName, info);
244 multiDisplayResolveInfo.addTarget(consolidated.get(packageName));
245 consolidated.put(packageName, multiDisplayResolveInfo);
246 } else {
247 consolidated.put(packageName, info);
248 }
249 }
250 mSortedList.addAll(consolidated.values());
251 } else {
252 mSortedList.addAll(mDisplayList);
253 }
arangelovb0802dc2019-10-18 18:03:44 +0100254 Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext));
255 }
256
257 @Override
arangelovb0802dc2019-10-18 18:03:44 +0100258 public int getCount() {
259 return getRankedTargetCount() + getAlphaTargetCount()
260 + getSelectableServiceTargetCount() + getCallerTargetCount();
261 }
262
263 @Override
264 public int getUnfilteredCount() {
265 int appTargets = super.getUnfilteredCount();
266 if (appTargets > mChooserListCommunicator.getMaxRankedTargets()) {
267 appTargets = appTargets + mChooserListCommunicator.getMaxRankedTargets();
268 }
269 return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
270 }
271
272
273 public int getCallerTargetCount() {
274 return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS);
275 }
276
277 /**
278 * Filter out placeholders and non-selectable service targets
279 */
280 public int getSelectableServiceTargetCount() {
281 int count = 0;
282 for (ChooserTargetInfo info : mServiceTargets) {
283 if (info instanceof SelectableTargetInfo) {
284 count++;
285 }
286 }
287 return count;
288 }
289
290 public int getServiceTargetCount() {
291 if (mChooserListCommunicator.isSendAction(mChooserListCommunicator.getTargetIntent())
292 && !ActivityManager.isLowRamDeviceStatic()) {
293 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
294 }
295
296 return 0;
297 }
298
299 int getAlphaTargetCount() {
Alison Cichowlas19ee2922019-12-16 19:43:12 -0500300 int standardCount = mSortedList.size();
arangelovb0802dc2019-10-18 18:03:44 +0100301 return standardCount > mChooserListCommunicator.getMaxRankedTargets() ? standardCount : 0;
302 }
303
304 int getRankedTargetCount() {
305 int spacesAvailable =
306 mChooserListCommunicator.getMaxRankedTargets() - getCallerTargetCount();
307 return Math.min(spacesAvailable, super.getCount());
308 }
309
310 public int getPositionTargetType(int position) {
311 int offset = 0;
312
313 final int serviceTargetCount = getServiceTargetCount();
314 if (position < serviceTargetCount) {
315 return TARGET_SERVICE;
316 }
317 offset += serviceTargetCount;
318
319 final int callerTargetCount = getCallerTargetCount();
320 if (position - offset < callerTargetCount) {
321 return TARGET_CALLER;
322 }
323 offset += callerTargetCount;
324
325 final int rankedTargetCount = getRankedTargetCount();
326 if (position - offset < rankedTargetCount) {
327 return TARGET_STANDARD;
328 }
329 offset += rankedTargetCount;
330
331 final int standardTargetCount = getAlphaTargetCount();
332 if (position - offset < standardTargetCount) {
333 return TARGET_STANDARD_AZ;
334 }
335
336 return TARGET_BAD;
337 }
338
339 @Override
340 public TargetInfo getItem(int position) {
341 return targetInfoForPosition(position, true);
342 }
343
344
345 /**
346 * Find target info for a given position.
347 * Since ChooserActivity displays several sections of content, determine which
348 * section provides this item.
349 */
350 @Override
351 public TargetInfo targetInfoForPosition(int position, boolean filtered) {
Zhen Zhangbde7b462019-11-11 11:49:33 -0800352 if (position == NO_POSITION) {
353 return null;
354 }
355
arangelovb0802dc2019-10-18 18:03:44 +0100356 int offset = 0;
357
358 // Direct share targets
359 final int serviceTargetCount = filtered ? getServiceTargetCount() :
360 getSelectableServiceTargetCount();
361 if (position < serviceTargetCount) {
362 return mServiceTargets.get(position);
363 }
364 offset += serviceTargetCount;
365
366 // Targets provided by calling app
367 final int callerTargetCount = getCallerTargetCount();
368 if (position - offset < callerTargetCount) {
369 return mCallerTargets.get(position - offset);
370 }
371 offset += callerTargetCount;
372
373 // Ranked standard app targets
374 final int rankedTargetCount = getRankedTargetCount();
375 if (position - offset < rankedTargetCount) {
376 return filtered ? super.getItem(position - offset)
377 : getDisplayResolveInfo(position - offset);
378 }
379 offset += rankedTargetCount;
380
381 // Alphabetical complete app target list.
382 if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) {
383 return mSortedList.get(position - offset);
384 }
385
386 return null;
387 }
388
389
390 /**
391 * Evaluate targets for inclusion in the direct share area. May not be included
392 * if score is too low.
393 */
394 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
arangelov5fc9e7d2020-01-07 17:59:14 +0000395 @ChooserActivity.ShareTargetType int targetType,
396 Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos) {
arangelovb0802dc2019-10-18 18:03:44 +0100397 if (DEBUG) {
398 Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
399 + " targets");
400 }
401
402 if (targets.size() == 0) {
403 return;
404 }
405
406 final float baseScore = getBaseScore(origTarget, targetType);
407 Collections.sort(targets, mBaseTargetComparator);
408
409 final boolean isShortcutResult =
410 (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
411 || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
412 final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
413 : MAX_CHOOSER_TARGETS_PER_APP;
414 float lastScore = 0;
415 boolean shouldNotify = false;
416 for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
417 final ChooserTarget target = targets.get(i);
418 float targetScore = target.getScore();
419 targetScore *= baseScore;
420 if (i > 0 && targetScore >= lastScore) {
421 // Apply a decay so that the top app can't crowd out everything else.
422 // This incents ChooserTargetServices to define what's truly better.
423 targetScore = lastScore * 0.95f;
424 }
arangelov5fc9e7d2020-01-07 17:59:14 +0000425 UserHandle userHandle = getUserHandle();
426 Context contextAsUser = mContext.createContextAsUser(userHandle, 0 /* flags */);
427 boolean isInserted = insertServiceTarget(new SelectableTargetInfo(contextAsUser,
428 origTarget, target, targetScore, mSelectableTargetInfoComunicator,
429 (isShortcutResult ? directShareToShortcutInfos.get(target) : null)));
arangelovb0802dc2019-10-18 18:03:44 +0100430
431 if (isInserted && isShortcutResult) {
432 mNumShortcutResults++;
433 }
434
435 shouldNotify |= isInserted;
436
437 if (DEBUG) {
438 Log.d(TAG, " => " + target.toString() + " score=" + targetScore
439 + " base=" + target.getScore()
440 + " lastScore=" + lastScore
441 + " baseScore=" + baseScore);
442 }
443
444 lastScore = targetScore;
445 }
446
447 if (shouldNotify) {
448 notifyDataSetChanged();
449 }
450 }
451
452 int getNumShortcutResults() {
453 return mNumShortcutResults;
454 }
455
456 /**
457 * Use the scoring system along with artificial boosts to create up to 4 distinct buckets:
458 * <ol>
459 * <li>App-supplied targets
460 * <li>Shortcuts ranked via App Prediction Manager
461 * <li>Shortcuts ranked via legacy heuristics
462 * <li>Legacy direct share targets
463 * </ol>
464 */
465 public float getBaseScore(
466 DisplayResolveInfo target,
467 @ChooserActivity.ShareTargetType int targetType) {
468 if (target == null) {
469 return CALLER_TARGET_SCORE_BOOST;
470 }
471
472 if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
473 return SHORTCUT_TARGET_SCORE_BOOST;
474 }
475
476 float score = super.getScore(target);
477 if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
478 return score * SHORTCUT_TARGET_SCORE_BOOST;
479 }
480
481 return score;
482 }
483
484 /**
485 * Calling this marks service target loading complete, and will attempt to no longer
486 * update the direct share area.
487 */
488 public void completeServiceTargetLoading() {
489 mServiceTargets.removeIf(o -> o instanceof ChooserActivity.PlaceHolderTargetInfo);
490
491 if (mServiceTargets.isEmpty()) {
492 mServiceTargets.add(new ChooserActivity.EmptyTargetInfo());
493 }
494 notifyDataSetChanged();
495 }
496
497 private boolean insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
498 // Avoid inserting any potentially late results
499 if (mServiceTargets.size() == 1
500 && mServiceTargets.get(0) instanceof ChooserActivity.EmptyTargetInfo) {
501 return false;
502 }
503
504 // Check for duplicates and abort if found
505 for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
506 if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
507 return false;
508 }
509 }
510
511 int currentSize = mServiceTargets.size();
512 final float newScore = chooserTargetInfo.getModifiedScore();
513 for (int i = 0; i < Math.min(currentSize, MAX_SERVICE_TARGETS); i++) {
514 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
515 if (serviceTarget == null) {
516 mServiceTargets.set(i, chooserTargetInfo);
517 return true;
518 } else if (newScore > serviceTarget.getModifiedScore()) {
519 mServiceTargets.add(i, chooserTargetInfo);
520 return true;
521 }
522 }
523
524 if (currentSize < MAX_SERVICE_TARGETS) {
525 mServiceTargets.add(chooserTargetInfo);
526 return true;
527 }
528
529 return false;
530 }
531
532 public ChooserTarget getChooserTargetForValue(int value) {
533 return mServiceTargets.get(value).getChooserTarget();
534 }
535
536 /**
537 * Rather than fully sorting the input list, this sorting task will put the top k elements
538 * in the head of input list and fill the tail with other elements in undetermined order.
539 */
540 @Override
541 AsyncTask<List<ResolvedComponentInfo>,
542 Void,
arangelov7981b122020-01-16 10:58:27 +0000543 List<ResolvedComponentInfo>> createSortingTask(boolean doPostProcessing) {
arangelovb0802dc2019-10-18 18:03:44 +0100544 return new AsyncTask<List<ResolvedComponentInfo>,
545 Void,
546 List<ResolvedComponentInfo>>() {
547 @Override
548 protected List<ResolvedComponentInfo> doInBackground(
549 List<ResolvedComponentInfo>... params) {
550 mResolverListController.topK(params[0],
551 mChooserListCommunicator.getMaxRankedTargets());
552 return params[0];
553 }
554 @Override
555 protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
arangelov7981b122020-01-16 10:58:27 +0000556 processSortedList(sortedComponents, doPostProcessing);
557 if (doPostProcessing) {
558 mChooserListCommunicator.updateProfileViewButton();
559 notifyDataSetChanged();
560 }
arangelovb0802dc2019-10-18 18:03:44 +0100561 }
562 };
563 }
564
arangelov5fc9e7d2020-01-07 17:59:14 +0000565 public void setAppPredictor(AppPredictor appPredictor) {
566 mAppPredictor = appPredictor;
567 }
568
569 public void setAppPredictorCallback(AppPredictor.Callback appPredictorCallback) {
570 mAppPredictorCallback = appPredictorCallback;
571 }
572
573 public void destroyAppPredictor() {
574 if (getAppPredictor() != null) {
575 getAppPredictor().unregisterPredictionUpdates(mAppPredictorCallback);
576 getAppPredictor().destroy();
577 }
578 }
579
arangelovb0802dc2019-10-18 18:03:44 +0100580 /**
581 * Necessary methods to communicate between {@link ChooserListAdapter}
582 * and {@link ChooserActivity}.
583 */
584 interface ChooserListCommunicator extends ResolverListCommunicator {
585
586 int getMaxRankedTargets();
587
arangelov5fc9e7d2020-01-07 17:59:14 +0000588 void sendListViewUpdateMessage(UserHandle userHandle);
arangelovb0802dc2019-10-18 18:03:44 +0100589
590 boolean isSendAction(Intent targetIntent);
591 }
592}