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.settings.bluetooth; 18 19 import android.app.Notification; 20 import android.app.NotificationManager; 21 import android.app.NotificationChannel; 22 import android.app.PendingIntent; 23 import android.app.Service; 24 import android.bluetooth.BluetoothDevice; 25 import android.content.IntentFilter; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.res.Resources; 30 import android.os.IBinder; 31 import android.text.TextUtils; 32 import android.util.Log; 33 34 import com.android.settings.R; 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 String TAG = "BluetoothPairingService"; 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 pairingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 71 return pairingIntent; 72 } 73 74 private boolean mRegistered = false; 75 private final BroadcastReceiver mCancelReceiver = new BroadcastReceiver() { 76 @Override 77 public void onReceive(Context context, Intent intent) { 78 String action = intent.getAction(); 79 if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { 80 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 81 BluetoothDevice.ERROR); 82 if ((bondState != BluetoothDevice.BOND_NONE) && (bondState != BluetoothDevice.BOND_BONDED)) { 83 return; 84 } 85 } else if (action.equals(ACTION_DISMISS_PAIRING)) { 86 Log.d(TAG, "Notification cancel " + mDevice.getAddress() + " (" + 87 mDevice.getName() + ")"); 88 mDevice.cancelPairingUserInput(); 89 } else { 90 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 91 BluetoothDevice.ERROR); 92 Log.d(TAG, "Dismiss pairing for " + mDevice.getAddress() + " (" + 93 mDevice.getName() + "), BondState: " + bondState); 94 } 95 stopForeground(true); 96 stopSelf(); 97 } 98 }; 99 100 @Override 101 public void onCreate() { 102 NotificationManager mgr = (NotificationManager)this 103 .getSystemService(Context.NOTIFICATION_SERVICE); 104 NotificationChannel notificationChannel = new NotificationChannel( 105 BLUETOOTH_NOTIFICATION_CHANNEL, 106 this.getString(R.string.bluetooth), 107 NotificationManager.IMPORTANCE_HIGH); 108 mgr.createNotificationChannel(notificationChannel); 109 } 110 111 @Override 112 public int onStartCommand(Intent intent, int flags, int startId) { 113 if (intent == null) { 114 Log.e(TAG, "Can't start: null intent!"); 115 stopSelf(); 116 return START_NOT_STICKY; 117 } 118 119 Resources res = getResources(); 120 Notification.Builder builder = new Notification.Builder(this, 121 BLUETOOTH_NOTIFICATION_CHANNEL) 122 .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) 123 .setTicker(res.getString(R.string.bluetooth_notif_ticker)) 124 .setLocalOnly(true); 125 126 PendingIntent pairIntent = PendingIntent.getActivity(this, 0, 127 getPairingDialogIntent(this, intent), PendingIntent.FLAG_ONE_SHOT); 128 129 PendingIntent dismissIntent = PendingIntent.getBroadcast(this, 0, 130 new Intent(ACTION_DISMISS_PAIRING), PendingIntent.FLAG_ONE_SHOT); 131 132 mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 133 134 if (mDevice != null && mDevice.getBondState() != BluetoothDevice.BOND_BONDING) { 135 Log.w(TAG, "Device " + mDevice + " not bonding: " + mDevice.getBondState()); 136 stopSelf(); 137 return START_NOT_STICKY; 138 } 139 140 String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); 141 if (TextUtils.isEmpty(name)) { 142 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 143 name = device != null ? device.getAliasName() : res.getString(android.R.string.unknownName); 144 } 145 146 Log.d(TAG, "Show pairing notification for " + mDevice.getAddress() + " (" + name + ")"); 147 148 Notification.Action pairAction = new Notification.Action.Builder(0, 149 res.getString(R.string.bluetooth_device_context_pair_connect), pairIntent).build(); 150 Notification.Action dismissAction = new Notification.Action.Builder(0, 151 res.getString(android.R.string.cancel), dismissIntent).build(); 152 153 builder.setContentTitle(res.getString(R.string.bluetooth_notif_title)) 154 .setContentText(res.getString(R.string.bluetooth_notif_message, name)) 155 .setContentIntent(pairIntent) 156 .setDefaults(Notification.DEFAULT_SOUND) 157 .setColor(getColor(com.android.internal.R.color.system_notification_accent_color)) 158 .addAction(pairAction) 159 .addAction(dismissAction); 160 161 IntentFilter filter = new IntentFilter(); 162 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 163 filter.addAction(BluetoothDevice.ACTION_PAIRING_CANCEL); 164 filter.addAction(ACTION_DISMISS_PAIRING); 165 registerReceiver(mCancelReceiver, filter); 166 mRegistered = true; 167 168 startForeground(NOTIFICATION_ID, builder.getNotification()); 169 return START_REDELIVER_INTENT; 170 } 171 172 @Override 173 public void onDestroy() { 174 if (mRegistered) { 175 unregisterReceiver(mCancelReceiver); 176 mRegistered = false; 177 } 178 stopForeground(true); 179 } 180 181 @Override 182 public IBinder onBind(Intent intent) { 183 // No binding. 184 return null; 185 } 186 } 187