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 33 package com.android.bluetooth.pbap; 34 35 import com.android.bluetooth.R; 36 37 import android.content.BroadcastReceiver; 38 import android.content.Context; 39 import android.content.DialogInterface; 40 import android.content.Intent; 41 import android.content.IntentFilter; 42 import android.os.Bundle; 43 import android.os.Handler; 44 import android.os.Message; 45 import android.preference.Preference; 46 import android.util.Log; 47 import android.view.View; 48 import android.widget.CheckBox; 49 import android.widget.CompoundButton; 50 import android.widget.EditText; 51 import android.widget.TextView; 52 import android.widget.Button; 53 import android.widget.CompoundButton.OnCheckedChangeListener; 54 import android.text.InputFilter; 55 import android.text.TextWatcher; 56 import android.text.InputFilter.LengthFilter; 57 58 import com.android.internal.app.AlertActivity; 59 import 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 */ 66 public 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_CONNECT = 1; 75 76 private static final int DIALOG_YES_NO_AUTH = 2; 77 78 private static final String KEY_USER_TIMEOUT = "user_timeout"; 79 80 private View mView; 81 82 private EditText mKeyView; 83 84 private TextView messageView; 85 86 private String mSessionKey = ""; 87 88 private int mCurrentDialog; 89 90 private Button mOkButton; 91 92 private CheckBox mAlwaysAllowed; 93 94 private boolean mTimeout = false; 95 96 private boolean mAlwaysAllowedValue = true; 97 98 private static final int DISMISS_TIMEOUT_DIALOG = 0; 99 100 private static final int DISMISS_TIMEOUT_DIALOG_VALUE = 2000; 101 102 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 103 @Override 104 public void onReceive(Context context, Intent intent) { 105 if (!BluetoothPbapService.USER_CONFIRM_TIMEOUT_ACTION.equals(intent.getAction())) { 106 return; 107 } 108 onTimeout(); 109 } 110 }; 111 112 @Override 113 protected void onCreate(Bundle savedInstanceState) { 114 super.onCreate(savedInstanceState); 115 Intent i = getIntent(); 116 String action = i.getAction(); 117 if (action.equals(BluetoothPbapService.ACCESS_REQUEST_ACTION)) { 118 showPbapDialog(DIALOG_YES_NO_CONNECT); 119 mCurrentDialog = DIALOG_YES_NO_CONNECT; 120 } else if (action.equals(BluetoothPbapService.AUTH_CHALL_ACTION)) { 121 showPbapDialog(DIALOG_YES_NO_AUTH); 122 mCurrentDialog = DIALOG_YES_NO_AUTH; 123 } else { 124 Log.e(TAG, "Error: this activity may be started only with intent " 125 + "PBAP_ACCESS_REQUEST or PBAP_AUTH_CHALL "); 126 finish(); 127 } 128 registerReceiver(mReceiver, new IntentFilter( 129 BluetoothPbapService.USER_CONFIRM_TIMEOUT_ACTION)); 130 } 131 132 private void showPbapDialog(int id) { 133 final AlertController.AlertParams p = mAlertParams; 134 switch (id) { 135 case DIALOG_YES_NO_CONNECT: 136 p.mIconId = android.R.drawable.ic_dialog_info; 137 p.mTitle = getString(R.string.pbap_acceptance_dialog_header); 138 p.mView = createView(DIALOG_YES_NO_CONNECT); 139 p.mPositiveButtonText = getString(android.R.string.yes); 140 p.mPositiveButtonListener = this; 141 p.mNegativeButtonText = getString(android.R.string.no); 142 p.mNegativeButtonListener = this; 143 mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE); 144 setupAlert(); 145 break; 146 case DIALOG_YES_NO_AUTH: 147 p.mIconId = android.R.drawable.ic_dialog_info; 148 p.mTitle = getString(R.string.pbap_session_key_dialog_header); 149 p.mView = createView(DIALOG_YES_NO_AUTH); 150 p.mPositiveButtonText = getString(android.R.string.ok); 151 p.mPositiveButtonListener = this; 152 p.mNegativeButtonText = getString(android.R.string.cancel); 153 p.mNegativeButtonListener = this; 154 setupAlert(); 155 mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE); 156 mOkButton.setEnabled(false); 157 break; 158 default: 159 break; 160 } 161 } 162 163 private String createDisplayText(final int id) { 164 String mRemoteName = BluetoothPbapService.getRemoteDeviceName(); 165 switch (id) { 166 case DIALOG_YES_NO_CONNECT: 167 String mMessage1 = getString(R.string.pbap_acceptance_dialog_title, mRemoteName, 168 mRemoteName); 169 return mMessage1; 170 case DIALOG_YES_NO_AUTH: 171 String mMessage2 = getString(R.string.pbap_session_key_dialog_title, mRemoteName); 172 return mMessage2; 173 default: 174 return null; 175 } 176 } 177 178 private View createView(final int id) { 179 switch (id) { 180 case DIALOG_YES_NO_CONNECT: 181 mView = getLayoutInflater().inflate(R.layout.access, null); 182 messageView = (TextView)mView.findViewById(R.id.message); 183 messageView.setText(createDisplayText(id)); 184 mAlwaysAllowed = (CheckBox)mView.findViewById(R.id.alwaysallowed); 185 mAlwaysAllowed.setChecked(true); 186 mAlwaysAllowed.setOnCheckedChangeListener(new OnCheckedChangeListener() { 187 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 188 if (isChecked) { 189 mAlwaysAllowedValue = true; 190 } else { 191 mAlwaysAllowedValue = false; 192 } 193 } 194 }); 195 return mView; 196 case DIALOG_YES_NO_AUTH: 197 mView = getLayoutInflater().inflate(R.layout.auth, null); 198 messageView = (TextView)mView.findViewById(R.id.message); 199 messageView.setText(createDisplayText(id)); 200 mKeyView = (EditText)mView.findViewById(R.id.text); 201 mKeyView.addTextChangedListener(this); 202 mKeyView.setFilters(new InputFilter[] { 203 new LengthFilter(BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH) 204 }); 205 return mView; 206 default: 207 return null; 208 } 209 } 210 211 private void onPositive() { 212 if (!mTimeout) { 213 if (mCurrentDialog == DIALOG_YES_NO_CONNECT) { 214 sendIntentToReceiver(BluetoothPbapService.ACCESS_ALLOWED_ACTION, 215 BluetoothPbapService.EXTRA_ALWAYS_ALLOWED, mAlwaysAllowedValue); 216 } else if (mCurrentDialog == DIALOG_YES_NO_AUTH) { 217 sendIntentToReceiver(BluetoothPbapService.AUTH_RESPONSE_ACTION, 218 BluetoothPbapService.EXTRA_SESSION_KEY, mSessionKey); 219 mKeyView.removeTextChangedListener(this); 220 } 221 } 222 mTimeout = false; 223 finish(); 224 } 225 226 private void onNegative() { 227 if (mCurrentDialog == DIALOG_YES_NO_CONNECT) { 228 sendIntentToReceiver(BluetoothPbapService.ACCESS_DISALLOWED_ACTION, null, null); 229 } else if (mCurrentDialog == DIALOG_YES_NO_AUTH) { 230 sendIntentToReceiver(BluetoothPbapService.AUTH_CANCELLED_ACTION, null, null); 231 mKeyView.removeTextChangedListener(this); 232 } 233 finish(); 234 } 235 236 private void sendIntentToReceiver(final String intentName, final String extraName, 237 final String extraValue) { 238 Intent intent = new Intent(intentName); 239 intent.setClassName(BluetoothPbapService.THIS_PACKAGE_NAME, BluetoothPbapReceiver.class 240 .getName()); 241 if (extraName != null) { 242 intent.putExtra(extraName, extraValue); 243 } 244 sendBroadcast(intent); 245 } 246 247 private void sendIntentToReceiver(final String intentName, final String extraName, 248 final boolean extraValue) { 249 Intent intent = new Intent(intentName); 250 intent.setClassName(BluetoothPbapService.THIS_PACKAGE_NAME, BluetoothPbapReceiver.class 251 .getName()); 252 if (extraName != null) { 253 intent.putExtra(extraName, extraValue); 254 } 255 sendBroadcast(intent); 256 } 257 258 public void onClick(DialogInterface dialog, int which) { 259 switch (which) { 260 case DialogInterface.BUTTON_POSITIVE: 261 if (mCurrentDialog == DIALOG_YES_NO_AUTH) { 262 mSessionKey = mKeyView.getText().toString(); 263 } 264 onPositive(); 265 break; 266 267 case DialogInterface.BUTTON_NEGATIVE: 268 onNegative(); 269 break; 270 default: 271 break; 272 } 273 } 274 275 private void onTimeout() { 276 mTimeout = true; 277 if (mCurrentDialog == DIALOG_YES_NO_CONNECT) { 278 messageView.setText(getString(R.string.pbap_acceptance_timeout_message, 279 BluetoothPbapService.getRemoteDeviceName())); 280 mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE); 281 mAlwaysAllowed.setVisibility(View.GONE); 282 mAlwaysAllowed.clearFocus(); 283 } else if (mCurrentDialog == DIALOG_YES_NO_AUTH) { 284 messageView.setText(getString(R.string.pbap_authentication_timeout_message, 285 BluetoothPbapService.getRemoteDeviceName())); 286 mKeyView.setVisibility(View.GONE); 287 mKeyView.clearFocus(); 288 mKeyView.removeTextChangedListener(this); 289 mOkButton.setEnabled(true); 290 mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE); 291 } 292 293 mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(DISMISS_TIMEOUT_DIALOG), 294 DISMISS_TIMEOUT_DIALOG_VALUE); 295 } 296 297 @Override 298 protected void onRestoreInstanceState(Bundle savedInstanceState) { 299 super.onRestoreInstanceState(savedInstanceState); 300 mTimeout = savedInstanceState.getBoolean(KEY_USER_TIMEOUT); 301 if (V) Log.v(TAG, "onRestoreInstanceState() mTimeout: " + mTimeout); 302 if (mTimeout) { 303 onTimeout(); 304 } 305 } 306 307 @Override 308 protected void onSaveInstanceState(Bundle outState) { 309 super.onSaveInstanceState(outState); 310 outState.putBoolean(KEY_USER_TIMEOUT, mTimeout); 311 } 312 313 @Override 314 protected void onDestroy() { 315 super.onDestroy(); 316 unregisterReceiver(mReceiver); 317 } 318 319 public boolean onPreferenceChange(Preference preference, Object newValue) { 320 return true; 321 } 322 323 public void beforeTextChanged(CharSequence s, int start, int before, int after) { 324 } 325 326 public void onTextChanged(CharSequence s, int start, int before, int count) { 327 } 328 329 public void afterTextChanged(android.text.Editable s) { 330 if (s.length() > 0) { 331 mOkButton.setEnabled(true); 332 } 333 } 334 335 private final Handler mTimeoutHandler = new Handler() { 336 @Override 337 public void handleMessage(Message msg) { 338 switch (msg.what) { 339 case DISMISS_TIMEOUT_DIALOG: 340 if (V) Log.v(TAG, "Received DISMISS_TIMEOUT_DIALOG msg."); 341 finish(); 342 break; 343 default: 344 break; 345 } 346 } 347 }; 348 } 349