2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.wfd;
19 import android.app.ActionBar;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.Service;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.database.ContentObserver;
29 import android.hardware.display.DisplayManager;
30 import android.hardware.display.WifiDisplay;
31 import android.hardware.display.WifiDisplayStatus;
32 import android.media.MediaRouter;
33 import android.media.MediaRouter.RouteInfo;
34 import android.net.Uri;
35 import android.net.wifi.p2p.WifiP2pManager;
36 import android.net.wifi.p2p.WifiP2pManager.ActionListener;
37 import android.net.wifi.p2p.WifiP2pManager.Channel;
38 import android.net.wifi.WpsInfo;
39 import android.os.Bundle;
40 import android.os.Handler;
41 import android.os.Looper;
42 import android.preference.ListPreference;
43 import android.preference.Preference;
44 import android.preference.PreferenceCategory;
45 import android.preference.PreferenceGroup;
46 import android.preference.PreferenceScreen;
47 import android.preference.SwitchPreference;
48 import android.provider.Settings;
49 import android.util.Slog;
50 import android.util.TypedValue;
51 import android.view.LayoutInflater;
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.view.ViewGroup;
58 import android.widget.Button;
59 import android.widget.EditText;
60 import android.widget.ImageView;
61 import android.widget.TextView;
63 import com.android.internal.app.MediaRouteDialogPresenter;
64 import com.android.settings.R;
65 import com.android.settings.SettingsPreferenceFragment;
68 * The Settings screen for WifiDisplay configuration and connection management.
70 * The wifi display routes are integrated together with other remote display routes
71 * from the media router. It may happen that wifi display isn't actually available
72 * on the system. In that case, the enable option will not be shown but other
73 * remote display routes will continue to be made available.
75 public final class WifiDisplaySettings extends SettingsPreferenceFragment {
76 private static final String TAG = "WifiDisplaySettings";
77 private static final boolean DEBUG = false;
79 private static final int MENU_ID_ENABLE_WIFI_DISPLAY = Menu.FIRST;
81 private static final int CHANGE_SETTINGS = 1 << 0;
82 private static final int CHANGE_ROUTES = 1 << 1;
83 private static final int CHANGE_WIFI_DISPLAY_STATUS = 1 << 2;
84 private static final int CHANGE_ALL = -1;
86 private static final int ORDER_CERTIFICATION = 1;
87 private static final int ORDER_CONNECTED = 2;
88 private static final int ORDER_AVAILABLE = 3;
89 private static final int ORDER_UNAVAILABLE = 4;
91 private final Handler mHandler;
93 private MediaRouter mRouter;
94 private DisplayManager mDisplayManager;
96 private boolean mStarted;
97 private int mPendingChanges;
99 private boolean mWifiDisplayOnSetting;
100 private WifiDisplayStatus mWifiDisplayStatus;
102 private TextView mEmptyView;
105 private boolean mWifiDisplayCertificationOn;
106 private WifiP2pManager mWifiP2pManager;
107 private Channel mWifiP2pChannel;
108 private PreferenceGroup mCertCategory;
109 private boolean mListen;
110 private boolean mAutoGO;
111 private int mWpsConfig = WpsInfo.INVALID;
112 private int mListenChannel;
113 private int mOperatingChannel;
115 public WifiDisplaySettings() {
116 mHandler = new Handler();
120 public void onCreate(Bundle icicle) {
121 super.onCreate(icicle);
123 final Context context = getActivity();
124 mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
125 mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
126 mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
127 mWifiP2pChannel = mWifiP2pManager.initialize(context, Looper.getMainLooper(), null);
129 addPreferencesFromResource(R.xml.wifi_display_settings);
130 setHasOptionsMenu(true);
134 protected int getHelpResource() {
135 return R.string.help_url_remote_display;
139 public void onActivityCreated(Bundle savedInstanceState) {
140 super.onActivityCreated(savedInstanceState);
142 mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
143 mEmptyView.setText(R.string.wifi_display_no_devices_found);
144 getListView().setEmptyView(mEmptyView);
148 public void onStart() {
152 final Context context = getActivity();
153 IntentFilter filter = new IntentFilter();
154 filter.addAction(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
155 context.registerReceiver(mReceiver, filter);
157 getContentResolver().registerContentObserver(Settings.Global.getUriFor(
158 Settings.Global.WIFI_DISPLAY_ON), false, mSettingsObserver);
159 getContentResolver().registerContentObserver(Settings.Global.getUriFor(
160 Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, mSettingsObserver);
161 getContentResolver().registerContentObserver(Settings.Global.getUriFor(
162 Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false, mSettingsObserver);
164 mRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mRouterCallback,
165 MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
171 public void onStop() {
175 final Context context = getActivity();
176 context.unregisterReceiver(mReceiver);
178 getContentResolver().unregisterContentObserver(mSettingsObserver);
180 mRouter.removeCallback(mRouterCallback);
186 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
187 if (mWifiDisplayStatus != null && mWifiDisplayStatus.getFeatureState()
188 != WifiDisplayStatus.FEATURE_STATE_UNAVAILABLE) {
189 MenuItem item = menu.add(Menu.NONE, MENU_ID_ENABLE_WIFI_DISPLAY, 0,
190 R.string.wifi_display_enable_menu_item);
191 item.setCheckable(true);
192 item.setChecked(mWifiDisplayOnSetting);
194 super.onCreateOptionsMenu(menu, inflater);
198 public boolean onOptionsItemSelected(MenuItem item) {
199 switch (item.getItemId()) {
200 case MENU_ID_ENABLE_WIFI_DISPLAY:
201 mWifiDisplayOnSetting = !item.isChecked();
202 item.setChecked(mWifiDisplayOnSetting);
203 Settings.Global.putInt(getContentResolver(),
204 Settings.Global.WIFI_DISPLAY_ON, mWifiDisplayOnSetting ? 1 : 0);
207 return super.onOptionsItemSelected(item);
210 private void scheduleUpdate(int changes) {
212 if (mPendingChanges == 0) {
213 mHandler.post(mUpdateRunnable);
215 mPendingChanges |= changes;
219 private void unscheduleUpdate() {
220 if (mPendingChanges != 0) {
222 mHandler.removeCallbacks(mUpdateRunnable);
226 private void update(int changes) {
227 boolean invalidateOptions = false;
230 if ((changes & CHANGE_SETTINGS) != 0) {
231 mWifiDisplayOnSetting = Settings.Global.getInt(getContentResolver(),
232 Settings.Global.WIFI_DISPLAY_ON, 0) != 0;
233 mWifiDisplayCertificationOn = Settings.Global.getInt(getContentResolver(),
234 Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, 0) != 0;
235 mWpsConfig = Settings.Global.getInt(getContentResolver(),
236 Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID);
238 // The wifi display enabled setting may have changed.
239 invalidateOptions = true;
242 // Update wifi display state.
243 if ((changes & CHANGE_WIFI_DISPLAY_STATUS) != 0) {
244 mWifiDisplayStatus = mDisplayManager.getWifiDisplayStatus();
246 // The wifi display feature state may have changed.
247 invalidateOptions = true;
250 // Rebuild the routes.
251 final PreferenceScreen preferenceScreen = getPreferenceScreen();
252 preferenceScreen.removeAll();
254 // Add all known remote display routes.
255 final int routeCount = mRouter.getRouteCount();
256 for (int i = 0; i < routeCount; i++) {
257 MediaRouter.RouteInfo route = mRouter.getRouteAt(i);
258 if (route.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) {
259 preferenceScreen.addPreference(createRoutePreference(route));
263 // Additional features for wifi display routes.
264 if (mWifiDisplayStatus != null
265 && mWifiDisplayStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
266 // Add all unpaired wifi displays.
267 for (WifiDisplay display : mWifiDisplayStatus.getDisplays()) {
268 if (!display.isRemembered() && display.isAvailable()
269 && !display.equals(mWifiDisplayStatus.getActiveDisplay())) {
270 preferenceScreen.addPreference(new UnpairedWifiDisplayPreference(
271 getActivity(), display));
275 // Add the certification menu if enabled in developer options.
276 if (mWifiDisplayCertificationOn) {
277 buildCertificationMenu(preferenceScreen);
281 // Invalidate menu options if needed.
282 if (invalidateOptions) {
283 getActivity().invalidateOptionsMenu();
287 private RoutePreference createRoutePreference(MediaRouter.RouteInfo route) {
288 WifiDisplay display = findWifiDisplay(route.getDeviceAddress());
289 if (display != null) {
290 return new WifiDisplayRoutePreference(getActivity(), route, display);
292 return new RoutePreference(getActivity(), route);
296 private WifiDisplay findWifiDisplay(String deviceAddress) {
297 if (mWifiDisplayStatus != null && deviceAddress != null) {
298 for (WifiDisplay display : mWifiDisplayStatus.getDisplays()) {
299 if (display.getDeviceAddress().equals(deviceAddress)) {
307 private void buildCertificationMenu(final PreferenceScreen preferenceScreen) {
308 if (mCertCategory == null) {
309 mCertCategory = new PreferenceCategory(getActivity());
310 mCertCategory.setTitle(R.string.wifi_display_certification_heading);
311 mCertCategory.setOrder(ORDER_CERTIFICATION);
313 mCertCategory.removeAll();
315 preferenceScreen.addPreference(mCertCategory);
317 // display session info if there is an active p2p session
318 if (!mWifiDisplayStatus.getSessionInfo().getGroupId().isEmpty()) {
319 Preference p = new Preference(getActivity());
320 p.setTitle(R.string.wifi_display_session_info);
321 p.setSummary(mWifiDisplayStatus.getSessionInfo().toString());
322 mCertCategory.addPreference(p);
324 // show buttons for Pause/Resume when a WFD session is established
325 if (mWifiDisplayStatus.getSessionInfo().getSessionId() != 0) {
326 mCertCategory.addPreference(new Preference(getActivity()) {
328 public View getView(View convertView, ViewGroup parent) {
330 if (convertView == null) {
331 LayoutInflater li = (LayoutInflater) getActivity().
332 getSystemService(Service.LAYOUT_INFLATER_SERVICE);
333 v = li.inflate(R.layout.two_buttons_panel, null);
338 Button b = (Button)v.findViewById(R.id.left_button);
339 b.setText(R.string.wifi_display_pause);
340 b.setOnClickListener(new OnClickListener() {
342 public void onClick(View v) {
343 mDisplayManager.pauseWifiDisplay();
347 b = (Button)v.findViewById(R.id.right_button);
348 b.setText(R.string.wifi_display_resume);
349 b.setOnClickListener(new OnClickListener() {
351 public void onClick(View v) {
352 mDisplayManager.resumeWifiDisplay();
362 // switch for Listen Mode
363 SwitchPreference pref = new SwitchPreference(getActivity()) {
365 protected void onClick() {
367 setListenMode(mListen);
371 pref.setTitle(R.string.wifi_display_listen_mode);
372 pref.setChecked(mListen);
373 mCertCategory.addPreference(pref);
375 // switch for Autonomous GO
376 pref = new SwitchPreference(getActivity()) {
378 protected void onClick() {
388 pref.setTitle(R.string.wifi_display_autonomous_go);
389 pref.setChecked(mAutoGO);
390 mCertCategory.addPreference(pref);
392 // Drop down list for choosing WPS method (PBC/KEYPAD/DISPLAY)
393 ListPreference lp = new ListPreference(getActivity()) {
395 protected void onDialogClosed(boolean positiveResult) {
396 super.onDialogClosed(positiveResult);
397 if (positiveResult) {
398 mWpsConfig = Integer.parseInt(getValue());
400 getActivity().invalidateOptionsMenu();
401 Settings.Global.putInt(getActivity().getContentResolver(),
402 Settings.Global.WIFI_DISPLAY_WPS_CONFIG, mWpsConfig);
406 mWpsConfig = Settings.Global.getInt(getActivity().getContentResolver(),
407 Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID);
408 String[] wpsEntries = { "Default", "PBC", "KEYPAD", "DISPLAY" };
409 String[] wpsValues = {
410 "" + WpsInfo.INVALID,
413 "" + WpsInfo.DISPLAY };
414 lp.setTitle(R.string.wifi_display_wps_config);
415 lp.setEntries(wpsEntries);
416 lp.setEntryValues(wpsValues);
417 lp.setValue("" + mWpsConfig);
418 lp.setSummary("%1$s");
419 mCertCategory.addPreference(lp);
421 // Drop down list for choosing listen channel
422 lp = new ListPreference(getActivity()) {
424 protected void onDialogClosed(boolean positiveResult) {
425 super.onDialogClosed(positiveResult);
426 if (positiveResult) {
427 mListenChannel = Integer.parseInt(getValue());
429 getActivity().invalidateOptionsMenu();
430 setWifiP2pChannels(mListenChannel, mOperatingChannel);
434 String[] lcEntries = { "Auto", "1", "6", "11" };
435 String[] lcValues = { "0", "1", "6", "11" };
436 lp.setTitle(R.string.wifi_display_listen_channel);
437 lp.setEntries(lcEntries);
438 lp.setEntryValues(lcValues);
439 lp.setValue("" + mListenChannel);
440 lp.setSummary("%1$s");
441 mCertCategory.addPreference(lp);
443 // Drop down list for choosing operating channel
444 lp = new ListPreference(getActivity()) {
446 protected void onDialogClosed(boolean positiveResult) {
447 super.onDialogClosed(positiveResult);
448 if (positiveResult) {
449 mOperatingChannel = Integer.parseInt(getValue());
451 getActivity().invalidateOptionsMenu();
452 setWifiP2pChannels(mListenChannel, mOperatingChannel);
456 String[] ocEntries = { "Auto", "1", "6", "11", "36" };
457 String[] ocValues = { "0", "1", "6", "11", "36" };
458 lp.setTitle(R.string.wifi_display_operating_channel);
459 lp.setEntries(ocEntries);
460 lp.setEntryValues(ocValues);
461 lp.setValue("" + mOperatingChannel);
462 lp.setSummary("%1$s");
463 mCertCategory.addPreference(lp);
466 private void startAutoGO() {
468 Slog.d(TAG, "Starting Autonomous GO...");
470 mWifiP2pManager.createGroup(mWifiP2pChannel, new ActionListener() {
472 public void onSuccess() {
474 Slog.d(TAG, "Successfully started AutoGO.");
479 public void onFailure(int reason) {
480 Slog.e(TAG, "Failed to start AutoGO with reason " + reason + ".");
485 private void stopAutoGO() {
487 Slog.d(TAG, "Stopping Autonomous GO...");
489 mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
491 public void onSuccess() {
493 Slog.d(TAG, "Successfully stopped AutoGO.");
498 public void onFailure(int reason) {
499 Slog.e(TAG, "Failed to stop AutoGO with reason " + reason + ".");
504 private void setListenMode(final boolean enable) {
506 Slog.d(TAG, "Setting listen mode to: " + enable);
508 mWifiP2pManager.listen(mWifiP2pChannel, enable, new ActionListener() {
510 public void onSuccess() {
512 Slog.d(TAG, "Successfully " + (enable ? "entered" : "exited")
518 public void onFailure(int reason) {
519 Slog.e(TAG, "Failed to " + (enable ? "entered" : "exited")
520 +" listen mode with reason " + reason + ".");
525 private void setWifiP2pChannels(final int lc, final int oc) {
527 Slog.d(TAG, "Setting wifi p2p channel: lc=" + lc + ", oc=" + oc);
529 mWifiP2pManager.setWifiP2pChannels(mWifiP2pChannel,
530 lc, oc, new ActionListener() {
532 public void onSuccess() {
534 Slog.d(TAG, "Successfully set wifi p2p channels.");
539 public void onFailure(int reason) {
540 Slog.e(TAG, "Failed to set wifi p2p channels with reason " + reason + ".");
545 private void toggleRoute(MediaRouter.RouteInfo route) {
546 if (route.isSelected()) {
547 MediaRouteDialogPresenter.showDialogFragment(getActivity(),
548 MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, null);
554 private void pairWifiDisplay(WifiDisplay display) {
555 if (display.canConnect()) {
556 mDisplayManager.connectWifiDisplay(display.getDeviceAddress());
560 private void showWifiDisplayOptionsDialog(final WifiDisplay display) {
561 View view = getActivity().getLayoutInflater().inflate(R.layout.wifi_display_options, null);
562 final EditText nameEditText = (EditText)view.findViewById(R.id.name);
563 nameEditText.setText(display.getFriendlyDisplayName());
565 DialogInterface.OnClickListener done = new DialogInterface.OnClickListener() {
567 public void onClick(DialogInterface dialog, int which) {
568 String name = nameEditText.getText().toString().trim();
569 if (name.isEmpty() || name.equals(display.getDeviceName())) {
572 mDisplayManager.renameWifiDisplay(display.getDeviceAddress(), name);
575 DialogInterface.OnClickListener forget = new DialogInterface.OnClickListener() {
577 public void onClick(DialogInterface dialog, int which) {
578 mDisplayManager.forgetWifiDisplay(display.getDeviceAddress());
582 AlertDialog dialog = new AlertDialog.Builder(getActivity())
584 .setTitle(R.string.wifi_display_options_title)
586 .setPositiveButton(R.string.wifi_display_options_done, done)
587 .setNegativeButton(R.string.wifi_display_options_forget, forget)
592 private final Runnable mUpdateRunnable = new Runnable() {
595 final int changes = mPendingChanges;
601 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
603 public void onReceive(Context context, Intent intent) {
604 String action = intent.getAction();
605 if (action.equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
606 scheduleUpdate(CHANGE_WIFI_DISPLAY_STATUS);
611 private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
613 public void onChange(boolean selfChange, Uri uri) {
614 scheduleUpdate(CHANGE_SETTINGS);
618 private final MediaRouter.Callback mRouterCallback = new MediaRouter.SimpleCallback() {
620 public void onRouteAdded(MediaRouter router, RouteInfo info) {
621 scheduleUpdate(CHANGE_ROUTES);
625 public void onRouteChanged(MediaRouter router, RouteInfo info) {
626 scheduleUpdate(CHANGE_ROUTES);
630 public void onRouteRemoved(MediaRouter router, RouteInfo info) {
631 scheduleUpdate(CHANGE_ROUTES);
635 public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
636 scheduleUpdate(CHANGE_ROUTES);
640 public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
641 scheduleUpdate(CHANGE_ROUTES);
645 private class RoutePreference extends Preference
646 implements Preference.OnPreferenceClickListener {
647 private final MediaRouter.RouteInfo mRoute;
649 public RoutePreference(Context context, MediaRouter.RouteInfo route) {
653 setTitle(route.getName());
654 setSummary(route.getDescription());
655 setEnabled(route.isEnabled());
656 if (route.isSelected()) {
657 setOrder(ORDER_CONNECTED);
658 if (route.isConnecting()) {
659 setSummary(R.string.wifi_display_status_connecting);
661 setSummary(R.string.wifi_display_status_connected);
665 setOrder(ORDER_AVAILABLE);
667 setOrder(ORDER_UNAVAILABLE);
668 if (route.getStatusCode() == MediaRouter.RouteInfo.STATUS_IN_USE) {
669 setSummary(R.string.wifi_display_status_in_use);
671 setSummary(R.string.wifi_display_status_not_available);
675 setOnPreferenceClickListener(this);
679 public boolean onPreferenceClick(Preference preference) {
685 private class WifiDisplayRoutePreference extends RoutePreference
686 implements View.OnClickListener {
687 private final WifiDisplay mDisplay;
689 public WifiDisplayRoutePreference(Context context, MediaRouter.RouteInfo route,
690 WifiDisplay display) {
691 super(context, route);
694 setWidgetLayoutResource(R.layout.wifi_display_preference);
698 protected void onBindView(View view) {
699 super.onBindView(view);
701 ImageView deviceDetails = (ImageView) view.findViewById(R.id.deviceDetails);
702 if (deviceDetails != null) {
703 deviceDetails.setOnClickListener(this);
705 TypedValue value = new TypedValue();
706 getContext().getTheme().resolveAttribute(android.R.attr.disabledAlpha,
708 deviceDetails.setImageAlpha((int)(value.getFloat() * 255));
709 deviceDetails.setEnabled(true); // always allow button to be pressed
715 public void onClick(View v) {
716 showWifiDisplayOptionsDialog(mDisplay);
720 private class UnpairedWifiDisplayPreference extends Preference
721 implements Preference.OnPreferenceClickListener {
722 private final WifiDisplay mDisplay;
724 public UnpairedWifiDisplayPreference(Context context, WifiDisplay display) {
728 setTitle(display.getFriendlyDisplayName());
729 setSummary(com.android.internal.R.string.wireless_display_route_description);
730 setEnabled(display.canConnect());
732 setOrder(ORDER_AVAILABLE);
734 setOrder(ORDER_UNAVAILABLE);
735 setSummary(R.string.wifi_display_status_in_use);
737 setOnPreferenceClickListener(this);
741 public boolean onPreferenceClick(Preference preference) {
742 pairWifiDisplay(mDisplay);