Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(457)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java

Issue 2081153005: [Offline Page] Offline page sharing implementation (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Grammar fix Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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 54de20eaae5d8078ced3a704d483a71bd0fff364..bb3df2ab5add2bdca89bc83287bec19f611672f9 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,19 +4,27 @@
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.FileUtils;
import org.chromium.base.Log;
+import org.chromium.base.StreamUtil;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram;
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 +37,11 @@ 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.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
import java.util.concurrent.TimeUnit;
/**
@@ -39,6 +52,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 DEFAULT_SNACKBAR_DURATION_MS = 6 * 1000; // 6 second
private static final long STORAGE_ALMOST_FULL_THRESHOLD_BYTES = 10L * (1 << 20); // 10M
@@ -48,6 +63,8 @@ public class OfflinePageUtils {
private static OfflinePageUtils sInstance;
+ private static File sOfflineSharingDirectory;
+
private static OfflinePageUtils getInstance() {
if (sInstance == null) {
sInstance = new OfflinePageUtils();
@@ -253,6 +270,172 @@ 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 saveLastUsed Whether to save the chosen activity for future direct sharing.
+ * @param mainActivity Activity that is used to access package manager.
+ * @param text Text to be shared. If both |text| and |url| are supplied, they are concatenated
+ * with a space.
+ * @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 callback Optional callback to be called when user makes a choice. Will not be called
+ * if receiving a response when the user makes a choice is not supported (see
+ * TargetChosenReceiver#isSupported()).
+ * @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 Tab currentTab) {
+ final String offlineUrl = currentTab.getUrl();
+ final String title = currentTab.getTitle();
+ OfflinePageBridge offlinePageBridge =
+ OfflinePageBridge.getForProfile(currentTab.getProfile());
+
+ if (offlinePageBridge == null) {
+ Log.e(TAG, "Unable to perform sharing on current tab.");
+ return;
+ }
+
+ offlinePageBridge.getPageByOfflineUrl(offlineUrl, new Callback<OfflinePageItem>() {
+ @Override
+ public void onResult(OfflinePageItem item) {
+ if (item == null) return;
+
+ String offlineFilePath = item.getFilePath();
+ prepareFileAndShare(shareDirectly, saveLastUsed, mainActivity, title, text,
+ onlineUrl, bitmap, callback, offlineFilePath);
+ }
+ });
+ }
+
+ private static void prepareFileAndShare(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) {
+ new AsyncTask<Void, Void, File>() {
+ @Override
+ protected File doInBackground(Void... params) {
+ File offlinePageOriginal = new File(filePath);
+ File shareableDir = getDirectoryForOfflineSharing(activity);
+
+ if (shareableDir == null) {
+ Log.e(TAG, "Unable to create subdirectory in shareable directory");
+ return null;
+ }
+
+ String fileName = rewriteOfflineFileName(offlinePageOriginal.getName());
+ File offlinePageShareable = new File(shareableDir, fileName);
+
+ if (offlinePageShareable.exists()) {
+ try {
+ // Old shareable files are stored in an external directory, which may cause
+ // problems when:
+ // 1. Files been changed by external sources.
+ // 2. Difference in file size that results in partial overwrite.
+ // Thus the file is deleted before we make a new copy.
+ offlinePageShareable.delete();
+ } catch (SecurityException e) {
+ Log.e(TAG, "Failed to delete: " + offlinePageOriginal.getName(), e);
+ return null;
+ }
+ }
+ if (copyToShareableLocation(offlinePageOriginal, offlinePageShareable)) {
+ return offlinePageShareable;
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(File offlinePageShareable) {
+ Uri offlineUri = null;
+ if (offlinePageShareable != null) {
+ offlineUri = Uri.fromFile(offlinePageShareable);
+ }
+ ShareHelper.share(shareDirectly, saveLastUsed, activity, title, text, onlineUrl,
+ offlineUri, bitmap, callback);
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+ }
+
+ /**
+ * Copies the file from internal storage to a sharable directory.
+ * @param src The original file to be copied.
+ * @param dst The destination file.
+ */
+ @VisibleForTesting
+ static boolean copyToShareableLocation(File src, File dst) {
+ FileInputStream inputStream = null;
+ FileOutputStream outputStream = null;
+
+ try {
+ inputStream = new FileInputStream(src);
+ outputStream = new FileOutputStream(dst);
+
+ FileChannel inChannel = inputStream.getChannel();
+ FileChannel outChannel = outputStream.getChannel();
+ inChannel.transferTo(0, inChannel.size(), outChannel);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to copy the file: " + src.getName(), e);
+ return false;
+ } finally {
+ StreamUtil.closeQuietly(inputStream);
+ StreamUtil.closeQuietly(outputStream);
+ }
+ return true;
+ }
+
+ /**
+ * Gets the directory to use for sharing offline pages, creating it if necessary.
+ * @param context Context that is used to access external cache directory.
+ * @return Path to the directory where shared files are stored.
+ */
+ @VisibleForTesting
+ static File getDirectoryForOfflineSharing(Context context) {
+ if (sOfflineSharingDirectory == null) {
+ sOfflineSharingDirectory =
+ new File(context.getExternalCacheDir(), EXTERNAL_MHTML_FILE_PATH);
+ }
+ if (!sOfflineSharingDirectory.exists() && !sOfflineSharingDirectory.mkdir()) {
+ sOfflineSharingDirectory = null;
+ }
+ return sOfflineSharingDirectory;
+ }
+
+ /**
+ * 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("\\.(?=.*\\.)", "_");
+ }
+
+ /**
+ * 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);
+ if (offlinePath != null) {
+ FileUtils.recursivelyDeleteFile(offlinePath);
+ }
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+ }
+
private static boolean isPowerConnected(Intent batteryStatus) {
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isConnected = (status == BatteryManager.BATTERY_STATUS_CHARGING

Powered by Google App Engine
This is Rietveld 408576698