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

Commit 1026a102 authored by Philip P. Moltmann's avatar Philip P. Moltmann Committed by Android (Google) Code Review
Browse files

Merge changes from topic "AssistantRoleHack"

* changes:
  Fall back to default assistant
  Add assitant role.
  Add getManageIntent() to RoleBehavior.
parents 09f3797a 4883f3b6
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -568,6 +568,8 @@
    <string name="role_label_call_screening">Call screening app</string>
    <string name="role_label_call_screening">Call screening app</string>
    <!-- Label for the call companion app role. [CHAR LIMIT=30] -->
    <!-- Label for the call companion app role. [CHAR LIMIT=30] -->
    <string name="role_label_call_companion">Call companion app</string>
    <string name="role_label_call_companion">Call companion app</string>
    <!-- Label for the assist app role. [CHAR LIMIT=30] -->
    <string name="role_label_assistant">Assist app</string>


    <!-- Dialog body explaining that the app just selected by the user will not work after a reboot until the user enters their credentials, such as a PIN or password. [CHAR LIMIT=NONE] -->
    <!-- Dialog body explaining that the app just selected by the user will not work after a reboot until the user enters their credentials, such as a PIN or password. [CHAR LIMIT=NONE] -->
    <string name="encryption_unaware_confirmation_message">Note: If you restart your device and have a screen lock set, this app can\u2019t start until you unlock your device.</string>
    <string name="encryption_unaware_confirmation_message">Note: If you restart your device and have a screen lock set, this app can\u2019t start until you unlock your device.</string>
+40 −0
Original line number Original line Diff line number Diff line
@@ -438,4 +438,44 @@
            </service>
            </service>
        </required-components>
        </required-components>
    </role>
    </role>

    <role
        name="android.app.role.ASSISTANT"
        behavior="AssistantRoleBehavior"
        exclusive="true"
        showNone="true"
        label="@string/role_label_assistant">
        <required-components>
            <!-- Qualified components are determined int AssistantRoleBehavior. This comment here is
                 ignored and represents just a rough description

            <any-of>
                <service permission="android.permission.BIND_VOICE_INTERACTION"
                         supportsAssist="true">
                    <intent-filter>
                        <action name="android.service.voice.VoiceInteractionService" />
                    </intent-filter>
                    <meta-data name="android.voice_interaction"
                               optional="false">
                        required tag in metadata xml: sessionService
                        required tag in metadata xml: recognitionService
                        required tag in metadata xml: supportsAssist = true
                    </meta-data>
                </service>
                <activity>
                    <intent-filter>
                        <action name="android.intent.action.ASSIST" />
                    </intent-filter>
                </activity>
            </ any-of>

            -->
        </required-components>
        <permissions>
            <!-- STOPSHIP: Allow to only enable permissions, but not grant them -->
            <permission-set name="sms" />
            <permission name="android.permission.READ_CALL_LOG" />
        </permissions>
    </role>

</roles>
</roles>
+6 −0
Original line number Original line Diff line number Diff line
@@ -63,4 +63,10 @@ public class Constants {
     */
     */
    public static final String KEY_LAST_LOCATION_ACCESS_NOTIFICATION_SHOWN =
    public static final String KEY_LAST_LOCATION_ACCESS_NOTIFICATION_SHOWN =
            "last_location_access_notification_shown";
            "last_location_access_notification_shown";

    /**
     * Key in the generic shared preferences that stores if the user manually selected the "none"
     * role holder for a role.
     */
    public static final String IS_NONE_ROLE_HOLDER_SELECTED_KEY = "is_none_role_holder_selected:";
}
}
+193 −0
Original line number Original line 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.role.model;


import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;

import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.service.voice.VoiceInteractionService;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Xml;

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

import com.android.packageinstaller.permission.utils.CollectionUtils;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Class for behavior of the assistant role.
 */
public class AssistantRoleBehavior implements RoleBehavior {
    private static final Intent ASSIST_SERVICE_PROBE =
            new Intent(VoiceInteractionService.SERVICE_INTERFACE);
    private static final Intent ASSIST_ACTIVITY_PROBE = new Intent(Intent.ACTION_ASSIST);

