• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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