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

Commit ce2a8043 authored by Hai Zhang's avatar Hai Zhang
Browse files

Use a single interface for reading pre-Q and pre-S role state.

The logic to read the platform XML is moved from RoleUserState. The
logic to read legacy settings is left untouched except refactored to
return the mapping at once.

Bug: 158736025
Test: presubmit
Change-Id: Ifd1770bf869ea77b2f8a4ff8b3eae46a46cc90cf
parent ce6238e0
Loading
Loading
Loading
Loading
+0 −171
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.server.policy.role;

import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;

import com.android.internal.R;
import com.android.internal.telephony.SmsApplication;
import com.android.internal.util.CollectionUtils;
import com.android.server.LocalServices;
import com.android.server.role.LegacyRoleHolderProvider;
import com.android.server.role.RoleManagerService;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * Logic to retrieve the various legacy(pre-Q) equivalents of role holders.
 *
 * Unlike {@link RoleManagerService} this is meant to be pretty high-level to allow for depending
 * on all kinds of various systems that are historically involved in legacy role resolution,
 * e.g. {@link SmsApplication}
 *
 * @see RoleManagerService#migrateRoleIfNecessary
 */
public class LegacyRoleResolutionPolicy implements LegacyRoleHolderProvider {

    private static final boolean DEBUG = false;
    private static final String LOG_TAG = "LegacyRoleResolutionPol";

    @NonNull
    private final Context mContext;

    public LegacyRoleResolutionPolicy(@NonNull Context context) {
        mContext = context;
    }

    @NonNull
    @Override
    public List<String> getLegacyRoleHolders(@NonNull String roleName, @UserIdInt int userId) {
        switch (roleName) {
            case RoleManager.ROLE_ASSISTANT: {
                String packageName;
                String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                        Settings.Secure.ASSISTANT, userId);
                // AssistUtils was using the default assistant app if Settings.Secure.ASSISTANT is
                // null, while only an empty string means user selected "None".
                if (setting != null) {
                    if (!setting.isEmpty()) {
                        ComponentName componentName = ComponentName.unflattenFromString(setting);
                        packageName = componentName != null ? componentName.getPackageName() : null;
                    } else {
                        packageName = null;
                    }
                } else if (mContext.getPackageManager().isDeviceUpgrading()) {
                    String defaultAssistant = mContext.getString(R.string.config_defaultAssistant);
                    packageName = !TextUtils.isEmpty(defaultAssistant) ? defaultAssistant : null;
                } else {
                    packageName = null;
                }
                return CollectionUtils.singletonOrEmpty(packageName);
            }
            case RoleManager.ROLE_BROWSER: {
                PackageManagerInternal packageManagerInternal = LocalServices.getService(
                        PackageManagerInternal.class);
                String packageName = packageManagerInternal.removeLegacyDefaultBrowserPackageName(
                        userId);
                return CollectionUtils.singletonOrEmpty(packageName);
            }
            case RoleManager.ROLE_DIALER: {
                String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                        Settings.Secure.DIALER_DEFAULT_APPLICATION, userId);
                String packageName;
                if (!TextUtils.isEmpty(setting)) {
                    packageName = setting;
                } else if (mContext.getPackageManager().isDeviceUpgrading()) {
                    // DefaultDialerManager was using the default dialer app if
                    // Settings.Secure.DIALER_DEFAULT_APPLICATION is invalid.
                    // TelecomManager.getSystemDialerPackage() won't work because it might not
                    // be ready.
                    packageName = mContext.getString(R.string.config_defaultDialer);
                } else {
                    packageName = null;
                }
                return CollectionUtils.singletonOrEmpty(packageName);
            }
            case RoleManager.ROLE_SMS: {
                String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                        Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
                String packageName;
                if (!TextUtils.isEmpty(setting)) {
                    packageName = setting;
                } else if (mContext.getPackageManager().isDeviceUpgrading()) {
                    // SmsApplication was using the default SMS app if
                    // Settings.Secure.DIALER_DEFAULT_APPLICATION is invalid.
                    packageName = mContext.getString(R.string.config_defaultSms);
                } else {
                    packageName = null;
                }
                return CollectionUtils.singletonOrEmpty(packageName);
            }
            case RoleManager.ROLE_HOME: {
                PackageManager packageManager = mContext.getPackageManager();
                String packageName;
                if (packageManager.isDeviceUpgrading()) {
                    ResolveInfo resolveInfo = packageManager.resolveActivityAsUser(
                            new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),
                            PackageManager.MATCH_DEFAULT_ONLY
                                    | PackageManager.MATCH_DIRECT_BOOT_AWARE
                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
                    packageName = resolveInfo != null && resolveInfo.activityInfo != null
                            ? resolveInfo.activityInfo.packageName : null;
                    if (packageName != null && isSettingsApplication(packageName, userId)) {
                        packageName = null;
                    }
                } else {
                    packageName = null;
                }
                return CollectionUtils.singletonOrEmpty(packageName);
            }
            case RoleManager.ROLE_EMERGENCY: {
                String defaultEmergencyApp = Settings.Secure.getStringForUser(
                        mContext.getContentResolver(),
                        Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION, userId);
                return CollectionUtils.singletonOrEmpty(defaultEmergencyApp);
            }
            default: {
                Slog.e(LOG_TAG, "Don't know how to find legacy role holders for " + roleName);
                return Collections.emptyList();
            }
        }
    }

    private boolean isSettingsApplication(@NonNull String packageName, @UserIdInt int userId) {
        PackageManager packageManager = mContext.getPackageManager();
        ResolveInfo resolveInfo = packageManager.resolveActivityAsUser(new Intent(
                Settings.ACTION_SETTINGS), PackageManager.MATCH_DEFAULT_ONLY
                | PackageManager.MATCH_DIRECT_BOOT_AWARE
                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
        if (resolveInfo == null || resolveInfo.activityInfo == null) {
            return false;
        }
        return Objects.equals(packageName, resolveInfo.activityInfo.packageName);
    }
}
+291 −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.server.policy.role;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.os.Environment;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Xml;

