Snap for 8426163 from 965cc56320b780ef6c30475e809886bb0cd44505 to mainline-tzdata2-release

Change-Id: I3cb19386c0c12321145b78aa47894598b938769c
diff --git a/Android.bp b/Android.bp
index 106ed1e..78c8072 100644
--- a/Android.bp
+++ b/Android.bp
@@ -13,28 +13,8 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: [
-        "packages_providers_DownloadProvider_license",
-    ],
-}
-
-// Added automatically by a large-scale-change
-// See: http://go/android-license-faq
-license {
-    name: "packages_providers_DownloadProvider_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        "SPDX-license-identifier-Apache-2.0",
-    ],
-    license_text: [
-        "NOTICE",
-    ],
-}
-
 android_app {
     name: "DownloadProvider",
-    defaults: ["platform_app_defaults"],
 
     manifest: "AndroidManifest.xml",
 
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 43cf258..6ef1b54 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -60,7 +60,6 @@
     <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
     <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
 
     <application android:process="android.process.media"
                  android:label="@string/app_label"
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
index 79add9e..8134437 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,2 @@
-# Bug component: 95221
-
-include platform/frameworks/base:/core/java/android/os/storage/OWNERS
+jsharkey@android.com
+sudheersai@google.com
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 2355624..bd9021e 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="5264040740662487684">"Менеджер скачивания"</string>
+    <string name="app_label" msgid="5264040740662487684">"Диспетчер загрузки"</string>
     <string name="storage_description" msgid="169690279447532621">"Скачанные"</string>
     <string name="permlab_downloadManager" msgid="4241473724446132797">"Доступ к менеджеру загрузки."</string>
     <string name="permdesc_downloadManager" msgid="5562734314998369030">"Приложение получит доступ к диспетчеру загрузок и сможет загружать файлы через него. Вредоносные программы смогут таким образом прерывать загрузки и просматривать личную информацию."</string>
diff --git a/src/com/android/providers/downloads/DownloadNotifier.java b/src/com/android/providers/downloads/DownloadNotifier.java
index 224aee4..d38aa75 100644
--- a/src/com/android/providers/downloads/DownloadNotifier.java
+++ b/src/com/android/providers/downloads/DownloadNotifier.java
@@ -233,8 +233,7 @@
                 intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS,
                         downloadIds);
                 builder.setContentIntent(PendingIntent.getBroadcast(mContext,
-                        0, intent,
-                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
+                        0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
                 if (type == TYPE_ACTIVE) {
                     builder.setOngoing(true);
                 }
@@ -251,8 +250,7 @@
                     android.R.drawable.ic_menu_close_clear_cancel,
                     res.getString(R.string.button_cancel_download),
                     PendingIntent.getBroadcast(mContext,
-                            0, cancelIntent,
-                            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
+                            0, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT));
 
             } else if (type == TYPE_COMPLETE) {
                 cursor.moveToPosition(cluster.get(0));
@@ -276,14 +274,12 @@
                 intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS,
                         getDownloadIds(cursor, cluster));
                 builder.setContentIntent(PendingIntent.getBroadcast(mContext,
-                        0, intent,
-                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
+                        0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
 
                 final Intent hideIntent = new Intent(Constants.ACTION_HIDE,
                         uri, mContext, DownloadReceiver.class);
                 hideIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-                builder.setDeleteIntent(PendingIntent.getBroadcast(mContext, 0, hideIntent,
-                            PendingIntent.FLAG_IMMUTABLE));
+                builder.setDeleteIntent(PendingIntent.getBroadcast(mContext, 0, hideIntent, 0));
             }
 
             // Calculate and show progress
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
index a9e0237..b868113 100644
--- a/src/com/android/providers/downloads/DownloadProvider.java
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -257,6 +257,7 @@
     private int mSystemUid = -1;
 
     private StorageManager mStorageManager;
+    private AppOpsManager mAppOpsManager;
 
     /**
      * Creates and updated database on demand when opening it.
@@ -587,6 +588,7 @@
         mSystemUid = Process.SYSTEM_UID;
 
         mStorageManager = getContext().getSystemService(StorageManager.class);
+        mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
 
         // Grant access permissions for all known downloads to the owning apps.
         final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
@@ -735,10 +737,9 @@
                         android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
                         "No permission to write");
 
-                final AppOpsManager appOps = getContext().getSystemService(AppOpsManager.class);
-                if (appOps.noteProxyOp(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE, getCallingPackage(),
-                        Binder.getCallingUid(), getCallingAttributionTag(), null)
-                        != AppOpsManager.MODE_ALLOWED) {
+                if (mAppOpsManager.noteProxyOp(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE,
+                        getCallingPackage(), Binder.getCallingUid(), getCallingAttributionTag(),
+                        null) != AppOpsManager.MODE_ALLOWED) {
                     throw new SecurityException("No permission to write");
                 }
             }
@@ -942,13 +943,6 @@
                 }
                 return mediaStoreUri;
             }
-        } catch (IllegalArgumentException ignored) {
-            // Insert or update MediaStore failed. At this point we can't do
-            // much here. If the file belongs to MediaStore collection, it will
-            // get added to MediaStore collection during next scan, and we will
-            // obtain the uri to the file in the next MediaStore#scanFile
-            // initiated by us
-            Log.w(Constants.TAG, "Couldn't update MediaStore for " + filePath, ignored);
         } catch (RemoteException e) {
             // Should not happen
         }
@@ -1067,41 +1061,11 @@
             throw new SecurityException(e);
         }
 
-        final int targetSdkVersion = getCallingPackageTargetSdkVersion();
-        final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
-        final boolean runningLegacyMode = appOpsManager.checkOp(AppOpsManager.OP_LEGACY_STORAGE,
+        final boolean isLegacyMode = mAppOpsManager.checkOp(AppOpsManager.OP_LEGACY_STORAGE,
                 Binder.getCallingUid(), getCallingPackage()) == AppOpsManager.MODE_ALLOWED;
-
-        if (Helpers.isFilenameValidInExternalPackage(getContext(), file, getCallingPackage())
-                || Helpers.isFilenameValidInKnownPublicDir(file.getAbsolutePath())) {
-            // No permissions required for paths belonging to calling package or
-            // public downloads dir.
-            return;
-        } else if (runningLegacyMode && Helpers.isFilenameValidInExternal(getContext(), file)) {
-            // Otherwise we require write permission
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
-                    "No permission to write to " + file);
-
-            final AppOpsManager appOps = getContext().getSystemService(AppOpsManager.class);
-            if (appOps.noteProxyOp(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE, getCallingPackage(),
-                    Binder.getCallingUid(), getCallingAttributionTag(), null)
-                    != AppOpsManager.MODE_ALLOWED) {
-                throw new SecurityException("No permission to write to " + file);
-            }
-        } else if (Helpers.isFilenameValidInExternalObbDir(file) &&
-                ((appOpsManager.noteOp(
-                    AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
-                    Binder.getCallingUid(), getCallingPackage(), null, "obb_download")
-                        == AppOpsManager.MODE_ALLOWED)
-                || (getContext().checkCallingOrSelfPermission(
-                    android.Manifest.permission.REQUEST_INSTALL_PACKAGES)
-                    == PackageManager.PERMISSION_GRANTED))) {
-            // Installers are allowed to download in OBB dirs, even outside their own package
-            return;
-        } else {
-            throw new SecurityException("Unsupported path " + file);
-        }
+        Helpers.checkDestinationFilePathRestrictions(file, getCallingPackage(), getContext(),
+                mAppOpsManager, getCallingAttributionTag(), isLegacyMode,
+                /* allowDownloadsDirOnly */ false);
     }
 
     private void checkDownloadedFilePath(ContentValues values) {
@@ -1123,49 +1087,15 @@
             throw new IllegalArgumentException("File doesn't exist: " + file);
         }
 
