Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package org.chromium.chrome.browser.omaha; | |
| 6 | |
| 7 import android.content.ActivityNotFoundException; | |
| 8 import android.content.Context; | |
| 9 import android.content.Intent; | |
| 10 import android.graphics.Bitmap; | |
| 11 import android.graphics.BitmapFactory; | |
| 12 import android.graphics.Canvas; | |
| 13 import android.graphics.Paint; | |
| 14 import android.graphics.PorterDuff; | |
| 15 import android.graphics.PorterDuffXfermode; | |
| 16 import android.net.Uri; | |
| 17 import android.os.AsyncTask; | |
| 18 import android.text.TextUtils; | |
| 19 | |
| 20 import org.chromium.base.CommandLine; | |
| 21 import org.chromium.base.Log; | |
| 22 import org.chromium.base.ThreadUtils; | |
| 23 import org.chromium.base.metrics.RecordHistogram; | |
| 24 import org.chromium.chrome.R; | |
| 25 import org.chromium.chrome.browser.ChromeActivity; | |
| 26 import org.chromium.chrome.browser.ChromeSwitches; | |
| 27 import org.chromium.chrome.browser.appmenu.AppMenu; | |
| 28 import org.chromium.chrome.browser.preferences.PrefServiceBridge; | |
| 29 import org.chromium.components.variations.VariationsAssociatedData; | |
| 30 import org.chromium.ui.base.LocalizationUtils; | |
| 31 | |
| 32 /** | |
| 33 * Contains logic for whether the update menu item should be shown, whether the update toolbar badge | |
| 34 * should be shown, and UMA logging for the update menu item. | |
| 35 */ | |
| 36 public class UpdateMenuItemHelper { | |
| 37 private static UpdateMenuItemHelper sInstance; | |
| 38 private static Object sCreateInstanceLock = new Object(); | |
| 39 | |
| 40 private static final String TAG = "UpdateMenuItemHelper"; | |
|
gone
2015/12/10 21:45:09
nit: private static finals go above private static
Theresa
2015/12/11 19:44:46
Done.
| |
| 41 | |
| 42 // VariationsAssociatedData configs | |
| 43 private static final String FIELD_TRIAL_NAME = "UpdateMenuItem"; | |
| 44 private static final String ENABLED_VALUE = "true"; | |
| 45 private static final String ENABLE_UPDATE_MENU_ITEM = "enable_update_menu_it em"; | |
| 46 private static final String ENABLE_UPDATE_BADGE = "enable_update_badge"; | |
| 47 private static final String SHOW_SUMMARY = "show_summary"; | |
| 48 private static final String CUSTOM_SUMMARY = "custom_summary"; | |
| 49 | |
| 50 // UMA constants for logging whether the menu item was clicked. | |
| 51 private static final int ITEM_NOT_CLICKED = 0; | |
| 52 private static final int ITEM_CLICKED_INTENT_LAUNCHED = 1; | |
| 53 private static final int ITEM_CLICKED_INTENT_FAILED = 2; | |
| 54 private static final int ITEM_CLICKED_BOUNDARY = 3; | |
| 55 | |
| 56 // UMA constants for logging whether Chrome was updated after the menu item was clicked. | |
| 57 private static final int UPDATED = 0; | |
| 58 private static final int NOT_UPDATED = 1; | |
| 59 private static final int UPDATED_BOUNDARY = 2; | |
| 60 | |
| 61 // Whether OmahaClient has already been checked for an update. | |
| 62 private boolean mAlreadyCheckedForUpdates; | |
| 63 | |
| 64 // Whether an update is available. | |
| 65 private boolean mUpdateAvailable; | |
| 66 | |
| 67 // URL to direct the user to when Omaha detects a newer version available. | |
| 68 private String mUpdateUrl; | |
| 69 | |
| 70 // Whether the menu item was clicked. This is used to log the click-through rate. | |
| 71 private boolean mMenuItemClicked; | |
| 72 | |
| 73 // The bitmap to use for the menu button when the badge is showing. | |
| 74 private Bitmap mBadgedMenuButtonBitmap; | |
| 75 | |
| 76 /** | |
| 77 * @return The {@link UpdateMenuItemHelper} instance. | |
| 78 */ | |
| 79 public static UpdateMenuItemHelper getInstance() { | |
| 80 if (sInstance == null) { | |
| 81 synchronized (UpdateMenuItemHelper.sCreateInstanceLock) { | |
|
gone
2015/12/10 21:45:10
you should put the synchronized on the outside of
Theresa
2015/12/11 19:44:46
Done.
| |
| 82 sInstance = new UpdateMenuItemHelper(); | |
| 83 String testMarketUrl = getStringParamValue(ChromeSwitches.MARKET _URL_FOR_TESTING); | |
| 84 if (!TextUtils.isEmpty(testMarketUrl)) { | |
| 85 sInstance.mUpdateUrl = testMarketUrl; | |
| 86 } | |
| 87 } | |
| 88 } | |
| 89 return sInstance; | |
| 90 } | |
| 91 | |
| 92 /** | |
| 93 * Checks if the {@link OmahaClient} knows about an update. | |
| 94 * @param activity The current {@link ChromeActivity}. | |
| 95 */ | |
| 96 public void checkForUpdateOnBackgroundThread(final ChromeActivity activity) { | |
| 97 if (!getBooleanParam(ENABLE_UPDATE_MENU_ITEM)) return; | |
| 98 | |
| 99 ThreadUtils.assertOnUiThread(); | |
| 100 | |
| 101 if (mAlreadyCheckedForUpdates) { | |
| 102 if (activity.isActivityDestroyed()) return; | |
| 103 activity.onCheckForUpdate(mUpdateAvailable); | |
| 104 return; | |
| 105 } | |
| 106 | |
| 107 mAlreadyCheckedForUpdates = true; | |
| 108 | |
| 109 new AsyncTask<Void, Void, Void>() { | |
| 110 @Override | |
| 111 protected Void doInBackground(Void... params) { | |
| 112 if (OmahaClient.isNewerVersionAvailable(activity)) { | |
| 113 mUpdateUrl = OmahaClient.getMarketURL(activity); | |
| 114 mUpdateAvailable = true; | |
| 115 } else { | |
| 116 mUpdateAvailable = false; | |
| 117 } | |
| 118 return null; | |
| 119 } | |
| 120 | |
| 121 @Override | |
| 122 protected void onPostExecute(Void result) { | |
| 123 if (activity.isActivityDestroyed()) return; | |
| 124 activity.onCheckForUpdate(mUpdateAvailable); | |
| 125 recordUpdateHistogram(); | |
| 126 } | |
| 127 }.execute(); | |
| 128 } | |
| 129 | |
| 130 /** | |
| 131 * Logs whether an update was performed if the update menu item was clicked. | |
| 132 * Should be called from ChromeActivity#onStart(). | |
| 133 */ | |
| 134 public void onStart() { | |
| 135 if (mAlreadyCheckedForUpdates) { | |
| 136 recordUpdateHistogram(); | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 /** | |
| 141 * @param activity The current {@link ChromeActivity}. | |
| 142 * @return Whether the update menu item should be shown. | |
| 143 */ | |
| 144 public boolean shouldShowMenuItem(ChromeActivity activity) { | |
| 145 if (getBooleanParam(ChromeSwitches.FORCE_SHOW_UPDATE_MENU_ITEM)) { | |
| 146 return true; | |
| 147 } | |
| 148 | |
| 149 if (!getBooleanParam(ENABLE_UPDATE_MENU_ITEM)) { | |
| 150 return false; | |
| 151 } | |
| 152 | |
| 153 return updateAvailable(activity); | |
| 154 } | |
| 155 | |
| 156 /** | |
| 157 * @param context The current {@link Context}. | |
| 158 * @return The string to use for summary text or the empty string if no summ ary should be shown. | |
| 159 */ | |
| 160 public String getMenuItemSummaryText(Context context) { | |
| 161 if (!getBooleanParam(SHOW_SUMMARY)) { | |
| 162 return ""; | |
| 163 } | |
| 164 | |
| 165 String customSummary = getStringParamValue(CUSTOM_SUMMARY); | |
| 166 if (!TextUtils.isEmpty(customSummary)) { | |
| 167 return customSummary; | |
| 168 } | |
| 169 | |
| 170 return context.getResources().getString(R.string.menu_update_summary_def ault); | |
| 171 } | |
| 172 | |
| 173 /** | |
| 174 * @param activity The current {@link ChromeActivity}. | |
| 175 * @return Whether the update badge should be shown in the toolbar. | |
| 176 */ | |
| 177 public boolean shouldShowToolbarBadge(ChromeActivity activity) { | |
| 178 if (getBooleanParam(ChromeSwitches.FORCE_SHOW_UPDATE_MENU_ITEM)) { | |
| 179 return true; | |
| 180 } | |
| 181 | |
| 182 if (!getBooleanParam(ENABLE_UPDATE_BADGE)) { | |
| 183 return false; | |
| 184 } | |
| 185 | |
| 186 return updateAvailable(activity); | |
| 187 } | |
| 188 | |
| 189 /** | |
| 190 * Handles a click on the update menu item. | |
| 191 * @param activity The current {@link ChromeActivity}. | |
| 192 */ | |
| 193 public void onMenuItemClicked(ChromeActivity activity) { | |
| 194 if (mUpdateUrl == null) return; | |
| 195 | |
| 196 // Fire an intent to open the URL. | |
| 197 try { | |
| 198 Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(mUpda teUrl)); | |
| 199 activity.startActivity(launchIntent); | |
| 200 recordItemClickedHistogram(ITEM_CLICKED_INTENT_LAUNCHED); | |
| 201 PrefServiceBridge.getInstance().setClickedUpdateMenuItem(true); | |
| 202 } catch (ActivityNotFoundException e) { | |
| 203 Log.e(TAG, "Failed to launch Activity for: " + mUpdateUrl); | |
| 204 recordItemClickedHistogram(ITEM_CLICKED_INTENT_FAILED); | |
| 205 } | |
| 206 } | |
| 207 | |
| 208 /** | |
| 209 * Returns a {@link Bitmap} to use for the menu button with part of the orig inal image removed | |
| 210 * to simulate a 1dp transparent border around the update badge. | |
| 211 * | |
| 212 * @param context The current {@link Context}. | |
| 213 * @return The {@link Bitmap} to use for the the menu button when showing th e update badge. | |
| 214 */ | |
| 215 public Bitmap getBadgedMenuButtonBitmap(Context context) { | |
| 216 if (mBadgedMenuButtonBitmap == null) { | |
| 217 // Punch a hole in the app menu button to create the illusion of a 1 dp transparent | |
| 218 // border around the update badge. | |
| 219 // Load btn_menu bitmap and use it to initialize a canvas. | |
| 220 BitmapFactory.Options opts = new BitmapFactory.Options(); | |
| 221 opts.inMutable = true; | |
| 222 mBadgedMenuButtonBitmap = BitmapFactory.decodeResource(context.getRe sources(), | |
| 223 R.drawable.btn_menu, opts); | |
| 224 Canvas canvas = new Canvas(); | |
| 225 canvas.setBitmap(mBadgedMenuButtonBitmap); | |
| 226 | |
| 227 // Calculate the dimensions and offsets for the update badge. | |
| 228 float menuBadgeBackgroundSize = context.getResources().getDimension( | |
| 229 R.dimen.menu_badge_background_size); | |
| 230 float radius = menuBadgeBackgroundSize / 2.f; | |
| 231 // The design specification calls for 4.5dp right offset and 9dp top offset. | |
| 232 float xPos = 4.5f * context.getResources().getDisplayMetrics().densi ty + radius; | |
| 233 float yPos = 9f * context.getResources().getDisplayMetrics().density + radius; | |
| 234 | |
| 235 if (LocalizationUtils.isLayoutRtl()) { | |
| 236 // In RTL layouts, the badge should be placed to the left of the menu button icon. | |
| 237 xPos = mBadgedMenuButtonBitmap.getWidth() - xPos; | |
| 238 } | |
| 239 | |
| 240 // Draw a transparent circle on top of the bitmap, creating a hole. | |
| 241 Paint paint = new Paint(); | |
| 242 paint.setFlags(Paint.ANTI_ALIAS_FLAG); | |
| 243 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); | |
| 244 canvas.drawCircle(xPos, yPos, radius, paint); | |
| 245 } | |
| 246 | |
| 247 return mBadgedMenuButtonBitmap; | |
| 248 } | |
| 249 | |
| 250 /** | |
| 251 * Should be called before the AppMenu is dismissed if the update menu item was clicked. | |
| 252 */ | |
| 253 public void setMenuItemClicked() { | |
| 254 mMenuItemClicked = true; | |
| 255 } | |
| 256 | |
| 257 /** | |
| 258 * Called when the {@link AppMenu} is dimissed. Logs a histogram immediately if the update menu | |
| 259 * item was not clicked. If it was clicked, logging is delayed until #onMenu ItemClicked(). | |
| 260 */ | |
| 261 public void onMenuDismissed() { | |
| 262 if (!mMenuItemClicked) { | |
| 263 recordItemClickedHistogram(ITEM_NOT_CLICKED); | |
| 264 } | |
| 265 mMenuItemClicked = false; | |
| 266 } | |
| 267 | |
| 268 private boolean updateAvailable(ChromeActivity activity) { | |
| 269 if (!mAlreadyCheckedForUpdates) { | |
| 270 checkForUpdateOnBackgroundThread(activity); | |
| 271 return false; | |
| 272 } | |
| 273 | |
| 274 return mUpdateAvailable; | |
| 275 } | |
| 276 | |
| 277 private void recordItemClickedHistogram(int action) { | |
| 278 RecordHistogram.recordEnumeratedHistogram("GoogleUpdate.MenuItem.ActionT akenOnMenuOpen", | |
| 279 action, ITEM_CLICKED_BOUNDARY); | |
| 280 } | |
| 281 | |
| 282 private void recordUpdateHistogram() { | |
| 283 if (PrefServiceBridge.getInstance().getClickedUpdateMenuItem()) { | |
| 284 RecordHistogram.recordEnumeratedHistogram( | |
| 285 "GoogleUpdate.MenuItem.ActionTakenAfterItemClicked", | |
| 286 mUpdateAvailable ? NOT_UPDATED : UPDATED, UPDATED_BOUNDARY); | |
| 287 PrefServiceBridge.getInstance().setClickedUpdateMenuItem(false); | |
| 288 } | |
| 289 } | |
| 290 | |
| 291 /** | |
| 292 * Gets a boolean VariationsAssociatedData parameter, assuming the <paramNam e>="true" format. | |
| 293 * Also checks for a command-line switch with the same name, for easy local testing. | |
| 294 * @param paramName The name of the parameter (or command-line switch) to ge t a value for. | |
| 295 * @return Whether the param is defined with a value "true", if there's a co mmand-line | |
| 296 * flag present with any value. | |
| 297 */ | |
| 298 private static boolean getBooleanParam(String paramName) { | |
| 299 if (CommandLine.getInstance().hasSwitch(paramName)) { | |
| 300 return true; | |
| 301 } | |
| 302 return TextUtils.equals(ENABLED_VALUE, | |
| 303 VariationsAssociatedData.getVariationParamValue(FIELD_TRIAL_NAME , paramName)); | |
| 304 } | |
| 305 | |
| 306 /** | |
| 307 * Gets a String VariationsAssociatedData parameter. Also checks for a comma nd-line switch with | |
| 308 * the same name, for easy local testing. | |
| 309 * @param paramName The name of the parameter (or command-line switch) to ge t a value for. | |
| 310 * @return The command-line flag value if present, or the param is value if present. | |
| 311 */ | |
| 312 private static String getStringParamValue(String paramName) { | |
| 313 String value = CommandLine.getInstance().getSwitchValue(paramName); | |
| 314 if (TextUtils.isEmpty(value)) { | |
| 315 value = VariationsAssociatedData.getVariationParamValue(FIELD_TRIAL_ NAME, paramName); | |
| 316 } | |
| 317 return value; | |
| 318 } | |
| 319 } | |
| OLD | NEW |