| /* |
| * Copyright (C) 2024 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.bluetooth; |
| |
| import android.app.Notification; |
| import android.app.NotificationChannel; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.bluetooth.BluetoothDevice; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.Bundle; |
| import android.os.PowerManager; |
| import android.os.UserHandle; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.widget.Toast; |
| |
| import androidx.core.app.NotificationCompat; |
| |
| import com.android.settings.R; |
| import com.android.settings.flags.Flags; |
| import com.android.settingslib.bluetooth.BluetoothUtils; |
| |
| /** |
| * BluetoothKeyMissingReceiver is a receiver for Bluetooth key missing error when reconnecting to a |
| * bonded bluetooth device. |
| */ |
| public final class BluetoothKeyMissingReceiver extends BroadcastReceiver { |
| private static final String TAG = "BtKeyMissingReceiver"; |
| private static final String CHANNEL_ID = "bluetooth_notification_channel"; |
| private static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth; |
| private static final String DEVICE_DETAILS_ACTION = |
| "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"; |
| |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (!Flags.enableBluetoothKeyMissingDialog()) { |
| return; |
| } |
| String action = intent.getAction(); |
| if (action == null) { |
| return; |
| } |
| |
| BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); |
| if (device == null) { |
| return; |
| } |
| if (BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, device)) { |
| Log.d(TAG, "Exclusively managed device " + device + ", skip"); |
| return; |
| } |
| PowerManager powerManager = context.getSystemService(PowerManager.class); |
| if (TextUtils.equals(action, BluetoothDevice.ACTION_KEY_MISSING)) { |
| Log.d(TAG, "Receive ACTION_KEY_MISSING"); |
| if (device.getBondState() == BluetoothDevice.BOND_NONE) { |
| Log.d( |
| TAG, |
| "Device " + device.getAnonymizedAddress() + " is already unbonded, skip."); |
| return; |
| } |
| Integer keyMissingCount = BluetoothUtils.getKeyMissingCount(device); |
| boolean keyMissingFirstTime = keyMissingCount == null || keyMissingCount == 1; |
| if (shouldShowDialog(context, device, powerManager)) { |
| if (keyMissingFirstTime) { |
| Intent dialogIntent = getKeyMissingDialogIntent(context, device); |
| Log.d(TAG, "Show key missing dialog:" + device); |
| context.startActivityAsUser(dialogIntent, UserHandle.CURRENT); |
| } else { |
| Log.d(TAG, "Show key missing toast:" + device); |
| Toast.makeText( |
| context, |
| context.getString( |
| R.string.bluetooth_key_missing_toast, |
| device.getAlias()), |
| Toast.LENGTH_SHORT) |
| .show(); |
| } |
| } else if (keyMissingFirstTime) { |
| Log.d(TAG, "Show key missing notification: " + device); |
| showNotification(context, device); |
| } |
| } |
| } |
| |
| private Intent getKeyMissingDialogIntent(Context context, BluetoothDevice device) { |
| Intent pairingIntent = new Intent(); |
| pairingIntent.setClass(context, BluetoothKeyMissingDialog.class); |
| pairingIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| pairingIntent.setAction(BluetoothDevice.ACTION_KEY_MISSING); |
| pairingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| return pairingIntent; |
| } |
| |
| private boolean shouldShowDialog( |
| Context context, BluetoothDevice device, PowerManager powerManager) { |
| return LocalBluetoothPreferences.shouldShowDialogInForeground(context, device) |
| && powerManager.isInteractive(); |
| } |
| |
| private void showNotification(Context context, BluetoothDevice bluetoothDevice) { |
| NotificationManager nm = context.getSystemService(NotificationManager.class); |
| NotificationChannel notificationChannel = |
| new NotificationChannel( |
| CHANNEL_ID, |
| context.getString(R.string.bluetooth), |
| NotificationManager.IMPORTANCE_HIGH); |
| nm.createNotificationChannel(notificationChannel); |
| |
| PendingIntent deviceDetailsIntent = |
| PendingIntent.getActivity( |
| context, |
| 0, |
| getDeviceDetailsIntent(bluetoothDevice.getAddress()), |
| PendingIntent.FLAG_ONE_SHOT |
| | PendingIntent.FLAG_UPDATE_CURRENT |
| | PendingIntent.FLAG_IMMUTABLE); |
| |
| NotificationCompat.Builder builder = new NotificationCompat.Builder(context, |
| CHANNEL_ID) |
| .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) |
| .setTicker(context.getString(R.string.bluetooth_notif_ticker)) |
| .setLocalOnly(true); |
| builder.setContentTitle( |
| context.getString( |
| R.string.bluetooth_key_missing_title, bluetoothDevice.getAlias())) |
| .setContentText(context.getString(R.string.bluetooth_key_missing_message)) |
| .setContentIntent(deviceDetailsIntent) |
| .setAutoCancel(true) |
| .setDefaults(Notification.DEFAULT_SOUND) |
| .setColor( |
| context.getColor( |
| com.android.internal.R.color.system_notification_accent_color)); |
| |
| nm.notify(NOTIFICATION_ID, builder.build()); |
| } |
| |
| private Intent getDeviceDetailsIntent(String address) { |
| Bundle args = new Bundle(); |
| args.putString("device_address", address); |
| Intent intent = new Intent(DEVICE_DETAILS_ACTION); |
| intent.putExtra(":settings:show_fragment_args", args); |
| |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| return intent; |
| } |
| } |