-        final int targetSdkVersion = getCallingPackageTargetSdkVersion();
-        final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
-        final boolean runningLegacyMode = appOpsManager.checkOp(AppOpsManager.OP_LEGACY_STORAGE,
-                Binder.getCallingUid(), getCallingPackage()) == AppOpsManager.MODE_ALLOWED;
-
         if (Binder.getCallingPid() == Process.myPid()) {
             return;
-        } else if (Helpers.isFilenameValidInExternalPackage(getContext(), file, getCallingPackage())
-                || Helpers.isFilenameValidInPublicDownloadsDir(file)) {
-            // No permissions required for paths belonging to calling package or
-            // public downloads dir.
-            return;
-        } else if (runningLegacyMode && Helpers.isFilenameValidInExternal(getContext(), file)) {
-            // Otherwise we require write permission
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
-                    "No permission to write to " + file);
-
-            final AppOpsManager appOps = getContext().getSystemService(AppOpsManager.class);
-            if (appOps.noteProxyOp(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE, getCallingPackage(),
-                    Binder.getCallingUid(), getCallingAttributionTag(), null)
-                    != AppOpsManager.MODE_ALLOWED) {
-                throw new SecurityException("No permission to write to " + file);
-            }
-        } else {
-            throw new SecurityException("Unsupported path " + file);
         }
-    }
 
-    private int getCallingPackageTargetSdkVersion() {
-        final String callingPackage = getCallingPackage();
-        if (callingPackage != null) {
-            ApplicationInfo ai = null;
-            try {
-                ai = getContext().getPackageManager()
-                        .getApplicationInfo(callingPackage, 0);
-            } catch (PackageManager.NameNotFoundException ignored) {
-            }
-            if (ai != null) {
-                return ai.targetSdkVersion;
-            }
-        }
-        return Build.VERSION_CODES.CUR_DEVELOPMENT;
+        final boolean isLegacyMode = mAppOpsManager.checkOp(AppOpsManager.OP_LEGACY_STORAGE,
+                Binder.getCallingUid(), getCallingPackage()) == AppOpsManager.MODE_ALLOWED;
+        Helpers.checkDestinationFilePathRestrictions(file, getCallingPackage(), getContext(),
+                mAppOpsManager, getCallingAttributionTag(), isLegacyMode,
+                /* allowDownloadsDirOnly */ true);
     }
 
     /**
@@ -1253,7 +1183,6 @@
                     error.append(", ");
                 }
                 error.append(entry.getKey());
-                first = false;
             }
             throw new SecurityException(error.toString());
         }
@@ -1817,7 +1746,7 @@
                         final ContentValues values = new ContentValues();
                         values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, file.length());
                         values.put(Downloads.Impl.COLUMN_LAST_MODIFICATION,
-                                mSystemFacade.currentTimeMillis());
+                                System.currentTimeMillis());
                         update(uri, values, null, null);
 
                         if (shouldScan) {
diff --git a/src/com/android/providers/downloads/DownloadStorageProvider.java b/src/com/android/providers/downloads/DownloadStorageProvider.java
index 421c261..36304aa 100644
--- a/src/com/android/providers/downloads/DownloadStorageProvider.java
+++ b/src/com/android/providers/downloads/DownloadStorageProvider.java
@@ -18,7 +18,7 @@
 
 import static com.android.providers.downloads.MediaStoreDownloadsHelper.getDocIdForMediaStoreDownload;
 import static com.android.providers.downloads.MediaStoreDownloadsHelper.getMediaStoreIdString;
-import static com.android.providers.downloads.MediaStoreDownloadsHelper.getMediaStoreUriForQuery;
+import static com.android.providers.downloads.MediaStoreDownloadsHelper.getMediaStoreUri;
 import static com.android.providers.downloads.MediaStoreDownloadsHelper.isMediaStoreDownload;
 import static com.android.providers.downloads.MediaStoreDownloadsHelper.isMediaStoreDownloadDir;
 
@@ -28,6 +28,7 @@
 import android.app.DownloadManager.Query;
 import android.content.ContentResolver;
 import android.content.ContentUris;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.UriPermission;
 import android.database.Cursor;
@@ -66,7 +67,6 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Locale;
 import java.util.Set;
 
 /**
@@ -251,7 +251,7 @@
 
             displayName = FileUtils.buildValidFatFilename(displayName);
             if (isMediaStoreDownload(docId)) {
-                return renameMediaStoreDownload(docId, displayName);
+                renameMediaStoreDownload(docId, displayName);
             } else {
                 final long id = Long.parseLong(docId);
                 if (!mDm.rename(getContext(), id, displayName)) {
@@ -281,7 +281,7 @@
             if (DOC_ID_ROOT.equals(docId)) {
                 includeDefaultDocument(result);
             } else if (isMediaStoreDownload(docId)) {
-                cursor = getContext().getContentResolver().query(getMediaStoreUriForQuery(docId),
+                cursor = getContext().getContentResolver().query(getMediaStoreUri(docId),
                         null, null, null);
                 copyNotificationUri(result, cursor);
                 if (cursor.moveToFirst()) {
@@ -513,7 +513,7 @@
             final ContentResolver resolver = getContext().getContentResolver();
             final Uri contentUri;
             if (isMediaStoreDownload(docId)) {
-                contentUri = getMediaStoreUriForQuery(docId);
+                contentUri = getMediaStoreUri(docId);
             } else {
                 final long id = Long.parseLong(docId);
                 contentUri = mDm.getDownloadUri(id);
@@ -537,7 +537,7 @@
             final ContentResolver resolver = getContext().getContentResolver();
             final Uri contentUri;
             if (isMediaStoreDownload(docId)) {
-                contentUri = getMediaStoreUriForQuery(docId);
+                contentUri = getMediaStoreUri(docId);
             } else {
                 final long id = Long.parseLong(docId);
                 contentUri = mDm.getDownloadUri(id);
@@ -769,7 +769,7 @@
         return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
     }
 
-    private String renameMediaStoreDownload(String docId, String displayName) {
+    private void renameMediaStoreDownload(String docId, String displayName) {
         final File before = getFileForMediaStoreDownload(docId);
         final File after = new File(before.getParentFile(), displayName);
 
@@ -780,34 +780,25 @@
             throw new IllegalStateException("Failed to rename from " + before + " to " + after);
         }
 
-        final String noMedia = ".nomedia";
-        // Scan the file to update the database
-        // For file, check whether the file is renamed to .nomedia. If yes, to scan the parent
-        // directory to update all files in the directory. We don't consider the case of renaming
-        // .nomedia file. We don't show .nomedia file.
-        if (!after.isDirectory() && displayName.toLowerCase(Locale.ROOT).endsWith(noMedia)) {
-            final Uri newUri = MediaStore.scanFile(getContext().getContentResolver(),
-                    after.getParentFile());
-            // the file will not show in the list, return the parent docId to avoid not finding
-            // the detail for the file.
-            return getDocIdForMediaStoreDownloadUri(newUri, true /* isDir */);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final Uri mediaStoreUri = getMediaStoreUri(docId);
+            final ContentValues values = new ContentValues();
+            values.put(DownloadColumns.DATA, after.getAbsolutePath());
+            values.put(DownloadColumns.DISPLAY_NAME, displayName);
+            final int count = getContext().getContentResolver().update(mediaStoreUri, values,
+                    null, null);
+            if (count != 1) {
+                throw new IllegalStateException("Failed to update " + mediaStoreUri
+                        + ", values=" + values);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
-        // update the database for the old file
-        MediaStore.scanFile(getContext().getContentResolver(), before);
-        // Update tne database for the new file and get the new uri
-        final Uri newUri = MediaStore.scanFile(getContext().getContentResolver(), after);
-        return getDocIdForMediaStoreDownloadUri(newUri, after.isDirectory());
-    }
-
-    private static String getDocIdForMediaStoreDownloadUri(Uri uri, boolean isDir) {
-        if (uri != null) {
-            return getDocIdForMediaStoreDownload(Long.parseLong(uri.getLastPathSegment()), isDir);
-        }
-        return null;
     }
 
     private File getFileForMediaStoreDownload(String docId) {
-        final Uri mediaStoreUri = getMediaStoreUriForQuery(docId);
+        final Uri mediaStoreUri = getMediaStoreUri(docId);
         final long token = Binder.clearCallingIdentity();
         try (Cursor cursor = queryForSingleItem(mediaStoreUri,
                 new String[] { DownloadColumns.DATA }, null, null, null)) {
diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java
index 00fa30a..752dbfc 100644
--- a/src/com/android/providers/downloads/DownloadThread.java
+++ b/src/com/android/providers/downloads/DownloadThread.java
@@ -90,7 +90,6 @@
 import java.net.URL;
 import java.net.URLConnection;
 import java.security.GeneralSecurityException;
-import java.util.Arrays;
 
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
@@ -234,6 +233,8 @@
     private boolean mIgnoreBlocked;
     private Network mNetwork;
 
+    private int mNetworkType = ConnectivityManager.TYPE_NONE;
+
     /** Historical bytes/second speed of this download. */
     private long mSpeed;
     /** Time when current sample started. */
@@ -293,6 +294,14 @@
                         "No network associated with requesting UID");
             }
 
