Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7a0ad2b3 authored by Felipe Leme's avatar Felipe Leme
Browse files

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
parent 0fea51b3
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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>
+12 −0
Original line number Diff line number Diff line
@@ -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>
+1 −0
Original line number Diff line number Diff line
@@ -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 */ }
+88 −0
Original line number Diff line number Diff line
/*
 * 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 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
Loading