summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/DownloadsIntegration.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/DownloadsIntegration.java')
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/DownloadsIntegration.java235
1 files changed, 235 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/DownloadsIntegration.java b/mobile/android/base/java/org/mozilla/gecko/DownloadsIntegration.java
new file mode 100644
index 000000000..ff3ac6110
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/DownloadsIntegration.java
@@ -0,0 +1,235 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.permissions.Permissions;
+import org.mozilla.gecko.util.NativeEventListener;
+import org.mozilla.gecko.util.NativeJSObject;
+import org.mozilla.gecko.util.EventCallback;
+
+import java.io.File;
+import java.lang.IllegalArgumentException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import android.app.DownloadManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.media.MediaScannerConnection;
+import android.media.MediaScannerConnection.MediaScannerConnectionClient;
+import android.net.Uri;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.Log;
+
+public class DownloadsIntegration implements NativeEventListener
+{
+ private static final String LOGTAG = "GeckoDownloadsIntegration";
+
+ private static final List<String> UNKNOWN_MIME_TYPES;
+ static {
+ final ArrayList<String> tempTypes = new ArrayList<>(3);
+ tempTypes.add("unknown/unknown"); // This will be used as a default mime type for unknown files
+ tempTypes.add("application/unknown");
+ tempTypes.add("application/octet-stream"); // Github uses this for APK files
+ UNKNOWN_MIME_TYPES = Collections.unmodifiableList(tempTypes);
+ }
+
+ private static final String DOWNLOAD_REMOVE = "Download:Remove";
+
+ private DownloadsIntegration() {
+ EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener)this, DOWNLOAD_REMOVE);
+ }
+
+ private static DownloadsIntegration sInstance;
+
+ private static class Download {
+ final File file;
+ final long id;
+
+ final private static int UNKNOWN_ID = -1;
+
+ public Download(final String path) {
+ this(path, UNKNOWN_ID);
+ }
+
+ public Download(final String path, final long id) {
+ file = new File(path);
+ this.id = id;
+ }
+
+ public static Download fromJSON(final NativeJSObject obj) {
+ final String path = obj.getString("path");
+ return new Download(path);
+ }
+
+ public static Download fromCursor(final Cursor c) {
+ final String path = c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_FILENAME));
+ final long id = c.getLong(c.getColumnIndexOrThrow(DownloadManager.COLUMN_ID));
+ return new Download(path, id);
+ }
+
+ public boolean equals(final Download download) {
+ return file.equals(download.file);
+ }
+ }
+
+ public static void init() {
+ if (sInstance == null) {
+ sInstance = new DownloadsIntegration();
+ }
+ }
+
+ @Override
+ public void handleMessage(final String event, final NativeJSObject message,
+ final EventCallback callback) {
+ if (DOWNLOAD_REMOVE.equals(event)) {
+ final Download d = Download.fromJSON(message);
+ removeDownload(d);
+ }
+ }
+
+ private static boolean useSystemDownloadManager() {
+ if (!AppConstants.ANDROID_DOWNLOADS_INTEGRATION) {
+ return false;
+ }
+
+ int state = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+ try {
+ state = GeckoAppShell.getContext().getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads");
+ } catch (IllegalArgumentException e) {
+ // Download Manager package does not exist
+ return false;
+ }
+
+ return (PackageManager.COMPONENT_ENABLED_STATE_ENABLED == state ||
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT == state);
+ }
+
+ @WrapForJNI(calledFrom = "gecko")
+ public static String getTemporaryDownloadDirectory() {
+ Context context = GeckoAppShell.getApplicationContext();
+
+ if (Permissions.has(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ // We do have the STORAGE permission, so we can save the file directly to the public
+ // downloads directory.
+ return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ .getAbsolutePath();
+ } else {
+ // Without the permission we are going to start to download the file to the cache
+ // directory. Later in the process we will ask for the permission and the download
+ // process will move the file to the actual downloads directory. If we do not get the
+ // permission then the download will be cancelled.
+ return context.getCacheDir().getAbsolutePath();
+ }
+ }
+
+
+ @WrapForJNI(calledFrom = "gecko")
+ public static void scanMedia(final String aFile, String aMimeType) {
+ String mimeType = aMimeType;
+ if (UNKNOWN_MIME_TYPES.contains(mimeType)) {
+ // If this is a generic undefined mimetype, erase it so that we can try to determine
+ // one from the file extension below.
+ mimeType = "";
+ }
+
+ // If the platform didn't give us a mimetype, try to guess one from the filename
+ if (TextUtils.isEmpty(mimeType)) {
+ final int extPosition = aFile.lastIndexOf(".");
+ if (extPosition > 0 && extPosition < aFile.length() - 1) {
+ mimeType = GeckoAppShell.getMimeTypeFromExtension(aFile.substring(extPosition + 1));
+ }
+ }
+
+ // addCompletedDownload will throw if it received any null parameters. Use aMimeType or a default
+ // if we still don't have one.
+ if (TextUtils.isEmpty(mimeType)) {
+ if (TextUtils.isEmpty(aMimeType)) {
+ mimeType = UNKNOWN_MIME_TYPES.get(0);
+ } else {
+ mimeType = aMimeType;
+ }
+ }
+
+ if (useSystemDownloadManager()) {
+ final File f = new File(aFile);
+ final DownloadManager dm = (DownloadManager) GeckoAppShell.getContext().getSystemService(Context.DOWNLOAD_SERVICE);
+ dm.addCompletedDownload(f.getName(),
+ f.getName(),
+ true, // Media scanner should scan this
+ mimeType,
+ f.getAbsolutePath(),
+ Math.max(1, f.length()), // Some versions of Android require downloads to be at least length 1
+ false); // Don't show a notification.
+ } else {
+ final Context context = GeckoAppShell.getContext();
+ final GeckoMediaScannerClient client = new GeckoMediaScannerClient(context, aFile, mimeType);
+ client.connect();
+ }
+ }
+
+ public static void removeDownload(final Download download) {
+ if (!useSystemDownloadManager()) {
+ return;
+ }
+
+ final DownloadManager dm = (DownloadManager) GeckoAppShell.getContext().getSystemService(Context.DOWNLOAD_SERVICE);
+
+ Cursor c = null;
+ try {
+ c = dm.query((new DownloadManager.Query()).setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL));
+ if (c == null || !c.moveToFirst()) {
+ return;
+ }
+
+ do {
+ final Download d = Download.fromCursor(c);
+ // Try hard as we can to verify this download is the one we think it is
+ if (download.equals(d)) {
+ dm.remove(d.id);
+ }
+ } while (c.moveToNext());
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ }
+
+ private static final class GeckoMediaScannerClient implements MediaScannerConnectionClient {
+ private final String mFile;
+ private final String mMimeType;
+ private MediaScannerConnection mScanner;
+
+ public GeckoMediaScannerClient(Context context, String file, String mimeType) {
+ mFile = file;
+ mMimeType = mimeType;
+ mScanner = new MediaScannerConnection(context, this);
+ }
+
+ public void connect() {
+ mScanner.connect();
+ }
+
+ @Override
+ public void onMediaScannerConnected() {
+ mScanner.scanFile(mFile, mMimeType);
+ }
+
+ @Override
+ public void onScanCompleted(String path, Uri uri) {
+ if (path.equals(mFile)) {
+ mScanner.disconnect();
+ mScanner = null;
+ }
+ }
+ }
+}