+            // Remember which network this download started on; used to
+            // determine if errors were due to network changes.
+            final NetworkInfo info = mSystemFacade.getNetworkInfo(mNetwork, mInfo.mUid,
+                    mIgnoreBlocked);
+            if (info != null) {
+                mNetworkType = info.getType();
+            }
+
             // Network traffic on this thread should be counted against the
             // requesting UID, and is tagged with well-known value.
             TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_DOWNLOAD);
@@ -331,11 +340,13 @@
                 }
 
                 if (mInfoDelta.mNumFailed < Constants.MAX_RETRIES) {
-                    if (null != mSystemFacade.getNetworkCapabilities(mNetwork)) {
+                    final NetworkInfo info = mSystemFacade.getNetworkInfo(mNetwork, mInfo.mUid,
+                            mIgnoreBlocked);
+                    if (info != null && info.getType() == mNetworkType && info.isConnected()) {
                         // Underlying network is still intact, use normal backoff
                         mInfoDelta.mStatus = STATUS_WAITING_TO_RETRY;
                     } else {
-                        // Network unavailable, retry on any next available
+                        // Network changed, retry on any next available
                         mInfoDelta.mStatus = STATUS_WAITING_FOR_NETWORK;
                     }
 
@@ -713,8 +724,9 @@
         // checking connectivity will apply current policy
         mPolicyDirty = false;
 
+        final NetworkInfo info = mSystemFacade.getNetworkInfo(mNetwork, mInfo.mUid, mIgnoreBlocked);
         final NetworkCapabilities caps = mSystemFacade.getNetworkCapabilities(mNetwork);
-        if (caps == null) {
+        if (info == null || !info.isConnected()) {
             throw new StopRequestException(STATUS_WAITING_FOR_NETWORK, "Network is disconnected");
         }
         if (!caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)
diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java
index 772c0b9..574b222 100644
--- a/src/com/android/providers/downloads/Helpers.java
+++ b/src/com/android/providers/downloads/Helpers.java
@@ -34,6 +34,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
 import android.content.ComponentName;
@@ -41,8 +42,10 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -84,6 +87,9 @@
     private static final Pattern PATTERN_ANDROID_DIRS =
             Pattern.compile("(?i)^/storage/[^/]+(?:/[0-9]+)?/Android/(?:data|obb|media)/.+");
 
+    private static final Pattern PATTERN_ANDROID_PRIVATE_DIRS =
+            Pattern.compile("(?i)^/storage/[^/]+(?:/[0-9]+)?/Android/(data|obb)/.+");
+
     private static final Pattern PATTERN_PUBLIC_DIRS =
             Pattern.compile("(?i)^/storage/[^/]+(?:/[0-9]+)?/([^/]+)/.+");
 
@@ -530,8 +536,7 @@
      * directories that are always writable to apps, regardless of storage
      * permission.
      */
-    static boolean isFilenameValidInExternalPackage(Context context, File file,
-            String packageName) {
+    static boolean isFilenameValidInExternalPackage(File file, String packageName) {
         try {
             if (containsCanonical(buildExternalStorageAppDataDirs(packageName), file) ||
                     containsCanonical(buildExternalStorageAppObbDirs(packageName), file) ||
@@ -539,7 +544,7 @@
                 return true;
             }
         } catch (IOException e) {
-            Log.w(TAG, "Failed to resolve canonical path: " + e);
+            Log.w(TAG, "Failed to resolve canonical path: " + file.getAbsolutePath(), e);
             return false;
         }
 
@@ -552,13 +557,77 @@
                 return true;
             }
         } catch (IOException e) {
-            Log.w(TAG, "Failed to resolve canonical path: " + e);
+            Log.w(TAG, "Failed to resolve canonical path: " + file.getAbsolutePath(), e);
             return false;
         }
 
         return false;
     }
 
+    /**
+     * Check if given file exists in one of the private package-specific external storage
+     * directories.
+     */
+    static boolean isFileInPrivateExternalAndroidDirs(File file) {
+        try {
+            return PATTERN_ANDROID_PRIVATE_DIRS.matcher(file.getCanonicalPath()).matches();
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to resolve canonical path: " + file.getAbsolutePath(), e);
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks destination file path restrictions adhering to App privacy restrictions
+     *
+     * Note: This method is extracted to a static method for better test coverage.
+     */
+    @VisibleForTesting
+    static void checkDestinationFilePathRestrictions(File file, String callingPackage,
+            Context context, AppOpsManager appOpsManager, String callingAttributionTag,
+            boolean isLegacyMode, boolean allowDownloadsDirOnly) {
+        boolean isFileNameValid = allowDownloadsDirOnly ? isFilenameValidInPublicDownloadsDir(file)
+                : isFilenameValidInKnownPublicDir(file.getAbsolutePath());
+        if (isFilenameValidInExternalPackage(file, callingPackage) || isFileNameValid) {
+            // No permissions required for paths belonging to calling package or
+            // public downloads dir.
+            return;
+        } else if (isFilenameValidInExternalObbDir(file) &&
+                isCallingAppInstaller(context, appOpsManager, callingPackage)) {
+            // Installers are allowed to download in OBB dirs, even outside their own package
+            return;
+        } else if (isFileInPrivateExternalAndroidDirs(file)) {
+            // Positive cases of writing to external Android dirs is covered in the if blocks above.
+            // If the caller made it this far, then it cannot write to this path as it is restricted
+            // from writing to other app's external Android dirs.
+            throw new SecurityException("Unsupported path " + file);
+        } else if (isLegacyMode && isFilenameValidInExternal(context, file)) {
+            // Otherwise we require write permission
+            context.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                    "No permission to write to " + file);
+
+            if (appOpsManager.noteProxyOp(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE,
+                    callingPackage, Binder.getCallingUid(), callingAttributionTag, null)
+                    != AppOpsManager.MODE_ALLOWED) {
+                throw new SecurityException("No permission to write to " + file);
+            }
+        } else {
+            throw new SecurityException("Unsupported path " + file);
+        }
+    }
+
+    private static boolean isCallingAppInstaller(Context context, AppOpsManager appOpsManager,
+            String callingPackage) {
+        return (appOpsManager.noteOp(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
+                Binder.getCallingUid(), callingPackage, null, "obb_download")
+                == AppOpsManager.MODE_ALLOWED)
+                || (context.checkCallingOrSelfPermission(
+                android.Manifest.permission.REQUEST_INSTALL_PACKAGES)
+                == PackageManager.PERMISSION_GRANTED);
+    }
+
     static boolean isFilenameValidInPublicDownloadsDir(File file) {
         try {
             if (containsCanonical(buildExternalStoragePublicDirs(
@@ -566,7 +635,7 @@
                 return true;
             }
         } catch (IOException e) {
-            Log.w(TAG, "Failed to resolve canonical path: " + e);
+            Log.w(TAG, "Failed to resolve canonical path: " + file.getAbsolutePath(), e);
             return false;
         }
 
@@ -608,7 +677,7 @@
                 }
             }
         } catch (IOException e) {
-            Log.w(TAG, "Failed to resolve canonical path: " + e);
+            Log.w(TAG, "Failed to resolve canonical path: " + file.getAbsolutePath(), e);
             return false;
         }
 
@@ -786,13 +855,13 @@
             final ContentValues values = new ContentValues();
             values.putNull(Constants.UID);
             downloadProvider.update(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, values,
-                    Helpers.buildQueryWithIds(idsToOrphan), null);
+                    buildQueryWithIds(idsToOrphan), null);
         }
         if (idsToDelete.size() > 0) {
             Log.i(Constants.TAG, "Deleting downloads with ids "
                     + Arrays.toString(idsToDelete.toArray()) + " as owner package is removed");
             downloadProvider.delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
-                    Helpers.buildQueryWithIds(idsToDelete), null);
+                    buildQueryWithIds(idsToDelete), null);
         }
     }
 
