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