1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings.wifi.p2p;
18
19import android.app.ActionBar;
20import android.app.Activity;
21import android.app.AlertDialog;
22import android.app.Dialog;
23import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.DialogInterface.OnClickListener;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.net.NetworkInfo;
30import android.net.wifi.p2p.WifiP2pConfig;
31import android.net.wifi.p2p.WifiP2pDevice;
32import android.net.wifi.p2p.WifiP2pDeviceList;
33import android.net.wifi.p2p.WifiP2pGroup;
34import android.net.wifi.p2p.WifiP2pGroupList;
35import android.net.wifi.p2p.WifiP2pManager;
36import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
37import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
38import android.net.wifi.p2p.WifiP2pManager.PersistentGroupInfoListener;
39import android.net.wifi.WpsInfo;
40import android.os.Bundle;
41import android.os.Handler;
42import android.os.SystemProperties;
43import android.preference.Preference;
44import android.preference.PreferenceActivity;
45import android.preference.PreferenceCategory;
46import android.preference.PreferenceGroup;
47import android.preference.PreferenceScreen;
48import android.text.TextUtils;
49import android.util.Log;
50import android.view.Gravity;
51import android.view.Menu;
52import android.view.MenuInflater;
53import android.view.MenuItem;
54import android.widget.EditText;
55import android.widget.Switch;
56import android.widget.Toast;
57
58import com.android.settings.R;
59import com.android.settings.SettingsPreferenceFragment;
60
61import java.util.Arrays;
62import java.util.List;
63import java.util.Collection;
64
65/*
66 * Displays Wi-fi p2p settings UI
67 */
68public class WifiP2pSettings extends SettingsPreferenceFragment
69        implements PeerListListener, PersistentGroupInfoListener, GroupInfoListener {
70
71    private static final String TAG = "WifiP2pSettings";
72    private static final boolean DBG = false;
73    private static final int MENU_ID_SEARCH = Menu.FIRST;
74    private static final int MENU_ID_RENAME = Menu.FIRST + 1;
75
76    private final IntentFilter mIntentFilter = new IntentFilter();
77    private WifiP2pManager mWifiP2pManager;
78    private WifiP2pManager.Channel mChannel;
79    private OnClickListener mRenameListener;
80    private OnClickListener mDisconnectListener;
81    private OnClickListener mCancelConnectListener;
82    private OnClickListener mDeleteGroupListener;
83    private WifiP2pPeer mSelectedWifiPeer;
84    private WifiP2pPersistentGroup mSelectedGroup;
85    private EditText mDeviceNameText;
86
87    private boolean mWifiP2pEnabled;
88    private boolean mWifiP2pSearching;
89    private int mConnectedDevices;
90    private WifiP2pGroup mConnectedGroup;
91
92    private PreferenceGroup mPeersGroup;
93    private PreferenceGroup mPersistentGroup;
94    private Preference mThisDevicePref;
95
96    private static final int DIALOG_DISCONNECT  = 1;
97    private static final int DIALOG_CANCEL_CONNECT = 2;
98    private static final int DIALOG_RENAME = 3;
99    private static final int DIALOG_DELETE_GROUP = 4;
100
101    private static final String SAVE_DIALOG_PEER = "PEER_STATE";
102    private static final String SAVE_DEVICE_NAME = "DEV_NAME";
103
104    private WifiP2pDevice mThisDevice;
105    private WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
106
107    private String mSavedDeviceName;
108
109    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
110        @Override
111        public void onReceive(Context context, Intent intent) {
112            String action = intent.getAction();
113
114            if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
115                mWifiP2pEnabled = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
116                    WifiP2pManager.WIFI_P2P_STATE_DISABLED) == WifiP2pManager.WIFI_P2P_STATE_ENABLED;
117                handleP2pStateChanged();
118            } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
119                if (mWifiP2pManager != null) {
120                    mWifiP2pManager.requestPeers(mChannel, WifiP2pSettings.this);
121                }
122            } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
123                if (mWifiP2pManager == null) return;
124                NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(
125                        WifiP2pManager.EXTRA_NETWORK_INFO);
126                if (mWifiP2pManager != null) {
127                    mWifiP2pManager.requestGroupInfo(mChannel, WifiP2pSettings.this);
128                }
129                if (networkInfo.isConnected()) {
130                    if (DBG) Log.d(TAG, "Connected");
131                } else {
132                    //start a search when we are disconnected
133                    startSearch();
134                }
135            } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
136                mThisDevice = (WifiP2pDevice) intent.getParcelableExtra(
137                        WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
138                if (DBG) Log.d(TAG, "Update device info: " + mThisDevice);
139                updateDevicePref();
140            } else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) {
141                int discoveryState = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE,
142                    WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
143                if (DBG) Log.d(TAG, "Discovery state changed: " + discoveryState);
144                if (discoveryState == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED) {
145                    updateSearchMenu(true);
146                } else {
147                    updateSearchMenu(false);
148                }
149            } else if (WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION.equals(action)) {
150                if (mWifiP2pManager != null) {
151                    mWifiP2pManager.requestPersistentGroupInfo(mChannel, WifiP2pSettings.this);
152                }
153            }
154        }
155    };
156
157    @Override
158    public void onActivityCreated(Bundle savedInstanceState) {
159        addPreferencesFromResource(R.xml.wifi_p2p_settings);
160
161        mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
162        mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
163        mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
164        mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
165        mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
166        mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION);
167
168        final Activity activity = getActivity();
169        mWifiP2pManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
170        if (mWifiP2pManager != null) {
171            mChannel = mWifiP2pManager.initialize(activity, getActivity().getMainLooper(), null);
172            if (mChannel == null) {
173                //Failure to set up connection
174                Log.e(TAG, "Failed to set up connection with wifi p2p service");
175                mWifiP2pManager = null;
176            }
177        } else {
178            Log.e(TAG, "mWifiP2pManager is null !");
179        }
180
181        if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_DIALOG_PEER)) {
182            WifiP2pDevice device = savedInstanceState.getParcelable(SAVE_DIALOG_PEER);
183            mSelectedWifiPeer = new WifiP2pPeer(getActivity(), device);
184        }
185        if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_DEVICE_NAME)) {
186            mSavedDeviceName = savedInstanceState.getString(SAVE_DEVICE_NAME);
187        }
188
189        mRenameListener = new OnClickListener() {
190            @Override
191            public void onClick(DialogInterface dialog, int which) {
192                if (which == DialogInterface.BUTTON_POSITIVE) {
193                    if (mWifiP2pManager != null) {
194                        mWifiP2pManager.setDeviceName(mChannel,
195                                mDeviceNameText.getText().toString(),
196                                new WifiP2pManager.ActionListener() {
197                            public void onSuccess() {
198                                if (DBG) Log.d(TAG, " device rename success");
199                            }
200                            public void onFailure(int reason) {
201                                Toast.makeText(getActivity(),
202                                        R.string.wifi_p2p_failed_rename_message,
203                                        Toast.LENGTH_LONG).show();
204                            }
205                        });
206                    }
207                }
208            }
209        };
210
211        //disconnect dialog listener
212        mDisconnectListener = new OnClickListener() {
213            @Override
214            public void onClick(DialogInterface dialog, int which) {
215                if (which == DialogInterface.BUTTON_POSITIVE) {
216                    if (mWifiP2pManager != null) {
217                        mWifiP2pManager.removeGroup(mChannel, new WifiP2pManager.ActionListener() {
218                            public void onSuccess() {
219                                if (DBG) Log.d(TAG, " remove group success");
220                            }
221                            public void onFailure(int reason) {
222                                if (DBG) Log.d(TAG, " remove group fail " + reason);
223                            }
224                        });
225                    }
226                }
227            }
228        };
229
230        //cancel connect dialog listener
231        mCancelConnectListener = new OnClickListener() {
232            @Override
233            public void onClick(DialogInterface dialog, int which) {
234                if (which == DialogInterface.BUTTON_POSITIVE) {
235                    if (mWifiP2pManager != null) {
236                        mWifiP2pManager.cancelConnect(mChannel,
237                                new WifiP2pManager.ActionListener() {
238                            public void onSuccess() {
239                                if (DBG) Log.d(TAG, " cancel connect success");
240                            }
241                            public void onFailure(int reason) {
242                                if (DBG) Log.d(TAG, " cancel connect fail " + reason);
243                            }
244                        });
245                    }
246                }
247            }
248        };
249
250        //delete persistent group dialog listener
251        mDeleteGroupListener = new OnClickListener() {
252            @Override
253            public void onClick(DialogInterface dialog, int which) {
254                if (which == DialogInterface.BUTTON_POSITIVE) {
255                    if (mWifiP2pManager != null) {
256                        mWifiP2pManager.deletePersistentGroup(mChannel,
257                                mSelectedGroup.getNetworkId(),
258                                new WifiP2pManager.ActionListener() {
259                            public void onSuccess() {
260                                if (DBG) Log.d(TAG, " delete group success");
261                            }
262                            public void onFailure(int reason) {
263                                if (DBG) Log.d(TAG, " delete group fail " + reason);
264                            }
265                        });
266                    }
267                }
268            }
269        };
270
271        setHasOptionsMenu(true);
272
273        final PreferenceScreen preferenceScreen = getPreferenceScreen();
274        preferenceScreen.removeAll();
275
276        preferenceScreen.setOrderingAsAdded(true);
277        mThisDevicePref = new Preference(getActivity());
278        preferenceScreen.addPreference(mThisDevicePref);
279
280        mPeersGroup = new PreferenceCategory(getActivity());
281        mPeersGroup.setTitle(R.string.wifi_p2p_peer_devices);
282
283        mPersistentGroup = new PreferenceCategory(getActivity());
284        mPersistentGroup.setTitle(R.string.wifi_p2p_remembered_groups);
285
286        super.onActivityCreated(savedInstanceState);
287    }
288
289    @Override
290    public void onResume() {
291        super.onResume();
292        getActivity().registerReceiver(mReceiver, mIntentFilter);
293    }
294
295    @Override
296    public void onPause() {
297        super.onPause();
298        mWifiP2pManager.stopPeerDiscovery(mChannel, null);
299        getActivity().unregisterReceiver(mReceiver);
300    }
301
302    @Override
303    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
304        int textId = mWifiP2pSearching ? R.string.wifi_p2p_menu_searching :
305                R.string.wifi_p2p_menu_search;
306        menu.add(Menu.NONE, MENU_ID_SEARCH, 0, textId)
307            .setEnabled(mWifiP2pEnabled)
308            .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
309        menu.add(Menu.NONE, MENU_ID_RENAME, 0, R.string.wifi_p2p_menu_rename)
310            .setEnabled(mWifiP2pEnabled)
311            .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
312        super.onCreateOptionsMenu(menu, inflater);
313    }
314
315    @Override
316    public void onPrepareOptionsMenu(Menu menu) {
317        MenuItem searchMenu = menu.findItem(MENU_ID_SEARCH);
318        MenuItem renameMenu = menu.findItem(MENU_ID_RENAME);
319        if (mWifiP2pEnabled) {
320            searchMenu.setEnabled(true);
321            renameMenu.setEnabled(true);
322        } else {
323            searchMenu.setEnabled(false);
324            renameMenu.setEnabled(false);
325        }
326
327        if (mWifiP2pSearching) {
328            searchMenu.setTitle(R.string.wifi_p2p_menu_searching);
329        } else {
330            searchMenu.setTitle(R.string.wifi_p2p_menu_search);
331        }
332    }
333
334    @Override
335    public boolean onOptionsItemSelected(MenuItem item) {
336        switch (item.getItemId()) {
337            case MENU_ID_SEARCH:
338                startSearch();
339                return true;
340            case MENU_ID_RENAME:
341                showDialog(DIALOG_RENAME);
342                return true;
343        }
344        return super.onOptionsItemSelected(item);
345    }
346
347    @Override
348    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
349        if (preference instanceof WifiP2pPeer) {
350            mSelectedWifiPeer = (WifiP2pPeer) preference;
351            if (mSelectedWifiPeer.device.status == WifiP2pDevice.CONNECTED) {
352                showDialog(DIALOG_DISCONNECT);
353            } else if (mSelectedWifiPeer.device.status == WifiP2pDevice.INVITED) {
354                showDialog(DIALOG_CANCEL_CONNECT);
355            } else {
356                WifiP2pConfig config = new WifiP2pConfig();
357                config.deviceAddress = mSelectedWifiPeer.device.deviceAddress;
358
359                int forceWps = SystemProperties.getInt("wifidirect.wps", -1);
360
361                if (forceWps != -1) {
362                    config.wps.setup = forceWps;
363                } else {
364                    if (mSelectedWifiPeer.device.wpsPbcSupported()) {
365                        config.wps.setup = WpsInfo.PBC;
366                    } else if (mSelectedWifiPeer.device.wpsKeypadSupported()) {
367                        config.wps.setup = WpsInfo.KEYPAD;
368                    } else {
369                        config.wps.setup = WpsInfo.DISPLAY;
370                    }
371                }
372
373                mWifiP2pManager.connect(mChannel, config,
374                        new WifiP2pManager.ActionListener() {
375                            public void onSuccess() {
376                                if (DBG) Log.d(TAG, " connect success");
377                            }
378                            public void onFailure(int reason) {
379                                Log.e(TAG, " connect fail " + reason);
380                                Toast.makeText(getActivity(),
381                                        R.string.wifi_p2p_failed_connect_message,
382                                        Toast.LENGTH_SHORT).show();
383                            }
384                    });
385            }
386        } else if (preference instanceof WifiP2pPersistentGroup) {
387            mSelectedGroup = (WifiP2pPersistentGroup) preference;
388            showDialog(DIALOG_DELETE_GROUP);
389        }
390        return super.onPreferenceTreeClick(screen, preference);
391    }
392
393    @Override
394    public Dialog onCreateDialog(int id) {
395        if (id == DIALOG_DISCONNECT) {
396            String deviceName = TextUtils.isEmpty(mSelectedWifiPeer.device.deviceName) ?
397                    mSelectedWifiPeer.device.deviceAddress :
398                    mSelectedWifiPeer.device.deviceName;
399            String msg;
400            if (mConnectedDevices > 1) {
401                msg = getActivity().getString(R.string.wifi_p2p_disconnect_multiple_message,
402                        deviceName, mConnectedDevices - 1);
403            } else {
404                msg = getActivity().getString(R.string.wifi_p2p_disconnect_message, deviceName);
405            }
406            AlertDialog dialog = new AlertDialog.Builder(getActivity())
407                .setTitle(R.string.wifi_p2p_disconnect_title)
408                .setMessage(msg)
409                .setPositiveButton(getActivity().getString(R.string.dlg_ok), mDisconnectListener)
410                .setNegativeButton(getActivity().getString(R.string.dlg_cancel), null)
411                .create();
412            return dialog;
413        } else if (id == DIALOG_CANCEL_CONNECT) {
414            int stringId = R.string.wifi_p2p_cancel_connect_message;
415            String deviceName = TextUtils.isEmpty(mSelectedWifiPeer.device.deviceName) ?
416                    mSelectedWifiPeer.device.deviceAddress :
417                    mSelectedWifiPeer.device.deviceName;
418
419            AlertDialog dialog = new AlertDialog.Builder(getActivity())
420                .setTitle(R.string.wifi_p2p_cancel_connect_title)
421                .setMessage(getActivity().getString(stringId, deviceName))
422                .setPositiveButton(getActivity().getString(R.string.dlg_ok), mCancelConnectListener)
423                .setNegativeButton(getActivity().getString(R.string.dlg_cancel), null)
424                .create();
425            return dialog;
426        } else if (id == DIALOG_RENAME) {
427            mDeviceNameText = new EditText(getActivity());
428            if (mSavedDeviceName != null) {
429                mDeviceNameText.setText(mSavedDeviceName);
430                mDeviceNameText.setSelection(mSavedDeviceName.length());
431            } else if (mThisDevice != null && !TextUtils.isEmpty(mThisDevice.deviceName)) {
432                mDeviceNameText.setText(mThisDevice.deviceName);
433                mDeviceNameText.setSelection(0, mThisDevice.deviceName.length());
434            }
435            mSavedDeviceName = null;
436            AlertDialog dialog = new AlertDialog.Builder(getActivity())
437                .setTitle(R.string.wifi_p2p_menu_rename)
438                .setView(mDeviceNameText)
439                .setPositiveButton(getActivity().getString(R.string.dlg_ok), mRenameListener)
440                .setNegativeButton(getActivity().getString(R.string.dlg_cancel), null)
441                .create();
442            return dialog;
443        } else if (id == DIALOG_DELETE_GROUP) {
444            int stringId = R.string.wifi_p2p_delete_group_message;
445
446            AlertDialog dialog = new AlertDialog.Builder(getActivity())
447                .setMessage(getActivity().getString(stringId))
448                .setPositiveButton(getActivity().getString(R.string.dlg_ok), mDeleteGroupListener)
449                .setNegativeButton(getActivity().getString(R.string.dlg_cancel), null)
450                .create();
451            return dialog;
452        }
453        return null;
454    }
455
456    @Override
457    public void onSaveInstanceState(Bundle outState) {
458        if (mSelectedWifiPeer != null) {
459            outState.putParcelable(SAVE_DIALOG_PEER, mSelectedWifiPeer.device);
460        }
461        if (mDeviceNameText != null) {
462            outState.putString(SAVE_DEVICE_NAME, mDeviceNameText.getText().toString());
463        }
464    }
465
466    public void onPeersAvailable(WifiP2pDeviceList peers) {
467        mPeersGroup.removeAll();
468
469        mPeers = peers;
470        mConnectedDevices = 0;
471        for (WifiP2pDevice peer: peers.getDeviceList()) {
472            if (DBG) Log.d(TAG, " peer " + peer);
473            mPeersGroup.addPreference(new WifiP2pPeer(getActivity(), peer));
474            if (peer.status == WifiP2pDevice.CONNECTED) mConnectedDevices++;
475        }
476        if (DBG) Log.d(TAG, " mConnectedDevices " + mConnectedDevices);
477    }
478
479    public void onPersistentGroupInfoAvailable(WifiP2pGroupList groups) {
480        mPersistentGroup.removeAll();
481
482        for (WifiP2pGroup group: groups.getGroupList()) {
483            if (DBG) Log.d(TAG, " group " + group);
484            mPersistentGroup.addPreference(new WifiP2pPersistentGroup(getActivity(), group));
485        }
486    }
487
488    public void onGroupInfoAvailable(WifiP2pGroup group) {
489        if (DBG) Log.d(TAG, " group " + group);
490        mConnectedGroup = group;
491        updateDevicePref();
492    }
493
494    private void handleP2pStateChanged() {
495        updateSearchMenu(false);
496        if (mWifiP2pEnabled) {
497            final PreferenceScreen preferenceScreen = getPreferenceScreen();
498            preferenceScreen.removeAll();
499
500            preferenceScreen.setOrderingAsAdded(true);
501            preferenceScreen.addPreference(mThisDevicePref);
502
503            mPeersGroup.setEnabled(true);
504            preferenceScreen.addPreference(mPeersGroup);
505
506            mPersistentGroup.setEnabled(true);
507            preferenceScreen.addPreference(mPersistentGroup);
508
509            /* Request latest set of peers */
510            mWifiP2pManager.requestPeers(mChannel, WifiP2pSettings.this);
511        }
512    }
513
514    private void updateSearchMenu(boolean searching) {
515       mWifiP2pSearching = searching;
516       Activity activity = getActivity();
517       if (activity != null) activity.invalidateOptionsMenu();
518    }
519
520    private void startSearch() {
521        if (mWifiP2pManager != null && !mWifiP2pSearching) {
522            mWifiP2pManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
523                public void onSuccess() {
524                }
525                public void onFailure(int reason) {
526                    if (DBG) Log.d(TAG, " discover fail " + reason);
527                }
528            });
529        }
530    }
531
532    private void updateDevicePref() {
533        if (mThisDevice != null) {
534            if (TextUtils.isEmpty(mThisDevice.deviceName)) {
535                mThisDevicePref.setTitle(mThisDevice.deviceAddress);
536            } else {
537                mThisDevicePref.setTitle(mThisDevice.deviceName);
538            }
539
540            mThisDevicePref.setPersistent(false);
541            mThisDevicePref.setEnabled(true);
542            mThisDevicePref.setSelectable(false);
543        }
544    }
545}
546