1 /* 2 * Copyright (C) 2017 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.car.settings.bluetooth; 18 19 import android.app.Notification; 20 import android.app.NotificationChannel; 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.app.Service; 24 import android.bluetooth.BluetoothDevice; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.content.res.Resources; 30 import android.os.IBinder; 31 import android.text.TextUtils; 32 33 import com.android.car.settings.R; 34 import com.android.car.settings.common.Logger; 35 36 /** 37 * BluetoothPairingService shows a notification if there is a pending bond request 38 * which can launch the appropriate pairing dialog when tapped. 39 */ 40 public final class BluetoothPairingService extends Service { 41 42 private static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth; 43 44 private static final String ACTION_DISMISS_PAIRING = 45 "com.android.settings.bluetooth.ACTION_DISMISS_PAIRING"; 46 47 private static final String BLUETOOTH_NOTIFICATION_CHANNEL = 48 "bluetooth_notification_channel"; 49 50 private static final Logger LOG = new Logger(BluetoothPairingService.class); 51 52 private BluetoothDevice mDevice; 53 54 public static Intent getPairingDialogIntent(Context context, Intent intent) { 55 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 56 int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 57 BluetoothDevice.ERROR); 58 Intent pairingIntent = new Intent(); 59 pairingIntent.setClass(context, BluetoothPairingDialog.class); 60 pairingIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 61 pairingIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, type); 62 if (type == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION || 63 type == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY || 64 type == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) { 65 int pairingKey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, 66 BluetoothDevice.ERROR); 67 pairingIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pairingKey); 68 } 69 pairingIntent.setAction(BluetoothDevice.ACTION_PAIRING_REQUEST); 70 return pairingIntent; 71 } 72 73 private boolean mRegistered = false; 74 private final BroadcastReceiver mCancelReceiver = new BroadcastReceiver() { 75 @Override 76 public void onReceive(Context context, Intent intent) { 77 String action = intent.getAction(); 78 if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { 79 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 80 BluetoothDevice.ERROR); 81 if ((bondState != BluetoothDevice.BOND_NONE) && (bondState != BluetoothDevice.BOND_BONDED)) { 82 return; 83 } 84 } else if (action.equals(ACTION_DISMISS_PAIRING)) { 85 LOG.d("Notification cancel " + mDevice.getAddress() + " (" 86 + mDevice.getName() + ")"); 87 mDevice.cancelPairingUserInput(); 88 } else { 89 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 90 BluetoothDevice.ERROR); 91 LOG.d("Dismiss pairing for " + mDevice.getAddress() + " (" 92 + mDevice.getName() + "), BondState: " + bondState); 93 } 94 stopForeground(true); 95 stopSelf(); 96 } 97 }; 98 99 @Override 100 public void onCreate() { 101 NotificationManager mgr = (NotificationManager)this 102 .getSystemService(Context.NOTIFICATION_SERVICE); 103 NotificationChannel notificationChannel = new NotificationChannel( 104 BLUETOOTH_NOTIFICATION_CHANNEL, 105 this.getString(R.string.bluetooth), 106 NotificationManager.IMPORTANCE_HIGH); 107 mgr.createNotificationChannel(notificationChannel); 108 } 109 110 @Override 111 public int onStartCommand(Intent intent, int flags, int startId) { 112 if (intent == null) { 113 LOG.e("Can't start: null intent!"); 114 stopSelf(); 115 return START_NOT_STICKY; 116 } 117 118 Resources res = getResources(); 119 Notification.Builder builder = new Notification.Builder(this, 120 BLUETOOTH_NOTIFICATION_CHANNEL) 121 .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) 122 .setTicker(res.getString(R.string.bluetooth_notif_ticker)) 123 .setLocalOnly(true); 124 125 PendingIntent pairIntent = PendingIntent.getActivity(this, 0, 126 getPairingDialogIntent(this, intent), PendingIntent.FLAG_ONE_SHOT); 127 128 PendingIntent dismissIntent = PendingIntent.getBroadcast(this, 0, 129 new Intent(ACTION_DISMISS_PAIRING), PendingIntent.FLAG_ONE_SHOT); 130 131 mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 132 133 if (mDevice != null && mDevice.getBondState() != BluetoothDevice.BOND_BONDING) { 134 LOG.w("Device " + mDevice + " not bonding: " + mDevice.getBondState()); 135 stopSelf(); 136 return START_NOT_STICKY; 137 } 138 139 String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); 140 if (TextUtils.isEmpty(name)) { 141 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 142 name = device != null ? device.getAliasName() : res.getString(android.R.string.unknownName); 143 } 144 145 LOG.d("Show pairing notification for " + mDevice.getAddress() + " (" + name + ")"); 146 147 Notification.Action pairAction = new Notification.Action.Builder(0, 148 res.getString(R.string.bluetooth_device_context_pair_connect), pairIntent).build(); 149 Notification.Action dismissAction = new Notification.Action.Builder(0, 150 res.getString(android.R.string.cancel), dismissIntent).build(); 151 152 builder.setContentTitle(res.getString(R.string.bluetooth_notif_title)) 153 .setContentText(res.getString(R.string.bluetooth_notif_message, name)) 154 .setContentIntent(pairIntent) 155 .setDefaults(Notification.DEFAULT_SOUND) 156 .setColor(getColor(com.android.internal.R.color.system_notification_accent_color)) 157 .addAction(pairAction) 158 .addAction(dismissAction); 159 160 IntentFilter filter = new IntentFilter(); 161 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 162 filter.addAction(BluetoothDevice.ACTION_PAIRING_CANCEL); 163 filter.addAction(ACTION_DISMISS_PAIRING); 164 registerReceiver(mCancelReceiver, filter); 165 mRegistered = true; 166 167 startForeground(NOTIFICATION_ID, builder.getNotification()); 168 return START_REDELIVER_INTENT; 169 } 170 171 @Override 172 public void onDestroy() { 173 if (mRegistered) { 174 unregisterReceiver(mCancelReceiver); 175 mRegistered = false; 176 } 177 stopForeground(true); 178 } 179 180 @Override 181 public IBinder onBind(Intent intent) { 182 // No binding. 183 return null; 184 } 185 } 186