diff --git a/src/com/android/providers/downloads/MediaStoreDownloadsHelper.java b/src/com/android/providers/downloads/MediaStoreDownloadsHelper.java
index 9c86d53..c4f347c 100644
--- a/src/com/android/providers/downloads/MediaStoreDownloadsHelper.java
+++ b/src/com/android/providers/downloads/MediaStoreDownloadsHelper.java
@@ -47,18 +47,8 @@
         return docId != null && docId.startsWith(MEDIASTORE_DOWNLOAD_DIR_PREFIX);
     }
 
-    /**
-     * The returned uri always appends external volume {@link MediaStore#VOLUME_EXTERNAL}.
-     * It doesn't consider the item is located on second volume. It can't be used to update
-     * or insert.
-     * @param docId the doc id
-     * @return external uri for query
-     */
-    public static Uri getMediaStoreUriForQuery(String docId) {
-        return getMediaStoreUri(MediaStore.VOLUME_EXTERNAL, docId);
-    }
-
-    public static Uri getMediaStoreUri(String volume, String docId) {
-        return MediaStore.Downloads.getContentUri(volume, getMediaStoreId(docId));
+    public static Uri getMediaStoreUri(String docId) {
+        return ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI,
+                getMediaStoreId(docId));
     }
 }
diff --git a/src/com/android/providers/downloads/RealSystemFacade.java b/src/com/android/providers/downloads/RealSystemFacade.java
index ba4068e..94461a6 100644
--- a/src/com/android/providers/downloads/RealSystemFacade.java
+++ b/src/com/android/providers/downloads/RealSystemFacade.java
@@ -57,6 +57,12 @@
     }
 
     @Override
