| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2016 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.chrome.browser.ntp; |
| 6 |
| 7 import android.support.annotation.IntDef; |
| 8 import android.view.ContextMenu; |
| 9 import android.view.Menu; |
| 10 import android.view.MenuItem; |
| 11 import android.view.MenuItem.OnMenuItemClickListener; |
| 12 import android.view.View; |
| 13 |
| 14 import org.chromium.base.Callback; |
| 15 import org.chromium.chrome.R; |
| 16 import org.chromium.chrome.browser.ChromeActivity; |
| 17 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager; |
| 18 import org.chromium.chrome.browser.ntp.snippets.SnippetsConfig; |
| 19 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; |
| 20 import org.chromium.ui.mojom.WindowOpenDisposition; |
| 21 |
| 22 import java.lang.annotation.Retention; |
| 23 import java.lang.annotation.RetentionPolicy; |
| 24 import java.util.Set; |
| 25 |
| 26 /** |
| 27 * Takes care of creating, closing a context menu and triaging the item clicks. |
| 28 */ |
| 29 public class ContextMenuManager { |
| 30 @IntDef({ID_OPEN_IN_NEW_WINDOW, ID_OPEN_IN_NEW_TAB, ID_OPEN_IN_INCOGNITO_TAB
, ID_REMOVE, |
| 31 ID_SAVE_FOR_OFFLINE}) |
| 32 @Retention(RetentionPolicy.SOURCE) |
| 33 public @interface ContextMenuItemId {} |
| 34 public static final int ID_OPEN_IN_NEW_WINDOW = 0; |
| 35 public static final int ID_OPEN_IN_NEW_TAB = 1; |
| 36 public static final int ID_OPEN_IN_INCOGNITO_TAB = 2; |
| 37 public static final int ID_REMOVE = 3; |
| 38 public static final int ID_SAVE_FOR_OFFLINE = 4; |
| 39 |
| 40 private final NewTabPageManager mManager; |
| 41 private final ChromeActivity mActivity; |
| 42 private final TouchDisableableView mOuterView; |
| 43 |
| 44 /** Defines callback to configure the context menu and respond to user inter
action. */ |
| 45 public interface Delegate { |
| 46 /** Opens the current item the way specified by {@code windowDisposition
}. */ |
| 47 void openItem(int windowDisposition); |
| 48 |
| 49 /** Remove the current item. */ |
| 50 void removeItem(); |
| 51 |
| 52 /** @return whether the current item can be saved offline. */ |
| 53 String getUrl(); |
| 54 |
| 55 /** @return IDs of the menu items that are supported. */ |
| 56 Set<Integer> getSupportedMenuItems(); |
| 57 } |
| 58 |
| 59 /** Interface for a view that can be set to stop responding to touches. */ |
| 60 public interface TouchDisableableView { void setTouchEnabled(boolean enabled
); } |
| 61 |
| 62 public ContextMenuManager(NewTabPageManager newTabPageManager, ChromeActivit
y activity, |
| 63 TouchDisableableView outerView) { |
| 64 mManager = newTabPageManager; |
| 65 mActivity = activity; |
| 66 mOuterView = outerView; |
| 67 } |
| 68 |
| 69 /** |
| 70 * Populates the context menu. |
| 71 * @param menu The menu to populate. |
| 72 * @param associatedView The view that requested a context menu. |
| 73 * @param delegate Delegate that defines the configuration of the menu and w
hat to do when items |
| 74 * are tapped. |
| 75 */ |
| 76 public void createContextMenu(ContextMenu menu, View associatedView, Delegat
e delegate) { |
| 77 OnMenuItemClickListener listener = new ItemClickListener(delegate); |
| 78 Set<Integer> supportedItems = delegate.getSupportedMenuItems(); |
| 79 boolean hasItems = false; |
| 80 |
| 81 if (shouldShowItem(ID_OPEN_IN_NEW_WINDOW, delegate, supportedItems)) { |
| 82 hasItems = true; |
| 83 addContextMenuItem(menu, ID_OPEN_IN_NEW_WINDOW, |
| 84 R.string.contextmenu_open_in_other_window, listener); |
| 85 } |
| 86 |
| 87 if (shouldShowItem(ID_OPEN_IN_NEW_TAB, delegate, supportedItems)) { |
| 88 hasItems = true; |
| 89 addContextMenuItem( |
| 90 menu, ID_OPEN_IN_NEW_TAB, R.string.contextmenu_open_in_new_t
ab, listener); |
| 91 } |
| 92 |
| 93 if (shouldShowItem(ID_OPEN_IN_INCOGNITO_TAB, delegate, supportedItems))
{ |
| 94 hasItems = true; |
| 95 addContextMenuItem(menu, ID_OPEN_IN_INCOGNITO_TAB, |
| 96 R.string.contextmenu_open_in_incognito_tab, listener); |
| 97 } |
| 98 |
| 99 if (shouldShowItem(ID_SAVE_FOR_OFFLINE, delegate, supportedItems)) { |
| 100 hasItems = true; |
| 101 addContextMenuItem(menu, ID_SAVE_FOR_OFFLINE, R.string.contextmenu_s
ave_link, listener); |
| 102 } |
| 103 |
| 104 if (shouldShowItem(ID_REMOVE, delegate, supportedItems)) { |
| 105 hasItems = true; |
| 106 addContextMenuItem(menu, ID_REMOVE, R.string.remove, listener); |
| 107 } |
| 108 |
| 109 // No item added. We won't show the menu, so we can skip the rest. |
| 110 if (!hasItems) return; |
| 111 |
| 112 associatedView.addOnAttachStateChangeListener(new View.OnAttachStateChan
geListener() { |
| 113 @Override |
| 114 public void onViewAttachedToWindow(View view) {} |
| 115 |
| 116 @Override |
| 117 public void onViewDetachedFromWindow(View view) { |
| 118 closeContextMenu(); |
| 119 view.removeOnAttachStateChangeListener(this); |
| 120 } |
| 121 }); |
| 122 |
| 123 // Disable touch events on the outer view while the context menu is open
. This is to |
| 124 // prevent the user long pressing to get the context menu then on the sa
me press scrolling |
| 125 // or swiping to dismiss an item (eg. https://crbug.com/638854, https://
crbug.com/638555, |
| 126 // https://crbug.com/636296) |
| 127 mOuterView.setTouchEnabled(false); |
| 128 |
| 129 mActivity.addContextMenuCloseCallback(new Callback<Menu>() { |
| 130 @Override |
| 131 public void onResult(Menu result) { |
| 132 mOuterView.setTouchEnabled(true); |
| 133 mActivity.removeContextMenuCloseCallback(this); |
| 134 } |
| 135 }); |
| 136 } |
| 137 |
| 138 /** Closes the context menu, if open. */ |
| 139 public void closeContextMenu() { |
| 140 mActivity.closeContextMenu(); |
| 141 } |
| 142 |
| 143 private boolean shouldShowItem( |
| 144 @ContextMenuItemId int itemId, Delegate delegate, Set<Integer> suppo
rtedItems) { |
| 145 if (!supportedItems.contains(itemId)) return false; |
| 146 |
| 147 switch (itemId) { |
| 148 case ID_OPEN_IN_NEW_WINDOW: |
| 149 return mManager.isOpenInNewWindowEnabled(); |
| 150 case ID_OPEN_IN_NEW_TAB: |
| 151 return true; |
| 152 case ID_OPEN_IN_INCOGNITO_TAB: |
| 153 return mManager.isOpenInIncognitoEnabled(); |
| 154 case ID_SAVE_FOR_OFFLINE: { |
| 155 if (!SnippetsConfig.isSaveToOfflineEnabled()) return false; |
| 156 String itemUrl = delegate.getUrl(); |
| 157 return itemUrl != null && OfflinePageBridge.canSavePage(itemUrl)
; |
| 158 } |
| 159 case ID_REMOVE: |
| 160 return true; |
| 161 |
| 162 default: |
| 163 assert false; |
| 164 return false; |
| 165 } |
| 166 } |
| 167 |
| 168 /** |
| 169 * Convenience method to reduce multi-line function call to single line. |
| 170 */ |
| 171 private void addContextMenuItem(ContextMenu menu, @ContextMenuItemId int id,
int resourceId, |
| 172 OnMenuItemClickListener listener) { |
| 173 menu.add(Menu.NONE, id, Menu.NONE, resourceId).setOnMenuItemClickListene
r(listener); |
| 174 } |
| 175 |
| 176 private static class ItemClickListener implements OnMenuItemClickListener { |
| 177 private final Delegate mDelegate; |
| 178 |
| 179 ItemClickListener(Delegate delegate) { |
| 180 mDelegate = delegate; |
| 181 } |
| 182 |
| 183 @Override |
| 184 public boolean onMenuItemClick(MenuItem item) { |
| 185 switch (item.getItemId()) { |
| 186 case ID_OPEN_IN_NEW_WINDOW: |
| 187 mDelegate.openItem(WindowOpenDisposition.NEW_WINDOW); |
| 188 return true; |
| 189 case ID_OPEN_IN_NEW_TAB: |
| 190 mDelegate.openItem(WindowOpenDisposition.NEW_FOREGROUND_TAB)
; |
| 191 return true; |
| 192 case ID_OPEN_IN_INCOGNITO_TAB: |
| 193 mDelegate.openItem(WindowOpenDisposition.OFF_THE_RECORD); |
| 194 return true; |
| 195 case ID_SAVE_FOR_OFFLINE: |
| 196 mDelegate.openItem(WindowOpenDisposition.SAVE_TO_DISK); |
| 197 return true; |
| 198 case ID_REMOVE: |
| 199 mDelegate.removeItem(); |
| 200 return true; |
| 201 default: |
| 202 return false; |
| 203 } |
| 204 } |
| 205 } |
| 206 } |
| OLD | NEW |