The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2007 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.providers.downloads; |
| 18 | |
Jeff Sharkey | ed30dea | 2015-07-13 10:25:58 -0700 | [diff] [blame] | 19 | import android.app.AppOpsManager; |
Vasu Nori | 3ca6774 | 2010-11-04 15:03:31 -0700 | [diff] [blame] | 20 | import android.app.DownloadManager; |
Jeff Sharkey | 51cc214 | 2011-04-13 12:39:50 -0700 | [diff] [blame] | 21 | import android.app.DownloadManager.Request; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 22 | import android.content.ContentProvider; |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 23 | import android.content.ContentUris; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 24 | import android.content.ContentValues; |
| 25 | import android.content.Context; |
| 26 | import android.content.Intent; |
| 27 | import android.content.UriMatcher; |
Suchi Amalapurapu | 91e4522 | 2010-01-25 15:50:36 -0800 | [diff] [blame] | 28 | import android.content.pm.ApplicationInfo; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 29 | import android.content.pm.PackageManager; |
Suchi Amalapurapu | 91e4522 | 2010-01-25 15:50:36 -0800 | [diff] [blame] | 30 | import android.content.pm.PackageManager.NameNotFoundException; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 31 | import android.database.Cursor; |
Vasu Nori | 3ca6774 | 2010-11-04 15:03:31 -0700 | [diff] [blame] | 32 | import android.database.DatabaseUtils; |
Jean-Baptiste Queru | c6f5aad | 2009-01-20 10:08:46 -0800 | [diff] [blame] | 33 | import android.database.SQLException; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 34 | import android.database.sqlite.SQLiteDatabase; |
| 35 | import android.database.sqlite.SQLiteOpenHelper; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 36 | import android.net.Uri; |
| 37 | import android.os.Binder; |
Jeff Sharkey | c067c8b | 2013-09-23 14:21:55 -0700 | [diff] [blame] | 38 | import android.os.Handler; |
Todd Kennedy | f775c98 | 2015-06-19 14:47:45 -0700 | [diff] [blame] | 39 | import android.os.HandlerThread; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 40 | import android.os.ParcelFileDescriptor; |
Jeff Sharkey | c067c8b | 2013-09-23 14:21:55 -0700 | [diff] [blame] | 41 | import android.os.ParcelFileDescriptor.OnCloseListener; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 42 | import android.os.Process; |
Jeff Sharkey | 1d0a0aa | 2013-01-30 11:26:46 -0800 | [diff] [blame] | 43 | import android.provider.BaseColumns; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 44 | import android.provider.Downloads; |
Jeff Sharkey | 9b60634 | 2012-04-16 14:29:04 -0700 | [diff] [blame] | 45 | import android.provider.OpenableColumns; |
Jeff Sharkey | c3f3d99 | 2012-04-19 10:10:53 -0700 | [diff] [blame] | 46 | import android.text.TextUtils; |
Jeff Sharkey | 1d0a0aa | 2013-01-30 11:26:46 -0800 | [diff] [blame] | 47 | import android.text.format.DateUtils; |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 48 | import android.util.Log; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 49 | |
Jeff Sharkey | ed30dea | 2015-07-13 10:25:58 -0700 | [diff] [blame] | 50 | import libcore.io.IoUtils; |
| 51 | |
Jeff Sharkey | 1d0a0aa | 2013-01-30 11:26:46 -0800 | [diff] [blame] | 52 | import com.android.internal.util.IndentingPrintWriter; |
Jeff Sharkey | 9b60634 | 2012-04-16 14:29:04 -0700 | [diff] [blame] | 53 | import com.google.android.collect.Maps; |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 54 | import com.google.common.annotations.VisibleForTesting; |
| 55 | |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 56 | import java.io.File; |
Jeff Sharkey | 1d0a0aa | 2013-01-30 11:26:46 -0800 | [diff] [blame] | 57 | import java.io.FileDescriptor; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 58 | import java.io.FileNotFoundException; |
Jeff Sharkey | d195a5c | 2011-10-12 12:04:10 -0700 | [diff] [blame] | 59 | import java.io.IOException; |
Jeff Sharkey | 1d0a0aa | 2013-01-30 11:26:46 -0800 | [diff] [blame] | 60 | import java.io.PrintWriter; |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 61 | import java.util.ArrayList; |
Vasu Nori | 01d0182 | 2010-11-09 12:30:19 -0800 | [diff] [blame] | 62 | import java.util.Arrays; |
Jeff Sharkey | 9b60634 | 2012-04-16 14:29:04 -0700 | [diff] [blame] | 63 | import java.util.HashMap; |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 64 | import java.util.HashSet; |
Steve Howard | e61798d | 2010-08-02 11:52:16 -0700 | [diff] [blame] | 65 | import java.util.Iterator; |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 66 | import java.util.List; |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 67 | import java.util.Map; |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 68 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 69 | /** |
| 70 | * Allows application to interact with the download manager. |
| 71 | */ |
| 72 | public final class DownloadProvider extends ContentProvider { |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 73 | /** Database filename */ |
| 74 | private static final String DB_NAME = "downloads.db"; |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 75 | /** Current database version */ |
Jeff Sharkey | c067c8b | 2013-09-23 14:21:55 -0700 | [diff] [blame] | 76 | private static final int DB_VERSION = 109; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 77 | /** Name of table in the database */ |
| 78 | private static final String DB_TABLE = "downloads"; |
| 79 | |
| 80 | /** MIME type for the entire download list */ |
| 81 | private static final String DOWNLOAD_LIST_TYPE = "vnd.android.cursor.dir/download"; |
| 82 | /** MIME type for an individual download */ |
| 83 | private static final String DOWNLOAD_TYPE = "vnd.android.cursor.item/download"; |
| 84 | |
| 85 | /** URI matcher used to recognize URIs sent by applications */ |
| 86 | private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 87 | /** URI matcher constant for the URI of all downloads belonging to the calling UID */ |
| 88 | private static final int MY_DOWNLOADS = 1; |
| 89 | /** URI matcher constant for the URI of an individual download belonging to the calling UID */ |
| 90 | private static final int MY_DOWNLOADS_ID = 2; |
| 91 | /** URI matcher constant for the URI of all downloads in the system */ |
| 92 | private static final int ALL_DOWNLOADS = 3; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 93 | /** URI matcher constant for the URI of an individual download */ |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 94 | private static final int ALL_DOWNLOADS_ID = 4; |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 95 | /** URI matcher constant for the URI of a download's request headers */ |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 96 | private static final int REQUEST_HEADERS_URI = 5; |
Vasu Nori | 3ca6774 | 2010-11-04 15:03:31 -0700 | [diff] [blame] | 97 | /** URI matcher constant for the public URI returned by |
| 98 | * {@link DownloadManager#getUriForDownloadedFile(long)} if the given downloaded file |
| 99 | * is publicly accessible. |
| 100 | */ |
| 101 | private static final int PUBLIC_DOWNLOAD_ID = 6; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 102 | static { |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 103 | sURIMatcher.addURI("downloads", "my_downloads", MY_DOWNLOADS); |
| 104 | sURIMatcher.addURI("downloads", "my_downloads/#", MY_DOWNLOADS_ID); |
| 105 | sURIMatcher.addURI("downloads", "all_downloads", ALL_DOWNLOADS); |
| 106 | sURIMatcher.addURI("downloads", "all_downloads/#", ALL_DOWNLOADS_ID); |
| 107 | sURIMatcher.addURI("downloads", |
| 108 | "my_downloads/#/" + Downloads.Impl.RequestHeaders.URI_SEGMENT, |
| 109 | REQUEST_HEADERS_URI); |
| 110 | sURIMatcher.addURI("downloads", |
| 111 | "all_downloads/#/" + Downloads.Impl.RequestHeaders.URI_SEGMENT, |
| 112 | REQUEST_HEADERS_URI); |
Steve Howard | 4bebe75 | 2010-09-17 16:55:25 -0700 | [diff] [blame] | 113 | // temporary, for backwards compatibility |
| 114 | sURIMatcher.addURI("downloads", "download", MY_DOWNLOADS); |
| 115 | sURIMatcher.addURI("downloads", "download/#", MY_DOWNLOADS_ID); |
| 116 | sURIMatcher.addURI("downloads", |
| 117 | "download/#/" + Downloads.Impl.RequestHeaders.URI_SEGMENT, |
| 118 | REQUEST_HEADERS_URI); |
Vasu Nori | 3ca6774 | 2010-11-04 15:03:31 -0700 | [diff] [blame] | 119 | sURIMatcher.addURI("downloads", |
| 120 | Downloads.Impl.PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT + "/#", |
| 121 | PUBLIC_DOWNLOAD_ID); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 122 | } |
| 123 | |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 124 | /** Different base URIs that could be used to access an individual download */ |
| 125 | private static final Uri[] BASE_URIS = new Uri[] { |
| 126 | Downloads.Impl.CONTENT_URI, |
| 127 | Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, |
| 128 | }; |
| 129 | |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 130 | private static final String[] sAppReadableColumnsArray = new String[] { |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 131 | Downloads.Impl._ID, |
| 132 | Downloads.Impl.COLUMN_APP_DATA, |
| 133 | Downloads.Impl._DATA, |
| 134 | Downloads.Impl.COLUMN_MIME_TYPE, |
| 135 | Downloads.Impl.COLUMN_VISIBILITY, |
| 136 | Downloads.Impl.COLUMN_DESTINATION, |
| 137 | Downloads.Impl.COLUMN_CONTROL, |
| 138 | Downloads.Impl.COLUMN_STATUS, |
| 139 | Downloads.Impl.COLUMN_LAST_MODIFICATION, |
| 140 | Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, |
| 141 | Downloads.Impl.COLUMN_NOTIFICATION_CLASS, |
| 142 | Downloads.Impl.COLUMN_TOTAL_BYTES, |
| 143 | Downloads.Impl.COLUMN_CURRENT_BYTES, |
| 144 | Downloads.Impl.COLUMN_TITLE, |
Steve Howard | 0a77c62 | 2010-07-21 11:41:30 -0700 | [diff] [blame] | 145 | Downloads.Impl.COLUMN_DESCRIPTION, |
Steve Howard | 0d8d891 | 2010-07-21 19:41:15 -0700 | [diff] [blame] | 146 | Downloads.Impl.COLUMN_URI, |
Steve Howard | 71e7fda | 2010-09-08 17:15:27 -0700 | [diff] [blame] | 147 | Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, |
Steve Howard | b9a0ad7 | 2010-09-16 12:04:17 -0700 | [diff] [blame] | 148 | Downloads.Impl.COLUMN_FILE_NAME_HINT, |
Vasu Nori | e00c312 | 2010-10-12 23:27:49 -0700 | [diff] [blame] | 149 | Downloads.Impl.COLUMN_MEDIAPROVIDER_URI, |
| 150 | Downloads.Impl.COLUMN_DELETED, |
Jeff Sharkey | 9b60634 | 2012-04-16 14:29:04 -0700 | [diff] [blame] | 151 | OpenableColumns.DISPLAY_NAME, |
| 152 | OpenableColumns.SIZE, |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 153 | }; |
| 154 | |
Jeff Sharkey | 9b60634 | 2012-04-16 14:29:04 -0700 | [diff] [blame] | 155 | private static final HashSet<String> sAppReadableColumnsSet; |
| 156 | private static final HashMap<String, String> sColumnsMap; |
| 157 | |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 158 | static { |
| 159 | sAppReadableColumnsSet = new HashSet<String>(); |
| 160 | for (int i = 0; i < sAppReadableColumnsArray.length; ++i) { |
| 161 | sAppReadableColumnsSet.add(sAppReadableColumnsArray[i]); |
| 162 | } |
Jeff Sharkey | 9b60634 | 2012-04-16 14:29:04 -0700 | [diff] [blame] | 163 | |
| 164 | sColumnsMap = Maps.newHashMap(); |
| 165 | sColumnsMap.put(OpenableColumns.DISPLAY_NAME, |
| 166 | Downloads.Impl.COLUMN_TITLE + " AS " + OpenableColumns.DISPLAY_NAME); |
| 167 | sColumnsMap.put(OpenableColumns.SIZE, |
| 168 | Downloads.Impl.COLUMN_TOTAL_BYTES + " AS " + OpenableColumns.SIZE); |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 169 | } |
Vasu Nori | 01d0182 | 2010-11-09 12:30:19 -0800 | [diff] [blame] | 170 | private static final List<String> downloadManagerColumnsList = |
| 171 | Arrays.asList(DownloadManager.UNDERLYING_COLUMNS); |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 172 | |
Jeff Sharkey | c067c8b | 2013-09-23 14:21:55 -0700 | [diff] [blame] | 173 | private Handler mHandler; |
| 174 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 175 | /** The database that lies underneath this content provider */ |
| 176 | private SQLiteOpenHelper mOpenHelper = null; |
| 177 | |
Suchi Amalapurapu | 91e4522 | 2010-01-25 15:50:36 -0800 | [diff] [blame] | 178 | /** List of uids that can access the downloads */ |
| 179 | private int mSystemUid = -1; |
| 180 | private int mDefContainerUid = -1; |
| 181 | |
Steve Howard | 6d9b982 | 2010-07-12 17:24:17 -0700 | [diff] [blame] | 182 | @VisibleForTesting |
| 183 | SystemFacade mSystemFacade; |
| 184 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 185 | /** |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 186 | * This class encapsulates a SQL where clause and its parameters. It makes it possible for |
| 187 | * shared methods (like {@link DownloadProvider#getWhereClause(Uri, String, String[], int)}) |
| 188 | * to return both pieces of information, and provides some utility logic to ease piece-by-piece |
| 189 | * construction of selections. |
| 190 | */ |
| 191 | private static class SqlSelection { |
| 192 | public StringBuilder mWhereClause = new StringBuilder(); |
| 193 | public List<String> mParameters = new ArrayList<String>(); |
| 194 | |
| 195 | public <T> void appendClause(String newClause, final T... parameters) { |
| 196 | if (newClause == null || newClause.isEmpty()) { |
| 197 | return; |
| 198 | } |
| 199 | if (mWhereClause.length() != 0) { |
| 200 | mWhereClause.append(" AND "); |
| 201 | } |
| 202 | mWhereClause.append("("); |
| 203 | mWhereClause.append(newClause); |
| 204 | mWhereClause.append(")"); |
| 205 | if (parameters != null) { |
| 206 | for (Object parameter : parameters) { |
| 207 | mParameters.add(parameter.toString()); |
| 208 | } |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | public String getSelection() { |
| 213 | return mWhereClause.toString(); |
| 214 | } |
| 215 | |
| 216 | public String[] getParameters() { |
| 217 | String[] array = new String[mParameters.size()]; |
| 218 | return mParameters.toArray(array); |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | /** |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 223 | * Creates and updated database on demand when opening it. |
| 224 | * Helper class to create database the first time the provider is |
| 225 | * initialized and upgrade it when a new version of the provider needs |
| 226 | * an updated version of the database. |
| 227 | */ |
| 228 | private final class DatabaseHelper extends SQLiteOpenHelper { |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 229 | public DatabaseHelper(final Context context) { |
| 230 | super(context, DB_NAME, null, DB_VERSION); |
| 231 | } |
| 232 | |
| 233 | /** |
| 234 | * Creates database the first time we try to open it. |
| 235 | */ |
| 236 | @Override |
| 237 | public void onCreate(final SQLiteDatabase db) { |
| 238 | if (Constants.LOGVV) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 239 | Log.v(Constants.TAG, "populating new database"); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 240 | } |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 241 | onUpgrade(db, 0, DB_VERSION); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 242 | } |
| 243 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 244 | /** |
| 245 | * Updates the database format when a content provider is used |
| 246 | * with a database that was created with a different format. |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 247 | * |
| 248 | * Note: to support downgrades, creating a table should always drop it first if it already |
| 249 | * exists. |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 250 | */ |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 251 | @Override |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 252 | public void onUpgrade(final SQLiteDatabase db, int oldV, final int newV) { |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 253 | if (oldV == 31) { |
| 254 | // 31 and 100 are identical, just in different codelines. Upgrading from 31 is the |
| 255 | // same as upgrading from 100. |
| 256 | oldV = 100; |
| 257 | } else if (oldV < 100) { |
| 258 | // no logic to upgrade from these older version, just recreate the DB |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 259 | Log.i(Constants.TAG, "Upgrading downloads database from version " + oldV |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 260 | + " to version " + newV + ", which will destroy all old data"); |
| 261 | oldV = 99; |
| 262 | } else if (oldV > newV) { |
| 263 | // user must have downgraded software; we have no way to know how to downgrade the |
| 264 | // DB, so just recreate it |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 265 | Log.i(Constants.TAG, "Downgrading downloads database from version " + oldV |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 266 | + " (current version is " + newV + "), destroying all old data"); |
| 267 | oldV = 99; |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 268 | } |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 269 | |
| 270 | for (int version = oldV + 1; version <= newV; version++) { |
| 271 | upgradeTo(db, version); |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | /** |
| 276 | * Upgrade database from (version - 1) to version. |
| 277 | */ |
| 278 | private void upgradeTo(SQLiteDatabase db, int version) { |
| 279 | switch (version) { |
| 280 | case 100: |
| 281 | createDownloadsTable(db); |
| 282 | break; |
| 283 | |
| 284 | case 101: |
| 285 | createHeadersTable(db); |
| 286 | break; |
| 287 | |
Steve Howard | 0a77c62 | 2010-07-21 11:41:30 -0700 | [diff] [blame] | 288 | case 102: |
| 289 | addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_IS_PUBLIC_API, |
| 290 | "INTEGER NOT NULL DEFAULT 0"); |
| 291 | addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_ALLOW_ROAMING, |
| 292 | "INTEGER NOT NULL DEFAULT 0"); |
| 293 | addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, |
| 294 | "INTEGER NOT NULL DEFAULT 0"); |
| 295 | break; |
| 296 | |
Steve Howard | 71e7fda | 2010-09-08 17:15:27 -0700 | [diff] [blame] | 297 | case 103: |
| 298 | addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, |
| 299 | "INTEGER NOT NULL DEFAULT 1"); |
| 300 | makeCacheDownloadsInvisible(db); |
| 301 | break; |
| 302 | |
Steve Howard | d319729 | 2010-09-17 16:45:58 -0700 | [diff] [blame] | 303 | case 104: |
| 304 | addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT, |
| 305 | "INTEGER NOT NULL DEFAULT 0"); |
| 306 | break; |
| 307 | |
Steve Howard | 73f5f22 | 2010-09-22 19:51:59 -0700 | [diff] [blame] | 308 | case 105: |
| 309 | fillNullValues(db); |
| 310 | break; |
| 311 | |
Vasu Nori | e00c312 | 2010-10-12 23:27:49 -0700 | [diff] [blame] | 312 | case 106: |
| 313 | addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_MEDIAPROVIDER_URI, "TEXT"); |
| 314 | addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_DELETED, |
| 315 | "BOOLEAN NOT NULL DEFAULT 0"); |
| 316 | break; |
| 317 | |
Vasu Nori | 9d27069 | 2010-11-11 15:26:07 -0800 | [diff] [blame] | 318 | case 107: |
| 319 | addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_ERROR_MSG, "TEXT"); |
| 320 | break; |
| 321 | |
Jeff Sharkey | a7ae77f | 2012-04-17 12:26:06 -0700 | [diff] [blame] | 322 | case 108: |
| 323 | addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_ALLOW_METERED, |
| 324 | "INTEGER NOT NULL DEFAULT 1"); |
| 325 | break; |
| 326 | |
Jeff Sharkey | c067c8b | 2013-09-23 14:21:55 -0700 | [diff] [blame] | 327 | case 109: |
| 328 | addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_ALLOW_WRITE, |
| 329 | "BOOLEAN NOT NULL DEFAULT 0"); |
| 330 | break; |
| 331 | |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 332 | default: |
| 333 | throw new IllegalStateException("Don't know how to upgrade to " + version); |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | /** |
Steve Howard | 73f5f22 | 2010-09-22 19:51:59 -0700 | [diff] [blame] | 338 | * insert() now ensures these four columns are never null for new downloads, so this method |
| 339 | * makes that true for existing columns, so that code can rely on this assumption. |
| 340 | */ |
| 341 | private void fillNullValues(SQLiteDatabase db) { |
| 342 | ContentValues values = new ContentValues(); |
| 343 | values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0); |
| 344 | fillNullValuesForColumn(db, values); |
| 345 | values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1); |
| 346 | fillNullValuesForColumn(db, values); |
| 347 | values.put(Downloads.Impl.COLUMN_TITLE, ""); |
| 348 | fillNullValuesForColumn(db, values); |
| 349 | values.put(Downloads.Impl.COLUMN_DESCRIPTION, ""); |
| 350 | fillNullValuesForColumn(db, values); |
| 351 | } |
| 352 | |
| 353 | private void fillNullValuesForColumn(SQLiteDatabase db, ContentValues values) { |
| 354 | String column = values.valueSet().iterator().next().getKey(); |
| 355 | db.update(DB_TABLE, values, column + " is null", null); |
| 356 | values.clear(); |
| 357 | } |
| 358 | |
| 359 | /** |
Steve Howard | 71e7fda | 2010-09-08 17:15:27 -0700 | [diff] [blame] | 360 | * Set all existing downloads to the cache partition to be invisible in the downloads UI. |
| 361 | */ |
| 362 | private void makeCacheDownloadsInvisible(SQLiteDatabase db) { |
| 363 | ContentValues values = new ContentValues(); |
| 364 | values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, false); |
| 365 | String cacheSelection = Downloads.Impl.COLUMN_DESTINATION |
| 366 | + " != " + Downloads.Impl.DESTINATION_EXTERNAL; |
| 367 | db.update(DB_TABLE, values, cacheSelection, null); |
| 368 | } |
| 369 | |
| 370 | /** |
Steve Howard | 0a77c62 | 2010-07-21 11:41:30 -0700 | [diff] [blame] | 371 | * Add a column to a table using ALTER TABLE. |
| 372 | * @param dbTable name of the table |
| 373 | * @param columnName name of the column to add |
| 374 | * @param columnDefinition SQL for the column definition |
| 375 | */ |
| 376 | private void addColumn(SQLiteDatabase db, String dbTable, String columnName, |
| 377 | String columnDefinition) { |
| 378 | db.execSQL("ALTER TABLE " + dbTable + " ADD COLUMN " + columnName + " " |
| 379 | + columnDefinition); |
| 380 | } |
| 381 | |
| 382 | /** |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 383 | * Creates the table that'll hold the download information. |
| 384 | */ |
| 385 | private void createDownloadsTable(SQLiteDatabase db) { |
| 386 | try { |
| 387 | db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE); |
| 388 | db.execSQL("CREATE TABLE " + DB_TABLE + "(" + |
| 389 | Downloads.Impl._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + |
| 390 | Downloads.Impl.COLUMN_URI + " TEXT, " + |
| 391 | Constants.RETRY_AFTER_X_REDIRECT_COUNT + " INTEGER, " + |
| 392 | Downloads.Impl.COLUMN_APP_DATA + " TEXT, " + |
| 393 | Downloads.Impl.COLUMN_NO_INTEGRITY + " BOOLEAN, " + |
| 394 | Downloads.Impl.COLUMN_FILE_NAME_HINT + " TEXT, " + |
Vasu Nori | f20af91 | 2011-02-03 12:07:29 -0800 | [diff] [blame] | 395 | Constants.OTA_UPDATE + " BOOLEAN, " + |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 396 | Downloads.Impl._DATA + " TEXT, " + |
| 397 | Downloads.Impl.COLUMN_MIME_TYPE + " TEXT, " + |
| 398 | Downloads.Impl.COLUMN_DESTINATION + " INTEGER, " + |
| 399 | Constants.NO_SYSTEM_FILES + " BOOLEAN, " + |
| 400 | Downloads.Impl.COLUMN_VISIBILITY + " INTEGER, " + |
| 401 | Downloads.Impl.COLUMN_CONTROL + " INTEGER, " + |
| 402 | Downloads.Impl.COLUMN_STATUS + " INTEGER, " + |
Jeff Sharkey | 12f5dc4 | 2013-01-17 17:26:51 -0800 | [diff] [blame] | 403 | Downloads.Impl.COLUMN_FAILED_CONNECTIONS + " INTEGER, " + |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 404 | Downloads.Impl.COLUMN_LAST_MODIFICATION + " BIGINT, " + |
| 405 | Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE + " TEXT, " + |
| 406 | Downloads.Impl.COLUMN_NOTIFICATION_CLASS + " TEXT, " + |
| 407 | Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS + " TEXT, " + |
| 408 | Downloads.Impl.COLUMN_COOKIE_DATA + " TEXT, " + |
| 409 | Downloads.Impl.COLUMN_USER_AGENT + " TEXT, " + |
| 410 | Downloads.Impl.COLUMN_REFERER + " TEXT, " + |
| 411 | Downloads.Impl.COLUMN_TOTAL_BYTES + " INTEGER, " + |
| 412 | Downloads.Impl.COLUMN_CURRENT_BYTES + " INTEGER, " + |
| 413 | Constants.ETAG + " TEXT, " + |
| 414 | Constants.UID + " INTEGER, " + |
| 415 | Downloads.Impl.COLUMN_OTHER_UID + " INTEGER, " + |
| 416 | Downloads.Impl.COLUMN_TITLE + " TEXT, " + |
| 417 | Downloads.Impl.COLUMN_DESCRIPTION + " TEXT, " + |
Jeff Sharkey | 495edec | 2014-08-05 11:51:24 -0700 | [diff] [blame] | 418 | Downloads.Impl.COLUMN_MEDIA_SCANNED + " BOOLEAN);"); |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 419 | } catch (SQLException ex) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 420 | Log.e(Constants.TAG, "couldn't create table in downloads database"); |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 421 | throw ex; |
| 422 | } |
| 423 | } |
| 424 | |
| 425 | private void createHeadersTable(SQLiteDatabase db) { |
| 426 | db.execSQL("DROP TABLE IF EXISTS " + Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE); |
| 427 | db.execSQL("CREATE TABLE " + Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE + "(" + |
| 428 | "id INTEGER PRIMARY KEY AUTOINCREMENT," + |
| 429 | Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + " INTEGER NOT NULL," + |
| 430 | Downloads.Impl.RequestHeaders.COLUMN_HEADER + " TEXT NOT NULL," + |
| 431 | Downloads.Impl.RequestHeaders.COLUMN_VALUE + " TEXT NOT NULL" + |
| 432 | ");"); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 433 | } |
| 434 | } |
| 435 | |
| 436 | /** |
| 437 | * Initializes the content provider when it is created. |
| 438 | */ |
| 439 | @Override |
| 440 | public boolean onCreate() { |
Steve Howard | 6d9b982 | 2010-07-12 17:24:17 -0700 | [diff] [blame] | 441 | if (mSystemFacade == null) { |
Steve Howard | af28400 | 2010-07-15 15:57:31 -0700 | [diff] [blame] | 442 | mSystemFacade = new RealSystemFacade(getContext()); |
Steve Howard | 6d9b982 | 2010-07-12 17:24:17 -0700 | [diff] [blame] | 443 | } |
| 444 | |
Todd Kennedy | f775c98 | 2015-06-19 14:47:45 -0700 | [diff] [blame] | 445 | HandlerThread handlerThread = |
| 446 | new HandlerThread("DownloadProvider handler", Process.THREAD_PRIORITY_BACKGROUND); |
| 447 | handlerThread.start(); |
| 448 | mHandler = new Handler(handlerThread.getLooper()); |
Jeff Sharkey | c067c8b | 2013-09-23 14:21:55 -0700 | [diff] [blame] | 449 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 450 | mOpenHelper = new DatabaseHelper(getContext()); |
Suchi Amalapurapu | 91e4522 | 2010-01-25 15:50:36 -0800 | [diff] [blame] | 451 | // Initialize the system uid |
| 452 | mSystemUid = Process.SYSTEM_UID; |
| 453 | // Initialize the default container uid. Package name hardcoded |
| 454 | // for now. |
| 455 | ApplicationInfo appInfo = null; |
| 456 | try { |
| 457 | appInfo = getContext().getPackageManager(). |
| 458 | getApplicationInfo("com.android.defcontainer", 0); |
| 459 | } catch (NameNotFoundException e) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 460 | Log.wtf(Constants.TAG, "Could not get ApplicationInfo for com.android.defconatiner", e); |
Suchi Amalapurapu | 91e4522 | 2010-01-25 15:50:36 -0800 | [diff] [blame] | 461 | } |
| 462 | if (appInfo != null) { |
| 463 | mDefContainerUid = appInfo.uid; |
| 464 | } |
Jeff Sharkey | 9144926 | 2016-09-16 12:12:17 -0600 | [diff] [blame] | 465 | |
| 466 | // Grant access permissions for all known downloads to the owning apps |
| 467 | final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); |
| 468 | final Cursor cursor = db.query(DB_TABLE, new String[] { |
| 469 | Downloads.Impl._ID, Constants.UID }, null, null, null, null, null); |
Suprabh Shukla | 8998652 | 2017-02-27 15:56:11 -0800 | [diff] [blame] | 470 | final ArrayList<Long> idsToDelete = new ArrayList<>(); |
Jeff Sharkey | 9144926 | 2016-09-16 12:12:17 -0600 | [diff] [blame] | 471 | try { |
| 472 | while (cursor.moveToNext()) { |
Suprabh Shukla | 8998652 | 2017-02-27 15:56:11 -0800 | [diff] [blame] | 473 | final long downloadId = cursor.getLong(0); |
| 474 | final int uid = cursor.getInt(1); |
| 475 | final String ownerPackage = getPackageForUid(uid); |
| 476 | if (ownerPackage == null) { |
| 477 | idsToDelete.add(downloadId); |
| 478 | } else { |
| 479 | grantAllDownloadsPermission(ownerPackage, downloadId); |
| 480 | } |
Jeff Sharkey | 9144926 | 2016-09-16 12:12:17 -0600 | [diff] [blame] | 481 | } |
| 482 | } finally { |
| 483 | cursor.close(); |
| 484 | } |
Suprabh Shukla | 8998652 | 2017-02-27 15:56:11 -0800 | [diff] [blame] | 485 | if (idsToDelete.size() > 0) { |
| 486 | Log.i(Constants.TAG, |
| 487 | "Deleting downloads with ids " + idsToDelete + " as owner package is missing"); |
| 488 | deleteDownloadsWithIds(idsToDelete); |
| 489 | } |
Jeff Sharkey | 9144926 | 2016-09-16 12:12:17 -0600 | [diff] [blame] | 490 | |
Vasu Nori | 5218d33 | 2010-12-16 18:31:23 -0800 | [diff] [blame] | 491 | // start the DownloadService class. don't wait for the 1st download to be issued. |
| 492 | // saves us by getting some initialization code in DownloadService out of the way. |
| 493 | Context context = getContext(); |
| 494 | context.startService(new Intent(context, DownloadService.class)); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 495 | return true; |
| 496 | } |
| 497 | |
Suprabh Shukla | 8998652 | 2017-02-27 15:56:11 -0800 | [diff] [blame] | 498 | private void deleteDownloadsWithIds(ArrayList<Long> downloadIds) { |
| 499 | final int N = downloadIds.size(); |
| 500 | if (N == 0) { |
| 501 | return; |
| 502 | } |
| 503 | final StringBuilder queryBuilder = new StringBuilder(Downloads.Impl._ID + " in ("); |
| 504 | for (int i = 0; i < N; i++) { |
| 505 | queryBuilder.append(downloadIds.get(i)); |
| 506 | queryBuilder.append((i == N - 1) ? ")" : ","); |
| 507 | } |
| 508 | delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, queryBuilder.toString(), null); |
| 509 | } |
| 510 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 511 | /** |
| 512 | * Returns the content-provider-style MIME types of the various |
| 513 | * types accessible through this content provider. |
| 514 | */ |
| 515 | @Override |
| 516 | public String getType(final Uri uri) { |
| 517 | int match = sURIMatcher.match(uri); |
| 518 | switch (match) { |
Jeff Sharkey | 9b60634 | 2012-04-16 14:29:04 -0700 | [diff] [blame] | 519 | case MY_DOWNLOADS: |
| 520 | case ALL_DOWNLOADS: { |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 521 | return DOWNLOAD_LIST_TYPE; |
| 522 | } |
Jeff Sharkey | 9b60634 | 2012-04-16 14:29:04 -0700 | [diff] [blame] | 523 | case MY_DOWNLOADS_ID: |
Jeff Sharkey | c3f3d99 | 2012-04-19 10:10:53 -0700 | [diff] [blame] | 524 | case ALL_DOWNLOADS_ID: |
Vasu Nori | 3ca6774 | 2010-11-04 15:03:31 -0700 | [diff] [blame] | 525 | case PUBLIC_DOWNLOAD_ID: { |
| 526 | // return the mimetype of this id from the database |
| 527 | final String id = getDownloadIdFromUri(uri); |
| 528 | final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); |
Jeff Sharkey | c3f3d99 | 2012-04-19 10:10:53 -0700 | [diff] [blame] | 529 | final String mimeType = DatabaseUtils.stringForQuery(db, |
Vasu Nori | 3ca6774 | 2010-11-04 15:03:31 -0700 | [diff] [blame] | 530 | "SELECT " + Downloads.Impl.COLUMN_MIME_TYPE + " FROM " + DB_TABLE + |
| 531 | " WHERE " + Downloads.Impl._ID + " = ?", |
| 532 | new String[]{id}); |
Jeff Sharkey | c3f3d99 | 2012-04-19 10:10:53 -0700 | [diff] [blame] | 533 | if (TextUtils.isEmpty(mimeType)) { |
| 534 | return DOWNLOAD_TYPE; |
| 535 | } else { |
| 536 | return mimeType; |
| 537 | } |
Vasu Nori | 3ca6774 | 2010-11-04 15:03:31 -0700 | [diff] [blame] | 538 | } |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 539 | default: { |
| 540 | if (Constants.LOGV) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 541 | Log.v(Constants.TAG, "calling getType on an unknown URI: " + uri); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 542 | } |
| 543 | throw new IllegalArgumentException("Unknown URI: " + uri); |
| 544 | } |
| 545 | } |
| 546 | } |
| 547 | |
| 548 | /** |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 549 | * Inserts a row in the database |
| 550 | */ |
| 551 | @Override |
| 552 | public Uri insert(final Uri uri, final ContentValues values) { |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 553 | checkInsertPermissions(values); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 554 | SQLiteDatabase db = mOpenHelper.getWritableDatabase(); |
| 555 | |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 556 | // note we disallow inserting into ALL_DOWNLOADS |
| 557 | int match = sURIMatcher.match(uri); |
| 558 | if (match != MY_DOWNLOADS) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 559 | Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: " + uri); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 560 | throw new IllegalArgumentException("Unknown/Invalid URI " + uri); |
| 561 | } |
| 562 | |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 563 | // copy some of the input values as it |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 564 | ContentValues filteredValues = new ContentValues(); |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 565 | copyString(Downloads.Impl.COLUMN_URI, values, filteredValues); |
| 566 | copyString(Downloads.Impl.COLUMN_APP_DATA, values, filteredValues); |
| 567 | copyBoolean(Downloads.Impl.COLUMN_NO_INTEGRITY, values, filteredValues); |
| 568 | copyString(Downloads.Impl.COLUMN_FILE_NAME_HINT, values, filteredValues); |
| 569 | copyString(Downloads.Impl.COLUMN_MIME_TYPE, values, filteredValues); |
Steve Howard | 71aab52 | 2010-07-20 16:32:31 -0700 | [diff] [blame] | 570 | copyBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API, values, filteredValues); |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 571 | |
Steve Howard | 71aab52 | 2010-07-20 16:32:31 -0700 | [diff] [blame] | 572 | boolean isPublicApi = |
| 573 | values.getAsBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API) == Boolean.TRUE; |
| 574 | |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 575 | // validate the destination column |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 576 | Integer dest = values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION); |
Jean-Baptiste Queru | ce8813a | 2009-02-06 11:09:27 -0800 | [diff] [blame] | 577 | if (dest != null) { |
Jeff Sharkey | dffbb9c | 2014-01-30 15:01:39 -0800 | [diff] [blame] | 578 | if (getContext().checkCallingOrSelfPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED) |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 579 | != PackageManager.PERMISSION_GRANTED |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 580 | && (dest == Downloads.Impl.DESTINATION_CACHE_PARTITION |
| 581 | || dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING |
| 582 | || dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION)) { |
Vasu Nori | 9aadb4b | 2010-12-13 16:29:29 -0800 | [diff] [blame] | 583 | throw new SecurityException("setting destination to : " + dest + |
| 584 | " not allowed, unless PERMISSION_ACCESS_ADVANCED is granted"); |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 585 | } |
Steve Howard | 71aab52 | 2010-07-20 16:32:31 -0700 | [diff] [blame] | 586 | // for public API behavior, if an app has CACHE_NON_PURGEABLE permission, automatically |
| 587 | // switch to non-purgeable download |
| 588 | boolean hasNonPurgeablePermission = |
Jeff Sharkey | dffbb9c | 2014-01-30 15:01:39 -0800 | [diff] [blame] | 589 | getContext().checkCallingOrSelfPermission( |
Steve Howard | 71aab52 | 2010-07-20 16:32:31 -0700 | [diff] [blame] | 590 | Downloads.Impl.PERMISSION_CACHE_NON_PURGEABLE) |
| 591 | == PackageManager.PERMISSION_GRANTED; |
| 592 | if (isPublicApi && dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE |
| 593 | && hasNonPurgeablePermission) { |
| 594 | dest = Downloads.Impl.DESTINATION_CACHE_PARTITION; |
| 595 | } |
Steve Howard | 6d9b982 | 2010-07-12 17:24:17 -0700 | [diff] [blame] | 596 | if (dest == Downloads.Impl.DESTINATION_FILE_URI) { |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 597 | checkFileUriDestination(values); |
Jeff Sharkey | ed30dea | 2015-07-13 10:25:58 -0700 | [diff] [blame] | 598 | |
| 599 | } else if (dest == Downloads.Impl.DESTINATION_EXTERNAL) { |
| 600 | getContext().enforceCallingOrSelfPermission( |
| 601 | android.Manifest.permission.WRITE_EXTERNAL_STORAGE, |
| 602 | "No permission to write"); |
| 603 | |
| 604 | final AppOpsManager appOps = getContext().getSystemService(AppOpsManager.class); |
Jeff Sharkey | b8bc2a7 | 2015-07-24 15:22:54 -0700 | [diff] [blame] | 605 | if (appOps.noteProxyOp(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE, |
Jeff Sharkey | ed30dea | 2015-07-13 10:25:58 -0700 | [diff] [blame] | 606 | getCallingPackage()) != AppOpsManager.MODE_ALLOWED) { |
| 607 | throw new SecurityException("No permission to write"); |
| 608 | } |
| 609 | |
Vasu Nori | 9aadb4b | 2010-12-13 16:29:29 -0800 | [diff] [blame] | 610 | } else if (dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) { |
| 611 | getContext().enforcePermission( |
| 612 | android.Manifest.permission.ACCESS_CACHE_FILESYSTEM, |
| 613 | Binder.getCallingPid(), Binder.getCallingUid(), |
| 614 | "need ACCESS_CACHE_FILESYSTEM permission to use system cache"); |
Steve Howard | 6d9b982 | 2010-07-12 17:24:17 -0700 | [diff] [blame] | 615 | } |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 616 | filteredValues.put(Downloads.Impl.COLUMN_DESTINATION, dest); |
Jean-Baptiste Queru | ce8813a | 2009-02-06 11:09:27 -0800 | [diff] [blame] | 617 | } |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 618 | |
| 619 | // validate the visibility column |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 620 | Integer vis = values.getAsInteger(Downloads.Impl.COLUMN_VISIBILITY); |
Jean-Baptiste Queru | ce8813a | 2009-02-06 11:09:27 -0800 | [diff] [blame] | 621 | if (vis == null) { |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 622 | if (dest == Downloads.Impl.DESTINATION_EXTERNAL) { |
| 623 | filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY, |
| 624 | Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); |
Jean-Baptiste Queru | ce8813a | 2009-02-06 11:09:27 -0800 | [diff] [blame] | 625 | } else { |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 626 | filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY, |
| 627 | Downloads.Impl.VISIBILITY_HIDDEN); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 628 | } |
Jean-Baptiste Queru | ce8813a | 2009-02-06 11:09:27 -0800 | [diff] [blame] | 629 | } else { |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 630 | filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY, vis); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 631 | } |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 632 | // copy the control column as is |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 633 | copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues); |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 634 | |
| 635 | /* |
| 636 | * requests coming from |
Vasu Nori | 9b2576f | 2011-03-07 11:22:11 -0800 | [diff] [blame] | 637 | * DownloadManager.addCompletedDownload(String, String, String, |
| 638 | * boolean, String, String, long) need special treatment |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 639 | */ |
| 640 | if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) == |
| 641 | Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) { |
| 642 | // these requests always are marked as 'completed' |
| 643 | filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS); |
| 644 | filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES, |
| 645 | values.getAsLong(Downloads.Impl.COLUMN_TOTAL_BYTES)); |
| 646 | filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0); |
| 647 | copyInteger(Downloads.Impl.COLUMN_MEDIA_SCANNED, values, filteredValues); |
| 648 | copyString(Downloads.Impl._DATA, values, filteredValues); |
Jeff Sharkey | c067c8b | 2013-09-23 14:21:55 -0700 | [diff] [blame] | 649 | copyBoolean(Downloads.Impl.COLUMN_ALLOW_WRITE, values, filteredValues); |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 650 | } else { |
| 651 | filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); |
| 652 | filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1); |
| 653 | filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0); |
| 654 | } |
| 655 | |
| 656 | // set lastupdate to current time |
Vasu Nori | 2c02577 | 2011-02-10 15:01:29 -0800 | [diff] [blame] | 657 | long lastMod = mSystemFacade.currentTimeMillis(); |
| 658 | filteredValues.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, lastMod); |
Steve Howard | 0a77c62 | 2010-07-21 11:41:30 -0700 | [diff] [blame] | 659 | |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 660 | // use packagename of the caller to set the notification columns |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 661 | String pckg = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE); |
| 662 | String clazz = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_CLASS); |
Steve Howard | 0a77c62 | 2010-07-21 11:41:30 -0700 | [diff] [blame] | 663 | if (pckg != null && (clazz != null || isPublicApi)) { |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 664 | int uid = Binder.getCallingUid(); |
| 665 | try { |
Steve Howard | 0a77c62 | 2010-07-21 11:41:30 -0700 | [diff] [blame] | 666 | if (uid == 0 || mSystemFacade.userOwnsPackage(uid, pckg)) { |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 667 | filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, pckg); |
Steve Howard | 0a77c62 | 2010-07-21 11:41:30 -0700 | [diff] [blame] | 668 | if (clazz != null) { |
| 669 | filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS, clazz); |
| 670 | } |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 671 | } |
| 672 | } catch (PackageManager.NameNotFoundException ex) { |
| 673 | /* ignored for now */ |
| 674 | } |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 675 | } |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 676 | |
| 677 | // copy some more columns as is |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 678 | copyString(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, values, filteredValues); |
| 679 | copyString(Downloads.Impl.COLUMN_COOKIE_DATA, values, filteredValues); |
| 680 | copyString(Downloads.Impl.COLUMN_USER_AGENT, values, filteredValues); |
| 681 | copyString(Downloads.Impl.COLUMN_REFERER, values, filteredValues); |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 682 | |
| 683 | // UID, PID columns |
Jeff Sharkey | dffbb9c | 2014-01-30 15:01:39 -0800 | [diff] [blame] | 684 | if (getContext().checkCallingOrSelfPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED) |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 685 | == PackageManager.PERMISSION_GRANTED) { |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 686 | copyInteger(Downloads.Impl.COLUMN_OTHER_UID, values, filteredValues); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 687 | } |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 688 | filteredValues.put(Constants.UID, Binder.getCallingUid()); |
| 689 | if (Binder.getCallingUid() == 0) { |
| 690 | copyInteger(Constants.UID, values, filteredValues); |
| 691 | } |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 692 | |
| 693 | // copy some more columns as is |
Steve Howard | a89321e | 2010-08-03 12:39:35 -0700 | [diff] [blame] | 694 | copyStringWithDefault(Downloads.Impl.COLUMN_TITLE, values, filteredValues, ""); |
| 695 | copyStringWithDefault(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues, ""); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 696 | |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 697 | // is_visible_in_downloads_ui column |
Steve Howard | 71e7fda | 2010-09-08 17:15:27 -0700 | [diff] [blame] | 698 | if (values.containsKey(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI)) { |
| 699 | copyBoolean(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, values, filteredValues); |
| 700 | } else { |
| 701 | // by default, make external downloads visible in the UI |
| 702 | boolean isExternal = (dest == null || dest == Downloads.Impl.DESTINATION_EXTERNAL); |
| 703 | filteredValues.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, isExternal); |
| 704 | } |
| 705 | |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 706 | // public api requests and networktypes/roaming columns |
Steve Howard | 0a77c62 | 2010-07-21 11:41:30 -0700 | [diff] [blame] | 707 | if (isPublicApi) { |
| 708 | copyInteger(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, values, filteredValues); |
| 709 | copyBoolean(Downloads.Impl.COLUMN_ALLOW_ROAMING, values, filteredValues); |
Jeff Sharkey | a7ae77f | 2012-04-17 12:26:06 -0700 | [diff] [blame] | 710 | copyBoolean(Downloads.Impl.COLUMN_ALLOW_METERED, values, filteredValues); |
Steve Howard | 0a77c62 | 2010-07-21 11:41:30 -0700 | [diff] [blame] | 711 | } |
| 712 | |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 713 | if (Constants.LOGVV) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 714 | Log.v(Constants.TAG, "initiating download with UID " |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 715 | + filteredValues.getAsInteger(Constants.UID)); |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 716 | if (filteredValues.containsKey(Downloads.Impl.COLUMN_OTHER_UID)) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 717 | Log.v(Constants.TAG, "other UID " + |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 718 | filteredValues.getAsInteger(Downloads.Impl.COLUMN_OTHER_UID)); |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 719 | } |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 720 | } |
| 721 | |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 722 | long rowID = db.insert(DB_TABLE, null, filteredValues); |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 723 | if (rowID == -1) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 724 | Log.d(Constants.TAG, "couldn't insert into downloads database"); |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 725 | return null; |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 726 | } |
| 727 | |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 728 | insertRequestHeaders(db, rowID, values); |
Suprabh Shukla | 8998652 | 2017-02-27 15:56:11 -0800 | [diff] [blame] | 729 | |
| 730 | final String callingPackage = getPackageForUid(Binder.getCallingUid()); |
| 731 | if (callingPackage == null) { |
| 732 | Log.e(Constants.TAG, "Package does not exist for calling uid"); |
| 733 | return null; |
| 734 | } |
| 735 | grantAllDownloadsPermission(callingPackage, rowID); |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 736 | notifyContentChanged(uri, match); |
Jeff Sharkey | 1225c34 | 2013-10-04 16:02:50 -0700 | [diff] [blame] | 737 | |
| 738 | // Always start service to handle notifications and/or scanning |
| 739 | final Context context = getContext(); |
| 740 | context.startService(new Intent(context, DownloadService.class)); |
| 741 | |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 742 | return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 743 | } |
| 744 | |
Suprabh Shukla | 8998652 | 2017-02-27 15:56:11 -0800 | [diff] [blame] | 745 | private String getPackageForUid(int uid) { |
| 746 | String[] packages = getContext().getPackageManager().getPackagesForUid(uid); |
| 747 | if (packages == null || packages.length == 0) { |
| 748 | return null; |
| 749 | } |
| 750 | // For permission related purposes, any package belonging to the given uid should work. |
| 751 | return packages[0]; |
| 752 | } |
| 753 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 754 | /** |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 755 | * Check that the file URI provided for DESTINATION_FILE_URI is valid. |
| 756 | */ |
| 757 | private void checkFileUriDestination(ContentValues values) { |
| 758 | String fileUri = values.getAsString(Downloads.Impl.COLUMN_FILE_NAME_HINT); |
| 759 | if (fileUri == null) { |
| 760 | throw new IllegalArgumentException( |
| 761 | "DESTINATION_FILE_URI must include a file URI under COLUMN_FILE_NAME_HINT"); |
| 762 | } |
| 763 | Uri uri = Uri.parse(fileUri); |
Steve Howard | 5d81e24 | 2010-09-30 19:45:56 -0700 | [diff] [blame] | 764 | String scheme = uri.getScheme(); |
| 765 | if (scheme == null || !scheme.equals("file")) { |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 766 | throw new IllegalArgumentException("Not a file URI: " + uri); |
| 767 | } |
Jeff Sharkey | d195a5c | 2011-10-12 12:04:10 -0700 | [diff] [blame] | 768 | final String path = uri.getPath(); |
Steve Howard | b9a0ad7 | 2010-09-16 12:04:17 -0700 | [diff] [blame] | 769 | if (path == null) { |
| 770 | throw new IllegalArgumentException("Invalid file URI: " + uri); |
| 771 | } |
Jeff Sharkey | ed30dea | 2015-07-13 10:25:58 -0700 | [diff] [blame] | 772 | |
| 773 | final File file = new File(path); |
| 774 | if (Helpers.isFilenameValidInExternalPackage(getContext(), file, getCallingPackage())) { |
| 775 | // No permissions required for paths belonging to calling package |
| 776 | return; |
| 777 | } else if (Helpers.isFilenameValidInExternal(getContext(), file)) { |
| 778 | // Otherwise we require write permission |
| 779 | getContext().enforceCallingOrSelfPermission( |
| 780 | android.Manifest.permission.WRITE_EXTERNAL_STORAGE, |
| 781 | "No permission to write to " + file); |
| 782 | |
| 783 | final AppOpsManager appOps = getContext().getSystemService(AppOpsManager.class); |
Jeff Sharkey | b8bc2a7 | 2015-07-24 15:22:54 -0700 | [diff] [blame] | 784 | if (appOps.noteProxyOp(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE, |
Jeff Sharkey | ed30dea | 2015-07-13 10:25:58 -0700 | [diff] [blame] | 785 | getCallingPackage()) != AppOpsManager.MODE_ALLOWED) { |
| 786 | throw new SecurityException("No permission to write to " + file); |
Jeff Sharkey | d195a5c | 2011-10-12 12:04:10 -0700 | [diff] [blame] | 787 | } |
Jeff Sharkey | ed30dea | 2015-07-13 10:25:58 -0700 | [diff] [blame] | 788 | |
| 789 | } else { |
| 790 | throw new SecurityException("Unsupported path " + file); |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 791 | } |
| 792 | } |
| 793 | |
| 794 | /** |
| 795 | * Apps with the ACCESS_DOWNLOAD_MANAGER permission can access this provider freely, subject to |
| 796 | * constraints in the rest of the code. Apps without that may still access this provider through |
| 797 | * the public API, but additional restrictions are imposed. We check those restrictions here. |
| 798 | * |
| 799 | * @param values ContentValues provided to insert() |
| 800 | * @throws SecurityException if the caller has insufficient permissions |
| 801 | */ |
| 802 | private void checkInsertPermissions(ContentValues values) { |
| 803 | if (getContext().checkCallingOrSelfPermission(Downloads.Impl.PERMISSION_ACCESS) |
| 804 | == PackageManager.PERMISSION_GRANTED) { |
| 805 | return; |
| 806 | } |
| 807 | |
| 808 | getContext().enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, |
| 809 | "INTERNET permission is required to use the download manager"); |
| 810 | |
| 811 | // ensure the request fits within the bounds of a public API request |
| 812 | // first copy so we can remove values |
| 813 | values = new ContentValues(values); |
| 814 | |
| 815 | // check columns whose values are restricted |
| 816 | enforceAllowedValues(values, Downloads.Impl.COLUMN_IS_PUBLIC_API, Boolean.TRUE); |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 817 | |
| 818 | // validate the destination column |
| 819 | if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) == |
| 820 | Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) { |
| 821 | /* this row is inserted by |
Vasu Nori | 9b2576f | 2011-03-07 11:22:11 -0800 | [diff] [blame] | 822 | * DownloadManager.addCompletedDownload(String, String, String, |
| 823 | * boolean, String, String, long) |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 824 | */ |
| 825 | values.remove(Downloads.Impl.COLUMN_TOTAL_BYTES); |
| 826 | values.remove(Downloads.Impl._DATA); |
| 827 | values.remove(Downloads.Impl.COLUMN_STATUS); |
| 828 | } |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 829 | enforceAllowedValues(values, Downloads.Impl.COLUMN_DESTINATION, |
| 830 | Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE, |
Vasu Nori | b18ed51 | 2011-01-20 17:59:34 -0800 | [diff] [blame] | 831 | Downloads.Impl.DESTINATION_FILE_URI, |
| 832 | Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD); |
Steve Howard | 9da9df3 | 2010-07-28 17:51:02 -0700 | [diff] [blame] | 833 | |
| 834 | if (getContext().checkCallingOrSelfPermission(Downloads.Impl.PERMISSION_NO_NOTIFICATION) |
| 835 | == PackageManager.PERMISSION_GRANTED) { |
| 836 | enforceAllowedValues(values, Downloads.Impl.COLUMN_VISIBILITY, |
Jeff Sharkey | 51cc214 | 2011-04-13 12:39:50 -0700 | [diff] [blame] | 837 | Request.VISIBILITY_HIDDEN, |
| 838 | Request.VISIBILITY_VISIBLE, |
| 839 | Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED, |
| 840 | Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION); |
Steve Howard | 9da9df3 | 2010-07-28 17:51:02 -0700 | [diff] [blame] | 841 | } else { |
| 842 | enforceAllowedValues(values, Downloads.Impl.COLUMN_VISIBILITY, |
Jeff Sharkey | 51cc214 | 2011-04-13 12:39:50 -0700 | [diff] [blame] | 843 | Request.VISIBILITY_VISIBLE, |
| 844 | Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED, |
| 845 | Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION); |
Steve Howard | 9da9df3 | 2010-07-28 17:51:02 -0700 | [diff] [blame] | 846 | } |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 847 | |
| 848 | // remove the rest of the columns that are allowed (with any value) |
| 849 | values.remove(Downloads.Impl.COLUMN_URI); |
| 850 | values.remove(Downloads.Impl.COLUMN_TITLE); |
| 851 | values.remove(Downloads.Impl.COLUMN_DESCRIPTION); |
| 852 | values.remove(Downloads.Impl.COLUMN_MIME_TYPE); |
| 853 | values.remove(Downloads.Impl.COLUMN_FILE_NAME_HINT); // checked later in insert() |
| 854 | values.remove(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE); // checked later in insert() |
| 855 | values.remove(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES); |
| 856 | values.remove(Downloads.Impl.COLUMN_ALLOW_ROAMING); |
Jeff Sharkey | a7ae77f | 2012-04-17 12:26:06 -0700 | [diff] [blame] | 857 | values.remove(Downloads.Impl.COLUMN_ALLOW_METERED); |
Steve Howard | 71e7fda | 2010-09-08 17:15:27 -0700 | [diff] [blame] | 858 | values.remove(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI); |
Vasu Nori | 3002e18 | 2010-11-05 11:00:31 -0700 | [diff] [blame] | 859 | values.remove(Downloads.Impl.COLUMN_MEDIA_SCANNED); |
Jeff Sharkey | c067c8b | 2013-09-23 14:21:55 -0700 | [diff] [blame] | 860 | values.remove(Downloads.Impl.COLUMN_ALLOW_WRITE); |
Steve Howard | e61798d | 2010-08-02 11:52:16 -0700 | [diff] [blame] | 861 | Iterator<Map.Entry<String, Object>> iterator = values.valueSet().iterator(); |
| 862 | while (iterator.hasNext()) { |
| 863 | String key = iterator.next().getKey(); |
| 864 | if (key.startsWith(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX)) { |
| 865 | iterator.remove(); |
| 866 | } |
| 867 | } |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 868 | |
| 869 | // any extra columns are extraneous and disallowed |
| 870 | if (values.size() > 0) { |
| 871 | StringBuilder error = new StringBuilder("Invalid columns in request: "); |
| 872 | boolean first = true; |
| 873 | for (Map.Entry<String, Object> entry : values.valueSet()) { |
| 874 | if (!first) { |
| 875 | error.append(", "); |
| 876 | } |
| 877 | error.append(entry.getKey()); |
| 878 | } |
| 879 | throw new SecurityException(error.toString()); |
| 880 | } |
| 881 | } |
| 882 | |
| 883 | /** |
| 884 | * Remove column from values, and throw a SecurityException if the value isn't within the |
| 885 | * specified allowedValues. |
| 886 | */ |
| 887 | private void enforceAllowedValues(ContentValues values, String column, |
| 888 | Object... allowedValues) { |
| 889 | Object value = values.get(column); |
| 890 | values.remove(column); |
| 891 | for (Object allowedValue : allowedValues) { |
| 892 | if (value == null && allowedValue == null) { |
| 893 | return; |
| 894 | } |
| 895 | if (value != null && value.equals(allowedValue)) { |
| 896 | return; |
| 897 | } |
| 898 | } |
| 899 | throw new SecurityException("Invalid value for " + column + ": " + value); |
| 900 | } |
| 901 | |
Jeff Sharkey | 2e16979 | 2014-08-05 18:04:54 -0700 | [diff] [blame] | 902 | private Cursor queryCleared(Uri uri, String[] projection, String selection, |
| 903 | String[] selectionArgs, String sort) { |
| 904 | final long token = Binder.clearCallingIdentity(); |
| 905 | try { |
| 906 | return query(uri, projection, selection, selectionArgs, sort); |
| 907 | } finally { |
| 908 | Binder.restoreCallingIdentity(token); |
| 909 | } |
| 910 | } |
| 911 | |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 912 | /** |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 913 | * Starts a database query |
| 914 | */ |
| 915 | @Override |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 916 | public Cursor query(final Uri uri, String[] projection, |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 917 | final String selection, final String[] selectionArgs, |
| 918 | final String sort) { |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 919 | |
| 920 | Helpers.validateSelection(selection, sAppReadableColumnsSet); |
| 921 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 922 | SQLiteDatabase db = mOpenHelper.getReadableDatabase(); |
| 923 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 924 | int match = sURIMatcher.match(uri); |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 925 | if (match == -1) { |
| 926 | if (Constants.LOGV) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 927 | Log.v(Constants.TAG, "querying unknown URI: " + uri); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 928 | } |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 929 | throw new IllegalArgumentException("Unknown URI: " + uri); |
| 930 | } |
| 931 | |
| 932 | if (match == REQUEST_HEADERS_URI) { |
| 933 | if (projection != null || selection != null || sort != null) { |
| 934 | throw new UnsupportedOperationException("Request header queries do not support " |
| 935 | + "projections, selections or sorting"); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 936 | } |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 937 | return queryRequestHeaders(db, uri); |
| 938 | } |
| 939 | |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 940 | SqlSelection fullSelection = getWhereClause(uri, selection, selectionArgs, match); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 941 | |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 942 | if (shouldRestrictVisibility()) { |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 943 | if (projection == null) { |
Jeff Sharkey | 9b60634 | 2012-04-16 14:29:04 -0700 | [diff] [blame] | 944 | projection = sAppReadableColumnsArray.clone(); |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 945 | } else { |
Vasu Nori | 01d0182 | 2010-11-09 12:30:19 -0800 | [diff] [blame] | 946 | // check the validity of the columns in projection |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 947 | for (int i = 0; i < projection.length; ++i) { |
Vasu Nori | 01d0182 | 2010-11-09 12:30:19 -0800 | [diff] [blame] | 948 | if (!sAppReadableColumnsSet.contains(projection[i]) && |
| 949 | !downloadManagerColumnsList.contains(projection[i])) { |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 950 | throw new IllegalArgumentException( |
| 951 | "column " + projection[i] + " is not allowed in queries"); |
| 952 | } |
| 953 | } |
| 954 | } |
Jeff Sharkey | 9b60634 | 2012-04-16 14:29:04 -0700 | [diff] [blame] | 955 | |
| 956 | for (int i = 0; i < projection.length; i++) { |
| 957 | final String newColumn = sColumnsMap.get(projection[i]); |
| 958 | if (newColumn != null) { |
| 959 | projection[i] = newColumn; |
| 960 | } |
| 961 | } |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 962 | } |
| 963 | |
| 964 | if (Constants.LOGVV) { |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 965 | logVerboseQueryInfo(projection, selection, selectionArgs, sort, db); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 966 | } |
| 967 | |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 968 | Cursor ret = db.query(DB_TABLE, projection, fullSelection.getSelection(), |
| 969 | fullSelection.getParameters(), null, null, sort); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 970 | |
| 971 | if (ret != null) { |
| 972 | ret.setNotificationUri(getContext().getContentResolver(), uri); |
| 973 | if (Constants.LOGVV) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 974 | Log.v(Constants.TAG, |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 975 | "created cursor " + ret + " on behalf of " + Binder.getCallingPid()); |
| 976 | } |
| 977 | } else { |
| 978 | if (Constants.LOGV) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 979 | Log.v(Constants.TAG, "query failed in downloads database"); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 980 | } |
| 981 | } |
| 982 | |
| 983 | return ret; |
| 984 | } |
| 985 | |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 986 | private void logVerboseQueryInfo(String[] projection, final String selection, |
| 987 | final String[] selectionArgs, final String sort, SQLiteDatabase db) { |
| 988 | java.lang.StringBuilder sb = new java.lang.StringBuilder(); |
| 989 | sb.append("starting query, database is "); |
| 990 | if (db != null) { |
| 991 | sb.append("not "); |
| 992 | } |
| 993 | sb.append("null; "); |
| 994 | if (projection == null) { |
| 995 | sb.append("projection is null; "); |
| 996 | } else if (projection.length == 0) { |
| 997 | sb.append("projection is empty; "); |
| 998 | } else { |
| 999 | for (int i = 0; i < projection.length; ++i) { |
| 1000 | sb.append("projection["); |
| 1001 | sb.append(i); |
| 1002 | sb.append("] is "); |
| 1003 | sb.append(projection[i]); |
| 1004 | sb.append("; "); |
| 1005 | } |
| 1006 | } |
| 1007 | sb.append("selection is "); |
| 1008 | sb.append(selection); |
| 1009 | sb.append("; "); |
| 1010 | if (selectionArgs == null) { |
| 1011 | sb.append("selectionArgs is null; "); |
| 1012 | } else if (selectionArgs.length == 0) { |
| 1013 | sb.append("selectionArgs is empty; "); |
| 1014 | } else { |
| 1015 | for (int i = 0; i < selectionArgs.length; ++i) { |
| 1016 | sb.append("selectionArgs["); |
| 1017 | sb.append(i); |
| 1018 | sb.append("] is "); |
| 1019 | sb.append(selectionArgs[i]); |
| 1020 | sb.append("; "); |
| 1021 | } |
| 1022 | } |
| 1023 | sb.append("sort is "); |
| 1024 | sb.append(sort); |
| 1025 | sb.append("."); |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 1026 | Log.v(Constants.TAG, sb.toString()); |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1027 | } |
| 1028 | |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 1029 | private String getDownloadIdFromUri(final Uri uri) { |
| 1030 | return uri.getPathSegments().get(1); |
| 1031 | } |
| 1032 | |
| 1033 | /** |
| 1034 | * Insert request headers for a download into the DB. |
| 1035 | */ |
| 1036 | private void insertRequestHeaders(SQLiteDatabase db, long downloadId, ContentValues values) { |
| 1037 | ContentValues rowValues = new ContentValues(); |
| 1038 | rowValues.put(Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID, downloadId); |
| 1039 | for (Map.Entry<String, Object> entry : values.valueSet()) { |
| 1040 | String key = entry.getKey(); |
| 1041 | if (key.startsWith(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX)) { |
| 1042 | String headerLine = entry.getValue().toString(); |
| 1043 | if (!headerLine.contains(":")) { |
| 1044 | throw new IllegalArgumentException("Invalid HTTP header line: " + headerLine); |
| 1045 | } |
| 1046 | String[] parts = headerLine.split(":", 2); |
| 1047 | rowValues.put(Downloads.Impl.RequestHeaders.COLUMN_HEADER, parts[0].trim()); |
| 1048 | rowValues.put(Downloads.Impl.RequestHeaders.COLUMN_VALUE, parts[1].trim()); |
| 1049 | db.insert(Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE, null, rowValues); |
| 1050 | } |
| 1051 | } |
| 1052 | } |
| 1053 | |
| 1054 | /** |
| 1055 | * Handle a query for the custom request headers registered for a download. |
| 1056 | */ |
| 1057 | private Cursor queryRequestHeaders(SQLiteDatabase db, Uri uri) { |
| 1058 | String where = Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + "=" |
| 1059 | + getDownloadIdFromUri(uri); |
| 1060 | String[] projection = new String[] {Downloads.Impl.RequestHeaders.COLUMN_HEADER, |
| 1061 | Downloads.Impl.RequestHeaders.COLUMN_VALUE}; |
Steve Howard | 53356ad | 2010-07-15 13:43:01 -0700 | [diff] [blame] | 1062 | return db.query(Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE, projection, where, |
| 1063 | null, null, null, null); |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 1064 | } |
| 1065 | |
| 1066 | /** |
| 1067 | * Delete request headers for downloads matching the given query. |
| 1068 | */ |
| 1069 | private void deleteRequestHeaders(SQLiteDatabase db, String where, String[] whereArgs) { |
| 1070 | String[] projection = new String[] {Downloads.Impl._ID}; |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 1071 | Cursor cursor = db.query(DB_TABLE, projection, where, whereArgs, null, null, null, null); |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 1072 | try { |
| 1073 | for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { |
| 1074 | long id = cursor.getLong(0); |
| 1075 | String idWhere = Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + "=" + id; |
| 1076 | db.delete(Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE, idWhere, null); |
| 1077 | } |
| 1078 | } finally { |
| 1079 | cursor.close(); |
| 1080 | } |
| 1081 | } |
| 1082 | |
| 1083 | /** |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1084 | * @return true if we should restrict the columns readable by this caller |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 1085 | */ |
| 1086 | private boolean shouldRestrictVisibility() { |
| 1087 | int callingUid = Binder.getCallingUid(); |
| 1088 | return Binder.getCallingPid() != Process.myPid() && |
| 1089 | callingUid != mSystemUid && |
Jeff Brown | 12c7bd4 | 2011-07-08 18:53:38 -0700 | [diff] [blame] | 1090 | callingUid != mDefContainerUid; |
Steve Howard | 5224c6f | 2010-07-14 11:30:59 -0700 | [diff] [blame] | 1091 | } |
| 1092 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1093 | /** |
| 1094 | * Updates a row in the database |
| 1095 | */ |
| 1096 | @Override |
| 1097 | public int update(final Uri uri, final ContentValues values, |
| 1098 | final String where, final String[] whereArgs) { |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1099 | |
| 1100 | Helpers.validateSelection(where, sAppReadableColumnsSet); |
| 1101 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1102 | SQLiteDatabase db = mOpenHelper.getWritableDatabase(); |
| 1103 | |
| 1104 | int count; |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1105 | boolean startService = false; |
| 1106 | |
Vasu Nori | e00c312 | 2010-10-12 23:27:49 -0700 | [diff] [blame] | 1107 | if (values.containsKey(Downloads.Impl.COLUMN_DELETED)) { |
| 1108 | if (values.getAsInteger(Downloads.Impl.COLUMN_DELETED) == 1) { |
| 1109 | // some rows are to be 'deleted'. need to start DownloadService. |
| 1110 | startService = true; |
| 1111 | } |
| 1112 | } |
| 1113 | |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1114 | ContentValues filteredValues; |
| 1115 | if (Binder.getCallingPid() != Process.myPid()) { |
| 1116 | filteredValues = new ContentValues(); |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 1117 | copyString(Downloads.Impl.COLUMN_APP_DATA, values, filteredValues); |
| 1118 | copyInteger(Downloads.Impl.COLUMN_VISIBILITY, values, filteredValues); |
| 1119 | Integer i = values.getAsInteger(Downloads.Impl.COLUMN_CONTROL); |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1120 | if (i != null) { |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 1121 | filteredValues.put(Downloads.Impl.COLUMN_CONTROL, i); |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1122 | startService = true; |
| 1123 | } |
Vasu Nori | e00c312 | 2010-10-12 23:27:49 -0700 | [diff] [blame] | 1124 | |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 1125 | copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues); |
| 1126 | copyString(Downloads.Impl.COLUMN_TITLE, values, filteredValues); |
Vasu Nori | e00c312 | 2010-10-12 23:27:49 -0700 | [diff] [blame] | 1127 | copyString(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI, values, filteredValues); |
Jean-Baptiste Queru | 7dd92fa | 2010-01-07 16:33:05 -0800 | [diff] [blame] | 1128 | copyString(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues); |
Vasu Nori | e00c312 | 2010-10-12 23:27:49 -0700 | [diff] [blame] | 1129 | copyInteger(Downloads.Impl.COLUMN_DELETED, values, filteredValues); |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1130 | } else { |
| 1131 | filteredValues = values; |
Leon Scroggins | a2028ed | 2010-01-28 17:53:26 -0500 | [diff] [blame] | 1132 | String filename = values.getAsString(Downloads.Impl._DATA); |
| 1133 | if (filename != null) { |
Mattias Nilsson | a2aad3a | 2014-03-27 16:20:21 +0100 | [diff] [blame] | 1134 | Cursor c = null; |
| 1135 | try { |
| 1136 | c = query(uri, new String[] |
| 1137 | { Downloads.Impl.COLUMN_TITLE }, null, null, null); |
| 1138 | if (!c.moveToFirst() || c.getString(0).isEmpty()) { |
| 1139 | values.put(Downloads.Impl.COLUMN_TITLE, new File(filename).getName()); |
| 1140 | } |
| 1141 | } finally { |
| 1142 | IoUtils.closeQuietly(c); |
Leon Scroggins | a2028ed | 2010-01-28 17:53:26 -0500 | [diff] [blame] | 1143 | } |
Leon Scroggins | a2028ed | 2010-01-28 17:53:26 -0500 | [diff] [blame] | 1144 | } |
Steve Howard | 71e7fda | 2010-09-08 17:15:27 -0700 | [diff] [blame] | 1145 | |
| 1146 | Integer status = values.getAsInteger(Downloads.Impl.COLUMN_STATUS); |
| 1147 | boolean isRestart = status != null && status == Downloads.Impl.STATUS_PENDING; |
Steve Howard | d319729 | 2010-09-17 16:45:58 -0700 | [diff] [blame] | 1148 | boolean isUserBypassingSizeLimit = |
| 1149 | values.containsKey(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT); |
| 1150 | if (isRestart || isUserBypassingSizeLimit) { |
Steve Howard | 71e7fda | 2010-09-08 17:15:27 -0700 | [diff] [blame] | 1151 | startService = true; |
| 1152 | } |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1153 | } |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1154 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1155 | int match = sURIMatcher.match(uri); |
| 1156 | switch (match) { |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1157 | case MY_DOWNLOADS: |
| 1158 | case MY_DOWNLOADS_ID: |
| 1159 | case ALL_DOWNLOADS: |
| 1160 | case ALL_DOWNLOADS_ID: |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 1161 | SqlSelection selection = getWhereClause(uri, where, whereArgs, match); |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1162 | if (filteredValues.size() > 0) { |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 1163 | count = db.update(DB_TABLE, filteredValues, selection.getSelection(), |
| 1164 | selection.getParameters()); |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1165 | } else { |
| 1166 | count = 0; |
| 1167 | } |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1168 | break; |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1169 | |
| 1170 | default: |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 1171 | Log.d(Constants.TAG, "updating unknown/invalid URI: " + uri); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1172 | throw new UnsupportedOperationException("Cannot update URI: " + uri); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1173 | } |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1174 | |
| 1175 | notifyContentChanged(uri, match); |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1176 | if (startService) { |
| 1177 | Context context = getContext(); |
| 1178 | context.startService(new Intent(context, DownloadService.class)); |
| 1179 | } |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1180 | return count; |
| 1181 | } |
| 1182 | |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1183 | /** |
| 1184 | * Notify of a change through both URIs (/my_downloads and /all_downloads) |
| 1185 | * @param uri either URI for the changed download(s) |
| 1186 | * @param uriMatch the match ID from {@link #sURIMatcher} |
| 1187 | */ |
| 1188 | private void notifyContentChanged(final Uri uri, int uriMatch) { |
| 1189 | Long downloadId = null; |
| 1190 | if (uriMatch == MY_DOWNLOADS_ID || uriMatch == ALL_DOWNLOADS_ID) { |
| 1191 | downloadId = Long.parseLong(getDownloadIdFromUri(uri)); |
| 1192 | } |
| 1193 | for (Uri uriToNotify : BASE_URIS) { |
| 1194 | if (downloadId != null) { |
| 1195 | uriToNotify = ContentUris.withAppendedId(uriToNotify, downloadId); |
| 1196 | } |
| 1197 | getContext().getContentResolver().notifyChange(uriToNotify, null); |
| 1198 | } |
| 1199 | } |
| 1200 | |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 1201 | private SqlSelection getWhereClause(final Uri uri, final String where, final String[] whereArgs, |
| 1202 | int uriMatch) { |
| 1203 | SqlSelection selection = new SqlSelection(); |
| 1204 | selection.appendClause(where, whereArgs); |
Vasu Nori | 3ca6774 | 2010-11-04 15:03:31 -0700 | [diff] [blame] | 1205 | if (uriMatch == MY_DOWNLOADS_ID || uriMatch == ALL_DOWNLOADS_ID || |
| 1206 | uriMatch == PUBLIC_DOWNLOAD_ID) { |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 1207 | selection.appendClause(Downloads.Impl._ID + " = ?", getDownloadIdFromUri(uri)); |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 1208 | } |
Kenny Root | e1823c8 | 2010-12-01 11:38:41 -0800 | [diff] [blame] | 1209 | if ((uriMatch == MY_DOWNLOADS || uriMatch == MY_DOWNLOADS_ID) |
Jeff Sharkey | dffbb9c | 2014-01-30 15:01:39 -0800 | [diff] [blame] | 1210 | && getContext().checkCallingOrSelfPermission(Downloads.Impl.PERMISSION_ACCESS_ALL) |
Kenny Root | e1823c8 | 2010-12-01 11:38:41 -0800 | [diff] [blame] | 1211 | != PackageManager.PERMISSION_GRANTED) { |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 1212 | selection.appendClause( |
| 1213 | Constants.UID + "= ? OR " + Downloads.Impl.COLUMN_OTHER_UID + "= ?", |
Jeff Sharkey | bff4fe9 | 2011-11-11 12:37:21 -0800 | [diff] [blame] | 1214 | Binder.getCallingUid(), Binder.getCallingUid()); |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 1215 | } |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 1216 | return selection; |
Steve Howard | b06b739 | 2010-07-22 11:33:50 -0700 | [diff] [blame] | 1217 | } |
| 1218 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1219 | /** |
| 1220 | * Deletes a row in the database |
| 1221 | */ |
| 1222 | @Override |
Jeff Sharkey | e3960ea | 2015-06-16 17:06:37 -0700 | [diff] [blame] | 1223 | public int delete(final Uri uri, final String where, final String[] whereArgs) { |
Jeff Sharkey | 1f2c2c5 | 2014-02-06 15:19:01 -0800 | [diff] [blame] | 1224 | if (shouldRestrictVisibility()) { |
| 1225 | Helpers.validateSelection(where, sAppReadableColumnsSet); |
| 1226 | } |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1227 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1228 | SQLiteDatabase db = mOpenHelper.getWritableDatabase(); |
| 1229 | int count; |
| 1230 | int match = sURIMatcher.match(uri); |
| 1231 | switch (match) { |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1232 | case MY_DOWNLOADS: |
| 1233 | case MY_DOWNLOADS_ID: |
| 1234 | case ALL_DOWNLOADS: |
| 1235 | case ALL_DOWNLOADS_ID: |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 1236 | SqlSelection selection = getWhereClause(uri, where, whereArgs, match); |
| 1237 | deleteRequestHeaders(db, selection.getSelection(), selection.getParameters()); |
Jeff Sharkey | afaf53b | 2013-09-26 14:30:47 -0700 | [diff] [blame] | 1238 | |
| 1239 | final Cursor cursor = db.query(DB_TABLE, new String[] { |
Jeff Sharkey | e3960ea | 2015-06-16 17:06:37 -0700 | [diff] [blame] | 1240 | Downloads.Impl._ID, Downloads.Impl._DATA |
| 1241 | }, selection.getSelection(), selection.getParameters(), null, null, null); |
Jeff Sharkey | afaf53b | 2013-09-26 14:30:47 -0700 | [diff] [blame] | 1242 | try { |
| 1243 | while (cursor.moveToNext()) { |
| 1244 | final long id = cursor.getLong(0); |
Jeff Sharkey | 9144926 | 2016-09-16 12:12:17 -0600 | [diff] [blame] | 1245 | revokeAllDownloadsPermission(id); |
Jeff Sharkey | afaf53b | 2013-09-26 14:30:47 -0700 | [diff] [blame] | 1246 | DownloadStorageProvider.onDownloadProviderDelete(getContext(), id); |
Jeff Sharkey | e3960ea | 2015-06-16 17:06:37 -0700 | [diff] [blame] | 1247 | |
| 1248 | final String path = cursor.getString(1); |
| 1249 | if (!TextUtils.isEmpty(path)) { |
| 1250 | final File file = new File(path); |
| 1251 | if (Helpers.isFilenameValid(getContext(), file)) { |
| 1252 | Log.v(Constants.TAG, "Deleting " + file + " via provider delete"); |
| 1253 | file.delete(); |
| 1254 | } |
| 1255 | } |
Jeff Sharkey | afaf53b | 2013-09-26 14:30:47 -0700 | [diff] [blame] | 1256 | } |
| 1257 | } finally { |
| 1258 | IoUtils.closeQuietly(cursor); |
| 1259 | } |
| 1260 | |
Steve Howard | e610c05 | 2010-10-07 18:16:15 -0700 | [diff] [blame] | 1261 | count = db.delete(DB_TABLE, selection.getSelection(), selection.getParameters()); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1262 | break; |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1263 | |
| 1264 | default: |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 1265 | Log.d(Constants.TAG, "deleting unknown/invalid URI: " + uri); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1266 | throw new UnsupportedOperationException("Cannot delete URI: " + uri); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1267 | } |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1268 | notifyContentChanged(uri, match); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1269 | return count; |
| 1270 | } |
Steve Howard | 71aab52 | 2010-07-20 16:32:31 -0700 | [diff] [blame] | 1271 | |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1272 | /** |
| 1273 | * Remotely opens a file |
| 1274 | */ |
| 1275 | @Override |
Jeff Sharkey | c067c8b | 2013-09-23 14:21:55 -0700 | [diff] [blame] | 1276 | public ParcelFileDescriptor openFile(final Uri uri, String mode) throws FileNotFoundException { |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1277 | if (Constants.LOGVV) { |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1278 | logVerboseOpenFileInfo(uri, mode); |
| 1279 | } |
| 1280 | |
Jeff Sharkey | 9144926 | 2016-09-16 12:12:17 -0600 | [diff] [blame] | 1281 | // Perform normal query to enforce caller identity access before |
| 1282 | // clearing it to reach internal-only columns |
| 1283 | final Cursor probeCursor = query(uri, new String[] { |
| 1284 | Downloads.Impl._DATA }, null, null, null); |
| 1285 | try { |
| 1286 | if ((probeCursor == null) || (probeCursor.getCount() == 0)) { |
| 1287 | throw new FileNotFoundException( |
| 1288 | "No file found for " + uri + " as UID " + Binder.getCallingUid()); |
| 1289 | } |
| 1290 | } finally { |
| 1291 | IoUtils.closeQuietly(probeCursor); |
| 1292 | } |
| 1293 | |
Jeff Sharkey | 2e16979 | 2014-08-05 18:04:54 -0700 | [diff] [blame] | 1294 | final Cursor cursor = queryCleared(uri, new String[] { |
Jeff Sharkey | 495edec | 2014-08-05 11:51:24 -0700 | [diff] [blame] | 1295 | Downloads.Impl._DATA, Downloads.Impl.COLUMN_STATUS, |
| 1296 | Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.COLUMN_MEDIA_SCANNED }, null, |
| 1297 | null, null); |
| 1298 | final String path; |
| 1299 | final boolean shouldScan; |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1300 | try { |
| 1301 | int count = (cursor != null) ? cursor.getCount() : 0; |
| 1302 | if (count != 1) { |
| 1303 | // If there is not exactly one result, throw an appropriate exception. |
| 1304 | if (count == 0) { |
| 1305 | throw new FileNotFoundException("No entry for " + uri); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1306 | } |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1307 | throw new FileNotFoundException("Multiple items at " + uri); |
| 1308 | } |
| 1309 | |
Jeff Sharkey | 495edec | 2014-08-05 11:51:24 -0700 | [diff] [blame] | 1310 | if (cursor.moveToFirst()) { |
| 1311 | final int status = cursor.getInt(1); |
| 1312 | final int destination = cursor.getInt(2); |
| 1313 | final int mediaScanned = cursor.getInt(3); |
| 1314 | |
| 1315 | path = cursor.getString(0); |
| 1316 | shouldScan = Downloads.Impl.isStatusSuccess(status) && ( |
| 1317 | destination == Downloads.Impl.DESTINATION_EXTERNAL |
| 1318 | || destination == Downloads.Impl.DESTINATION_FILE_URI |
| 1319 | || destination == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) |
| 1320 | && mediaScanned != 2; |
| 1321 | } else { |
| 1322 | throw new FileNotFoundException("Failed moveToFirst"); |
| 1323 | } |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1324 | } finally { |
Jeff Sharkey | c067c8b | 2013-09-23 14:21:55 -0700 | [diff] [blame] | 1325 | IoUtils.closeQuietly(cursor); |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1326 | } |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1327 | |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1328 | if (path == null) { |
| 1329 | throw new FileNotFoundException("No filename found."); |
| 1330 | } |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1331 | |
Jeff Sharkey | 5508e27 | 2016-01-07 14:15:59 -0700 | [diff] [blame] | 1332 | final File file; |
| 1333 | try { |
| 1334 | file = new File(path).getCanonicalFile(); |
| 1335 | } catch (IOException e) { |
| 1336 | throw new FileNotFoundException(e.getMessage()); |
| 1337 | } |
| 1338 | |
Jeff Sharkey | dffbb9c | 2014-01-30 15:01:39 -0800 | [diff] [blame] | 1339 | if (!Helpers.isFilenameValid(getContext(), file)) { |
Jeff Sharkey | 5508e27 | 2016-01-07 14:15:59 -0700 | [diff] [blame] | 1340 | throw new FileNotFoundException("Invalid file path: " + file); |
Jeff Sharkey | dffbb9c | 2014-01-30 15:01:39 -0800 | [diff] [blame] | 1341 | } |
| 1342 | |
Jeff Sharkey | 495edec | 2014-08-05 11:51:24 -0700 | [diff] [blame] | 1343 | final int pfdMode = ParcelFileDescriptor.parseMode(mode); |
| 1344 | if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY) { |
| 1345 | return ParcelFileDescriptor.open(file, pfdMode); |
Jeff Sharkey | c067c8b | 2013-09-23 14:21:55 -0700 | [diff] [blame] | 1346 | } else { |
| 1347 | try { |
| 1348 | // When finished writing, update size and timestamp |
Jeff Sharkey | 495edec | 2014-08-05 11:51:24 -0700 | [diff] [blame] | 1349 | return ParcelFileDescriptor.open(file, pfdMode, mHandler, new OnCloseListener() { |
| 1350 | @Override |
| 1351 | public void onClose(IOException e) { |
| 1352 | final ContentValues values = new ContentValues(); |
| 1353 | values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, file.length()); |
| 1354 | values.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, |
| 1355 | System.currentTimeMillis()); |
| 1356 | update(uri, values, null, null); |
| 1357 | |
| 1358 | if (shouldScan) { |
| 1359 | final Intent intent = new Intent( |
| 1360 | Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); |
| 1361 | intent.setData(Uri.fromFile(file)); |
| 1362 | getContext().sendBroadcast(intent); |
| 1363 | } |
| 1364 | } |
| 1365 | }); |
Jeff Sharkey | c067c8b | 2013-09-23 14:21:55 -0700 | [diff] [blame] | 1366 | } catch (IOException e) { |
| 1367 | throw new FileNotFoundException("Failed to open for writing: " + e); |
| 1368 | } |
| 1369 | } |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1370 | } |
| 1371 | |
Jeff Sharkey | 1d0a0aa | 2013-01-30 11:26:46 -0800 | [diff] [blame] | 1372 | @Override |
| 1373 | public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { |
| 1374 | final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 120); |
| 1375 | |
| 1376 | pw.println("Downloads updated in last hour:"); |
| 1377 | pw.increaseIndent(); |
| 1378 | |
| 1379 | final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); |
| 1380 | final long modifiedAfter = mSystemFacade.currentTimeMillis() - DateUtils.HOUR_IN_MILLIS; |
| 1381 | final Cursor cursor = db.query(DB_TABLE, null, |
| 1382 | Downloads.Impl.COLUMN_LAST_MODIFICATION + ">" + modifiedAfter, null, null, null, |
| 1383 | Downloads.Impl._ID + " ASC"); |
| 1384 | try { |
| 1385 | final String[] cols = cursor.getColumnNames(); |
| 1386 | final int idCol = cursor.getColumnIndex(BaseColumns._ID); |
| 1387 | while (cursor.moveToNext()) { |
| 1388 | pw.println("Download #" + cursor.getInt(idCol) + ":"); |
| 1389 | pw.increaseIndent(); |
| 1390 | for (int i = 0; i < cols.length; i++) { |
| 1391 | // Omit sensitive data when dumping |
| 1392 | if (Downloads.Impl.COLUMN_COOKIE_DATA.equals(cols[i])) { |
| 1393 | continue; |
| 1394 | } |
| 1395 | pw.printPair(cols[i], cursor.getString(i)); |
| 1396 | } |
| 1397 | pw.println(); |
| 1398 | pw.decreaseIndent(); |
| 1399 | } |
| 1400 | } finally { |
| 1401 | cursor.close(); |
| 1402 | } |
| 1403 | |
| 1404 | pw.decreaseIndent(); |
| 1405 | } |
| 1406 | |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1407 | private void logVerboseOpenFileInfo(Uri uri, String mode) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 1408 | Log.v(Constants.TAG, "openFile uri: " + uri + ", mode: " + mode |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1409 | + ", uid: " + Binder.getCallingUid()); |
| 1410 | Cursor cursor = query(Downloads.Impl.CONTENT_URI, |
| 1411 | new String[] { "_id" }, null, null, "_id"); |
| 1412 | if (cursor == null) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 1413 | Log.v(Constants.TAG, "null cursor in openFile"); |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1414 | } else { |
Mattias Nilsson | a2aad3a | 2014-03-27 16:20:21 +0100 | [diff] [blame] | 1415 | try { |
| 1416 | if (!cursor.moveToFirst()) { |
| 1417 | Log.v(Constants.TAG, "empty cursor in openFile"); |
| 1418 | } else { |
| 1419 | do { |
| 1420 | Log.v(Constants.TAG, "row " + cursor.getInt(0) + " available"); |
| 1421 | } while(cursor.moveToNext()); |
| 1422 | } |
| 1423 | } finally { |
| 1424 | cursor.close(); |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1425 | } |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1426 | } |
| 1427 | cursor = query(uri, new String[] { "_data" }, null, null, null); |
| 1428 | if (cursor == null) { |
Doug Zongker | 9b731a5 | 2014-01-27 11:19:19 -0800 | [diff] [blame] | 1429 | Log.v(Constants.TAG, "null cursor in openFile"); |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1430 | } else { |
Mattias Nilsson | a2aad3a | 2014-03-27 16:20:21 +0100 | [diff] [blame] | 1431 | try { |
| 1432 | if (!cursor.moveToFirst()) { |
| 1433 | Log.v(Constants.TAG, "empty cursor in openFile"); |
| 1434 | } else { |
| 1435 | String filename = cursor.getString(0); |
| 1436 | Log.v(Constants.TAG, "filename in openFile: " + filename); |
| 1437 | if (new java.io.File(filename).isFile()) { |
| 1438 | Log.v(Constants.TAG, "file exists in openFile"); |
| 1439 | } |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1440 | } |
Mattias Nilsson | a2aad3a | 2014-03-27 16:20:21 +0100 | [diff] [blame] | 1441 | } finally { |
| 1442 | cursor.close(); |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1443 | } |
Steve Howard | 3d55d82 | 2010-09-12 18:53:31 -0700 | [diff] [blame] | 1444 | } |
| 1445 | } |
| 1446 | |
The Android Open Source Project | 1fbad9c | 2008-12-17 18:06:03 -0800 | [diff] [blame] | 1447 | private static final void copyInteger(String key, ContentValues from, ContentValues to) { |
| 1448 | Integer i = from.getAsInteger(key); |
| 1449 | if (i != null) { |
| 1450 | to.put(key, i); |
| 1451 | } |
| 1452 | } |
| 1453 | |
| 1454 | private static final void copyBoolean(String key, ContentValues from, ContentValues to) { |
| 1455 | Boolean b = from.getAsBoolean(key); |
| 1456 | if (b != null) { |
| 1457 | to.put(key, b); |
| 1458 | } |
| 1459 | } |
| 1460 | |
| 1461 | private static final void copyString(String key, ContentValues from, ContentValues to) { |
| 1462 | String s = from.getAsString(key); |
| 1463 | if (s != null) { |
| 1464 | to.put(key, s); |
| 1465 | } |
| 1466 | } |
| 1467 | |
Steve Howard | a89321e | 2010-08-03 12:39:35 -0700 | [diff] [blame] | 1468 | private static final void copyStringWithDefault(String key, ContentValues from, |
| 1469 | ContentValues to, String defaultValue) { |
| 1470 | copyString(key, from, to); |
| 1471 | if (!to.containsKey(key)) { |
| 1472 | to.put(key, defaultValue); |
| 1473 | } |
| 1474 | } |
Jeff Sharkey | 9144926 | 2016-09-16 12:12:17 -0600 | [diff] [blame] | 1475 | |
Suprabh Shukla | 8998652 | 2017-02-27 15:56:11 -0800 | [diff] [blame] | 1476 | private void grantAllDownloadsPermission(String toPackage, long id) { |
Jeff Sharkey | 9144926 | 2016-09-16 12:12:17 -0600 | [diff] [blame] | 1477 | final Uri uri = ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id); |
Suprabh Shukla | 8998652 | 2017-02-27 15:56:11 -0800 | [diff] [blame] | 1478 | getContext().grantUriPermission(toPackage, uri, |
Jeff Sharkey | 9144926 | 2016-09-16 12:12:17 -0600 | [diff] [blame] | 1479 | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); |
| 1480 | } |
| 1481 | |
| 1482 | private void revokeAllDownloadsPermission(long id) { |
| 1483 | final Uri uri = ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id); |
| 1484 | getContext().revokeUriPermission(uri, ~0); |
| 1485 | } |
The Android Open Source Project | 57f55b3 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1486 | } |