1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.content.browser.selection;
6 
7 import android.annotation.TargetApi;
8 import android.app.PendingIntent;
9 import android.app.RemoteAction;
10 import android.content.Context;
11 import android.os.Build;
12 import android.text.TextUtils;
13 import android.view.Menu;
14 import android.view.MenuItem;
15 import android.view.View;
16 import android.view.View.OnClickListener;
17 import android.view.textclassifier.TextClassification;
18 
19 import org.chromium.base.Log;
20 import org.chromium.base.annotations.VerifiesOnP;
21 
22 import java.util.HashMap;
23 import java.util.Map;
24 
25 // TODO(ctzsm): Add unit tests for this class once this is upstreamed.
26 /**
27  * Implements AdditionalMenuItemProvider interface.
28  * We prevent inlinings since this uses a number of new Android APIs which would create verification
29  * errors (on older Android versions) which would require a slow re-verification at runtime.
30  */
31 @VerifiesOnP
32 @TargetApi(Build.VERSION_CODES.P)
33 public class AdditionalMenuItemProviderImpl implements AdditionalMenuItemProvider {
34     private static final String TAG = "MenuItemProvider";
35     // We want the secondary assist actions to come after the default actions but before the text
36     // processing actions. This constant needs to be greater than all of the default action orders
37     // but small enough so that all of the secondary items have order less than
38     // MENU_ITEM_ORDER_TEXT_PROCESS_START in SelectionPopupControllerImpl.
39     private static final int MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50;
40 
41     // Record MenuItem OnClickListener pair we added to menu.
42     private final Map<MenuItem, OnClickListener> mAssistClickHandlers = new HashMap<>();
43 
44     @Override
addMenuItems(Context context, Menu menu, TextClassification classification)45     public void addMenuItems(Context context, Menu menu, TextClassification classification) {
46         if (menu == null || classification == null) return;
47 
48         final int count = classification.getActions().size();
49 
50         // Fallback to new API to set icon on P.
51         if (count > 0) {
52             RemoteAction primaryAction = classification.getActions().get(0);
53 
54             MenuItem item = menu.findItem(android.R.id.textAssist);
55             if (primaryAction.shouldShowIcon()) {
56                 item.setIcon(primaryAction.getIcon().loadDrawable(context));
57             } else {
58                 item.setIcon(null);
59             }
60         }
61 
62         // First action is reserved for primary action.
63         for (int i = 1; i < count; ++i) {
64             RemoteAction action = classification.getActions().get(i);
65             final OnClickListener listener =
66                     getSupportedOnClickListener(action.getTitle(), action.getActionIntent());
67             if (listener == null) continue;
68 
69             // We have to use android.R.id.textAssist as group id to make framework show icons for
70             // these menu items.
71             MenuItem item = menu.add(android.R.id.textAssist, Menu.NONE,
72                     MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i, action.getTitle());
73             item.setContentDescription(action.getContentDescription());
74             if (action.shouldShowIcon()) {
75                 item.setIcon(action.getIcon().loadDrawable(context));
76             }
77             // Set this flag to SHOW_AS_ACTION_IF_ROOM to match text processing menu items. So
78             // Android could put them to the same level and then consider their actual order.
79             item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
80             mAssistClickHandlers.put(item, listener);
81         }
82     }
83 
84     @Override
clearMenuItemListeners()85     public void clearMenuItemListeners() {
86         mAssistClickHandlers.clear();
87     }
88 
89     @Override
performAction(MenuItem item, View view)90     public void performAction(MenuItem item, View view) {
91         OnClickListener listener = mAssistClickHandlers.get(item);
92         if (listener == null) return;
93         listener.onClick(view);
94     }
95 
getSupportedOnClickListener( CharSequence title, PendingIntent pendingIntent)96     private static OnClickListener getSupportedOnClickListener(
97             CharSequence title, PendingIntent pendingIntent) {
98         if (TextUtils.isEmpty(title) || pendingIntent == null) return null;
99 
100         return v -> {
101             try {
102                 pendingIntent.send();
103             } catch (PendingIntent.CanceledException e) {
104                 Log.e(TAG, "Error creating OnClickListener from PendingIntent", e);
105             }
106         };
107     }
108 }
109