1/*
2 * Copyright (c) 2008-2009, Motorola, Inc.
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * - Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * - Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * - Neither the name of the Motorola, Inc. nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.android.bluetooth.pbap;
34
35import com.android.bluetooth.R;
36
37import android.content.BroadcastReceiver;
38import android.content.Context;
39import android.content.DialogInterface;
40import android.content.Intent;
41import android.content.IntentFilter;
42import android.os.Bundle;
43import android.os.Handler;
44import android.os.Message;
45import android.preference.Preference;
46import android.util.Log;
47import android.view.View;
48import android.widget.CheckBox;
49import android.widget.CompoundButton;
50import android.widget.EditText;
51import android.widget.TextView;
52import android.widget.Button;
53import android.widget.CompoundButton.OnCheckedChangeListener;
54import android.text.InputFilter;
55import android.text.TextWatcher;
56import android.text.InputFilter.LengthFilter;
57
58import com.android.internal.app.AlertActivity;
59import com.android.internal.app.AlertController;
60
61/**
62 * PbapActivity shows two dialogues: One for accepting incoming pbap request and
63 * the other prompts the user to enter a session key for authentication with a
64 * remote Bluetooth device.
65 */
66public class BluetoothPbapActivity extends AlertActivity implements
67        DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener, TextWatcher {
68    private static final String TAG = "BluetoothPbapActivity";
69
70    private static final boolean V = BluetoothPbapService.VERBOSE;
71
72    private static final int BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH = 16;
73
74    private static final int DIALOG_YES_NO_AUTH = 1;
75
76    private static final String KEY_USER_TIMEOUT = "user_timeout";
77
78    private View mView;
79
80    private EditText mKeyView;
81
82    private TextView messageView;
83
84    private String mSessionKey = "";
85
86    private int mCurrentDialog;
87
88    private Button mOkButton;
89
90    private CheckBox mAlwaysAllowed;
91
92    private boolean mTimeout = false;
93
94    private boolean mAlwaysAllowedValue = true;
95
96    private static final int DISMISS_TIMEOUT_DIALOG = 0;
97
98    private static final int DISMISS_TIMEOUT_DIALOG_VALUE = 2000;
99
100    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
101        @Override
102        public void onReceive(Context context, Intent intent) {
103            if (!BluetoothPbapService.USER_CONFIRM_TIMEOUT_ACTION.equals(intent.getAction())) {
104                return;
105            }
106            onTimeout();
107        }
108    };
109
110    @Override
111    protected void onCreate(Bundle savedInstanceState) {
112        super.onCreate(savedInstanceState);
113        Intent i = getIntent();
114        String action = i.getAction();
115        if (action.equals(BluetoothPbapService.AUTH_CHALL_ACTION)) {
116            showPbapDialog(DIALOG_YES_NO_AUTH);
117            mCurrentDialog = DIALOG_YES_NO_AUTH;
118        } else {
119            Log.e(TAG, "Error: this activity may be started only with intent "
120                    + "PBAP_ACCESS_REQUEST or PBAP_AUTH_CHALL ");
121            finish();
122        }
123        registerReceiver(mReceiver, new IntentFilter(
124                BluetoothPbapService.USER_CONFIRM_TIMEOUT_ACTION));
125    }
126
127    private void showPbapDialog(int id) {
128        final AlertController.AlertParams p = mAlertParams;
129        switch (id) {
130            case DIALOG_YES_NO_AUTH:
131                p.mTitle = getString(R.string.pbap_session_key_dialog_header);
132                p.mView = createView(DIALOG_YES_NO_AUTH);
133                p.mPositiveButtonText = getString(android.R.string.ok);
134                p.mPositiveButtonListener = this;
135                p.mNegativeButtonText = getString(android.R.string.cancel);
136                p.mNegativeButtonListener = this;
137                setupAlert();
138                mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
139                mOkButton.setEnabled(false);
140                break;
141            default:
142                break;
143        }
144    }
145
146    private String createDisplayText(final int id) {
147        String mRemoteName = BluetoothPbapService.getRemoteDeviceName();
148        switch (id) {
149            case DIALOG_YES_NO_AUTH:
150                String mMessage2 = getString(R.string.pbap_session_key_dialog_title, mRemoteName);
151                return mMessage2;
152            default:
153                return null;
154        }
155    }
156
157    private View createView(final int id) {
158        switch (id) {
159            case DIALOG_YES_NO_AUTH:
160                mView = getLayoutInflater().inflate(R.layout.auth, null);
161                messageView = (TextView)mView.findViewById(R.id.message);
162                messageView.setText(createDisplayText(id));
163                mKeyView = (EditText)mView.findViewById(R.id.text);
164                mKeyView.addTextChangedListener(this);
165                mKeyView.setFilters(new InputFilter[] {
166                    new LengthFilter(BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH)
167                });
168                return mView;
169            default:
170                return null;
171        }
172    }
173
174    private void onPositive() {
175        if (!mTimeout) {
176            if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
177                sendIntentToReceiver(BluetoothPbapService.AUTH_RESPONSE_ACTION,
178                        BluetoothPbapService.EXTRA_SESSION_KEY, mSessionKey);
179                mKeyView.removeTextChangedListener(this);
180            }
181        }
182        mTimeout = false;
183        finish();
184    }
185
186    private void onNegative() {
187        if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
188            sendIntentToReceiver(BluetoothPbapService.AUTH_CANCELLED_ACTION, null, null);
189            mKeyView.removeTextChangedListener(this);
190        }
191        finish();
192    }
193
194    private void sendIntentToReceiver(final String intentName, final String extraName,
195            final String extraValue) {
196        Intent intent = new Intent(intentName);
197        intent.setClassName(BluetoothPbapService.THIS_PACKAGE_NAME, BluetoothPbapReceiver.class
198                .getName());
199        if (extraName != null) {
200            intent.putExtra(extraName, extraValue);
201        }
202        sendBroadcast(intent);
203    }
204
205    private void sendIntentToReceiver(final String intentName, final String extraName,
206            final boolean extraValue) {
207        Intent intent = new Intent(intentName);
208        intent.setClassName(BluetoothPbapService.THIS_PACKAGE_NAME, BluetoothPbapReceiver.class
209                .getName());
210        if (extraName != null) {
211            intent.putExtra(extraName, extraValue);
212        }
213        sendBroadcast(intent);
214    }
215
216    public void onClick(DialogInterface dialog, int which) {
217        switch (which) {
218            case DialogInterface.BUTTON_POSITIVE:
219                if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
220                    mSessionKey = mKeyView.getText().toString();
221                }
222                onPositive();
223                break;
224
225            case DialogInterface.BUTTON_NEGATIVE:
226                onNegative();
227                break;
228            default:
229                break;
230        }
231    }
232
233    private void onTimeout() {
234        mTimeout = true;
235        if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
236            messageView.setText(getString(R.string.pbap_authentication_timeout_message,
237                    BluetoothPbapService.getRemoteDeviceName()));
238            mKeyView.setVisibility(View.GONE);
239            mKeyView.clearFocus();
240            mKeyView.removeTextChangedListener(this);
241            mOkButton.setEnabled(true);
242            mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
243        }
244
245        mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(DISMISS_TIMEOUT_DIALOG),
246                DISMISS_TIMEOUT_DIALOG_VALUE);
247    }
248
249    @Override
250    protected void onRestoreInstanceState(Bundle savedInstanceState) {
251        super.onRestoreInstanceState(savedInstanceState);
252        mTimeout = savedInstanceState.getBoolean(KEY_USER_TIMEOUT);
253        if (V) Log.v(TAG, "onRestoreInstanceState() mTimeout: " + mTimeout);
254        if (mTimeout) {
255            onTimeout();
256        }
257    }
258
259    @Override
260    protected void onSaveInstanceState(Bundle outState) {
261        super.onSaveInstanceState(outState);
262        outState.putBoolean(KEY_USER_TIMEOUT, mTimeout);
263    }
264
265    @Override
266    protected void onDestroy() {
267        super.onDestroy();
268        unregisterReceiver(mReceiver);
269    }
270
271    public boolean onPreferenceChange(Preference preference, Object newValue) {
272        return true;
273    }
274
275    public void beforeTextChanged(CharSequence s, int start, int before, int after) {
276    }
277
278    public void onTextChanged(CharSequence s, int start, int before, int count) {
279    }
280
281    public void afterTextChanged(android.text.Editable s) {
282        if (s.length() > 0) {
283            mOkButton.setEnabled(true);
284        }
285    }
286
287    private final Handler mTimeoutHandler = new Handler() {
288        @Override
289        public void handleMessage(Message msg) {
290            switch (msg.what) {
291                case DISMISS_TIMEOUT_DIALOG:
292                    if (V) Log.v(TAG, "Received DISMISS_TIMEOUT_DIALOG msg.");
293                    finish();
294                    break;
295                default:
296                    break;
297            }
298        }
299    };
300}
301