Chromium Code Reviews| Index: chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java |
| index 106027043db0a10c3070b964590dfbeb35bdde21..c5c9eee5995b132a31517a0c1223be235692117b 100644 |
| --- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java |
| @@ -4,12 +4,17 @@ |
| package org.chromium.chrome.browser.offlinepages; |
| +import android.app.Activity; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| +import android.graphics.Bitmap; |
| +import android.net.Uri; |
| +import android.os.AsyncTask; |
| import android.os.BatteryManager; |
| import android.os.Environment; |
| +import org.chromium.base.Callback; |
| import org.chromium.base.Log; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.base.metrics.RecordHistogram; |
| @@ -17,6 +22,7 @@ import org.chromium.base.metrics.RecordUserAction; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.ChromeActivity; |
| import org.chromium.chrome.browser.profiles.Profile; |
| +import org.chromium.chrome.browser.share.ShareHelper; |
| import org.chromium.chrome.browser.snackbar.Snackbar; |
| import org.chromium.chrome.browser.snackbar.SnackbarManager; |
| import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarController; |
| @@ -29,6 +35,12 @@ import org.chromium.net.ConnectionType; |
| import org.chromium.net.NetworkChangeNotifier; |
| import org.chromium.ui.base.PageTransition; |
| +import java.io.File; |
| +import java.io.FileInputStream; |
| +import java.io.FileNotFoundException; |
| +import java.io.FileOutputStream; |
| +import java.io.IOException; |
| +import java.nio.channels.FileChannel; |
| import java.util.concurrent.TimeUnit; |
| /** |
| @@ -39,6 +51,8 @@ public class OfflinePageUtils { |
| /** Background task tag to differentiate from other task types */ |
| public static final String TASK_TAG = "OfflinePageUtils"; |
| + public static final String EXTERNAL_MHTML_FILE_PATH = "offline-pages"; |
| + |
| private static final int SNACKBAR_DURATION = 6 * 1000; // 6 second |
| private static final long STORAGE_ALMOST_FULL_THRESHOLD_BYTES = 10L * (1 << 20); // 10M |
| @@ -250,6 +264,163 @@ public class OfflinePageUtils { |
| TimeUnit.MILLISECONDS); |
| } |
| + /** |
| + * Share saved offline page. |
| + * @param shareDirectly Whether it should share directly with the activity that was most |
| + * recently used to share. |
| + * @param mainActivity Activity that is used to access package manager |
| + * @param onlineUrl Online URL associated with the offline page that is used to access the |
| + * offline page file path. |
| + * @param bitmap Screenshot of the page to be shared. |
| + * @param mContext The application context. |
| + * @param currentTab Tab that is used to access offlineUrl and tile. |
| + */ |
| + public static void shareOfflinePage(final boolean shareDirectly, final boolean saveLastUsed, |
| + final Activity mainActivity, final String text, final String onlineUrl, |
| + final Bitmap bitmap, final ShareHelper.TargetChosenCallback callback, |
| + final Context mContext, final Tab currentTab) { |
| + final String offlineUrl = currentTab.getUrl(); |
| + final String title = currentTab.getTitle(); |
| + OfflinePageBridge offlinePageBridge = |
| + OfflinePageBridge.getForProfile(currentTab.getProfile()); |
| + if (offlinePageBridge != null) { |
|
gone
2016/08/12 01:26:12
Avoid indentation by early exiting:
if (offlinePa
Vivian
2016/08/12 23:07:12
Done.
|
| + offlinePageBridge.getPageByOfflineUrl(offlineUrl, new Callback<OfflinePageItem>() { |
| + @Override |
| + public void onResult(OfflinePageItem item) { |
|
gone
2016/08/12 01:26:13
nit: When indentation gets deep in simple function
Vivian
2016/08/12 23:07:12
Done.
|
| + if (item != null) { |
| + String offlineFilePath = item.getFilePath(); |
| + prepareForSharing(shareDirectly, saveLastUsed, mainActivity, title, text, |
| + onlineUrl, bitmap, callback, offlineFilePath, mContext); |
| + } |
| + } |
| + }); |
| + } else { |
| + Log.e(TAG, "Unable to perform sharing on current tab."); |
| + } |
| + } |
| + |
| + private static void prepareForSharing(final boolean shareDirectly, final boolean saveLastUsed, |
| + final Activity activity, final String title, final String text, final String onlineUrl, |
| + final Bitmap bitmap, final ShareHelper.TargetChosenCallback callback, |
| + final String filePath, final Context context) { |
| + new AsyncTask<Void, Void, File>() { |
| + @Override |
| + protected File doInBackground(Void... params) { |
| + File offlinePageOriginal = new File(filePath); |
| + File shareableDir = getDirectoryForOfflineSharing(context); |
| + if (shareableDir != null) { |
| + String fileName = rewriteOfflineFileName(offlinePageOriginal.getName()); |
| + File offlinePageShareable = new File(shareableDir, fileName); |
|
Theresa
2016/08/12 17:36:44
Do we really need to rewrite the offline page file
Vivian
2016/08/12 23:07:12
This was my initial approach. The problem is, we w
Theresa
2016/08/13 15:58:48
That really surprises me. The Android documentatio
Theresa
2016/08/13 16:05:55
To expand a little more on why I'm pushing on this
|
| + |
| + if (offlinePageShareable.exists()) { |
| + try { |
| + offlinePageShareable.delete(); |
| + } catch (SecurityException e) { |
| + Log.e(TAG, |
| + "failed to delete the file: " + offlinePageOriginal.getName()); |
|
gone
2016/08/12 01:26:12
nit: Might be worth printing the exception:
Log.e
Vivian
2016/08/12 23:07:12
Done.
|
| + } |
| + } |
| + if (copyToShareableLocation(offlinePageOriginal, offlinePageShareable)) { |
| + return offlinePageShareable; |
| + } |
| + } else { |
| + Log.e(TAG, "Unable to create subdirectory in shareable directory"); |
| + } |
| + return null; |
| + } |
| + |
| + @Override |
| + protected void onPostExecute(File offlinePageShareable) { |
| + if (offlinePageShareable == null) return; |
| + Uri offlineUri = Uri.fromFile(offlinePageShareable); |
| + ShareHelper.share(shareDirectly, saveLastUsed, activity, title, text, onlineUrl, |
| + offlineUri, bitmap, callback); |
|
gone
2016/08/12 01:26:12
If sharing the offline page fails, you should fall
Vivian
2016/08/12 23:07:12
Done.
|
| + } |
| + }.execute(); |
| + } |
| + |
| + /** |
| + * This method copies the file from internal storage to a sharable directory. |
| + * @param src file path of the original file to be copied |
| + * @param dst file path of the destination |
| + */ |
| + @VisibleForTesting |
| + static boolean copyToShareableLocation(File src, File dst) { |
| + try { |
| + FileChannel inChannel = new FileInputStream(src).getChannel(); |
| + FileChannel outChannel = new FileOutputStream(dst).getChannel(); |
| + |
| + inChannel.transferTo(0, inChannel.size(), outChannel); |
| + |
| + if (inChannel != null) inChannel.close(); |
| + if (outChannel != null) outChannel.close(); |
|
gone
2016/08/12 01:26:12
1) Log.e takes an Exception as the third argument,
Vivian
2016/08/12 23:07:12
Done.
|
| + } catch (FileNotFoundException e) { |
| + Log.e(TAG, "failed to copy the file: " + src.getName() + ", " + e); |
| + return false; |
| + } catch (IOException e) { |
| + Log.e(TAG, "failed to copy the file: " + src.getName() + ", " + e); |
| + return false; |
| + } |
| + return true; |
| + } |
| + |
| + /** |
| + * Get a directory for offline page sharing operation. |
| + * @param context Context that is used to access external cache directory. |
| + * @return path to directory for the shared file to be stored |
| + */ |
| + private static File getDirectoryForOfflineSharing(Context context) { |
| + File path = new File(context.getExternalCacheDir(), EXTERNAL_MHTML_FILE_PATH); |
| + boolean success = true; |
|
gone
2016/08/12 01:26:13
Useless variable?
Vivian
2016/08/12 23:07:12
Yes. Nice catch! Deleted it.
|
| + if (!path.exists() && !path.mkdir()) { |
| + path = null; |
| + } |
| + return path; |
| + } |
| + |
| + /** |
| + * Rewrite file name so that it does not contain periods except the one to separate the file |
| + * extension. |
| + * This step is used to ensure that file name can be recognized by intent filter (.*\\.mhtml"). |
| + * As Android's path pattern only matches the first dot that appears in a file path. |
| + * @pram fileName Name of the offline page file. |
| + */ |
| + @VisibleForTesting |
| + static String rewriteOfflineFileName(String fileName) { |
| + fileName = fileName.replaceAll("\\s+", ""); |
| + return fileName.replaceAll("\\.(?=.*\\.)", "_"); |
| + } |
| + |
| + /** |
| + * Delete a shared mhtml file. If the file path is a directory, delete all files in directory. |
| + * @param file File object of the offline page. |
| + */ |
| + @VisibleForTesting |
| + static void deleteSharedOfflineFiles(File file) { |
|
gone
2016/08/12 01:26:13
Just use FileUtils.recursivelyDeleteFile(File file
Vivian
2016/08/12 23:07:12
Done.
|
| + if (!file.exists()) return; |
| + if (file.isDirectory()) { |
| + for (File f : file.listFiles()) deleteSharedOfflineFiles(f); |
| + } |
| + if (!file.delete()) { |
| + Log.w(TAG, "Failed to delete shared offline file: %s", file.getAbsolutePath()); |
| + } |
| + } |
| + |
| + /** |
| + * Clears all shared mhtml files. |
| + * @param context Context that is used to access external cache directory. |
| + */ |
| + public static void clearSharedOfflineFiles(final Context context) { |
| + new AsyncTask<Void, Void, Void>() { |
| + @Override |
| + protected Void doInBackground(Void... params) { |
| + File offlinePath = getDirectoryForOfflineSharing(context); |
| + deleteSharedOfflineFiles(offlinePath); |
| + return null; |
| + } |
| + }.execute(); |
|
gone
2016/08/12 01:26:12
AsyncTask#execute() does different things, dependi
Vivian
2016/08/12 23:07:12
Done.
|
| + } |
| + |
| private static boolean isPowerConnected(Intent batteryStatus) { |
| int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); |
| boolean isConnected = (status == BatteryManager.BATTERY_STATUS_CHARGING |