import com.android.internal.R;
import com.android.server.LocalServices;
import com.android.server.role.LegacyRoleStateProvider;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * Implementation to provide legacy role state.
 */
public class LegacyRoleStateProviderImpl implements LegacyRoleStateProvider {
    private static final String LOG_TAG = "LegacyRoleState";

    private static final String ROLES_FILE_NAME = "roles.xml";

    private static final String TAG_ROLES = "roles";
    private static final String TAG_ROLE = "role";
    private static final String TAG_HOLDER = "holder";
    private static final String ATTRIBUTE_NAME = "name";

    @NonNull
    private final Context mContext;

    public LegacyRoleStateProviderImpl(@NonNull Context context) {
        mContext = context;
    }

    @NonNull
    @Override
    public Map<String, Set<String>> getLegacyRoleState(@UserIdInt int userId) {
        Map<String, Set<String>> roles = readFile(userId);
        if (roles == null) {
            roles = readFromLegacySettings(userId);
        }
        return roles;
    }

    @Nullable
    private Map<String, Set<String>> readFile(@UserIdInt int userId) {
        File file = getFile(userId);
        try (FileInputStream in = new AtomicFile(file).openRead()) {
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(in, null);
            Map<String, Set<String>> roles = parseXml(parser);
            Slog.i(LOG_TAG, "Read legacy roles.xml successfully");
            return roles;
        } catch (FileNotFoundException e) {
            Slog.i(LOG_TAG, "Legacy roles.xml not found");
            return null;
        } catch (XmlPullParserException | IOException e) {
            Slog.wtf(LOG_TAG, "Failed to parse legacy roles.xml: " + file, e);
            return null;
        }
    }

    @NonNull
    private Map<String, Set<String>> parseXml(@NonNull XmlPullParser parser) throws IOException,
            XmlPullParserException {
        int type;
        int depth;
        int innerDepth = parser.getDepth() + 1;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
                continue;
            }

