1 /* 2 * Copyright (C) 2016 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.google.android.car.kitchensink.bluetooth; 18 19 import android.Manifest; 20 import android.annotation.TargetApi; 21 import android.app.PendingIntent; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothDevicePicker; 25 import android.bluetooth.BluetoothMapClient; 26 import android.bluetooth.BluetoothProfile; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.PackageManager; 32 import android.net.Uri; 33 import android.os.Build; 34 import android.os.Bundle; 35 import android.telecom.PhoneAccount; 36 import android.util.Log; 37 import android.view.LayoutInflater; 38 import android.view.View; 39 import android.view.ViewGroup; 40 import android.widget.Button; 41 import android.widget.CheckBox; 42 import android.widget.EditText; 43 import android.widget.TextView; 44 import android.widget.Toast; 45 46 import androidx.annotation.Nullable; 47 import androidx.fragment.app.Fragment; 48 49 import com.google.android.car.kitchensink.KitchenSinkActivity; 50 import com.google.android.car.kitchensink.R; 51 52 import java.util.Date; 53 import java.util.HashSet; 54 import java.util.List; 55 56 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 57 public class MapMceTestFragment extends Fragment { 58 static final String REPLY_MESSAGE_TO_SEND = "I am currently driving."; 59 static final String NEW_MESSAGE_TO_SEND_SHORT = "This is a new message."; 60 static final String NEW_MESSAGE_TO_SEND_LONG = "Lorem ipsum dolor sit amet, consectetur " 61 + "adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna " 62 + "aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi " 63 + "ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in " 64 + "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " 65 + "occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim " 66 + "id est laborum.\n\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. " 67 + "Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus " 68 + "magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis " 69 + "ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. " 70 + "Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt " 71 + "sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. " 72 + "Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, " 73 + "consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl " 74 + "adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque " 75 + "nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, " 76 + "laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, " 77 + "feugiat in, orci. In hac habitasse platea dictumst.\n\nLorem ipsum dolor sit " 78 + "amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et " 79 + "dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " 80 + "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in " 81 + "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " 82 + "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia " 83 + "deserunt mollit anim id est laborum.\n\nCurabitur pretium tincidunt lacus. Nulla " 84 + "gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum " 85 + "elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh " 86 + "euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus " 87 + "a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod " 88 + "turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec " 89 + "fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, " 90 + "commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, " 91 + "felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis " 92 + "scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus " 93 + "quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, " 94 + "feugiat in, orci. In hac habitasse platea dictumst."; 95 private static final int SEND_NEW_SMS_SHORT = 1; 96 private static final int SEND_NEW_SMS_LONG = 2; 97 private static final int SEND_NEW_MMS_SHORT = 3; 98 private static final int SEND_NEW_MMS_LONG = 4; 99 private int mSendNewMsgCounter = 0; 100 private static final String TAG = "CAR.BLUETOOTH.KS"; 101 private static final int SEND_SMS_PERMISSIONS_REQUEST = 1; 102 BluetoothMapClient mMapProfile; 103 BluetoothAdapter mBluetoothAdapter; 104 Button mDevicePicker; 105 Button mDeviceDisconnect; 106 TextView mMessage; 107 EditText mOriginator; 108 EditText mSmsTelNum; 109 TextView mOriginatorDisplayName; 110 CheckBox mSent; 111 CheckBox mDelivered; 112 TextView mBluetoothDevice; 113 PendingIntent mSentIntent; 114 PendingIntent mDeliveredIntent; 115 NotificationReceiver mTransmissionStatusReceiver; 116 Object mLock = new Object(); 117 private KitchenSinkActivity mActivity; 118 private Intent mSendIntent; 119 private Intent mDeliveryIntent; 120 EditText mUploadingSupportedFeatureText; 121 122 @Override onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)123 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 124 @Nullable Bundle savedInstanceState) { 125 View v = inflater.inflate(R.layout.sms_received, container, false); 126 mActivity = (KitchenSinkActivity) getHost(); 127 Button reply = (Button) v.findViewById(R.id.reply); 128 Button checkMessages = (Button) v.findViewById(R.id.check_messages); 129 mBluetoothDevice = (TextView) v.findViewById(R.id.bluetoothDevice); 130 Button sendNewMsgShort = (Button) v.findViewById(R.id.sms_new_message); 131 Button sendNewMsgLong = (Button) v.findViewById(R.id.mms_new_message); 132 Button resetSendNewMsgCounter = (Button) v.findViewById(R.id.reset_message_counter); 133 mSmsTelNum = (EditText) v.findViewById(R.id.sms_tel_num); 134 mOriginator = (EditText) v.findViewById(R.id.messageOriginator); 135 mOriginatorDisplayName = (TextView) v.findViewById(R.id.messageOriginatorDisplayName); 136 mSent = (CheckBox) v.findViewById(R.id.sent_checkbox); 137 mDelivered = (CheckBox) v.findViewById(R.id.delivered_checkbox); 138 mSendIntent = new Intent(BluetoothMapClient.ACTION_MESSAGE_SENT_SUCCESSFULLY); 139 mDeliveryIntent = new Intent(BluetoothMapClient.ACTION_MESSAGE_DELIVERED_SUCCESSFULLY); 140 mMessage = (TextView) v.findViewById(R.id.messageContent); 141 mDevicePicker = (Button) v.findViewById(R.id.bluetooth_pick_device); 142 mDeviceDisconnect = (Button) v.findViewById(R.id.bluetooth_disconnect_device); 143 Button uploadingFeatureValue = (Button) v.findViewById(R.id.uploading_supported_feature); 144 mUploadingSupportedFeatureText = 145 (EditText) v.findViewById(R.id.uploading_supported_feature_value); 146 147 uploadingFeatureValue.setOnClickListener(new View.OnClickListener() { 148 @Override 149 public void onClick(View view) { 150 int value = getUploadingFeatureValue(); 151 mUploadingSupportedFeatureText.setText(value + ""); 152 } 153 }); 154 155 //TODO add manual entry option for phone number 156 reply.setOnClickListener(new View.OnClickListener() { 157 @Override 158 public void onClick(View view) { 159 sendMessage(new Uri[]{Uri.parse(mOriginator.getText().toString())}, 160 REPLY_MESSAGE_TO_SEND); 161 } 162 }); 163 164 sendNewMsgShort.setOnClickListener(new View.OnClickListener() { 165 @Override 166 public void onClick(View view) { 167 sendNewMsgOnClick(SEND_NEW_SMS_SHORT); 168 } 169 }); 170 171 sendNewMsgLong.setOnClickListener(new View.OnClickListener() { 172 @Override 173 public void onClick(View view) { 174 sendNewMsgOnClick(SEND_NEW_MMS_LONG); 175 } 176 }); 177 178 resetSendNewMsgCounter.setOnClickListener(new View.OnClickListener() { 179 @Override 180 public void onClick(View view) { 181 mSendNewMsgCounter = 0; 182 Toast.makeText(getContext(), "Counter reset to zero.", Toast.LENGTH_SHORT).show(); 183 } 184 }); 185 186 checkMessages.setOnClickListener(new View.OnClickListener() { 187 @Override 188 public void onClick(View view) { 189 getMessages(); 190 } 191 }); 192 193 // Pick a bluetooth device 194 mDevicePicker.setOnClickListener(new View.OnClickListener() { 195 @Override 196 public void onClick(View view) { 197 launchDevicePicker(); 198 } 199 }); 200 mDeviceDisconnect.setOnClickListener(new View.OnClickListener() { 201 @Override 202 public void onClick(View view) { 203 disconnectDevice(mBluetoothDevice.getText().toString()); 204 } 205 }); 206 207 mTransmissionStatusReceiver = new NotificationReceiver(); 208 return v; 209 } 210 launchDevicePicker()211 void launchDevicePicker() { 212 IntentFilter filter = new IntentFilter(); 213 filter.addAction(BluetoothDevicePicker.ACTION_DEVICE_SELECTED); 214 getContext().registerReceiver(mPickerReceiver, filter); 215 216 Intent intent = new Intent(BluetoothDevicePicker.ACTION_LAUNCH); 217 intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 218 getContext().startActivity(intent); 219 } 220 disconnectDevice(String device)221 void disconnectDevice(String device) { 222 try { 223 mMapProfile.disconnect(mBluetoothAdapter.getRemoteDevice(device)); 224 } catch (IllegalArgumentException e) { 225 Log.e(TAG, "Failed to disconnect from " + device, e); 226 } 227 } 228 229 @Override onResume()230 public void onResume() { 231 super.onResume(); 232 233 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 234 mBluetoothAdapter.getProfileProxy(getContext(), new MapServiceListener(), 235 BluetoothProfile.MAP_CLIENT); 236 237 IntentFilter intentFilter = new IntentFilter(); 238 intentFilter.addAction(BluetoothMapClient.ACTION_MESSAGE_SENT_SUCCESSFULLY); 239 intentFilter.addAction(BluetoothMapClient.ACTION_MESSAGE_DELIVERED_SUCCESSFULLY); 240 intentFilter.addAction(BluetoothMapClient.ACTION_MESSAGE_RECEIVED); 241 intentFilter.addAction(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED); 242 getContext().registerReceiver(mTransmissionStatusReceiver, intentFilter); 243 } 244 245 @Override onPause()246 public void onPause() { 247 super.onPause(); 248 getContext().unregisterReceiver(mTransmissionStatusReceiver); 249 } 250 getMessages()251 private void getMessages() { 252 synchronized (mLock) { 253 BluetoothDevice remoteDevice; 254 try { 255 remoteDevice = mBluetoothAdapter.getRemoteDevice( 256 mBluetoothDevice.getText().toString()); 257 } catch (java.lang.IllegalArgumentException e) { 258 Log.e(TAG, e.toString()); 259 return; 260 } 261 262 if (mMapProfile != null) { 263 Log.d(TAG, "Getting Messages"); 264 mMapProfile.getUnreadMessages(remoteDevice); 265 } 266 } 267 } 268 sendNewMsgOnClick(int msgType)269 private void sendNewMsgOnClick(int msgType) { 270 String messageToSend = ""; 271 switch (msgType) { 272 case SEND_NEW_SMS_SHORT: 273 messageToSend = NEW_MESSAGE_TO_SEND_SHORT; 274 break; 275 case SEND_NEW_MMS_LONG: 276 messageToSend = NEW_MESSAGE_TO_SEND_LONG; 277 break; 278 } 279 String s = mSmsTelNum.getText().toString(); 280 Toast.makeText(getContext(), "sending msg to " + s, Toast.LENGTH_SHORT).show(); 281 HashSet<Uri> uris = new HashSet<Uri>(); 282 Uri.Builder builder = new Uri.Builder(); 283 for (String telNum : s.split(",")) { 284 uris.add(builder.path(telNum).scheme(PhoneAccount.SCHEME_TEL).build()); 285 } 286 sendMessage(uris.toArray(new Uri[uris.size()]), Integer.toString(mSendNewMsgCounter) 287 + ": " + messageToSend); 288 mSendNewMsgCounter += 1; 289 } 290 getUploadingFeatureValue()291 private int getUploadingFeatureValue() { 292 synchronized (mLock) { 293 BluetoothDevice remoteDevice; 294 try { 295 remoteDevice = mBluetoothAdapter.getRemoteDevice( 296 mBluetoothDevice.getText().toString()); 297 } catch (java.lang.IllegalArgumentException e) { 298 Log.e(TAG, e.toString()); 299 return -1; 300 } 301 302 if (mMapProfile != null) { 303 Log.d(TAG, "getUploadingFeatureValue"); 304 return (mMapProfile.isUploadingSupported(remoteDevice)) ? 1 : 0; 305 } 306 return -1; 307 } 308 } 309 sendMessage(Uri[] recipients, String message)310 private void sendMessage(Uri[] recipients, String message) { 311 if (mActivity.checkSelfPermission(Manifest.permission.SEND_SMS) 312 != PackageManager.PERMISSION_GRANTED) { 313 Log.d(TAG,"Don't have SMS permission in kitchesink app. Requesting it"); 314 mActivity.requestPermissions(new String[]{Manifest.permission.SEND_SMS}, 315 SEND_SMS_PERMISSIONS_REQUEST); 316 Toast.makeText(getContext(), "Try again after granting SEND_SMS perm!", 317 Toast.LENGTH_SHORT).show(); 318 return; 319 } 320 synchronized (mLock) { 321 BluetoothDevice remoteDevice; 322 try { 323 remoteDevice = mBluetoothAdapter.getRemoteDevice( 324 mBluetoothDevice.getText().toString()); 325 } catch (java.lang.IllegalArgumentException e) { 326 Log.e(TAG, e.toString()); 327 return; 328 } 329 mSent.setChecked(false); 330 mDelivered.setChecked(false); 331 if (mMapProfile != null) { 332 Log.d(TAG, "Sending reply"); 333 if (recipients == null) { 334 Log.d(TAG, "Recipients is null"); 335 return; 336 } 337 if (mBluetoothDevice == null) { 338 Log.d(TAG, "BluetoothDevice is null"); 339 return; 340 } 341 342 mSentIntent = PendingIntent.getBroadcast(getContext(), 0, mSendIntent, 343 PendingIntent.FLAG_ONE_SHOT); 344 mDeliveredIntent = PendingIntent.getBroadcast(getContext(), 0, mDeliveryIntent, 345 PendingIntent.FLAG_ONE_SHOT); 346 Log.d(TAG,"Sending message in kitchesink app: " + message); 347 mMapProfile.sendMessage( 348 remoteDevice, 349 recipients, message, mSentIntent, mDeliveredIntent); 350 } 351 } 352 } 353 354 @Override onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)355 public void onRequestPermissionsResult(int requestCode, String[] permissions, 356 int[] grantResults) { 357 Log.d(TAG, "onRequestPermissionsResult reqCode=" + requestCode); 358 if (SEND_SMS_PERMISSIONS_REQUEST == requestCode) { 359 for (int i=0; i<permissions.length; i++) { 360 if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { 361 if (permissions[i] == Manifest.permission.SEND_SMS) { 362 Log.d(TAG, "Got the SEND_SMS perm"); 363 return; 364 } 365 } 366 } 367 } 368 } 369 370 class MapServiceListener implements BluetoothProfile.ServiceListener { 371 @Override onServiceConnected(int profile, BluetoothProfile proxy)372 public void onServiceConnected(int profile, BluetoothProfile proxy) { 373 synchronized (mLock) { 374 mMapProfile = (BluetoothMapClient) proxy; 375 List<BluetoothDevice> connectedDevices = proxy.getConnectedDevices(); 376 if (connectedDevices.size() > 0) { 377 mBluetoothDevice.setText(connectedDevices.get(0).getAddress()); 378 } 379 } 380 } 381 382 @Override onServiceDisconnected(int profile)383 public void onServiceDisconnected(int profile) { 384 synchronized (mLock) { 385 mMapProfile = null; 386 } 387 } 388 } 389 390 private class NotificationReceiver extends BroadcastReceiver { 391 @Override onReceive(Context context, Intent intent)392 public void onReceive(Context context, Intent intent) { 393 String action = intent.getAction(); 394 synchronized (mLock) { 395 if (action.equals(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED)) { 396 if (intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0) 397 == BluetoothProfile.STATE_CONNECTED) { 398 mBluetoothDevice.setText(((BluetoothDevice) intent.getParcelableExtra( 399 BluetoothDevice.EXTRA_DEVICE)).getAddress()); 400 } else if (intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0) 401 == BluetoothProfile.STATE_DISCONNECTED) { 402 mBluetoothDevice.setText("Disconnected"); 403 } 404 } else if (action.equals(BluetoothMapClient.ACTION_MESSAGE_SENT_SUCCESSFULLY)) { 405 mSent.setChecked(true); 406 } else if (action.equals( 407 BluetoothMapClient.ACTION_MESSAGE_DELIVERED_SUCCESSFULLY)) { 408 mDelivered.setChecked(true); 409 } else if (action.equals(BluetoothMapClient.ACTION_MESSAGE_RECEIVED)) { 410 String senderUri = 411 intent.getStringExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_URI); 412 if (senderUri == null) { 413 senderUri = "<null>"; 414 } 415 416 String senderName = intent.getStringExtra( 417 BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME); 418 if (senderName == null) { 419 senderName = "<null>"; 420 } 421 Date msgTimestamp = new Date(intent.getLongExtra( 422 BluetoothMapClient.EXTRA_MESSAGE_TIMESTAMP, 423 System.currentTimeMillis())); 424 boolean msgReadStatus = intent.getBooleanExtra( 425 BluetoothMapClient.EXTRA_MESSAGE_READ_STATUS, false); 426 String msgText = intent.getStringExtra(android.content.Intent.EXTRA_TEXT); 427 String msg = "[" + msgTimestamp + "] " + "(" 428 + (msgReadStatus ? "READ" : "UNREAD") + ") " + msgText; 429 mMessage.setText(msg); 430 mOriginator.setText(senderUri); 431 mOriginatorDisplayName.setText(senderName); 432 } 433 } 434 } 435 } 436 437 private final BroadcastReceiver mPickerReceiver = new BroadcastReceiver() { 438 @Override 439 public void onReceive(Context context, Intent intent) { 440 String action = intent.getAction(); 441 442 Log.v(TAG, "mPickerReceiver got " + action); 443 444 if (BluetoothDevicePicker.ACTION_DEVICE_SELECTED.equals(action)) { 445 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 446 Log.v(TAG, "mPickerReceiver got " + device); 447 if (device == null) { 448 Toast.makeText(getContext(), "No device selected", Toast.LENGTH_SHORT).show(); 449 return; 450 } 451 mMapProfile.connect(device); 452 453 // The receiver can now be disabled. 454 getContext().unregisterReceiver(mPickerReceiver); 455 } 456 } 457 }; 458 } 459