summaryrefslogtreecommitdiffstats
path: root/src/com/android/providers/downloads/Helpers.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/providers/downloads/Helpers.java')
-rw-r--r--src/com/android/providers/downloads/Helpers.java114
1 files changed, 107 insertions, 7 deletions
diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java
index 87cf0467..226fb481 100644
--- a/src/com/android/providers/downloads/Helpers.java
+++ b/src/com/android/providers/downloads/Helpers.java
@@ -16,15 +16,20 @@
package com.android.providers.downloads;
-import static android.os.Environment.buildExternalStorageAppCacheDirs;
-import static android.os.Environment.buildExternalStorageAppFilesDirs;
+import static android.os.Environment.buildExternalStorageAppDataDirs;
import static android.os.Environment.buildExternalStorageAppMediaDirs;
import static android.os.Environment.buildExternalStorageAppObbDirs;
+import static android.os.Environment.buildExternalStoragePublicDirs;
+import static android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
+import static android.provider.Downloads.Impl.DESTINATION_FILE_URI;
+import static android.provider.Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD;
import static android.provider.Downloads.Impl.FLAG_REQUIRES_CHARGING;
import static android.provider.Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE;
import static com.android.providers.downloads.Constants.TAG;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
@@ -43,13 +48,20 @@ import android.os.storage.StorageVolume;
import android.provider.Downloads;
import android.text.TextUtils;
import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.webkit.MimeTypeMap;
+import com.android.internal.util.ArrayUtils;
+
import com.google.common.annotations.VisibleForTesting;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Random;
+import java.util.Set;
import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -64,6 +76,12 @@ public class Helpers {
private static final Pattern CONTENT_DISPOSITION_PATTERN =
Pattern.compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
+ private static final Pattern PATTERN_ANDROID_DIRS =
+ Pattern.compile("(?i)^/storage/[^/]+(?:/[0-9]+)?/Android/(?:data|obb|media)/.+");
+
+ private static final Pattern PATTERN_PUBLIC_DIRS =
+ Pattern.compile("(?i)^/storage/[^/]+(?:/[0-9]+)?/([^/]+)/.+");
+
private static final Object sUniqueLock = new Object();
private static HandlerThread sAsyncHandlerThread;
@@ -144,7 +162,7 @@ public class Helpers {
// When this download will show a notification, run with a higher
// priority, since it's effectively a foreground service
if (info.isVisible()) {
- builder.setPriority(JobInfo.PRIORITY_FOREGROUND_APP);
+ builder.setPriority(JobInfo.PRIORITY_FOREGROUND_SERVICE);
builder.setFlags(JobInfo.FLAG_WILL_BE_FOREGROUND);
}
@@ -472,6 +490,10 @@ public class Helpers {
throw new IOException("Failed to generate an available filename");
}
+ public static boolean isFileInExternalAndroidDirs(String filePath) {
+ return PATTERN_ANDROID_DIRS.matcher(filePath).matches();
+ }
+
static boolean isFilenameValid(Context context, File file) {
return isFilenameValid(context, file, true);
}
@@ -488,9 +510,8 @@ public class Helpers {
static boolean isFilenameValidInExternalPackage(Context context, File file,
String packageName) {
try {
- if (containsCanonical(buildExternalStorageAppFilesDirs(packageName), file) ||
+ if (containsCanonical(buildExternalStorageAppDataDirs(packageName), file) ||
containsCanonical(buildExternalStorageAppObbDirs(packageName), file) ||
- containsCanonical(buildExternalStorageAppCacheDirs(packageName), file) ||
containsCanonical(buildExternalStorageAppMediaDirs(packageName), file)) {
return true;
}
@@ -499,7 +520,33 @@ public class Helpers {
return false;
}
- Log.w(TAG, "Path appears to be invalid: " + file);
+ return false;
+ }
+
+ static boolean isFilenameValidInPublicDownloadsDir(File file) {
+ try {
+ if (containsCanonical(buildExternalStoragePublicDirs(
+ Environment.DIRECTORY_DOWNLOADS), file)) {
+ return true;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to resolve canonical path: " + e);
+ return false;
+ }
+
+ return false;
+ }
+
+ @com.android.internal.annotations.VisibleForTesting
+ public static boolean isFilenameValidInKnownPublicDir(@Nullable String filePath) {
+ if (filePath == null) {
+ return false;
+ }
+ final Matcher matcher = PATTERN_PUBLIC_DIRS.matcher(filePath);
+ if (matcher.matches()) {
+ final String publicDir = matcher.group(1);
+ return ArrayUtils.contains(Environment.STANDARD_DIRECTORIES, publicDir);
+ }
return false;
}
@@ -529,7 +576,6 @@ public class Helpers {
return false;
}
- Log.w(TAG, "Path appears to be invalid: " + file);
return false;
}
@@ -580,4 +626,58 @@ public class Helpers {
throw new IllegalStateException("unexpected destination: " + destination);
}
}
+
+ public static void handleRemovedUidEntries(@NonNull Context context, @NonNull Cursor cursor,
+ @NonNull ArrayList<Long> idsToDelete, @NonNull ArrayList<Long> idsToOrphan,
+ @Nullable LongSparseArray<String> idsToGrantPermission) {
+ final SparseArray<String> knownUids = new SparseArray<>();
+ while (cursor.moveToNext()) {
+ final long downloadId = cursor.getLong(0);
+ final int uid = cursor.getInt(1);
+
+ final String ownerPackageName;
+ final int index = knownUids.indexOfKey(uid);
+ if (index >= 0) {
+ ownerPackageName = knownUids.valueAt(index);
+ } else {
+ ownerPackageName = getPackageForUid(context, uid);
+ knownUids.put(uid, ownerPackageName);
+ }
+
+ if (ownerPackageName == null) {
+ final int destination = cursor.getInt(2);
+ final String filePath = cursor.getString(3);
+
+ if ((destination == DESTINATION_EXTERNAL
+ || destination == DESTINATION_FILE_URI
+ || destination == DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD)
+ && isFilenameValidInKnownPublicDir(filePath)) {
+ idsToOrphan.add(downloadId);
+ } else {
+ idsToDelete.add(downloadId);
+ }
+ } else if (idsToGrantPermission != null) {
+ idsToGrantPermission.put(downloadId, ownerPackageName);
+ }
+ }
+ }
+
+ public static String buildQueryWithIds(ArrayList<Long> downloadIds) {
+ final StringBuilder queryBuilder = new StringBuilder(Downloads.Impl._ID + " in (");
+ final int size = downloadIds.size();
+ for (int i = 0; i < size; i++) {
+ queryBuilder.append(downloadIds.get(i));
+ queryBuilder.append((i == size - 1) ? ")" : ",");
+ }
+ return queryBuilder.toString();
+ }
+
+ public static String getPackageForUid(Context context, int uid) {
+ String[] packages = context.getPackageManager().getPackagesForUid(uid);
+ if (packages == null || packages.length == 0) {
+ return null;
+ }
+ // For permission related purposes, any package belonging to the given uid should work.
+ return packages[0];
+ }
}