diff options
author | 2018-01-03 11:16:09 -0800 | |
---|---|---|
committer | 2018-01-03 11:17:07 -0800 | |
commit | 420534939aeb3b3d155388cf87b71f5432485ed9 (patch) | |
tree | c43c7d24f1b0209930aed04618f7be3dce86d80d | |
parent | 67aa5151d2d4f6bc09f945d1eaf98c7d06fc76b5 (diff) |
Introduce seperate build target for Android TV.
Bug: 68140422
Test: Manually tested that both targets work properly.
Change-Id: Icb1ce0eb91c46d0533e89dce0a92cde3b362e6d7
21 files changed, 592 insertions, 209 deletions
diff --git a/Android.mk b/Android.mk index 3044ea53f..8e6b70987 100644 --- a/Android.mk +++ b/Android.mk @@ -1,37 +1,40 @@ LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE_TAGS := optional -LOCAL_PRIVILEGED_MODULE := true +######################## +# Complete DocumentsUI app: +include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_STATIC_JAVA_LIBRARIES += guava - +LOCAL_PACKAGE_NAME := DocumentsUI LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res +LOCAL_FULL_MANIFEST_FILE := $(LOCAL_PATH)/AndroidManifest.xml -LOCAL_STATIC_ANDROID_LIBRARIES := \ - android-support-core-ui \ - android-support-v4 \ - android-support-v7-appcompat \ - android-support-v13 \ - android-support-design \ - android-support-transition \ - android-support-v7-recyclerview - -LOCAL_USE_AAPT2 := true +include $(LOCAL_PATH)/build_apk.mk -LOCAL_JACK_FLAGS := \ - -D jack.optimization.inner-class.accessors=true - -# Only enable asserts on userdebug/eng builds -ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT))) -LOCAL_JACK_FLAGS += -D jack.assert.policy=always -endif - -LOCAL_PACKAGE_NAME := DocumentsUI -LOCAL_CERTIFICATE := platform -LOCAL_PROGUARD_FLAG_FILES := proguard.flags +######################## +# Minimal DocumentsUI app (supports Scoped Directory Access only): +include $(CLEAR_VARS) -include $(BUILD_PACKAGE) +LOCAL_SRC_FILES := \ + src/com/android/documentsui/OpenExternalDirectoryActivity.java \ + src/com/android/documentsui/ScopedAccessPackageReceiver.java \ + src/com/android/documentsui/ScopedAccessMetrics.java \ + src/com/android/documentsui/archives/Archive.java \ + src/com/android/documentsui/archives/ArchiveId.java \ + src/com/android/documentsui/archives/ArchivesProvider.java \ + src/com/android/documentsui/archives/Loader.java \ + src/com/android/documentsui/archives/Proxy.java \ + src/com/android/documentsui/archives/ReadableArchive.java \ + src/com/android/documentsui/archives/WriteableArchive.java \ + src/com/android/documentsui/base/Providers.java \ + src/com/android/documentsui/prefs/ScopedAccessLocalPreferences.java + +LOCAL_PACKAGE_NAME := DocumentsUIMinimal +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/minimal/res +LOCAL_FULL_MANIFEST_FILE := $(LOCAL_PATH)/minimal/AndroidManifest.xml + +include $(LOCAL_PATH)/build_apk.mk + +# Include makefiles for tests and libraries under the current path include $(call all-makefiles-under, $(LOCAL_PATH)) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 68a3df8ff..9abc7efca 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,3 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2007-2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.documentsui"> diff --git a/build_apk.mk b/build_apk.mk new file mode 100644 index 000000000..c09517fe9 --- /dev/null +++ b/build_apk.mk @@ -0,0 +1,28 @@ +LOCAL_MODULE_TAGS := optional +LOCAL_PRIVILEGED_MODULE := true + +LOCAL_STATIC_JAVA_LIBRARIES += guava + +LOCAL_STATIC_ANDROID_LIBRARIES := \ + android-support-core-ui \ + android-support-v4 \ + android-support-v7-appcompat \ + android-support-v13 \ + android-support-design \ + android-support-transition \ + android-support-v7-recyclerview + +LOCAL_USE_AAPT2 := true + +LOCAL_JACK_FLAGS := \ + -D jack.optimization.inner-class.accessors=true + +# Only enable asserts on userdebug/eng builds +ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT))) +LOCAL_JACK_FLAGS += -D jack.assert.policy=always +endif + +LOCAL_CERTIFICATE := platform +LOCAL_PROGUARD_FLAG_FILES := proguard.flags + +include $(BUILD_PACKAGE) diff --git a/minimal/AndroidManifest.xml b/minimal/AndroidManifest.xml new file mode 100644 index 000000000..b985c98c8 --- /dev/null +++ b/minimal/AndroidManifest.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2007-2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.documentsui"> + + <uses-permission android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" /> + <uses-permission android:name="android.permission.MANAGE_DOCUMENTS" /> + <uses-permission android:name="android.permission.CACHE_CONTENT" /> + + <application + android:label="@string/app_label" + android:icon="@drawable/app_icon" + android:supportsRtl="true" + android:allowBackup="false" + android:fullBackupOnly="false"> + + <activity + android:name=".OpenExternalDirectoryActivity" + android:theme="@android:style/Theme.Translucent.NoTitleBar"> + <intent-filter> + <action android:name="android.os.storage.action.OPEN_EXTERNAL_DIRECTORY" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + + <receiver android:name=".ScopedAccessPackageReceiver"> + <intent-filter> + <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" /> + <action android:name="android.intent.action.PACKAGE_DATA_CLEARED" /> + <data android:scheme="package" /> + </intent-filter> + </receiver> + + </application> +</manifest> diff --git a/minimal/res/layout/dialog_open_scoped_directory.xml b/minimal/res/layout/dialog_open_scoped_directory.xml new file mode 100644 index 000000000..cb3920604 --- /dev/null +++ b/minimal/res/layout/dialog_open_scoped_directory.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:theme="@style/Theme.AppCompat.Light.Dialog.Alert" + android:orientation="vertical" + android:paddingEnd="24dp" + android:paddingStart="24dp" > + + <TextView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/message" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingEnd="24dp" + android:paddingStart="32dp" + android:paddingTop="24dp"> + </TextView> + + <CheckBox + android:id="@+id/do_not_ask_checkbox" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dip" + android:text="@string/never_ask_again" + android:textColor="?android:attr/textColorSecondary" + android:visibility="gone" /> +</LinearLayout> diff --git a/minimal/res/mipmap-anydpi/ic_app_icon.xml b/minimal/res/mipmap-anydpi/ic_app_icon.xml new file mode 100644 index 000000000..cd4fa58a8 --- /dev/null +++ b/minimal/res/mipmap-anydpi/ic_app_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/app_icon_background"/> + <foreground android:drawable="@mipmap/ic_launcher_icon_foreground"/> +</adaptive-icon> diff --git a/minimal/res/mipmap-hdpi/ic_launcher_icon_foreground.png b/minimal/res/mipmap-hdpi/ic_launcher_icon_foreground.png Binary files differnew file mode 100644 index 000000000..992c44e2d --- /dev/null +++ b/minimal/res/mipmap-hdpi/ic_launcher_icon_foreground.png diff --git a/minimal/res/mipmap-mdpi/ic_launcher_icon_foreground.png b/minimal/res/mipmap-mdpi/ic_launcher_icon_foreground.png Binary files differnew file mode 100644 index 000000000..4639ff014 --- /dev/null +++ b/minimal/res/mipmap-mdpi/ic_launcher_icon_foreground.png diff --git a/minimal/res/mipmap-xhdpi/ic_launcher_icon_foreground.png b/minimal/res/mipmap-xhdpi/ic_launcher_icon_foreground.png Binary files differnew file mode 100644 index 000000000..b99294403 --- /dev/null +++ b/minimal/res/mipmap-xhdpi/ic_launcher_icon_foreground.png diff --git a/minimal/res/mipmap-xxhdpi/ic_launcher_icon_foreground.png b/minimal/res/mipmap-xxhdpi/ic_launcher_icon_foreground.png Binary files differnew file mode 100644 index 000000000..ae44b2f68 --- /dev/null +++ b/minimal/res/mipmap-xxhdpi/ic_launcher_icon_foreground.png diff --git a/minimal/res/mipmap-xxxhdpi/ic_launcher_icon_foreground.png b/minimal/res/mipmap-xxxhdpi/ic_launcher_icon_foreground.png Binary files differnew file mode 100644 index 000000000..85150eb3b --- /dev/null +++ b/minimal/res/mipmap-xxxhdpi/ic_launcher_icon_foreground.png diff --git a/minimal/res/values/colors.xml b/minimal/res/values/colors.xml new file mode 100644 index 000000000..61a8150e6 --- /dev/null +++ b/minimal/res/values/colors.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <color name="app_icon_background">#ff4688f2</color> +</resources> diff --git a/minimal/res/values/drawables.xml b/minimal/res/values/drawables.xml new file mode 100644 index 000000000..2e5e77d7a --- /dev/null +++ b/minimal/res/values/drawables.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <item name="app_icon" type="drawable">@mipmap/ic_app_icon</item> + <item name="launcher_icon" type="drawable">@mipmap/ic_app_icon</item> +</resources> diff --git a/minimal/res/values/strings.xml b/minimal/res/values/strings.xml new file mode 100644 index 000000000..bcffd6b69 --- /dev/null +++ b/minimal/res/values/strings.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Title of the Files application [CHAR LIMIT=32] --> + <string name="files_label">Files</string> + + <!-- Title of the documents application [CHAR LIMIT=32] --> + <string name="app_label">@string/files_label</string> + + <!-- Title of the documents application [CHAR LIMIT=32] --> + <string name="launcher_label">@string/files_label</string> + + <!-- Text in an alert dialog asking user to grant app access to a given directory in an external storage volume --> + <string name="open_external_dialog_request">Grant <xliff:g id="appName" example="System Settings"><b>^1</b></xliff:g> + access to <xliff:g id="directory" example="Pictures"><i>^2</i></xliff:g> directory on + <xliff:g id="storage" example="SD Card"><i>^3</i></xliff:g>?</string> + <!-- Text in an alert dialog asking user to grant app access to a given directory in the internal storage --> + <string name="open_external_dialog_request_primary_volume">Grant <xliff:g id="appName" example="System Settings"><b>^1</b></xliff:g> + access to <xliff:g id="directory" example="Pictures"><i>^2</i></xliff:g> directory?</string> + <!-- Text in an alert dialog asking user to grant app access to all data in an external storage volume --> + <string name="open_external_dialog_root_request">Grant <xliff:g id="appName" example="System Settings"><b>^1</b></xliff:g> + access to your data, including photos and videos, on <xliff:g id="storage" example="SD Card"><i>^2</i></xliff:g>?</string> + <!-- Checkbox that allows user to not be questioned about the directory access request again --> + <string name="never_ask_again">Don\'t ask again</string> + <!-- Text in the button asking user to allow access to a given directory. --> + <string name="allow">Allow</string> + <!-- Text in the button asking user to deny access to a given directory. --> + <string name="deny">Deny</string> + + <!-- Error message shown when an archive fails to load --> + <string name="archive_loading_failed">Unable to open archive for browsing. File is either corrupt, or an unsupported format.</string> + +</resources> diff --git a/src/com/android/documentsui/Metrics.java b/src/com/android/documentsui/Metrics.java index f421906be..b7f756f5e 100644 --- a/src/com/android/documentsui/Metrics.java +++ b/src/com/android/documentsui/Metrics.java @@ -16,14 +16,11 @@ package com.android.documentsui; -import static android.os.Environment.STANDARD_DIRECTORIES; import static com.android.documentsui.DocumentsApplication.acquireUnstableProviderOrThrow; import static com.android.documentsui.base.Shared.DEBUG; import android.annotation.IntDef; import android.annotation.Nullable; -import android.annotation.StringDef; -import android.app.Activity; import android.content.ContentProviderClient; import android.content.Context; import android.content.Intent; @@ -45,13 +42,14 @@ import com.android.documentsui.roots.ProvidersAccess; import com.android.documentsui.services.FileOperationService; import com.android.documentsui.services.FileOperationService.OpType; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; -/** @hide */ +/** + * Methods for logging metrics. + */ public final class Metrics { private static final String TAG = "Metrics"; @@ -689,102 +687,6 @@ public final class Metrics { logHistogram(context, histogram, getOpCode(operationType, PROVIDER_INTRA)); } - // Types for logInvalidScopedAccessRequest - public static final String SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS = - "docsui_scoped_directory_access_invalid_args"; - public static final String SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY = - "docsui_scoped_directory_access_invalid_dir"; - public static final String SCOPED_DIRECTORY_ACCESS_ERROR = - "docsui_scoped_directory_access_error"; - - @StringDef(value = { - SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS, - SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY, - SCOPED_DIRECTORY_ACCESS_ERROR - }) - @Retention(RetentionPolicy.SOURCE) - public @interface InvalidScopedAccess{} - - public static void logInvalidScopedAccessRequest(Context context, - @InvalidScopedAccess String type) { - switch (type) { - case SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS: - case SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY: - case SCOPED_DIRECTORY_ACCESS_ERROR: - logCount(context, type); - break; - default: - Log.wtf(TAG, "invalid InvalidScopedAccess: " + type); - } - } - - // Types for logValidScopedAccessRequest - public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED = 0; - public static final int SCOPED_DIRECTORY_ACCESS_GRANTED = 1; - public static final int SCOPED_DIRECTORY_ACCESS_DENIED = 2; - public static final int SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST = 3; - public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED = 4; - - @IntDef(flag = true, value = { - SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED, - SCOPED_DIRECTORY_ACCESS_GRANTED, - SCOPED_DIRECTORY_ACCESS_DENIED, - SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST, - SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ScopedAccessGrant {} - - public static void logValidScopedAccessRequest(Activity activity, String directory, - @ScopedAccessGrant int type) { - int index = -1; - if (OpenExternalDirectoryActivity.DIRECTORY_ROOT.equals(directory)) { - index = -2; - } else { - for (int i = 0; i < STANDARD_DIRECTORIES.length; i++) { - if (STANDARD_DIRECTORIES[i].equals(directory)) { - index = i; - break; - } - } - } - final String packageName = activity.getCallingPackage(); - switch (type) { - case SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED: - MetricsLogger.action(activity, MetricsEvent - .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_PACKAGE, packageName); - MetricsLogger.action(activity, MetricsEvent - .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_FOLDER, index); - break; - case SCOPED_DIRECTORY_ACCESS_GRANTED: - MetricsLogger.action(activity, MetricsEvent - .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_PACKAGE, packageName); - MetricsLogger.action(activity, MetricsEvent - .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_FOLDER, index); - break; - case SCOPED_DIRECTORY_ACCESS_DENIED: - MetricsLogger.action(activity, MetricsEvent - .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_PACKAGE, packageName); - MetricsLogger.action(activity, MetricsEvent - .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_FOLDER, index); - break; - case SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST: - MetricsLogger.action(activity, MetricsEvent - .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_PACKAGE, packageName); - MetricsLogger.action(activity, MetricsEvent - .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_FOLDER, index); - break; - case SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED: - MetricsLogger.action(activity, MetricsEvent - .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_PACKAGE, packageName); - MetricsLogger.action(activity, MetricsEvent - .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_FOLDER, index); - break; - default: - Log.wtf(TAG, "invalid ScopedAccessGrant: " + type); - } - } - /** * Logs the action that was started by user. * @param context diff --git a/src/com/android/documentsui/OpenExternalDirectoryActivity.java b/src/com/android/documentsui/OpenExternalDirectoryActivity.java index 74af224b5..04ac42cec 100644 --- a/src/com/android/documentsui/OpenExternalDirectoryActivity.java +++ b/src/com/android/documentsui/OpenExternalDirectoryActivity.java @@ -19,21 +19,21 @@ package com.android.documentsui; import static android.os.Environment.isStandardDirectory; import static android.os.storage.StorageVolume.EXTRA_DIRECTORY_NAME; import static android.os.storage.StorageVolume.EXTRA_STORAGE_VOLUME; -import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED; -import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED; -import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_DENIED; -import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST; -import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_ERROR; -import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_GRANTED; -import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS; -import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY; -import static com.android.documentsui.Metrics.logInvalidScopedAccessRequest; -import static com.android.documentsui.Metrics.logValidScopedAccessRequest; -import static com.android.documentsui.base.Shared.DEBUG; -import static com.android.documentsui.prefs.LocalPreferences.PERMISSION_ASK_AGAIN; -import static com.android.documentsui.prefs.LocalPreferences.PERMISSION_NEVER_ASK; -import static com.android.documentsui.prefs.LocalPreferences.getScopedAccessPermissionStatus; -import static com.android.documentsui.prefs.LocalPreferences.setScopedAccessPermissionStatus; +import static com.android.documentsui.ScopedAccessMetrics.DEBUG; +import static com.android.documentsui.ScopedAccessMetrics.SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED; +import static com.android.documentsui.ScopedAccessMetrics.SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED; +import static com.android.documentsui.ScopedAccessMetrics.SCOPED_DIRECTORY_ACCESS_DENIED; +import static com.android.documentsui.ScopedAccessMetrics.SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST; +import static com.android.documentsui.ScopedAccessMetrics.SCOPED_DIRECTORY_ACCESS_ERROR; +import static com.android.documentsui.ScopedAccessMetrics.SCOPED_DIRECTORY_ACCESS_GRANTED; +import static com.android.documentsui.ScopedAccessMetrics.SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS; +import static com.android.documentsui.ScopedAccessMetrics.SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY; +import static com.android.documentsui.ScopedAccessMetrics.logInvalidScopedAccessRequest; +import static com.android.documentsui.ScopedAccessMetrics.logValidScopedAccessRequest; +import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_ASK_AGAIN; +import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_NEVER_ASK; +import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.getScopedAccessPermissionStatus; +import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.setScopedAccessPermissionStatus; import android.annotation.SuppressLint; import android.app.Activity; diff --git a/src/com/android/documentsui/PackageReceiver.java b/src/com/android/documentsui/PackageReceiver.java index 5cb28272b..e917369d9 100644 --- a/src/com/android/documentsui/PackageReceiver.java +++ b/src/com/android/documentsui/PackageReceiver.java @@ -23,10 +23,11 @@ import android.content.Intent; import android.net.Uri; import com.android.documentsui.picker.LastAccessedProvider; -import com.android.documentsui.prefs.LocalPreferences; +import com.android.documentsui.prefs.ScopedAccessLocalPreferences; /** - * Clean up {@link LastAccessedProvider} and {@link LocalPreferences} when packages are removed. + * Clean up {@link LastAccessedProvider} and {@link ScopedAccessLocalPreferences} when packages + * are removed. */ public class PackageReceiver extends BroadcastReceiver { @Override @@ -44,7 +45,7 @@ public class PackageReceiver extends BroadcastReceiver { null, null); if (packageName != null) { - LocalPreferences.clearPackagePreferences(context, packageName); + ScopedAccessLocalPreferences.clearPackagePreferences(context, packageName); } } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) { if (packageName != null) { @@ -52,7 +53,7 @@ public class PackageReceiver extends BroadcastReceiver { LastAccessedProvider.buildLastAccessed(packageName), LastAccessedProvider.METHOD_PURGE_PACKAGE, packageName, null); - LocalPreferences.clearPackagePreferences(context, packageName); + ScopedAccessLocalPreferences.clearPackagePreferences(context, packageName); } } } diff --git a/src/com/android/documentsui/ScopedAccessMetrics.java b/src/com/android/documentsui/ScopedAccessMetrics.java new file mode 100644 index 000000000..db5fa88a1 --- /dev/null +++ b/src/com/android/documentsui/ScopedAccessMetrics.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import static android.os.Environment.STANDARD_DIRECTORIES; + +import android.annotation.IntDef; +import android.annotation.StringDef; +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import android.util.Log; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Methods for logging scoped directory access metrics. + */ +public final class ScopedAccessMetrics { + private static final String TAG = "Metrics"; + + public static final boolean DEBUG = Build.IS_DEBUGGABLE; + + // Types for logInvalidScopedAccessRequest + public static final String SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS = + "docsui_scoped_directory_access_invalid_args"; + public static final String SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY = + "docsui_scoped_directory_access_invalid_dir"; + public static final String SCOPED_DIRECTORY_ACCESS_ERROR = + "docsui_scoped_directory_access_error"; + + @StringDef(value = { + SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS, + SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY, + SCOPED_DIRECTORY_ACCESS_ERROR + }) + @Retention(RetentionPolicy.SOURCE) + public @interface InvalidScopedAccess{} + + public static void logInvalidScopedAccessRequest(Context context, + @InvalidScopedAccess String type) { + switch (type) { + case SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS: + case SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY: + case SCOPED_DIRECTORY_ACCESS_ERROR: + logCount(context, type); + break; + default: + Log.wtf(TAG, "invalid InvalidScopedAccess: " + type); + } + } + + // Types for logValidScopedAccessRequest + public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED = 0; + public static final int SCOPED_DIRECTORY_ACCESS_GRANTED = 1; + public static final int SCOPED_DIRECTORY_ACCESS_DENIED = 2; + public static final int SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST = 3; + public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED = 4; + + @IntDef(flag = true, value = { + SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED, + SCOPED_DIRECTORY_ACCESS_GRANTED, + SCOPED_DIRECTORY_ACCESS_DENIED, + SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST, + SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ScopedAccessGrant {} + + public static void logValidScopedAccessRequest(Activity activity, String directory, + @ScopedAccessGrant int type) { + int index = -1; + if (OpenExternalDirectoryActivity.DIRECTORY_ROOT.equals(directory)) { + index = -2; + } else { + for (int i = 0; i < STANDARD_DIRECTORIES.length; i++) { + if (STANDARD_DIRECTORIES[i].equals(directory)) { + index = i; + break; + } + } + } + final String packageName = activity.getCallingPackage(); + switch (type) { + case SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED: + MetricsLogger.action(activity, MetricsEvent + .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_PACKAGE, packageName); + MetricsLogger.action(activity, MetricsEvent + .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_FOLDER, index); + break; + case SCOPED_DIRECTORY_ACCESS_GRANTED: + MetricsLogger.action(activity, MetricsEvent + .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_PACKAGE, packageName); + MetricsLogger.action(activity, MetricsEvent + .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_FOLDER, index); + break; + case SCOPED_DIRECTORY_ACCESS_DENIED: + MetricsLogger.action(activity, MetricsEvent + .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_PACKAGE, packageName); + MetricsLogger.action(activity, MetricsEvent + .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_FOLDER, index); + break; + case SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST: + MetricsLogger.action(activity, MetricsEvent + .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_PACKAGE, packageName); + MetricsLogger.action(activity, MetricsEvent + .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_FOLDER, index); + break; + case SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED: + MetricsLogger.action(activity, MetricsEvent + .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_PACKAGE, packageName); + MetricsLogger.action(activity, MetricsEvent + .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_FOLDER, index); + break; + default: + Log.wtf(TAG, "invalid ScopedAccessGrant: " + type); + } + } + + /** + * Internal method for making a MetricsLogger.count call. Increments the given counter by 1. + * + * @param context + * @param name The counter to increment. + */ + private static void logCount(Context context, String name) { + if (DEBUG) Log.d(TAG, name + ": " + 1); + MetricsLogger.count(context, name, 1); + } +} diff --git a/src/com/android/documentsui/ScopedAccessPackageReceiver.java b/src/com/android/documentsui/ScopedAccessPackageReceiver.java new file mode 100644 index 000000000..515f1db04 --- /dev/null +++ b/src/com/android/documentsui/ScopedAccessPackageReceiver.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +import com.android.documentsui.prefs.ScopedAccessLocalPreferences; + +/** + * Clean up {@link ScopedAccessLocalPreferences} when packages are removed. + */ +public class ScopedAccessPackageReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final ContentResolver resolver = context.getContentResolver(); + + final String action = intent.getAction(); + final Uri data = intent.getData(); + final String packageName = data == null ? null : data.getSchemeSpecificPart(); + + if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { + if (packageName != null) { + ScopedAccessLocalPreferences.clearPackagePreferences(context, packageName); + } + } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) { + if (packageName != null) { + ScopedAccessLocalPreferences.clearPackagePreferences(context, packageName); + } + } + } +} diff --git a/src/com/android/documentsui/prefs/LocalPreferences.java b/src/com/android/documentsui/prefs/LocalPreferences.java index 48a922013..955b19da8 100644 --- a/src/com/android/documentsui/prefs/LocalPreferences.java +++ b/src/com/android/documentsui/prefs/LocalPreferences.java @@ -19,20 +19,19 @@ package com.android.documentsui.prefs; import static com.android.documentsui.base.State.MODE_UNKNOWN; import android.annotation.IntDef; -import android.annotation.Nullable; import android.content.Context; import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.os.UserHandle; import android.preference.PreferenceManager; import com.android.documentsui.base.RootInfo; -import com.android.documentsui.base.State; import com.android.documentsui.base.State.ViewMode; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +/** + * Methods for accessing the local preferences. + */ public class LocalPreferences { private static final String ROOT_VIEW_MODE_PREFIX = "rootViewMode-"; @@ -66,64 +65,6 @@ public class LocalPreferences { @Retention(RetentionPolicy.SOURCE) public @interface PermissionStatus {} - /** - * Clears all preferences associated with a given package. - * - * <p>Typically called when a package is removed or when user asked to clear its data. - */ - public static void clearPackagePreferences(Context context, String packageName) { - clearScopedAccessPreferences(context, packageName); - } - - /** - * Methods below are used to keep track of denied user requests on scoped directory access so - * the dialog is not offered when user checked the 'Do not ask again' box - * - * <p>It uses a shared preferences, whose key is: - * <ol> - * <li>{@code USER_ID|PACKAGE_NAME|VOLUME_UUID|DIRECTORY} for storage volumes that have a UUID - * (typically physical volumes like SD cards). - * <li>{@code USER_ID|PACKAGE_NAME||DIRECTORY} for storage volumes that do not have a UUID - * (typically the emulated volume used for primary storage - * </ol> - */ - public static @PermissionStatus int getScopedAccessPermissionStatus(Context context, - String packageName, @Nullable String uuid, String directory) { - final String key = getScopedAccessDenialsKey(packageName, uuid, directory); - return getPrefs(context).getInt(key, PERMISSION_ASK); - } - - public static void setScopedAccessPermissionStatus(Context context, String packageName, - @Nullable String uuid, String directory, @PermissionStatus int status) { - final String key = getScopedAccessDenialsKey(packageName, uuid, directory); - getPrefs(context).edit().putInt(key, status).apply(); - } - - private static void clearScopedAccessPreferences(Context context, String packageName) { - final String keySubstring = "|" + packageName + "|"; - final SharedPreferences prefs = getPrefs(context); - Editor editor = null; - for (final String key : prefs.getAll().keySet()) { - if (key.contains(keySubstring)) { - if (editor == null) { - editor = prefs.edit(); - } - editor.remove(key); - } - } - if (editor != null) { - editor.apply(); - } - } - - private static String getScopedAccessDenialsKey(String packageName, String uuid, - String directory) { - final int userId = UserHandle.myUserId(); - return uuid == null - ? userId + "|" + packageName + "||" + directory - : userId + "|" + packageName + "|" + uuid + "|" + directory; - } - public static boolean shouldBackup(String s) { return (s != null) ? s.startsWith(ROOT_VIEW_MODE_PREFIX) : false; } diff --git a/src/com/android/documentsui/prefs/ScopedAccessLocalPreferences.java b/src/com/android/documentsui/prefs/ScopedAccessLocalPreferences.java new file mode 100644 index 000000000..f3e9a2591 --- /dev/null +++ b/src/com/android/documentsui/prefs/ScopedAccessLocalPreferences.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.documentsui.prefs; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.os.UserHandle; +import android.preference.PreferenceManager; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Methods for accessing the local preferences with regards to scoped directory access. + */ +public class ScopedAccessLocalPreferences { + + private static SharedPreferences getPrefs(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context); + } + + public static final int PERMISSION_ASK = 0; + public static final int PERMISSION_ASK_AGAIN = 1; + public static final int PERMISSION_NEVER_ASK = -1; + + @IntDef(flag = true, value = { + PERMISSION_ASK, + PERMISSION_ASK_AGAIN, + PERMISSION_NEVER_ASK, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PermissionStatus {} + + /** + * Methods below are used to keep track of denied user requests on scoped directory access so + * the dialog is not offered when user checked the 'Do not ask again' box + * + * <p>It uses a shared preferences, whose key is: + * <ol> + * <li>{@code USER_ID|PACKAGE_NAME|VOLUME_UUID|DIRECTORY} for storage volumes that have a UUID + * (typically physical volumes like SD cards). + * <li>{@code USER_ID|PACKAGE_NAME||DIRECTORY} for storage volumes that do not have a UUID + * (typically the emulated volume used for primary storage + * </ol> + */ + public static @PermissionStatus int getScopedAccessPermissionStatus(Context context, + String packageName, @Nullable String uuid, String directory) { + final String key = getScopedAccessDenialsKey(packageName, uuid, directory); + return getPrefs(context).getInt(key, PERMISSION_ASK); + } + + public static void setScopedAccessPermissionStatus(Context context, String packageName, + @Nullable String uuid, String directory, @PermissionStatus int status) { + final String key = getScopedAccessDenialsKey(packageName, uuid, directory); + getPrefs(context).edit().putInt(key, status).apply(); + } + + public static void clearScopedAccessPreferences(Context context, String packageName) { + final String keySubstring = "|" + packageName + "|"; + final SharedPreferences prefs = getPrefs(context); + Editor editor = null; + for (final String key : prefs.getAll().keySet()) { + if (key.contains(keySubstring)) { + if (editor == null) { + editor = prefs.edit(); + } + editor.remove(key); + } + } + if (editor != null) { + editor.apply(); + } + } + + private static String getScopedAccessDenialsKey(String packageName, String uuid, + String directory) { + final int userId = UserHandle.myUserId(); + return uuid == null + ? userId + "|" + packageName + "||" + directory + : userId + "|" + packageName + "|" + uuid + "|" + directory; + } + + /** + * Clears all preferences associated with a given package. + * + * <p>Typically called when a package is removed or when user asked to clear its data. + */ + public static void clearPackagePreferences(Context context, String packageName) { + ScopedAccessLocalPreferences.clearScopedAccessPreferences(context, packageName); + } +} |