• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 
17 package com.android.car.settings.applications.assist;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.content.pm.ResolveInfo;
24 import android.content.pm.ServiceInfo;
25 import android.content.res.Resources;
26 import android.content.res.TypedArray;
27 import android.content.res.XmlResourceParser;
28 import android.service.voice.VoiceInteractionService;
29 import android.service.voice.VoiceInteractionServiceInfo;
30 import android.speech.RecognitionService;
31 import android.util.AttributeSet;
32 import android.util.Xml;
33 
34 import androidx.annotation.Nullable;
35 import androidx.annotation.VisibleForTesting;
36 import androidx.collection.ArrayMap;
37 import androidx.collection.ArraySet;
38 
39 import com.android.car.settings.common.Logger;
40 
41 import org.xmlpull.v1.XmlPullParser;
42 import org.xmlpull.v1.XmlPullParserException;
43 
44 import java.io.IOException;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 
51 /**
52  * Extracts the voice interaction services and voice recognition services and converts them into
53  * {@link VoiceInteractionInfo} instances and {@link VoiceRecognitionInfo} instances.
54  */
55 public class VoiceInputInfoProvider {
56 
57     private static final Logger LOG = new Logger(VoiceInputInfoProvider.class);
58     @VisibleForTesting
59     static final Intent VOICE_INTERACTION_SERVICE_TAG = new Intent(
60             VoiceInteractionService.SERVICE_INTERFACE);
61     @VisibleForTesting
62     static final Intent VOICE_RECOGNITION_SERVICE_TAG = new Intent(
63             RecognitionService.SERVICE_INTERFACE);
64 
65     private final Context mContext;
66     private final Map<ComponentName, VoiceInputInfo> mComponentToInfoMap = new ArrayMap<>();
67     private final List<VoiceInteractionInfo> mVoiceInteractionInfoList = new ArrayList<>();
68     private final List<VoiceRecognitionInfo> mVoiceRecognitionInfoList = new ArrayList<>();
69     private final Set<ComponentName> mRecognitionServiceNames = new ArraySet<>();
70 
VoiceInputInfoProvider(Context context)71     public VoiceInputInfoProvider(Context context) {
72         mContext = context;
73 
74         loadVoiceInteractionServices();
75         loadVoiceRecognitionServices();
76     }
77 
78     /**
79      * Gets the list of voice interaction services represented as {@link VoiceInteractionInfo}
80      * instances.
81      */
getVoiceInteractionInfoList()82     public List<VoiceInteractionInfo> getVoiceInteractionInfoList() {
83         return mVoiceInteractionInfoList;
84     }
85 
86     /**
87      * Gets the list of voice recognition services represented as {@link VoiceRecognitionInfo}
88      * instances.
89      */
getVoiceRecognitionInfoList()90     public List<VoiceRecognitionInfo> getVoiceRecognitionInfoList() {
91         return mVoiceRecognitionInfoList;
92     }
93 
94     /**
95      * Returns the appropriate {@link VoiceInteractionInfo} or {@link VoiceRecognitionInfo} based on
96      * the provided {@link ComponentName}.
97      *
98      * @return {@link VoiceInputInfo} if it exists for the component name, null otherwise.
99      */
100     @Nullable
getInfoForComponent(ComponentName key)101     public VoiceInputInfo getInfoForComponent(ComponentName key) {
102         return mComponentToInfoMap.getOrDefault(key, null);
103     }
104 
loadVoiceInteractionServices()105     private void loadVoiceInteractionServices() {
106         List<ResolveInfo> mAvailableVoiceInteractionServices =
107                 mContext.getPackageManager().queryIntentServices(VOICE_INTERACTION_SERVICE_TAG,
108                         PackageManager.GET_META_DATA);
109 
110         for (ResolveInfo resolveInfo : mAvailableVoiceInteractionServices) {
111             VoiceInteractionServiceInfo interactionServiceInfo = new VoiceInteractionServiceInfo(
112                     mContext.getPackageManager(), resolveInfo.serviceInfo);
113             if (interactionServiceInfo.getParseError() != null) {
114                 LOG.w("Error in VoiceInteractionService " + resolveInfo.serviceInfo.packageName
115                         + "/" + resolveInfo.serviceInfo.name + ": "
116                         + interactionServiceInfo.getParseError());
117                 continue;
118             }
119             VoiceInteractionInfo voiceInteractionInfo = new VoiceInteractionInfo(mContext,
120                     interactionServiceInfo);
121             mVoiceInteractionInfoList.add(voiceInteractionInfo);
122             if (interactionServiceInfo.getRecognitionService() != null) {
123                 mRecognitionServiceNames.add(new ComponentName(resolveInfo.serviceInfo.packageName,
124                         interactionServiceInfo.getRecognitionService()));
125             }
126             mComponentToInfoMap.put(new ComponentName(resolveInfo.serviceInfo.packageName,
127                     resolveInfo.serviceInfo.name), voiceInteractionInfo);
128         }
129         Collections.sort(mVoiceInteractionInfoList);
130     }
131 
loadVoiceRecognitionServices()132     private void loadVoiceRecognitionServices() {
133         List<ResolveInfo> mAvailableRecognitionServices =
134                 mContext.getPackageManager().queryIntentServices(VOICE_RECOGNITION_SERVICE_TAG,
135                         PackageManager.GET_META_DATA);
136         for (ResolveInfo resolveInfo : mAvailableRecognitionServices) {
137             ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName,
138                     resolveInfo.serviceInfo.name);
139 
140             VoiceRecognitionInfo voiceRecognitionInfo = new VoiceRecognitionInfo(mContext,
141                     resolveInfo.serviceInfo);
142             mVoiceRecognitionInfoList.add(voiceRecognitionInfo);
143             mRecognitionServiceNames.add(componentName);
144             mComponentToInfoMap.put(componentName, voiceRecognitionInfo);
145         }
146         Collections.sort(mVoiceRecognitionInfoList);
147     }
148 
149     /**
150      * Base object used to represent {@link VoiceInteractionInfo} and {@link VoiceRecognitionInfo}.
151      */
152     abstract static class VoiceInputInfo implements Comparable {
153         private final Context mContext;
154         private final ServiceInfo mServiceInfo;
155 
VoiceInputInfo(Context context, ServiceInfo serviceInfo)156         VoiceInputInfo(Context context, ServiceInfo serviceInfo) {
157             mContext = context;
158             mServiceInfo = serviceInfo;
159         }
160 
getContext()161         protected Context getContext() {
162             return mContext;
163         }
164 
getServiceInfo()165         protected ServiceInfo getServiceInfo() {
166             return mServiceInfo;
167         }
168 
169         @Override
compareTo(Object o)170         public int compareTo(Object o) {
171             return getTag().toString().compareTo(((VoiceInputInfo) o).getTag().toString());
172         }
173 
174         /**
175          * Returns the {@link ComponentName} which represents the settings activity, if it exists.
176          */
177         @Nullable
getSettingsActivityComponentName()178         ComponentName getSettingsActivityComponentName() {
179             String activity = getSettingsActivity();
180             return (activity != null) ? new ComponentName(mServiceInfo.packageName, activity)
181                     : null;
182         }
183 
184         /** Returns the package name for the service represented by this {@link VoiceInputInfo}. */
getPackageName()185         String getPackageName() {
186             return mServiceInfo.packageName;
187         }
188 
189         /**
190          * Returns the component name for the service represented by this {@link VoiceInputInfo}.
191          */
getComponentName()192         ComponentName getComponentName() {
193             return new ComponentName(mServiceInfo.packageName, mServiceInfo.name);
194         }
195 
196         /**
197          * Returns the label to describe the service represented by this {@link VoiceInputInfo}.
198          */
getLabel()199         abstract CharSequence getLabel();
200 
201         /**
202          * The string representation of the settings activity for the service represented by this
203          * {@link VoiceInputInfo}.
204          */
getSettingsActivity()205         protected abstract String getSettingsActivity();
206 
207         /**
208          * Returns a tag used to determine the sort order of the {@link VoiceInputInfo} instances.
209          */
getTag()210         protected CharSequence getTag() {
211             return mServiceInfo.loadLabel(mContext.getPackageManager());
212         }
213     }
214 
215     /** An object to represent {@link VoiceInteractionService} instances. */
216     static class VoiceInteractionInfo extends VoiceInputInfo {
217         private final VoiceInteractionServiceInfo mInteractionServiceInfo;
218 
VoiceInteractionInfo(Context context, VoiceInteractionServiceInfo info)219         VoiceInteractionInfo(Context context, VoiceInteractionServiceInfo info) {
220             super(context, info.getServiceInfo());
221 
222             mInteractionServiceInfo = info;
223         }
224 
225         /** Returns the recognition service associated with this {@link VoiceInteractionService}. */
getRecognitionService()226         String getRecognitionService() {
227             return mInteractionServiceInfo.getRecognitionService();
228         }
229 
230         @Override
getSettingsActivity()231         protected String getSettingsActivity() {
232             return mInteractionServiceInfo.getSettingsActivity();
233         }
234 
235         @Override
getLabel()236         CharSequence getLabel() {
237             return getServiceInfo().applicationInfo.loadLabel(getContext().getPackageManager());
238         }
239     }
240 
241     /** An object to represent {@link RecognitionService} instances. */
242     static class VoiceRecognitionInfo extends VoiceInputInfo {
243 
VoiceRecognitionInfo(Context context, ServiceInfo serviceInfo)244         VoiceRecognitionInfo(Context context, ServiceInfo serviceInfo) {
245             super(context, serviceInfo);
246         }
247 
248         @Override
getSettingsActivity()249         protected String getSettingsActivity() {
250             return getServiceSettingsActivity(getServiceInfo());
251         }
252 
253         @Override
getLabel()254         CharSequence getLabel() {
255             return getTag();
256         }
257 
getServiceSettingsActivity(ServiceInfo serviceInfo)258         private String getServiceSettingsActivity(ServiceInfo serviceInfo) {
259             XmlResourceParser parser = null;
260             String settingActivity = null;
261             try {
262                 parser = serviceInfo.loadXmlMetaData(getContext().getPackageManager(),
263                         RecognitionService.SERVICE_META_DATA);
264                 if (parser == null) {
265                     throw new XmlPullParserException(
266                             "No " + RecognitionService.SERVICE_META_DATA + " meta-data for "
267                                     + serviceInfo.packageName);
268                 }
269 
270                 Resources res = getContext().getPackageManager().getResourcesForApplication(
271                         serviceInfo.applicationInfo);
272 
273                 AttributeSet attrs = Xml.asAttributeSet(parser);
274 
275                 int type;
276                 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
277                         && type != XmlPullParser.START_TAG) {
278                     continue;
279                 }
280 
281                 String nodeName = parser.getName();
282                 if (!"recognition-service".equals(nodeName)) {
283                     throw new XmlPullParserException(
284                             "Meta-data does not start with recognition-service tag");
285                 }
286 
287                 TypedArray array = res.obtainAttributes(attrs,
288                         com.android.internal.R.styleable.RecognitionService);
289                 settingActivity = array.getString(
290                         com.android.internal.R.styleable.RecognitionService_settingsActivity);
291                 array.recycle();
292             } catch (XmlPullParserException e) {
293                 LOG.e("error parsing recognition service meta-data", e);
294             } catch (IOException e) {
295                 LOG.e("error parsing recognition service meta-data", e);
296             } catch (PackageManager.NameNotFoundException e) {
297                 LOG.e("error parsing recognition service meta-data", e);
298             } finally {
299                 if (parser != null) parser.close();
300             }
301 
302             return settingActivity;
303         }
304     }
305 }
306