Extend trampoline to show dialogs.
Jeff Sharkey [Fri, 6 Sep 2013 00:13:59 +0000 (17:13 -0700)]
Handle incoming manage requests by launching finished downloads,
or showing various retry dialogs.  Pipe through summary, show
percentage when in progress, and always show total size and MIME type.

Bug: 10531347, 10599641
Change-Id: I3be2bc67ea3c0ef795146177200f5be77ad5114e

AndroidManifest.xml
res/values/strings.xml
src/com/android/providers/downloads/Constants.java
src/com/android/providers/downloads/DownloadStorageProvider.java
src/com/android/providers/downloads/TrampolineActivity.java [deleted file]
ui/AndroidManifest.xml
ui/src/com/android/providers/downloads/ui/DownloadList.java
ui/src/com/android/providers/downloads/ui/TrampolineActivity.java [new file with mode: 0644]

index 398f8f4..49f039e 100644 (file)
         <activity android:name=".SizeLimitActivity"
                   android:launchMode="singleTask"
                   android:theme="@style/Theme.Translucent"/>
-
-        <!-- PackageInstaller really wants raw files -->
-        <activity
-            android:name=".TrampolineActivity"
-            android:theme="@android:style/Theme.NoDisplay"
-            android:permission="android.permission.MANAGE_DOCUMENTS">
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data
-                    android:scheme="content"
-                    android:host="com.android.providers.downloads.documents"
-                    android:mimeType="application/vnd.android.package-archive" />
-            </intent-filter>
-        </activity>
     </application>
 </manifest>
index 620e3fa..c571219 100644 (file)
          an individual item in the download list.  [CHAR LIMIT=24] -->
     <string name="download_error">Unsuccessful</string>
 
+    <!-- Representation of download progress percentage when running. [CHAR LIMIT=24] -->
+    <string name="download_running_percent">In progress, <xliff:g id="number">%d</xliff:g><xliff:g id="percent">%%</xliff:g></string>
+
 </resources>
index e33a636..89210a2 100644 (file)
@@ -176,5 +176,5 @@ public class Constants {
     public static final boolean LOGVV = LOCAL_LOGVV && LOGV;
 
     public static final String STORAGE_AUTHORITY = "com.android.providers.downloads.documents";
-    public static final String STORAGE_DOC_ID_ROOT = "downloads";
+    public static final String STORAGE_ROOT_ID = "downloads";
 }
