Bug 869123: Quick share action for android menu. [r=wesj]

This commit is contained in:
Sriram Ramasubramanian 2013-05-15 10:55:06 -07:00
parent 5eae1f66f2
commit b5d2f39b0b
12 changed files with 1528 additions and 14 deletions

View File

@ -20,6 +20,7 @@ import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UiAsyncTask;
import org.mozilla.gecko.widget.AboutHome;
import org.mozilla.gecko.widget.GeckoActionProvider;
import org.json.JSONArray;
import org.json.JSONException;
@ -1426,6 +1427,19 @@ abstract public class BrowserApp extends GeckoApp
mAddonMenuItemsCache.clear();
}
// Action providers are available only ICS+.
if (Build.VERSION.SDK_INT >= 14) {
MenuItem share = mMenu.findItem(R.id.share);
GeckoActionProvider provider = new GeckoActionProvider(this);
provider.setOnTargetSelectedListener(new GeckoActionProvider.OnTargetSelectedListener() {
@Override
public void onTargetSelected() {
closeOptionsMenu();
}
});
share.setActionProvider(provider);
}
return true;
}
@ -1516,6 +1530,16 @@ abstract public class BrowserApp extends GeckoApp
share.setEnabled(!(scheme.equals("about") || scheme.equals("chrome") ||
scheme.equals("file") || scheme.equals("resource")));
// Action providers are available only ICS+.
if (Build.VERSION.SDK_INT >= 14) {
GeckoActionProvider provider = (GeckoActionProvider) share.getActionProvider();
if (provider != null) {
Intent shareIntent = GeckoAppShell.getShareIntent(this, url,
"text/plain", tab.getDisplayTitle());
provider.setIntent(shareIntent);
}
}
// Disable save as PDF for about:home and xul pages
saveAsPDF.setEnabled(!(tab.getURL().equals("about:home") ||
tab.getContentType().equals("application/vnd.mozilla.xul+xml")));

View File

