• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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.car.settings.bluetooth;
18 
19 import static android.os.UserManager.DISALLOW_BLUETOOTH;
20 import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
21 
22 import static com.android.car.settings.enterprise.ActionDisabledByAdminDialogFragment.DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG;
23 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByDpm;
24 
25 import android.bluetooth.BluetoothAdapter;
26 import android.car.drivingstate.CarUxRestrictions;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.pm.ApplicationInfo;
32 import android.content.pm.PackageManager;
33 import android.content.pm.ResolveInfo;
34 import android.os.UserManager;
35 
36 import androidx.lifecycle.LifecycleObserver;
37 import androidx.preference.Preference;
38 
39 import com.android.car.settings.R;
40 import com.android.car.settings.common.FragmentController;
41 import com.android.car.settings.common.Logger;
42 import com.android.car.settings.common.PreferenceController;
43 import com.android.car.settings.common.Settings;
44 import com.android.car.settings.enterprise.EnterpriseUtils;
45 
46 import java.util.List;
47 import java.util.stream.Collectors;
48 
49 /**
50  * Controls a preference that, when clicked, launches the page for pairing new Bluetooth devices.
51  * The associated preference for this controller should define the fragment attribute or an intent
52  * to launch for the Bluetooth device pairing page. If the adapter is not enabled, a click will
53  * enable Bluetooth. The summary message is updated to indicate this effect to the user.
54  */
55 public class PairNewDevicePreferenceController extends PreferenceController<Preference> implements
56         LifecycleObserver {
57 
58     private static final Logger LOG = new Logger(PairNewDevicePreferenceController.class);
59 
60     private final IntentFilter mIntentFilter = new IntentFilter(
61             BluetoothAdapter.ACTION_STATE_CHANGED);
62     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
63         @Override
64         public void onReceive(Context context, Intent intent) {
65             refreshUi();
66         }
67     };
68     private final UserManager mUserManager;
69 
PairNewDevicePreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)70     public PairNewDevicePreferenceController(Context context, String preferenceKey,
71             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
72         super(context, preferenceKey, fragmentController, uxRestrictions);
73         mUserManager = UserManager.get(context);
74     }
75 
76     @Override
getPreferenceType()77     protected Class<Preference> getPreferenceType() {
78         return Preference.class;
79     }
80 
81     @Override
onCreateInternal()82     protected void onCreateInternal() {
83         super.onCreateInternal();
84         setClickableWhileDisabled(getPreference(), /* clickable= */ true, p -> {
85             if (getAvailabilityStatus() == AVAILABLE_FOR_VIEWING) {
86                 showActionDisabledByAdminDialog();
87             }
88         });
89     }
90 
showActionDisabledByAdminDialog()91     private void showActionDisabledByAdminDialog() {
92         getFragmentController().showDialog(
93                 EnterpriseUtils.getActionDisabledByAdminDialog(getContext(),
94                         DISALLOW_CONFIG_BLUETOOTH),
95                 DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG);
96     }
97 
98     @Override
checkInitialized()99     protected void checkInitialized() {
100         if (getPreference().getIntent() == null && getPreference().getFragment() == null) {
101             throw new IllegalStateException(
102                     "Preference should declare fragment or intent for page to pair new devices");
103         }
104     }
105 
106     @Override
getDefaultAvailabilityStatus()107     protected int getDefaultAvailabilityStatus() {
108         if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
109             return UNSUPPORTED_ON_DEVICE;
110         }
111         if (hasUserRestrictionByDpm(getContext(), DISALLOW_CONFIG_BLUETOOTH)) {
112             return AVAILABLE_FOR_VIEWING;
113         }
114         return isUserRestricted() ? DISABLED_FOR_PROFILE : AVAILABLE;
115     }
116 
isUserRestricted()117     private boolean isUserRestricted() {
118         return mUserManager.hasUserRestriction(DISALLOW_BLUETOOTH)
119                 || mUserManager.hasUserRestriction(DISALLOW_CONFIG_BLUETOOTH);
120     }
121 
122     @Override
onStartInternal()123     protected void onStartInternal() {
124         getContext().registerReceiver(mReceiver, mIntentFilter);
125     }
126 
127     @Override
onStopInternal()128     protected void onStopInternal() {
129         getContext().unregisterReceiver(mReceiver);
130     }
131 
132     @Override
updateState(Preference preference)133     protected void updateState(Preference preference) {
134         preference.setSummary(
135                 BluetoothAdapter.getDefaultAdapter().isEnabled() ? "" : getContext().getString(
136                         R.string.bluetooth_pair_new_device_summary));
137     }
138 
139     /**
140      * Checks whether provided application object represents system application.
141      */
isSystemApp(ResolveInfo info)142     private boolean isSystemApp(ResolveInfo info) {
143         return (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
144     }
145 
146     @Override
handlePreferenceClicked(Preference preference)147     protected boolean handlePreferenceClicked(Preference preference) {
148         // Enable the adapter if it is not on (user is notified via summary message).
149         BluetoothAdapter.getDefaultAdapter().enable();
150 
151         Context context = getContext();
152 
153         if (!context.getResources().getBoolean(
154                 R.bool.config_use_custom_pair_device_flow)) {
155             LOG.i("Custom pairing device flow is deactivated");
156             return false;
157         }
158 
159         Intent intent = getPreference().getIntent();
160         if (intent == null) {
161             LOG.e("Preference doesn't contain " + Settings.ACTION_PAIR_DEVICE_SETTINGS + " intent");
162             return false;
163         }
164 
165         List<ResolveInfo> infos = context.getPackageManager().queryIntentActivities(intent,
166                 PackageManager.MATCH_DEFAULT_ONLY);
167 
168         List<String> packages = infos.stream().filter(this::isSystemApp).map(
169                 ri -> ri.activityInfo.packageName).collect(
170                 Collectors.toUnmodifiableList());
171 
172         LOG.i("Found " + packages.size() + " system packages matching "
173                 + intent.getAction() + " intent action. Found packages are: " + packages);
174 
175         if (packages.size() > 1) {
176             LOG.w("More than one package found satisfying " + intent.getAction()
177                     + " intent action. Picking the first one.");
178         }
179 
180         for (String packageName : packages) {
181             LOG.i("Starting custom pair device activity identified by " + packageName + " package");
182 
183             intent.setPackage(packageName);
184             context.startActivity(intent);
185             return true; // new activity will handle pairing workflow
186         }
187 
188         return false;
189     }
190 }
191