• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.settings.language;
18 
19 import android.app.Dialog;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.UserHandle;
24 import android.os.UserManager;
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 import androidx.annotation.Nullable;
29 import androidx.preference.Preference;
30 
31 import com.android.internal.R;
32 import com.android.settings.core.BasePreferenceController;
33 import com.android.settings.dashboard.profileselector.ProfileSelectDialog;
34 import com.android.settings.dashboard.profileselector.UserAdapter;
35 
36 import java.lang.ref.WeakReference;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Optional;
40 
41 /** Controller of the On-device recognition preference. */
42 public class OnDeviceRecognitionPreferenceController extends BasePreferenceController {
43 
44     private static final String TAG = "OnDeviceRecognitionPreferenceController";
45 
46     private Optional<Intent> mIntent;
47 
48     private WeakReference<Dialog> mProfileSelectDialog = new WeakReference<>(null);
49 
OnDeviceRecognitionPreferenceController(Context context, String preferenceKey)50     public OnDeviceRecognitionPreferenceController(Context context, String preferenceKey) {
51         super(context, preferenceKey);
52     }
53 
54     @Override
getAvailabilityStatus()55     public int getAvailabilityStatus() {
56         if (mIntent == null) {
57             mIntent = Optional.ofNullable(onDeviceRecognitionIntent());
58         }
59         return mIntent.isPresent()
60                 ? AVAILABLE
61                 : CONDITIONALLY_UNAVAILABLE;
62     }
63 
64     @Override
updateState(Preference preference)65     public void updateState(Preference preference) {
66         super.updateState(preference);
67         if (mIntent != null && mIntent.isPresent()) {
68             preference.setIntent(mIntent.get());
69         }
70     }
71 
72     @Override
handlePreferenceTreeClick(Preference preference)73     public boolean handlePreferenceTreeClick(Preference preference) {
74         if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
75             return super.handlePreferenceTreeClick(preference);
76         }
77         show(preference);
78         return true;
79     }
80 
show(Preference preference)81     private void show(Preference preference) {
82         final List<UserHandle> userHandles = UserManager.get(mContext).getEnabledProfiles();
83 
84         // Only a single profile is installed. Proceed with its settings.
85         if (userHandles.size() == 1) {
86             mContext.startActivityAsUser(preference.getIntent(), userHandles.get(0));
87             return;
88         }
89 
90         // Multiple profiles are installed. Show a dialog to the user to pick one to proceed with.
91         createAndShowProfileSelectDialog(preference.getIntent(), userHandles);
92     }
93 
createProfileDialogClickCallback( Intent intent, List<UserHandle> userHandles)94     private UserAdapter.OnClickListener createProfileDialogClickCallback(
95             Intent intent, List<UserHandle> userHandles) {
96         return (int position) -> {
97             mContext.startActivityAsUser(intent, userHandles.get(position));
98             if (mProfileSelectDialog.get() != null) {
99                 mProfileSelectDialog.get().dismiss();
100             }
101         };
102     }
103 
createAndShowProfileSelectDialog(Intent intent, List<UserHandle> userHandles)104     private void createAndShowProfileSelectDialog(Intent intent, List<UserHandle> userHandles) {
105         Dialog profileSelectDialog = ProfileSelectDialog.createDialog(
106                 mContext, userHandles, createProfileDialogClickCallback(intent, userHandles));
107         mProfileSelectDialog = new WeakReference<>(profileSelectDialog);
108         profileSelectDialog.show();
109     }
110 
111     /**
112      * Create an {@link Intent} for the activity in the default on-device recognizer service if
113      * there is a properly defined speech recognition xml meta-data for that service.
114      *
115      * @return {@link Intent} if the proper activity is fount, {@code null} otherwise.
116      */
117     @Nullable
onDeviceRecognitionIntent()118     private Intent onDeviceRecognitionIntent() {
119         final String resString = mContext.getString(
120                 R.string.config_defaultOnDeviceSpeechRecognitionService);
121 
122         if (resString == null) {
123             Log.v(TAG, "No on-device recognizer, intent not created.");
124             return null;
125         }
126 
127         final ComponentName defaultOnDeviceRecognizerComponentName =
128                 ComponentName.unflattenFromString(resString);
129 
130         if (defaultOnDeviceRecognizerComponentName == null) {
131             Log.v(TAG, "Invalid on-device recognizer string format, intent not created.");
132             return null;
133         }
134 
135         final ArrayList<VoiceInputHelper.RecognizerInfo> validRecognitionServices =
136                 VoiceInputHelper.validRecognitionServices(mContext);
137 
138         if (validRecognitionServices.isEmpty()) {
139             Log.v(TAG, "No speech recognition services"
140                     + "with proper `recognition-service` meta-data found.");
141             return null;
142         }
143 
144         // Filter the recognizer services which are in the same package as the default on-device
145         // speech recognizer and have a settings activity defined in the meta-data.
146         final ArrayList<VoiceInputHelper.RecognizerInfo> validOnDeviceRecognitionServices =
147                 new ArrayList<>();
148         for (VoiceInputHelper.RecognizerInfo recognizerInfo: validRecognitionServices) {
149             if (!defaultOnDeviceRecognizerComponentName.getPackageName().equals(
150                     recognizerInfo.mService.packageName)) {
151                 Log.v(TAG, String.format("Recognition service not in the same package as the "
152                         + "default on-device recognizer: %s.",
153                         recognizerInfo.mComponentName.flattenToString()));
154             } else if (recognizerInfo.mSettings == null) {
155                 Log.v(TAG, String.format("Recognition service with no settings activity: %s.",
156                         recognizerInfo.mComponentName.flattenToString()));
157             } else {
158                 validOnDeviceRecognitionServices.add(recognizerInfo);
159                 Log.v(TAG, String.format("Recognition service in the same package as the default "
160                                 + "on-device recognizer with settings activity: %s.",
161                         recognizerInfo.mSettings.flattenToString()));
162             }
163         }
164 
165         if (validOnDeviceRecognitionServices.isEmpty()) {
166             Log.v(TAG, "No speech recognition services with proper `recognition-service` "
167                     + "meta-data found in the same package as the default on-device recognizer.");
168             return null;
169         }
170 
171         // Not more than one proper recognition services should be found in the same
172         // package as the default on-device recognizer. If that happens,
173         // the first one which passed the filter will be selected.
174         if (validOnDeviceRecognitionServices.size() > 1) {
175             Log.w(TAG, "More than one recognition services with proper `recognition-service` "
176                     + "meta-data found in the same package as the default on-device recognizer.");
177         }
178         VoiceInputHelper.RecognizerInfo chosenRecognizer = validOnDeviceRecognitionServices.get(0);
179 
180         return new Intent(Intent.ACTION_MAIN).setComponent(chosenRecognizer.mSettings);
181     }
182 }