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 package com.android.settings.bluetooth;
17 
18 import android.app.settings.SettingsEnums;
19 import android.bluetooth.BluetoothAdapter;
20 import android.content.Context;
21 import android.util.Log;
22 import android.view.View;
23 
24 import androidx.annotation.VisibleForTesting;
25 
26 import com.android.settings.R;
27 import com.android.settings.core.SubSettingLauncher;
28 import com.android.settings.location.BluetoothScanningFragment;
29 import com.android.settings.overlay.FeatureFactory;
30 import com.android.settings.widget.SwitchWidgetController;
31 import com.android.settingslib.core.lifecycle.LifecycleObserver;
32 import com.android.settingslib.core.lifecycle.events.OnStart;
33 import com.android.settingslib.core.lifecycle.events.OnStop;
34 import com.android.settingslib.widget.FooterPreference;
35 
36 /**
37  * PreferenceController to update of bluetooth state. All behavior except managing the footer text
38  * is delegated to the SwitchWidgetController it uses.
39  */
40 public class BluetoothSwitchPreferenceController
41         implements LifecycleObserver,
42                 OnStart,
43                 OnStop,
44                 SwitchWidgetController.OnSwitchChangeListener,
45                 View.OnClickListener {
46     private static final String TAG = "BluetoothSwitchPrefCtrl";
47 
48     private BluetoothEnabler mBluetoothEnabler;
49     private RestrictionUtils mRestrictionUtils;
50     private SwitchWidgetController mSwitch;
51     private Context mContext;
52     private BluetoothAdapter mBluetoothAdapter;
53     private FooterPreference mFooterPreference;
54     private boolean mIsAlwaysDiscoverable;
55 
56     @VisibleForTesting AlwaysDiscoverable mAlwaysDiscoverable;
57 
BluetoothSwitchPreferenceController( Context context, SwitchWidgetController switchController, FooterPreference footerPreference)58     public BluetoothSwitchPreferenceController(
59             Context context,
60             SwitchWidgetController switchController,
61             FooterPreference footerPreference) {
62         this(context, new RestrictionUtils(), switchController, footerPreference);
63     }
64 
65     @VisibleForTesting
BluetoothSwitchPreferenceController( Context context, RestrictionUtils restrictionUtils, SwitchWidgetController switchController, FooterPreference footerPreference)66     public BluetoothSwitchPreferenceController(
67             Context context,
68             RestrictionUtils restrictionUtils,
69             SwitchWidgetController switchController,
70             FooterPreference footerPreference) {
71         mRestrictionUtils = restrictionUtils;
72         mSwitch = switchController;
73         mContext = context;
74         mFooterPreference = footerPreference;
75 
76         mSwitch.setupView();
77         updateText(mSwitch.isChecked());
78 
79         mBluetoothEnabler =
80                 new BluetoothEnabler(
81                         context,
82                         switchController,
83                         FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(),
84                         SettingsEnums.ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE,
85                         mRestrictionUtils);
86         mBluetoothEnabler.setToggleCallback(this);
87         mAlwaysDiscoverable = new AlwaysDiscoverable(context);
88         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
89     }
90 
91     @Override
onStart()92     public void onStart() {
93         mBluetoothEnabler.resume(mContext);
94         if (mIsAlwaysDiscoverable) {
95             mAlwaysDiscoverable.start();
96         }
97         if (mSwitch != null) {
98             updateText(mSwitch.isChecked());
99         }
100     }
101 
102     @Override
onStop()103     public void onStop() {
104         mBluetoothEnabler.pause();
105         if (mIsAlwaysDiscoverable) {
106             mAlwaysDiscoverable.stop();
107         }
108     }
109 
110     /**
111      * Set whether the device can be discovered. By default the value will be {@code false}.
112      *
113      * @param isAlwaysDiscoverable {@code true} if the device can be discovered, otherwise {@code
114      *     false}
115      */
setAlwaysDiscoverable(boolean isAlwaysDiscoverable)116     public void setAlwaysDiscoverable(boolean isAlwaysDiscoverable) {
117         mIsAlwaysDiscoverable = isAlwaysDiscoverable;
118     }
119 
120     @Override
onSwitchToggled(boolean isChecked)121     public boolean onSwitchToggled(boolean isChecked) {
122         updateText(isChecked);
123         return true;
124     }
125 
126     @Override
onClick(View v)127     public void onClick(View v) {
128         // send users to scanning settings if they click on the link in the summary text
129         new SubSettingLauncher(mContext)
130                 .setDestination(BluetoothScanningFragment.class.getName())
131                 .setSourceMetricsCategory(SettingsEnums.BLUETOOTH_FRAGMENT)
132                 .launch();
133     }
134 
135     @VisibleForTesting
updateText(boolean isChecked)136     void updateText(boolean isChecked) {
137         if (!isChecked && Utils.isBluetoothScanningEnabled(mContext)) {
138             if (isAutoOnFeatureAvailable()) {
139                 mFooterPreference.setTitle(
140                         R.string.bluetooth_scanning_on_info_message_auto_on_available);
141             } else {
142                 mFooterPreference.setTitle(R.string.bluetooth_scanning_on_info_message);
143             }
144             mFooterPreference.setLearnMoreText(mContext.getString(R.string.bluetooth_scan_change));
145             mFooterPreference.setLearnMoreAction(v -> onClick(v));
146         } else {
147             if (isAutoOnFeatureAvailable()) {
148                 mFooterPreference.setTitle(
149                         R.string.bluetooth_empty_list_bluetooth_off_auto_on_available);
150             } else {
151                 mFooterPreference.setTitle(R.string.bluetooth_empty_list_bluetooth_off);
152             }
153             mFooterPreference.setLearnMoreText("");
154             mFooterPreference.setLearnMoreAction(null);
155         }
156     }
157 
isAutoOnFeatureAvailable()158     private boolean isAutoOnFeatureAvailable() {
159         if (mBluetoothAdapter == null) {
160             return false;
161         }
162         try {
163             return mBluetoothAdapter.isAutoOnSupported();
164         } catch (Exception e) {
165             // Server could throw TimeoutException, InterruptedException or ExecutionException
166             Log.e(TAG, "Error calling isAutoOnFeatureAvailable()", e);
167             return false;
168         }
169     }
170 }
171