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