Index: chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..999689752532cfa22b2d76310893de515559930d |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java |
@@ -0,0 +1,319 @@ |
+// Copyright 2015 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.omaha; |
+ |
+import android.content.ActivityNotFoundException; |
+import android.content.Context; |
+import android.content.Intent; |
+import android.graphics.Bitmap; |
+import android.graphics.BitmapFactory; |
+import android.graphics.Canvas; |
+import android.graphics.Paint; |
+import android.graphics.PorterDuff; |
+import android.graphics.PorterDuffXfermode; |
+import android.net.Uri; |
+import android.os.AsyncTask; |
+import android.text.TextUtils; |
+ |
+import org.chromium.base.CommandLine; |
+import org.chromium.base.Log; |
+import org.chromium.base.ThreadUtils; |
+import org.chromium.base.metrics.RecordHistogram; |
+import org.chromium.chrome.R; |
+import org.chromium.chrome.browser.ChromeActivity; |
+import org.chromium.chrome.browser.ChromeSwitches; |
+import org.chromium.chrome.browser.appmenu.AppMenu; |
+import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
+import org.chromium.components.variations.VariationsAssociatedData; |
+import org.chromium.ui.base.LocalizationUtils; |
+ |
+/** |
+ * Contains logic for whether the update menu item should be shown, whether the update toolbar badge |
+ * should be shown, and UMA logging for the update menu item. |
+ */ |
+public class UpdateMenuItemHelper { |
+ private static final String TAG = "UpdateMenuItemHelper"; |
+ |
+ // VariationsAssociatedData configs |
+ private static final String FIELD_TRIAL_NAME = "UpdateMenuItem"; |
+ private static final String ENABLED_VALUE = "true"; |
+ private static final String ENABLE_UPDATE_MENU_ITEM = "enable_update_menu_item"; |
+ private static final String ENABLE_UPDATE_BADGE = "enable_update_badge"; |
+ private static final String SHOW_SUMMARY = "show_summary"; |
+ private static final String CUSTOM_SUMMARY = "custom_summary"; |
+ |
+ // UMA constants for logging whether the menu item was clicked. |
+ private static final int ITEM_NOT_CLICKED = 0; |
+ private static final int ITEM_CLICKED_INTENT_LAUNCHED = 1; |
+ private static final int ITEM_CLICKED_INTENT_FAILED = 2; |
+ private static final int ITEM_CLICKED_BOUNDARY = 3; |
+ |
+ // UMA constants for logging whether Chrome was updated after the menu item was clicked. |
+ private static final int UPDATED = 0; |
+ private static final int NOT_UPDATED = 1; |
+ private static final int UPDATED_BOUNDARY = 2; |
+ |
+ private static UpdateMenuItemHelper sInstance; |
+ private static Object sGetInstanceLock = new Object(); |
+ |
+ // Whether OmahaClient has already been checked for an update. |
+ private boolean mAlreadyCheckedForUpdates; |
+ |
+ // Whether an update is available. |
+ private boolean mUpdateAvailable; |
+ |
+ // URL to direct the user to when Omaha detects a newer version available. |
+ private String mUpdateUrl; |
+ |
+ // Whether the menu item was clicked. This is used to log the click-through rate. |
+ private boolean mMenuItemClicked; |
+ |
+ // The bitmap to use for the menu button when the badge is showing. |
+ private Bitmap mBadgedMenuButtonBitmap; |
+ |
+ /** |
+ * @return The {@link UpdateMenuItemHelper} instance. |
+ */ |
+ public static UpdateMenuItemHelper getInstance() { |
+ synchronized (UpdateMenuItemHelper.sGetInstanceLock) { |
+ if (sInstance == null) { |
+ sInstance = new UpdateMenuItemHelper(); |
+ String testMarketUrl = getStringParamValue(ChromeSwitches.MARKET_URL_FOR_TESTING); |
+ if (!TextUtils.isEmpty(testMarketUrl)) { |
+ sInstance.mUpdateUrl = testMarketUrl; |
+ } |
+ } |
+ return sInstance; |
+ } |
+ } |
+ |
+ /** |
+ * Checks if the {@link OmahaClient} knows about an update. |
+ * @param activity The current {@link ChromeActivity}. |
+ */ |
+ public void checkForUpdateOnBackgroundThread(final ChromeActivity activity) { |
+ if (!getBooleanParam(ENABLE_UPDATE_MENU_ITEM)) return; |
+ |
+ ThreadUtils.assertOnUiThread(); |
+ |
+ if (mAlreadyCheckedForUpdates) { |
+ if (activity.isActivityDestroyed()) return; |
+ activity.onCheckForUpdate(mUpdateAvailable); |
+ return; |
+ } |
+ |
+ mAlreadyCheckedForUpdates = true; |
+ |
+ new AsyncTask<Void, Void, Void>() { |
+ @Override |
+ protected Void doInBackground(Void... params) { |
+ if (OmahaClient.isNewerVersionAvailable(activity)) { |
+ mUpdateUrl = OmahaClient.getMarketURL(activity); |
+ mUpdateAvailable = true; |
+ } else { |
+ mUpdateAvailable = false; |
+ } |
+ return null; |
+ } |
+ |
+ @Override |
+ protected void onPostExecute(Void result) { |
+ if (activity.isActivityDestroyed()) return; |
+ activity.onCheckForUpdate(mUpdateAvailable); |
+ recordUpdateHistogram(); |
+ } |
+ }.execute(); |
+ } |
+ |
+ /** |
+ * Logs whether an update was performed if the update menu item was clicked. |
+ * Should be called from ChromeActivity#onStart(). |
+ */ |
+ public void onStart() { |
+ if (mAlreadyCheckedForUpdates) { |
+ recordUpdateHistogram(); |
+ } |
+ } |
+ |
+ /** |
+ * @param activity The current {@link ChromeActivity}. |
+ * @return Whether the update menu item should be shown. |
+ */ |
+ public boolean shouldShowMenuItem(ChromeActivity activity) { |
+ if (getBooleanParam(ChromeSwitches.FORCE_SHOW_UPDATE_MENU_ITEM)) { |
+ return true; |
+ } |
+ |
+ if (!getBooleanParam(ENABLE_UPDATE_MENU_ITEM)) { |
+ return false; |
+ } |
+ |
+ return updateAvailable(activity); |
+ } |
+ |
+ /** |
+ * @param context The current {@link Context}. |
+ * @return The string to use for summary text or the empty string if no summary should be shown. |
+ */ |
+ public String getMenuItemSummaryText(Context context) { |
+ if (!getBooleanParam(SHOW_SUMMARY)) { |
+ return ""; |
+ } |
+ |
+ String customSummary = getStringParamValue(CUSTOM_SUMMARY); |
+ if (!TextUtils.isEmpty(customSummary)) { |
+ return customSummary; |
+ } |
+ |
+ return context.getResources().getString(R.string.menu_update_summary_default); |
+ } |
+ |
+ /** |
+ * @param activity The current {@link ChromeActivity}. |
+ * @return Whether the update badge should be shown in the toolbar. |
+ */ |
+ public boolean shouldShowToolbarBadge(ChromeActivity activity) { |
+ if (getBooleanParam(ChromeSwitches.FORCE_SHOW_UPDATE_MENU_BADGE)) { |
+ return true; |
+ } |
+ |
+ if (!getBooleanParam(ENABLE_UPDATE_BADGE)) { |
+ return false; |
+ } |
+ |
+ return updateAvailable(activity); |
+ } |
+ |
+ /** |
+ * Handles a click on the update menu item. |
+ * @param activity The current {@link ChromeActivity}. |
+ */ |
+ public void onMenuItemClicked(ChromeActivity activity) { |
+ if (mUpdateUrl == null) return; |
+ |
+ // Fire an intent to open the URL. |
+ try { |
+ Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(mUpdateUrl)); |
+ activity.startActivity(launchIntent); |
+ recordItemClickedHistogram(ITEM_CLICKED_INTENT_LAUNCHED); |
+ PrefServiceBridge.getInstance().setClickedUpdateMenuItem(true); |
+ } catch (ActivityNotFoundException e) { |
+ Log.e(TAG, "Failed to launch Activity for: " + mUpdateUrl); |
+ recordItemClickedHistogram(ITEM_CLICKED_INTENT_FAILED); |
+ } |
+ } |
+ |
+ /** |
+ * Returns a {@link Bitmap} to use for the menu button with part of the original image removed |
+ * to simulate a 1dp transparent border around the update badge. |
+ * |
+ * @param context The current {@link Context}. |
+ * @return The {@link Bitmap} to use for the the menu button when showing the update badge. |
+ */ |
+ public Bitmap getBadgedMenuButtonBitmap(Context context) { |
+ if (mBadgedMenuButtonBitmap == null) { |
+ // Punch a hole in the app menu button to create the illusion of a 1dp transparent |
+ // border around the update badge. |
+ // Load btn_menu bitmap and use it to initialize a canvas. |
+ BitmapFactory.Options opts = new BitmapFactory.Options(); |
+ opts.inMutable = true; |
+ mBadgedMenuButtonBitmap = BitmapFactory.decodeResource(context.getResources(), |
+ R.drawable.btn_menu, opts); |
+ Canvas canvas = new Canvas(); |
+ canvas.setBitmap(mBadgedMenuButtonBitmap); |
+ |
+ // Calculate the dimensions and offsets for the update badge. |
+ float menuBadgeBackgroundSize = context.getResources().getDimension( |
+ R.dimen.menu_badge_background_size); |
+ float radius = menuBadgeBackgroundSize / 2.f; |
+ // The design specification calls for 4.5dp right offset and 9dp top offset. |
+ float xPos = 4.5f * context.getResources().getDisplayMetrics().density + radius; |
+ float yPos = 9f * context.getResources().getDisplayMetrics().density + radius; |
+ |
+ if (LocalizationUtils.isLayoutRtl()) { |
+ // In RTL layouts, the badge should be placed to the left of the menu button icon. |
+ xPos = mBadgedMenuButtonBitmap.getWidth() - xPos; |
+ } |
+ |
+ // Draw a transparent circle on top of the bitmap, creating a hole. |
+ Paint paint = new Paint(); |
+ paint.setFlags(Paint.ANTI_ALIAS_FLAG); |
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); |
+ canvas.drawCircle(xPos, yPos, radius, paint); |
+ } |
+ |
+ return mBadgedMenuButtonBitmap; |
+ } |
+ |
+ /** |
+ * Should be called before the AppMenu is dismissed if the update menu item was clicked. |
+ */ |
+ public void setMenuItemClicked() { |
+ mMenuItemClicked = true; |
+ } |
+ |
+ /** |
+ * Called when the {@link AppMenu} is dimissed. Logs a histogram immediately if the update menu |
+ * item was not clicked. If it was clicked, logging is delayed until #onMenuItemClicked(). |
+ */ |
+ public void onMenuDismissed() { |
+ if (!mMenuItemClicked) { |
+ recordItemClickedHistogram(ITEM_NOT_CLICKED); |
+ } |
+ mMenuItemClicked = false; |
+ } |
+ |
+ private boolean updateAvailable(ChromeActivity activity) { |
+ if (!mAlreadyCheckedForUpdates) { |
+ checkForUpdateOnBackgroundThread(activity); |
+ return false; |
+ } |
+ |
+ return mUpdateAvailable; |
+ } |
+ |
+ private void recordItemClickedHistogram(int action) { |
+ RecordHistogram.recordEnumeratedHistogram("GoogleUpdate.MenuItem.ActionTakenOnMenuOpen", |
+ action, ITEM_CLICKED_BOUNDARY); |
+ } |
+ |
+ private void recordUpdateHistogram() { |
+ if (PrefServiceBridge.getInstance().getClickedUpdateMenuItem()) { |
+ RecordHistogram.recordEnumeratedHistogram( |
+ "GoogleUpdate.MenuItem.ActionTakenAfterItemClicked", |
+ mUpdateAvailable ? NOT_UPDATED : UPDATED, UPDATED_BOUNDARY); |
+ PrefServiceBridge.getInstance().setClickedUpdateMenuItem(false); |
+ } |
+ } |
+ |
+ /** |
+ * Gets a boolean VariationsAssociatedData parameter, assuming the <paramName>="true" format. |
+ * Also checks for a command-line switch with the same name, for easy local testing. |
+ * @param paramName The name of the parameter (or command-line switch) to get a value for. |
+ * @return Whether the param is defined with a value "true", if there's a command-line |
+ * flag present with any value. |
+ */ |
+ private static boolean getBooleanParam(String paramName) { |
+ if (CommandLine.getInstance().hasSwitch(paramName)) { |
+ return true; |
+ } |
+ return TextUtils.equals(ENABLED_VALUE, |
+ VariationsAssociatedData.getVariationParamValue(FIELD_TRIAL_NAME, paramName)); |
+ } |
+ |
+ /** |
+ * Gets a String VariationsAssociatedData parameter. Also checks for a command-line switch with |
+ * the same name, for easy local testing. |
+ * @param paramName The name of the parameter (or command-line switch) to get a value for. |
+ * @return The command-line flag value if present, or the param is value if present. |
+ */ |
+ private static String getStringParamValue(String paramName) { |
+ String value = CommandLine.getInstance().getSwitchValue(paramName); |
+ if (TextUtils.isEmpty(value)) { |
+ value = VariationsAssociatedData.getVariationParamValue(FIELD_TRIAL_NAME, paramName); |
+ } |
+ return value; |
+ } |
+} |