Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.offlinepages; | 5 package org.chromium.chrome.browser.offlinepages; |
| 6 | 6 |
| 7 import android.app.Activity; | |
| 7 import android.content.Context; | 8 import android.content.Context; |
| 8 import android.content.Intent; | 9 import android.content.Intent; |
| 9 import android.content.IntentFilter; | 10 import android.content.IntentFilter; |
| 11 import android.graphics.Bitmap; | |
| 12 import android.net.Uri; | |
| 13 import android.os.AsyncTask; | |
| 10 import android.os.BatteryManager; | 14 import android.os.BatteryManager; |
| 11 import android.os.Environment; | 15 import android.os.Environment; |
| 12 | 16 |
| 17 import org.chromium.base.Callback; | |
| 18 import org.chromium.base.FileUtils; | |
| 13 import org.chromium.base.Log; | 19 import org.chromium.base.Log; |
| 20 import org.chromium.base.StreamUtil; | |
| 14 import org.chromium.base.VisibleForTesting; | 21 import org.chromium.base.VisibleForTesting; |
| 15 import org.chromium.base.metrics.RecordHistogram; | 22 import org.chromium.base.metrics.RecordHistogram; |
| 16 import org.chromium.base.metrics.RecordUserAction; | 23 import org.chromium.base.metrics.RecordUserAction; |
| 17 import org.chromium.chrome.R; | 24 import org.chromium.chrome.R; |
| 18 import org.chromium.chrome.browser.ChromeActivity; | 25 import org.chromium.chrome.browser.ChromeActivity; |
| 19 import org.chromium.chrome.browser.profiles.Profile; | 26 import org.chromium.chrome.browser.profiles.Profile; |
| 27 import org.chromium.chrome.browser.share.ShareHelper; | |
| 20 import org.chromium.chrome.browser.snackbar.Snackbar; | 28 import org.chromium.chrome.browser.snackbar.Snackbar; |
| 21 import org.chromium.chrome.browser.snackbar.SnackbarManager; | 29 import org.chromium.chrome.browser.snackbar.SnackbarManager; |
| 22 import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarController; | 30 import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarController; |
| 23 import org.chromium.chrome.browser.tab.Tab; | 31 import org.chromium.chrome.browser.tab.Tab; |
| 24 import org.chromium.chrome.browser.tabmodel.TabModelSelector; | 32 import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| 25 import org.chromium.components.bookmarks.BookmarkId; | 33 import org.chromium.components.bookmarks.BookmarkId; |
| 26 import org.chromium.content_public.browser.LoadUrlParams; | 34 import org.chromium.content_public.browser.LoadUrlParams; |
| 27 import org.chromium.content_public.browser.WebContents; | 35 import org.chromium.content_public.browser.WebContents; |
| 28 import org.chromium.net.ConnectionType; | 36 import org.chromium.net.ConnectionType; |
| 29 import org.chromium.net.NetworkChangeNotifier; | 37 import org.chromium.net.NetworkChangeNotifier; |
| 30 import org.chromium.ui.base.PageTransition; | 38 import org.chromium.ui.base.PageTransition; |
| 31 | 39 |
| 40 import java.io.File; | |
| 41 import java.io.FileInputStream; | |
| 42 import java.io.FileNotFoundException; | |
| 43 import java.io.FileOutputStream; | |
| 44 import java.io.IOException; | |
| 45 import java.nio.channels.FileChannel; | |
| 32 import java.util.concurrent.TimeUnit; | 46 import java.util.concurrent.TimeUnit; |
| 33 | 47 |
| 34 /** | 48 /** |
| 35 * A class holding static util functions for offline pages. | 49 * A class holding static util functions for offline pages. |
| 36 */ | 50 */ |
| 37 public class OfflinePageUtils { | 51 public class OfflinePageUtils { |
| 38 private static final String TAG = "OfflinePageUtils"; | 52 private static final String TAG = "OfflinePageUtils"; |
| 39 /** Background task tag to differentiate from other task types */ | 53 /** Background task tag to differentiate from other task types */ |
| 40 public static final String TASK_TAG = "OfflinePageUtils"; | 54 public static final String TASK_TAG = "OfflinePageUtils"; |
| 41 | 55 |
| 56 public static final String EXTERNAL_MHTML_FILE_PATH = "offline-pages"; | |
| 57 | |
| 42 private static final int DEFAULT_SNACKBAR_DURATION_MS = 6 * 1000; // 6 secon d | 58 private static final int DEFAULT_SNACKBAR_DURATION_MS = 6 * 1000; // 6 secon d |
| 43 | 59 |
| 44 private static final long STORAGE_ALMOST_FULL_THRESHOLD_BYTES = 10L * (1 << 20); // 10M | 60 private static final long STORAGE_ALMOST_FULL_THRESHOLD_BYTES = 10L * (1 << 20); // 10M |
| 45 | 61 |
| 46 // Used instead of the constant so tests can override the value. | 62 // Used instead of the constant so tests can override the value. |
| 47 private static int sSnackbarDurationMs = DEFAULT_SNACKBAR_DURATION_MS; | 63 private static int sSnackbarDurationMs = DEFAULT_SNACKBAR_DURATION_MS; |
| 48 | 64 |
| 49 private static OfflinePageUtils sInstance; | 65 private static OfflinePageUtils sInstance; |
| 50 | 66 |
| 51 private static OfflinePageUtils getInstance() { | 67 private static OfflinePageUtils getInstance() { |
| (...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 246 long delayInMilliseconds = nowMillis - taskScheduledTimeMillis; | 262 long delayInMilliseconds = nowMillis - taskScheduledTimeMillis; |
| 247 if (delayInMilliseconds <= 0) { | 263 if (delayInMilliseconds <= 0) { |
| 248 return; | 264 return; |
| 249 } | 265 } |
| 250 RecordHistogram.recordLongTimesHistogram( | 266 RecordHistogram.recordLongTimesHistogram( |
| 251 "OfflinePages.Wakeup.DelayTime", | 267 "OfflinePages.Wakeup.DelayTime", |
| 252 delayInMilliseconds, | 268 delayInMilliseconds, |
| 253 TimeUnit.MILLISECONDS); | 269 TimeUnit.MILLISECONDS); |
| 254 } | 270 } |
| 255 | 271 |
| 272 /** | |
| 273 * Share saved offline page. | |
| 274 * @param shareDirectly Whether it should share directly with the activity t hat was most | |
| 275 * recently used to share. | |
| 276 * @param mainActivity Activity that is used to access package manager | |
|
Theresa
2016/08/15 20:50:00
nit: missing period at the end of this line
"...
Vivian
2016/08/16 17:40:12
Done.
| |
| 277 * @param onlineUrl Online URL associated with the offline page that is used to access the | |
| 278 * offline page file path. | |
| 279 * @param bitmap Screenshot of the page to be shared. | |
| 280 * @param currentTab Tab that is used to access offlineUrl and tile. | |
| 281 */ | |
| 282 public static void shareOfflinePage(final boolean shareDirectly, final boole an saveLastUsed, | |
|
Theresa
2016/08/15 20:50:00
The JavaDoc is missing some params - saveLastUsed,
Vivian
2016/08/16 17:40:11
Done.
| |
| 283 final Activity mainActivity, final String text, final String onlineU rl, | |
| 284 final Bitmap bitmap, final ShareHelper.TargetChosenCallback callback , | |
| 285 final Tab currentTab) { | |
| 286 final String offlineUrl = currentTab.getUrl(); | |
| 287 final String title = currentTab.getTitle(); | |
| 288 OfflinePageBridge offlinePageBridge = | |
| 289 OfflinePageBridge.getForProfile(currentTab.getProfile()); | |
| 290 | |
| 291 if (offlinePageBridge != null) { | |
| 292 Log.e(TAG, "Unable to perform sharing on current tab."); | |
| 293 return; | |
| 294 } | |
| 295 | |
| 296 offlinePageBridge.getPageByOfflineUrl(offlineUrl, new Callback<OfflinePa geItem>() { | |
| 297 @Override | |
| 298 public void onResult(OfflinePageItem item) { | |
| 299 if (item == null) return; | |
| 300 | |
| 301 String offlineFilePath = item.getFilePath(); | |
| 302 prepareForSharing(shareDirectly, saveLastUsed, mainActivity, tit le, text, onlineUrl, | |
| 303 bitmap, callback, offlineFilePath); | |
| 304 } | |
| 305 }); | |
| 306 } | |
| 307 | |
| 308 private static void prepareForSharing(final boolean shareDirectly, final boo lean saveLastUsed, | |
| 309 final Activity activity, final String title, final String text, fina l String onlineUrl, | |
| 310 final Bitmap bitmap, final ShareHelper.TargetChosenCallback callback , | |
| 311 final String filePath) { | |
| 312 new AsyncTask<Void, Void, File>() { | |
| 313 @Override | |
| 314 protected File doInBackground(Void... params) { | |
| 315 File offlinePageOriginal = new File(filePath); | |
| 316 File shareableDir = getDirectoryForOfflineSharing(activity); | |
| 317 if (shareableDir != null) { | |
|
Theresa
2016/08/15 20:50:00
early exit here too:
if (shareableDir == null) {
Vivian
2016/08/16 17:40:11
Done.
| |
| 318 String fileName = rewriteOfflineFileName(offlinePageOriginal .getName()); | |
| 319 File offlinePageShareable = new File(shareableDir, fileName) ; | |
| 320 | |
| 321 if (offlinePageShareable.exists()) { | |
|
Theresa
2016/08/15 20:49:59
Please add a comment explaining why the old offlin
Vivian
2016/08/16 17:40:11
Done.
| |
| 322 try { | |
| 323 offlinePageShareable.delete(); | |
| 324 } catch (SecurityException e) { | |
| 325 Log.e(TAG, "Failed to delete: " + offlinePageOrigina l.getName(), e); | |
|
Theresa
2016/08/15 20:50:00
If we fail to delete the old shareable file, what
Vivian
2016/08/16 17:40:12
Good question!
It will overwrite the some of the e
Theresa
2016/08/16 18:47:20
Sounds good, thanks!
| |
| 326 } | |
| 327 } | |
| 328 if (copyToShareableLocation(offlinePageOriginal, offlinePage Shareable)) { | |
| 329 return offlinePageShareable; | |
| 330 } | |
| 331 } else { | |
| 332 Log.e(TAG, "Unable to create subdirectory in shareable direc tory"); | |
| 333 } | |
| 334 return null; | |
| 335 } | |
| 336 | |
| 337 @Override | |
| 338 protected void onPostExecute(File offlinePageShareable) { | |
| 339 Uri offlineUri = null; | |
|
Theresa
2016/08/13 15:58:48
Eventually we'll want to be able to share the mhtm
Theresa
2016/08/15 20:50:00
I think we can skip this for now.
Vivian
2016/08/16 17:40:11
Acknowledged.
| |
| 340 if (offlinePageShareable != null) { | |
| 341 offlineUri = Uri.fromFile(offlinePageShareable); | |
| 342 } | |
| 343 ShareHelper.share(shareDirectly, saveLastUsed, activity, title, text, onlineUrl, | |
| 344 offlineUri, bitmap, callback); | |
| 345 } | |
| 346 }.execute(); | |
|
Theresa
2016/08/15 20:50:00
Add AsyncTask.SERIAL_EXECUTOR here. Not specifying
Vivian
2016/08/16 17:40:11
Done.
| |
| 347 } | |
| 348 | |
| 349 /** | |
| 350 * This method copies the file from internal storage to a sharable directory . | |
| 351 * @param src file path of the original file to be copied | |
|
Theresa
2016/08/15 20:50:00
nits:
remove "This method"
capitalize the first w
Vivian
2016/08/16 17:40:11
Done.
| |
| 352 * @param dst file path of the destination | |
| 353 */ | |
| 354 @VisibleForTesting | |
| 355 static boolean copyToShareableLocation(File src, File dst) { | |
| 356 FileInputStream inputStream = null; | |
| 357 FileOutputStream outputStream = null; | |
| 358 | |
| 359 try { | |
| 360 inputStream = new FileInputStream(src); | |
| 361 outputStream = new FileOutputStream(dst); | |
| 362 | |
| 363 FileChannel inChannel = inputStream.getChannel(); | |
| 364 FileChannel outChannel = outputStream.getChannel(); | |
| 365 inChannel.transferTo(0, inChannel.size(), outChannel); | |
| 366 } catch (FileNotFoundException e) { | |
| 367 Log.e(TAG, "Failed to copy the file: " + src.getName(), e); | |
| 368 return false; | |
| 369 } catch (IOException e) { | |
|
Theresa
2016/08/15 20:50:00
This can be combined with the catch above:
...
}
Vivian
2016/08/16 17:40:12
Done.
| |
| 370 Log.e(TAG, "Failed to copy the file: " + src.getName(), e); | |
| 371 return false; | |
| 372 } finally { | |
| 373 StreamUtil.closeQuietly(inputStream); | |
| 374 StreamUtil.closeQuietly(outputStream); | |
| 375 } | |
| 376 return true; | |
| 377 } | |
| 378 | |
| 379 /** | |
| 380 * Get a directory for offline page sharing operation. | |
|
Theresa
2016/08/15 20:50:00
nit: "Gets the directory to use for sharing offlin
Vivian
2016/08/16 17:40:12
Done.
| |
| 381 * @param context Context that is used to access external cache directory. | |
| 382 * @return path to directory for the shared file to be stored | |
|
Theresa
2016/08/15 20:50:00
nit: capitalize Path
Vivian
2016/08/16 17:40:12
Done.
| |
| 383 */ | |
| 384 @VisibleForTesting | |
| 385 static File getDirectoryForOfflineSharing(Context context) { | |
| 386 File path = new File(context.getExternalCacheDir(), EXTERNAL_MHTML_FILE_ PATH); | |
|
Theresa
2016/08/15 20:49:59
I think we should create a static variable that ca
Theresa
2016/08/16 03:00:10
*sOfflineSharingDirectory (since it'd be static)
Vivian
2016/08/16 17:40:12
Done.
| |
| 387 if (!path.exists() && !path.mkdir()) { | |
| 388 path = null; | |
| 389 } | |
| 390 return path; | |
| 391 } | |
| 392 | |
| 393 /** | |
| 394 * Rewrite file name so that it does not contain periods except the one to s eparate the file | |
| 395 * extension. | |
| 396 * This step is used to ensure that file name can be recognized by intent fi lter (.*\\.mhtml"). | |
|
Theresa
2016/08/15 20:50:00
nit: "... (.*\\.mhtml) as Android's path..."
Vivian
2016/08/16 17:40:12
Done.
| |
| 397 * As Android's path pattern only matches the first dot that appears in a fi le path. | |
| 398 * @pram fileName Name of the offline page file. | |
| 399 */ | |
| 400 @VisibleForTesting | |
| 401 static String rewriteOfflineFileName(String fileName) { | |
| 402 fileName = fileName.replaceAll("\\s+", ""); | |
| 403 return fileName.replaceAll("\\.(?=.*\\.)", "_"); | |
| 404 } | |
| 405 | |
| 406 /** | |
| 407 * Clears all shared mhtml files. | |
| 408 * @param context Context that is used to access external cache directory. | |
| 409 */ | |
| 410 public static void clearSharedOfflineFiles(final Context context) { | |
| 411 new AsyncTask<Void, Void, Void>() { | |
| 412 @Override | |
| 413 protected Void doInBackground(Void... params) { | |
| 414 File offlinePath = getDirectoryForOfflineSharing(context); | |
| 415 FileUtils.recursivelyDeleteFile(offlinePath); | |
| 416 return null; | |
| 417 } | |
| 418 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | |
|
Theresa
2016/08/15 20:50:00
This should be done on the SERIAL_EXECUTOR so that
Vivian
2016/08/16 17:40:11
Done.
| |
| 419 } | |
| 420 | |
| 256 private static boolean isPowerConnected(Intent batteryStatus) { | 421 private static boolean isPowerConnected(Intent batteryStatus) { |
| 257 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); | 422 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); |
| 258 boolean isConnected = (status == BatteryManager.BATTERY_STATUS_CHARGING | 423 boolean isConnected = (status == BatteryManager.BATTERY_STATUS_CHARGING |
| 259 || status == BatteryManager.BATTERY_STATUS_FULL); | 424 || status == BatteryManager.BATTERY_STATUS_FULL); |
| 260 Log.d(TAG, "Power connected is " + isConnected); | 425 Log.d(TAG, "Power connected is " + isConnected); |
| 261 return isConnected; | 426 return isConnected; |
| 262 } | 427 } |
| 263 | 428 |
| 264 private static int batteryPercentage(Intent batteryStatus) { | 429 private static int batteryPercentage(Intent batteryStatus) { |
| 265 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); | 430 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 291 @VisibleForTesting | 456 @VisibleForTesting |
| 292 static void setInstanceForTesting(OfflinePageUtils instance) { | 457 static void setInstanceForTesting(OfflinePageUtils instance) { |
| 293 sInstance = instance; | 458 sInstance = instance; |
| 294 } | 459 } |
| 295 | 460 |
| 296 @VisibleForTesting | 461 @VisibleForTesting |
| 297 public static void setSnackbarDurationForTesting(int durationMs) { | 462 public static void setSnackbarDurationForTesting(int durationMs) { |
| 298 sSnackbarDurationMs = durationMs; | 463 sSnackbarDurationMs = durationMs; |
| 299 } | 464 } |
| 300 } | 465 } |
| OLD | NEW |