            if (parser.getName().equals(TAG_ROLES)) {
                return parseRoles(parser);
            }
        }

        throw new IOException("Missing <" + TAG_ROLES + "> in roles.xml");
    }

    @NonNull
    private Map<String, Set<String>> parseRoles(@NonNull XmlPullParser parser) throws IOException,
            XmlPullParserException {
        Map<String, Set<String>> roles = new ArrayMap<>();

        int type;
        int depth;
        int innerDepth = parser.getDepth() + 1;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
                continue;
            }

            if (parser.getName().equals(TAG_ROLE)) {
                String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
                Set<String> roleHolders = parseRoleHoldersLocked(parser);
                roles.put(roleName, roleHolders);
            }
        }

        return roles;
    }

    @NonNull
    private Set<String> parseRoleHoldersLocked(@NonNull XmlPullParser parser)
            throws IOException, XmlPullParserException {
        Set<String> roleHolders = new ArraySet<>();

        int type;
        int depth;
        int innerDepth = parser.getDepth() + 1;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
                continue;
            }

            if (parser.getName().equals(TAG_HOLDER)) {
                String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME);
                roleHolders.add(roleHolder);
            }
        }

        return roleHolders;
    }

    @NonNull
    private static File getFile(@UserIdInt int userId) {
        return new File(Environment.getUserSystemDirectory(userId), ROLES_FILE_NAME);
    }

    @NonNull
    private Map<String, Set<String>> readFromLegacySettings(@UserIdInt int userId) {
        Map<String, Set<String>> roles = new ArrayMap<>();

        // Assistant
        ContentResolver contentResolver = mContext.getContentResolver();
        String assistantSetting = Settings.Secure.getStringForUser(contentResolver,
                Settings.Secure.ASSISTANT, userId);
        PackageManager packageManager = mContext.getPackageManager();
        String assistantPackageName;
        // AssistUtils was using the default assistant app if Settings.Secure.ASSISTANT is
        // null, while only an empty string means user selected "None".
        if (assistantSetting != null) {
            if (!assistantSetting.isEmpty()) {
                ComponentName componentName = ComponentName.unflattenFromString(assistantSetting);
                assistantPackageName = componentName != null ? componentName.getPackageName()
                        : null;
            } else {
                assistantPackageName = null;
            }
        } else if (packageManager.isDeviceUpgrading()) {
            String defaultAssistant = mContext.getString(R.string.config_defaultAssistant);
            assistantPackageName = !TextUtils.isEmpty(defaultAssistant) ? defaultAssistant : null;
        } else {
            assistantPackageName = null;
        }
        if (assistantPackageName != null) {
            roles.put(RoleManager.ROLE_ASSISTANT, Collections.singleton(assistantPackageName));
        }

        // Browser
        PackageManagerInternal packageManagerInternal = LocalServices.getService(
                PackageManagerInternal.class);
        String browserPackageName = packageManagerInternal.removeLegacyDefaultBrowserPackageName(
                userId);
        if (browserPackageName != null) {
            roles.put(RoleManager.ROLE_BROWSER, Collections.singleton(browserPackageName));
        }

        // Dialer
        String dialerSetting = Settings.Secure.getStringForUser(contentResolver,
                Settings.Secure.DIALER_DEFAULT_APPLICATION, userId);
        String dialerPackageName;
        if (!TextUtils.isEmpty(dialerSetting)) {
            dialerPackageName = dialerSetting;
        } else if (packageManager.isDeviceUpgrading()) {
            // DefaultDialerManager was using the default dialer app if
            // Settings.Secure.DIALER_DEFAULT_APPLICATION is invalid.
            // TelecomManager.getSystemDialerPackage() won't work because it might not
            // be ready.
            dialerPackageName = mContext.getString(R.string.config_defaultDialer);
        } else {
            dialerPackageName = null;
        }
        if (dialerPackageName != null) {
            roles.put(RoleManager.ROLE_DIALER, Collections.singleton(dialerPackageName));
        }

        // SMS
        String smsSetting = Settings.Secure.getStringForUser(contentResolver,
                Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
        String smsPackageName;
        if (!TextUtils.isEmpty(smsSetting)) {
            smsPackageName = smsSetting;
        } else if (mContext.getPackageManager().isDeviceUpgrading()) {
            // SmsApplication was using the default SMS app if
            // Settings.Secure.DIALER_DEFAULT_APPLICATION is invalid.
            smsPackageName = mContext.getString(R.string.config_defaultSms);
        } else {
            smsPackageName = null;
        }
        if (smsPackageName != null) {
            roles.put(RoleManager.ROLE_SMS, Collections.singleton(smsPackageName));
        }

        // Home
        String homePackageName;
        if (packageManager.isDeviceUpgrading()) {
            ResolveInfo resolveInfo = packageManager.resolveActivityAsUser(
                    new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),
                    PackageManager.MATCH_DEFAULT_ONLY
                            | PackageManager.MATCH_DIRECT_BOOT_AWARE
                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
            homePackageName = resolveInfo != null && resolveInfo.activityInfo != null
                    ? resolveInfo.activityInfo.packageName : null;
            if (homePackageName != null && isSettingsApplication(homePackageName, userId)) {
                homePackageName = null;
            }
        } else {
            homePackageName = null;
        }
        if (homePackageName != null) {
            roles.put(RoleManager.ROLE_HOME, Collections.singleton(homePackageName));
        }

        // Emergency
        String emergencyPackageName = Settings.Secure.getStringForUser(contentResolver,
                Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION, userId);
        if (emergencyPackageName != null) {
            roles.put(RoleManager.ROLE_EMERGENCY, Collections.singleton(emergencyPackageName));
        }

        return roles;
    }

    private boolean isSettingsApplication(@NonNull String packageName, @UserIdInt int userId) {
        PackageManager packageManager = mContext.getPackageManager();
        ResolveInfo resolveInfo = packageManager.resolveActivityAsUser(new Intent(
                Settings.ACTION_SETTINGS), PackageManager.MATCH_DEFAULT_ONLY
                | PackageManager.MATCH_DIRECT_BOOT_AWARE
                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
        if (resolveInfo == null || resolveInfo.activityInfo == null) {
            return false;
        }
        return Objects.equals(packageName, resolveInfo.activityInfo.packageName);
    }
}
+13 −10
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 * Copyright (C) 2021 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.
@@ -19,22 +19,25 @@ package com.android.server.role;
import android.annotation.NonNull;
import android.annotation.UserIdInt;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A provider for migrating legacy "role"s to their actual role implementation.
 * Provider for legacy role state.
 * <p>
 * The role state may come from two sources, either the different pre-role default app settings, or
 * the pre-modularization roles.xml file stored in platform.
 *
 * @hide
 */
//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface LegacyRoleHolderProvider {
public interface LegacyRoleStateProvider {
    /**
     * Get the list of holders of a legacy "role" before its actual role is introduced.
     * <p>
     * This method will only be called for the first time a role is made available in the platform.
     * Get the legacy role state stored in the platform.
     *
     * @param roleName the name of the role
     * @param userId the user ID
     * @return a list of holders for the given role
     * @return a mapping of role name to its set of holders
     */
    @NonNull
    List<String> getLegacyRoleHolders(@NonNull String roleName, @UserIdInt int userId);
    Map<String, Set<String>> getLegacyRoleState(@UserIdInt int userId);
}
+4 −31
Original line number Diff line number Diff line
@@ -103,7 +103,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
    private final Object mLock = new Object();

    @NonNull
    private final LegacyRoleHolderProvider mLegacyRoleHolderProvider;
    private final LegacyRoleStateProvider mLegacyRoleStateProvider;

    /**
     * Maps user id to its state.
@@ -139,10 +139,10 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
            new SparseArray<>();

    public RoleManagerService(@NonNull Context context,
            @NonNull LegacyRoleHolderProvider legacyRoleHolderProvider) {
            @NonNull LegacyRoleStateProvider legacyRoleStateProvider) {
        super(context);

        mLegacyRoleHolderProvider = legacyRoleHolderProvider;
        mLegacyRoleStateProvider = legacyRoleStateProvider;

        RoleControllerManager.initializeRemoteServiceComponentName(context);

@@ -241,16 +241,6 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
            return AndroidFuture.completedFuture(null);
        }

        //TODO gradually add more role migrations statements here for remaining roles
        // Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders
        // for a given role before adding a migration statement for it here
        maybeMigrateRole(RoleManager.ROLE_ASSISTANT, userId);
        maybeMigrateRole(RoleManager.ROLE_BROWSER, userId);
        maybeMigrateRole(RoleManager.ROLE_DIALER, userId);
        maybeMigrateRole(RoleManager.ROLE_SMS, userId);
        maybeMigrateRole(RoleManager.ROLE_EMERGENCY, userId);
        maybeMigrateRole(RoleManager.ROLE_HOME, userId);

        // Some package state has changed, so grant default roles again.
        Slog.i(LOG_TAG, "Granting default roles...");
        AndroidFuture<Void> future = new AndroidFuture<>();
@@ -266,23 +256,6 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
        return future;
    }

    private void maybeMigrateRole(String role, @UserIdInt int userId) {
        // Any role for which we have a record are already migrated
        RoleUserState userState = getOrCreateUserState(userId);
        if (!userState.isRoleAvailable(role)) {
            List<String> roleHolders = mLegacyRoleHolderProvider.getLegacyRoleHolders(role, userId);
            if (roleHolders.isEmpty()) {
                return;
            }
            Slog.i(LOG_TAG, "Migrating " + role + ", legacy holders: " + roleHolders);
            userState.addRoleName(role);
            int size = roleHolders.size();
            for (int i = 0; i < size; i++) {
                userState.addRoleHolder(role, roleHolders.get(i));
            }
        }
    }

    @Nullable
    private String computePackageStateHash(@UserIdInt int userId) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
@@ -327,7 +300,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
        synchronized (mLock) {
            RoleUserState userState = mUserStates.get(userId);
            if (userState == null) {
                userState = new RoleUserState(userId, this);
                userState = new RoleUserState(userId, mLegacyRoleStateProvider, this);
                mUserStates.put(userId, userState);
            }
            return userState;
+19 −112

File changed.

Preview size limit exceeded, changes collapsed.

Loading