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

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

Merge "Assistant is now set as role."

parents c79e4a59 efae104f
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -25,8 +25,7 @@
    <com.android.settings.widget.GearPreference
        android:key="default_assist"
        android:title="@string/default_assist_title"
        android:summary="@string/summary_placeholder"
        android:fragment="com.android.settings.applications.assist.DefaultAssistPicker" />
        android:summary="@string/summary_placeholder" />

    <Preference
        android:key="gesture_assist_application"
+0 −246
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.assist;

import android.app.ActivityManager;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.provider.Settings;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionServiceInfo;
import android.speech.RecognitionService;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.VisibleForTesting;

import com.android.internal.app.AssistUtils;
import com.android.settings.R;
import com.android.settings.applications.defaultapps.DefaultAppPickerFragment;
import com.android.settingslib.applications.DefaultAppInfo;
import com.android.settingslib.widget.CandidateInfo;

import java.util.ArrayList;
import java.util.List;

public class DefaultAssistPicker extends DefaultAppPickerFragment {

    private static final String TAG = "DefaultAssistPicker";
    private static final Intent ASSIST_SERVICE_PROBE =
            new Intent(VoiceInteractionService.SERVICE_INTERFACE);
    private static final Intent ASSIST_ACTIVITY_PROBE =
            new Intent(Intent.ACTION_ASSIST);

    @VisibleForTesting
    final List<Info> mAvailableAssistants = new ArrayList<>();

