From a149731a6764f259b7d15e05a2f557a3bdd23aab Mon Sep 17 00:00:00 2001
From: Jean Chalard <jchalard@google.com>
Date: Mon, 17 Feb 2014 16:14:18 +0900
Subject: [PATCH] Catch exceptions we can't do anything about.

This also abstracts away the "package deactivated" case for
simpler and safer code.

Bug: 11072561
Change-Id: Idaaf2ae8d8d5b2c4a15de641bbf2f8c5c7cc9410
---
 .../dictionarypack/ActionBatch.java           | 21 +---
 .../DictionaryDownloadProgressBar.java        | 10 +-
 .../DownloadManagerWrapper.java               | 99 +++++++++++++++++++
 .../dictionarypack/UpdateHandler.java         | 32 +++---
 4 files changed, 118 insertions(+), 44 deletions(-)
 create mode 100644 java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java

diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
index d5e638e7e5..706bdea8ee 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
@@ -117,16 +117,11 @@ public final class ActionBatch {
             final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
                     mWordList.mId, mWordList.mVersion);
             final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
-            final DownloadManager manager =
-                    (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+            final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
             if (MetadataDbHelper.STATUS_DOWNLOADING == status) {
                 // The word list is still downloading. Cancel the download and revert the
                 // word list status to "available".
-                if (null != manager) {
-                    // DownloadManager is disabled (or not installed?). We can't cancel - there
-                    // is nothing we can do. We still need to mark the entry as available.
-                    manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
-                }
+                 manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
                 MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion);
             } else if (MetadataDbHelper.STATUS_AVAILABLE != status) {
                 // Should never happen
@@ -136,9 +131,6 @@ public final class ActionBatch {
             // Download it.
             DebugLogUtils.l("Upgrade word list, downloading", mWordList.mRemoteFilename);
 
-            // TODO: if DownloadManager is disabled or not installed, download by ourselves
-            if (null == manager) return;
-
             // This is an upgraded word list: we should download it.
             // Adding a disambiguator to circumvent a bug in older versions of DownloadManager.
             // DownloadManager also stupidly cuts the extension to replace with its own that it
@@ -293,13 +285,8 @@ public final class ActionBatch {
                 }
                 // The word list is still downloading. Cancel the download and revert the
                 // word list status to "available".
-                final DownloadManager manager =
-                        (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
-                if (null != manager) {
-                    // If we can't cancel the download because DownloadManager is not available,
-                    // we still need to mark the entry as available.
-                    manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
-                }
+                final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
+                manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
                 MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion);
             }
         }
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
index 384ee3e07c..2623eff569 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
@@ -107,26 +107,22 @@ public class DictionaryDownloadProgressBar extends ProgressBar {
 
     private class UpdaterThread extends Thread {
         private final static int REPORT_PERIOD = 150; // how often to report progress, in ms
-        final DownloadManager mDownloadManager;
+        final DownloadManagerWrapper mDownloadManagerWrapper;
         final int mId;
         public UpdaterThread(final Context context, final int id) {
             super();
-            mDownloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+            mDownloadManagerWrapper = new DownloadManagerWrapper(context);
             mId = id;
         }
         @Override
         public void run() {
             try {
-                // It's almost impossible that mDownloadManager is null (it would mean it has been
-                // disabled between pressing the 'install' button and displaying the progress
-                // bar), but just in case.
-                if (null == mDownloadManager) return;
                 final UpdateHelper updateHelper = new UpdateHelper();
                 final Query query = new Query().setFilterById(mId);
                 int lastProgress = 0;
                 setIndeterminate(true);
                 while (!isInterrupted()) {
-                    final Cursor cursor = mDownloadManager.query(query);
+                    final Cursor cursor = mDownloadManagerWrapper.query(query);
                     if (null == cursor) {
                         // Can't contact DownloadManager: this should never happen.
                         return;
diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java b/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java
new file mode 100644
index 0000000000..e95ca1799f
--- /dev/null
+++ b/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 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.inputmethod.dictionarypack;
+
+import android.app.DownloadManager;
+import android.app.DownloadManager.Query;
+import android.app.DownloadManager.Request;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+
+/**
+ * A class to help with calling DownloadManager methods.
+ *
+ * Mostly, the problem here is that most methods from DownloadManager may throw SQL exceptions if
+ * they can't open the database on disk. We want to avoid crashing in these cases but can't do
+ * much more, so this class insulates the callers from these. SQLiteException also inherit from
+ * RuntimeException so they are unchecked :(
+ * While we're at it, we also insulate callers from the cases where DownloadManager is disabled,
+ * and getSystemService returns null.
+ */
+public class DownloadManagerWrapper {
+    private final static String TAG = DownloadManagerWrapper.class.getSimpleName();
+    private final DownloadManager mDownloadManager;
+
+    public DownloadManagerWrapper(final Context context) {
+        this((DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE));
+    }
+
+    private DownloadManagerWrapper(final DownloadManager downloadManager) {
+        mDownloadManager = downloadManager;
+    }
+
+    public void remove(final long... ids) {
+        try {
+            if (null != mDownloadManager) {
+                mDownloadManager.remove(ids);
+            }
+        } catch (SQLiteException e) {
+            // We couldn't remove the file from DownloadManager. Apparently, the database can't
+            // be opened. It may be a problem with file system corruption. In any case, there is
+            // not much we can do apart from avoiding crashing.
+            Log.e(TAG, "Can't remove files with ID " + ids + " from download manager", e);
+        }
+    }
+
+    public ParcelFileDescriptor openDownloadedFile(final long fileId) throws FileNotFoundException {
+        try {
+            if (null != mDownloadManager) {
+                return mDownloadManager.openDownloadedFile(fileId);
+            }
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Can't open downloaded file with ID " + fileId, e);
+        }
+        // We come here if mDownloadManager is null or if an exception was thrown.
+        throw new FileNotFoundException();
+    }
+
+    public Cursor query(final Query query) {
+        try {
+            if (null != mDownloadManager) {
+                return mDownloadManager.query(query);
+            }
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Can't query the download manager", e);
+        }
+        // We come here if mDownloadManager is null or if an exception was thrown.
+        return null;
+    }
+
+    public long enqueue(final Request request) {
+        try {
+            if (null != mDownloadManager) {
+                return mDownloadManager.enqueue(request);
+            }
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Can't enqueue a request with the download manager", e);
+        }
+        return 0;
+    }
+}
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index 0e7c3bb7e0..dcff490dbe 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -249,13 +249,7 @@ public final class UpdateHandler {
         metadataRequest.setVisibleInDownloadsUi(
                 res.getBoolean(R.bool.metadata_downloads_visible_in_download_UI));
 
-        final DownloadManager manager =
-                (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
-        if (null == manager) {
-            // Download manager is not installed or disabled.
-            // TODO: fall back to self-managed download?
-            return;
-        }
+        final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
         cancelUpdateWithDownloadManager(context, metadataUri, manager);
         final long downloadId;
         synchronized (sSharedIdProtector) {
@@ -278,10 +272,10 @@ public final class UpdateHandler {
      *
      * @param context the context to open the database on
      * @param metadataUri the URI to cancel
-     * @param manager an instance of DownloadManager
+     * @param manager an wrapped instance of DownloadManager
      */
     private static void cancelUpdateWithDownloadManager(final Context context,
-            final String metadataUri, final DownloadManager manager) {
+            final String metadataUri, final DownloadManagerWrapper manager) {
         synchronized (sSharedIdProtector) {
             final long metadataDownloadId =
                     MetadataDbHelper.getMetadataDownloadIdForURI(context, metadataUri);
@@ -306,10 +300,9 @@ public final class UpdateHandler {
      * @param clientId the ID of the client we want to cancel the update of
      */
     public static void cancelUpdate(final Context context, final String clientId) {
-        final DownloadManager manager =
-                    (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+        final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
         final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId);
-        if (null != manager) cancelUpdateWithDownloadManager(context, metadataUri, manager);
+        cancelUpdateWithDownloadManager(context, metadataUri, manager);
     }
 
     /**
@@ -323,15 +316,15 @@ public final class UpdateHandler {
      * download request id, which is not known before submitting the request to the download
      * manager. Hence, it only updates the relevant line.
      *
-     * @param manager the download manager service to register the request with.
+     * @param manager a wrapped download manager service to register the request with.
      * @param request the request to register.
      * @param db the metadata database.
      * @param id the id of the word list.
      * @param version the version of the word list.
      * @return the download id returned by the download manager.
      */
-    public static long registerDownloadRequest(final DownloadManager manager, final Request request,
-            final SQLiteDatabase db, final String id, final int version) {
+    public static long registerDownloadRequest(final DownloadManagerWrapper manager,
+            final Request request, final SQLiteDatabase db, final String id, final int version) {
         DebugLogUtils.l("RegisterDownloadRequest for word list id : ", id, ", version ", version);
         final long downloadId;
         synchronized (sSharedIdProtector) {
@@ -345,8 +338,8 @@ public final class UpdateHandler {
     /**
      * Retrieve information about a specific download from DownloadManager.
      */
-    private static CompletedDownloadInfo getCompletedDownloadInfo(final DownloadManager manager,
-            final long downloadId) {
+    private static CompletedDownloadInfo getCompletedDownloadInfo(
+            final DownloadManagerWrapper manager, final long downloadId) {
         final Query query = new Query().setFilterById(downloadId);
         final Cursor cursor = manager.query(query);
 
@@ -425,8 +418,7 @@ public final class UpdateHandler {
         DebugLogUtils.l("DownloadFinished with id", fileId);
         if (NOT_AN_ID == fileId) return; // Spurious wake-up: ignore
 
-        final DownloadManager manager =
-                (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+        final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
         final CompletedDownloadInfo downloadInfo = getCompletedDownloadInfo(manager, fileId);
 
         final ArrayList<DownloadRecord> recordList =
@@ -517,7 +509,7 @@ public final class UpdateHandler {
     }
 
     private static boolean handleDownloadedFile(final Context context,
-            final DownloadRecord downloadRecord, final DownloadManager manager,
+            final DownloadRecord downloadRecord, final DownloadManagerWrapper manager,
             final long fileId) {
         try {
             // {@link handleWordList(Context,InputStream,ContentValues)}.
-- 
GitLab