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 }