• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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