blob: 245bda1a30e77e7bffcf152983013866305a2831 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.contextmenu;
import android.app.Activity;
import android.content.ComponentName;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Build;
import android.support.annotation.Nullable;
import android.util.Pair;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import org.chromium.base.Callback;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.share.ShareHelper;
import org.chromium.chrome.browser.share.ShareParams;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.MenuSourceType;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.base.WindowAndroid.OnCloseContextMenuListener;
import java.util.List;
/**
* A helper class that handles generating context menus for {@link WebContents}s.
*/
public class ContextMenuHelper implements OnCreateContextMenuListener {
private static final int MAX_SHARE_DIMEN_PX = 2048;
private final WebContents mWebContents;
private long mNativeContextMenuHelper;
private ContextMenuPopulator mPopulator;
private ContextMenuParams mCurrentContextMenuParams;
private Activity mActivity;
private Callback<Integer> mCallback;
private Runnable mOnMenuShown;
private Runnable mOnMenuClosed;
private ContextMenuHelper(long nativeContextMenuHelper, WebContents webContents) {
mNativeContextMenuHelper = nativeContextMenuHelper;
mWebContents = webContents;
}
@CalledByNative
private static ContextMenuHelper create(long nativeContextMenuHelper, WebContents webContents) {
return new ContextMenuHelper(nativeContextMenuHelper, webContents);
}
@CalledByNative
private void destroy() {
if (mPopulator != null) mPopulator.onDestroy();
mNativeContextMenuHelper = 0;
}
/**
* @return The activity that corresponds to the context menu helper.
*/
protected Activity getActivity() {
return mActivity;
}
/**
* @param populator A {@link ContextMenuPopulator} that is responsible for managing and showing
* context menus.
*/
@CalledByNative
private void setPopulator(ContextMenuPopulator populator) {
if (mPopulator != null) mPopulator.onDestroy();
mPopulator = populator;
}
/**
* Starts showing a context menu for {@code view} based on {@code params}.
* @param params The {@link ContextMenuParams} that indicate what menu items to show.
* @param view container view for the menu.
* @param topContentOffsetPx the offset of the content from the top.
*/
@CalledByNative
private void showContextMenu(
final ContextMenuParams params, View view, float topContentOffsetPx) {
if (params.isFile()) return;
final WindowAndroid windowAndroid = mWebContents.getTopLevelNativeWindow();
if (view == null || view.getVisibility() != View.VISIBLE || view.getParent() == null
|| windowAndroid == null || windowAndroid.getActivity().get() == null
|| mPopulator == null) {
return;
}
mCurrentContextMenuParams = params;
mActivity = windowAndroid.getActivity().get();
mCallback = new Callback<Integer>() {
@Override
public void onResult(Integer result) {
mPopulator.onItemSelected(
ContextMenuHelper.this, mCurrentContextMenuParams, result);
}
};
mOnMenuShown = new Runnable() {
@Override
public void run() {
RecordHistogram.recordBooleanHistogram("ContextMenu.Shown", mWebContents != null);
}
};
mOnMenuClosed = new Runnable() {
@Override
public void run() {
if (mNativeContextMenuHelper == 0) return;
nativeOnContextMenuClosed(mNativeContextMenuHelper);
}
};
if (ChromeFeatureList.isEnabled(ChromeFeatureList.CUSTOM_CONTEXT_MENU)
&& params.getSourceType() != MenuSourceType.MENU_SOURCE_MOUSE) {
List<Pair<Integer, List<ContextMenuItem>>> items =
mPopulator.buildContextMenu(null, mActivity, mCurrentContextMenuParams);
if (items.isEmpty()) {
ThreadUtils.postOnUiThread(mOnMenuClosed);
return;
}
final TabularContextMenuUi menuUi = new TabularContextMenuUi(new Callback<Boolean>() {
@Override
public void onResult(Boolean isShareLink) {
if (isShareLink) {
ShareParams shareParams =
new ShareParams.Builder(mActivity, params.getUrl(), params.getUrl())
.setShareDirectly(true)
.setSaveLastUsed(false)
.build();
ShareHelper.share(shareParams);
} else {
shareImageDirectly(ShareHelper.getLastShareComponentName(null));
}
}
});
menuUi.setTopContentOffsetY(topContentOffsetPx);
menuUi.displayMenu(mActivity, mCurrentContextMenuParams, items, mCallback, mOnMenuShown,
mOnMenuClosed);
if (mCurrentContextMenuParams.isImage()) {
getThumbnail(menuUi, new Callback<Bitmap>() {
@Override
public void onResult(Bitmap result) {
menuUi.onImageThumbnailRetrieved(result);
}
});
}
return;
}
// The Platform Context Menu requires the listener within this helper since this helper and
// provides context menu for us to show.
view.setOnCreateContextMenuListener(this);
boolean wasContextMenuShown = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
&& params.getSourceType() == MenuSourceType.MENU_SOURCE_MOUSE) {
final float density = view.getResources().getDisplayMetrics().density;
final float touchPointXPx = params.getTriggeringTouchXDp() * density;
final float touchPointYPx =
(params.getTriggeringTouchYDp() * density) + topContentOffsetPx;
wasContextMenuShown = view.showContextMenu(touchPointXPx, touchPointYPx);
} else {
wasContextMenuShown = view.showContextMenu();
}
if (wasContextMenuShown) {
mOnMenuShown.run();
windowAndroid.addContextMenuCloseListener(new OnCloseContextMenuListener() {
@Override
public void onContextMenuClosed() {
mOnMenuClosed.run();
windowAndroid.removeContextMenuCloseListener(this);
}
});
}
}
/**
* Starts a download based on the current {@link ContextMenuParams}.
* @param isLink Whether or not the download target is a link.
*/
public void startContextMenuDownload(boolean isLink, boolean isDataReductionProxyEnabled) {
if (mNativeContextMenuHelper != 0) {
nativeOnStartDownload(mNativeContextMenuHelper, isLink, isDataReductionProxyEnabled);
}
}
/**
* Trigger an image search for the current image that triggered the context menu.
*/
public void searchForImage() {
if (mNativeContextMenuHelper == 0) return;
nativeSearchForImage(mNativeContextMenuHelper);
}
/**
* Share the image that triggered the current context menu.
*/
public void shareImage() {
shareImageDirectly(null);
}
/**
* Share image triggered with the current context menu directly with a specific app.
* @param name The {@link ComponentName} of the app to share the image directly with.
*/
public void shareImageDirectly(@Nullable final ComponentName name) {
if (mNativeContextMenuHelper == 0) return;
Callback<byte[]> callback = new Callback<byte[]>() {
@Override
public void onResult(byte[] result) {
WindowAndroid windowAndroid = mWebContents.getTopLevelNativeWindow();
Activity activity = windowAndroid.getActivity().get();
if (activity == null) return;
ShareHelper.shareImage(activity, result, name);
}
};
nativeRetrieveImageForShare(
mNativeContextMenuHelper, callback, MAX_SHARE_DIMEN_PX, MAX_SHARE_DIMEN_PX);
}
/**
* Gets the thumbnail of the current image that triggered the context menu.
* @param callback Called once the the thumbnail is received.
*/
private void getThumbnail(TabularContextMenuUi menuUi, final Callback<Bitmap> callback) {
if (mNativeContextMenuHelper == 0) return;
Resources res = mActivity.getResources();
int maxWidthPx = menuUi.getMaxThumbnailWidthPx(res);
int maxHeightPx = menuUi.getMaxThumbnailHeightPx(res);
Callback<Bitmap> bitmapCallback = new Callback<Bitmap>() {
@Override
public void onResult(Bitmap result) {
callback.onResult(result);
}
};
nativeRetrieveImageForContextMenu(
mNativeContextMenuHelper, bitmapCallback, maxWidthPx, maxHeightPx);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
List<Pair<Integer, List<ContextMenuItem>>> items =
mPopulator.buildContextMenu(menu, v.getContext(), mCurrentContextMenuParams);
if (items.isEmpty()) {
ThreadUtils.postOnUiThread(mOnMenuClosed);
return;
}
ContextMenuUi menuUi = new PlatformContextMenuUi(menu);
menuUi.displayMenu(mActivity, mCurrentContextMenuParams, items, mCallback, mOnMenuShown,
mOnMenuClosed);
}
/**
* @return The {@link ContextMenuPopulator} responsible for populating the context menu.
*/
@VisibleForTesting
public ContextMenuPopulator getPopulator() {
return mPopulator;
}
private native void nativeOnStartDownload(
long nativeContextMenuHelper, boolean isLink, boolean isDataReductionProxyEnabled);
private native void nativeSearchForImage(long nativeContextMenuHelper);
private native void nativeRetrieveImageForShare(long nativeContextMenuHelper,
Callback<byte[]> callback, int maxWidthPx, int maxHeightPx);
private native void nativeRetrieveImageForContextMenu(long nativeContextMenuHelper,
Callback<Bitmap> callback, int maxWidthPx, int maxHeightPx);
private native void nativeOnContextMenuClosed(long nativeContextMenuHelper);
}