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.mIconId = android.R.drawable.ic_dialog_info;
132                p.mTitle = getString(R.string.pbap_session_key_dialog_header);
133                p.mView = createView(DIALOG_YES_NO_AUTH);
134                p.mPositiveButtonText = getString(android.R.string.ok);
135                p.mPositiveButtonListener = this;
136                p.mNegativeButtonText = getString(android.R.string.cancel);
137                p.mNegativeButtonListener = this;
138                setupAlert();
139                mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
140                mOkButton.setEnabled(false);
141                break;
142            default:
143                break;
144        }
145    }
146
147    private String createDisplayText(final int id) {
148        String mRemoteName = BluetoothPbapService.getRemoteDeviceName();
149        switch (id) {
150            case DIALOG_YES_NO_AUTH:
151                String mMessage2 = getString(R.string.pbap_session_key_dialog_title, mRemoteName);
152                return mMessage2;
153            default:
154                return null;
155        }
156    }
157
158    private View createView(final int id) {
159        switch (id) {
160            case DIALOG_YES_NO_AUTH:
161                mView = getLayoutInflater().inflate(R.layout.auth, null);
162                messageView = (TextView)mView.findViewById(R.id.message);
163                messageView.setText(createDisplayText(id));
164                mKeyView = (EditText)mView.findViewById(R.id.text);
165                mKeyView.addTextChangedListener(this);
166                mKeyView.setFilters(new InputFilter[] {
167                    new LengthFilter(BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH)
168                });
169                return mView;
170            default:
171                return null;
172        }
173    }
174
175    private void onPositive() {
176        if (!mTimeout) {
177            if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
178                sendIntentToReceiver(BluetoothPbapService.AUTH_RESPONSE_ACTION,
179                        BluetoothPbapService.EXTRA_SESSION_KEY, mSessionKey);
180                mKeyView.removeTextChangedListener(this);
181            }
182        }
183        mTimeout = false;
184        finish();
185    }
186
187    private void onNegative() {
188        if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
189            sendIntentToReceiver(BluetoothPbapService.AUTH_CANCELLED_ACTION, null, null);
190            mKeyView.removeTextChangedListener(this);
191        }
192        finish();
193    }
194
195    private void sendIntentToReceiver(final String intentName, final String extraName,
196            final String extraValue) {
197        Intent intent = new Intent(intentName);
198        intent.setClassName(BluetoothPbapService.THIS_PACKAGE_NAME, BluetoothPbapReceiver.class
199                .getName());
200        if (extraName != null) {
201            intent.putExtra(extraName, extraValue);
202        }
203        sendBroadcast(intent);
204    }
205
206    private void sendIntentToReceiver(final String intentName, final String extraName,
207            final boolean extraValue) {
208        Intent intent = new Intent(intentName);
209        intent.setClassName(BluetoothPbapService.THIS_PACKAGE_NAME, BluetoothPbapReceiver.class
210                .getName());
211        if (extraName != null) {
212            intent.putExtra(extraName, extraValue);
213        }
214        sendBroadcast(intent);
215    }
216
217    public void onClick(DialogInterface dialog, int which) {
218        switch (which) {
219            case DialogInterface.BUTTON_POSITIVE:
220                if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
221                    mSessionKey = mKeyView.getText().toString();
222                }
223                onPositive();
224                break;
225
226            case DialogInterface.BUTTON_NEGATIVE:
227                onNegative();
228                break;
229            default:
230                break;
231        }
232    }
233
234    private void onTimeout() {
235        mTimeout = true;
236        if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
237            messageView.setText(getString(R.string.pbap_authentication_timeout_message,
238                    BluetoothPbapService.getRemoteDeviceName()));
239            mKeyView.setVisibility(View.GONE);
240            mKeyView.clearFocus();
241            mKeyView.removeTextChangedListener(this);
242            mOkButton.setEnabled(true);
243            mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
244        }
245
246        mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(DISMISS_TIMEOUT_DIALOG),
247                DISMISS_TIMEOUT_DIALOG_VALUE);
248    }
249
250    @Override
251    protected void onRestoreInstanceState(Bundle savedInstanceState) {
252        super.onRestoreInstanceState(savedInstanceState);
253        mTimeout = savedInstanceState.getBoolean(KEY_USER_TIMEOUT);
254        if (V) Log.v(TAG, "onRestoreInstanceState() mTimeout: " + mTimeout);
255        if (mTimeout) {
256            onTimeout();
257        }
258    }
259
260    @Override
261    protected void onSaveInstanceState(Bundle outState) {
262        super.onSaveInstanceState(outState);
263        outState.putBoolean(KEY_USER_TIMEOUT, mTimeout);
264    }
265
266    @Override
267    protected void onDestroy() {
268        super.onDestroy();
269        unregisterReceiver(mReceiver);
270    }
271
272    public boolean onPreferenceChange(Preference preference, Object newValue) {
273        return true;
274    }
275
276    public void beforeTextChanged(CharSequence s, int start, int before, int after) {
277    }
278
279    public void onTextChanged(CharSequence s, int start, int before, int count) {
280    }
281
282    public void afterTextChanged(android.text.Editable s) {
283        if (s.length() > 0) {
284            mOkButton.setEnabled(true);
285        }
286    }
287
288    private final Handler mTimeoutHandler = new Handler() {
289        @Override
290        public void handleMessage(Message msg) {
291            switch (msg.what) {
292                case DISMISS_TIMEOUT_DIALOG:
293                    if (V) Log.v(TAG, "Received DISMISS_TIMEOUT_DIALOG msg.");
294                    finish();
295                    break;
296                default:
297                    break;
298            }
299        }
300    };
301}
302