Initial screens for Storage Access.
This new settings will be accessed through Apps -> Special Apps and it will let users change the status of granted (or denied) Scoped Access Permissions. This initial CL implements the landing activity that shows all packages and an initial version of the detailed activity for a specific page, althouth the detailed activity is not ready yet. Test: manual verification Test: export ROBOTEST_FILTER=com.android.settings.core.codeinspection.CodeInspectionTest && make RunSettingsRoboTests -j40 Bug: 63720392 Change-Id: Id0419c2c4aaaec365803218d6947e230e4776f13
This commit is contained in:
@@ -9120,4 +9120,9 @@
|
||||
<!-- UI debug setting: preference summary - describes the behavior of forcing full raw GNSS satellite measurements [CHAR LIMIT=NONE] -->
|
||||
<string name="enable_gnss_raw_meas_full_tracking_summary">Track all GNSS constellations and frequencies with no duty cycling</string>
|
||||
|
||||
<!-- Title for Storage Access settings -->
|
||||
<string name="storage_access">Storage access</string>
|
||||
<!-- Keywords for Storage Access settings -->
|
||||
<string name="keywords_storage_access">storage access scoped directory</string>
|
||||
|
||||
</resources>
|
||||
|
@@ -111,4 +111,16 @@
|
||||
android:value="com.android.settings.Settings$VrListenersSettingsActivity" />
|
||||
</Preference>
|
||||
|
||||
<!-- TODO(b/63720392): add when ready
|
||||
<Preference
|
||||
android:key="special_app_storage_access"
|
||||
android:title="@string/storage_access"
|
||||
android:fragment="com.android.settings.applications.manageapplications.ManageApplications"
|
||||
settings:keywords="@string/keywords_storage_access">
|
||||
<extra
|
||||
android:name="classname"
|
||||
android:value="com.android.settings.Settings$StorageAccessSettingsActivity" />
|
||||
</Preference>
|
||||
-->
|
||||
|
||||
</PreferenceScreen>
|
||||
|
@@ -120,6 +120,7 @@ public class Settings extends SettingsActivity {
|
||||
public static class PhotosStorageActivity extends SettingsActivity {
|
||||
/* empty */
|
||||
}
|
||||
public static class StorageAccessSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
|
||||
public static class TopLevelSettings extends SettingsActivity { /* empty */ }
|
||||
public static class ApnSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
|
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 static android.os.storage.StorageVolume.ScopedAccessProviderContract.AUTHORITY;
|
||||
import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES;
|
||||
import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES_COLUMNS;
|
||||
import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES_COL_PACKAGE;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settingslib.applications.ApplicationsState;
|
||||
import com.android.settingslib.applications.ApplicationsState.AppEntry;
|
||||
import com.android.settingslib.applications.ApplicationsState.AppFilter;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
// TODO(b/63720392): add unit tests
|
||||
public class AppStateStorageAccessBridge extends AppStateBaseBridge {
|
||||
|
||||
private static final String TAG = "StorageAccessBridge";
|
||||
|
||||
public AppStateStorageAccessBridge(ApplicationsState appState, Callback callback) {
|
||||
super(appState, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAllExtraInfo() { }
|
||||
|
||||
@Override
|
||||
protected void updateExtraInfo(AppEntry app, String pkg, int uid) { }
|
||||
|
||||
public static final AppFilter FILTER_APP_HAS_STORAGE_ACCESS = new AppFilter() {
|
||||
|
||||
private Set<String> mPackages;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
throw new UnsupportedOperationException("Need to call constructor that takes context");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Context context) {
|
||||
try (Cursor cursor = context.getContentResolver().query(
|
||||
new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(AUTHORITY).appendPath(TABLE_PACKAGES).appendPath("*")
|
||||
.build(), TABLE_PACKAGES_COLUMNS, null, null)) {
|
||||
if (cursor == null) {
|
||||
Log.w(TAG, "didn't get cursor");
|
||||
return;
|
||||
}
|
||||
final int count = cursor.getCount();
|
||||
if (count == 0) {
|
||||
Log.d(TAG, "no packages");
|
||||
return;
|
||||
}
|
||||
mPackages = new ArraySet<>(count);
|
||||
while (cursor.moveToNext()) {
|
||||
mPackages.add(cursor.getString(TABLE_PACKAGES_COL_PACKAGE));
|
||||
}
|
||||
Log.v(TAG, "init(): " + mPackages);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean filterApp(AppEntry info) {
|
||||
return mPackages != null && mPackages.contains(info.info.packageName);
|
||||
}
|
||||
};
|
||||
}
|
110
src/com/android/settings/applications/StorageAccessDetails.java
Normal file
110
src/com/android/settings/applications/StorageAccessDetails.java
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 static android.os.storage.StorageVolume.ScopedAccessProviderContract.AUTHORITY;
|
||||
import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS;
|
||||
import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COLUMNS;
|
||||
import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_DIRECTORY;
|
||||
import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_GRANTED;
|
||||
import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_PACKAGE;
|
||||
import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_VOLUME_UUID;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.Preference.OnPreferenceChangeListener;
|
||||
import android.support.v7.preference.Preference.OnPreferenceClickListener;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* Detailed settings for an app's storage access permissions (A.K.A Scoped Directory Access).
|
||||
*/
|
||||
// TODO(b/63720392): explain its layout
|
||||
// TODO(b/63720392): add unit tests
|
||||
public class StorageAccessDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
|
||||
OnPreferenceClickListener {
|
||||
private static final String MY_TAG = "StorageAccessDetails";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (true) {
|
||||
// TODO(b/63720392): temporarily hack so the screen doesn't crash..
|
||||
addPreferencesFromResource(R.xml.app_ops_permissions_details);
|
||||
// ... we need to dynamically create the preferences by calling the provider instead:
|
||||
try (Cursor cursor = getContentResolver().query(
|
||||
new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(AUTHORITY).appendPath(TABLE_PERMISSIONS).appendPath("*")
|
||||
.build(),
|
||||
TABLE_PERMISSIONS_COLUMNS, null, null)) {
|
||||
if (cursor == null) {
|
||||
Log.w(TAG, "didn't get cursor");
|
||||
return;
|
||||
}
|
||||
final int count = cursor.getCount();
|
||||
if (count == 0) {
|
||||
Log.d(TAG, "no permissions");
|
||||
return;
|
||||
}
|
||||
while (cursor.moveToNext()) {
|
||||
final String pkg = cursor.getString(TABLE_PERMISSIONS_COL_PACKAGE);
|
||||
final String uuid = cursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID);
|
||||
final String dir = cursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY);
|
||||
final boolean granted = cursor.getInt(TABLE_PERMISSIONS_COL_GRANTED) == 1;
|
||||
Log.v(MY_TAG, "pkg:" + pkg + " uuid: " + uuid + " dir: " + dir + "> "
|
||||
+ granted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
// TODO(b/63720392): implement or remove listener
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
// TODO(b/63720392): implement or remove listener
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean refreshUi() {
|
||||
// TODO(b/63720392): implement
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AlertDialog createDialog(int id, int errorCode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return MetricsEvent.APPLICATIONS_USAGE_ACCESS_DETAIL;
|
||||
}
|
||||
}
|
@@ -23,6 +23,7 @@ import com.android.settings.applications.AppStateInstallAppsBridge;
|
||||
import com.android.settings.applications.AppStateNotificationBridge;
|
||||
import com.android.settings.applications.AppStateOverlayBridge;
|
||||
import com.android.settings.applications.AppStatePowerBridge;
|
||||
import com.android.settings.applications.AppStateStorageAccessBridge;
|
||||
import com.android.settings.applications.AppStateUsageBridge;
|
||||
import com.android.settings.applications.AppStateWriteSettingsBridge;
|
||||
import com.android.settingslib.applications.ApplicationsState;
|
||||
@@ -65,14 +66,15 @@ public class AppFilterRegistry {
|
||||
public static final int FILTER_APPS_WITH_OVERLAY = 10;
|
||||
public static final int FILTER_APPS_WRITE_SETTINGS = 11;
|
||||
public static final int FILTER_APPS_INSTALL_SOURCES = 12;
|
||||
// Next id: 13
|
||||
public static final int FILTER_APP_HAS_STORAGE_ACCESS = 13;
|
||||
// Next id: 14
|
||||
|
||||
private static AppFilterRegistry sRegistry;
|
||||
|
||||
private final AppFilterItem[] mFilters;
|
||||
|
||||
private AppFilterRegistry() {
|
||||
mFilters = new AppFilterItem[13];
|
||||
mFilters = new AppFilterItem[14];
|
||||
|
||||
// High power whitelist, on
|
||||
mFilters[FILTER_APPS_POWER_WHITELIST] = new AppFilterItem(
|
||||
@@ -155,6 +157,12 @@ public class AppFilterRegistry {
|
||||
AppStateInstallAppsBridge.FILTER_APP_SOURCES,
|
||||
FILTER_APPS_INSTALL_SOURCES,
|
||||
R.string.filter_install_sources_apps);
|
||||
|
||||
// Apps that interacted with storage access permissions (A.K.A. Scoped Directory Access)
|
||||
mFilters[FILTER_APP_HAS_STORAGE_ACCESS] = new AppFilterItem(
|
||||
AppStateStorageAccessBridge.FILTER_APP_HAS_STORAGE_ACCESS,
|
||||
FILTER_APP_HAS_STORAGE_ACCESS,
|
||||
R.string.filter_install_sources_apps);
|
||||
}
|
||||
|
||||
public static AppFilterRegistry getInstance() {
|
||||
@@ -177,6 +185,8 @@ public class AppFilterRegistry {
|
||||
return FILTER_APPS_WRITE_SETTINGS;
|
||||
case ManageApplications.LIST_TYPE_MANAGE_SOURCES:
|
||||
return FILTER_APPS_INSTALL_SOURCES;
|
||||
case ManageApplications.LIST_TYPE_STORAGE_ACCESS:
|
||||
return FILTER_APP_HAS_STORAGE_ACCESS;
|
||||
default:
|
||||
return FILTER_APPS_ALL;
|
||||
}
|
||||
|
@@ -84,6 +84,7 @@ import com.android.settings.applications.AppStateInstallAppsBridge;
|
||||
import com.android.settings.applications.AppStateNotificationBridge;
|
||||
import com.android.settings.applications.AppStateOverlayBridge;
|
||||
import com.android.settings.applications.AppStatePowerBridge;
|
||||
import com.android.settings.applications.AppStateStorageAccessBridge;
|
||||
import com.android.settings.applications.AppStateUsageBridge;
|
||||
import com.android.settings.applications.AppStateUsageBridge.UsageState;
|
||||
import com.android.settings.applications.AppStateWriteSettingsBridge;
|
||||
@@ -92,6 +93,7 @@ import com.android.settings.applications.DefaultAppSettings;
|
||||
import com.android.settings.applications.InstalledAppCounter;
|
||||
import com.android.settings.applications.InstalledAppDetails;
|
||||
import com.android.settings.applications.NotificationApps;
|
||||
import com.android.settings.applications.StorageAccessDetails;
|
||||
import com.android.settings.applications.UsageAccessDetails;
|
||||
import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
|
||||
import com.android.settings.applications.appinfo.DrawOverlayDetails;
|
||||
@@ -204,6 +206,7 @@ public class ManageApplications extends InstrumentedPreferenceFragment
|
||||
public static final int LIST_TYPE_GAMES = 9;
|
||||
public static final int LIST_TYPE_MOVIES = 10;
|
||||
public static final int LIST_TYPE_PHOTOGRAPHY = 11;
|
||||
public static final int LIST_TYPE_STORAGE_ACCESS = 12;
|
||||
|
||||
// List types that should show instant apps.
|
||||
public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList(
|
||||
@@ -279,6 +282,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment
|
||||
mListType = LIST_TYPE_PHOTOGRAPHY;
|
||||
mSortOrder = R.id.sort_order_size;
|
||||
mStorageType = args.getInt(EXTRA_STORAGE_TYPE, STORAGE_TYPE_DEFAULT);
|
||||
} else if (className.equals(Settings.StorageAccessSettingsActivity.class.getName())) {
|
||||
mListType = LIST_TYPE_STORAGE_ACCESS;
|
||||
screenTitle = R.string.storage_access;
|
||||
} else {
|
||||
mListType = LIST_TYPE_MAIN;
|
||||
}
|
||||
@@ -443,6 +449,8 @@ public class ManageApplications extends InstrumentedPreferenceFragment
|
||||
return MetricsEvent.SYSTEM_ALERT_WINDOW_APPS;
|
||||
case LIST_TYPE_MANAGE_SOURCES:
|
||||
return MetricsEvent.MANAGE_EXTERNAL_SOURCES;
|
||||
case LIST_TYPE_STORAGE_ACCESS:
|
||||
return MetricsEvent.STORAGE_ACCESS;
|
||||
default:
|
||||
return MetricsEvent.VIEW_UNKNOWN;
|
||||
}
|
||||
@@ -537,6 +545,10 @@ public class ManageApplications extends InstrumentedPreferenceFragment
|
||||
case LIST_TYPE_PHOTOGRAPHY:
|
||||
startAppInfoFragment(AppStorageSettings.class, R.string.storage_photos_videos);
|
||||
break;
|
||||
case LIST_TYPE_STORAGE_ACCESS:
|
||||
startAppInfoFragment(StorageAccessDetails.class, R.string.storage_access);
|
||||
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.
|
||||
@@ -840,6 +852,8 @@ public class ManageApplications extends InstrumentedPreferenceFragment
|
||||
mExtraInfoBridge = new AppStateWriteSettingsBridge(mContext, mState, this);
|
||||
} else if (mManageApplications.mListType == LIST_TYPE_MANAGE_SOURCES) {
|
||||
mExtraInfoBridge = new AppStateInstallAppsBridge(mContext, mState, this);
|
||||
} else if (mManageApplications.mListType == LIST_TYPE_STORAGE_ACCESS) {
|
||||
mExtraInfoBridge = new AppStateStorageAccessBridge(mState, this);
|
||||
} else {
|
||||
mExtraInfoBridge = null;
|
||||
}
|
||||
@@ -1241,6 +1255,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment
|
||||
case LIST_TYPE_MANAGE_SOURCES:
|
||||
holder.setSummary(ExternalSourcesDetails.getPreferenceSummary(mContext, entry));
|
||||
break;
|
||||
case LIST_TYPE_STORAGE_ACCESS:
|
||||
holder.setSummary(null);
|
||||
break;
|
||||
default:
|
||||
holder.updateSizeText(entry, mManageApplications.mInvalidSizeStr, mWhichSize);
|
||||
break;
|
||||
|
@@ -45,6 +45,7 @@ import com.android.settings.applications.ManageDomainUrls;
|
||||
import com.android.settings.applications.NotificationApps;
|
||||
import com.android.settings.applications.ProcessStatsSummary;
|
||||
import com.android.settings.applications.ProcessStatsUi;
|
||||
import com.android.settings.applications.StorageAccessDetails;
|
||||
import com.android.settings.applications.UsageAccessDetails;
|
||||
import com.android.settings.applications.VrListenerSettings;
|
||||
import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
|
||||
@@ -252,7 +253,8 @@ public class SettingsGateway {
|
||||
WebViewAppPicker.class.getName(),
|
||||
LockscreenDashboardFragment.class.getName(),
|
||||
BluetoothDeviceDetailsFragment.class.getName(),
|
||||
DataUsageList.class.getName()
|
||||
DataUsageList.class.getName(),
|
||||
StorageAccessDetails.class.getName()
|
||||
};
|
||||
|
||||
public static final String[] SETTINGS_FOR_RESTRICTED = {
|
||||
|
@@ -73,3 +73,4 @@ com.android.settings.TetherSettings
|
||||
com.android.settings.ApnEditor
|
||||
com.android.settings.UserCredentialsSettings
|
||||
com.android.settings.TestingSettings
|
||||
com.android.settings.applications.StorageAccessDetails
|
Reference in New Issue
Block a user