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
17package com.android.settings.wfd;
18
19import android.app.AlertDialog;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.database.ContentObserver;
26import android.hardware.display.DisplayManager;
27import android.hardware.display.WifiDisplay;
28import android.hardware.display.WifiDisplayStatus;
29import android.media.MediaRouter;
30import android.media.MediaRouter.RouteInfo;
31import android.net.Uri;
32import android.net.wifi.WpsInfo;
33import android.net.wifi.p2p.WifiP2pManager;
34import android.net.wifi.p2p.WifiP2pManager.ActionListener;
35import android.net.wifi.p2p.WifiP2pManager.Channel;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.Looper;
39import android.provider.Settings;
40import android.support.v14.preference.SwitchPreference;
41import android.support.v7.preference.ListPreference;
42import android.support.v7.preference.Preference;
43import android.support.v7.preference.Preference.OnPreferenceChangeListener;
44import android.support.v7.preference.PreferenceCategory;
45import android.support.v7.preference.PreferenceGroup;
46import android.support.v7.preference.PreferenceScreen;
47import android.support.v7.preference.PreferenceViewHolder;
48import android.util.Slog;
49import android.util.TypedValue;
50import android.view.Menu;
51import android.view.MenuInflater;
52import android.view.MenuItem;
53import android.view.View;
54import android.view.View.OnClickListener;
55import android.widget.Button;
56import android.widget.EditText;
57import android.widget.ImageView;
58import android.widget.TextView;
59
60import com.android.internal.app.MediaRouteDialogPresenter;
61import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
62import com.android.settings.R;
63import com.android.settings.SettingsPreferenceFragment;
64import 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 */
74public 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
114    public WifiDisplaySettings() {
115        mHandler = new Handler();
116    }
117
118    @Override
119    public int getMetricsCategory() {
120        return MetricsEvent.WFD_WIFI_DISPLAY;
121    }
122
123    @Override
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
138    protected int getHelpResource() {
139        return R.string.help_url_remote_display;
140    }
141
142    @Override
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
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
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
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
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
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
219    private void scheduleUpdate(int changes) {
220        if (mStarted) {
221            if (mPendingChanges == 0) {
222                mHandler.post(mUpdateRunnable);
223            }
224            mPendingChanges |= changes;
225        }
226    }
227
228    private void unscheduleUpdate() {
229        if (mPendingChanges != 0) {
230            mPendingChanges = 0;
231            mHandler.removeCallbacks(mUpdateRunnable);
232        }
233    }
234
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
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
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
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
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
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
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
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
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
561    private void pairWifiDisplay(WifiDisplay display) {
562        if (display.canConnect()) {
563            mDisplayManager.connectWifiDisplay(display.getDeviceAddress());
564        }
565    }
566
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
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
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
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
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
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
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
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
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
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
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