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.WifiP2pManager;
34import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
35import android.net.wifi.WpsInfo;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.SystemProperties;
39import android.preference.Preference;
40import android.preference.PreferenceActivity;
41import android.preference.PreferenceCategory;
42import android.preference.PreferenceGroup;
43import android.preference.PreferenceScreen;
44import android.text.TextUtils;
45import android.util.Log;
46import android.view.Gravity;
47import android.view.Menu;
48import android.view.MenuInflater;
49import android.view.MenuItem;
50import android.widget.EditText;
51import android.widget.Switch;
52import android.widget.Toast;
53
54import com.android.settings.R;
55import com.android.settings.SettingsPreferenceFragment;
56
57import java.util.Arrays;
58import java.util.List;
59import java.util.Collection;
60
61/*
62 * Displays Wi-fi p2p settings UI
63 */
64public class WifiP2pSettings extends SettingsPreferenceFragment
65        implements PeerListListener {
66
67    private static final String TAG = "WifiP2pSettings";
68    private static final boolean DBG = false;
69    private static final int MENU_ID_SEARCH = Menu.FIRST;
70    private static final int MENU_ID_RENAME = Menu.FIRST + 1;
71
72    private final IntentFilter mIntentFilter = new IntentFilter();
73    private WifiP2pManager mWifiP2pManager;
74    private WifiP2pManager.Channel mChannel;
75    private OnClickListener mRenameListener;
76    private OnClickListener mDisconnectListener;
77    private OnClickListener mCancelConnectListener;
78    private WifiP2pPeer mSelectedWifiPeer;
79    private EditText mDeviceNameText;
80
81    private boolean mWifiP2pEnabled;
82    private boolean mWifiP2pSearching;
83    private int mConnectedDevices;
84
85    private PreferenceGroup mPeersGroup;
86    private Preference mThisDevicePref;
87
88    private static final int DIALOG_DISCONNECT  = 1;
89    private static final int DIALOG_CANCEL_CONNECT = 2;
90    private static final int DIALOG_RENAME = 3;
91
92    private static final String SAVE_DIALOG_PEER = "PEER_STATE";
93    private static final String SAVE_DEVICE_NAME = "DEV_NAME";
94
95    private WifiP2pDevice mThisDevice;
96    private WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
97
98    private String mSavedDeviceName;
99
100    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
101        @Override
102        public void onReceive(Context context, Intent intent) {
103            String action = intent.getAction();
104
105            if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
106                mWifiP2pEnabled = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
107                    WifiP2pManager.WIFI_P2P_STATE_DISABLED) == WifiP2pManager.WIFI_P2P_STATE_ENABLED;
108                handleP2pStateChanged();
109            } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
110                if (mWifiP2pManager != null) {
111                    mWifiP2pManager.requestPeers(mChannel, WifiP2pSettings.this);
112                }
113            } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
114                if (mWifiP2pManager == null) return;
115                NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(
116                        WifiP2pManager.EXTRA_NETWORK_INFO);
117                if (networkInfo.isConnected()) {
118                    if (DBG) Log.d(TAG, "Connected");
119                } else {
120                    //start a search when we are disconnected
121                    startSearch();
122                }
123            } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
124                mThisDevice = (WifiP2pDevice) intent.getParcelableExtra(
125                        WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
126                if (DBG) Log.d(TAG, "Update device info: " + mThisDevice);
127                updateDevicePref();
128            } else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) {
129                int discoveryState = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE,
130                    WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
131                if (DBG) Log.d(TAG, "Discovery state changed: " + discoveryState);
132                if (discoveryState == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED) {
133                    updateSearchMenu(true);
134                } else {
135                    updateSearchMenu(false);
136                }
137            }
138        }
139    };
140
141    @Override
142    public void onActivityCreated(Bundle savedInstanceState) {
143        addPreferencesFromResource(R.xml.wifi_p2p_settings);
144
145        mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
146        mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
147        mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
148        mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
149        mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
150
151        final Activity activity = getActivity();
152        mWifiP2pManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
153        if (mWifiP2pManager != null) {
154            mChannel = mWifiP2pManager.initialize(activity, getActivity().getMainLooper(), null);
155            if (mChannel == null) {
156                //Failure to set up connection
157                Log.e(TAG, "Failed to set up connection with wifi p2p service");
158                mWifiP2pManager = null;
159            }
160        } else {
161            Log.e(TAG, "mWifiP2pManager is null !");
162        }
163
164        if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_DIALOG_PEER)) {
165            WifiP2pDevice device = savedInstanceState.getParcelable(SAVE_DIALOG_PEER);
166            mSelectedWifiPeer = new WifiP2pPeer(getActivity(), device);
167        }
168        if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_DEVICE_NAME)) {
169            mSavedDeviceName = savedInstanceState.getString(SAVE_DEVICE_NAME);
170        }
171
172        mRenameListener = new OnClickListener() {
173            @Override
174            public void onClick(DialogInterface dialog, int which) {
175                if (which == DialogInterface.BUTTON_POSITIVE) {
176                    if (mWifiP2pManager != null) {
177                        mWifiP2pManager.setDeviceName(mChannel,
178                                mDeviceNameText.getText().toString(),
179                                new WifiP2pManager.ActionListener() {
180                            public void onSuccess() {
181                                if (DBG) Log.d(TAG, " device rename success");
182                            }
183                            public void onFailure(int reason) {
184                                Toast.makeText(getActivity(),
185                                        R.string.wifi_p2p_failed_rename_message,
186                                        Toast.LENGTH_LONG).show();
187                            }
188                        });
189                    }
190                }
191            }
192        };
193
194        //disconnect dialog listener
195        mDisconnectListener = new OnClickListener() {
196            @Override
197            public void onClick(DialogInterface dialog, int which) {
198                if (which == DialogInterface.BUTTON_POSITIVE) {
199                    if (mWifiP2pManager != null) {
200                        mWifiP2pManager.removeGroup(mChannel, new WifiP2pManager.ActionListener() {
201                            public void onSuccess() {
202                                if (DBG) Log.d(TAG, " remove group success");
203                            }
204                            public void onFailure(int reason) {
205                                if (DBG) Log.d(TAG, " remove group fail " + reason);
206                            }
207                        });
208                    }
209                }
210            }
211        };
212
213        //cancel connect dialog listener
214        mCancelConnectListener = new OnClickListener() {
215            @Override
216            public void onClick(DialogInterface dialog, int which) {
217                if (which == DialogInterface.BUTTON_POSITIVE) {
218                    if (mWifiP2pManager != null) {
219                        mWifiP2pManager.cancelConnect(mChannel,
220                                new WifiP2pManager.ActionListener() {
221                            public void onSuccess() {
222                                if (DBG) Log.d(TAG, " cancel connect success");
223                            }
224                            public void onFailure(int reason) {
225                                if (DBG) Log.d(TAG, " cancel connect fail " + reason);
226                            }
227                        });
228                    }
229                }
230            }
231        };
232
233        setHasOptionsMenu(true);
234
235        final PreferenceScreen preferenceScreen = getPreferenceScreen();
236        preferenceScreen.removeAll();
237
238        preferenceScreen.setOrderingAsAdded(true);
239        mThisDevicePref = new Preference(getActivity());
240        preferenceScreen.addPreference(mThisDevicePref);
241
242        mPeersGroup = new PreferenceCategory(getActivity());
243        mPeersGroup.setTitle(R.string.wifi_p2p_peer_devices);
244
245        super.onActivityCreated(savedInstanceState);
246    }
247
248    @Override
249    public void onResume() {
250        super.onResume();
251        getActivity().registerReceiver(mReceiver, mIntentFilter);
252    }
253
254    @Override
255    public void onPause() {
256        super.onPause();
257        mWifiP2pManager.stopPeerDiscovery(mChannel, null);
258        getActivity().unregisterReceiver(mReceiver);
259    }
260
261    @Override
262    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
263        int textId = mWifiP2pSearching ? R.string.wifi_p2p_menu_searching :
264                R.string.wifi_p2p_menu_search;
265        menu.add(Menu.NONE, MENU_ID_SEARCH, 0, textId)
266            .setEnabled(mWifiP2pEnabled)
267            .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
268        menu.add(Menu.NONE, MENU_ID_RENAME, 0, R.string.wifi_p2p_menu_rename)
269            .setEnabled(mWifiP2pEnabled)
270            .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
271        super.onCreateOptionsMenu(menu, inflater);
272    }
273
274    @Override
275    public void onPrepareOptionsMenu(Menu menu) {
276        MenuItem searchMenu = menu.findItem(MENU_ID_SEARCH);
277        MenuItem renameMenu = menu.findItem(MENU_ID_RENAME);
278        if (mWifiP2pEnabled) {
279            searchMenu.setEnabled(true);
280            renameMenu.setEnabled(true);
281        } else {
282            searchMenu.setEnabled(false);
283            renameMenu.setEnabled(false);
284        }
285
286        if (mWifiP2pSearching) {
287            searchMenu.setTitle(R.string.wifi_p2p_menu_searching);
288        } else {
289            searchMenu.setTitle(R.string.wifi_p2p_menu_search);
290        }
291    }
292
293    @Override
294    public boolean onOptionsItemSelected(MenuItem item) {
295        switch (item.getItemId()) {
296            case MENU_ID_SEARCH:
297                startSearch();
298                return true;
299            case MENU_ID_RENAME:
300                showDialog(DIALOG_RENAME);
301                return true;
302        }
303        return super.onOptionsItemSelected(item);
304    }
305
306    @Override
307    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
308        if (preference instanceof WifiP2pPeer) {
309            mSelectedWifiPeer = (WifiP2pPeer) preference;
310            if (mSelectedWifiPeer.device.status == WifiP2pDevice.CONNECTED) {
311                showDialog(DIALOG_DISCONNECT);
312            } else if (mSelectedWifiPeer.device.status == WifiP2pDevice.INVITED) {
313                showDialog(DIALOG_CANCEL_CONNECT);
314            } else {
315                WifiP2pConfig config = new WifiP2pConfig();
316                config.deviceAddress = mSelectedWifiPeer.device.deviceAddress;
317
318                int forceWps = SystemProperties.getInt("wifidirect.wps", -1);
319
320                if (forceWps != -1) {
321                    config.wps.setup = forceWps;
322                } else {
323                    if (mSelectedWifiPeer.device.wpsPbcSupported()) {
324                        config.wps.setup = WpsInfo.PBC;
325                    } else if (mSelectedWifiPeer.device.wpsKeypadSupported()) {
326                        config.wps.setup = WpsInfo.KEYPAD;
327                    } else {
328                        config.wps.setup = WpsInfo.DISPLAY;
329                    }
330                }
331
332                mWifiP2pManager.connect(mChannel, config,
333                        new WifiP2pManager.ActionListener() {
334                            public void onSuccess() {
335                                if (DBG) Log.d(TAG, " connect success");
336                            }
337                            public void onFailure(int reason) {
338                                Log.e(TAG, " connect fail " + reason);
339                                Toast.makeText(getActivity(),
340                                        R.string.wifi_p2p_failed_connect_message,
341                                        Toast.LENGTH_SHORT).show();
342                            }
343                    });
344            }
345        }
346        return super.onPreferenceTreeClick(screen, preference);
347    }
348
349    @Override
350    public Dialog onCreateDialog(int id) {
351        if (id == DIALOG_DISCONNECT) {
352            String deviceName = TextUtils.isEmpty(mSelectedWifiPeer.device.deviceName) ?
353                    mSelectedWifiPeer.device.deviceAddress :
354                    mSelectedWifiPeer.device.deviceName;
355            String msg;
356            if (mConnectedDevices > 1) {
357                msg = getActivity().getString(R.string.wifi_p2p_disconnect_multiple_message,
358                        deviceName, mConnectedDevices - 1);
359            } else {
360                msg = getActivity().getString(R.string.wifi_p2p_disconnect_message, deviceName);
361            }
362            AlertDialog dialog = new AlertDialog.Builder(getActivity())
363                .setTitle(R.string.wifi_p2p_disconnect_title)
364                .setMessage(msg)
365                .setPositiveButton(getActivity().getString(R.string.dlg_ok), mDisconnectListener)
366                .setNegativeButton(getActivity().getString(R.string.dlg_cancel), null)
367                .create();
368            return dialog;
369        } else if (id == DIALOG_CANCEL_CONNECT) {
370            int stringId = R.string.wifi_p2p_cancel_connect_message;
371            String deviceName = TextUtils.isEmpty(mSelectedWifiPeer.device.deviceName) ?
372                    mSelectedWifiPeer.device.deviceAddress :
373                    mSelectedWifiPeer.device.deviceName;
374
375            AlertDialog dialog = new AlertDialog.Builder(getActivity())
376                .setTitle(R.string.wifi_p2p_cancel_connect_title)
377                .setMessage(getActivity().getString(stringId, deviceName))
378                .setPositiveButton(getActivity().getString(R.string.dlg_ok), mCancelConnectListener)
379                .setNegativeButton(getActivity().getString(R.string.dlg_cancel), null)
380                .create();
381            return dialog;
382        } else if (id == DIALOG_RENAME) {
383            mDeviceNameText = new EditText(getActivity());
384            if (mSavedDeviceName != null) {
385                mDeviceNameText.setText(mSavedDeviceName);
386                mDeviceNameText.setSelection(mSavedDeviceName.length());
387            } else if (mThisDevice != null && !TextUtils.isEmpty(mThisDevice.deviceName)) {
388                mDeviceNameText.setText(mThisDevice.deviceName);
389                mDeviceNameText.setSelection(0, mThisDevice.deviceName.length());
390            }
391            mSavedDeviceName = null;
392            AlertDialog dialog = new AlertDialog.Builder(getActivity())
393                .setTitle(R.string.wifi_p2p_menu_rename)
394                .setView(mDeviceNameText)
395                .setPositiveButton(getActivity().getString(R.string.dlg_ok), mRenameListener)
396                .setNegativeButton(getActivity().getString(R.string.dlg_cancel), null)
397                .create();
398            return dialog;
399        }
400        return null;
401    }
402
403    @Override
404    public void onSaveInstanceState(Bundle outState) {
405        if (mSelectedWifiPeer != null) {
406            outState.putParcelable(SAVE_DIALOG_PEER, mSelectedWifiPeer.device);
407        }
408        if (mDeviceNameText != null) {
409            outState.putString(SAVE_DEVICE_NAME, mDeviceNameText.getText().toString());
410        }
411    }
412
413    public void onPeersAvailable(WifiP2pDeviceList peers) {
414        mPeersGroup.removeAll();
415
416        mPeers = peers;
417        mConnectedDevices = 0;
418        for (WifiP2pDevice peer: peers.getDeviceList()) {
419            if (DBG) Log.d(TAG, " peer " + peer);
420            mPeersGroup.addPreference(new WifiP2pPeer(getActivity(), peer));
421            if (peer.status == WifiP2pDevice.CONNECTED) mConnectedDevices++;
422        }
423        if (DBG) Log.d(TAG, " mConnectedDevices " + mConnectedDevices);
424    }
425
426    private void handleP2pStateChanged() {
427        updateSearchMenu(false);
428        if (mWifiP2pEnabled) {
429            final PreferenceScreen preferenceScreen = getPreferenceScreen();
430            preferenceScreen.removeAll();
431
432            preferenceScreen.setOrderingAsAdded(true);
433            preferenceScreen.addPreference(mThisDevicePref);
434
435            mPeersGroup.setEnabled(true);
436            preferenceScreen.addPreference(mPeersGroup);
437
438            /* Request latest set of peers */
439            mWifiP2pManager.requestPeers(mChannel, WifiP2pSettings.this);
440        }
441    }
442
443    private void updateSearchMenu(boolean searching) {
444       mWifiP2pSearching = searching;
445       Activity activity = getActivity();
446       if (activity != null) activity.invalidateOptionsMenu();
447    }
448
449    private void startSearch() {
450        if (mWifiP2pManager != null && !mWifiP2pSearching) {
451            mWifiP2pManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
452                public void onSuccess() {
453                }
454                public void onFailure(int reason) {
455                    if (DBG) Log.d(TAG, " discover fail " + reason);
456                }
457            });
458        }
459    }
460
461    private void updateDevicePref() {
462        if (mThisDevice != null) {
463            if (TextUtils.isEmpty(mThisDevice.deviceName)) {
464                mThisDevicePref.setTitle(mThisDevice.deviceAddress);
465            } else {
466                mThisDevicePref.setTitle(mThisDevice.deviceName);
467            }
468
469            mThisDevicePref.setPersistent(false);
470            mThisDevicePref.setEnabled(true);
471            mThisDevicePref.setSelectable(false);
472        }
473    }
474}
475