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