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.developeroptions.wfd; 18 19 import android.app.settings.SettingsEnums; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.pm.PackageManager; 26 import android.database.ContentObserver; 27 import android.hardware.display.DisplayManager; 28 import android.hardware.display.WifiDisplay; 29 import android.hardware.display.WifiDisplayStatus; 30 import android.media.MediaRouter; 31 import android.media.MediaRouter.RouteInfo; 32 import android.net.Uri; 33 import android.net.wifi.WpsInfo; 34 import android.net.wifi.p2p.WifiP2pManager; 35 import android.net.wifi.p2p.WifiP2pManager.ActionListener; 36 import android.net.wifi.p2p.WifiP2pManager.Channel; 37 import android.os.Bundle; 38 import android.os.Handler; 39 import android.os.Looper; 40 import android.provider.SearchIndexableResource; 41 import android.provider.Settings; 42 import android.util.Slog; 43 import android.util.TypedValue; 44 import android.view.Menu; 45 import android.view.MenuInflater; 46 import android.view.MenuItem; 47 import android.view.View; 48 import android.view.View.OnClickListener; 49 import android.widget.Button; 50 import android.widget.EditText; 51 import android.widget.ImageView; 52 import android.widget.TextView; 53 54 import androidx.appcompat.app.AlertDialog; 55 import androidx.preference.ListPreference; 56 import androidx.preference.Preference; 57 import androidx.preference.Preference.OnPreferenceChangeListener; 58 import androidx.preference.PreferenceCategory; 59 import androidx.preference.PreferenceGroup; 60 import androidx.preference.PreferenceScreen; 61 import androidx.preference.PreferenceViewHolder; 62 import androidx.preference.SwitchPreference; 63 64 import com.android.internal.app.MediaRouteDialogPresenter; 65 import com.android.car.developeroptions.R; 66 import com.android.car.developeroptions.SettingsPreferenceFragment; 67 import com.android.car.developeroptions.dashboard.SummaryLoader; 68 import com.android.car.developeroptions.search.BaseSearchIndexProvider; 69 import com.android.car.developeroptions.search.Indexable; 70 import com.android.settingslib.search.SearchIndexable; 71 72 import java.util.ArrayList; 73 import java.util.List; 74 75 /** 76 * The Settings screen for WifiDisplay configuration and connection management. 77 * 78 * The wifi display routes are integrated together with other remote display routes 79 * from the media router. It may happen that wifi display isn't actually available 80 * on the system. In that case, the enable option will not be shown but other 81 * remote display routes will continue to be made available. 82 */ 83 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) 84 public final class WifiDisplaySettings extends SettingsPreferenceFragment implements Indexable { 85 private static final String TAG = "WifiDisplaySettings"; 86 private static final boolean DEBUG = false; 87 88 private static final int MENU_ID_ENABLE_WIFI_DISPLAY = Menu.FIRST; 89 90 private static final int CHANGE_SETTINGS = 1 << 0; 91 private static final int CHANGE_ROUTES = 1 << 1; 92 private static final int CHANGE_WIFI_DISPLAY_STATUS = 1 << 2; 93 private static final int CHANGE_ALL = -1; 94 95 private static final int ORDER_CERTIFICATION = 1; 96 private static final int ORDER_CONNECTED = 2; 97 private static final int ORDER_AVAILABLE = 3; 98 private static final int ORDER_UNAVAILABLE = 4; 99 100 private final Handler mHandler; 101 102 private MediaRouter mRouter; 103 private DisplayManager mDisplayManager; 104 105 private boolean mStarted; 106 private int mPendingChanges; 107 108 private boolean mWifiDisplayOnSetting; 109 private WifiDisplayStatus mWifiDisplayStatus; 110 111 private TextView mEmptyView; 112 113 /* certification */ 114 private boolean mWifiDisplayCertificationOn; 115 private WifiP2pManager mWifiP2pManager; 116 private Channel mWifiP2pChannel; 117 private PreferenceGroup mCertCategory; 118 private boolean mListen; 119 private boolean mAutoGO; 120 private int mWpsConfig = WpsInfo.INVALID; 121 private int mListenChannel; 122 private int mOperatingChannel; 123 WifiDisplaySettings()124 public WifiDisplaySettings() { 125 mHandler = new Handler(); 126 } 127 128 @Override getMetricsCategory()129 public int getMetricsCategory() { 130 return SettingsEnums.WFD_WIFI_DISPLAY; 131 } 132 133 @Override onCreate(Bundle icicle)134 public void onCreate(Bundle icicle) { 135 super.onCreate(icicle); 136 137 final Context context = getActivity(); 138 mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); 139 mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); 140 mWifiP2pManager = (WifiP2pManager) context.getSystemService(Context.WIFI_P2P_SERVICE); 141 mWifiP2pChannel = mWifiP2pManager.initialize(context, Looper.getMainLooper(), null); 142 143 addPreferencesFromResource(R.xml.wifi_display_settings); 144 setHasOptionsMenu(true); 145 } 146 147 @Override getHelpResource()148 public int getHelpResource() { 149 return R.string.help_url_remote_display; 150 } 151 152 @Override onActivityCreated(Bundle savedInstanceState)153 public void onActivityCreated(Bundle savedInstanceState) { 154 super.onActivityCreated(savedInstanceState); 155 156 mEmptyView = (TextView) getView().findViewById(android.R.id.empty); 157 mEmptyView.setText(R.string.wifi_display_no_devices_found); 158 setEmptyView(mEmptyView); 159 } 160 161 @Override onStart()162 public void onStart() { 163 super.onStart(); 164 mStarted = true; 165 166 final Context context = getActivity(); 167 IntentFilter filter = new IntentFilter(); 168 filter.addAction(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED); 169 context.registerReceiver(mReceiver, filter); 170 171 getContentResolver().registerContentObserver(Settings.Global.getUriFor( 172 Settings.Global.WIFI_DISPLAY_ON), false, mSettingsObserver); 173 getContentResolver().registerContentObserver(Settings.Global.getUriFor( 174 Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, mSettingsObserver); 175 getContentResolver().registerContentObserver(Settings.Global.getUriFor( 176 Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false, mSettingsObserver); 177 178 mRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mRouterCallback, 179 MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); 180 181 update(CHANGE_ALL); 182 } 183 184 @Override onStop()185 public void onStop() { 186 super.onStop(); 187 mStarted = false; 188 189 final Context context = getActivity(); 190 context.unregisterReceiver(mReceiver); 191 192 getContentResolver().unregisterContentObserver(mSettingsObserver); 193 194 mRouter.removeCallback(mRouterCallback); 195 196 unscheduleUpdate(); 197 } 198 199 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)200 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 201 if (mWifiDisplayStatus != null && mWifiDisplayStatus.getFeatureState() 202 != WifiDisplayStatus.FEATURE_STATE_UNAVAILABLE) { 203 MenuItem item = menu.add(Menu.NONE, MENU_ID_ENABLE_WIFI_DISPLAY, 0, 204 R.string.wifi_display_enable_menu_item); 205 item.setCheckable(true); 206 item.setChecked(mWifiDisplayOnSetting); 207 } 208 super.onCreateOptionsMenu(menu, inflater); 209 } 210 211 @Override onOptionsItemSelected(MenuItem item)212 public boolean onOptionsItemSelected(MenuItem item) { 213 switch (item.getItemId()) { 214 case MENU_ID_ENABLE_WIFI_DISPLAY: 215 mWifiDisplayOnSetting = !item.isChecked(); 216 item.setChecked(mWifiDisplayOnSetting); 217 Settings.Global.putInt(getContentResolver(), 218 Settings.Global.WIFI_DISPLAY_ON, mWifiDisplayOnSetting ? 1 : 0); 219 return true; 220 } 221 return super.onOptionsItemSelected(item); 222 } 223 isAvailable(Context context)224 public static boolean isAvailable(Context context) { 225 return context.getSystemService(Context.DISPLAY_SERVICE) != null 226 && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT) 227 && context.getSystemService(Context.WIFI_P2P_SERVICE) != null; 228 } 229 scheduleUpdate(int changes)230 private void scheduleUpdate(int changes) { 231 if (mStarted) { 232 if (mPendingChanges == 0) { 233 mHandler.post(mUpdateRunnable); 234 } 235 mPendingChanges |= changes; 236 } 237 } 238 unscheduleUpdate()239 private void unscheduleUpdate() { 240 if (mPendingChanges != 0) { 241 mPendingChanges = 0; 242 mHandler.removeCallbacks(mUpdateRunnable); 243 } 244 } 245 update(int changes)246 private void update(int changes) { 247 boolean invalidateOptions = false; 248 249 // Update settings. 250 if ((changes & CHANGE_SETTINGS) != 0) { 251 mWifiDisplayOnSetting = Settings.Global.getInt(getContentResolver(), 252 Settings.Global.WIFI_DISPLAY_ON, 0) != 0; 253 mWifiDisplayCertificationOn = Settings.Global.getInt(getContentResolver(), 254 Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, 0) != 0; 255 mWpsConfig = Settings.Global.getInt(getContentResolver(), 256 Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID); 257 258 // The wifi display enabled setting may have changed. 259 invalidateOptions = true; 260 } 261 262 // Update wifi display state. 263 if ((changes & CHANGE_WIFI_DISPLAY_STATUS) != 0) { 264 mWifiDisplayStatus = mDisplayManager.getWifiDisplayStatus(); 265 266 // The wifi display feature state may have changed. 267 invalidateOptions = true; 268 } 269 270 // Rebuild the routes. 271 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 272 preferenceScreen.removeAll(); 273 274 // Add all known remote display routes. 275 final int routeCount = mRouter.getRouteCount(); 276 for (int i = 0; i < routeCount; i++) { 277 MediaRouter.RouteInfo route = mRouter.getRouteAt(i); 278 if (route.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) { 279 preferenceScreen.addPreference(createRoutePreference(route)); 280 } 281 } 282 283 // Additional features for wifi display routes. 284 if (mWifiDisplayStatus != null 285 && mWifiDisplayStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) { 286 // Add all unpaired wifi displays. 287 for (WifiDisplay display : mWifiDisplayStatus.getDisplays()) { 288 if (!display.isRemembered() && display.isAvailable() 289 && !display.equals(mWifiDisplayStatus.getActiveDisplay())) { 290 preferenceScreen.addPreference(new UnpairedWifiDisplayPreference( 291 getPrefContext(), display)); 292 } 293 } 294 295 // Add the certification menu if enabled in developer options. 296 if (mWifiDisplayCertificationOn) { 297 buildCertificationMenu(preferenceScreen); 298 } 299 } 300 301 // Invalidate menu options if needed. 302 if (invalidateOptions) { 303 getActivity().invalidateOptionsMenu(); 304 } 305 } 306 createRoutePreference(MediaRouter.RouteInfo route)307 private RoutePreference createRoutePreference(MediaRouter.RouteInfo route) { 308 WifiDisplay display = findWifiDisplay(route.getDeviceAddress()); 309 if (display != null) { 310 return new WifiDisplayRoutePreference(getPrefContext(), route, display); 311 } else { 312 return new RoutePreference(getPrefContext(), route); 313 } 314 } 315 findWifiDisplay(String deviceAddress)316 private WifiDisplay findWifiDisplay(String deviceAddress) { 317 if (mWifiDisplayStatus != null && deviceAddress != null) { 318 for (WifiDisplay display : mWifiDisplayStatus.getDisplays()) { 319 if (display.getDeviceAddress().equals(deviceAddress)) { 320 return display; 321 } 322 } 323 } 324 return null; 325 } 326 buildCertificationMenu(final PreferenceScreen preferenceScreen)327 private void buildCertificationMenu(final PreferenceScreen preferenceScreen) { 328 if (mCertCategory == null) { 329 mCertCategory = new PreferenceCategory(getPrefContext()); 330 mCertCategory.setTitle(R.string.wifi_display_certification_heading); 331 mCertCategory.setOrder(ORDER_CERTIFICATION); 332 } else { 333 mCertCategory.removeAll(); 334 } 335 preferenceScreen.addPreference(mCertCategory); 336 337 // display session info if there is an active p2p session 338 if (!mWifiDisplayStatus.getSessionInfo().getGroupId().isEmpty()) { 339 Preference p = new Preference(getPrefContext()); 340 p.setTitle(R.string.wifi_display_session_info); 341 p.setSummary(mWifiDisplayStatus.getSessionInfo().toString()); 342 mCertCategory.addPreference(p); 343 344 // show buttons for Pause/Resume when a WFD session is established 345 if (mWifiDisplayStatus.getSessionInfo().getSessionId() != 0) { 346 mCertCategory.addPreference(new Preference(getPrefContext()) { 347 @Override 348 public void onBindViewHolder(PreferenceViewHolder view) { 349 super.onBindViewHolder(view); 350 351 Button b = (Button) view.findViewById(R.id.left_button); 352 b.setText(R.string.wifi_display_pause); 353 b.setOnClickListener(new OnClickListener() { 354 @Override 355 public void onClick(View v) { 356 mDisplayManager.pauseWifiDisplay(); 357 } 358 }); 359 360 b = (Button) view.findViewById(R.id.right_button); 361 b.setText(R.string.wifi_display_resume); 362 b.setOnClickListener(new OnClickListener() { 363 @Override 364 public void onClick(View v) { 365 mDisplayManager.resumeWifiDisplay(); 366 } 367 }); 368 } 369 }); 370 mCertCategory.setLayoutResource(R.layout.two_buttons_panel); 371 } 372 } 373 374 // switch for Listen Mode 375 SwitchPreference pref = new SwitchPreference(getPrefContext()) { 376 @Override 377 protected void onClick() { 378 mListen = !mListen; 379 setListenMode(mListen); 380 setChecked(mListen); 381 } 382 }; 383 pref.setTitle(R.string.wifi_display_listen_mode); 384 pref.setChecked(mListen); 385 mCertCategory.addPreference(pref); 386 387 // switch for Autonomous GO 388 pref = new SwitchPreference(getPrefContext()) { 389 @Override 390 protected void onClick() { 391 mAutoGO = !mAutoGO; 392 if (mAutoGO) { 393 startAutoGO(); 394 } else { 395 stopAutoGO(); 396 } 397 setChecked(mAutoGO); 398 } 399 }; 400 pref.setTitle(R.string.wifi_display_autonomous_go); 401 pref.setChecked(mAutoGO); 402 mCertCategory.addPreference(pref); 403 404 // Drop down list for choosing WPS method (PBC/KEYPAD/DISPLAY) 405 ListPreference lp = new ListPreference(getPrefContext()); 406 lp.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 407 @Override 408 public boolean onPreferenceChange(Preference preference, Object value) { 409 int wpsConfig = Integer.parseInt((String) value); 410 if (wpsConfig != mWpsConfig) { 411 mWpsConfig = wpsConfig; 412 getActivity().invalidateOptionsMenu(); 413 Settings.Global.putInt(getActivity().getContentResolver(), 414 Settings.Global.WIFI_DISPLAY_WPS_CONFIG, mWpsConfig); 415 } 416 return true; 417 } 418 }); 419 mWpsConfig = Settings.Global.getInt(getActivity().getContentResolver(), 420 Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID); 421 String[] wpsEntries = {"Default", "PBC", "KEYPAD", "DISPLAY"}; 422 String[] wpsValues = { 423 "" + WpsInfo.INVALID, 424 "" + WpsInfo.PBC, 425 "" + WpsInfo.KEYPAD, 426 "" + WpsInfo.DISPLAY}; 427 lp.setKey("wps"); 428 lp.setTitle(R.string.wifi_display_wps_config); 429 lp.setEntries(wpsEntries); 430 lp.setEntryValues(wpsValues); 431 lp.setValue("" + mWpsConfig); 432 lp.setSummary("%1$s"); 433 mCertCategory.addPreference(lp); 434 435 // Drop down list for choosing listen channel 436 lp = new ListPreference(getPrefContext()); 437 lp.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 438 @Override 439 public boolean onPreferenceChange(Preference preference, Object value) { 440 int channel = Integer.parseInt((String) value); 441 if (channel != mListenChannel) { 442 mListenChannel = channel; 443 getActivity().invalidateOptionsMenu(); 444 setWifiP2pChannels(mListenChannel, mOperatingChannel); 445 } 446 return true; 447 } 448 }); 449 String[] lcEntries = {"Auto", "1", "6", "11"}; 450 String[] lcValues = {"0", "1", "6", "11"}; 451 lp.setKey("listening_channel"); 452 lp.setTitle(R.string.wifi_display_listen_channel); 453 lp.setEntries(lcEntries); 454 lp.setEntryValues(lcValues); 455 lp.setValue("" + mListenChannel); 456 lp.setSummary("%1$s"); 457 mCertCategory.addPreference(lp); 458 459 // Drop down list for choosing operating channel 460 lp = new ListPreference(getPrefContext()); 461 lp.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 462 @Override 463 public boolean onPreferenceChange(Preference preference, Object value) { 464 int channel = Integer.parseInt((String) value); 465 if (channel != mOperatingChannel) { 466 mOperatingChannel = channel; 467 getActivity().invalidateOptionsMenu(); 468 setWifiP2pChannels(mListenChannel, mOperatingChannel); 469 } 470 return true; 471 } 472 }); 473 String[] ocEntries = {"Auto", "1", "6", "11", "36"}; 474 String[] ocValues = {"0", "1", "6", "11", "36"}; 475 lp.setKey("operating_channel"); 476 lp.setTitle(R.string.wifi_display_operating_channel); 477 lp.setEntries(ocEntries); 478 lp.setEntryValues(ocValues); 479 lp.setValue("" + mOperatingChannel); 480 lp.setSummary("%1$s"); 481 mCertCategory.addPreference(lp); 482 } 483 startAutoGO()484 private void startAutoGO() { 485 if (DEBUG) { 486 Slog.d(TAG, "Starting Autonomous GO..."); 487 } 488 mWifiP2pManager.createGroup(mWifiP2pChannel, new ActionListener() { 489 @Override 490 public void onSuccess() { 491 if (DEBUG) { 492 Slog.d(TAG, "Successfully started AutoGO."); 493 } 494 } 495 496 @Override 497 public void onFailure(int reason) { 498 Slog.e(TAG, "Failed to start AutoGO with reason " + reason + "."); 499 } 500 }); 501 } 502 stopAutoGO()503 private void stopAutoGO() { 504 if (DEBUG) { 505 Slog.d(TAG, "Stopping Autonomous GO..."); 506 } 507 mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() { 508 @Override 509 public void onSuccess() { 510 if (DEBUG) { 511 Slog.d(TAG, "Successfully stopped AutoGO."); 512 } 513 } 514 515 @Override 516 public void onFailure(int reason) { 517 Slog.e(TAG, "Failed to stop AutoGO with reason " + reason + "."); 518 } 519 }); 520 } 521 setListenMode(final boolean enable)522 private void setListenMode(final boolean enable) { 523 if (DEBUG) { 524 Slog.d(TAG, "Setting listen mode to: " + enable); 525 } 526 mWifiP2pManager.listen(mWifiP2pChannel, enable, new ActionListener() { 527 @Override 528 public void onSuccess() { 529 if (DEBUG) { 530 Slog.d(TAG, "Successfully " + (enable ? "entered" : "exited") 531 + " listen mode."); 532 } 533 } 534 535 @Override 536 public void onFailure(int reason) { 537 Slog.e(TAG, "Failed to " + (enable ? "entered" : "exited") 538 + " listen mode with reason " + reason + "."); 539 } 540 }); 541 } 542 setWifiP2pChannels(final int lc, final int oc)543 private void setWifiP2pChannels(final int lc, final int oc) { 544 if (DEBUG) { 545 Slog.d(TAG, "Setting wifi p2p channel: lc=" + lc + ", oc=" + oc); 546 } 547 mWifiP2pManager.setWifiP2pChannels(mWifiP2pChannel, 548 lc, oc, new ActionListener() { 549 @Override 550 public void onSuccess() { 551 if (DEBUG) { 552 Slog.d(TAG, "Successfully set wifi p2p channels."); 553 } 554 } 555 556 @Override 557 public void onFailure(int reason) { 558 Slog.e(TAG, "Failed to set wifi p2p channels with reason " + reason + "."); 559 } 560 }); 561 } 562 toggleRoute(MediaRouter.RouteInfo route)563 private void toggleRoute(MediaRouter.RouteInfo route) { 564 if (route.isSelected()) { 565 MediaRouteDialogPresenter.showDialogFragment(getActivity(), 566 MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, null); 567 } else { 568 route.select(); 569 } 570 } 571 pairWifiDisplay(WifiDisplay display)572 private void pairWifiDisplay(WifiDisplay display) { 573 if (display.canConnect()) { 574 mDisplayManager.connectWifiDisplay(display.getDeviceAddress()); 575 } 576 } 577 showWifiDisplayOptionsDialog(final WifiDisplay display)578 private void showWifiDisplayOptionsDialog(final WifiDisplay display) { 579 View view = getActivity().getLayoutInflater().inflate(R.layout.wifi_display_options, null); 580 final EditText nameEditText = (EditText) view.findViewById(R.id.name); 581 nameEditText.setText(display.getFriendlyDisplayName()); 582 583 DialogInterface.OnClickListener done = new DialogInterface.OnClickListener() { 584 @Override 585 public void onClick(DialogInterface dialog, int which) { 586 String name = nameEditText.getText().toString().trim(); 587 if (name.isEmpty() || name.equals(display.getDeviceName())) { 588 name = null; 589 } 590 mDisplayManager.renameWifiDisplay(display.getDeviceAddress(), name); 591 } 592 }; 593 DialogInterface.OnClickListener forget = new DialogInterface.OnClickListener() { 594 @Override 595 public void onClick(DialogInterface dialog, int which) { 596 mDisplayManager.forgetWifiDisplay(display.getDeviceAddress()); 597 } 598 }; 599 600 AlertDialog dialog = new AlertDialog.Builder(getActivity()) 601 .setCancelable(true) 602 .setTitle(R.string.wifi_display_options_title) 603 .setView(view) 604 .setPositiveButton(R.string.wifi_display_options_done, done) 605 .setNegativeButton(R.string.wifi_display_options_forget, forget) 606 .create(); 607 dialog.show(); 608 } 609 610 private final Runnable mUpdateRunnable = new Runnable() { 611 @Override 612 public void run() { 613 final int changes = mPendingChanges; 614 mPendingChanges = 0; 615 update(changes); 616 } 617 }; 618 619 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 620 @Override 621 public void onReceive(Context context, Intent intent) { 622 String action = intent.getAction(); 623 if (action.equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) { 624 scheduleUpdate(CHANGE_WIFI_DISPLAY_STATUS); 625 } 626 } 627 }; 628 629 private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) { 630 @Override 631 public void onChange(boolean selfChange, Uri uri) { 632 scheduleUpdate(CHANGE_SETTINGS); 633 } 634 }; 635 636 private final MediaRouter.Callback mRouterCallback = new MediaRouter.SimpleCallback() { 637 @Override 638 public void onRouteAdded(MediaRouter router, RouteInfo info) { 639 scheduleUpdate(CHANGE_ROUTES); 640 } 641 642 @Override 643 public void onRouteChanged(MediaRouter router, RouteInfo info) { 644 scheduleUpdate(CHANGE_ROUTES); 645 } 646 647 @Override 648 public void onRouteRemoved(MediaRouter router, RouteInfo info) { 649 scheduleUpdate(CHANGE_ROUTES); 650 } 651 652 @Override 653 public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { 654 scheduleUpdate(CHANGE_ROUTES); 655 } 656 657 @Override 658 public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { 659 scheduleUpdate(CHANGE_ROUTES); 660 } 661 }; 662 663 private class RoutePreference extends Preference 664 implements Preference.OnPreferenceClickListener { 665 private final MediaRouter.RouteInfo mRoute; 666 RoutePreference(Context context, MediaRouter.RouteInfo route)667 public RoutePreference(Context context, MediaRouter.RouteInfo route) { 668 super(context); 669 670 mRoute = route; 671 setTitle(route.getName()); 672 setSummary(route.getDescription()); 673 setEnabled(route.isEnabled()); 674 if (route.isSelected()) { 675 setOrder(ORDER_CONNECTED); 676 if (route.isConnecting()) { 677 setSummary(R.string.wifi_display_status_connecting); 678 } else { 679 setSummary(R.string.wifi_display_status_connected); 680 } 681 } else { 682 if (isEnabled()) { 683 setOrder(ORDER_AVAILABLE); 684 } else { 685 setOrder(ORDER_UNAVAILABLE); 686 if (route.getStatusCode() == MediaRouter.RouteInfo.STATUS_IN_USE) { 687 setSummary(R.string.wifi_display_status_in_use); 688 } else { 689 setSummary(R.string.wifi_display_status_not_available); 690 } 691 } 692 } 693 setOnPreferenceClickListener(this); 694 } 695 696 @Override onPreferenceClick(Preference preference)697 public boolean onPreferenceClick(Preference preference) { 698 toggleRoute(mRoute); 699 return true; 700 } 701 } 702 703 private class WifiDisplayRoutePreference extends RoutePreference 704 implements View.OnClickListener { 705 private final WifiDisplay mDisplay; 706 WifiDisplayRoutePreference(Context context, MediaRouter.RouteInfo route, WifiDisplay display)707 public WifiDisplayRoutePreference(Context context, MediaRouter.RouteInfo route, 708 WifiDisplay display) { 709 super(context, route); 710 711 mDisplay = display; 712 setWidgetLayoutResource(R.layout.wifi_display_preference); 713 } 714 715 @Override onBindViewHolder(PreferenceViewHolder view)716 public void onBindViewHolder(PreferenceViewHolder view) { 717 super.onBindViewHolder(view); 718 719 ImageView deviceDetails = (ImageView) view.findViewById(R.id.deviceDetails); 720 if (deviceDetails != null) { 721 deviceDetails.setOnClickListener(this); 722 if (!isEnabled()) { 723 TypedValue value = new TypedValue(); 724 getContext().getTheme().resolveAttribute(android.R.attr.disabledAlpha, 725 value, true); 726 deviceDetails.setImageAlpha((int) (value.getFloat() * 255)); 727 deviceDetails.setEnabled(true); // always allow button to be pressed 728 } 729 } 730 } 731 732 @Override onClick(View v)733 public void onClick(View v) { 734 showWifiDisplayOptionsDialog(mDisplay); 735 } 736 } 737 738 private class UnpairedWifiDisplayPreference extends Preference 739 implements Preference.OnPreferenceClickListener { 740 private final WifiDisplay mDisplay; 741 UnpairedWifiDisplayPreference(Context context, WifiDisplay display)742 public UnpairedWifiDisplayPreference(Context context, WifiDisplay display) { 743 super(context); 744 745 mDisplay = display; 746 setTitle(display.getFriendlyDisplayName()); 747 setSummary(com.android.internal.R.string.wireless_display_route_description); 748 setEnabled(display.canConnect()); 749 if (isEnabled()) { 750 setOrder(ORDER_AVAILABLE); 751 } else { 752 setOrder(ORDER_UNAVAILABLE); 753 setSummary(R.string.wifi_display_status_in_use); 754 } 755 setOnPreferenceClickListener(this); 756 } 757 758 @Override onPreferenceClick(Preference preference)759 public boolean onPreferenceClick(Preference preference) { 760 pairWifiDisplay(mDisplay); 761 return true; 762 } 763 } 764 765 private static class SummaryProvider implements SummaryLoader.SummaryProvider { 766 767 private final Context mContext; 768 private final SummaryLoader mSummaryLoader; 769 private final MediaRouter mRouter; 770 private final MediaRouter.Callback mRouterCallback = new MediaRouter.SimpleCallback() { 771 @Override 772 public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { 773 updateSummary(); 774 } 775 776 @Override 777 public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { 778 updateSummary(); 779 } 780 781 @Override 782 public void onRouteAdded(MediaRouter router, RouteInfo info) { 783 updateSummary(); 784 } 785 786 @Override 787 public void onRouteRemoved(MediaRouter router, RouteInfo info) { 788 updateSummary(); 789 } 790 791 @Override 792 public void onRouteChanged(MediaRouter router, RouteInfo info) { 793 updateSummary(); 794 } 795 }; 796 SummaryProvider(Context context, SummaryLoader summaryLoader)797 public SummaryProvider(Context context, SummaryLoader summaryLoader) { 798 mContext = context; 799 mSummaryLoader = summaryLoader; 800 mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); 801 } 802 803 @Override setListening(boolean listening)804 public void setListening(boolean listening) { 805 if (listening) { 806 mRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mRouterCallback); 807 updateSummary(); 808 } else { 809 mRouter.removeCallback(mRouterCallback); 810 } 811 } 812 updateSummary()813 private void updateSummary() { 814 String summary = mContext.getString(R.string.disconnected); 815 816 final int routeCount = mRouter.getRouteCount(); 817 for (int i = 0; i < routeCount; i++) { 818 final MediaRouter.RouteInfo route = mRouter.getRouteAt(i); 819 if (route.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) 820 && route.isSelected() && !route.isConnecting()) { 821 summary = mContext.getString(R.string.wifi_display_status_connected); 822 break; 823 } 824 } 825 mSummaryLoader.setSummary(this, summary); 826 } 827 } 828 829 public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY 830 = (activity, summaryLoader) -> new SummaryProvider(activity, summaryLoader); 831 832 public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 833 new BaseSearchIndexProvider() { 834 @Override 835 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, 836 boolean enabled) { 837 final ArrayList<SearchIndexableResource> result = new ArrayList<>(); 838 839 final SearchIndexableResource sir = new SearchIndexableResource(context); 840 sir.xmlResId = R.xml.wifi_display_settings; 841 result.add(sir); 842 return result; 843 } 844 }; 845 } 846