/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.talkback.contextmenu;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public abstract class ContextMenu implements Menu {
private static final int ID_CANCEL = -1;
private Context mContext;
private List<ContextMenuItem> mItems;
private MenuItem.OnMenuItemClickListener mListener;
public ContextMenu(Context context) {
mContext = context;
mItems = new ArrayList<>();
}
/**
* Sets the default click listener for menu items. If the default click
* listener returns false for an event, it will be passed to the menu's
* parent menu (if any).
*
* @param listener The default click listener for menu items.
*/
public void setDefaultListener(MenuItem.OnMenuItemClickListener listener) {
mListener = listener;
}
public MenuItem.OnMenuItemClickListener getDefaultListener() {
return mListener;
}
public Context getContext() {
return mContext;
}
@Override
public ContextMenuItem add(int titleRes) {
return add(NONE, NONE, NONE, titleRes);
}
@Override
public ContextMenuItem add(CharSequence title) {
return add(NONE, NONE, NONE, title);
}
@Override
public ContextMenuItem add(int groupId, int itemId, int order, int titleRes) {
CharSequence title = mContext.getText(titleRes);
return add(groupId, itemId, order, title);
}
public void add(ContextMenuItem item) {
mItems.add(item);
}
public abstract ContextMenuItem add(int groupId, int itemId, int order, CharSequence title);
@Override
public SubMenu addSubMenu(int titleRes) {
return addSubMenu(NONE, NONE, NONE, titleRes);
}
@Override
public SubMenu addSubMenu(CharSequence title) {
return addSubMenu(NONE, NONE, NONE, title);
}
@Override
public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
final CharSequence title = mContext.getText(titleRes);
return addSubMenu(groupId, itemId, order, title);
}
public abstract ContextSubMenu addSubMenu(int groupId, int itemId, int order,
CharSequence title);
@Override
public void setQwertyMode(boolean isQwerty) {
//NoOp
}
@Override
public int size() {
return mItems.size();
}
@Override
public void clear() {
mItems.clear();
}
@Override
public void removeItem(int id) {
final MenuItem item = findItem(id);
if (item == null) {
return;
}
mItems.remove(item);
}
@Override
public boolean hasVisibleItems() {
for (MenuItem item : mItems) {
if (item.isVisible()) {
return true;
}
}
return false;
}
@Override
public ContextMenuItem getItem(int index) {
return mItems.get(index);
}
protected int indexOf(ContextMenuItem item) {
return mItems.indexOf(item);
}
@Override
public void removeGroup(int group) {
List<MenuItem> removeItems = new LinkedList<>();
for (MenuItem item : mItems) {
if (item.getGroupId() == group) {
removeItems.add(item);
}
}
mItems.removeAll(removeItems);
}
@Override
public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
for (MenuItem item : mItems) {
if (item.getGroupId() == group) {
item.setCheckable(checkable);
}
}
}
@Override
public void setGroupEnabled(int group, boolean enabled) {
for (MenuItem item : mItems) {
if (item.getGroupId() == group) {
item.setEnabled(enabled);
}
}
}
@Override
public void setGroupVisible(int group, boolean visible) {
for (MenuItem item : mItems) {
if (item.getGroupId() == group) {
item.setVisible(visible);
}
}
}
@Override
public ContextMenuItem findItem(int id) {
if (id == ID_CANCEL) {
return null;
}
for (ContextMenuItem item : mItems) {
if (item.getItemId() == id) {
return item;
}
}
return null;
}
@Override
public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
final ContextMenuItem item = getItemForShortcut(keyCode);
return performMenuItem(item, flags);
}
@Override
public boolean performIdentifierAction(int id, int flags) {
final ContextMenuItem item = findItem(id);
return performMenuItem(item, flags);
}
@Override
public boolean isShortcutKey(int keyCode, KeyEvent event) {
final MenuItem item = getItemForShortcut(keyCode);
return (item != null);
}
/**
* Performs the action associated with a particular {@link RadialMenuItem},
* or {@code null} if the menu was cancelled.
*
* @param item to perform
* @param flags for action
* @return {@code true} if the menu item performs an action
*/
public boolean performMenuItem(ContextMenuItem item, int flags) {
final boolean performedAction = (item == null) || item.onClickPerformed()
|| ((mListener != null) && mListener.onMenuItemClick(item));
if ((item == null) || ((flags & FLAG_PERFORM_NO_CLOSE) == 0)
|| ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0)) {
close();
}
return performedAction;
}
/**
* Returns the {@link MenuItem} that responds to a given shortcut key.
*
* @param keyCode for the shortcut
* @return the {@link MenuItem} that responds to a given shortcut key
*/
protected ContextMenuItem getItemForShortcut(int keyCode) {
for (ContextMenuItem item : mItems) {
if (item.getAlphabeticShortcut() == keyCode || item.getNumericShortcut() == keyCode) {
return item;
}
}
return null;
}
@Override
public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller,
Intent[] specifics, Intent intent, int flags,
MenuItem[] outSpecificItems) {
final PackageManager manager = getContext().getPackageManager();
final List<ResolveInfo> infoList = manager.queryIntentActivityOptions(caller, specifics,
intent, 0);
if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
removeGroup(groupId);
}
int i = 0;
for (ResolveInfo info : infoList) {
final Drawable icon = info.loadIcon(manager);
final CharSequence title = info.loadLabel(manager);
final MenuItem item = add(groupId, itemId, order, title);
item.setIcon(icon);
if (i < outSpecificItems.length) {
outSpecificItems[i++] = item;
} else {
throw new ArrayIndexOutOfBoundsException();
}
}
return infoList.size();
}
public abstract ContextMenuItemBuilder getMenuItemBuilder();
}