+    public NetworkInfo getNetworkInfo(Network network, int uid, boolean ignoreBlocked) {
+        return mContext.getSystemService(ConnectivityManager.class)
+                .getNetworkInfoForUid(network, uid, ignoreBlocked);
+    }
+
+    @Override
     public NetworkCapabilities getNetworkCapabilities(Network network) {
         return mContext.getSystemService(ConnectivityManager.class)
                 .getNetworkCapabilities(network);
diff --git a/src/com/android/providers/downloads/SystemFacade.java b/src/com/android/providers/downloads/SystemFacade.java
index 1fda858..d73fe11 100644
--- a/src/com/android/providers/downloads/SystemFacade.java
+++ b/src/com/android/providers/downloads/SystemFacade.java
@@ -37,6 +37,7 @@
 
     public Network getNetwork(JobParameters params);
 
+    public NetworkInfo getNetworkInfo(Network network, int uid, boolean ignoreBlocked);
     public NetworkCapabilities getNetworkCapabilities(Network network);
 
     /**
diff --git a/tests/Android.bp b/tests/Android.bp
index d0ba0a5..3c03c82 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -13,17 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "packages_providers_DownloadProvider_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: [
-        "packages_providers_DownloadProvider_license",
-    ],
-}
-
 android_test {
     name: "DownloadProviderTests",
 
@@ -38,8 +27,6 @@
     ],
 
     static_libs: [
-        "androidx.core_core",
-        "androidx.test.rules",
         "mockito-target",
         "mockwebserver",
     ],
diff --git a/tests/permission/Android.bp b/tests/permission/Android.bp
index a96621f..274106f 100644
--- a/tests/permission/Android.bp
+++ b/tests/permission/Android.bp
@@ -13,17 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "packages_providers_DownloadProvider_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: [
-        "packages_providers_DownloadProvider_license",
-    ],
-}
-
 android_test {
     name: "DownloadProviderPermissionTests",
     test_suites: ["device-tests"],
diff --git a/tests/public_api_access/Android.bp b/tests/public_api_access/Android.bp
index 3d2cd98..1f982b3 100644
--- a/tests/public_api_access/Android.bp
+++ b/tests/public_api_access/Android.bp
@@ -13,17 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "packages_providers_DownloadProvider_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: [
-        "packages_providers_DownloadProvider_license",
-    ],
-}
-
 android_test {
     name: "DownloadPublicApiAccessTests",
     test_suites: ["device-tests"],
diff --git a/tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java b/tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java
index 4afe3b7..a505632 100644
--- a/tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java
+++ b/tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java
@@ -127,6 +127,11 @@
         public Bundle call(String method, String request, Bundle args) {
           return new Bundle();
         }
+
+        @Override
+        public IBinder getIContentProviderBinder() {
+            return new Binder();
+        }
     }
 
     /**
@@ -208,7 +213,6 @@
         setupService();
         Helpers.setSystemFacade(mSystemFacade);
 
-        cleanUpDownloads();
         mSystemFacade.setUp();
         assertDatabaseEmpty(); // ensure we're not messing with real data
         assertDatabaseSecureAgainstBadSelection();
diff --git a/tests/src/com/android/providers/downloads/FakeInputStream.java b/tests/src/com/android/providers/downloads/FakeInputStream.java
index 65724ac..3f68e31 100644
--- a/tests/src/com/android/providers/downloads/FakeInputStream.java
+++ b/tests/src/com/android/providers/downloads/FakeInputStream.java
@@ -16,6 +16,8 @@
 
 package com.android.providers.downloads;
 
+import libcore.util.ArrayUtils;
+
 import java.io.InputStream;
 
 /**
@@ -41,7 +43,7 @@
 
     @Override
     public int read(byte[] buffer, int offset, int length) {
-        throwsIfOutOfBounds(buffer.length, offset, length);
+        ArrayUtils.throwsIfOutOfBounds(buffer.length, offset, length);
 
         if (length > mRemaining) {
             length = (int) mRemaining;
@@ -54,15 +56,4 @@
             return length;
         }
     }
-
-    private static void throwsIfOutOfBounds(int len, int offset, int count) {
-        if (len < 0) {
-            throw new ArrayIndexOutOfBoundsException("Negative length: " + len);
-        }
-
-        if ((offset | count) < 0 || offset > len - count) {
-            throw new ArrayIndexOutOfBoundsException(
-                    "length=" + len + "; regionStart=" + offset + "; regionLength=" + count);
-        }
-    }
 }
diff --git a/tests/src/com/android/providers/downloads/FakeSystemFacade.java b/tests/src/com/android/providers/downloads/FakeSystemFacade.java
index fadcd36..d11e0ac 100644
--- a/tests/src/com/android/providers/downloads/FakeSystemFacade.java
+++ b/tests/src/com/android/providers/downloads/FakeSystemFacade.java
@@ -93,14 +93,25 @@
     }
 
     @Override
+    public NetworkInfo getNetworkInfo(Network network, int uid, boolean ignoreBlocked) {
+        if (mActiveNetworkType == null) {
+            return null;
+        } else {
+            final NetworkInfo info = new NetworkInfo(mActiveNetworkType, 0, null, null);
+            info.setDetailedState(DetailedState.CONNECTED, null, null);
+            return info;
+        }
+    }
+
+    @Override
     public NetworkCapabilities getNetworkCapabilities(Network network) {
         if (mActiveNetworkType == null) {
             return null;
         } else {
-            final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
-            if (!mIsMetered) builder.addCapability(NET_CAPABILITY_NOT_METERED);
-            if (!mIsRoaming) builder.addCapability(NET_CAPABILITY_NOT_ROAMING);
-            return builder.build();
+            final NetworkCapabilities caps = new NetworkCapabilities();
+            caps.setCapability(NET_CAPABILITY_NOT_METERED, !mIsMetered);
+            caps.setCapability(NET_CAPABILITY_NOT_ROAMING, !mIsRoaming);
+            return caps;
         }
     }
 
diff --git a/tests/src/com/android/providers/downloads/FsHelper.java b/tests/src/com/android/providers/downloads/FsHelper.java
deleted file mode 100644
index d83827c..0000000
--- a/tests/src/com/android/providers/downloads/FsHelper.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.android.providers.downloads;
-
-import java.io.File;
-
-public class FsHelper {
-
-    /**
-     * Deletes all files under a given directory. Deliberately ignores errors, on the assumption
-     * that test cleanup is only supposed to be best-effort.
-     *
-     * @param directory directory to clear its contents
-     */
-    public static void deleteContents(File directory) {
-        File[] files = directory.listFiles();
-        if (files != null) {
-            for (File file : files) {
-                if (file.isDirectory()) {
-                    deleteContents(file);
-                }
-                file.delete();
-            }
-        }
-    }
-}
diff --git a/tests/src/com/android/providers/downloads/HelpersTest.java b/tests/src/com/android/providers/downloads/HelpersTest.java
index 08c0b13..793a157 100644
--- a/tests/src/com/android/providers/downloads/HelpersTest.java
+++ b/tests/src/com/android/providers/downloads/HelpersTest.java
@@ -35,11 +35,13 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AppOpsManager;
 import android.content.ContentProvider;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.database.MatrixCursor;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Environment;
 import android.os.Process;
 import android.provider.Downloads;
