Merge "Add settings UI for MANAGE_EXTERNAL_STORAGE"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6589023..e68feb8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6968,6 +6968,8 @@
     <string name="help_uri_apps_photography" translatable="false"></string>
     <!-- Help URI, manage apps wifi access [DO NOT TRANSLATE] -->
     <string name="help_uri_apps_wifi_access" translatable="false"></string>
+    <!-- Help URI, manage apps that have access to all files [DO NOT TRANSLATE] -->
+    <string name="help_uri_manage_external_storage" translatable="false"></string>
     <!-- Help URI, Storage [DO NOT TRANSLATE] -->
     <string name="help_uri_storage" translatable="false"></string>
     <!-- Help URI, Accessibility [DO NOT TRANSLATE] -->
@@ -9445,6 +9447,16 @@
     <!-- Description of allowing overlay setting [CHAR LIMIT=NONE] -->
     <string name="allow_overlay_description">Allow this app to display on top of other apps you\u2019re using. It may interfere with your use of those apps or change the way they seem to appear or behave.</string>
 
+    <!-- Manager External Storage settings title [CHAR LIMIT=30] -->
+    <string name="manage_external_storage_title">All files access</string>
+    <!-- Label for a setting which controls whether an app can manage external storage [CHAR LIMIT=45] -->
+    <string name="permit_manage_external_storage">Allow access to manage all files</string>
+    <!-- Description for a setting which controls whether an app can manage external storage
+         [CHAR LIMIT=NONE] -->
+    <string name="allow_manage_external_storage_description">Allow this app to read, modify and delete all files on this device or any connected storage volumes. If granted, app may access files without your explicit knowledge.</string>
+    <!-- Label for showing apps that can manage external storage[CHAR LIMIT=45] -->
+    <string name="filter_manage_external_storage">Can access all files</string>
+
     <!-- Keyword for VR setting -->
     <string name="keywords_vr_listener">vr virtual reality listener stereo helper service</string>
     <!-- Main settings screen item's title to go into the overlay settings screen [CHAR LIMIT=30] -->
diff --git a/res/xml/manage_external_storage_permission_details.xml b/res/xml/manage_external_storage_permission_details.xml
new file mode 100644
index 0000000..b540ff6
--- /dev/null
+++ b/res/xml/manage_external_storage_permission_details.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:key="manage_external_storage_permission_details"
+    android:title="@string/manage_external_storage_title">
+
+    <SwitchPreference
+        android:key="app_ops_settings_switch"
+        android:title="@string/permit_manage_external_storage"/>
+
+    <Preference
+        android:summary="@string/allow_manage_external_storage_description"
+        android:selectable="false"/>
+
+</PreferenceScreen>
diff --git a/res/xml/special_access.xml b/res/xml/special_access.xml
index 25422bf..e511d17 100644
--- a/res/xml/special_access.xml
+++ b/res/xml/special_access.xml
@@ -21,6 +21,15 @@
     android:title="@string/special_access">
 
     <Preference
+        android:key="manage_external_storage"
+        android:title="@string/manage_external_storage_title"
+        android:fragment="com.android.settings.applications.manageapplications.ManageApplications">
+        <extra
+            android:name="classname"
+            android:value="com.android.settings.Settings$ManageExternalStorageActivity" />
+    </Preference>
+
+    <Preference
         android:key="high_power_apps"
         android:title="@string/high_power_apps"
         android:fragment="com.android.settings.applications.manageapplications.ManageApplications"
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 50caf32..d5c0871 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -138,6 +138,7 @@
     public static class MemorySettingsActivity extends SettingsActivity { /* empty */ }
     public static class AppMemoryUsageActivity extends SettingsActivity { /* empty */ }
     public static class OverlaySettingsActivity extends SettingsActivity { /* empty */ }
+    public static class ManageExternalStorageActivity extends SettingsActivity { /* empty */ }
     public static class WriteSettingsActivity extends SettingsActivity { /* empty */ }
     public static class ChangeWifiStateActivity extends SettingsActivity { /* empty */ }
     public static class AppDrawOverlaySettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/applications/AppStateAppOpsBridge.java b/src/com/android/settings/applications/AppStateAppOpsBridge.java
