• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.companion.Association;
22 import android.companion.CompanionDeviceManager;
23 import android.companion.ICompanionDeviceManager;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.pm.PackageManager;
28 import android.graphics.drawable.Drawable;
29 import android.net.Uri;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import android.provider.DeviceConfig;
33 import android.text.TextUtils;
34 import android.util.Log;
35 
36 import androidx.annotation.VisibleForTesting;
37 import androidx.appcompat.app.AlertDialog;
38 import androidx.preference.Preference;
39 import androidx.preference.PreferenceCategory;
40 import androidx.preference.PreferenceFragmentCompat;
41 import androidx.preference.PreferenceScreen;
42 
43 import com.android.settings.R;
44 import com.android.settings.core.SettingsUIDeviceConfig;
45 import com.android.settings.overlay.FeatureFactory;
46 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
47 import com.android.settingslib.core.lifecycle.Lifecycle;
48 
49 import com.google.common.base.Objects;
50 
51 import java.util.ArrayList;
52 import java.util.HashSet;
53 import java.util.List;
54 import java.util.Set;
55 import java.util.stream.Collectors;
56 
57 
58 /**
59  * This class adds Companion Device app rows to launch the app or remove the associations
60  */
61 public class BluetoothDetailsCompanionAppsController extends BluetoothDetailsController {
62     public static final String KEY_DEVICE_COMPANION_APPS = "device_companion_apps";
63     private static final String LOG_TAG = "BTCompanionController";
64 
65     private CachedBluetoothDevice mCachedDevice;
66 
67     @VisibleForTesting
68     PreferenceCategory mProfilesContainer;
69 
70     @VisibleForTesting
71     CompanionDeviceManager mCompanionDeviceManager;
72 
73     @VisibleForTesting
74     PackageManager mPackageManager;
75 
BluetoothDetailsCompanionAppsController(Context context, PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle)76     public BluetoothDetailsCompanionAppsController(Context context,
77             PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) {
78         super(context, fragment, device, lifecycle);
79         mCachedDevice = device;
80         mCompanionDeviceManager = context.getSystemService(CompanionDeviceManager.class);
81         mPackageManager = context.getPackageManager();
82         lifecycle.addObserver(this);
83     }
84 
85     @Override
init(PreferenceScreen screen)86     protected void init(PreferenceScreen screen) {
87         mProfilesContainer = screen.findPreference(getPreferenceKey());
88         mProfilesContainer.setLayoutResource(R.layout.preference_companion_app);
89     }
90 
getAssociations(String address)91     private List<Association> getAssociations(String address) {
92         return filter(
93                 mCompanionDeviceManager.getAllAssociations(),
94                 a -> Objects.equal(address, a.getDeviceMacAddress()));
95     }
96 
removePreference(PreferenceCategory container, String packageName)97     private static void removePreference(PreferenceCategory container, String packageName) {
98         Preference preference = container.findPreference(packageName);
99         if (preference != null) {
100             container.removePreference(preference);
101         }
102     }
103 
removeAssociationDialog(String packageName, String address, PreferenceCategory container, CharSequence appName, Context context)104     private void removeAssociationDialog(String packageName, String address,
105             PreferenceCategory container, CharSequence appName, Context context) {
106         DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> {
107             if (which == DialogInterface.BUTTON_POSITIVE) {
108                 removeAssociation(packageName, address, container);
109             }
110         };
111 
112         AlertDialog.Builder builder = new AlertDialog.Builder(context);
113 
114         builder.setPositiveButton(
115                 R.string.bluetooth_companion_app_remove_association_confirm_button,
116                 dialogClickListener)
117                 .setNegativeButton(android.R.string.cancel, dialogClickListener)
118                 .setTitle(R.string.bluetooth_companion_app_remove_association_dialog_title)
119                 .setMessage(mContext.getString(
120                         R.string.bluetooth_companion_app_body, appName, mCachedDevice.getName()))
121                 .show();
122     }
123 
removeAssociation(String packageName, String address, PreferenceCategory container)124     private static void removeAssociation(String packageName, String address,
125             PreferenceCategory container) {
126         try {
127             java.util.Objects.requireNonNull(ICompanionDeviceManager.Stub.asInterface(
128                     ServiceManager.getService(
129                             Context.COMPANION_DEVICE_SERVICE))).disassociate(
130                                     address, packageName);
131         } catch (RemoteException e) {
132             throw new RuntimeException(e);
133         }
134 
135         removePreference(container, packageName);
136     }
137 
getAppName(String packageName)138     private CharSequence getAppName(String packageName) {
139         CharSequence appName = null;
140         try {
141             appName = mPackageManager.getApplicationLabel(
142                     mPackageManager.getApplicationInfo(packageName, 0));
143         } catch (PackageManager.NameNotFoundException e) {
144             Log.e(LOG_TAG, "Package Not Found", e);
145         }
146 
147         return appName;
148     }
149 
getPreferencesNeedToShow(String address, PreferenceCategory container)150     private List<String> getPreferencesNeedToShow(String address, PreferenceCategory container) {
151         List<String> preferencesToRemove = new ArrayList<>();
152         Set<String> packages = getAssociations(address)
153                 .stream().map(Association::getPackageName)
154                 .collect(Collectors.toSet());
155 
156         for (int i = 0; i < container.getPreferenceCount(); i++) {
157             String preferenceKey = container.getPreference(i).getKey();
158             if (packages.isEmpty() || !packages.contains(preferenceKey)) {
159                 preferencesToRemove.add(preferenceKey);
160             }
161         }
162 
163         for (String preferenceName : preferencesToRemove) {
164             removePreference(container, preferenceName);
165         }
166 
167         return packages.stream()
168                 .filter(p -> container.findPreference(p) == null)
169                 .collect(Collectors.toList());
170     }
171 
172     /**
173      * Refreshes the state of the preferences for all the associations, possibly adding or
174      * removing preferences as needed.
175      */
176     @Override
refresh()177     protected void refresh() {
178         // Do nothing. More details in b/191992001
179     }
180 
181     /**
182      * Add preferences for each association for the bluetooth device
183      */
updatePreferences(Context context, String address, PreferenceCategory container)184     public void updatePreferences(Context context,
185             String address, PreferenceCategory container) {
186         // If the device is FastPair, remove CDM companion apps.
187         final BluetoothFeatureProvider bluetoothFeatureProvider = FeatureFactory.getFactory(context)
188                 .getBluetoothFeatureProvider(context);
189         final boolean sliceEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
190                 SettingsUIDeviceConfig.BT_SLICE_SETTINGS_ENABLED, true);
191         final Uri settingsUri = bluetoothFeatureProvider.getBluetoothDeviceSettingsUri(
192                 mCachedDevice.getDevice());
193         if (sliceEnabled && settingsUri != null) {
194             container.removeAll();
195             return;
196         }
197 
198         Set<String> addedPackages = new HashSet<>();
199 
200         for (String packageName : getPreferencesNeedToShow(address, container)) {
201             CharSequence appName = getAppName(packageName);
202 
203             if (TextUtils.isEmpty(appName) || !addedPackages.add(packageName)) {
204                 continue;
205             }
206 
207             Drawable removeIcon = context.getResources().getDrawable(R.drawable.ic_clear);
208             CompanionAppWidgetPreference preference = new CompanionAppWidgetPreference(
209                     removeIcon,
210                     v -> removeAssociationDialog(packageName, address, container, appName, context),
211                     context
212             );
213 
214             Drawable appIcon;
215 
216             try {
217                 appIcon = mPackageManager.getApplicationIcon(packageName);
218             } catch (PackageManager.NameNotFoundException e) {
219                 Log.e(LOG_TAG, "Icon Not Found", e);
220                 continue;
221             }
222             Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
223             preference.setIcon(appIcon);
224             preference.setTitle(appName.toString());
225             preference.setOnPreferenceClickListener(v -> {
226                 context.startActivity(intent);
227                 return true;
228             });
229 
230             preference.setKey(packageName);
231             preference.setVisible(true);
232             container.addPreference(preference);
233         }
234     }
235 
236     @Override
getPreferenceKey()237     public String getPreferenceKey() {
238         return KEY_DEVICE_COMPANION_APPS;
239     }
240 }
241