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