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 static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 36 37 import android.bluetooth.AlertActivity; 38 import android.bluetooth.BluetoothDevice; 39 import android.content.BroadcastReceiver; 40 import android.content.Context; 41 import android.content.DialogInterface; 42 import android.content.Intent; 43 import android.content.IntentFilter; 44 import android.os.Bundle; 45 import android.os.Handler; 46 import android.os.Message; 47 import android.preference.Preference; 48 import android.text.InputFilter; 49 import android.text.InputFilter.LengthFilter; 50 import android.text.TextWatcher; 51 import android.util.Log; 52 import android.view.View; 53 import android.widget.EditText; 54 import android.widget.TextView; 55 56 import com.android.bluetooth.R; 57 import com.android.internal.annotations.VisibleForTesting; 58 59 /** 60 * PbapActivity shows two dialogues: One for accepting incoming pbap request and 61 * the other prompts the user to enter a session key for authentication with a 62 * remote Bluetooth device. 63 */ 64 public class BluetoothPbapActivity extends AlertActivity 65 implements Preference.OnPreferenceChangeListener, TextWatcher { 66 private static final String TAG = "BluetoothPbapActivity"; 67 68 private static final boolean V = BluetoothPbapService.VERBOSE; 69 70 private static final int BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH = 16; 71 72 @VisibleForTesting 73 static final int DIALOG_YES_NO_AUTH = 1; 74 75 private static final String KEY_USER_TIMEOUT = "user_timeout"; 76 77 private View mView; 78 79 private EditText mKeyView; 80 81 private TextView mMessageView; 82 83 private String mSessionKey = ""; 84 85 @VisibleForTesting 86 int mCurrentDialog; 87 88 private boolean mTimeout = false; 89 90 private static final int DISMISS_TIMEOUT_DIALOG = 0; 91 92 private static final int DISMISS_TIMEOUT_DIALOG_VALUE = 2000; 93 94 private BluetoothDevice mDevice; 95 96 @VisibleForTesting 97 BroadcastReceiver mReceiver = new BroadcastReceiver() { 98 @Override 99 public void onReceive(Context context, Intent intent) { 100 if (!BluetoothPbapService.USER_CONFIRM_TIMEOUT_ACTION.equals(intent.getAction())) { 101 return; 102 } 103 onTimeout(); 104 } 105 }; 106 107 @Override onCreate(Bundle savedInstanceState)108 protected void onCreate(Bundle savedInstanceState) { 109 super.onCreate(savedInstanceState); 110 111 getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 112 Intent i = getIntent(); 113 String action = i.getAction(); 114 mDevice = i.getParcelableExtra(BluetoothPbapService.EXTRA_DEVICE); 115 if (action != null && 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, 124 new IntentFilter(BluetoothPbapService.USER_CONFIRM_TIMEOUT_ACTION)); 125 } 126 showPbapDialog(int id)127 private void showPbapDialog(int id) { 128 switch (id) { 129 case DIALOG_YES_NO_AUTH: 130 mAlertBuilder.setTitle(getString(R.string.pbap_session_key_dialog_header)); 131 mAlertBuilder.setView(createView(DIALOG_YES_NO_AUTH)); 132 mAlertBuilder.setPositiveButton(android.R.string.ok, 133 (dialog, which) -> onPositive()); 134 mAlertBuilder.setNegativeButton(android.R.string.cancel, 135 (dialog, which) -> onNegative()); 136 setupAlert(); 137 changeButtonEnabled(DialogInterface.BUTTON_POSITIVE, false); 138 break; 139 default: 140 break; 141 } 142 } 143 createDisplayText(final int id)144 private String createDisplayText(final int id) { 145 switch (id) { 146 case DIALOG_YES_NO_AUTH: 147 String mMessage2 = getString(R.string.pbap_session_key_dialog_title, mDevice); 148 return mMessage2; 149 default: 150 return null; 151 } 152 } 153 createView(final int id)154 private View createView(final int id) { 155 switch (id) { 156 case DIALOG_YES_NO_AUTH: 157 mView = getLayoutInflater().inflate(R.layout.auth, null); 158 mMessageView = (TextView) mView.findViewById(R.id.message); 159 mMessageView.setText(createDisplayText(id)); 160 mKeyView = (EditText) mView.findViewById(R.id.text); 161 mKeyView.addTextChangedListener(this); 162 mKeyView.setFilters(new InputFilter[]{ 163 new LengthFilter(BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH) 164 }); 165 return mView; 166 default: 167 return null; 168 } 169 } 170 171 @VisibleForTesting onPositive()172 void onPositive() { 173 if (mCurrentDialog == DIALOG_YES_NO_AUTH) { 174 mSessionKey = mKeyView.getText().toString(); 175 } 176 177 if (!mTimeout) { 178 if (mCurrentDialog == DIALOG_YES_NO_AUTH) { 179 sendIntentToReceiver(BluetoothPbapService.AUTH_RESPONSE_ACTION, 180 BluetoothPbapService.EXTRA_SESSION_KEY, mSessionKey); 181 mKeyView.removeTextChangedListener(this); 182 } 183 } 184 mTimeout = false; 185 finish(); 186 } 187 188 @VisibleForTesting onNegative()189 void onNegative() { 190 if (mCurrentDialog == DIALOG_YES_NO_AUTH) { 191 sendIntentToReceiver(BluetoothPbapService.AUTH_CANCELLED_ACTION, null, null); 192 mKeyView.removeTextChangedListener(this); 193 } 194 finish(); 195 } 196 sendIntentToReceiver(final String intentName, final String extraName, final String extraValue)197 private void sendIntentToReceiver(final String intentName, final String extraName, 198 final String extraValue) { 199 Intent intent = new Intent(intentName); 200 intent.setPackage(getPackageName()); 201 intent.putExtra(BluetoothPbapService.EXTRA_DEVICE, mDevice); 202 if (extraName != null) { 203 intent.putExtra(extraName, extraValue); 204 } 205 sendBroadcast(intent); 206 } 207 208 @VisibleForTesting onTimeout()209 private void onTimeout() { 210 mTimeout = true; 211 if (mCurrentDialog == DIALOG_YES_NO_AUTH) { 212 mMessageView.setText(getString(R.string.pbap_authentication_timeout_message, mDevice)); 213 mKeyView.setVisibility(View.GONE); 214 mKeyView.clearFocus(); 215 mKeyView.removeTextChangedListener(this); 216 changeButtonEnabled(DialogInterface.BUTTON_POSITIVE, true); 217 changeButtonVisibility(DialogInterface.BUTTON_NEGATIVE, View.GONE); 218 } 219 220 mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(DISMISS_TIMEOUT_DIALOG), 221 DISMISS_TIMEOUT_DIALOG_VALUE); 222 } 223 224 @Override onRestoreInstanceState(Bundle savedInstanceState)225 protected void onRestoreInstanceState(Bundle savedInstanceState) { 226 super.onRestoreInstanceState(savedInstanceState); 227 mTimeout = savedInstanceState.getBoolean(KEY_USER_TIMEOUT); 228 if (V) { 229 Log.v(TAG, "onRestoreInstanceState() mTimeout: " + mTimeout); 230 } 231 if (mTimeout) { 232 onTimeout(); 233 } 234 } 235 236 @Override onSaveInstanceState(Bundle outState)237 protected void onSaveInstanceState(Bundle outState) { 238 super.onSaveInstanceState(outState); 239 outState.putBoolean(KEY_USER_TIMEOUT, mTimeout); 240 } 241 242 @Override onDestroy()243 protected void onDestroy() { 244 super.onDestroy(); 245 unregisterReceiver(mReceiver); 246 } 247 248 @Override onPreferenceChange(Preference preference, Object newValue)249 public boolean onPreferenceChange(Preference preference, Object newValue) { 250 return true; 251 } 252 253 @Override beforeTextChanged(CharSequence s, int start, int before, int after)254 public void beforeTextChanged(CharSequence s, int start, int before, int after) { 255 } 256 257 @Override onTextChanged(CharSequence s, int start, int before, int count)258 public void onTextChanged(CharSequence s, int start, int before, int count) { 259 } 260 261 @Override afterTextChanged(android.text.Editable s)262 public void afterTextChanged(android.text.Editable s) { 263 if (s.length() > 0) { 264 changeButtonEnabled(DialogInterface.BUTTON_POSITIVE, true); 265 } 266 } 267 268 private final Handler mTimeoutHandler = new Handler() { 269 @Override 270 public void handleMessage(Message msg) { 271 switch (msg.what) { 272 case DISMISS_TIMEOUT_DIALOG: 273 if (V) { 274 Log.v(TAG, "Received DISMISS_TIMEOUT_DIALOG msg."); 275 } 276 finish(); 277 break; 278 default: 279 break; 280 } 281 } 282 }; 283 } 284