    private AssistUtils mAssistUtils;
    private ActivityManager mActivityManager;

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.DEFAULT_ASSIST_PICKER;
    }

    @Override
    protected boolean shouldShowItemNone() {
        return true;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mAssistUtils = new AssistUtils(context);
        mActivityManager = context.getSystemService(ActivityManager.class);
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.default_assist_settings;
    }

    @Override
    protected List<DefaultAppInfo> getCandidates() {
        mAvailableAssistants.clear();
        addAssistServices();
        addAssistActivities();

        final List<String> packages = new ArrayList<>();
        final List<DefaultAppInfo> candidates = new ArrayList<>();
        for (Info info : mAvailableAssistants) {
            final String packageName = info.component.getPackageName();
            if (packages.contains(packageName)) {
                // A service appears before an activity thus overrides it if from the same package.
                continue;
            }
            packages.add(packageName);
            candidates.add(new DefaultAppInfo(getContext(), mPm, mUserId, info.component));
        }
        return candidates;
    }

    @Override
    protected String getDefaultKey() {
        final ComponentName cn = getCurrentAssist();
        if (cn != null) {
            return new DefaultAppInfo(getContext(), mPm, mUserId, cn).getKey();
        }
        return null;
    }

    @Override
    protected String getConfirmationMessage(CandidateInfo appInfo) {
        if (appInfo == null) {
            return null;
        }
        return getContext().getString(R.string.assistant_security_warning, appInfo.loadLabel());
    }

    @Override
    protected boolean setDefaultKey(String key) {
        if (TextUtils.isEmpty(key)) {
            setAssistNone();
            return true;
        }
        ComponentName cn = ComponentName.unflattenFromString(key);
        final Info info = findAssistantByPackageName(cn.getPackageName());
        if (info == null) {
            setAssistNone();
            return true;
        }

        if (info.isVoiceInteractionService()) {
            setAssistService(info);
        } else {
            setAssistActivity(info);
        }
        return true;
    }

    public ComponentName getCurrentAssist() {
        return mAssistUtils.getAssistComponentForUser(mUserId);
    }

    @VisibleForTesting
    void addAssistServices() {
        if (mActivityManager.isLowRamDevice()) {
            return;
        }
        final List<ResolveInfo> services = mPm.queryIntentServices(
                ASSIST_SERVICE_PROBE, PackageManager.GET_META_DATA);
        for (ResolveInfo resolveInfo : services) {
            VoiceInteractionServiceInfo voiceInteractionServiceInfo =
                    new VoiceInteractionServiceInfo(mPm, resolveInfo.serviceInfo);
            if (!voiceInteractionServiceInfo.getSupportsAssist()) {
                continue;
            }

            mAvailableAssistants.add(new Info(
                    new ComponentName(resolveInfo.serviceInfo.packageName,
                            resolveInfo.serviceInfo.name),
                    voiceInteractionServiceInfo));
        }
    }

    private void addAssistActivities() {
        final List<ResolveInfo> activities = mPm.queryIntentActivities(
                ASSIST_ACTIVITY_PROBE, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : activities) {
            mAvailableAssistants.add(new Info(
                    new ComponentName(resolveInfo.activityInfo.packageName,
                            resolveInfo.activityInfo.name)));
        }
    }

    private Info findAssistantByPackageName(String packageName) {
        for (Info info : mAvailableAssistants) {
            if (TextUtils.equals(info.component.getPackageName(), packageName)) {
                return info;
            }
        }
        return null;
    }

    private void setAssistNone() {
        Settings.Secure.putString(getContext().getContentResolver(),
                Settings.Secure.ASSISTANT, "");
        Settings.Secure.putString(getContext().getContentResolver(),
                Settings.Secure.VOICE_INTERACTION_SERVICE, "");
        Settings.Secure.putString(getContext().getContentResolver(),
                Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer());
    }

    private void setAssistService(Info serviceInfo) {
        final String serviceComponentName = serviceInfo.component.
                flattenToShortString();
        final String serviceRecognizerName = new ComponentName(
                serviceInfo.component.getPackageName(),
                serviceInfo.voiceInteractionServiceInfo.getRecognitionService())
                .flattenToShortString();

        Settings.Secure.putString(getContext().getContentResolver(),
                Settings.Secure.ASSISTANT, serviceComponentName);
        Settings.Secure.putString(getContext().getContentResolver(),
                Settings.Secure.VOICE_INTERACTION_SERVICE, serviceComponentName);
        Settings.Secure.putString(getContext().getContentResolver(),
                Settings.Secure.VOICE_RECOGNITION_SERVICE, serviceRecognizerName);
    }

    private void setAssistActivity(Info activityInfo) {
        Settings.Secure.putString(getContext().getContentResolver(),
                Settings.Secure.ASSISTANT, activityInfo.component.flattenToShortString());
        Settings.Secure.putString(getContext().getContentResolver(),
                Settings.Secure.VOICE_INTERACTION_SERVICE, "");
        Settings.Secure.putString(getContext().getContentResolver(),
                Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer());
    }

    private String getDefaultRecognizer() {
        final ResolveInfo resolveInfo = mPm.resolveService(
                new Intent(RecognitionService.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
            Log.w(TAG, "Unable to resolve default voice recognition service.");
            return "";
        }

        return new ComponentName(resolveInfo.serviceInfo.packageName,
                resolveInfo.serviceInfo.name).flattenToShortString();
    }

    static class Info {
        public final ComponentName component;
        public final VoiceInteractionServiceInfo voiceInteractionServiceInfo;

        Info(ComponentName component) {
            this.component = component;
            this.voiceInteractionServiceInfo = null;
        }

        Info(ComponentName component, VoiceInteractionServiceInfo voiceInteractionServiceInfo) {
            this.component = component;
            this.voiceInteractionServiceInfo = voiceInteractionServiceInfo;
        }

        public boolean isVoiceInteractionService() {
            return voiceInteractionServiceInfo != null;
        }
    }
}
+25 −0
Original line number Diff line number Diff line
@@ -16,15 +16,19 @@

package com.android.settings.applications.assist;

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.ResolveInfo;
import android.provider.Settings;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionServiceInfo;
import android.text.TextUtils;

import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;