index 0e3ee2d..3dbdbe9 100755
--- a/src/com/android/settings/applications/AppStateAppOpsBridge.java
+++ b/src/com/android/settings/applications/AppStateAppOpsBridge.java
@@ -328,7 +328,7 @@
         public boolean isPermissible() {
             // defining the default behavior as permissible as long as the package requested this
             // permission (this means pre-M gets approval during install time; M apps gets approval
-            // during runtime.
+            // during runtime).
             if (appOpMode == AppOpsManager.MODE_DEFAULT) {
                 return staticPermissionGranted;
             }
diff --git a/src/com/android/settings/applications/AppStateManageExternalStorageBridge.java b/src/com/android/settings/applications/AppStateManageExternalStorageBridge.java
new file mode 100644
index 0000000..5a69035
--- /dev/null
+++ b/src/com/android/settings/applications/AppStateManageExternalStorageBridge.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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.settings.applications;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+
+import com.android.settingslib.applications.ApplicationsState;
+
+/**
+ * Retrieves information from {@link AppOpsManager} and {@link android.content.pm.PackageManager}
+ * regarding {@link AppOpsManager#OP_MANAGE_EXTERNAL_STORAGE} and
+ * {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}.
+ */
+public class AppStateManageExternalStorageBridge extends AppStateAppOpsBridge {
+    private static final int APP_OPS_OP_CODE = AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE;
+    private static final String[] PERMISSIONS = {
+            Manifest.permission.MANAGE_EXTERNAL_STORAGE
+    };
+
+    public AppStateManageExternalStorageBridge(Context context, ApplicationsState appState,
+            Callback callback) {
+        super(context, appState, callback, APP_OPS_OP_CODE, PERMISSIONS);
+    }
+
+    @Override
+    protected void updateExtraInfo(ApplicationsState.AppEntry app, String pkg, int uid) {
+        app.extraInfo = getManageExternalStoragePermState(pkg, uid);
+    }
+
+    /**
+     * Returns the MANAGE_EXTERNAL_STORAGE {@link AppStateAppOpsBridge.PermissionState} object
+     * associated with the given package and user.
+     */
+    public PermissionState getManageExternalStoragePermState(String pkg, int uid) {
+        return getPermissionInfo(pkg, uid);
+    }
+
+    /**
+     * Used by {@link com.android.settings.applications.manageapplications.AppFilterRegistry} to
+     * determine which apps get to appear on the Special App Access list.
+     */
+    public static final ApplicationsState.AppFilter FILTER_MANAGE_EXTERNAL_STORAGE =
+            new ApplicationsState.AppFilter() {
+        @Override
+        public void init() {
+        }
+
+        @Override
+        public boolean filterApp(ApplicationsState.AppEntry info) {
+            // If extraInfo != null, it means that the app has declared
+            // Manifest.permission.MANAGE_EXTERNAL_STORAGE and therefore it should appear on our
+            // list
+            return info.extraInfo != null;
+        }
+    };
+}
diff --git a/src/com/android/settings/applications/appinfo/ManageExternalStorageDetails.java b/src/com/android/settings/applications/appinfo/ManageExternalStorageDetails.java
new file mode 100644
index 0000000..63ce440
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/ManageExternalStorageDetails.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2020 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.settings.applications.appinfo;
+
+import android.app.AppOpsManager;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.Preference;
+import androidx.preference.Preference.OnPreferenceChangeListener;
+import androidx.preference.Preference.OnPreferenceClickListener;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoWithHeader;
+import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
+import com.android.settings.applications.AppStateManageExternalStorageBridge;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+/**
+ * Class for displaying app info related to {@link AppOpsManager#OP_MANAGE_EXTERNAL_STORAGE}.
+ */
+public class ManageExternalStorageDetails extends AppInfoWithHeader implements
+        OnPreferenceChangeListener, OnPreferenceClickListener {
+
+    private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch";
+
+    private AppStateManageExternalStorageBridge mBridge;
+    private AppOpsManager mAppOpsManager;
+    private SwitchPreference mSwitchPref;
+    private PermissionState mPermissionState;
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Context context = getActivity();
+        mBridge = new AppStateManageExternalStorageBridge(context, mState, null);
+        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+
+        // initialize preferences
+        addPreferencesFromResource(R.xml.manage_external_storage_permission_details);
+        mSwitchPref = findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
+
+        // install event listeners
+        mSwitchPref.setOnPreferenceChangeListener(this);
+
+        mMetricsFeatureProvider =
+                FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater,
+            ViewGroup container,
+            Bundle savedInstanceState) {
+        // if we don't have a package info, show a page saying this is unsupported
+        if (mPackageInfo == null) {
+            return inflater.inflate(R.layout.manage_applications_apps_unsupported, null);
+        }
+        return super.onCreateView(inflater, container, savedInstanceState);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mBridge.release();
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        return false;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (preference == mSwitchPref) {
+            if (mPermissionState != null && !newValue.equals(mPermissionState.isPermissible())) {
+                setManageExternalStorageState((Boolean) newValue);
+                refreshUi();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Toggles {@link AppOpsManager#OP_MANAGE_EXTERNAL_STORAGE} for the app.
+     */
+    private void setManageExternalStorageState(boolean newState) {
+        logSpecialPermissionChange(newState, mPackageName);
+        mAppOpsManager.setMode(AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE,
+                mPackageInfo.applicationInfo.uid, mPackageName, newState
+                        ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
+    }
+
+    private void logSpecialPermissionChange(boolean newState, String packageName) {
+        int logCategory = newState ? SettingsEnums.APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_ALLOW
+                : SettingsEnums.APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_DENY;
+
+        mMetricsFeatureProvider.action(
+                mMetricsFeatureProvider.getAttribution(getActivity()),
+                logCategory,
+                getMetricsCategory(),
+                packageName,
+                0 /* value */);
+    }
+
+    @Override
+    protected boolean refreshUi() {
+        if (mPackageInfo == null) {
+            return true;
+        }
+
+        mPermissionState = mBridge.getManageExternalStoragePermState(mPackageName,
+                mPackageInfo.applicationInfo.uid);
+
+        mSwitchPref.setChecked(mPermissionState.isPermissible());
+
+        // you cannot ask a user to grant you a permission you did not have!
+        mSwitchPref.setEnabled(mPermissionState.permissionDeclared);
+
+        return true;
+    }
+
+    @Override
+    protected AlertDialog createDialog(int id, int errorCode) {
+        return null;
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.MANAGE_EXTERNAL_STORAGE;
+    }
+
+    /**
+     * Returns the string that states whether whether the app has access to
+     * {@link AppOpsManager#OP_MANAGE_EXTERNAL_STORAGE}.
+     * <p>This string is used in the "All files access" page that displays all apps requesting
+     * {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+     */
+    public static CharSequence getSummary(Context context, AppEntry entry) {
+        final PermissionState state;
+        if (entry.extraInfo instanceof PermissionState) {
+            state = (PermissionState) entry.extraInfo;
+        } else {
+            state = new AppStateManageExternalStorageBridge(context, null, null)
+                    .getManageExternalStoragePermState(entry.info.packageName, entry.info.uid);
+        }
+
+        return getSummary(context, state);
+    }
+
+    private static CharSequence getSummary(Context context, PermissionState state) {
+        return context.getString(state.isPermissible()
+                ? R.string.app_permission_summary_allowed
+                : R.string.app_permission_summary_not_allowed);
+    }
+}
diff --git a/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java b/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java
index 250dce0..58907a7 100644
--- a/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java
+++ b/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java
@@ -20,6 +20,7 @@
 
 import com.android.settings.R;
 import com.android.settings.applications.AppStateInstallAppsBridge;
+import com.android.settings.applications.AppStateManageExternalStorageBridge;
 import com.android.settings.applications.AppStateNotificationBridge;
 import com.android.settings.applications.AppStateOverlayBridge;
 import com.android.settings.applications.AppStatePowerBridge;
@@ -71,14 +72,15 @@
     public static final int FILTER_APPS_INSTALL_SOURCES = 13;
     public static final int FILTER_APP_CAN_CHANGE_WIFI_STATE = 15;
     public static final int FILTER_APPS_BLOCKED = 16;
-    // Next id: 17
+    public static final int FILTER_MANAGE_EXTERNAL_STORAGE = 17;
+    // Next id: 18. If you add an entry here, length of mFilters should be updated
 
     private static AppFilterRegistry sRegistry;
 
     private final AppFilterItem[] mFilters;
 
     private AppFilterRegistry() {
-        mFilters = new AppFilterItem[17];
+        mFilters = new AppFilterItem[18];
 
         // High power whitelist, on
         mFilters[FILTER_APPS_POWER_WHITELIST] = new AppFilterItem(
@@ -178,6 +180,11 @@
                 AppStateNotificationBridge.FILTER_APP_NOTIFICATION_BLOCKED,
                 FILTER_APPS_BLOCKED,
                 R.string.filter_notif_blocked_apps);
+
+        mFilters[FILTER_MANAGE_EXTERNAL_STORAGE] = new AppFilterItem(
+                AppStateManageExternalStorageBridge.FILTER_MANAGE_EXTERNAL_STORAGE,
+                FILTER_MANAGE_EXTERNAL_STORAGE,
+                R.string.filter_manage_external_storage);
     }
 
     public static AppFilterRegistry getInstance() {
@@ -204,6 +211,8 @@
                 return FILTER_APP_CAN_CHANGE_WIFI_STATE;
             case ManageApplications.LIST_TYPE_NOTIFICATION:
                 return FILTER_APPS_RECENT;
+            case ManageApplications.LIST_MANAGE_EXTERNAL_STORAGE:
+                return FILTER_MANAGE_EXTERNAL_STORAGE;
             default:
                 return FILTER_APPS_ALL;
         }
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index 02e42e2..d38893f 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -88,6 +88,7 @@
 import com.android.settings.SettingsActivity;
 import com.android.settings.Utils;
 import com.android.settings.applications.AppInfoBase;
+import com.android.settings.applications.AppStateManageExternalStorageBridge;
 import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
 import com.android.settings.applications.AppStateBaseBridge;
 import com.android.settings.applications.AppStateInstallAppsBridge;
@@ -100,6 +101,7 @@
 import com.android.settings.applications.AppStateWriteSettingsBridge;
 import com.android.settings.applications.AppStorageSettings;
 import com.android.settings.applications.UsageAccessDetails;
+import com.android.settings.applications.appinfo.ManageExternalStorageDetails;
 import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
 import com.android.settings.applications.appinfo.DrawOverlayDetails;
 import com.android.settings.applications.appinfo.ExternalSourcesDetails;
@@ -224,6 +226,7 @@
     public static final int LIST_TYPE_MOVIES = 10;
     public static final int LIST_TYPE_PHOTOGRAPHY = 11;
     public static final int LIST_TYPE_WIFI_ACCESS = 13;
+    public static final int LIST_MANAGE_EXTERNAL_STORAGE = 14;
 
     // List types that should show instant apps.
     public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList(
@@ -311,6 +314,9 @@
         } else if (className.equals(Settings.ChangeWifiStateActivity.class.getName())) {
             mListType = LIST_TYPE_WIFI_ACCESS;
             screenTitle = R.string.change_wifi_state_title;
+        } else if (className.equals(Settings.ManageExternalStorageActivity.class.getName())) {
+            mListType = LIST_MANAGE_EXTERNAL_STORAGE;
+            screenTitle = R.string.manage_external_storage_title;
         } else if (className.equals(Settings.NotificationAppListActivity.class.getName())) {
             mListType = LIST_TYPE_NOTIFICATION;
             mUsageStatsManager = IUsageStatsManager.Stub.asInterface(
@@ -538,6 +544,8 @@
                 return SettingsEnums.MANAGE_EXTERNAL_SOURCES;
             case LIST_TYPE_WIFI_ACCESS:
                 return SettingsEnums.CONFIGURE_WIFI;
+            case LIST_MANAGE_EXTERNAL_STORAGE:
+                return SettingsEnums.MANAGE_EXTERNAL_STORAGE;
             default:
                 return SettingsEnums.PAGE_UNKNOWN;
         }
@@ -640,6 +648,10 @@
                 startAppInfoFragment(ChangeWifiStateDetails.class,
                         R.string.change_wifi_state_title);
                 break;
+            case LIST_MANAGE_EXTERNAL_STORAGE:
+                startAppInfoFragment(ManageExternalStorageDetails.class,
+                        R.string.manage_external_storage_title);
+                break;
             // TODO: Figure out if there is a way where we can spin up the profile's settings
             // process ahead of time, to avoid a long load of data when user clicks on a managed
             // app. Maybe when they load the list of apps that contains managed profile apps.
@@ -713,6 +725,8 @@
                 return R.string.help_uri_apps_photography;
             case LIST_TYPE_WIFI_ACCESS:
                 return R.string.help_uri_apps_wifi_access;
+            case LIST_MANAGE_EXTERNAL_STORAGE:
+                return R.string.help_uri_manage_external_storage;
             default:
             case LIST_TYPE_MAIN:
                 return R.string.help_uri_apps;
@@ -1031,6 +1045,8 @@
                 mExtraInfoBridge = new AppStateInstallAppsBridge(mContext, mState, this);
             } else if (mManageApplications.mListType == LIST_TYPE_WIFI_ACCESS) {
                 mExtraInfoBridge = new AppStateChangeWifiStateBridge(mContext, mState, this);
+            } else if (mManageApplications.mListType == LIST_MANAGE_EXTERNAL_STORAGE) {
+                mExtraInfoBridge = new AppStateManageExternalStorageBridge(mContext, mState, this);
             } else {
                 mExtraInfoBridge = null;
             }
@@ -1486,6 +1502,9 @@
                 case LIST_TYPE_WIFI_ACCESS:
                     holder.setSummary(ChangeWifiStateDetails.getSummary(mContext, entry));
                     break;
+                case LIST_MANAGE_EXTERNAL_STORAGE:
+                    holder.setSummary(ManageExternalStorageDetails.getSummary(mContext, entry));
+                    break;
                 default:
                     holder.updateSizeText(entry, mManageApplications.mInvalidSizeStr, mWhichSize);
                     break;