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 static com.android.internal.util.CollectionUtils.filter; 20 21 import android.app.Activity; 22 import android.app.Dialog; 23 import android.app.settings.SettingsEnums; 24 import android.bluetooth.BluetoothDevice; 25 import android.companion.AssociationInfo; 26 import android.companion.CompanionDeviceManager; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.content.pm.PackageManager; 30 import android.icu.text.ListFormatter; 31 import android.os.Bundle; 32 import android.text.TextUtils; 33 import android.util.Log; 34 35 import androidx.annotation.NonNull; 36 import androidx.annotation.Nullable; 37 import androidx.annotation.VisibleForTesting; 38 import androidx.appcompat.app.AlertDialog; 39 40 import com.android.settings.R; 41 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 42 import com.android.settings.flags.Flags; 43 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 44 import com.android.settingslib.bluetooth.LocalBluetoothManager; 45 46 import com.google.common.base.Objects; 47 48 import java.util.HashSet; 49 import java.util.List; 50 import java.util.Locale; 51 import java.util.Set; 52 53 /** Implements an AlertDialog for confirming that a user wishes to unpair or "forget" a paired 54 * device*/ 55 public class ForgetDeviceDialogFragment extends InstrumentedDialogFragment { 56 public static final String TAG = "ForgetBluetoothDevice"; 57 private static final String KEY_DEVICE_ADDRESS = "device_address"; 58 59 @VisibleForTesting 60 CachedBluetoothDevice mDevice; 61 @VisibleForTesting 62 CompanionDeviceManager mCompanionDeviceManager; 63 @VisibleForTesting 64 PackageManager mPackageManager; newInstance(String deviceAddress)65 public static ForgetDeviceDialogFragment newInstance(String deviceAddress) { 66 Bundle args = new Bundle(1); 67 args.putString(KEY_DEVICE_ADDRESS, deviceAddress); 68 ForgetDeviceDialogFragment dialog = new ForgetDeviceDialogFragment(); 69 dialog.setArguments(args); 70 return dialog; 71 } 72 73 @VisibleForTesting getDevice(Context context)74 CachedBluetoothDevice getDevice(Context context) { 75 String deviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS); 76 LocalBluetoothManager manager = Utils.getLocalBtManager(context); 77 BluetoothDevice device = manager.getBluetoothAdapter().getRemoteDevice(deviceAddress); 78 return manager.getCachedDeviceManager().findDevice(device); 79 } 80 81 @Override getMetricsCategory()82 public int getMetricsCategory() { 83 return SettingsEnums.DIALOG_BLUETOOTH_PAIRED_DEVICE_FORGET; 84 } 85 86 @Override onAttach(@onNull Context context)87 public void onAttach(@NonNull Context context) { 88 super.onAttach(context); 89 mCompanionDeviceManager = context.getSystemService(CompanionDeviceManager.class); 90 mPackageManager = context.getPackageManager(); 91 mDevice = getDevice(context); 92 } 93 94 @NonNull 95 @Override onCreateDialog(@ullable Bundle inState)96 public Dialog onCreateDialog(@Nullable Bundle inState) { 97 if (mDevice == null) { 98 throw new IllegalStateException("Device must not be null when creating dialog."); 99 } 100 List<AssociationInfo> associationInfos = getAssociations(mDevice.getAddress()); 101 Set<String> packageNames = new HashSet<>(); 102 if (Flags.enableRemoveAssociationBtUnpair()) { 103 for (AssociationInfo ai : associationInfos) { 104 CharSequence appLabel = getAppLabel(ai.getPackageName()); 105 if (!TextUtils.isEmpty(appLabel)) { 106 packageNames.add(appLabel.toString()); 107 } 108 } 109 } 110 111 DialogInterface.OnClickListener onConfirm = (dialog, which) -> { 112 // 1. Unpair the device. 113 mDevice.unpair(); 114 // 2. Remove the associations if any. 115 if (Flags.enableRemoveAssociationBtUnpair()) { 116 for (AssociationInfo ai : associationInfos) { 117 mCompanionDeviceManager.disassociate(ai.getId()); 118 } 119 } 120 121 Activity activity = getActivity(); 122 if (activity != null) { 123 activity.finish(); 124 } 125 }; 126 127 AlertDialog dialog = new AlertDialog.Builder(getActivity()) 128 .setPositiveButton(R.string.bluetooth_unpair_dialog_forget_confirm_button, 129 onConfirm) 130 .setNegativeButton(android.R.string.cancel, null) 131 .create(); 132 133 dialog.setTitle(R.string.bluetooth_unpair_dialog_title); 134 String message = buildUnpairMessage( 135 getActivity(), mDevice, associationInfos, packageNames.stream().toList()); 136 dialog.setMessage(message); 137 138 return dialog; 139 } 140 getAssociations(String address)141 private List<AssociationInfo> getAssociations(String address) { 142 return filter( 143 mCompanionDeviceManager.getAllAssociations(), 144 a -> Objects.equal(address, a.getDeviceMacAddressAsString())); 145 } 146 buildUnpairMessage(Context context, CachedBluetoothDevice device, List<AssociationInfo> associationInfos, List<String> packageNames)147 private String buildUnpairMessage(Context context, CachedBluetoothDevice device, 148 List<AssociationInfo> associationInfos, List<String> packageNames) { 149 if (Flags.enableRemoveAssociationBtUnpair() && !associationInfos.isEmpty()) { 150 String appNamesString = getAppNamesString(packageNames.stream().toList()); 151 return context.getString(R.string.bluetooth_unpair_dialog_with_associations_body, 152 device.getName(), appNamesString); 153 } else { 154 return context.getString(R.string.bluetooth_unpair_dialog_body, device.getName()); 155 } 156 } 157 getAppNamesString(List<String> appNames)158 private String getAppNamesString(List<String> appNames) { 159 if (appNames == null || appNames.isEmpty()) { 160 return ""; 161 } 162 163 ListFormatter formatter = ListFormatter.getInstance(Locale.getDefault()); 164 return formatter.format(appNames); 165 } 166 getAppLabel(String packageName)167 private CharSequence getAppLabel(String packageName) { 168 try { 169 return mPackageManager.getApplicationLabel( 170 mPackageManager.getApplicationInfo(packageName, 0)); 171 } catch (PackageManager.NameNotFoundException e) { 172 Log.e(TAG, "Package Not Found", e); 173 return ""; 174 } 175 } 176 } 177