import com.android.internal.app.AssistUtils;
import com.android.settings.R;
@@ -38,6 +42,7 @@ public class DefaultAssistPreferenceController extends DefaultAppPreferenceContr
    private final AssistUtils mAssistUtils;
    private final boolean mShowSetting;
    private final String mPrefKey;
    private final Intent mIntent;

    public DefaultAssistPreferenceController(Context context, String prefKey,
            boolean showSetting) {
@@ -45,6 +50,15 @@ public class DefaultAssistPreferenceController extends DefaultAppPreferenceContr
        mPrefKey = prefKey;
        mShowSetting = showSetting;
        mAssistUtils = new AssistUtils(context);

        final String packageName = mPackageManager.getPermissionControllerPackageName();
        if (packageName != null) {
            mIntent = new Intent(Intent.ACTION_MANAGE_DEFAULT_APP)
                    .setPackage(packageName)
                    .putExtra(Intent.EXTRA_ROLE_NAME, RoleManager.ROLE_ASSISTANT);
        } else {
            mIntent = null;
        }
    }

    @Override
@@ -72,6 +86,17 @@ public class DefaultAssistPreferenceController extends DefaultAppPreferenceContr
                .setComponent(new ComponentName(cn.getPackageName(), activity));
    }

    @Override
    public boolean handlePreferenceTreeClick(Preference preference) {
        if (TextUtils.equals(preference.getKey(), "default_assist")) {
            if (mIntent != null) {
                mContext.startActivity(mIntent);
            }
            return true;
        }
        return false;
    }

    @Override
    public boolean isAvailable() {
        return mContext.getResources().getBoolean(R.bool.config_show_assist_and_voice_input);
+0 −110
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.assist;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;

import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.provider.Settings;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowActivityManager;
import org.robolectric.util.ReflectionHelpers;

import java.util.ArrayList;
import java.util.List;

@RunWith(RobolectricTestRunner.class)
public class DefaultAssistPickerTest {

    private static ComponentName sTestAssist;

    @BeforeClass
    public static void beforeClass() {
        sTestAssist = new ComponentName("com.android.settings", "assist");
    }

    private Context mContext;
    private DefaultAssistPicker mPicker;
    private ShadowActivityManager mShadowActivityManager;

    @Before
    public void setUp() {
        mContext = RuntimeEnvironment.application;
        mShadowActivityManager = Shadow.extract(mContext.getSystemService(ActivityManager.class));
        mPicker = spy(new DefaultAssistPicker());
        mPicker.onAttach(mContext);
        doReturn(mContext).when(mPicker).getContext();
    }

    @Test
    public void setDefaultAppKey_shouldUpdateDefaultAssist() {
        final List<DefaultAssistPicker.Info> assistants = new ArrayList<>();
        assistants.add(new DefaultAssistPicker.Info(sTestAssist));
        ReflectionHelpers.setField(mPicker, "mAvailableAssistants", assistants);
        mPicker.setDefaultKey(sTestAssist.flattenToString());

        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
                Settings.Secure.ASSISTANT))
                .isEqualTo(sTestAssist.flattenToString());
        assertThat(mPicker.getDefaultKey()).isEqualTo(sTestAssist.flattenToString());
    }

    @Test
    public void setDefaultAppKey_noAvailableAssist_shouldClearDefaultAssist() {
        final List<DefaultAssistPicker.Info> assistants = new ArrayList<>();
        ReflectionHelpers.setField(mPicker, "mAvailableAssistants", assistants);
        mPicker.setDefaultKey(sTestAssist.flattenToString());

        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
                Settings.Secure.ASSISTANT))
                .isEmpty();
        assertThat(mPicker.getDefaultKey()).isNull();
    }

    @Test
    public void setDefaultAppKeyToNull_shouldClearDefaultAssist() {
        final List<DefaultAssistPicker.Info> assistants = new ArrayList<>();
        assistants.add(new DefaultAssistPicker.Info(sTestAssist));
        ReflectionHelpers.setField(mPicker, "mAvailableAssistants", assistants);
        mPicker.setDefaultKey(null);

        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
                Settings.Secure.ASSISTANT))
                .isEmpty();
        assertThat(mPicker.getDefaultKey()).isNull();
    }

    @Test
    public void addAssistService_lowRamDevice_shouldDoNothing() {
        mShadowActivityManager.setIsLowRamDevice(true);
        mPicker.addAssistServices();

        assertThat(mPicker.mAvailableAssistants).hasSize(0);
    }
}