• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.tv.settings;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.content.pm.PackageManager;
22 import android.content.res.Resources;
23 import android.database.ContentObserver;
24 import android.database.Cursor;
25 import android.net.Uri;
26 import android.os.AsyncTask;
27 import android.support.v14.preference.SwitchPreference;
28 import android.support.v7.preference.Preference;
29 import android.text.TextUtils;
30 import android.util.Log;
31 
32 import com.android.settingslib.core.AbstractPreferenceController;
33 
34 /**
35  * Controller for the hotword switch preference.
36  */
37 public class HotwordSwitchController extends AbstractPreferenceController {
38 
39     private static final String TAG = "HotwordController";
40     private static final Uri URI = Uri.parse("content://com.google.android.katniss.search."
41             + "searchapi.VoiceInteractionProvider/sharedvalue");
42     static final String ASSISTANT_PGK_NAME = "com.google.android.katniss";
43     static final String ACTION_HOTWORD_ENABLE =
44             "com.google.android.assistant.HOTWORD_ENABLE";
45     static final String ACTION_HOTWORD_DISABLE =
46             "com.google.android.assistant.HOTWORD_DISABLE";
47 
48     static final String KEY_HOTWORD_SWITCH = "hotword_switch";
49 
50     /** Listen to hotword state events. */
51     public interface HotwordStateListener {
52         /** hotword state has changed */
onHotwordStateChanged()53         void onHotwordStateChanged();
54         /** request to enable hotwording */
onHotwordEnable()55         void onHotwordEnable();
56         /** request to disable hotwording */
onHotwordDisable()57         void onHotwordDisable();
58     }
59 
60     private ContentObserver mHotwordSwitchObserver = new ContentObserver(null) {
61         @Override
62         public void onChange(boolean selfChange) {
63             onChange(selfChange, null);
64         }
65 
66         @Override
67         public void onChange(boolean selfChange, Uri uri) {
68             new HotwordLoader().execute();
69         }
70     };
71 
72     private static class HotwordState {
73         private boolean mHotwordEnabled;
74         private boolean mHotwordSwitchVisible;
75         private boolean mHotwordSwitchDisabled;
76         private String mHotwordSwitchTitle;
77         private String mHotwordSwitchDescription;
78     }
79 
80     /**
81      * Task to retrieve state of the hotword switch from a content provider.
82      */
83     private class HotwordLoader extends AsyncTask<Void, Void, HotwordState> {
84 
85         @Override
doInBackground(Void... voids)86         protected HotwordState doInBackground(Void... voids) {
87             HotwordState hotwordState = new HotwordState();
88             Context context = mContext.getApplicationContext();
89             try (Cursor cursor = context.getContentResolver().query(URI, null, null, null,
90                     null, null)) {
91                 if (cursor != null) {
92                     int idxKey = cursor.getColumnIndex("key");
93                     int idxValue = cursor.getColumnIndex("value");
94                     if (idxKey < 0 || idxValue < 0) {
95                         return null;
96                     }
97                     while (cursor.moveToNext()) {
98                         String key = cursor.getString(idxKey);
99                         String value = cursor.getString(idxValue);
100                         if (key == null || value == null) {
101                             continue;
102                         }
103                         try {
104                             switch (key) {
105                                 case "is_listening_for_hotword":
106                                     hotwordState.mHotwordEnabled = Integer.valueOf(value) == 1;
107                                     break;
108                                 case "is_hotword_switch_visible":
109                                     hotwordState.mHotwordSwitchVisible =
110                                             Integer.valueOf(value) == 1;
111                                     break;
112                                 case "is_hotword_switch_disabled":
113                                     hotwordState.mHotwordSwitchDisabled =
114                                             Integer.valueOf(value) == 1;
115                                     break;
116                                 case "hotword_switch_title":
117                                     hotwordState.mHotwordSwitchTitle = getLocalizedStringResource(
118                                             value, mContext.getString(R.string.hotwording_title));
119                                     break;
120                                 case "hotword_switch_description":
121                                     hotwordState.mHotwordSwitchDescription =
122                                             getLocalizedStringResource(value, null);
123                                     break;
124                                 default:
125                             }
126                         } catch (NumberFormatException e) {
127                             Log.w(TAG, "Invalid value.", e);
128                         }
129                     }
130                     return hotwordState;
131                 }
132             } catch (Exception e) {
133                 Log.e(TAG, "Exception loading hotword state.", e);
134             }
135             return null;
136         }
137 
138         @Override
onPostExecute(HotwordState hotwordState)139         protected void onPostExecute(HotwordState hotwordState) {
140             if (hotwordState != null) {
141                 mHotwordState = hotwordState;
142             }
143             mHotwordStateListener.onHotwordStateChanged();
144         }
145     }
146 
147     private HotwordStateListener mHotwordStateListener = null;
148     private HotwordState mHotwordState = new HotwordState();
149 
HotwordSwitchController(Context context)150     public HotwordSwitchController(Context context) {
151         super(context);
152     }
153 
154     /** Must be invoked to init controller and observe state changes. */
init(HotwordStateListener listener)155     public void init(HotwordStateListener listener) {
156         mHotwordState.mHotwordSwitchTitle = mContext.getString(R.string.hotwording_title);
157         mHotwordStateListener = listener;
158         try {
159             mContext.getContentResolver().registerContentObserver(URI, true,
160                     mHotwordSwitchObserver);
161             new HotwordLoader().execute();
162         } catch (SecurityException e) {
163             Log.w(TAG, "Hotword content provider not found.", e);
164         }
165     }
166 
167     /** Must be invoked by caller to unregister receivers. */
unregister()168     public void unregister() {
169         mContext.getContentResolver().unregisterContentObserver(mHotwordSwitchObserver);
170     }
171 
172     @Override
isAvailable()173     public boolean isAvailable() {
174         return mHotwordState.mHotwordSwitchVisible;
175     }
176 
177     @Override
getPreferenceKey()178     public String getPreferenceKey() {
179         return KEY_HOTWORD_SWITCH;
180     }
181 
182     @Override
updateState(Preference preference)183     public void updateState(Preference preference) {
184         super.updateState(preference);
185         if (KEY_HOTWORD_SWITCH.equals(preference.getKey())) {
186             ((SwitchPreference) preference).setChecked(mHotwordState.mHotwordEnabled);
187             preference.setIcon(mHotwordState.mHotwordEnabled
188                     ? R.drawable.ic_mic_on : R.drawable.ic_mic_off);
189             preference.setEnabled(!mHotwordState.mHotwordSwitchDisabled);
190             preference.setTitle(mHotwordState.mHotwordSwitchTitle);
191             preference.setSummary(mHotwordState.mHotwordSwitchDescription);
192         }
193     }
194 
195     @Override
handlePreferenceTreeClick(Preference preference)196     public boolean handlePreferenceTreeClick(Preference preference) {
197         if (KEY_HOTWORD_SWITCH.equals(preference.getKey())) {
198             SwitchPreference hotwordSwitchPref = (SwitchPreference) preference;
199             if (hotwordSwitchPref.isChecked()) {
200                 hotwordSwitchPref.setChecked(false);
201                 mHotwordStateListener.onHotwordEnable();
202             } else {
203                 hotwordSwitchPref.setChecked(true);
204                 mHotwordStateListener.onHotwordDisable();
205             }
206         }
207         return super.handlePreferenceTreeClick(preference);
208     }
209 
210     /**
211      * Extracts a string resource from a given package.
212      *
213      * @param resource fully qualified resource identifier,
214      *        e.g. com.google.android.katniss:string/enable_ok_google
215      * @param defaultValue returned if resource cannot be extracted
216      */
getLocalizedStringResource(String resource, @Nullable String defaultValue)217     private String getLocalizedStringResource(String resource, @Nullable String defaultValue) {
218         if (TextUtils.isEmpty(resource)) {
219             return defaultValue;
220         }
221         try {
222             String[] parts = TextUtils.split(resource, ":");
223             if (parts.length == 0) {
224                 return defaultValue;
225             }
226             final String pkgName = parts[0];
227             Context targetContext = mContext.createPackageContext(pkgName, 0);
228             int resId = targetContext.getResources().getIdentifier(resource, null, null);
229             if (resId != 0) {
230                 return targetContext.getResources().getString(resId);
231             }
232         } catch (Resources.NotFoundException | PackageManager.NameNotFoundException
233                 | SecurityException e) {
234             Log.w(TAG, "Unable to get string resource.", e);
235         }
236         return defaultValue;
237     }
238 }
239