@ -1104,6 +1104,38 @@ public class GeckoAppShell
return u.buildUpon().scheme(lower).build();
}
/**
* Given a URI, a MIME type, and a title,
* produce a share intent which can be used to query all activities
* than can open the specified URI.
*
* @param context a <code>Context</code> instance.
* @param targetURI the string spec of the URI to open.
* @param mimeType an optional MIME type string.
* @param title the title to use in <code>ACTION_SEND</code> intents.
* @return an <code>Intent</code>, or <code>null</code> if none could be
* produced.
*/
static Intent getShareIntent(final Context context,
final String targetURI,
final String mimeType,
final String title) {
Intent shareIntent = getIntentForActionString(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, targetURI);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
// Note that EXTRA_TITLE is intended to be used for share dialog
// titles. Common usage (e.g., Pocket) suggests that it's sometimes
// interpreted as an alternate to EXTRA_SUBJECT, so we include it.
shareIntent.putExtra(Intent.EXTRA_TITLE, title);
if (mimeType != null && mimeType.length() > 0) {
shareIntent.setType(mimeType);
}
return shareIntent;
}
/**
* Given a URI, a MIME type, an Android intent "action", and a title,
* produce an intent which can be used to start an activity to open
@ -1125,19 +1157,7 @@ public class GeckoAppShell
final String title) {
if (action.equalsIgnoreCase(Intent.ACTION_SEND)) {
Intent shareIntent = getIntentForActionString(action);
shareIntent.putExtra(Intent.EXTRA_TEXT, targetURI);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
// Note that EXTRA_TITLE is intended to be used for share dialog
// titles. Common usage (e.g., Pocket) suggests that it's sometimes
// interpreted as an alternate to EXTRA_SUBJECT, so we include it.
shareIntent.putExtra(Intent.EXTRA_TITLE, title);
if (mimeType != null && mimeType.length() > 0) {
shareIntent.setType(mimeType);
}
Intent shareIntent = getShareIntent(context, targetURI, mimeType, title);
return Intent.createChooser(shareIntent,
context.getResources().getString(R.string.share_title));
}

View File

@ -5,6 +5,7 @@
package org.mozilla.gecko;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.menu.MenuItemActionBar;
import org.mozilla.gecko.menu.MenuItemDefault;
import org.mozilla.gecko.widget.AboutHomeView;
import org.mozilla.gecko.widget.AddonsSection;
@ -63,6 +64,7 @@ public final class GeckoViewsFactory implements LayoutInflater.Factory {
mFactoryMap.put("ForwardButton", ForwardButton.class.getConstructor(arg1Class, arg2Class));
mFactoryMap.put("GeckoApp$MainLayout", GeckoApp.MainLayout.class.getConstructor(arg1Class, arg2Class));
mFactoryMap.put("LinkTextView", LinkTextView.class.getConstructor(arg1Class, arg2Class));
mFactoryMap.put("MenuItemActionBar", MenuItemActionBar.class.getConstructor(arg1Class, arg2Class));
mFactoryMap.put("MenuItemDefault", MenuItemDefault.class.getConstructor(arg1Class, arg2Class));
mFactoryMap.put("FindInPageBar", FindInPageBar.class.getConstructor(arg1Class, arg2Class));
mFactoryMap.put("IconTabWidget", IconTabWidget.class.getConstructor(arg1Class, arg2Class));

View File

@ -218,17 +218,20 @@ FENNEC_JAVA_FILES = \
menu/GeckoMenuItem.java \
menu/GeckoSubMenu.java \
menu/MenuItemActionBar.java \
menu/MenuItemActionView.java \
menu/MenuItemDefault.java \
menu/MenuPanel.java \
menu/MenuPopup.java \
widget/AboutHome.java \
widget/AboutHomeView.java \
widget/AboutHomeSection.java \
widget/ActivityChooserModel.java \
widget/AddonsSection.java \
widget/DateTimePicker.java \
widget/Divider.java \
widget/FaviconView.java \
widget/GeckoPopupMenu.java \
widget/GeckoActionProvider.java \
widget/IconTabWidget.java \
widget/LastTabsSection.java \
widget/LinkTextView.java \
@ -439,6 +442,7 @@ RES_LAYOUT = \
res/layout/launch_app_list.xml \
res/layout/launch_app_listitem.xml \
res/layout/menu_action_bar.xml \
res/layout/menu_item_action_view.xml \
res/layout/menu_popup.xml \
res/layout/notification_icon_text.xml \
res/layout/notification_progress.xml \
@ -1037,6 +1041,7 @@ MOZ_ANDROID_DRAWABLES += \
mobile/android/base/resources/drawable/awesomebar_tab_indicator.xml \
mobile/android/base/resources/drawable/awesomebar_tab_selected.xml \
mobile/android/base/resources/drawable/awesomebar_tab_unselected.xml \
mobile/android/base/resources/drawable/divider_vertical.xml \
mobile/android/base/resources/drawable/favicon_bg.xml \
mobile/android/base/resources/drawable/handle_end_level.xml \
mobile/android/base/resources/drawable/handle_start_level.xml \

View File

@ -232,10 +232,23 @@ public class GeckoMenu extends ListView
@Override
public void clear() {
for (GeckoMenuItem menuItem : mItems) {
if (menuItem.hasSubMenu()) {
SubMenu subMenu = menuItem.getSubMenu();
subMenu.clear();
}
}
mAdapter.clear();
mItems.clear();
mActionItems.clear();
}
@Override
public void close() {
if (mMenuPresenter != null)
mMenuPresenter.closeMenu();
}
@Override
@ -555,6 +568,11 @@ public class GeckoMenu extends ListView
notifyDataSetChanged();
}
public void clear() {
mItems.clear();
notifyDataSetChanged();
}
public GeckoMenuItem getMenuItem(int id) {
for (GeckoMenuItem item : mItems) {
if (item.getItemId() == id)

View File

@ -4,9 +4,12 @@
package org.mozilla.gecko.menu;
import org.mozilla.gecko.widget.GeckoActionProvider;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.ActionProvider;
import android.view.ContextMenu;
import android.view.LayoutInflater;
@ -53,6 +56,7 @@ public class GeckoMenuItem implements MenuItem, View.OnClickListener {
private boolean mEnabled;
private Drawable mIcon;
private int mIconRes;
private ActionProvider mActionProvider;
private GeckoMenu mMenu;
private GeckoSubMenu mSubMenu;
private MenuItem.OnMenuItemClickListener mMenuItemClickListener;
@ -91,11 +95,17 @@ public class GeckoMenuItem implements MenuItem, View.OnClickListener {
@Override
public ActionProvider getActionProvider() {
return null;
return mActionProvider;
}
@Override
public View getActionView() {
if (mActionProvider != null && mActionProvider instanceof GeckoActionProvider) {
final View view = ((GeckoActionProvider) mActionProvider).getView(this);
view.setOnClickListener(this);
return view;
}
return null;
}
@ -132,6 +142,9 @@ public class GeckoMenuItem implements MenuItem, View.OnClickListener {
}
public View getLayout() {
if (mActionProvider != null)
return getActionView();
return mLayout.getView();
}
@ -156,6 +169,9 @@ public class GeckoMenuItem implements MenuItem, View.OnClickListener {
@Override
public SubMenu getSubMenu() {
if (mActionProvider != null)
mActionProvider.onPrepareSubMenu(mSubMenu);
return mSubMenu;
}
@ -171,6 +187,9 @@ public class GeckoMenuItem implements MenuItem, View.OnClickListener {
@Override
public boolean hasSubMenu() {
if (mActionProvider != null)
return mActionProvider.hasSubMenu();
return (mSubMenu != null);
}
@ -201,6 +220,8 @@ public class GeckoMenuItem implements MenuItem, View.OnClickListener {
@Override
public MenuItem setActionProvider(ActionProvider actionProvider) {
mActionProvider = actionProvider;
mSubMenu = new GeckoSubMenu(mContext, null);
return this;
}

View File

@ -0,0 +1,98 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
/* 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.menu;
import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ImageButton;
public class MenuItemActionView extends LinearLayout
implements GeckoMenuItem.Layout {
private static final String LOGTAG = "GeckoMenuItemActionView";
private MenuItemDefault mMenuItem;
private ImageButton mActionButton;
public MenuItemActionView(Context context, AttributeSet attrs) {
super(context, attrs);
Resources res = context.getResources();
int width = res.getDimensionPixelSize(R.dimen.menu_item_row_width);
int height = res.getDimensionPixelSize(R.dimen.menu_item_row_height);
setMinimumWidth(width);
setMinimumHeight(height);
setShowDividers(SHOW_DIVIDER_MIDDLE);
setDividerDrawable(res.getDrawable(R.drawable.divider_vertical));
setDividerPadding((int) (8 * res.getDisplayMetrics().density));
LayoutInflater.from(context).inflate(R.layout.menu_item_action_view, this);
mMenuItem = (MenuItemDefault) findViewById(R.id.menu_item);
mActionButton = (ImageButton) findViewById(R.id.action_button);
mMenuItem.setBackgroundResource(R.drawable.action_bar_button);
}
@Override
public View getView() {
return this;
}
@Override
public void setIcon(Drawable icon) {
mMenuItem.setIcon(icon);
}
@Override
public void setIcon(int icon) {
mMenuItem.setIcon(icon);
}
@Override
public void setTitle(CharSequence title) {
mMenuItem.setTitle(title);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
mMenuItem.setEnabled(enabled);
if (mActionButton != null) {
mActionButton.setEnabled(enabled);
mActionButton.setAlpha(enabled ? 255 : 99);
}
}
@Override
public void setCheckable(boolean checkable) {
}
@Override
public void setChecked(boolean checked) {
}
@Override
public void setSubMenuIndicator(boolean hasSubMenu) {
}
public void setActionButtonClickListener(View.OnClickListener listener) {
mActionButton.setOnClickListener(listener);
}
public void setActionButton(Drawable drawable) {
mActionButton.setImageDrawable(drawable);
}
}

View File

@ -0,0 +1,12 @@
<?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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#D1D5DA"/>
<size android:width="1dp" />
</shape>

View File

@ -0,0 +1,23 @@
<?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/. -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<org.mozilla.gecko.menu.MenuItemDefault
android:id="@+id/menu_item"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1.0"/>
<ImageButton android:id="@+id/action_button"
android:layout_width="@dimen/menu_item_action_icon"
android:layout_height="@dimen/menu_item_row_height"
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:scaleType="centerInside"
android:background="@drawable/action_bar_button"
android:layout_gravity="center_vertical"/>
</merge>

View File

@ -38,6 +38,7 @@
<dimen name="doorhanger_width">400dp</dimen>
<dimen name="flow_layout_spacing">6dp</dimen>
<dimen name="menu_item_action_icon">80dp</dimen>
<dimen name="menu_item_icon">21dp</dimen>
<dimen name="menu_item_state_icon">18dp</dimen>
<dimen name="menu_item_row_height">44dp</dimen>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,143 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.widget;
import org.mozilla.gecko.menu.MenuItemActionView;
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.ActionProvider;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.SubMenu;
import android.view.View;
import android.view.View.OnClickListener;
public class GeckoActionProvider extends ActionProvider {
/**
* A listener to know when a target was selected.
* When setting a provider, the activity can listen to this,
* to close the menu.
*/
public interface OnTargetSelectedListener {
public void onTargetSelected();
}
private final Context mContext;
public static final String DEFAULT_HISTORY_FILE_NAME = "history.xml";
// History file.
private String mHistoryFileName = DEFAULT_HISTORY_FILE_NAME;
private OnTargetSelectedListener mOnTargetListener;
private final Callbacks mCallbacks = new Callbacks();
public GeckoActionProvider(Context context) {
super(context);
mContext = context;
}
@Override
public View onCreateActionView() {
// Create the view and set its data model.
ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
MenuItemActionView view = new MenuItemActionView(mContext, null);
view.setActionButtonClickListener(mCallbacks);
PackageManager packageManager = mContext.getPackageManager();
ResolveInfo defaultActivity = dataModel.getDefaultActivity();
view.setActionButton(defaultActivity == null ? null : defaultActivity.loadIcon(packageManager));
return view;
}
@Override
public View onCreateActionView(MenuItem item) {
MenuItemActionView view = (MenuItemActionView) onCreateActionView();
view.setId(item.getItemId());
view.setTitle(item.getTitle());
view.setIcon(item.getIcon());
view.setVisibility(item.isVisible() ? View.VISIBLE : View.GONE);
view.setEnabled(item.isEnabled());
return view;
}
public View getView(MenuItem item) {
return onCreateActionView(item);
}
@Override
public boolean hasSubMenu() {
return true;
}
@Override
public void onPrepareSubMenu(SubMenu subMenu) {
// Clear since the order of items may change.
subMenu.clear();
ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
PackageManager packageManager = mContext.getPackageManager();
// Populate the sub-menu with a sub set of the activities.
for (int i = 0; i < dataModel.getActivityCount(); i++) {
ResolveInfo activity = dataModel.getActivity(i);
subMenu.add(0, i, i, activity.loadLabel(packageManager))
.setIcon(activity.loadIcon(packageManager))
.setOnMenuItemClickListener(mCallbacks);
}
}
public void setHistoryFileName(String historyFile) {
mHistoryFileName = historyFile;
}
public void setIntent(Intent intent) {
ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
dataModel.setIntent(intent);
}
public void setOnTargetSelectedListener(OnTargetSelectedListener listener) {
mOnTargetListener = listener;
}
/**
* Listener for handling default activity / menu item clicks.
*/
private class Callbacks implements OnMenuItemClickListener,
OnClickListener {
private void chooseActivity(int index) {
if (mOnTargetListener != null)
mOnTargetListener.onTargetSelected();
ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
Intent launchIntent = dataModel.chooseActivity(index);
if (launchIntent != null) {
launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
mContext.startActivity(launchIntent);
}
}
@Override
public boolean onMenuItemClick(MenuItem item) {
chooseActivity(item.getItemId());
return true;
}
@Override
public void onClick(View view) {
ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
chooseActivity(dataModel.getActivityIndex(dataModel.getDefaultActivity()));
}
}
}