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

Commit 4f85de48 authored by Philip P. Moltmann's avatar Philip P. Moltmann
Browse files

Add assitant role.

The role can be held by voice interaction services or by activities
that support the assist activity.

Bug: 110557011
Test: Set both kind of assistants
Change-Id: I3f75e5cfee250ef91e9c30e151bdee902d49cd7b
parent d6cdd89f
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -568,6 +568,8 @@
    <string name="role_label_call_screening">Call screening app</string>
    <!-- Label for the call companion app role. [CHAR LIMIT=30] -->
    <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] -->
    <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 Diff line number Diff line
@@ -438,4 +438,44 @@
            </service>
        </required-components>
    </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>
+187 −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.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.Collections;
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) {
        String defaultPackageName = CollectionUtils.firstOrNull(DefaultRoleHolders.get(context).get(
                role.getName()));
        if (defaultPackageName == null || !isPackageQualified(role, defaultPackageName, context)) {
            return Collections.emptyList();
        }
        return Collections.singletonList(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;
    }
}