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