1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.verifier.bluetooth; 18 19 import com.android.cts.verifier.PassFailButtons; 20 import com.android.cts.verifier.R; 21 import com.android.cts.verifier.TestResult; 22 23 import android.app.AlertDialog; 24 import android.bluetooth.BluetoothAdapter; 25 import android.bluetooth.BluetoothDevice; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.Message; 34 import android.view.View; 35 import android.view.View.OnClickListener; 36 import android.widget.ArrayAdapter; 37 import android.widget.Button; 38 import android.widget.ListView; 39 import android.widget.ProgressBar; 40 import android.widget.TextView; 41 import android.widget.Toast; 42 43 import java.util.UUID; 44 import java.util.regex.Matcher; 45 import java.util.regex.Pattern; 46 47 class MessageTestActivity extends PassFailButtons.Activity { 48 49 /** Broadcast action that should only be fired when pairing for a secure connection. */ 50 private static final String ACTION_PAIRING_REQUEST = 51 "android.bluetooth.device.action.PAIRING_REQUEST"; 52 53 private static final int ENABLE_BLUETOOTH_REQUEST = 1; 54 private static final int PICK_SERVER_DEVICE_REQUEST = 2; 55 56 private static final String MESSAGE_DELIMITER = "\n"; 57 private static final Pattern MESSAGE_PATTERN = Pattern.compile("Message (\\d+) to .*"); 58 59 private BluetoothAdapter mBluetoothAdapter; 60 private PairingActionReceiver mPairingActionReceiver; 61 private BluetoothChatService mChatService; 62 63 private ArrayAdapter<String> mReceivedMessagesAdapter; 64 private ArrayAdapter<String> mSentMessagesAdapter; 65 66 private ListView mReceivedMessages; 67 private ListView mSentMessages; 68 69 private TextView mEmptyReceivedView; 70 private TextView mEmptySentView; 71 72 private AlertDialog mInstructionsDialog; 73 74 private ProgressBar mProgressBar; 75 76 private String mDeviceAddress; 77 78 private final boolean mSecure; 79 private final boolean mServer; 80 private final UUID mUuid; 81 82 private String mRemoteDeviceName = ""; 83 private StringBuilder mMessageBuffer = new StringBuilder(); 84 MessageTestActivity(boolean secure, boolean server, UUID uuid)85 MessageTestActivity(boolean secure, boolean server, UUID uuid) { 86 mSecure = secure; 87 mServer = server; 88 mUuid = uuid; 89 } 90 91 @Override onCreate(Bundle savedInstanceState)92 protected void onCreate(Bundle savedInstanceState) { 93 super.onCreate(savedInstanceState); 94 setContentView(R.layout.bt_messages); 95 setPassFailButtonClickListeners(); 96 97 mProgressBar = (ProgressBar) findViewById(R.id.bt_progress_bar); 98 99 if (mServer) { 100 setTitle(mSecure ? R.string.bt_secure_server : R.string.bt_insecure_server); 101 } else { 102 setTitle(mSecure ? R.string.bt_secure_client : R.string.bt_insecure_client); 103 } 104 105 mReceivedMessages = (ListView) findViewById(R.id.bt_received_messages); 106 mReceivedMessagesAdapter = new ArrayAdapter<String>(this, R.layout.bt_message_row); 107 mReceivedMessages.setAdapter(mReceivedMessagesAdapter); 108 109 mSentMessages = (ListView) findViewById(R.id.bt_sent_messages); 110 mSentMessagesAdapter = new ArrayAdapter<String>(this, R.layout.bt_message_row); 111 mSentMessages.setAdapter(mSentMessagesAdapter); 112 113 mEmptyReceivedView = (TextView) findViewById(R.id.bt_empty_received_messages); 114 mReceivedMessages.setEmptyView(mEmptyReceivedView); 115 116 mEmptySentView = (TextView) findViewById(R.id.bt_empty_sent_messages); 117 mSentMessages.setEmptyView(mEmptySentView); 118 119 setEmptyViewText(R.string.bt_no_messages); 120 121 Button makeDiscoverableButton = (Button) findViewById(R.id.bt_make_discoverable_button); 122 makeDiscoverableButton.setVisibility(mServer ? View.VISIBLE : View.GONE); 123 makeDiscoverableButton.setOnClickListener(new OnClickListener() { 124 @Override 125 public void onClick(View v) { 126 makeDiscoverable(); 127 } 128 }); 129 130 getPassButton().setEnabled(false); 131 132 mPairingActionReceiver = new PairingActionReceiver(); 133 IntentFilter intentFilter = new IntentFilter(ACTION_PAIRING_REQUEST); 134 registerReceiver(mPairingActionReceiver, intentFilter); 135 136 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 137 if (!mServer) { 138 Intent intent = new Intent(this, DevicePickerActivity.class); 139 startActivityForResult(intent, PICK_SERVER_DEVICE_REQUEST); 140 } else { 141 if (mBluetoothAdapter.isEnabled()) { 142 startChatService(); 143 } else { 144 Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 145 startActivityForResult(intent, ENABLE_BLUETOOTH_REQUEST); 146 } 147 } 148 } 149 150 @Override onActivityResult(int requestCode, int resultCode, Intent data)151 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 152 super.onActivityResult(requestCode, resultCode, data); 153 switch (requestCode) { 154 case ENABLE_BLUETOOTH_REQUEST: 155 if (resultCode == RESULT_OK) { 156 startChatService(); 157 } else { 158 setResult(RESULT_CANCELED); 159 finish(); 160 } 161 break; 162 163 case PICK_SERVER_DEVICE_REQUEST: 164 if (resultCode == RESULT_OK) { 165 mDeviceAddress = data.getStringExtra(DevicePickerActivity.EXTRA_DEVICE_ADDRESS); 166 startChatService(); 167 } else { 168 setResult(RESULT_CANCELED); 169 finish(); 170 } 171 break; 172 } 173 } 174 startChatService()175 private void startChatService() { 176 mChatService = new BluetoothChatService(this, new ChatHandler(), mUuid); 177 if (mServer) { 178 mChatService.start(mSecure); 179 } else { 180 BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mDeviceAddress); 181 mChatService.connect(device, mSecure); 182 } 183 } 184 makeDiscoverable()185 private void makeDiscoverable() { 186 if (mBluetoothAdapter.getScanMode() != 187 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { 188 Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); 189 intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 30); 190 startActivity(intent); 191 } 192 } 193 194 private class ChatHandler extends Handler { 195 @Override handleMessage(Message msg)196 public void handleMessage(Message msg) { 197 super.handleMessage(msg); 198 switch (msg.what) { 199 case BluetoothChatService.MESSAGE_STATE_CHANGE: 200 handleStateChange(msg); 201 break; 202 case BluetoothChatService.MESSAGE_READ: 203 handleMessageRead(msg); 204 break; 205 case BluetoothChatService.MESSAGE_WRITE: 206 handleMessageWrite(msg); 207 break; 208 case BluetoothChatService.MESSAGE_DEVICE_NAME: 209 handleDeviceName(msg); 210 break; 211 case BluetoothChatService.MESSAGE_TOAST: 212 handleToast(msg); 213 break; 214 } 215 } 216 } 217 handleStateChange(Message msg)218 private void handleStateChange(Message msg) { 219 int state = msg.arg1; 220 switch (state) { 221 case BluetoothChatService.STATE_LISTEN: 222 setEmptyViewText(R.string.bt_waiting); 223 mProgressBar.setVisibility(View.VISIBLE); 224 showInstructionsDialog(); 225 break; 226 227 case BluetoothChatService.STATE_CONNECTING: 228 setEmptyViewText(R.string.bt_connecting); 229 mProgressBar.setVisibility(View.VISIBLE); 230 break; 231 232 case BluetoothChatService.STATE_CONNECTED: 233 setEmptyViewText(R.string.bt_no_messages); 234 mProgressBar.setVisibility(View.INVISIBLE); 235 236 hideInstructionsDialog(); 237 sendInitialMessageFromClient(); 238 break; 239 240 case BluetoothChatService.STATE_NONE: 241 setEmptyViewText(R.string.bt_no_messages); 242 mProgressBar.setVisibility(View.INVISIBLE); 243 break; 244 } 245 } 246 setEmptyViewText(int textId)247 private void setEmptyViewText(int textId) { 248 mEmptyReceivedView.setText(textId); 249 mEmptySentView.setText(textId); 250 } 251 showInstructionsDialog()252 private void showInstructionsDialog() { 253 if (mInstructionsDialog == null) { 254 mInstructionsDialog = new AlertDialog.Builder(this) 255 .setIcon(android.R.drawable.ic_dialog_info) 256 .setTitle(getString(R.string.bt_waiting)) 257 .setMessage(getString(mSecure 258 ? R.string.bt_secure_server_instructions 259 : R.string.bt_insecure_server_instructions)) 260 .setPositiveButton(android.R.string.ok, null) 261 .create(); 262 } 263 mInstructionsDialog.show(); 264 } 265 hideInstructionsDialog()266 private void hideInstructionsDialog() { 267 if (mInstructionsDialog != null) { 268 mInstructionsDialog.hide(); 269 } 270 } 271 sendInitialMessageFromClient()272 private void sendInitialMessageFromClient() { 273 if (!mServer) { 274 sendMessage(0); 275 } 276 } 277 sendMessage(int number)278 private void sendMessage(int number) { 279 String message = "Message " + number + " to " 280 + (mRemoteDeviceName != null ? mRemoteDeviceName : "") 281 + MESSAGE_DELIMITER; 282 mChatService.write(message.getBytes()); 283 } 284 handleMessageRead(Message msg)285 private void handleMessageRead(Message msg) { 286 String chunk = new String((byte[]) msg.obj, 0, msg.arg1); 287 mMessageBuffer.append(chunk); 288 289 int delimiterIndex = mMessageBuffer.indexOf(MESSAGE_DELIMITER); 290 if (delimiterIndex != -1) { 291 String message = mMessageBuffer.substring(0, delimiterIndex); // Chop off delimiter 292 mMessageBuffer.delete(0, delimiterIndex + 1); 293 addNewMessage(message); 294 } 295 } 296 addNewMessage(String msg)297 private void addNewMessage(String msg) { 298 mReceivedMessagesAdapter.add(msg); 299 Matcher matcher = MESSAGE_PATTERN.matcher(msg); 300 if (matcher.matches()) { 301 int number = Integer.valueOf(matcher.group(1)); 302 if (mServer && number == 10 || !mServer && number == 11) { 303 getPassButton().setEnabled(true); 304 } 305 if (number <= 10) { 306 sendMessage(number + 1); 307 } 308 } 309 } 310 handleMessageWrite(Message msg)311 private void handleMessageWrite(Message msg) { 312 String sentMessage = new String((byte[]) msg.obj).trim(); // Chop off delimiter 313 mSentMessagesAdapter.add(sentMessage); 314 } 315 handleDeviceName(Message msg)316 private void handleDeviceName(Message msg) { 317 mRemoteDeviceName = msg.getData().getString(BluetoothChatService.DEVICE_NAME); 318 } 319 handleToast(Message msg)320 private void handleToast(Message msg) { 321 String toast = msg.getData().getString(BluetoothChatService.TOAST); 322 Toast.makeText(this, toast, Toast.LENGTH_LONG).show(); 323 } 324 325 class PairingActionReceiver extends BroadcastReceiver { 326 @Override onReceive(Context context, Intent intent)327 public void onReceive(Context context, Intent intent) { 328 if (!mSecure && ACTION_PAIRING_REQUEST.equals(intent.getAction())) { 329 runOnUiThread(new Runnable() { 330 @Override 331 public void run() { 332 showPairingErrorDialog(); 333 } 334 }); 335 } 336 } 337 } 338 showPairingErrorDialog()339 private void showPairingErrorDialog() { 340 new AlertDialog.Builder(MessageTestActivity.this) 341 .setIcon(android.R.drawable.ic_dialog_alert) 342 .setTitle(R.string.bt_insecure_pairing_error_title) 343 .setMessage(R.string.bt_insecure_pairing_error_message) 344 .setPositiveButton(android.R.string.ok, 345 new DialogInterface.OnClickListener() { 346 @Override 347 public void onClick(DialogInterface dialog, int which) { 348 TestResult.setFailedResult(MessageTestActivity.this, getTestId(), null); 349 finish(); 350 } 351 }) 352 .setCancelable(false) 353 .show(); 354 } 355 356 @Override onDestroy()357 protected void onDestroy() { 358 super.onDestroy(); 359 if (mChatService != null) { 360 mChatService.stop(); 361 } 362 unregisterReceiver(mPairingActionReceiver); 363 } 364 } 365