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