1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.settings.applications;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.PackageManager;
23import android.content.pm.ResolveInfo;
24import android.os.UserHandle;
25import android.provider.Settings;
26import android.service.voice.VoiceInteractionService;
27import android.service.voice.VoiceInteractionServiceInfo;
28import android.speech.RecognitionService;
29import android.util.AttributeSet;
30import android.util.Log;
31
32import com.android.internal.app.AssistUtils;
33import com.android.settings.AppListPreferenceWithSettings;
34import com.android.settings.R;
35
36import java.util.ArrayList;
37import java.util.List;
38
39public class DefaultAssistPreference extends AppListPreferenceWithSettings {
40
41    private static final String TAG = DefaultAssistPreference.class.getSimpleName();
42
43    private final List<Info> mAvailableAssistants = new ArrayList<>();
44
45    private final AssistUtils mAssistUtils;
46
47    public DefaultAssistPreference(Context context, AttributeSet attrs) {
48        super(context, attrs);
49        setShowItemNone(true);
50        setDialogTitle(R.string.choose_assist_title);
51        mAssistUtils = new AssistUtils(context);
52    }
53
54    @Override
55    protected boolean persistString(String value) {
56        final Info info = findAssistantByPackageName(value);
57        if (info == null) {
58            setAssistNone();
59            return true;
60        }
61
62        if (info.isVoiceInteractionService()) {
63            setAssistService(info);
64        } else {
65            setAssistActivity(info);
66        }
67        return true;
68    }
69
70    private void setAssistNone() {
71        Settings.Secure.putString(getContext().getContentResolver(),
72                Settings.Secure.ASSISTANT, ITEM_NONE_VALUE);
73        Settings.Secure.putString(getContext().getContentResolver(),
74                Settings.Secure.VOICE_INTERACTION_SERVICE, "");
75        Settings.Secure.putString(getContext().getContentResolver(),
76                Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer());
77
78        setSummary(getContext().getText(R.string.default_assist_none));
79        setSettingsComponent(null);
80    }
81
82    private void setAssistService(Info serviceInfo) {
83        final String serviceComponentName = serviceInfo.component.flattenToShortString();
84        final String serviceRecognizerName = new ComponentName(
85                serviceInfo.component.getPackageName(),
86                serviceInfo.voiceInteractionServiceInfo.getRecognitionService())
87                .flattenToShortString();
88
89        Settings.Secure.putString(getContext().getContentResolver(),
90                Settings.Secure.ASSISTANT, serviceComponentName);
91        Settings.Secure.putString(getContext().getContentResolver(),
92                Settings.Secure.VOICE_INTERACTION_SERVICE, serviceComponentName);
93        Settings.Secure.putString(getContext().getContentResolver(),
94                Settings.Secure.VOICE_RECOGNITION_SERVICE, serviceRecognizerName);
95
96        setSummary(getEntry());
97        final String settingsActivity =
98                serviceInfo.voiceInteractionServiceInfo.getSettingsActivity();
99        setSettingsComponent(settingsActivity == null ?
100                null :
101                new ComponentName(serviceInfo.component.getPackageName(), settingsActivity));
102    }
103
104    private void setAssistActivity(Info activityInfo) {
105        Settings.Secure.putString(getContext().getContentResolver(),
106                Settings.Secure.ASSISTANT, activityInfo.component.flattenToShortString());
107        Settings.Secure.putString(getContext().getContentResolver(),
108                Settings.Secure.VOICE_INTERACTION_SERVICE, "");
109        Settings.Secure.putString(getContext().getContentResolver(),
110                Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer());
111
112        setSummary(getEntry());
113        setSettingsComponent(null);
114    }
115
116    private String getDefaultRecognizer() {
117        ResolveInfo resolveInfo = getContext().getPackageManager().resolveService(
118                new Intent(RecognitionService.SERVICE_INTERFACE),
119                PackageManager.GET_META_DATA);
120        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
121            Log.w(TAG, "Unable to resolve default voice recognition service.");
122            return "";
123        }
124
125        return new ComponentName(resolveInfo.serviceInfo.packageName,
126                resolveInfo.serviceInfo.name).flattenToShortString();
127    }
128
129    private Info findAssistantByPackageName(String packageName) {
130        for (int i = 0; i < mAvailableAssistants.size(); ++i) {
131            Info info = mAvailableAssistants.get(i);
132            if (info.component.getPackageName().equals(packageName)) {
133                return info;
134            }
135        }
136        return null;
137    }
138
139    private void addAssistServices() {
140        PackageManager pm = getContext().getPackageManager();
141
142        List<ResolveInfo> services = pm.queryIntentServices(
143                new Intent(VoiceInteractionService.SERVICE_INTERFACE),
144                PackageManager.GET_META_DATA);
145        for (int i = 0; i < services.size(); ++i) {
146            ResolveInfo resolveInfo = services.get(i);
147            VoiceInteractionServiceInfo voiceInteractionServiceInfo =
148                    new VoiceInteractionServiceInfo(pm, resolveInfo.serviceInfo);
149            if (!voiceInteractionServiceInfo.getSupportsAssist()) {
150                continue;
151            }
152
153            mAvailableAssistants.add(new Info(
154                    new ComponentName(resolveInfo.serviceInfo.packageName,
155                                      resolveInfo.serviceInfo.name),
156                    voiceInteractionServiceInfo));
157        }
158    }
159
160    private void addAssistActivities() {
161        PackageManager pm = getContext().getPackageManager();
162
163        List<ResolveInfo> activities = pm.queryIntentActivities(
164                new Intent(Intent.ACTION_ASSIST),
165                PackageManager.MATCH_DEFAULT_ONLY);
166        for (int i = 0; i < activities.size(); ++i) {
167            ResolveInfo resolveInfo = activities.get(i);
168            mAvailableAssistants.add(new Info(
169                    new ComponentName(resolveInfo.activityInfo.packageName,
170                                      resolveInfo.activityInfo.name)));
171        }
172    }
173
174    public ComponentName getCurrentAssist() {
175        return mAssistUtils.getAssistComponentForUser(UserHandle.myUserId());
176    }
177
178    public void refreshAssistApps() {
179        mAvailableAssistants.clear();
180        addAssistServices();
181        addAssistActivities();
182
183        List<String> packages = new ArrayList<>();
184        for (int i = 0; i < mAvailableAssistants.size(); ++i) {
185            String packageName = mAvailableAssistants.get(i).component.getPackageName();
186            if (packages.contains(packageName)) {
187                // A service appears before an activity thus overrides it if from the same package.
188                continue;
189            }
190            packages.add(packageName);
191        }
192
193        ComponentName currentAssist = getCurrentAssist();
194        setPackageNames(packages.toArray(new String[packages.size()]),
195                currentAssist == null ? null : currentAssist.getPackageName());
196    }
197
198    private static class Info {
199        public final ComponentName component;
200        public final VoiceInteractionServiceInfo voiceInteractionServiceInfo;
201
202        Info(ComponentName component) {
203            this.component = component;
204            this.voiceInteractionServiceInfo = null;
205        }
206
207        Info(ComponentName component, VoiceInteractionServiceInfo voiceInteractionServiceInfo) {
208            this.component = component;
209            this.voiceInteractionServiceInfo = voiceInteractionServiceInfo;
210        }
211
212        public boolean isVoiceInteractionService() {
213            return voiceInteractionServiceInfo != null;
214        }
215    }
216}
217