@@ -53,7 +55,6 @@
 
 import java.io.File;
 import java.util.Arrays;
-import java.util.function.BiConsumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -62,6 +63,7 @@
  */
 @SmallTest
 public class HelpersTest extends AndroidTestCase {
+    private static final String TAG = "DownloadManagerHelpersTest";
 
     private final static int TEST_UID1 = 11111;
     private final static int TEST_UID2 = 11112;
@@ -72,15 +74,16 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-
+        // This is necessary for mockito to work
+        System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
         mMockitoHelper.setUp(getClass());
     }
 
     @Override
     protected void tearDown() throws Exception {
         mMockitoHelper.tearDown();
-        FsHelper.deleteContents(getContext().getFilesDir());
-        FsHelper.deleteContents(getContext().getCacheDir());
+        IoUtils.deleteContents(getContext().getFilesDir());
+        IoUtils.deleteContents(getContext().getCacheDir());
 
         super.tearDown();
     }
@@ -149,6 +152,363 @@
                 "/storage/AAAA-FFFF/Download/dir/bar.html"));
     }
 
+    public void testCheckDestinationFilePathRestrictions_noPermission() throws Exception {
+        // Downloading to our own private app directory should always be allowed, even for
+        // permission-less app
+        checkDestinationFilePathRestrictions_noPermission(
+                "/storage/emulated/0/Android/data/DownloadManagerHelpersTest/test",
+                /* isLegacyMode */ false);
+        checkDestinationFilePathRestrictions_noPermission(
+                "/storage/emulated/0/Android/data/DownloadManagerHelpersTest/test",
+                /* isLegacyMode */ true);
+        checkDestinationFilePathRestrictions_noPermission(
+                "/storage/emulated/0/Android/obb/DownloadManagerHelpersTest/test",
+                /* isLegacyMode */ false);
+        checkDestinationFilePathRestrictions_noPermission(
+                "/storage/emulated/0/Android/obb/DownloadManagerHelpersTest/test",
+                /* isLegacyMode */ true);
+        checkDestinationFilePathRestrictions_noPermission(
+                "/storage/emulated/0/Android/media/DownloadManagerHelpersTest/test",
+                /* isLegacyMode */ false);
+        checkDestinationFilePathRestrictions_noPermission(
+                "/storage/emulated/0/Android/media/DownloadManagerHelpersTest/test",
+                /* isLegacyMode */ true);
+
+        // All apps can write to Environment.STANDARD_DIRECTORIES
+        checkDestinationFilePathRestrictions_noPermission("/storage/emulated/0/Pictures/test",
+                /* isLegacyMode */ false);
+        checkDestinationFilePathRestrictions_noPermission("/storage/emulated/0/Download/test",
+                /* isLegacyMode */ false);
+        checkDestinationFilePathRestrictions_noPermission("/storage/emulated/0/Pictures/test",
+                /* isLegacyMode */ true);
+        checkDestinationFilePathRestrictions_noPermission("/storage/emulated/0/Download/test",
+                /* isLegacyMode */ true);
+
+        // Apps can never access other app's private directories (Android/data, Android/obb) paths
+        // (unless they are installers in which case they can access Android/obb paths)
+        try {
+            checkDestinationFilePathRestrictions_noPermission(
+                    "/storage/emulated/0/Android/data/foo/test", /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot access other app's private packages");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_noPermission(
+                    "/storage/emulated/0/Android/data/foo/test", /* isLegacyMode */ true);
+            fail("Expected SecurityException as caller cannot access other app's private packages"
+                    + " even in legacy mode");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_noPermission(
+                    "/storage/emulated/0/Android/obb/foo/test", /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot access other app's private packages");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_noPermission(
+                    "/storage/emulated/0/Android/obb/foo/test", /* isLegacyMode */ true);
+            fail("Expected SecurityException as caller cannot access other app's private packages"
+                    + " even in legacy mode");
+        } catch (SecurityException expected) {
+        }
+
+        // Non-legacy apps can never access Android/ or Android/media dirs for other packages.
+        try {
+            checkDestinationFilePathRestrictions_noPermission("/storage/emulated/0/Android/",
+                    /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot write to Android dir");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_noPermission(
+                    "/storage/emulated/0/Android/media/", /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot write to Android dir");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_noPermission(
+                    "/storage/emulated/0/Android/media/foo", /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot write to Android dir");
+        } catch (SecurityException expected) {
+        }
+
+        // Legacy apps require WRITE_EXTERNAL_STORAGE permission to access Android/ or Android/media
+        // dirs.
+        try {
+            checkDestinationFilePathRestrictions_noPermission("/storage/emulated/0/Android/",
+                    /* isLegacyMode */ true);
+            fail("Expected SecurityException as caller cannot write to Android/ as it does not"
+                    + " have WRITE_EXTERNAL_STORAGE permission");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_noPermission(
+                    "/storage/emulated/0/Android/media/", /* isLegacyMode */ true);
+            fail("Expected SecurityException as caller cannot write to Android/ as it does not"
+                    + " have WRITE_EXTERNAL_STORAGE permission");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_noPermission(
+                    "/storage/emulated/0/Android/media/foo", /* isLegacyMode */ true);
+            fail("Expected SecurityException as caller cannot write to Android/media as it does not"
+                    + " have WRITE_EXTERNAL_STORAGE permission");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void testCheckDestinationFilePathRestrictions_installer() throws Exception {
+        // Downloading to other obb dirs should be allowed as installer
+        checkDestinationFilePathRestrictions_installer("/storage/emulated/0/Android/obb/foo/test",
+                /* isLegacyMode */ false);
+        checkDestinationFilePathRestrictions_installer("/storage/emulated/0/Android/obb/foo/test",
+                /* isLegacyMode */ true);
+
+        // Installer apps can not access other app's Android/data private dirs
+        try {
+            checkDestinationFilePathRestrictions_installer(
+                    "/storage/emulated/0/Android/data/foo/test", /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot access other app's private packages");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_installer(
+                    "/storage/emulated/0/Android/data/foo/test", /* isLegacyMode */ true);
+            fail("Expected SecurityException as caller cannot access other app's private packages"
+                    + " even in legacy mode");
+        } catch (SecurityException expected) {
+        }
+
+        // Non-legacy apps can never access Android/ or Android/media dirs for other packages.
+        try {
+            checkDestinationFilePathRestrictions_installer("/storage/emulated/0/Android/",
+                    /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot write to Android dir");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_installer("/storage/emulated/0/Android/media/",
+                    /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot write to Android dir");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_installer("/storage/emulated/0/Android/media/foo",
+                    /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot write to Android dir");
+        } catch (SecurityException expected) {
+        }
+
+        // Legacy apps require WRITE_EXTERNAL_STORAGE permission to access Android/ or Android/media
+        // dirs.
+        try {
+            checkDestinationFilePathRestrictions_installer("/storage/emulated/0/Android/",
+                    /* isLegacyMode */ true);
+            fail("Expected SecurityException as caller cannot write to Android/ as it does not"
+                    + " have WRITE_EXTERNAL_STORAGE permission");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_installer("/storage/emulated/0/Android/media/",
+                    /* isLegacyMode */ true);
+            fail("Expected SecurityException as caller cannot write to Android/ as it does not"
+                    + " have WRITE_EXTERNAL_STORAGE permission");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_installer("/storage/emulated/0/Android/media/foo",
+                    /* isLegacyMode */ true);
+            fail("Expected SecurityException as caller cannot write to Android/media as it does not"
+                    + " have WRITE_EXTERNAL_STORAGE permission");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void testCheckDestinationFilePathRestrictions_WES() throws Exception {
+        // Apps with WRITE_EXTERNAL_STORAGE can not access other app's private dirs
+        // (Android/data and Android/obb paths)
+        try {
+            checkDestinationFilePathRestrictions_WES("/storage/emulated/0/Android/data/foo/test",
+                    /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot access other app's private packages");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_WES("/storage/emulated/0/Android/data/foo/test",
+                    /* isLegacyMode */ true);
+            fail("Expected SecurityException as caller cannot access other app's private packages"
+                    + " even in legacy mode");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_WES("/storage/emulated/0/Android/obb/foo/test",
+                    /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot access other app's private packages");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_WES("/storage/emulated/0/Android/obb/foo/test",
+                    /* isLegacyMode */ true);
+            fail("Expected SecurityException as caller cannot access other app's private packages"
+                    + " even in legacy mode");
+        } catch (SecurityException expected) {
+        }
+
+        // Non-legacy apps can never access Android/ or Android/media dirs for other packages.
+        try {
+            checkDestinationFilePathRestrictions_WES("/storage/emulated/0/Android/",
+                    /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot write to Android dir");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_WES("/storage/emulated/0/Android/media/",
+                    /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot write to Android dir");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            checkDestinationFilePathRestrictions_WES("/storage/emulated/0/Android/media/foo",
+                    /* isLegacyMode */ false);
+            fail("Expected SecurityException as caller cannot write to Android dir");
+        } catch (SecurityException expected) {
+        }
+
+        // Legacy apps with WRITE_EXTERNAL_STORAGE can access shared storage file path including
+        // Android/ and Android/media dirs
+        checkDestinationFilePathRestrictions_WES("/storage/emulated/0/Pictures/test",
+                /* isLegacyMode */ true);
+        checkDestinationFilePathRestrictions_WES("/storage/emulated/0/Download/test",
+                /* isLegacyMode */ true);
+        checkDestinationFilePathRestrictions_WES("/storage/emulated/0/Android/",
+                /* isLegacyMode */ true);
+        checkDestinationFilePathRestrictions_WES("/storage/emulated/0/Android/media/",
+                /* isLegacyMode */ true);
+        checkDestinationFilePathRestrictions_WES("/storage/emulated/0/Android/media/foo",
+                /* isLegacyMode */ true);
+    }
+
+    private void checkDestinationFilePathRestrictions_noPermission(String filePath,
+            boolean isLegacyMode) {
+        final Context mockContext = mock(Context.class);
+        when(mockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.REQUEST_INSTALL_PACKAGES))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_EXTERNAL_STORAGE))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        final String callingAttributionTag = "test";
+        final AppOpsManager mockAppOpsManager = mock(AppOpsManager.class);
+        final String callingPackage = TAG;
+        when(mockAppOpsManager.noteOp(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
+                Binder.getCallingUid(), callingPackage, null, "obb_download"))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
+        when(mockAppOpsManager.noteProxyOp(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE,
+                callingPackage, Binder.getCallingUid(), callingAttributionTag, null))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
+        File file = new File(filePath);
+
+        Helpers.checkDestinationFilePathRestrictions(file, callingPackage, mockContext,
+                mockAppOpsManager, callingAttributionTag, isLegacyMode,
+                /* allowDownloadsDirOnly */ false);
+    }
+
+    private void checkDestinationFilePathRestrictions_installer(String filePath,
+            boolean isLegacyMode) throws Exception {
+        final Context mockContext = mock(Context.class);
+        when(mockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.REQUEST_INSTALL_PACKAGES))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_EXTERNAL_STORAGE))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        final String callingAttributionTag = "test";
+        final AppOpsManager mockAppOpsManager = mock(AppOpsManager.class);
+        final String callingPackage = TAG;
+        when(mockAppOpsManager.noteOp(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
+                Binder.getCallingUid(), callingPackage, null, "obb_download"))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        when(mockAppOpsManager.noteProxyOp(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE,
+                callingPackage, Binder.getCallingUid(), callingAttributionTag, null))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
+        File file = new File(filePath);
+
+        Helpers.checkDestinationFilePathRestrictions(file, callingPackage, mockContext,
+                mockAppOpsManager, callingAttributionTag, isLegacyMode,
+                /* allowDownloadsDirOnly */ false);
+    }
+
+    private void checkDestinationFilePathRestrictions_WES(String filePath, boolean isLegacyMode)
+            throws Exception {
+        final Context mockContext = mock(Context.class);
+        when(mockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_EXTERNAL_STORAGE))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.REQUEST_INSTALL_PACKAGES))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        final AppOpsManager mockAppOpsManager = mock(AppOpsManager.class);
+        final String callingAttributionTag = "test";
+        final String callingPackage = TAG;
+        when(mockAppOpsManager.noteProxyOp(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE,
+                callingPackage, Binder.getCallingUid(), callingAttributionTag, null))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        when(mockAppOpsManager.noteOp(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
+                Binder.getCallingUid(), callingPackage, null, "obb_download"))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
+        File file = new File(filePath);
+
+        Helpers.checkDestinationFilePathRestrictions(file, callingPackage, mockContext,
+                mockAppOpsManager, callingAttributionTag, isLegacyMode,
+                /* allowDownloadsDirOnly */ false);
+    }
+
+    public void testIsFileInPrivateExternalAndroidDirs() throws Exception {
+        assertTrue(isFileInPrivateExternalAndroidDirs(
+                "/storage/emulated/0/Android/data/com.example"));
+        assertTrue(isFileInPrivateExternalAndroidDirs(
+                "/storage/emulated/0/Android/data/com.example/colors.txt"));
+        assertTrue(isFileInPrivateExternalAndroidDirs(
+                "/storage/emulated/0/Android/obb/com.example/file.mp4"));
+        assertTrue(isFileInPrivateExternalAndroidDirs(
+                "/storage/AAAA-FFFF/Android/obb/com.example/file.mp4"));
+
+        assertFalse(isFileInPrivateExternalAndroidDirs("/storage/emulated/0/Android/"));
+        assertFalse(isFileInPrivateExternalAndroidDirs("/storage/AAAA-FFFF/Android/"));
+        assertFalse(isFileInPrivateExternalAndroidDirs(
+                "/storage/emulated/0/Android/media/com.example/file.mp4"));
+        assertFalse(isFileInPrivateExternalAndroidDirs(
+                "/storage/AAAA-FFFF/Android/media/com.example/file.mp4"));
+        assertFalse(isFileInPrivateExternalAndroidDirs("/storage/emulated/0/Download/foo.pdf"));
+        assertFalse(isFileInPrivateExternalAndroidDirs(
+                "/storage/emulated/0/Download/dir/bar.html"));
+        assertFalse(isFileInPrivateExternalAndroidDirs("/storage/AAAA-FFFF/Download/dir/bar.html"));
+    }
+
+    private static boolean isFileInPrivateExternalAndroidDirs(String filePath) {
+        return Helpers.isFileInPrivateExternalAndroidDirs(new File(filePath));
+    }
+
     public void testIsFilenameValidinKnownPublicDir() throws Exception {
         assertTrue(Helpers.isFilenameValidInKnownPublicDir(
                 "/storage/emulated/0/Download/dir/file.txt"));
diff --git a/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java b/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java
index f45e102..0652f24 100644
--- a/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java
+++ b/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java
@@ -52,7 +52,7 @@
 import android.test.suitebuilder.annotation.Suppress;
 import android.text.format.DateUtils;
 
-import androidx.test.filters.FlakyTest;
+import libcore.io.IoUtils;
 
 import com.google.mockwebserver.MockResponse;
 import com.google.mockwebserver.RecordedRequest;
@@ -91,7 +91,7 @@
                 Environment.DIRECTORY_DOWNLOADS)
                 + File.separator + "download_manager_functional_test");
         if (mTestDirectory.exists()) {
-            FsHelper.deleteContents(mTestDirectory);
+            IoUtils.deleteContents(mTestDirectory);
         } else {
             mTestDirectory.mkdir();
         }