index 04cbf09..f1cd8fa 100644 (file)
@@ -41,7 +41,7 @@ import java.io.FileNotFoundException;
  * contents.
  */
 public class DownloadStorageProvider extends DocumentsProvider {
-    private static final String DOC_ID_ROOT = Constants.STORAGE_DOC_ID_ROOT;
+    private static final String DOC_ID_ROOT = Constants.STORAGE_ROOT_ID;
 
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
             Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
@@ -51,7 +51,8 @@ public class DownloadStorageProvider extends DocumentsProvider {
 
     private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
             Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
-            Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
+            Document.COLUMN_SUMMARY, Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS,
+            Document.COLUMN_SIZE,
     };
 
     private DownloadManager mDm;
@@ -214,33 +215,35 @@ public class DownloadStorageProvider extends DocumentsProvider {
         if (mimeType == null) {
             mimeType = "application/octet-stream";
         }
-        Long size = null;
+        Long size = cursor.getLong(
+                cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
+        if (size == -1) {
+            size = null;
+        }
 
         final int status = cursor.getInt(
                 cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
         switch (status) {
             case DownloadManager.STATUS_SUCCESSFUL:
-                size = cursor.getLong(
-                        cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
-                if (size == -1) {
-                    size = null;
-                }
                 break;
             case DownloadManager.STATUS_PAUSED:
-                mimeType = null;
                 summary = getContext().getString(R.string.download_queued);
                 break;
             case DownloadManager.STATUS_PENDING:
-                mimeType = null;
                 summary = getContext().getString(R.string.download_queued);
                 break;
             case DownloadManager.STATUS_RUNNING:
-                mimeType = null;
-                summary = getContext().getString(R.string.download_running);
+                final long progress = cursor.getLong(cursor.getColumnIndexOrThrow(
+                        DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
+                if (size != null) {
+                    final long percent = progress * 100 / size;
+                    summary = getContext().getString(R.string.download_running_percent, percent);
+                } else {
+                    summary = getContext().getString(R.string.download_running);
+                }
                 break;
             case DownloadManager.STATUS_FAILED:
             default:
-                mimeType = null;
                 summary = getContext().getString(R.string.download_error);
                 break;
         }
@@ -256,6 +259,7 @@ public class DownloadStorageProvider extends DocumentsProvider {
         final RowBuilder row = result.newRow();
         row.offer(Document.COLUMN_DOCUMENT_ID, docId);
         row.offer(Document.COLUMN_DISPLAY_NAME, displayName);
+        row.offer(Document.COLUMN_SUMMARY, summary);
         row.offer(Document.COLUMN_SIZE, size);
         row.offer(Document.COLUMN_MIME_TYPE, mimeType);
         row.offer(Document.COLUMN_LAST_MODIFIED, lastModified);
diff --git a/src/com/android/providers/downloads/TrampolineActivity.java b/src/com/android/providers/downloads/TrampolineActivity.java
deleted file mode 100644 (file)
index 0f494cf..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.providers.downloads;
-
-import android.app.Activity;
-import android.content.ContentUris;
-import android.content.Intent;
-import android.os.Bundle;
-
-/**
- * PackageInstaller really wants raw files.
- */
-public class TrampolineActivity extends Activity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        final long id = ContentUris.parseId(getIntent().getData());
-        final Intent intent = OpenHelper.buildViewIntent(this, id);
-        startActivity(intent);
-        finish();
-    }
-}
index 6166915..93fb522 100644 (file)
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
+
+        <activity
+            android:name=".TrampolineActivity"
+            android:theme="@android:style/Theme.NoDisplay"
+            android:permission="android.permission.MANAGE_DOCUMENTS">
+            <intent-filter>
+                <action android:name="android.provider.action.MANAGE_DOCUMENT" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data
+                    android:scheme="content"
+                    android:host="com.android.providers.downloads.documents"
+                    android:mimeType="*/*" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
index 443491a..991d70b 100644 (file)
@@ -151,9 +151,9 @@ public class DownloadList extends Activity {
         super.onCreate(icicle);
 
         // Trampoline over to new management UI
-        final Intent intent = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENTS);
-        intent.setData(DocumentsContract.buildDocumentUri(
-                Constants.STORAGE_AUTHORITY, Constants.STORAGE_DOC_ID_ROOT));
+        final Intent intent = new Intent(DocumentsContract.ACTION_MANAGE_ROOT);
+        intent.setData(DocumentsContract.buildRootUri(
+                Constants.STORAGE_AUTHORITY, Constants.STORAGE_ROOT_ID));
         startActivity(intent);
         finish();
     }
diff --git a/ui/src/com/android/providers/downloads/ui/TrampolineActivity.java b/ui/src/com/android/providers/downloads/ui/TrampolineActivity.java
new file mode 100644 (file)
index 0000000..e9cc17e
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.downloads.ui;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.DownloadManager;
+import android.app.DownloadManager.Query;
+import android.app.FragmentManager;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.providers.downloads.Constants;
+import com.android.providers.downloads.OpenHelper;
+
+import libcore.io.IoUtils;
+
+/**
+ * Intercept all download clicks to provide special behavior. For example,
+ * PackageInstaller really wants raw file paths.
+ */
+public class TrampolineActivity extends Activity {
+    private static final String TAG_PAUSED = "paused";
+    private static final String TAG_FAILED = "failed";
+
+    private static final String KEY_ID = "id";
+    private static final String KEY_REASON = "reason";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final long id = ContentUris.parseId(getIntent().getData());
+
+        final DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
+        dm.setAccessAllDownloads(true);
+
+        final int status;
+        final int reason;
+
+        final Cursor cursor = dm.query(new Query().setFilterById(id));
+        try {
+            if (cursor.moveToFirst()) {
+                status = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
+                reason = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_REASON));
+            } else {
+                Toast.makeText(this, R.string.dialog_file_missing_body, Toast.LENGTH_SHORT).show();
+                finish();
+                return;
+            }
+        } finally {
+            IoUtils.closeQuietly(cursor);
+        }
+
+        Log.d(Constants.TAG, "Found " + id + " with status " + status + ", reason " + reason);
+        switch (status) {
+            case DownloadManager.STATUS_PENDING:
+            case DownloadManager.STATUS_RUNNING:
+                sendRunningDownloadClickedBroadcast(id);
+                finish();
+                break;
+
+            case DownloadManager.STATUS_PAUSED:
+                if (reason == DownloadManager.PAUSED_QUEUED_FOR_WIFI) {
+                    PausedDialogFragment.show(getFragmentManager(), id);
+                } else {
+                    sendRunningDownloadClickedBroadcast(id);
+                    finish();
+                }
+                break;
+
+            case DownloadManager.STATUS_SUCCESSFUL:
+                final Intent intent = OpenHelper.buildViewIntent(this, id);
+                startActivity(intent);
+                finish();
+                break;
+
+            case DownloadManager.STATUS_FAILED:
+                FailedDialogFragment.show(getFragmentManager(), id, reason);
+                break;
+        }
+    }
+
+    private void sendRunningDownloadClickedBroadcast(long id) {
+        final Intent intent = new Intent(Constants.ACTION_LIST);
+        intent.setPackage(Constants.PROVIDER_PACKAGE_NAME);
+        intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, new long[] { id });
+        sendBroadcast(intent);
+    }
+
+    public static class PausedDialogFragment extends DialogFragment {
+        public static void show(FragmentManager fm, long id) {
+            final PausedDialogFragment dialog = new PausedDialogFragment();
+            final Bundle args = new Bundle();
+            args.putLong(KEY_ID, id);
+            dialog.setArguments(args);
+            dialog.show(fm, TAG_PAUSED);
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Context context = getActivity();
+
+            final DownloadManager dm = (DownloadManager) context.getSystemService(
+                    Context.DOWNLOAD_SERVICE);
+            dm.setAccessAllDownloads(true);
+
+            final long id = getArguments().getLong(KEY_ID);
+
+            final AlertDialog.Builder builder = new AlertDialog.Builder(
+                    context, AlertDialog.THEME_HOLO_LIGHT);
+            builder.setTitle(R.string.dialog_title_queued_body);
+            builder.setMessage(R.string.dialog_queued_body);
+
+            builder.setPositiveButton(R.string.keep_queued_download, null);
+
+            builder.setNegativeButton(
+                    R.string.remove_download, new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            dm.remove(id);
+                        }
+                    });
+
+            return builder.create();
+        }
+
+        @Override
+        public void onDismiss(DialogInterface dialog) {
+            super.onDismiss(dialog);
+            getActivity().finish();
+        }
+    }
+
+    public static class FailedDialogFragment extends DialogFragment {
+        public static void show(FragmentManager fm, long id, int reason) {
+            final FailedDialogFragment dialog = new FailedDialogFragment();
+            final Bundle args = new Bundle();
+            args.putLong(KEY_ID, id);
+            args.putInt(KEY_REASON, reason);
+            dialog.setArguments(args);
+            dialog.show(fm, TAG_FAILED);
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Context context = getActivity();
+
+            final DownloadManager dm = (DownloadManager) context.getSystemService(
+                    Context.DOWNLOAD_SERVICE);
+            dm.setAccessAllDownloads(true);
+
+            final long id = getArguments().getLong(KEY_ID);
+            final int reason = getArguments().getInt(KEY_REASON);
+
+            final AlertDialog.Builder builder = new AlertDialog.Builder(
+                    context, AlertDialog.THEME_HOLO_LIGHT);
+            builder.setTitle(R.string.dialog_title_not_available);
+
+            final String message;
+            switch (reason) {
+                case DownloadManager.ERROR_FILE_ALREADY_EXISTS:
+                    builder.setMessage(R.string.dialog_file_already_exists);
+                    break;
+                case DownloadManager.ERROR_INSUFFICIENT_SPACE:
+                    builder.setMessage(R.string.dialog_insufficient_space_on_external);
+                    break;
+                case DownloadManager.ERROR_DEVICE_NOT_FOUND:
+                    builder.setMessage(R.string.dialog_media_not_found);
+                    break;
+                case DownloadManager.ERROR_CANNOT_RESUME:
+                    builder.setMessage(R.string.dialog_cannot_resume);
+                    break;
+                default:
+                    builder.setMessage(R.string.dialog_failed_body);
+            }
+
+            builder.setNegativeButton(
+                    R.string.delete_download, new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            dm.remove(id);
+                        }
+                    });
+
+            builder.setPositiveButton(
+                    R.string.retry_download, new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            dm.restartDownload(id);
+                        }
+                    });
+
+            return builder.create();
+        }
+
+        @Override
+        public void onDismiss(DialogInterface dialog) {
+            super.onDismiss(dialog);
+            getActivity().finish();
+        }
+    }
+}