Bug 1315937 - Add basic custom menu to CustomTabsActivity
Besides custom-menu-items from 3-rd party apps, we designed a custom
menu which always has
* buttons for Forward, Reload
* One menu item for 'Open by Firefox'
* a footer 'Powered by Firefox'
This patch adds a button as an anchor to standard menu. Once user click
it, to show a custom menu (GeckoPopupMenu) base on the anchor.
In current design, there is only one style for menu, regardless of Dark
or Light theme.
MozReview-Commit-ID: 5RMiGDlxLTU
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -2,55 +2,64 @@
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.customtabs;
import android.app.PendingIntent;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.StyleRes;
import android.support.annotation.VisibleForTesting;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
+import android.widget.ImageButton;
import android.widget.TextView;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.menu.GeckoMenu;
+import org.mozilla.gecko.menu.GeckoMenuInflater;
import org.mozilla.gecko.util.ColorUtil;
+import org.mozilla.gecko.widget.GeckoPopupMenu;
import java.lang.reflect.Field;
import static android.support.customtabs.CustomTabsIntent.EXTRA_TOOLBAR_COLOR;
public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedListener {
private static final String LOGTAG = "CustomTabsActivity";
private static final String SAVED_TOOLBAR_COLOR = "SavedToolbarColor";
private static final String SAVED_TOOLBAR_TITLE = "SavedToolbarTitle";
private static final int NO_COLOR = -1;
private ActionBar actionBar;
+ private GeckoPopupMenu popupMenu;
private int tabId = -1;
private boolean useDomainTitle = true;
private int toolbarColor;
private String toolbarTitle;
// A state to indicate whether this activity is finishing with customize animation
private boolean usingCustomAnimation = false;
@@ -180,16 +189,18 @@ public class CustomTabsActivity extends
}
if (!useDomainTitle || title == null || title.isEmpty()) {
toolbarTitle = AppConstants.MOZ_APP_BASENAME;
} else {
toolbarTitle = title;
}
actionBar.setTitle(toolbarTitle);
}
+
+ updateMenuItemForward();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SAVED_TOOLBAR_COLOR, toolbarColor);
outState.putString(SAVED_TOOLBAR_TITLE, toolbarTitle);
@@ -209,27 +220,58 @@ public class CustomTabsActivity extends
// Usually should use onCreateOptionsMenu() to initialize menu items. But GeckoApp overwrite
// it to support custom menu(Bug 739412). Then the parameter *menu* in this.onCreateOptionsMenu()
// and this.onPrepareOptionsMenu() are different instances - GeckoApp.onCreatePanelMenu() changed it.
// CustomTabsActivity only use standard menu in ActionBar, so initialize menu here.
@Override
public boolean onCreatePanelMenu(final int id, final Menu menu) {
insertActionButton(menu, getIntent());
+
+ popupMenu = createCustomPopupMenu();
+
+ // Create a ImageButton manually, and use it as an anchor for PopupMenu.
+ final ImageButton btn = new ImageButton(getContext(),
+ null, 0, R.style.Widget_MenuButtonCustomTabs);
+ btn.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ btn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View anchor) {
+ popupMenu.setAnchor(anchor);
+ popupMenu.show();
+ }
+ });
+
+ // Insert the anchor-button to Menu
+ final MenuItem item = menu.add(Menu.NONE, R.id.menu, Menu.NONE, "Menu Button");
+ item.setActionView(btn);
+ MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
+
+ updateMenuItemForward();
return true;
}
+ @Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.action_button:
onActionButtonClicked();
return true;
+ case R.id.custom_tabs_menu_forward:
+ onForwardClicked();
+ return true;
+ case R.id.custom_tabs_menu_reload:
+ onReloadClicked();
+ return true;
+ case R.id.custom_tabs_menu_open_in:
+ onOpenInClicked();
+ return true;
}
return super.onOptionsItemSelected(item);
}
/**
* To insert a MenuItem (as an ActionButton) into Menu.
*
* @param menu The options menu in which to place items.
@@ -283,13 +325,97 @@ public class CustomTabsActivity extends
additional.setData(Uri.parse(tab.getURL()));
try {
pendingIntent.send(this, 0, additional);
} catch (PendingIntent.CanceledException e) {
Log.w(LOGTAG, "Performing a canceled pending intent", e);
}
}
+ /**
+ * To generate a popup menu which looks like an ordinary option menu, but have extra elements
+ * such as footer.
+ *
+ * @return a GeckoPopupMenu which can be placed on any view.
+ */
+ private GeckoPopupMenu createCustomPopupMenu() {
+ final GeckoPopupMenu popupMenu = new GeckoPopupMenu(this);
+ final GeckoMenu geckoMenu = popupMenu.getMenu();
+
+ // pass to to Activity.onMenuItemClick for consistency.
+ popupMenu.setOnMenuItemClickListener(new GeckoPopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ return CustomTabsActivity.this.onMenuItemClick(item);
+ }
+ });
+
+ // to add Fennec default menu
+ final MenuInflater inflater = new GeckoMenuInflater(this);
+ inflater.inflate(R.menu.customtabs_menu, geckoMenu);
+
+ // insert default browser name to title of menu-item-Open-In
+ final MenuItem openItem = geckoMenu.findItem(R.id.custom_tabs_menu_open_in);
+ if (openItem != null) {
+ final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"));
+ final ResolveInfo info = getPackageManager()
+ .resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY);
+ final String name = info.loadLabel(getPackageManager()).toString();
+ openItem.setTitle(getString(R.string.custom_tabs_menu_item_open_in, name));
+ }
+
+ geckoMenu.addFooterView(
+ getLayoutInflater().inflate(R.layout.customtabs_options_menu_footer, geckoMenu, false),
+ null,
+ false);
+
+ return popupMenu;
+ }
+
+ /**
+ * Update state of Forward button in Popup Menu. It is clickable only if current tab can do forward.
+ */
+ private void updateMenuItemForward() {
+ if ((popupMenu == null)
+ || (popupMenu.getMenu() == null)
+ || (popupMenu.getMenu().findItem(R.id.custom_tabs_menu_forward) == null)) {
+ return;
+ }
+
+ final MenuItem forwardMenuItem = popupMenu.getMenu().findItem(R.id.custom_tabs_menu_forward);
+ final Tab tab = Tabs.getInstance().getSelectedTab();
+ final boolean enabled = (tab != null && tab.canDoForward());
+ forwardMenuItem.setEnabled(enabled);
+ }
+
+ private void onReloadClicked() {
+ final Tab tab = Tabs.getInstance().getSelectedTab();
+ if (tab != null) {
+ tab.doReload(true);
+ }
+ }
+
+ private void onForwardClicked() {
+ final Tab tab = Tabs.getInstance().getSelectedTab();
+ if ((tab != null) && tab.canDoForward()) {
+ tab.doForward();
+ }
+ }
+
+ /**
+ * Callback for Open-in menu item.
+ */
+ private void onOpenInClicked() {
+ final Tab tab = Tabs.getInstance().getSelectedTab();
+ if (tab != null) {
+ // To launch default browser with url of current tab.
+ final Intent intent = new Intent();
+ intent.setData(Uri.parse(tab.getURL()));
+ intent.setAction(Intent.ACTION_VIEW);
+ startActivity(intent);
+ }
+ }
+
private void onActionButtonClicked() {
PendingIntent pendingIntent = IntentUtil.getActionButtonPendingIntent(getIntent());
performPendingIntent(pendingIntent);
}
}
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -277,16 +277,20 @@
for experimental features. -->
<!ENTITY pref_category_experimental "Experimental features">
<!-- Custom Tabs is an Android API for allowing third-party apps to open URLs in a customized UI.
Instead of switching to the browser it appears as if the user stays in the third-party app.
For more see: https://developer.chrome.com/multidevice/android/customtabs -->
<!ENTITY pref_custom_tabs "Custom Tabs">
<!ENTITY pref_custom_tabs_summary3 "Allow apps to open websites using a customized version of &brandShortName;">
+<!-- Localization note (custom_tabs_menu_item_open_in): The variable is replaced by the name of
+ default browser from user's preference, such as "Open in Firefox" -->
+<!ENTITY custom_tabs_menu_item_open_in "Open in &formatS;">
+<!ENTITY custom_tabs_menu_footer "Powered by &brandShortName;">
<!-- Localization note (pref_activity_stream): Experimental feature, see https://testpilot.firefox.com/experiments/activity-stream -->
<!ENTITY pref_activity_stream "Activity Stream">
<!ENTITY pref_activity_stream_summary "A rich visual history feed and a reimagined home page make it easier than ever to find exactly what you\'re looking for in &brandShortName;.">
<!ENTITY tracking_protection_prompt_title "Now with Tracking Protection">
<!ENTITY tracking_protection_prompt_text "Actively block tracking elements so you don\'t have to worry.">
<!ENTITY tracking_protection_prompt_tip_text "Visit Privacy settings to learn more">
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/customtabs_options_menu_footer.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <!-- padding is consistent with Widget.MenuItemDefault -->
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="30dp"
+ android:background="@color/toolbar_menu_dark_grey"
+ android:gravity="center_vertical"
+ android:paddingEnd="10dp"
+ android:paddingLeft="15dp"
+ android:paddingRight="10dp"
+ android:paddingStart="15dp"
+ android:text="@string/custom_tabs_menu_footer"
+ android:textColor="#8f9195"
+ android:textSize="12sp"
+ tools:ignore="RtlHardcoded"/>
+</FrameLayout>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/menu/customtabs_menu.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!-- We disable AlwaysShowAction because we interpret the menu
+ attributes ourselves and thus the warning isn't relevant to us. -->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="AlwaysShowAction">
+
+ <item
+ android:id="@+id/custom_tabs_menu_forward"
+ android:icon="@drawable/ic_menu_forward"
+ android:showAsAction="always"
+ android:title="@string/forward"/>
+
+ <item
+ android:id="@+id/custom_tabs_menu_reload"
+ android:icon="@drawable/ic_menu_reload"
+ android:showAsAction="always"
+ android:title="@string/share"/>
+
+ <item
+ android:id="@+id/custom_tabs_menu_open_in"
+ android:title="@string/custom_tabs_menu_item_open_in"/>
+
+</menu>
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -114,16 +114,28 @@
<item name="android:drawablePadding">6dip</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textAppearance">@style/TextAppearance</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">middle</item>
<item name="android:textSize">@dimen/menu_item_textsize</item>
</style>
+ <style name="Widget.MenuButtonCustomTabs" parent="GeckoActionBar.Button.MenuButton">
+ <item name="android:tint">?android:textColorPrimary</item>
+ </style>
+
+ <style name="Widget.MenuItemCustomTabs" parent="Widget.MenuItemDefault">
+ <!-- In current design, there is only one style for Popup-menu, regardless of Dark/Light theme -->
+ <item name="android:textColor">@android:color/black</item>
+ <item name="android:textColorHighlight">?android:textColorHighlight</item>
+ <item name="android:textColorHint">?android:attr/textColorHint</item>
+ <item name="android:textColorLink">?android:attr/textColorLink</item>
+ </style>
+
<style name="Widget.FolderTitle">
<item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemTitle</item>
</style>
<style name="Widget.FolderTitle.OneLine">
<item name="android:textSize">@dimen/home_folder_title_oneline_textsize</item>
</style>
--- a/mobile/android/base/resources/values/themes.xml
+++ b/mobile/android/base/resources/values/themes.xml
@@ -119,15 +119,20 @@
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
<!--
Themes for CustomTabsActivity. Since CustomTabsActivity usually be used by 3-rd party apps,
to create separated themes to keep look and feel be consistent with ordinary Android app.
And ensure changes to CustomTabsActivity won't effect GeckoApp.
-->
+
<style name="GeckoCustomTabs" parent="Theme.AppCompat.NoActionBar">
+ <item name="menuItemActionBarStyle">@style/Widget.MenuItemActionBar</item>
+ <item name="menuItemDefaultStyle">@style/Widget.MenuItemCustomTabs</item>
</style>
<style name="GeckoCustomTabs.Light" parent="Theme.AppCompat.Light.NoActionBar">
+ <item name="menuItemActionBarStyle">@style/Widget.MenuItemActionBar</item>
+ <item name="menuItemDefaultStyle">@style/Widget.MenuItemCustomTabs</item>
</style>
</resources>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -234,16 +234,18 @@
<string name="pref_whats_new_notification">&pref_whats_new_notification;</string>
<string name="pref_whats_new_notification_summary">&pref_whats_new_notification_summary;</string>
<string name="pref_category_experimental">&pref_category_experimental;</string>
<string name="pref_custom_tabs">&pref_custom_tabs;</string>
<string name="pref_custom_tabs_summary">&pref_custom_tabs_summary3;</string>
+ <string name="custom_tabs_menu_item_open_in">&custom_tabs_menu_item_open_in;</string>
+ <string name="custom_tabs_menu_footer">&custom_tabs_menu_footer;</string>
<string name="pref_activity_stream">&pref_activity_stream;</string>
<string name="pref_activity_stream_summary">&pref_activity_stream_summary;</string>
<string name="pref_char_encoding">&pref_char_encoding;</string>
<string name="pref_char_encoding_on">&pref_char_encoding_on;</string>
<string name="pref_char_encoding_off">&pref_char_encoding_off;</string>
<string name="pref_clear_private_data_now">&pref_clear_private_data2;</string>