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