blob: 561b5e40571eddacf8aba3a6e84b0f0d47167f56 [file] [log] [blame]
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001/*
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
17package com.android.providers.downloads;
18
Jeff Sharkeyed30dea2015-07-13 10:25:58 -070019import android.app.AppOpsManager;
Vasu Nori3ca67742010-11-04 15:03:31 -070020import android.app.DownloadManager;
Jeff Sharkey51cc2142011-04-13 12:39:50 -070021import android.app.DownloadManager.Request;
The Android Open Source Project57f55b32008-10-21 07:00:00 -070022import android.content.ContentProvider;
Steve Howard3d55d822010-09-12 18:53:31 -070023import android.content.ContentUris;
The Android Open Source Project57f55b32008-10-21 07:00:00 -070024import android.content.ContentValues;
25import android.content.Context;
26import android.content.Intent;
27import android.content.UriMatcher;
Suchi Amalapurapu91e45222010-01-25 15:50:36 -080028import android.content.pm.ApplicationInfo;
The Android Open Source Project57f55b32008-10-21 07:00:00 -070029import android.content.pm.PackageManager;
Suchi Amalapurapu91e45222010-01-25 15:50:36 -080030import android.content.pm.PackageManager.NameNotFoundException;
The Android Open Source Project57f55b32008-10-21 07:00:00 -070031import android.database.Cursor;
Vasu Nori3ca67742010-11-04 15:03:31 -070032import android.database.DatabaseUtils;
Jean-Baptiste Queruc6f5aad2009-01-20 10:08:46 -080033import android.database.SQLException;
The Android Open Source Project57f55b32008-10-21 07:00:00 -070034import android.database.sqlite.SQLiteDatabase;
35import android.database.sqlite.SQLiteOpenHelper;
The Android Open Source Project57f55b32008-10-21 07:00:00 -070036import android.net.Uri;
37import android.os.Binder;
Jeff Sharkeyc067c8b2013-09-23 14:21:55 -070038import android.os.Handler;
Todd Kennedyf775c982015-06-19 14:47:45 -070039import android.os.HandlerThread;
The Android Open Source Project57f55b32008-10-21 07:00:00 -070040import android.os.ParcelFileDescriptor;
Jeff Sharkeyc067c8b2013-09-23 14:21:55 -070041import android.os.ParcelFileDescriptor.OnCloseListener;
The Android Open Source Project57f55b32008-10-21 07:00:00 -070042import android.os.Process;
Jeff Sharkey1d0a0aa2013-01-30 11:26:46 -080043import android.provider.BaseColumns;
The Android Open Source Project57f55b32008-10-21 07:00:00 -070044import android.provider.Downloads;
Jeff Sharkey9b606342012-04-16 14:29:04 -070045import android.provider.OpenableColumns;
Jeff Sharkeyc3f3d992012-04-19 10:10:53 -070046import android.text.TextUtils;
Jeff Sharkey1d0a0aa2013-01-30 11:26:46 -080047import android.text.format.DateUtils;
Doug Zongker9b731a52014-01-27 11:19:19 -080048import android.util.Log;
The Android Open Source Project57f55b32008-10-21 07:00:00 -070049
Jeff Sharkeyed30dea2015-07-13 10:25:58 -070050import libcore.io.IoUtils;
51
Jeff Sharkey1d0a0aa2013-01-30 11:26:46 -080052import com.android.internal.util.IndentingPrintWriter;
Jeff Sharkey9b606342012-04-16 14:29:04 -070053import com.google.android.collect.Maps;
Steve Howard5224c6f2010-07-14 11:30:59 -070054import com.google.common.annotations.VisibleForTesting;
55
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -080056import java.io.File;
Jeff Sharkey1d0a0aa2013-01-30 11:26:46 -080057import java.io.FileDescriptor;
The Android Open Source Project57f55b32008-10-21 07:00:00 -070058import java.io.FileNotFoundException;
Jeff Sharkeyd195a5c2011-10-12 12:04:10 -070059import java.io.IOException;
Jeff Sharkey1d0a0aa2013-01-30 11:26:46 -080060import java.io.PrintWriter;
Steve Howarde610c052010-10-07 18:16:15 -070061import java.util.ArrayList;
Vasu Nori01d01822010-11-09 12:30:19 -080062import java.util.Arrays;
Jeff Sharkey9b606342012-04-16 14:29:04 -070063import java.util.HashMap;
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -080064import java.util.HashSet;
Steve Howarde61798d2010-08-02 11:52:16 -070065import java.util.Iterator;
Steve Howarde610c052010-10-07 18:16:15 -070066import java.util.List;
Steve Howard5224c6f2010-07-14 11:30:59 -070067import java.util.Map;
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -080068
The Android Open Source Project57f55b32008-10-21 07:00:00 -070069/**
70 * Allows application to interact with the download manager.
71 */
72public final class DownloadProvider extends ContentProvider {
The Android Open Source Project57f55b32008-10-21 07:00:00 -070073 /** Database filename */
74 private static final String DB_NAME = "downloads.db";
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -080075 /** Current database version */
Jeff Sharkeyc067c8b2013-09-23 14:21:55 -070076 private static final int DB_VERSION = 109;
The Android Open Source Project57f55b32008-10-21 07:00:00 -070077 /** 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 Howard3d55d822010-09-12 18:53:31 -070087 /** 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 Project57f55b32008-10-21 07:00:00 -070093 /** URI matcher constant for the URI of an individual download */
Steve Howard3d55d822010-09-12 18:53:31 -070094 private static final int ALL_DOWNLOADS_ID = 4;
Steve Howard5224c6f2010-07-14 11:30:59 -070095 /** URI matcher constant for the URI of a download's request headers */
Steve Howard3d55d822010-09-12 18:53:31 -070096 private static final int REQUEST_HEADERS_URI = 5;
Vasu Nori3ca67742010-11-04 15:03:31 -070097 /** 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 Project57f55b32008-10-21 07:00:00 -0700102 static {
Steve Howard3d55d822010-09-12 18:53:31 -0700103 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 Howard4bebe752010-09-17 16:55:25 -0700113 // 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 Nori3ca67742010-11-04 15:03:31 -0700119 sURIMatcher.addURI("downloads",
120 Downloads.Impl.PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT + "/#",
121 PUBLIC_DOWNLOAD_ID);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700122 }
123
Steve Howard3d55d822010-09-12 18:53:31 -0700124 /** 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 Project1fbad9c2008-12-17 18:06:03 -0800130 private static final String[] sAppReadableColumnsArray = new String[] {
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800131 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 Howard0a77c622010-07-21 11:41:30 -0700145 Downloads.Impl.COLUMN_DESCRIPTION,
Steve Howard0d8d8912010-07-21 19:41:15 -0700146 Downloads.Impl.COLUMN_URI,
Steve Howard71e7fda2010-09-08 17:15:27 -0700147 Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI,
Steve Howardb9a0ad72010-09-16 12:04:17 -0700148 Downloads.Impl.COLUMN_FILE_NAME_HINT,
Vasu Norie00c3122010-10-12 23:27:49 -0700149 Downloads.Impl.COLUMN_MEDIAPROVIDER_URI,
150 Downloads.Impl.COLUMN_DELETED,
Jeff Sharkey9b606342012-04-16 14:29:04 -0700151 OpenableColumns.DISPLAY_NAME,
152 OpenableColumns.SIZE,
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800153 };
154
Jeff Sharkey9b606342012-04-16 14:29:04 -0700155 private static final HashSet<String> sAppReadableColumnsSet;
156 private static final HashMap<String, String> sColumnsMap;
157
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800158 static {
159 sAppReadableColumnsSet = new HashSet<String>();
160 for (int i = 0; i < sAppReadableColumnsArray.length; ++i) {
161 sAppReadableColumnsSet.add(sAppReadableColumnsArray[i]);
162 }
Jeff Sharkey9b606342012-04-16 14:29:04 -0700163
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 Project1fbad9c2008-12-17 18:06:03 -0800169 }
Vasu Nori01d01822010-11-09 12:30:19 -0800170 private static final List<String> downloadManagerColumnsList =
171 Arrays.asList(DownloadManager.UNDERLYING_COLUMNS);
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800172
Jeff Sharkeyc067c8b2013-09-23 14:21:55 -0700173 private Handler mHandler;
174
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700175 /** The database that lies underneath this content provider */
176 private SQLiteOpenHelper mOpenHelper = null;
177
Suchi Amalapurapu91e45222010-01-25 15:50:36 -0800178 /** List of uids that can access the downloads */
179 private int mSystemUid = -1;
180 private int mDefContainerUid = -1;
181
Steve Howard6d9b9822010-07-12 17:24:17 -0700182 @VisibleForTesting
183 SystemFacade mSystemFacade;
184
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700185 /**
Steve Howarde610c052010-10-07 18:16:15 -0700186 * 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 Project57f55b32008-10-21 07:00:00 -0700223 * 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 Project57f55b32008-10-21 07:00:00 -0700229 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 Zongker9b731a52014-01-27 11:19:19 -0800239 Log.v(Constants.TAG, "populating new database");
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700240 }
Steve Howard5224c6f2010-07-14 11:30:59 -0700241 onUpgrade(db, 0, DB_VERSION);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700242 }
243
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700244 /**
245 * Updates the database format when a content provider is used
246 * with a database that was created with a different format.
Steve Howard5224c6f2010-07-14 11:30:59 -0700247 *
248 * Note: to support downgrades, creating a table should always drop it first if it already
249 * exists.
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700250 */
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700251 @Override
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800252 public void onUpgrade(final SQLiteDatabase db, int oldV, final int newV) {
Steve Howard5224c6f2010-07-14 11:30:59 -0700253 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 Zongker9b731a52014-01-27 11:19:19 -0800259 Log.i(Constants.TAG, "Upgrading downloads database from version " + oldV
Steve Howard5224c6f2010-07-14 11:30:59 -0700260 + " 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 Zongker9b731a52014-01-27 11:19:19 -0800265 Log.i(Constants.TAG, "Downgrading downloads database from version " + oldV
Steve Howard5224c6f2010-07-14 11:30:59 -0700266 + " (current version is " + newV + "), destroying all old data");
267 oldV = 99;
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800268 }
Steve Howard5224c6f2010-07-14 11:30:59 -0700269
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 Howard0a77c622010-07-21 11:41:30 -0700288 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 Howard71e7fda2010-09-08 17:15:27 -0700297 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 Howardd3197292010-09-17 16:45:58 -0700303 case 104:
304 addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT,
305 "INTEGER NOT NULL DEFAULT 0");
306 break;
307
Steve Howard73f5f222010-09-22 19:51:59 -0700308 case 105:
309 fillNullValues(db);
310 break;
311
Vasu Norie00c3122010-10-12 23:27:49 -0700312 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 Nori9d270692010-11-11 15:26:07 -0800318 case 107:
319 addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_ERROR_MSG, "TEXT");
320 break;
321
Jeff Sharkeya7ae77f2012-04-17 12:26:06 -0700322 case 108:
323 addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_ALLOW_METERED,
324 "INTEGER NOT NULL DEFAULT 1");
325 break;
326
Jeff Sharkeyc067c8b2013-09-23 14:21:55 -0700327 case 109:
328 addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_ALLOW_WRITE,
329 "BOOLEAN NOT NULL DEFAULT 0");
330 break;
331
Steve Howard5224c6f2010-07-14 11:30:59 -0700332 default:
333 throw new IllegalStateException("Don't know how to upgrade to " + version);
334 }
335 }
336
337 /**
Steve Howard73f5f222010-09-22 19:51:59 -0700338 * 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 Howard71e7fda2010-09-08 17:15:27 -0700360 * 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 Howard0a77c622010-07-21 11:41:30 -0700371 * 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 Howard5224c6f2010-07-14 11:30:59 -0700383 * 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 Norif20af912011-02-03 12:07:29 -0800395 Constants.OTA_UPDATE + " BOOLEAN, " +
Steve Howard5224c6f2010-07-14 11:30:59 -0700396 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 Sharkey12f5dc42013-01-17 17:26:51 -0800403 Downloads.Impl.COLUMN_FAILED_CONNECTIONS + " INTEGER, " +
Steve Howard5224c6f2010-07-14 11:30:59 -0700404 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 Sharkey495edec2014-08-05 11:51:24 -0700418 Downloads.Impl.COLUMN_MEDIA_SCANNED + " BOOLEAN);");
Steve Howard5224c6f2010-07-14 11:30:59 -0700419 } catch (SQLException ex) {
Doug Zongker9b731a52014-01-27 11:19:19 -0800420 Log.e(Constants.TAG, "couldn't create table in downloads database");
Steve Howard5224c6f2010-07-14 11:30:59 -0700421 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 Project57f55b32008-10-21 07:00:00 -0700433 }
434 }
435
436 /**
437 * Initializes the content provider when it is created.
438 */
439 @Override
440 public boolean onCreate() {
Steve Howard6d9b9822010-07-12 17:24:17 -0700441 if (mSystemFacade == null) {
Steve Howardaf284002010-07-15 15:57:31 -0700442 mSystemFacade = new RealSystemFacade(getContext());
Steve Howard6d9b9822010-07-12 17:24:17 -0700443 }
444
Todd Kennedyf775c982015-06-19 14:47:45 -0700445 HandlerThread handlerThread =
446 new HandlerThread("DownloadProvider handler", Process.THREAD_PRIORITY_BACKGROUND);
447 handlerThread.start();
448 mHandler = new Handler(handlerThread.getLooper());
Jeff Sharkeyc067c8b2013-09-23 14:21:55 -0700449
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700450 mOpenHelper = new DatabaseHelper(getContext());
Suchi Amalapurapu91e45222010-01-25 15:50:36 -0800451 // 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 Zongker9b731a52014-01-27 11:19:19 -0800460 Log.wtf(Constants.TAG, "Could not get ApplicationInfo for com.android.defconatiner", e);
Suchi Amalapurapu91e45222010-01-25 15:50:36 -0800461 }
462 if (appInfo != null) {
463 mDefContainerUid = appInfo.uid;
464 }
Jeff Sharkey91449262016-09-16 12:12:17 -0600465
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 Shukla89986522017-02-27 15:56:11 -0800470 final ArrayList<Long> idsToDelete = new ArrayList<>();
Jeff Sharkey91449262016-09-16 12:12:17 -0600471 try {
472 while (cursor.moveToNext()) {
Suprabh Shukla89986522017-02-27 15:56:11 -0800473 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 Sharkey91449262016-09-16 12:12:17 -0600481 }
482 } finally {
483 cursor.close();
484 }
Suprabh Shukla89986522017-02-27 15:56:11 -0800485 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 Sharkey91449262016-09-16 12:12:17 -0600490
Vasu Nori5218d332010-12-16 18:31:23 -0800491 // 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 Project57f55b32008-10-21 07:00:00 -0700495 return true;
496 }
497
Suprabh Shukla89986522017-02-27 15:56:11 -0800498 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 Project57f55b32008-10-21 07:00:00 -0700511 /**
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 Sharkey9b606342012-04-16 14:29:04 -0700519 case MY_DOWNLOADS:
520 case ALL_DOWNLOADS: {
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700521 return DOWNLOAD_LIST_TYPE;
522 }
Jeff Sharkey9b606342012-04-16 14:29:04 -0700523 case MY_DOWNLOADS_ID:
Jeff Sharkeyc3f3d992012-04-19 10:10:53 -0700524 case ALL_DOWNLOADS_ID:
Vasu Nori3ca67742010-11-04 15:03:31 -0700525 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 Sharkeyc3f3d992012-04-19 10:10:53 -0700529 final String mimeType = DatabaseUtils.stringForQuery(db,
Vasu Nori3ca67742010-11-04 15:03:31 -0700530 "SELECT " + Downloads.Impl.COLUMN_MIME_TYPE + " FROM " + DB_TABLE +
531 " WHERE " + Downloads.Impl._ID + " = ?",
532 new String[]{id});
Jeff Sharkeyc3f3d992012-04-19 10:10:53 -0700533 if (TextUtils.isEmpty(mimeType)) {
534 return DOWNLOAD_TYPE;
535 } else {
536 return mimeType;
537 }
Vasu Nori3ca67742010-11-04 15:03:31 -0700538 }
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700539 default: {
540 if (Constants.LOGV) {
Doug Zongker9b731a52014-01-27 11:19:19 -0800541 Log.v(Constants.TAG, "calling getType on an unknown URI: " + uri);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700542 }
543 throw new IllegalArgumentException("Unknown URI: " + uri);
544 }
545 }
546 }
547
548 /**
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700549 * Inserts a row in the database
550 */
551 @Override
552 public Uri insert(final Uri uri, final ContentValues values) {
Steve Howardb06b7392010-07-22 11:33:50 -0700553 checkInsertPermissions(values);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700554 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
555
Steve Howard3d55d822010-09-12 18:53:31 -0700556 // note we disallow inserting into ALL_DOWNLOADS
557 int match = sURIMatcher.match(uri);
558 if (match != MY_DOWNLOADS) {
Doug Zongker9b731a52014-01-27 11:19:19 -0800559 Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: " + uri);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700560 throw new IllegalArgumentException("Unknown/Invalid URI " + uri);
561 }
562
Vasu Norib18ed512011-01-20 17:59:34 -0800563 // copy some of the input values as it
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800564 ContentValues filteredValues = new ContentValues();
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800565 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 Howard71aab522010-07-20 16:32:31 -0700570 copyBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API, values, filteredValues);
Vasu Norib18ed512011-01-20 17:59:34 -0800571
Steve Howard71aab522010-07-20 16:32:31 -0700572 boolean isPublicApi =
573 values.getAsBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API) == Boolean.TRUE;
574
Vasu Norib18ed512011-01-20 17:59:34 -0800575 // validate the destination column
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800576 Integer dest = values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION);
Jean-Baptiste Queruce8813a2009-02-06 11:09:27 -0800577 if (dest != null) {
Jeff Sharkeydffbb9c2014-01-30 15:01:39 -0800578 if (getContext().checkCallingOrSelfPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800579 != PackageManager.PERMISSION_GRANTED
Vasu Norib18ed512011-01-20 17:59:34 -0800580 && (dest == Downloads.Impl.DESTINATION_CACHE_PARTITION
581 || dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING
582 || dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION)) {
Vasu Nori9aadb4b2010-12-13 16:29:29 -0800583 throw new SecurityException("setting destination to : " + dest +
584 " not allowed, unless PERMISSION_ACCESS_ADVANCED is granted");
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800585 }
Steve Howard71aab522010-07-20 16:32:31 -0700586 // for public API behavior, if an app has CACHE_NON_PURGEABLE permission, automatically
587 // switch to non-purgeable download
588 boolean hasNonPurgeablePermission =
Jeff Sharkeydffbb9c2014-01-30 15:01:39 -0800589 getContext().checkCallingOrSelfPermission(
Steve Howard71aab522010-07-20 16:32:31 -0700590 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 Howard6d9b9822010-07-12 17:24:17 -0700596 if (dest == Downloads.Impl.DESTINATION_FILE_URI) {
Steve Howardb06b7392010-07-22 11:33:50 -0700597 checkFileUriDestination(values);
Jeff Sharkeyed30dea2015-07-13 10:25:58 -0700598
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 Sharkeyb8bc2a72015-07-24 15:22:54 -0700605 if (appOps.noteProxyOp(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE,
Jeff Sharkeyed30dea2015-07-13 10:25:58 -0700606 getCallingPackage()) != AppOpsManager.MODE_ALLOWED) {
607 throw new SecurityException("No permission to write");
608 }
609
Vasu Nori9aadb4b2010-12-13 16:29:29 -0800610 } 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 Howard6d9b9822010-07-12 17:24:17 -0700615 }
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800616 filteredValues.put(Downloads.Impl.COLUMN_DESTINATION, dest);
Jean-Baptiste Queruce8813a2009-02-06 11:09:27 -0800617 }
Vasu Norib18ed512011-01-20 17:59:34 -0800618
619 // validate the visibility column
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800620 Integer vis = values.getAsInteger(Downloads.Impl.COLUMN_VISIBILITY);
Jean-Baptiste Queruce8813a2009-02-06 11:09:27 -0800621 if (vis == null) {
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800622 if (dest == Downloads.Impl.DESTINATION_EXTERNAL) {
623 filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY,
624 Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
Jean-Baptiste Queruce8813a2009-02-06 11:09:27 -0800625 } else {
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800626 filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY,
627 Downloads.Impl.VISIBILITY_HIDDEN);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700628 }
Jean-Baptiste Queruce8813a2009-02-06 11:09:27 -0800629 } else {
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800630 filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY, vis);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700631 }
Vasu Norib18ed512011-01-20 17:59:34 -0800632 // copy the control column as is
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800633 copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues);
Vasu Norib18ed512011-01-20 17:59:34 -0800634
635 /*
636 * requests coming from
Vasu Nori9b2576f2011-03-07 11:22:11 -0800637 * DownloadManager.addCompletedDownload(String, String, String,
638 * boolean, String, String, long) need special treatment
Vasu Norib18ed512011-01-20 17:59:34 -0800639 */
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 Sharkeyc067c8b2013-09-23 14:21:55 -0700649 copyBoolean(Downloads.Impl.COLUMN_ALLOW_WRITE, values, filteredValues);
Vasu Norib18ed512011-01-20 17:59:34 -0800650 } 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 Nori2c025772011-02-10 15:01:29 -0800657 long lastMod = mSystemFacade.currentTimeMillis();
658 filteredValues.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, lastMod);
Steve Howard0a77c622010-07-21 11:41:30 -0700659
Vasu Norib18ed512011-01-20 17:59:34 -0800660 // use packagename of the caller to set the notification columns
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800661 String pckg = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
662 String clazz = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_CLASS);
Steve Howard0a77c622010-07-21 11:41:30 -0700663 if (pckg != null && (clazz != null || isPublicApi)) {
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800664 int uid = Binder.getCallingUid();
665 try {
Steve Howard0a77c622010-07-21 11:41:30 -0700666 if (uid == 0 || mSystemFacade.userOwnsPackage(uid, pckg)) {
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800667 filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, pckg);
Steve Howard0a77c622010-07-21 11:41:30 -0700668 if (clazz != null) {
669 filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS, clazz);
670 }
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800671 }
672 } catch (PackageManager.NameNotFoundException ex) {
673 /* ignored for now */
674 }
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700675 }
Vasu Norib18ed512011-01-20 17:59:34 -0800676
677 // copy some more columns as is
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800678 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 Norib18ed512011-01-20 17:59:34 -0800682
683 // UID, PID columns
Jeff Sharkeydffbb9c2014-01-30 15:01:39 -0800684 if (getContext().checkCallingOrSelfPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800685 == PackageManager.PERMISSION_GRANTED) {
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800686 copyInteger(Downloads.Impl.COLUMN_OTHER_UID, values, filteredValues);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700687 }
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800688 filteredValues.put(Constants.UID, Binder.getCallingUid());
689 if (Binder.getCallingUid() == 0) {
690 copyInteger(Constants.UID, values, filteredValues);
691 }
Vasu Norib18ed512011-01-20 17:59:34 -0800692
693 // copy some more columns as is
Steve Howarda89321e2010-08-03 12:39:35 -0700694 copyStringWithDefault(Downloads.Impl.COLUMN_TITLE, values, filteredValues, "");
695 copyStringWithDefault(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues, "");
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700696
Vasu Norib18ed512011-01-20 17:59:34 -0800697 // is_visible_in_downloads_ui column
Steve Howard71e7fda2010-09-08 17:15:27 -0700698 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 Norib18ed512011-01-20 17:59:34 -0800706 // public api requests and networktypes/roaming columns
Steve Howard0a77c622010-07-21 11:41:30 -0700707 if (isPublicApi) {
708 copyInteger(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, values, filteredValues);
709 copyBoolean(Downloads.Impl.COLUMN_ALLOW_ROAMING, values, filteredValues);
Jeff Sharkeya7ae77f2012-04-17 12:26:06 -0700710 copyBoolean(Downloads.Impl.COLUMN_ALLOW_METERED, values, filteredValues);
Steve Howard0a77c622010-07-21 11:41:30 -0700711 }
712
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800713 if (Constants.LOGVV) {
Doug Zongker9b731a52014-01-27 11:19:19 -0800714 Log.v(Constants.TAG, "initiating download with UID "
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800715 + filteredValues.getAsInteger(Constants.UID));
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800716 if (filteredValues.containsKey(Downloads.Impl.COLUMN_OTHER_UID)) {
Doug Zongker9b731a52014-01-27 11:19:19 -0800717 Log.v(Constants.TAG, "other UID " +
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -0800718 filteredValues.getAsInteger(Downloads.Impl.COLUMN_OTHER_UID));
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800719 }
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700720 }
721
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800722 long rowID = db.insert(DB_TABLE, null, filteredValues);
Steve Howard3d55d822010-09-12 18:53:31 -0700723 if (rowID == -1) {
Doug Zongker9b731a52014-01-27 11:19:19 -0800724 Log.d(Constants.TAG, "couldn't insert into downloads database");
Steve Howard3d55d822010-09-12 18:53:31 -0700725 return null;
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700726 }
727
Steve Howard3d55d822010-09-12 18:53:31 -0700728 insertRequestHeaders(db, rowID, values);
Suprabh Shukla89986522017-02-27 15:56:11 -0800729
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 Howard3d55d822010-09-12 18:53:31 -0700736 notifyContentChanged(uri, match);
Jeff Sharkey1225c342013-10-04 16:02:50 -0700737
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 Howard3d55d822010-09-12 18:53:31 -0700742 return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700743 }
744
Suprabh Shukla89986522017-02-27 15:56:11 -0800745 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 Project57f55b32008-10-21 07:00:00 -0700754 /**
Steve Howardb06b7392010-07-22 11:33:50 -0700755 * 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 Howard5d81e242010-09-30 19:45:56 -0700764 String scheme = uri.getScheme();
765 if (scheme == null || !scheme.equals("file")) {
Steve Howardb06b7392010-07-22 11:33:50 -0700766 throw new IllegalArgumentException("Not a file URI: " + uri);
767 }
Jeff Sharkeyd195a5c2011-10-12 12:04:10 -0700768 final String path = uri.getPath();
Steve Howardb9a0ad72010-09-16 12:04:17 -0700769 if (path == null) {
770 throw new IllegalArgumentException("Invalid file URI: " + uri);
771 }
Jeff Sharkeyed30dea2015-07-13 10:25:58 -0700772
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 Sharkeyb8bc2a72015-07-24 15:22:54 -0700784 if (appOps.noteProxyOp(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE,
Jeff Sharkeyed30dea2015-07-13 10:25:58 -0700785 getCallingPackage()) != AppOpsManager.MODE_ALLOWED) {
786 throw new SecurityException("No permission to write to " + file);
Jeff Sharkeyd195a5c2011-10-12 12:04:10 -0700787 }
Jeff Sharkeyed30dea2015-07-13 10:25:58 -0700788
789 } else {
790 throw new SecurityException("Unsupported path " + file);
Steve Howardb06b7392010-07-22 11:33:50 -0700791 }
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 Norib18ed512011-01-20 17:59:34 -0800817
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 Nori9b2576f2011-03-07 11:22:11 -0800822 * DownloadManager.addCompletedDownload(String, String, String,
823 * boolean, String, String, long)
Vasu Norib18ed512011-01-20 17:59:34 -0800824 */
825 values.remove(Downloads.Impl.COLUMN_TOTAL_BYTES);
826 values.remove(Downloads.Impl._DATA);
827 values.remove(Downloads.Impl.COLUMN_STATUS);
828 }
Steve Howardb06b7392010-07-22 11:33:50 -0700829 enforceAllowedValues(values, Downloads.Impl.COLUMN_DESTINATION,
830 Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE,
Vasu Norib18ed512011-01-20 17:59:34 -0800831 Downloads.Impl.DESTINATION_FILE_URI,
832 Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD);
Steve Howard9da9df32010-07-28 17:51:02 -0700833
834 if (getContext().checkCallingOrSelfPermission(Downloads.Impl.PERMISSION_NO_NOTIFICATION)
835 == PackageManager.PERMISSION_GRANTED) {
836 enforceAllowedValues(values, Downloads.Impl.COLUMN_VISIBILITY,
Jeff Sharkey51cc2142011-04-13 12:39:50 -0700837 Request.VISIBILITY_HIDDEN,
838 Request.VISIBILITY_VISIBLE,
839 Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED,
840 Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION);
Steve Howard9da9df32010-07-28 17:51:02 -0700841 } else {
842 enforceAllowedValues(values, Downloads.Impl.COLUMN_VISIBILITY,
Jeff Sharkey51cc2142011-04-13 12:39:50 -0700843 Request.VISIBILITY_VISIBLE,
844 Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED,
845 Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION);
Steve Howard9da9df32010-07-28 17:51:02 -0700846 }
Steve Howardb06b7392010-07-22 11:33:50 -0700847
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 Sharkeya7ae77f2012-04-17 12:26:06 -0700857 values.remove(Downloads.Impl.COLUMN_ALLOW_METERED);
Steve Howard71e7fda2010-09-08 17:15:27 -0700858 values.remove(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI);
Vasu Nori3002e182010-11-05 11:00:31 -0700859 values.remove(Downloads.Impl.COLUMN_MEDIA_SCANNED);
Jeff Sharkeyc067c8b2013-09-23 14:21:55 -0700860 values.remove(Downloads.Impl.COLUMN_ALLOW_WRITE);
Steve Howarde61798d2010-08-02 11:52:16 -0700861 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 Howardb06b7392010-07-22 11:33:50 -0700868
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 Sharkey2e169792014-08-05 18:04:54 -0700902 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 Howardb06b7392010-07-22 11:33:50 -0700912 /**
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700913 * Starts a database query
914 */
915 @Override
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800916 public Cursor query(final Uri uri, String[] projection,
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700917 final String selection, final String[] selectionArgs,
918 final String sort) {
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800919
920 Helpers.validateSelection(selection, sAppReadableColumnsSet);
921
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700922 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
923
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700924 int match = sURIMatcher.match(uri);
Steve Howard3d55d822010-09-12 18:53:31 -0700925 if (match == -1) {
926 if (Constants.LOGV) {
Doug Zongker9b731a52014-01-27 11:19:19 -0800927 Log.v(Constants.TAG, "querying unknown URI: " + uri);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700928 }
Steve Howard3d55d822010-09-12 18:53:31 -0700929 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 Project57f55b32008-10-21 07:00:00 -0700936 }
Steve Howard3d55d822010-09-12 18:53:31 -0700937 return queryRequestHeaders(db, uri);
938 }
939
Steve Howarde610c052010-10-07 18:16:15 -0700940 SqlSelection fullSelection = getWhereClause(uri, selection, selectionArgs, match);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700941
Steve Howard5224c6f2010-07-14 11:30:59 -0700942 if (shouldRestrictVisibility()) {
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800943 if (projection == null) {
Jeff Sharkey9b606342012-04-16 14:29:04 -0700944 projection = sAppReadableColumnsArray.clone();
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800945 } else {
Vasu Nori01d01822010-11-09 12:30:19 -0800946 // check the validity of the columns in projection
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800947 for (int i = 0; i < projection.length; ++i) {
Vasu Nori01d01822010-11-09 12:30:19 -0800948 if (!sAppReadableColumnsSet.contains(projection[i]) &&
949 !downloadManagerColumnsList.contains(projection[i])) {
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -0800950 throw new IllegalArgumentException(
951 "column " + projection[i] + " is not allowed in queries");
952 }
953 }
954 }
Jeff Sharkey9b606342012-04-16 14:29:04 -0700955
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 Project57f55b32008-10-21 07:00:00 -0700962 }
963
964 if (Constants.LOGVV) {
Steve Howard3d55d822010-09-12 18:53:31 -0700965 logVerboseQueryInfo(projection, selection, selectionArgs, sort, db);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700966 }
967
Steve Howarde610c052010-10-07 18:16:15 -0700968 Cursor ret = db.query(DB_TABLE, projection, fullSelection.getSelection(),
969 fullSelection.getParameters(), null, null, sort);
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700970
971 if (ret != null) {
972 ret.setNotificationUri(getContext().getContentResolver(), uri);
973 if (Constants.LOGVV) {
Doug Zongker9b731a52014-01-27 11:19:19 -0800974 Log.v(Constants.TAG,
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700975 "created cursor " + ret + " on behalf of " + Binder.getCallingPid());
976 }
977 } else {
978 if (Constants.LOGV) {
Doug Zongker9b731a52014-01-27 11:19:19 -0800979 Log.v(Constants.TAG, "query failed in downloads database");
The Android Open Source Project57f55b32008-10-21 07:00:00 -0700980 }
981 }
982
983 return ret;
984 }
985
Steve Howard3d55d822010-09-12 18:53:31 -0700986 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 Zongker9b731a52014-01-27 11:19:19 -08001026 Log.v(Constants.TAG, sb.toString());
Steve Howard3d55d822010-09-12 18:53:31 -07001027 }
1028
Steve Howard5224c6f2010-07-14 11:30:59 -07001029 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 Howard53356ad2010-07-15 13:43:01 -07001062 return db.query(Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE, projection, where,
1063 null, null, null, null);
Steve Howard5224c6f2010-07-14 11:30:59 -07001064 }
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 Howardb06b7392010-07-22 11:33:50 -07001071 Cursor cursor = db.query(DB_TABLE, projection, where, whereArgs, null, null, null, null);
Steve Howard5224c6f2010-07-14 11:30:59 -07001072 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 Howard3d55d822010-09-12 18:53:31 -07001084 * @return true if we should restrict the columns readable by this caller
Steve Howard5224c6f2010-07-14 11:30:59 -07001085 */
1086 private boolean shouldRestrictVisibility() {
1087 int callingUid = Binder.getCallingUid();
1088 return Binder.getCallingPid() != Process.myPid() &&
1089 callingUid != mSystemUid &&
Jeff Brown12c7bd42011-07-08 18:53:38 -07001090 callingUid != mDefContainerUid;
Steve Howard5224c6f2010-07-14 11:30:59 -07001091 }
1092
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001093 /**
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 Project1fbad9c2008-12-17 18:06:03 -08001099
1100 Helpers.validateSelection(where, sAppReadableColumnsSet);
1101
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001102 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1103
1104 int count;
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -08001105 boolean startService = false;
1106
Vasu Norie00c3122010-10-12 23:27:49 -07001107 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 Project1fbad9c2008-12-17 18:06:03 -08001114 ContentValues filteredValues;
1115 if (Binder.getCallingPid() != Process.myPid()) {
1116 filteredValues = new ContentValues();
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -08001117 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 Project1fbad9c2008-12-17 18:06:03 -08001120 if (i != null) {
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -08001121 filteredValues.put(Downloads.Impl.COLUMN_CONTROL, i);
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -08001122 startService = true;
1123 }
Vasu Norie00c3122010-10-12 23:27:49 -07001124
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -08001125 copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues);
1126 copyString(Downloads.Impl.COLUMN_TITLE, values, filteredValues);
Vasu Norie00c3122010-10-12 23:27:49 -07001127 copyString(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI, values, filteredValues);
Jean-Baptiste Queru7dd92fa2010-01-07 16:33:05 -08001128 copyString(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues);
Vasu Norie00c3122010-10-12 23:27:49 -07001129 copyInteger(Downloads.Impl.COLUMN_DELETED, values, filteredValues);
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -08001130 } else {
1131 filteredValues = values;
Leon Scrogginsa2028ed2010-01-28 17:53:26 -05001132 String filename = values.getAsString(Downloads.Impl._DATA);
1133 if (filename != null) {
Mattias Nilssona2aad3a2014-03-27 16:20:21 +01001134 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 Scrogginsa2028ed2010-01-28 17:53:26 -05001143 }
Leon Scrogginsa2028ed2010-01-28 17:53:26 -05001144 }
Steve Howard71e7fda2010-09-08 17:15:27 -07001145
1146 Integer status = values.getAsInteger(Downloads.Impl.COLUMN_STATUS);
1147 boolean isRestart = status != null && status == Downloads.Impl.STATUS_PENDING;
Steve Howardd3197292010-09-17 16:45:58 -07001148 boolean isUserBypassingSizeLimit =
1149 values.containsKey(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT);
1150 if (isRestart || isUserBypassingSizeLimit) {
Steve Howard71e7fda2010-09-08 17:15:27 -07001151 startService = true;
1152 }
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001153 }
Steve Howard3d55d822010-09-12 18:53:31 -07001154
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001155 int match = sURIMatcher.match(uri);
1156 switch (match) {
Steve Howard3d55d822010-09-12 18:53:31 -07001157 case MY_DOWNLOADS:
1158 case MY_DOWNLOADS_ID:
1159 case ALL_DOWNLOADS:
1160 case ALL_DOWNLOADS_ID:
Steve Howarde610c052010-10-07 18:16:15 -07001161 SqlSelection selection = getWhereClause(uri, where, whereArgs, match);
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -08001162 if (filteredValues.size() > 0) {
Steve Howarde610c052010-10-07 18:16:15 -07001163 count = db.update(DB_TABLE, filteredValues, selection.getSelection(),
1164 selection.getParameters());
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -08001165 } else {
1166 count = 0;
1167 }
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001168 break;
Steve Howard3d55d822010-09-12 18:53:31 -07001169
1170 default:
Doug Zongker9b731a52014-01-27 11:19:19 -08001171 Log.d(Constants.TAG, "updating unknown/invalid URI: " + uri);
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001172 throw new UnsupportedOperationException("Cannot update URI: " + uri);
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001173 }
Steve Howard3d55d822010-09-12 18:53:31 -07001174
1175 notifyContentChanged(uri, match);
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -08001176 if (startService) {
1177 Context context = getContext();
1178 context.startService(new Intent(context, DownloadService.class));
1179 }
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001180 return count;
1181 }
1182
Steve Howard3d55d822010-09-12 18:53:31 -07001183 /**
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 Howarde610c052010-10-07 18:16:15 -07001201 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 Nori3ca67742010-11-04 15:03:31 -07001205 if (uriMatch == MY_DOWNLOADS_ID || uriMatch == ALL_DOWNLOADS_ID ||
1206 uriMatch == PUBLIC_DOWNLOAD_ID) {
Steve Howarde610c052010-10-07 18:16:15 -07001207 selection.appendClause(Downloads.Impl._ID + " = ?", getDownloadIdFromUri(uri));
Steve Howardb06b7392010-07-22 11:33:50 -07001208 }
Kenny Roote1823c82010-12-01 11:38:41 -08001209 if ((uriMatch == MY_DOWNLOADS || uriMatch == MY_DOWNLOADS_ID)
Jeff Sharkeydffbb9c2014-01-30 15:01:39 -08001210 && getContext().checkCallingOrSelfPermission(Downloads.Impl.PERMISSION_ACCESS_ALL)
Kenny Roote1823c82010-12-01 11:38:41 -08001211 != PackageManager.PERMISSION_GRANTED) {
Steve Howarde610c052010-10-07 18:16:15 -07001212 selection.appendClause(
1213 Constants.UID + "= ? OR " + Downloads.Impl.COLUMN_OTHER_UID + "= ?",
Jeff Sharkeybff4fe92011-11-11 12:37:21 -08001214 Binder.getCallingUid(), Binder.getCallingUid());
Steve Howardb06b7392010-07-22 11:33:50 -07001215 }
Steve Howarde610c052010-10-07 18:16:15 -07001216 return selection;
Steve Howardb06b7392010-07-22 11:33:50 -07001217 }
1218
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001219 /**
1220 * Deletes a row in the database
1221 */
1222 @Override
Jeff Sharkeye3960ea2015-06-16 17:06:37 -07001223 public int delete(final Uri uri, final String where, final String[] whereArgs) {
Jeff Sharkey1f2c2c52014-02-06 15:19:01 -08001224 if (shouldRestrictVisibility()) {
1225 Helpers.validateSelection(where, sAppReadableColumnsSet);
1226 }
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -08001227
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001228 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1229 int count;
1230 int match = sURIMatcher.match(uri);
1231 switch (match) {
Steve Howard3d55d822010-09-12 18:53:31 -07001232 case MY_DOWNLOADS:
1233 case MY_DOWNLOADS_ID:
1234 case ALL_DOWNLOADS:
1235 case ALL_DOWNLOADS_ID:
Steve Howarde610c052010-10-07 18:16:15 -07001236 SqlSelection selection = getWhereClause(uri, where, whereArgs, match);
1237 deleteRequestHeaders(db, selection.getSelection(), selection.getParameters());
Jeff Sharkeyafaf53b2013-09-26 14:30:47 -07001238
1239 final Cursor cursor = db.query(DB_TABLE, new String[] {
Jeff Sharkeye3960ea2015-06-16 17:06:37 -07001240 Downloads.Impl._ID, Downloads.Impl._DATA
1241 }, selection.getSelection(), selection.getParameters(), null, null, null);
Jeff Sharkeyafaf53b2013-09-26 14:30:47 -07001242 try {
1243 while (cursor.moveToNext()) {
1244 final long id = cursor.getLong(0);
Jeff Sharkey91449262016-09-16 12:12:17 -06001245 revokeAllDownloadsPermission(id);
Jeff Sharkeyafaf53b2013-09-26 14:30:47 -07001246 DownloadStorageProvider.onDownloadProviderDelete(getContext(), id);
Jeff Sharkeye3960ea2015-06-16 17:06:37 -07001247
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 Sharkeyafaf53b2013-09-26 14:30:47 -07001256 }
1257 } finally {
1258 IoUtils.closeQuietly(cursor);
1259 }
1260
Steve Howarde610c052010-10-07 18:16:15 -07001261 count = db.delete(DB_TABLE, selection.getSelection(), selection.getParameters());
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001262 break;
Steve Howard3d55d822010-09-12 18:53:31 -07001263
1264 default:
Doug Zongker9b731a52014-01-27 11:19:19 -08001265 Log.d(Constants.TAG, "deleting unknown/invalid URI: " + uri);
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001266 throw new UnsupportedOperationException("Cannot delete URI: " + uri);
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001267 }
Steve Howard3d55d822010-09-12 18:53:31 -07001268 notifyContentChanged(uri, match);
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001269 return count;
1270 }
Steve Howard71aab522010-07-20 16:32:31 -07001271
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001272 /**
1273 * Remotely opens a file
1274 */
1275 @Override
Jeff Sharkeyc067c8b2013-09-23 14:21:55 -07001276 public ParcelFileDescriptor openFile(final Uri uri, String mode) throws FileNotFoundException {
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001277 if (Constants.LOGVV) {
Steve Howard3d55d822010-09-12 18:53:31 -07001278 logVerboseOpenFileInfo(uri, mode);
1279 }
1280
Jeff Sharkey91449262016-09-16 12:12:17 -06001281 // 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 Sharkey2e169792014-08-05 18:04:54 -07001294 final Cursor cursor = queryCleared(uri, new String[] {
Jeff Sharkey495edec2014-08-05 11:51:24 -07001295 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 Howard3d55d822010-09-12 18:53:31 -07001300 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 Project57f55b32008-10-21 07:00:00 -07001306 }
Steve Howard3d55d822010-09-12 18:53:31 -07001307 throw new FileNotFoundException("Multiple items at " + uri);
1308 }
1309
Jeff Sharkey495edec2014-08-05 11:51:24 -07001310 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 Howard3d55d822010-09-12 18:53:31 -07001324 } finally {
Jeff Sharkeyc067c8b2013-09-23 14:21:55 -07001325 IoUtils.closeQuietly(cursor);
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001326 }
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -08001327
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -08001328 if (path == null) {
1329 throw new FileNotFoundException("No filename found.");
1330 }
Steve Howard3d55d822010-09-12 18:53:31 -07001331
Jeff Sharkey5508e272016-01-07 14:15:59 -07001332 final File file;
1333 try {
1334 file = new File(path).getCanonicalFile();
1335 } catch (IOException e) {
1336 throw new FileNotFoundException(e.getMessage());
1337 }
1338
Jeff Sharkeydffbb9c2014-01-30 15:01:39 -08001339 if (!Helpers.isFilenameValid(getContext(), file)) {
Jeff Sharkey5508e272016-01-07 14:15:59 -07001340 throw new FileNotFoundException("Invalid file path: " + file);
Jeff Sharkeydffbb9c2014-01-30 15:01:39 -08001341 }
1342
Jeff Sharkey495edec2014-08-05 11:51:24 -07001343 final int pfdMode = ParcelFileDescriptor.parseMode(mode);
1344 if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY) {
1345 return ParcelFileDescriptor.open(file, pfdMode);
Jeff Sharkeyc067c8b2013-09-23 14:21:55 -07001346 } else {
1347 try {
1348 // When finished writing, update size and timestamp
Jeff Sharkey495edec2014-08-05 11:51:24 -07001349 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 Sharkeyc067c8b2013-09-23 14:21:55 -07001366 } catch (IOException e) {
1367 throw new FileNotFoundException("Failed to open for writing: " + e);
1368 }
1369 }
The Android Open Source Project57f55b32008-10-21 07:00:00 -07001370 }
1371
Jeff Sharkey1d0a0aa2013-01-30 11:26:46 -08001372 @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 Howard3d55d822010-09-12 18:53:31 -07001407 private void logVerboseOpenFileInfo(Uri uri, String mode) {
Doug Zongker9b731a52014-01-27 11:19:19 -08001408 Log.v(Constants.TAG, "openFile uri: " + uri + ", mode: " + mode
Steve Howard3d55d822010-09-12 18:53:31 -07001409 + ", uid: " + Binder.getCallingUid());
1410 Cursor cursor = query(Downloads.Impl.CONTENT_URI,
1411 new String[] { "_id" }, null, null, "_id");
1412 if (cursor == null) {
Doug Zongker9b731a52014-01-27 11:19:19 -08001413 Log.v(Constants.TAG, "null cursor in openFile");
Steve Howard3d55d822010-09-12 18:53:31 -07001414 } else {
Mattias Nilssona2aad3a2014-03-27 16:20:21 +01001415 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 Howard3d55d822010-09-12 18:53:31 -07001425 }
Steve Howard3d55d822010-09-12 18:53:31 -07001426 }
1427 cursor = query(uri, new String[] { "_data" }, null, null, null);
1428 if (cursor == null) {
Doug Zongker9b731a52014-01-27 11:19:19 -08001429 Log.v(Constants.TAG, "null cursor in openFile");
Steve Howard3d55d822010-09-12 18:53:31 -07001430 } else {
Mattias Nilssona2aad3a2014-03-27 16:20:21 +01001431 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 Howard3d55d822010-09-12 18:53:31 -07001440 }
Mattias Nilssona2aad3a2014-03-27 16:20:21 +01001441 } finally {
1442 cursor.close();
Steve Howard3d55d822010-09-12 18:53:31 -07001443 }
Steve Howard3d55d822010-09-12 18:53:31 -07001444 }
1445 }
1446
The Android Open Source Project1fbad9c2008-12-17 18:06:03 -08001447 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 Howarda89321e2010-08-03 12:39:35 -07001468 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 Sharkey91449262016-09-16 12:12:17 -06001475
Suprabh Shukla89986522017-02-27 15:56:11 -08001476 private void grantAllDownloadsPermission(String toPackage, long id) {
Jeff Sharkey91449262016-09-16 12:12:17 -06001477 final Uri uri = ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
Suprabh Shukla89986522017-02-27 15:56:11 -08001478 getContext().grantUriPermission(toPackage, uri,
Jeff Sharkey91449262016-09-16 12:12:17 -06001479 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 Project57f55b32008-10-21 07:00:00 -07001486}