1 /*
2  * Copyright (C) 2014 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.systemui.qs.tiles;
18 
19 import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
20 
21 import android.content.Intent;
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.os.UserManager;
25 import android.provider.Settings;
26 import android.service.quicksettings.Tile;
27 import android.util.Log;
28 import android.view.View;
29 import android.widget.Switch;
30 
31 import androidx.annotation.Nullable;
32 
33 import com.android.internal.logging.MetricsLogger;
34 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
35 import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
36 import com.android.systemui.R;
37 import com.android.systemui.dagger.qualifiers.Background;
38 import com.android.systemui.dagger.qualifiers.Main;
39 import com.android.systemui.plugins.ActivityStarter;
40 import com.android.systemui.plugins.FalsingManager;
41 import com.android.systemui.plugins.qs.QSTile.BooleanState;
42 import com.android.systemui.plugins.statusbar.StatusBarStateController;
43 import com.android.systemui.qs.QSHost;
44 import com.android.systemui.qs.QsEventLogger;
45 import com.android.systemui.qs.logging.QSLogger;
46 import com.android.systemui.qs.tileimpl.QSTileImpl;
47 import com.android.systemui.statusbar.policy.DataSaverController;
48 import com.android.systemui.statusbar.policy.HotspotController;
49 
50 import javax.inject.Inject;
51 
52 /** Quick settings tile: Hotspot **/
53 public class HotspotTile extends QSTileImpl<BooleanState> {
54 
55     public static final String TILE_SPEC = "hotspot";
56     private final HotspotController mHotspotController;
57     private final DataSaverController mDataSaverController;
58 
59     private final HotspotAndDataSaverCallbacks mCallbacks = new HotspotAndDataSaverCallbacks();
60     private boolean mListening;
61 
62     @Inject
HotspotTile( QSHost host, QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, HotspotController hotspotController, DataSaverController dataSaverController )63     public HotspotTile(
64             QSHost host,
65             QsEventLogger uiEventLogger,
66             @Background Looper backgroundLooper,
67             @Main Handler mainHandler,
68             FalsingManager falsingManager,
69             MetricsLogger metricsLogger,
70             StatusBarStateController statusBarStateController,
71             ActivityStarter activityStarter,
72             QSLogger qsLogger,
73             HotspotController hotspotController,
74             DataSaverController dataSaverController
75     ) {
76         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
77                 statusBarStateController, activityStarter, qsLogger);
78         mHotspotController = hotspotController;
79         mDataSaverController = dataSaverController;
80         mHotspotController.observe(this, mCallbacks);
81         mDataSaverController.observe(this, mCallbacks);
82     }
83 
84     @Override
isAvailable()85     public boolean isAvailable() {
86         return mHotspotController.isHotspotSupported();
87     }
88 
89     @Override
handleDestroy()90     protected void handleDestroy() {
91         super.handleDestroy();
92     }
93 
94     @Override
handleSetListening(boolean listening)95     public void handleSetListening(boolean listening) {
96         super.handleSetListening(listening);
97         if (mListening == listening) return;
98         mListening = listening;
99         if (listening) {
100             refreshState();
101         }
102     }
103 
104     @Override
getLongClickIntent()105     public Intent getLongClickIntent() {
106         return new Intent(Settings.ACTION_WIFI_TETHER_SETTING);
107     }
108 
109     @Override
newTileState()110     public BooleanState newTileState() {
111         return new BooleanState();
112     }
113 
114     @Override
handleClick(@ullable View view)115     protected void handleClick(@Nullable View view) {
116         final boolean isEnabled = mState.value;
117         if (!isEnabled && mDataSaverController.isDataSaverEnabled()) {
118             return;
119         }
120         // Immediately enter transient enabling state when turning hotspot on.
121         refreshState(isEnabled ? null : ARG_SHOW_TRANSIENT_ENABLING);
122         mHotspotController.setHotspotEnabled(!isEnabled);
123     }
124 
125     @Override
getTileLabel()126     public CharSequence getTileLabel() {
127         return mContext.getString(R.string.quick_settings_hotspot_label);
128     }
129 
130     @Override
handleUpdateState(BooleanState state, Object arg)131     protected void handleUpdateState(BooleanState state, Object arg) {
132         final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING;
133 
134         final int numConnectedDevices;
135         final boolean isTransient = transientEnabling || mHotspotController.isHotspotTransient();
136         final boolean isDataSaverEnabled;
137 
138         checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_CONFIG_TETHERING);
139 
140         if (arg instanceof CallbackInfo) {
141             final CallbackInfo info = (CallbackInfo) arg;
142             state.value = transientEnabling || info.isHotspotEnabled;
143             numConnectedDevices = info.numConnectedDevices;
144             isDataSaverEnabled = info.isDataSaverEnabled;
145         } else {
146             state.value = transientEnabling || mHotspotController.isHotspotEnabled();
147             numConnectedDevices = mHotspotController.getNumConnectedDevices();
148             isDataSaverEnabled = mDataSaverController.isDataSaverEnabled();
149         }
150 
151         state.label = mContext.getString(R.string.quick_settings_hotspot_label);
152         state.isTransient = isTransient;
153         if (state.isTransient) {
154             state.icon = ResourceIcon.get(
155                     R.drawable.qs_hotspot_icon_search);
156         } else {
157             state.icon = ResourceIcon.get(state.value
158                     ? R.drawable.qs_hotspot_icon_on : R.drawable.qs_hotspot_icon_off);
159         }
160         state.expandedAccessibilityClassName = Switch.class.getName();
161         state.contentDescription = state.label;
162 
163         final boolean isWifiTetheringAllowed =
164                 WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(mHost.getUserContext());
165         final boolean isTileUnavailable = isDataSaverEnabled || !isWifiTetheringAllowed;
166         final boolean isTileActive = (state.value || state.isTransient);
167 
168         if (isTileUnavailable) {
169             state.state = Tile.STATE_UNAVAILABLE;
170         } else {
171             state.state = isTileActive ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
172         }
173 
174         state.secondaryLabel = getSecondaryLabel(isTileActive, isTransient, isDataSaverEnabled,
175                 numConnectedDevices, isWifiTetheringAllowed);
176         state.stateDescription = state.secondaryLabel;
177     }
178 
179     @Nullable
getSecondaryLabel(boolean isActive, boolean isTransient, boolean isDataSaverEnabled, int numConnectedDevices, boolean isWifiTetheringAllowed)180     private String getSecondaryLabel(boolean isActive, boolean isTransient,
181             boolean isDataSaverEnabled, int numConnectedDevices, boolean isWifiTetheringAllowed) {
182         if (!isWifiTetheringAllowed) {
183             return mContext.getString(R.string.wifitrackerlib_admin_restricted_network);
184         } else if (isTransient) {
185             return mContext.getString(R.string.quick_settings_hotspot_secondary_label_transient);
186         } else if (isDataSaverEnabled) {
187             return mContext.getString(
188                     R.string.quick_settings_hotspot_secondary_label_data_saver_enabled);
189         } else if (numConnectedDevices > 0 && isActive) {
190             return icuMessageFormat(mContext.getResources(),
191                     R.string.quick_settings_hotspot_secondary_label_num_devices,
192                     numConnectedDevices);
193         }
194 
195         return null;
196     }
197 
198     @Override
getMetricsCategory()199     public int getMetricsCategory() {
200         return MetricsEvent.QS_HOTSPOT;
201     }
202 
203     /**
204      * Listens to changes made to hotspot and data saver states (to toggle tile availability).
205      */
206     private final class HotspotAndDataSaverCallbacks implements HotspotController.Callback,
207             DataSaverController.Listener {
208         CallbackInfo mCallbackInfo = new CallbackInfo();
209 
210         @Override
onDataSaverChanged(boolean isDataSaving)211         public void onDataSaverChanged(boolean isDataSaving) {
212             mCallbackInfo.isDataSaverEnabled = isDataSaving;
213             refreshState(mCallbackInfo);
214         }
215 
216         @Override
onHotspotChanged(boolean enabled, int numDevices)217         public void onHotspotChanged(boolean enabled, int numDevices) {
218             mCallbackInfo.isHotspotEnabled = enabled;
219             mCallbackInfo.numConnectedDevices = numDevices;
220             refreshState(mCallbackInfo);
221         }
222 
223         @Override
onHotspotAvailabilityChanged(boolean available)224         public void onHotspotAvailabilityChanged(boolean available) {
225             if (!available) {
226                 Log.d(TAG, "Tile removed. Hotspot no longer available");
227                 mHost.removeTile(getTileSpec());
228             }
229         }
230     }
231 
232     /**
233      * Holder for any hotspot state info that needs to passed from the callback to
234      * {@link #handleUpdateState(State, Object)}.
235      */
236     protected static final class CallbackInfo {
237         boolean isHotspotEnabled;
238         int numConnectedDevices;
239         boolean isDataSaverEnabled;
240 
241         @Override
toString()242         public String toString() {
243             return new StringBuilder("CallbackInfo[")
244                     .append("isHotspotEnabled=").append(isHotspotEnabled)
245                     .append(",numConnectedDevices=").append(numConnectedDevices)
246                     .append(",isDataSaverEnabled=").append(isDataSaverEnabled)
247                     .append(']').toString();
248         }
249     }
250 }
251