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 17 package com.android.tv.settings; 18 19 import android.accounts.Account; 20 import android.accounts.AccountManager; 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.content.ActivityNotFoundException; 24 import android.content.BroadcastReceiver; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.content.pm.ApplicationInfo; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ResolveInfo; 32 import android.os.Bundle; 33 import android.service.settings.suggestions.Suggestion; 34 import android.support.annotation.VisibleForTesting; 35 import android.support.v14.preference.SwitchPreference; 36 import android.support.v7.preference.Preference; 37 import android.support.v7.preference.PreferenceCategory; 38 import android.telephony.SignalStrength; 39 import android.util.Log; 40 41 import com.android.settingslib.core.AbstractPreferenceController; 42 import com.android.settingslib.suggestions.SuggestionControllerMixin; 43 import com.android.settingslib.utils.IconCache; 44 import com.android.tv.settings.HotwordSwitchController.HotwordStateListener; 45 import com.android.tv.settings.accounts.AccountsFragment; 46 import com.android.tv.settings.connectivity.ConnectivityListener; 47 import com.android.tv.settings.suggestions.SuggestionPreference; 48 import com.android.tv.settings.system.SecurityFragment; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 import java.util.Set; 53 54 /** 55 * The fragment where all good things begin. Evil is handled elsewhere. 56 */ 57 public class MainFragment extends PreferenceControllerFragment implements 58 SuggestionControllerMixin.SuggestionControllerHost, SuggestionPreference.Callback, 59 HotwordStateListener { 60 61 private static final String TAG = "MainFragment"; 62 private static final String KEY_SUGGESTIONS_LIST = "suggestions"; 63 @VisibleForTesting 64 static final String KEY_ACCOUNTS_AND_SIGN_IN = "accounts_and_sign_in"; 65 private static final String KEY_APPLICATIONS = "applications"; 66 @VisibleForTesting 67 static final String KEY_ACCESSORIES = "remotes_and_accessories"; 68 @VisibleForTesting 69 static final String KEY_NETWORK = "network"; 70 71 @VisibleForTesting 72 static final String KEY_QUICK_SETTINGS = "quick_settings"; 73 74 @VisibleForTesting 75 ConnectivityListener mConnectivityListener; 76 @VisibleForTesting 77 PreferenceCategory mSuggestionsList; 78 private SuggestionControllerMixin mSuggestionControllerMixin; 79 @VisibleForTesting 80 IconCache mIconCache; 81 @VisibleForTesting 82 BluetoothAdapter mBtAdapter; 83 @VisibleForTesting 84 boolean mHasBtAccessories; 85 @VisibleForTesting 86 boolean mHasAccounts; 87 88 /** Controllers for the Quick Settings section. */ 89 private List<AbstractPreferenceController> mPreferenceControllers; 90 private HotwordSwitchController mHotwordSwitchController; 91 private PreferenceCategory mQuickSettingsList; 92 private SwitchPreference mHotwordSwitch; 93 94 private final BroadcastReceiver mBCMReceiver = new BroadcastReceiver() { 95 @Override 96 public void onReceive(Context context, Intent intent) { 97 updateAccessoryPref(); 98 } 99 }; 100 101 public static MainFragment newInstance() { 102 return new MainFragment(); 103 } 104 105 @Override 106 public int getMetricsCategory() { 107 // Do not log visibility. 108 return METRICS_CATEGORY_UNKNOWN; 109 } 110 111 @Override 112 protected int getPreferenceScreenResId() { 113 return R.xml.main_prefs; 114 } 115 116 @Override 117 public void onCreate(Bundle savedInstanceState) { 118 mIconCache = new IconCache(getContext()); 119 mConnectivityListener = 120 new ConnectivityListener(getContext(), this::updateWifi, getLifecycle()); 121 mBtAdapter = BluetoothAdapter.getDefaultAdapter(); 122 super.onCreate(savedInstanceState); 123 } 124 125 @Override 126 public void onDestroy() { 127 if (mHotwordSwitchController != null) { 128 mHotwordSwitchController.unregister(); 129 } 130 super.onDestroy(); 131 } 132 133 /** @return true if there is at least one available item in quick settings. */ 134 private boolean shouldShowQuickSettings() { 135 for (AbstractPreferenceController controller : mPreferenceControllers) { 136 if (controller.isAvailable()) { 137 return true; 138 } 139 } 140 return false; 141 } 142 143 private void showOrHideQuickSettings() { 144 if (shouldShowQuickSettings()) { 145 showQuickSettings(); 146 } else { 147 hideQuickSettings(); 148 } 149 } 150 151 /** Creates the quick settings category and its children. */ 152 private void showQuickSettings() { 153 if (mQuickSettingsList != null) { 154 return; 155 } 156 mQuickSettingsList = new PreferenceCategory(this.getPreferenceManager().getContext()); 157 mQuickSettingsList.setKey(KEY_QUICK_SETTINGS); 158 mQuickSettingsList.setTitle(R.string.header_category_quick_settings); 159 mQuickSettingsList.setLayoutResource(R.layout.preference_category_compact_layout); 160 mQuickSettingsList.setOrder(1); // at top, but below suggested settings 161 getPreferenceScreen().addPreference(mQuickSettingsList); 162 mHotwordSwitch = new SwitchPreference(this.getPreferenceManager().getContext()); 163 mHotwordSwitch.setKey(HotwordSwitchController.KEY_HOTWORD_SWITCH); 164 mHotwordSwitchController.updateState(mHotwordSwitch); 165 mQuickSettingsList.addPreference(mHotwordSwitch); 166 } 167 168 /** Removes the quick settings category and all its children. */ 169 private void hideQuickSettings() { 170 Preference quickSettingsPref = findPreference(KEY_QUICK_SETTINGS); 171 if (quickSettingsPref == null) { 172 return; 173 } 174 mQuickSettingsList.removeAll(); 175 getPreferenceScreen().removePreference(mQuickSettingsList); 176 mQuickSettingsList = null; 177 } 178 179 @Override 180 public void onHotwordStateChanged() { 181 if (mHotwordSwitch != null) { 182 mHotwordSwitchController.updateState(mHotwordSwitch); 183 } 184 showOrHideQuickSettings(); 185 } 186 187 @Override 188 public void onHotwordEnable() { 189 try { 190 Intent intent = new Intent(HotwordSwitchController.ACTION_HOTWORD_ENABLE); 191 intent.setPackage(HotwordSwitchController.ASSISTANT_PGK_NAME); 192 startActivityForResult(intent, 0); 193 } catch (ActivityNotFoundException e) { 194 Log.w(TAG, "Unable to find hotwording activity.", e); 195 } 196 } 197 198 @Override 199 public void onHotwordDisable() { 200 try { 201 Intent intent = new Intent(HotwordSwitchController.ACTION_HOTWORD_DISABLE); 202 intent.setPackage(HotwordSwitchController.ASSISTANT_PGK_NAME); 203 startActivityForResult(intent, 0); 204 } catch (ActivityNotFoundException e) { 205 Log.w(TAG, "Unable to find hotwording activity.", e); 206 } 207 } 208 209 @Override 210 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 211 setPreferencesFromResource(R.xml.main_prefs, null); 212 if (isRestricted()) { 213 Preference appPref = findPreference(KEY_APPLICATIONS); 214 if (appPref != null) { 215 appPref.setVisible(false); 216 } 217 Preference accountsPref = findPreference(KEY_ACCOUNTS_AND_SIGN_IN); 218 if (accountsPref != null) { 219 accountsPref.setVisible(false); 220 } 221 } 222 mHotwordSwitchController.init(this); 223 } 224 225 @Override 226 protected List<AbstractPreferenceController> onCreatePreferenceControllers(Context context) { 227 mPreferenceControllers = new ArrayList<>(1); 228 mHotwordSwitchController = new HotwordSwitchController(context); 229 mPreferenceControllers.add(mHotwordSwitchController); 230 return mPreferenceControllers; 231 } 232 233 @VisibleForTesting 234 void updateWifi() { 235 final Preference networkPref = findPreference(KEY_NETWORK); 236 if (networkPref == null) { 237 return; 238 } 239 240 if (mConnectivityListener.isCellConnected()) { 241 final int signal = mConnectivityListener.getCellSignalStrength(); 242 switch (signal) { 243 case SignalStrength.SIGNAL_STRENGTH_GREAT: 244 networkPref.setIcon(R.drawable.ic_cell_signal_4_white); 245 break; 246 case SignalStrength.SIGNAL_STRENGTH_GOOD: 247 networkPref.setIcon(R.drawable.ic_cell_signal_3_white); 248 break; 249 case SignalStrength.SIGNAL_STRENGTH_MODERATE: 250 networkPref.setIcon(R.drawable.ic_cell_signal_2_white); 251 break; 252 case SignalStrength.SIGNAL_STRENGTH_POOR: 253 networkPref.setIcon(R.drawable.ic_cell_signal_1_white); 254 break; 255 case SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN: 256 default: 257 networkPref.setIcon(R.drawable.ic_cell_signal_0_white); 258 break; 259 } 260 } else if (mConnectivityListener.isEthernetConnected()) { 261 networkPref.setIcon(R.drawable.ic_ethernet_white); 262 networkPref.setSummary(R.string.connectivity_summary_ethernet_connected); 263 } else if (mConnectivityListener.isWifiEnabledOrEnabling()) { 264 if (mConnectivityListener.isWifiConnected()) { 265 final int signal = mConnectivityListener.getWifiSignalStrength(5); 266 switch (signal) { 267 case 4: 268 networkPref.setIcon(R.drawable.ic_wifi_signal_4_white); 269 break; 270 case 3: 271 networkPref.setIcon(R.drawable.ic_wifi_signal_3_white); 272 break; 273 case 2: 274 networkPref.setIcon(R.drawable.ic_wifi_signal_2_white); 275 break; 276 case 1: 277 networkPref.setIcon(R.drawable.ic_wifi_signal_1_white); 278 break; 279 case 0: 280 default: 281 networkPref.setIcon(R.drawable.ic_wifi_signal_0_white); 282 break; 283 } 284 networkPref.setSummary(mConnectivityListener.getSsid()); 285 } else { 286 networkPref.setIcon(R.drawable.ic_wifi_not_connected); 287 networkPref.setSummary(R.string.connectivity_summary_no_network_connected); 288 } 289 } else { 290 networkPref.setIcon(R.drawable.ic_wifi_signal_off_white); 291 networkPref.setSummary(R.string.connectivity_summary_wifi_disabled); 292 } 293 } 294 295 /** 296 * Returns the ResolveInfo for the system activity that matches given intent filter or null if 297 * no such activity exists. 298 * @param context Context of the caller 299 * @param intent The intent matching the desired system app 300 * @return ResolveInfo of the matching activity or null if no match exists 301 */ 302 public static ResolveInfo systemIntentIsHandled(Context context, Intent intent) { 303 if (intent == null) { 304 return null; 305 } 306 307 final PackageManager pm = context.getPackageManager(); 308 309 for (ResolveInfo info : pm.queryIntentActivities(intent, 0)) { 310 if (info.activityInfo != null 311 && (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) 312 == ApplicationInfo.FLAG_SYSTEM) { 313 return info; 314 } 315 } 316 return null; 317 } 318 319 @Override 320 public void onSuggestionReady(List<Suggestion> data) { 321 if (data == null || data.size() == 0) { 322 if (mSuggestionsList != null) { 323 getPreferenceScreen().removePreference(mSuggestionsList); 324 mSuggestionsList = null; 325 } 326 return; 327 } 328 329 if (mSuggestionsList == null) { 330 mSuggestionsList = new PreferenceCategory(this.getPreferenceManager().getContext()); 331 mSuggestionsList.setKey(KEY_SUGGESTIONS_LIST); 332 mSuggestionsList.setTitle(R.string.header_category_suggestions); 333 mSuggestionsList.setLayoutResource(R.layout.preference_category_compact_layout); 334 mSuggestionsList.setOrder(0); // always at top 335 getPreferenceScreen().addPreference(mSuggestionsList); 336 } 337 updateSuggestionList(data); 338 } 339 340 @VisibleForTesting 341 void updateSuggestionList(List<Suggestion> suggestions) { 342 // Remove suggestions that are not in the new list. 343 for (int i = 0; i < mSuggestionsList.getPreferenceCount(); i++) { 344 SuggestionPreference pref = (SuggestionPreference) mSuggestionsList.getPreference(i); 345 boolean isInNewSuggestionList = false; 346 for (Suggestion suggestion : suggestions) { 347 if (pref.getId().equals(suggestion.getId())) { 348 isInNewSuggestionList = true; 349 break; 350 } 351 } 352 if (!isInNewSuggestionList) { 353 mSuggestionsList.removePreference(pref); 354 } 355 } 356 357 // Add suggestions that are not in the old list and update the existing suggestions. 358 for (Suggestion suggestion : suggestions) { 359 Preference curPref = findPreference( 360 SuggestionPreference.SUGGESTION_PREFERENCE_KEY + suggestion.getId()); 361 if (curPref == null) { 362 SuggestionPreference newSuggPref = new SuggestionPreference( 363 suggestion, this.getPreferenceManager().getContext(), 364 mSuggestionControllerMixin, this); 365 newSuggPref.setIcon(mIconCache.getIcon(suggestion.getIcon())); 366 newSuggPref.setTitle(suggestion.getTitle()); 367 newSuggPref.setSummary(suggestion.getSummary()); 368 mSuggestionsList.addPreference(newSuggPref); 369 } else { 370 // Even though the id of suggestion might not change, the details could change. 371 // So we need to update icon, title and summary for the suggestions. 372 curPref.setIcon(mIconCache.getIcon(suggestion.getIcon())); 373 curPref.setTitle(suggestion.getTitle()); 374 curPref.setSummary(suggestion.getSummary()); 375 } 376 } 377 } 378 379 @Override 380 public void onResume() { 381 super.onResume(); 382 updateAccountPref(); 383 updateAccessoryPref(); 384 } 385 386 private boolean isRestricted() { 387 return SecurityFragment.isRestrictedProfileInEffect(getContext()); 388 } 389 390 @VisibleForTesting 391 void updateAccessoryPref() { 392 Preference accessoryPreference = findPreference(KEY_ACCESSORIES); 393 if (mBtAdapter == null || accessoryPreference == null) { 394 return; 395 } 396 397 final Set<BluetoothDevice> bondedDevices = mBtAdapter.getBondedDevices(); 398 if (bondedDevices.size() == 0) { 399 mHasBtAccessories = false; 400 } else { 401 mHasBtAccessories = true; 402 } 403 } 404 405 @VisibleForTesting 406 void updateAccountPref() { 407 final Preference accountsPref = findPreference(KEY_ACCOUNTS_AND_SIGN_IN); 408 if (accountsPref != null && accountsPref.isVisible()) { 409 final AccountManager am = AccountManager.get(getContext()); 410 Account[] accounts = am.getAccounts(); 411 if (accounts.length == 0) { 412 mHasAccounts = false; 413 accountsPref.setIcon(R.drawable.ic_add_an_account); 414 accountsPref.setSummary(R.string.accounts_category_summary_no_account); 415 AccountsFragment.setUpAddAccountPrefIntent(accountsPref, getContext()); 416 } else { 417 mHasAccounts = true; 418 accountsPref.setIcon(R.drawable.ic_accounts_and_sign_in); 419 if (accounts.length == 1) { 420 accountsPref.setSummary(accounts[0].name); 421 } else { 422 accountsPref.setSummary(getResources().getQuantityString( 423 R.plurals.accounts_category_summary, accounts.length, accounts.length)); 424 } 425 } 426 } 427 } 428 429 @Override 430 public void onStart() { 431 super.onStart(); 432 IntentFilter btChangeFilter = new IntentFilter(); 433 btChangeFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); 434 btChangeFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 435 btChangeFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 436 getContext().registerReceiver(mBCMReceiver, btChangeFilter); 437 } 438 439 @Override 440 public void onStop() { 441 super.onStop(); 442 getContext().unregisterReceiver(mBCMReceiver); 443 } 444 445 @Override 446 public void onAttach(Context context) { 447 super.onAttach(context); 448 ComponentName componentName = new ComponentName( 449 "com.android.settings.intelligence", 450 "com.android.settings.intelligence.suggestions.SuggestionService"); 451 mSuggestionControllerMixin = new SuggestionControllerMixin( 452 context, this, getLifecycle(), componentName); 453 } 454 455 @Override 456 public boolean onPreferenceTreeClick(Preference preference) { 457 if (preference.getKey().equals(KEY_ACCOUNTS_AND_SIGN_IN) && !mHasAccounts 458 || (preference.getKey().equals(KEY_ACCESSORIES) && !mHasBtAccessories)) { 459 getContext().startActivity(preference.getIntent()); 460 return true; 461 } else { 462 return super.onPreferenceTreeClick(preference); 463 } 464 } 465 466 @Override 467 public void onSuggestionClosed(Preference preference) { 468 if (mSuggestionsList == null || mSuggestionsList.getPreferenceCount() == 0) { 469 return; 470 } else if (mSuggestionsList.getPreferenceCount() == 1) { 471 getPreferenceScreen().removePreference(mSuggestionsList); 472 } else { 473 mSuggestionsList.removePreference(preference); 474 } 475 } 476 } 477