diff options
author | Vasu Nori <vnori@google.com> | 2010-10-13 16:56:17 -0700 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2010-10-13 16:56:17 -0700 |
commit | 57c4e9180e3a339dba984f1c8dff76ef18443a7a (patch) | |
tree | 241f465bb2cb8e627c40cbefb1e66f2ec5fe003f | |
parent | 1f262cf3af0512e0d621b7818aab9bb79527f01f (diff) | |
parent | e00c31208405bd2e4c88e069df7a2b15237f70bf (diff) | |
download | android_packages_providers_DownloadProvider-57c4e9180e3a339dba984f1c8dff76ef18443a7a.tar.gz android_packages_providers_DownloadProvider-57c4e9180e3a339dba984f1c8dff76ef18443a7a.tar.bz2 android_packages_providers_DownloadProvider-57c4e9180e3a339dba984f1c8dff76ef18443a7a.zip |
am e00c3120: bug:3069735 in Download UI app, handle deletes correctly
Merge commit 'e00c31208405bd2e4c88e069df7a2b15237f70bf' into gingerbread-plus-aosp
* commit 'e00c31208405bd2e4c88e069df7a2b15237f70bf':
bug:3069735 in Download UI app, handle deletes correctly
4 files changed, 157 insertions, 37 deletions
diff --git a/src/com/android/providers/downloads/DownloadInfo.java b/src/com/android/providers/downloads/DownloadInfo.java index 3327e80b..36816b59 100644 --- a/src/com/android/providers/downloads/DownloadInfo.java +++ b/src/com/android/providers/downloads/DownloadInfo.java @@ -29,6 +29,7 @@ import android.net.ConnectivityManager; import android.net.Uri; import android.provider.Downloads; import android.provider.Downloads.Impl; +import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -84,6 +85,9 @@ public class DownloadInfo { info.mCurrentBytes = getLong(Downloads.Impl.COLUMN_CURRENT_BYTES); info.mETag = getString(info.mETag, Constants.ETAG); info.mMediaScanned = getInt(Constants.MEDIA_SCANNED) == 1; + info.mDeleted = getInt(Downloads.Impl.COLUMN_DELETED) == 1; + info.mMediaProviderUri = getString(info.mMediaProviderUri, + Downloads.Impl.COLUMN_MEDIAPROVIDER_URI); info.mIsPublicApi = getInt(Downloads.Impl.COLUMN_IS_PUBLIC_API) != 0; info.mAllowedNetworkTypes = getInt(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES); info.mAllowRoaming = getInt(Downloads.Impl.COLUMN_ALLOW_ROAMING) != 0; @@ -231,6 +235,8 @@ public class DownloadInfo { public long mCurrentBytes; public String mETag; public boolean mMediaScanned; + public boolean mDeleted; + public String mMediaProviderUri; public boolean mIsPublicApi; public int mAllowedNetworkTypes; public boolean mAllowRoaming; @@ -511,6 +517,8 @@ public class DownloadInfo { Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes); Log.v(Constants.TAG, "ETAG : " + mETag); Log.v(Constants.TAG, "SCANNED : " + mMediaScanned); + Log.v(Constants.TAG, "DELETED : " + mDeleted); + Log.v(Constants.TAG, "MEDIAPROVIDER_URI : " + mMediaProviderUri); } /** diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java index 3201463b..d3fe4ab9 100644 --- a/src/com/android/providers/downloads/DownloadProvider.java +++ b/src/com/android/providers/downloads/DownloadProvider.java @@ -49,6 +49,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; /** @@ -58,7 +59,7 @@ public final class DownloadProvider extends ContentProvider { /** Database filename */ private static final String DB_NAME = "downloads.db"; /** Current database version */ - private static final int DB_VERSION = 105; + private static final int DB_VERSION = 106; /** Name of table in the database */ private static final String DB_TABLE = "downloads"; @@ -123,6 +124,8 @@ public final class DownloadProvider extends ContentProvider { Downloads.Impl.COLUMN_URI, Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, Downloads.Impl.COLUMN_FILE_NAME_HINT, + Downloads.Impl.COLUMN_MEDIAPROVIDER_URI, + Downloads.Impl.COLUMN_DELETED, }; private static HashSet<String> sAppReadableColumnsSet; @@ -270,6 +273,12 @@ public final class DownloadProvider extends ContentProvider { fillNullValues(db); break; + case 106: + addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_MEDIAPROVIDER_URI, "TEXT"); + addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_DELETED, + "BOOLEAN NOT NULL DEFAULT 0"); + break; + default: throw new IllegalStateException("Don't know how to upgrade to " + version); } @@ -356,7 +365,9 @@ public final class DownloadProvider extends ContentProvider { Downloads.Impl.COLUMN_OTHER_UID + " INTEGER, " + Downloads.Impl.COLUMN_TITLE + " TEXT, " + Downloads.Impl.COLUMN_DESCRIPTION + " TEXT, " + - Constants.MEDIA_SCANNED + " BOOLEAN);"); + Constants.MEDIA_SCANNED + " BOOLEAN, " + + Downloads.Impl.COLUMN_MEDIAPROVIDER_URI + " TEXT, " + + Downloads.Impl.COLUMN_DELETED + " BOOLEAN NOT NULL DEFAULT 1);"); } catch (SQLException ex) { Log.e(Constants.TAG, "couldn't create table in downloads database"); throw ex; @@ -869,6 +880,13 @@ public final class DownloadProvider extends ContentProvider { int count; boolean startService = false; + if (values.containsKey(Downloads.Impl.COLUMN_DELETED)) { + if (values.getAsInteger(Downloads.Impl.COLUMN_DELETED) == 1) { + // some rows are to be 'deleted'. need to start DownloadService. + startService = true; + } + } + ContentValues filteredValues; if (Binder.getCallingPid() != Process.myPid()) { filteredValues = new ContentValues(); @@ -879,9 +897,12 @@ public final class DownloadProvider extends ContentProvider { filteredValues.put(Downloads.Impl.COLUMN_CONTROL, i); startService = true; } + copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues); copyString(Downloads.Impl.COLUMN_TITLE, values, filteredValues); + copyString(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI, values, filteredValues); copyString(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues); + copyInteger(Downloads.Impl.COLUMN_DELETED, values, filteredValues); } else { filteredValues = values; String filename = values.getAsString(Downloads.Impl._DATA); diff --git a/src/com/android/providers/downloads/DownloadService.java b/src/com/android/providers/downloads/DownloadService.java index 8fbcf4b4..0850af74 100644 --- a/src/com/android/providers/downloads/DownloadService.java +++ b/src/com/android/providers/downloads/DownloadService.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.content.ServiceConnection; import android.database.ContentObserver; import android.database.Cursor; +import android.media.IMediaScannerListener; import android.media.IMediaScannerService; import android.net.Uri; import android.os.Environment; @@ -35,12 +36,14 @@ import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.provider.Downloads; +import android.text.TextUtils; import android.util.Log; import com.google.android.collect.Maps; import com.google.common.annotations.VisibleForTesting; import java.io.File; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; @@ -94,6 +97,18 @@ public class DownloadService extends Service { @VisibleForTesting SystemFacade mSystemFacade; + /** Before calling + * {@link IMediaScannerService#requestScanFile(String, String, IMediaScannerListener)}, + * a (key,value) pair of (filepath, uri-to-update-the-row-in-dowloads-db) + * is stored in the following struct. + * When the callback from + * {@link IMediaScannerService#requestScanFile(String, String, IMediaScannerListener)} is + * received with params (filepath, uri-to-update-the-row-in-mediaprovider-db), + * this struct comes in handy to locate the row in downloads table whose mediaprovier-uri + * column is to be updated with the value returned by this callback method. + */ + private final HashMap<String, Uri> downloadsToBeUpdated = new HashMap<String, Uri>(); + /** * Receives notifications when the data in the content provider changes */ @@ -125,17 +140,20 @@ public class DownloadService extends Service { if (Constants.LOGVV) { Log.v(Constants.TAG, "Connected to Media Scanner"); } - mMediaScannerConnecting = false; synchronized (DownloadService.this) { + mMediaScannerConnecting = false; mMediaScannerService = IMediaScannerService.Stub.asInterface(service); if (mMediaScannerService != null) { updateFromProvider(); } + // notify anyone waiting on successful connection to MediaService + DownloadService.this.notifyAll(); } } public void disconnectMediaScanner() { synchronized (DownloadService.this) { + mMediaScannerConnecting = false; if (mMediaScannerService != null) { mMediaScannerService = null; if (Constants.LOGVV) { @@ -144,11 +162,11 @@ public class DownloadService extends Service { try { unbindService(this); } catch (IllegalArgumentException ex) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "unbindService threw up: " + ex); - } + Log.w(Constants.TAG, "unbindService failed: " + ex); } } + // notify anyone waiting on unsuccessful connection to MediaService + DownloadService.this.notifyAll(); } } @@ -158,6 +176,9 @@ public class DownloadService extends Service { } synchronized (DownloadService.this) { mMediaScannerService = null; + mMediaScannerConnecting = false; + // notify anyone waiting on disconnect from MediaService + DownloadService.this.notifyAll(); } } } @@ -315,13 +336,48 @@ public class DownloadService extends Service { deleteDownload(id); } + // is there a need to start the DownloadService? yes, if there are rows to be + // deleted. + if (!mustScan) { + for (DownloadInfo info : mDownloads.values()) { + if (info.mDeleted && TextUtils.isEmpty(info.mMediaProviderUri)) { + mustScan = true; + keepService = true; + break; + } + } + } mNotifier.updateNotification(mDownloads.values()); - if (mustScan) { bindMediaScanner(); } else { mMediaScannerConnection.disconnectMediaScanner(); } + + // look for all rows with deleted flag set and delete the rows from the database + // permanently + for (DownloadInfo info : mDownloads.values()) { + if (info.mDeleted) { + // this row is to be deleted from the database. but does it have + // mediaProviderUri? + if (TextUtils.isEmpty(info.mMediaProviderUri)) { + // initiate rescan of the file to - which will populate mediaProviderUri + // column in this row + if (!scanFile(info, true)) { + throw new IllegalStateException("scanFile failed!"); + } + } else { + // yes it has mediaProviderUri column already filled in. + // delete it from MediaProvider database and then from downloads table + // in DownProvider database (the order of deletion is important). + getContentResolver().delete(Uri.parse(info.mMediaProviderUri), null, + null); + getContentResolver().delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, + Downloads.Impl._ID + " = ? ", + new String[]{String.valueOf(info.mId)}); + } + } + } } } @@ -491,24 +547,60 @@ public class DownloadService extends Service { private boolean scanFile(DownloadInfo info, boolean updateDatabase) { synchronized (this) { if (mMediaScannerService == null) { + // not bound to mediaservice. but if in the process of connecting to it, wait until + // connection is resolved + while (mMediaScannerConnecting) { + Log.d(Constants.TAG, "waiting for mMediaScannerService service: "); + try { + this.wait(); + } catch (InterruptedException e1) { + throw new IllegalStateException("wait interrupted"); + } + } + } + // do we have mediaservice? + if (mMediaScannerService == null) { + // no available MediaService And not even in the process of connecting to it return false; } - try { - if (Constants.LOGV) { - Log.v(Constants.TAG, "Scanning file " + info.mFileName); - } - mMediaScannerService.scanFile(info.mFileName, info.mMimeType); - if (updateDatabase) { - ContentValues values = new ContentValues(); - values.put(Constants.MEDIA_SCANNED, 1); - getContentResolver().update(info.getAllDownloadsUri(), values, null, null); + if (Constants.LOGV) { + Log.v(Constants.TAG, "Scanning file " + info.mFileName); + } + if (updateDatabase) { + synchronized(downloadsToBeUpdated) { + Uri value = info.getAllDownloadsUri(); + if (value != null) { + downloadsToBeUpdated.put(info.mFileName, value); + } } + } + try { + mMediaScannerService.requestScanFile(info.mFileName, info.mMimeType, + new IMediaScannerListener.Stub() { + public void scanCompleted(String path, Uri uri) { + Uri key; + boolean updateMediaproviderUriColumn; + synchronized(downloadsToBeUpdated) { + key = downloadsToBeUpdated.get(path); + updateMediaproviderUriColumn = (key != null); + } + if (updateMediaproviderUriColumn) { + ContentValues values = new ContentValues(); + values.put(Constants.MEDIA_SCANNED, 1); + values.put(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI, + uri.toString()); + getContentResolver().update(key, values, null, null); + synchronized(downloadsToBeUpdated) { + downloadsToBeUpdated.remove(path); + } + } + } + }); return true; } catch (RemoteException e) { - Log.d(Constants.TAG, "Failed to scan file " + info.mFileName); + Log.w(Constants.TAG, "Failed to scan file " + info.mFileName); return false; } } } - } diff --git a/ui/src/com/android/providers/downloads/ui/DownloadList.java b/ui/src/com/android/providers/downloads/ui/DownloadList.java index f1cb91fe..33f821f6 100644 --- a/ui/src/com/android/providers/downloads/ui/DownloadList.java +++ b/ui/src/com/android/providers/downloads/ui/DownloadList.java @@ -33,6 +33,7 @@ import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.provider.Downloads; +import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -51,7 +52,6 @@ import android.widget.Toast; import com.android.providers.downloads.ui.DownloadItem.DownloadSelectListener; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashSet; @@ -86,6 +86,7 @@ public class DownloadList extends Activity private int mLocalUriColumnId; private int mMediaTypeColumnId; private int mReasonColumndId; + private int mMediaProviderUriId; private boolean mIsSortedBySize = false; private Set<Long> mSelectedIds = new HashSet<Long>(); @@ -148,6 +149,9 @@ public class DownloadList extends Activity mDateSortedCursor.getColumnIndexOrThrow(DownloadManager.COLUMN_MEDIA_TYPE); mReasonColumndId = mDateSortedCursor.getColumnIndexOrThrow(DownloadManager.COLUMN_REASON); + mMediaProviderUriId = + mDateSortedCursor.getColumnIndexOrThrow( + DownloadManager.COLUMN_MEDIAPROVIDER_URI); mDateSortedAdapter = new DateSortedDownloadAdapter(this, mDateSortedCursor, this); mDateOrderedListView.setAdapter(mDateSortedAdapter); @@ -588,30 +592,25 @@ public class DownloadList extends Activity if (isComplete && localUri != null) { String path = Uri.parse(localUri).getPath(); if (path.startsWith(Environment.getExternalStorageDirectory().getPath())) { - String mediaType = mDateSortedCursor.getString(mMediaTypeColumnId); - deleteDownloadedFile(path, mediaType); + String mediaProviderUri = mDateSortedCursor.getString(mMediaProviderUriId); + if (TextUtils.isEmpty(mediaProviderUri)) { + // downloads database doesn't have the mediaprovider_uri. It means + // this download occurred before mediaprovider_uri column existed + // in downloads table. Since MediaProvider needs the mediaprovider_uri to + // delete this download, just set the 'deleted' flag to 1 on this row + // in the database. DownloadService, upon seeing this flag set to 1, will + // re-scan the file and get the MediaProviderUri and then delete the file + mDownloadManager.markRowDeleted(downloadId); + return; + } else { + getContentResolver().delete(Uri.parse(mediaProviderUri), null, null); + } } } } mDownloadManager.remove(downloadId); } - /** - * Delete the file at the given path. Try sending an intent to delete it, but if that goes - * unhandled, delete it ourselves. - * @param path path to the file to delete - */ - private void deleteDownloadedFile(String path, String mediaType) { - Intent intent = new Intent(Intent.ACTION_DELETE); - File file = new File(path); - intent.setDataAndType(Uri.fromFile(file), mediaType); - try { - startActivity(intent); - } catch (ActivityNotFoundException exc) { - file.delete(); - } - } - @Override public boolean isDownloadSelected(long id) { return mSelectedIds.contains(id); |