    @Override
    public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user,
            @NonNull Context context) {
        UserManager userManager = context.getSystemService(UserManager.class);

        return !userManager.isManagedProfile(user.getIdentifier())
                && !context.getSystemService(ActivityManager.class).isLowRamDevice();
    }

    @NonNull
    @Override
    public List<String> getDefaultHolders(@NonNull Role role, @NonNull Context context) {
        return CollectionUtils.singletonOrEmpty(getFallbackHolder(role, context));
    }

    @Nullable
    @Override
    public String getFallbackHolder(@NonNull Role role, @NonNull Context context) {
        String defaultPackageName = CollectionUtils.firstOrNull(DefaultRoleHolders.get(context).get(
                role.getName()));
        if (defaultPackageName == null || !isPackageQualified(role, defaultPackageName, context)) {
            return null;
        }

        return defaultPackageName;
    }

    @Nullable
    @Override
    public Intent getManageIntentAsUser(@NonNull Role role, @NonNull UserHandle user,
            @NonNull Context context) {
        return new Intent(Settings.ACTION_VOICE_INPUT_SETTINGS);
    }

    @Nullable
    @Override
    public List<String> getQualifyingPackagesAsUser(@NonNull Role role, @NonNull UserHandle user,
            @NonNull Context context) {
        PackageManager pm = context.getPackageManager();
        Set<String> availableAssistants = new ArraySet<>();

        List<ResolveInfo> services = pm.queryIntentServicesAsUser(ASSIST_SERVICE_PROBE,
                PackageManager.GET_META_DATA, user);

        int numServices = services.size();
        for (int i = 0; i < numServices; i++) {
            ResolveInfo service = services.get(i);

            if (isAssistantVoiceInteractionService(pm, service.serviceInfo)) {
                availableAssistants.add(service.serviceInfo.packageName);
            }
        }

        List<ResolveInfo> activities = pm.queryIntentActivitiesAsUser(ASSIST_ACTIVITY_PROBE,
                PackageManager.MATCH_DEFAULT_ONLY, user);

        int numActivities = activities.size();
        for (int i = 0; i < numActivities; i++) {
            availableAssistants.add(activities.get(i).activityInfo.packageName);
        }

        return new ArrayList<>(availableAssistants);
    }

    @Nullable
    @Override
    public Boolean isPackageQualified(@NonNull Role role, @NonNull String packageName,
            @NonNull Context context) {
        PackageManager pm = context.getPackageManager();

        Intent pkgServiceProbe = new Intent(ASSIST_SERVICE_PROBE).setPackage(packageName);
        List<ResolveInfo> services = pm.queryIntentServices(pkgServiceProbe,
                PackageManager.MATCH_DEFAULT_ONLY);

        int numServices = services.size();
        for (int i = 0; i < numServices; i++) {
            ResolveInfo service = services.get(i);

            if (isAssistantVoiceInteractionService(pm, service.serviceInfo)) {
                return true;
            }
        }

        Intent pkgActivityProbe = new Intent(ASSIST_ACTIVITY_PROBE).setPackage(packageName);
        return !pm.queryIntentActivities(pkgActivityProbe,
                PackageManager.MATCH_DEFAULT_ONLY).isEmpty();
    }

    private boolean isAssistantVoiceInteractionService(@NonNull PackageManager pm,
            @NonNull ServiceInfo si) {
        if (!android.Manifest.permission.BIND_VOICE_INTERACTION.equals(si.permission)) {
            return false;
        }

        try (XmlResourceParser parser = si.loadXmlMetaData(pm,
                VoiceInteractionService.SERVICE_META_DATA)) {
            if (parser == null) {
                return false;
            }

            int type;
            do {
                type = parser.next();
            } while (type != END_DOCUMENT && type != START_TAG);

            String sessionService = null;
            String recognitionService = null;
            boolean supportsAssist = false;

            AttributeSet attrs = Xml.asAttributeSet(parser);
            int numAttrs = attrs.getAttributeCount();
            for (int i = 0; i < numAttrs; i++) {
                switch (attrs.getAttributeNameResource(i)) {
                    case android.R.attr.sessionService:
                        sessionService = attrs.getAttributeValue(i);
                        break;
                    case android.R.attr.recognitionService:
                        recognitionService = attrs.getAttributeValue(i);
                        break;
                    case android.R.attr.supportsAssist:
                        supportsAssist = attrs.getAttributeBooleanValue(i, false);
                        break;
                }
            }

            if (sessionService == null || recognitionService == null || !supportsAssist) {
                return false;
            }
        } catch (XmlPullParserException | IOException | Resources.NotFoundException ignored) {
            return false;
        }

        return true;
    }
}
+57 −1
Original line number Original line Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.ActivityManager;
import android.app.role.RoleManager;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ApplicationInfo;
import android.os.Process;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserHandle;
@@ -30,7 +31,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.StringRes;