@@ -100,7 +100,7 @@
     @Override
     protected void tearDown() throws Exception {
         if (mTestDirectory != null && mTestDirectory.exists()) {
-            FsHelper.deleteContents(mTestDirectory);
+            IoUtils.deleteContents(mTestDirectory);
             mTestDirectory.delete();
         }
         super.tearDown();
@@ -691,7 +691,6 @@
         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
     }
 
-    @FlakyTest(bugId = 177499952)
     public void testManyInterruptions() throws Exception {
         final int length = FILE_CONTENT.length();
         for (int i = 0; i < length; i++) {
diff --git a/ui/Android.bp b/ui/Android.bp
index 04606c2..5192ead 100644
--- a/ui/Android.bp
+++ b/ui/Android.bp
@@ -13,17 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "packages_providers_DownloadProvider_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: [
-        "packages_providers_DownloadProvider_license",
-    ],
-}
-
 android_app {
     name: "DownloadProviderUi",
 
diff --git a/ui/AndroidManifest.xml b/ui/AndroidManifest.xml
index 8eae933..d08ec19 100644
--- a/ui/AndroidManifest.xml
+++ b/ui/AndroidManifest.xml
@@ -1,30 +1,30 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="com.android.providers.downloads.ui"
-     android:sharedUserId="android.media">
+        package="com.android.providers.downloads.ui"
+        android:sharedUserId="android.media">
 
-    <uses-permission android:name="android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS"/>
-    <uses-permission android:name="android.permission.ACCESS_ALL_DOWNLOADS"/>
+    <uses-permission android:name="android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS" />
+    <uses-permission android:name="android.permission.ACCESS_ALL_DOWNLOADS" />
 
     <application android:process="android.process.media"
-         android:label="@string/app_label"
-         android:icon="@mipmap/ic_launcher_download"
-         android:hardwareAccelerated="true"
-         android:supportsRtl="true"
-         android:requiredForAllUsers="true"
-         android:usesCleartextTraffic="true">
+                 android:label="@string/app_label"
+                 android:icon="@mipmap/ic_launcher_download"
+                 android:hardwareAccelerated="true"
+                 android:supportsRtl="true"
+                 android:requiredForAllUsers="true"
+                 android:usesCleartextTraffic="true">
 
-        <activity android:name=".TrampolineActivity"
-             android:theme="@android:style/Theme.Translucent.NoTitleBar"
-             android:permission="android.permission.MANAGE_DOCUMENTS"
-             android:exported="true">
+        <activity
+            android:name=".TrampolineActivity"
+            android:theme="@android:style/Theme.Translucent.NoTitleBar"
+            android:permission="android.permission.MANAGE_DOCUMENTS">
             <intent-filter>
-                <action android:name="android.provider.action.MANAGE_DOCUMENT"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <data android:scheme="content"
-                     android:host="com.android.providers.downloads.documents"
-                     android:mimeType="*/*"/>
+                <action android:name="android.provider.action.MANAGE_DOCUMENT" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data
+                    android:scheme="content"
+                    android:host="com.android.providers.downloads.documents"
+                    android:mimeType="*/*" />
             </intent-filter>
         </activity>
     </application>