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

Commit 08baa641 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes I04454537,Idc94ef85

* changes:
  Use a trampoline activity to ensure only exposed to settings search.
  Extract BaseSearchIndexablesProvider.
parents 73092fb1 c965f9d1
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -102,14 +102,16 @@
            </intent-filter>
        </activity>

        <activity-alias android:targetActivity="com.android.packageinstaller.permission.ui.ManagePermissionsActivity"
                  android:name="om.android.packageinstaller.permission.ManagePermissionsActivityTrampoline">
        <activity android:name="com.android.packageinstaller.permission.ui.ManagePermissionsActivityTrampoline"
                  android:excludeFromRecents="true"
                  android:noHistory="true"
                  android:theme="@android:style/Theme.NoDisplay">
            <intent-filter android:priority="1">
                <action android:name="com.android.permissioncontroller.settingssearch.action.MANAGE_PERMISSION_APPS" />
                <action android:name="com.android.permissioncontroller.settingssearch.action.REVIEW_PERMISSION_USAGE" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity-alias>
        </activity>

        <activity android:name="com.android.packageinstaller.permission.ui.AppPermissionActivity"
                  android:configChanges="orientation|keyboardHidden|screenSize"
+7 −0
Original line number Diff line number Diff line
@@ -85,6 +85,13 @@ public class Constants {
     */
    public static final String IS_NONE_ROLE_HOLDER_SELECTED_KEY = "is_none_role_holder_selected:";

    /**
     * Key in the generic shared preferences that stores if the user manually selected the "none"
     * role holder for a role.
     */
    public static final String SEARCH_INDEXABLE_PROVIDER_PASSWORD_KEY =
            "search_indexable_provider_password";

    /**
     * Name of file containing the permissions that should be restored, but have not been restored
     * yet.
+137 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.packageinstaller.permission.service;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.provider.SearchIndexablesContract;
import android.provider.SearchIndexablesProvider;
import android.util.Log;

import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.packageinstaller.Constants;
import com.android.packageinstaller.permission.utils.Utils;

import java.util.Objects;
import java.util.UUID;

/**
 * Base class for {@link SearchIndexablesProvider} inside permission controller, which allows using
 * a password in raw data key and verifying incoming intents afterwards.
 */
public abstract class BaseSearchIndexablesProvider extends SearchIndexablesProvider {

    private static final String LOG_TAG = BaseSearchIndexablesProvider.class.getSimpleName();

    private static final String EXTRA_SETTINGS_SEARCH_KEY = ":settings:fragment_args_key";

    private static final int PASSWORD_LENGTH = 36;

    @NonNull
    private static final Object sPasswordLock = new Object();

    @Override
    public boolean onCreate() {
        return true;
    }

    @Nullable
    @Override
    public Cursor queryXmlResources(@Nullable String[] projection) {
        return new MatrixCursor(SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS);
    }

    @Nullable
    @Override
    public Cursor queryNonIndexableKeys(@Nullable String[] projection) {
        return new MatrixCursor(SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS);
    }

    @NonNull
    private static String getPassword(@NonNull Context context) {
        synchronized (sPasswordLock) {
            SharedPreferences sharedPreferences = Utils.getDeviceProtectedSharedPreferences(
                    context);
            String password = sharedPreferences.getString(
                    Constants.SEARCH_INDEXABLE_PROVIDER_PASSWORD_KEY, null);
            if (password == null) {
                password = UUID.randomUUID().toString();
                sharedPreferences.edit()
                        .putString(Constants.SEARCH_INDEXABLE_PROVIDER_PASSWORD_KEY, password)
                        .apply();
            }
            return password;
        }
    }

    /**
     * Create a unique raw data key with password.
     *
     * @param key the original key, can be retrieved later with {@link #getOriginalKey(Intent)}
     * @param context the context to use
     * @return the created raw data key
     */
    @NonNull
    protected static String createRawDataKey(@NonNull String key, @NonNull Context context) {
        return getPassword(context) + context.getPackageName() + ',' + key;
    }

    /**
     * Check if the intent contains the properties expected from an intent launched from settings
     * search.
     *
     * @param intent the intent to check
     * @param context the context to get password
     *
     * @return whether the intent is valid
     */
    @CheckResult
    public static boolean isIntentValid(@NonNull Intent intent, @NonNull Context context) {
        String key = intent.getStringExtra(EXTRA_SETTINGS_SEARCH_KEY);
        String passwordFromIntent = key.substring(0, PASSWORD_LENGTH);
        String password = getPassword(context);
        boolean verified = Objects.equals(passwordFromIntent, password);
        if (!verified) {
            Log.w(LOG_TAG, "Invalid password: " + passwordFromIntent);
        }
        return verified;
    }

    /**
     * Get the original key passed to {@link #createRawDataKey(String, Context)}. Should only be
     * called after {@link #isIntentValid(Intent, Context)}.
     *
     * @param intent the intent to get the original key
     *
     * @return the original key from the intent, or {@code null} if none
     */
    @Nullable
    public static String getOriginalKey(@NonNull Intent intent) {
        String key = intent.getStringExtra(EXTRA_SETTINGS_SEARCH_KEY);
        if (key == null) {
            return null;
        }
        int keyStart = key.indexOf(',') + 1;
        return keyStart <= key.length() ? key.substring(keyStart) : null;
    }
}
+12 −96
Original line number Diff line number Diff line
@@ -16,10 +16,7 @@

package com.android.packageinstaller.permission.service;

import static android.content.Context.MODE_PRIVATE;
import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;
import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS;
import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS;
import static android.provider.SearchIndexablesContract.RawData.COLUMN_INTENT_ACTION;
import static android.provider.SearchIndexablesContract.RawData.COLUMN_KEY;
import static android.provider.SearchIndexablesContract.RawData.COLUMN_KEYWORDS;
@@ -27,54 +24,31 @@ import static android.provider.SearchIndexablesContract.RawData.COLUMN_RANK;
import static android.provider.SearchIndexablesContract.RawData.COLUMN_TITLE;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.provider.SearchIndexablesProvider;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.packageinstaller.permission.utils.Utils;
import com.android.permissioncontroller.R;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.UUID;

public class PermissionSearchIndexablesProvider extends SearchIndexablesProvider {
/**
 * {@link android.provider.SearchIndexablesProvider} for permissions.
 */
public class PermissionSearchIndexablesProvider extends BaseSearchIndexablesProvider {
    private static final String LOG_TAG = PermissionSearchIndexablesProvider.class.getSimpleName();

    private static final String EXTRA_SETTINGS_SEARCH_KEY = ":settings:fragment_args_key";

    public static final String ACTION_MANAGE_PERMISSION_APPS =
            "com.android.permissioncontroller.settingssearch.action.MANAGE_PERMISSION_APPS";
    public static final String ACTION_REVIEW_PERMISSION_USAGE =
            "com.android.permissioncontroller.settingssearch.action.REVIEW_PERMISSION_USAGE";

    private static final String PASSWORD_FILE_NAME = "settings-search-password";
    private static final int PASSWORD_LENGTH = 36;

    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public Cursor queryXmlResources(String[] projection) {
        return new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
    }

    @Override
    public Cursor queryRawData(String[] projection) {
        String password = getPassword(getContext());

        PackageManager pm = getContext().getPackageManager();
        Context context = getContext();
        PackageManager pm = context.getPackageManager();

        List<String> permissionGroupNames = Utils.getPlatformPermissionGroups();
        MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
@@ -87,18 +61,16 @@ public class PermissionSearchIndexablesProvider extends SearchIndexablesProvider

            cursor.newRow().add(COLUMN_RANK, 0)
                    .add(COLUMN_TITLE, label)
                    .add(COLUMN_KEYWORDS, label + ", "
                            + getContext().getString(R.string.permission_search_keyword))
                    .add(COLUMN_KEY, password + getContext().getPackageName()
                            + "," + groupName)
                    .add(COLUMN_KEYWORDS, label + ", " + context.getString(
                            R.string.permission_search_keyword))
                    .add(COLUMN_KEY, createRawDataKey(groupName, context))
                    .add(COLUMN_INTENT_ACTION, ACTION_MANAGE_PERMISSION_APPS);
        }

        cursor.newRow().add(COLUMN_RANK, 0)
                .add(COLUMN_TITLE, getContext().getString(R.string.permission_usage_title))
                .add(COLUMN_KEYWORDS, getContext().getString(R.string.permission_search_keyword))
                .add(COLUMN_KEY, password + getContext().getPackageName()
                        + "," + "permissions usage")
                .add(COLUMN_TITLE, context.getString(R.string.permission_usage_title))
                .add(COLUMN_KEYWORDS, context.getString(R.string.permission_search_keyword))
                .add(COLUMN_KEY, createRawDataKey("permissions usage", context))
                .add(COLUMN_INTENT_ACTION, ACTION_REVIEW_PERMISSION_USAGE);

        return cursor;
@@ -112,60 +84,4 @@ public class PermissionSearchIndexablesProvider extends SearchIndexablesProvider
        }
        return null;
    }

    @Override
    public Cursor queryNonIndexableKeys(String[] projection) {
        return new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);
    }

    /**
     * Verify that the intent contains the properties expected from an intent launched from settings
     * search.
     *
     * @param context A context of this app
     * @param intent The intent to verify
     *
     * @return The payload of the intent or {@code null} if there no payload for the action
     */
    public static @Nullable String verifyIntent(@NonNull Context context, @NonNull Intent intent) {
        String key = intent.getStringExtra(EXTRA_SETTINGS_SEARCH_KEY);
        String passwordFromIntent = key.substring(0, PASSWORD_LENGTH);
        String password = getPassword(context);

        if (!passwordFromIntent.equals(password)) {
            throw new SecurityException("password " + passwordFromIntent + " is not valid");
        }

        switch (intent.getAction()) {
            case ACTION_MANAGE_PERMISSION_APPS:
                return key.substring(key.indexOf(",") + 1);
            case ACTION_REVIEW_PERMISSION_USAGE:
                return null;
            default:
                throw new IllegalArgumentException("Not a valid action");
        }
    }

    private static @NonNull String getPassword(@NonNull Context context) {
        try {
            try {
                try (FileInputStream passwordFile = context.openFileInput(PASSWORD_FILE_NAME)) {
                    byte[] password = new byte[PASSWORD_LENGTH];
                    passwordFile.read(password);

                    return new String(password);
                }
            } catch (FileNotFoundException e) {
                String password = UUID.randomUUID().toString();
                try (FileOutputStream passwordFile = context.openFileOutput(PASSWORD_FILE_NAME,
                        MODE_PRIVATE)) {
                    passwordFile.write(password.getBytes());
                }

                return password;
            }
        } catch (IOException e) {
            throw new IllegalStateException("Could not create new password");
        }
    }
}
+3 −13
Original line number Diff line number Diff line
@@ -18,8 +18,6 @@ package com.android.packageinstaller.permission.ui;