import com.android.packageinstaller.Constants;
import com.android.packageinstaller.role.utils.PackageUtils;
import com.android.packageinstaller.role.utils.PackageUtils;
import com.android.packageinstaller.role.utils.UserUtils;


import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collections;
@@ -229,12 +232,28 @@ public class Role {
     */
     */
    @Nullable
    @Nullable
    public String getFallbackHolder(@NonNull Context context) {
    public String getFallbackHolder(@NonNull Context context) {
        if (mBehavior != null) {
        if (mBehavior != null && !isNoneHolderSelected(context)) {
            return mBehavior.getFallbackHolder(this, context);
            return mBehavior.getFallbackHolder(this, context);
        }
        }
        return null;
        return null;
    }
    }


    /**
     * Get the {@link Intent} to manage this role, or {@code null} to use the default UI.
     *
     * @param user the user to manage this role for
     * @param context the {@code Context} to retrieve system services
     *
     * @return the {@link Intent} to manage this role, or {@code null} to use the default UI.
     */
    @Nullable
    public Intent getManageIntentAsUser(@NonNull UserHandle user, @NonNull Context context) {
        if (mBehavior != null) {
            return mBehavior.getManageIntentAsUser(this, user, context);
        }
        return null;
    }

    /**
    /**
     * Get the confirmation message for adding an application as a holder of this role.
     * Get the confirmation message for adding an application as a holder of this role.
     *
     *
@@ -482,6 +501,43 @@ public class Role {
        activityManager.killUid(applicationInfo.uid, "Permission or app op changed");
        activityManager.killUid(applicationInfo.uid, "Permission or app op changed");
    }
    }


    /**
     * Did the user selected the "none" role holder.
     *
     * @param context A context to use
     *
     * @return {@code true} iff the user selected the "none" role holder
     */
    private boolean isNoneHolderSelected(@NonNull Context context) {
        return context.getSharedPreferences(Constants.PREFERENCES_FILE, Context.MODE_PRIVATE)
                .getBoolean(Constants.IS_NONE_ROLE_HOLDER_SELECTED_KEY + mName, false);
    }

    /**
     * Indicate that the any other holder than "none" was selected.
     *
     * @param context A context to use
     * @param user the user the role belongs to
     */
    public void onHolderSelectedAsUser(@NonNull Context context, @NonNull UserHandle user) {
        UserUtils.getUserContext(context, user)
                .getSharedPreferences(Constants.PREFERENCES_FILE, Context.MODE_PRIVATE)
                .edit().remove(Constants.IS_NONE_ROLE_HOLDER_SELECTED_KEY + mName).apply();
    }

    /**
     * Indicate that the "none" role holder was selected.
     *
     * @param context A context to use
     * @param user the user the role belongs to
     */
    public void onNoneHolderSelectedAsUser(@NonNull Context context, @NonNull UserHandle user) {
        UserUtils.getUserContext(context, user)
                .getSharedPreferences(Constants.PREFERENCES_FILE, Context.MODE_PRIVATE)
                .edit().putBoolean(Constants.IS_NONE_ROLE_HOLDER_SELECTED_KEY + mName, true)
                .apply();
    }

    @Override
    @Override
    public String toString() {
    public String toString() {
        return "Role{"
        return "Role{"
Loading