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