import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;

import static com.android.packageinstaller.permission.service.PermissionSearchIndexablesProvider.verifyIntent;

import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
@@ -30,7 +28,6 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;

import com.android.packageinstaller.DeviceUtils;
import com.android.packageinstaller.permission.service.PermissionSearchIndexablesProvider;
import com.android.packageinstaller.permission.ui.handheld.ManageStandardPermissionsFragment;
import com.android.packageinstaller.permission.ui.handheld.PermissionUsageFragment;
import com.android.packageinstaller.permission.ui.wear.AppPermissionsFragmentWear;
@@ -52,7 +49,7 @@ public final class ManagePermissionsActivity extends FragmentActivity {

        getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);

        String permissionName = null;
        String permissionName;
        switch (action) {
            case Intent.ACTION_MANAGE_PERMISSIONS:
                if (DeviceUtils.isTelevision(this)) {
@@ -64,11 +61,9 @@ public final class ManagePermissionsActivity extends FragmentActivity {
                }
                break;

            case PermissionSearchIndexablesProvider.ACTION_REVIEW_PERMISSION_USAGE:
                verifyIntent(this, getIntent());
                // fall through
            case Intent.ACTION_REVIEW_PERMISSION_USAGE: {
                if (!Utils.isPermissionsHubEnabled()) {
                    finish();
                    return;
                }

@@ -128,13 +123,8 @@ public final class ManagePermissionsActivity extends FragmentActivity {
                }
            } break;

            case PermissionSearchIndexablesProvider.ACTION_MANAGE_PERMISSION_APPS:
                permissionName = verifyIntent(this, getIntent());
                // fall through
            case Intent.ACTION_MANAGE_PERMISSION_APPS: {
                if (permissionName == null) {
                permissionName = getIntent().getStringExtra(Intent.EXTRA_PERMISSION_NAME);
                }

                if (permissionName == null) {
                    Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PERMISSION_NAME");
Loading