1 /* 2 * Copyright (C) 2020 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.network; 18 19 import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; 20 21 import static com.android.settings.slices.CustomSliceRegistry.PROVIDER_MODEL_SLICE_URI; 22 import static com.android.settings.slices.CustomSliceRegistry.WIFI_SLICE_URI; 23 24 import android.annotation.ColorInt; 25 import android.app.AlertDialog; 26 import android.app.AlertDialog.Builder; 27 import android.app.PendingIntent; 28 import android.app.settings.SettingsEnums; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.SharedPreferences; 32 import android.graphics.Color; 33 import android.graphics.drawable.ColorDrawable; 34 import android.graphics.drawable.Drawable; 35 import android.net.Uri; 36 import android.telephony.SubscriptionManager; 37 import android.util.EventLog; 38 import android.util.Log; 39 import android.view.WindowManager.LayoutParams; 40 41 import androidx.annotation.VisibleForTesting; 42 import androidx.core.graphics.drawable.IconCompat; 43 import androidx.slice.Slice; 44 import androidx.slice.builders.ListBuilder; 45 import androidx.slice.builders.SliceAction; 46 47 import com.android.settings.R; 48 import com.android.settings.SubSettings; 49 import com.android.settings.Utils; 50 import com.android.settings.network.telephony.MobileNetworkUtils; 51 import com.android.settings.network.telephony.NetworkProviderWorker; 52 import com.android.settings.slices.CustomSliceable; 53 import com.android.settings.slices.SliceBackgroundWorker; 54 import com.android.settings.slices.SliceBroadcastReceiver; 55 import com.android.settings.slices.SliceBuilderUtils; 56 import com.android.settings.wifi.WifiUtils; 57 import com.android.settings.wifi.slice.WifiSlice; 58 import com.android.settings.wifi.slice.WifiSliceItem; 59 import com.android.wifitrackerlib.WifiEntry; 60 61 import java.util.List; 62 import java.util.stream.Collectors; 63 64 /** 65 * {@link CustomSliceable} for Wi-Fi and mobile data connection, used by generic clients. 66 */ 67 // ToDo If the provider model become default design in the future, the code needs to refactor 68 // the whole structure and use new "data object", and then split provider model out of old design. 69 public class ProviderModelSlice extends WifiSlice { 70 71 private static final String TAG = "ProviderModelSlice"; 72 protected static final String PREF_NAME = "ProviderModelSlice"; 73 protected static final String PREF_HAS_TURNED_OFF_MOBILE_DATA = "PrefHasTurnedOffMobileData"; 74 75 private final ProviderModelSliceHelper mHelper; 76 private final SharedPreferences mSharedPref; 77 ProviderModelSlice(Context context)78 public ProviderModelSlice(Context context) { 79 super(context); 80 mHelper = getHelper(); 81 mSharedPref = getSharedPreference(); 82 } 83 84 @Override getUri()85 public Uri getUri() { 86 return PROVIDER_MODEL_SLICE_URI; 87 } 88 log(String s)89 private static void log(String s) { 90 Log.d(TAG, s); 91 } 92 isApRowCollapsed()93 protected boolean isApRowCollapsed() { 94 return false; 95 } 96 97 @Override getSlice()98 public Slice getSlice() { 99 // The provider model slice step: 100 // First section: Add the Ethernet item. 101 // Second section: Add the carrier item. 102 // Third section: Add the Wi-Fi toggle item. 103 // Fourth section: Add the connected Wi-Fi item. 104 // Fifth section: Add the Wi-Fi items which are not connected. 105 // Sixth section: Add the See All item. 106 final ListBuilder listBuilder = mHelper.createListBuilder(getUri()); 107 if (isGuestUser(mContext)) { 108 Log.e(TAG, "Guest user is not allowed to configure Internet!"); 109 EventLog.writeEvent(0x534e4554, "227470877", -1 /* UID */, "User is a guest"); 110 return listBuilder.build(); 111 } 112 113 int maxListSize = 0; 114 final NetworkProviderWorker worker = getWorker(); 115 if (worker != null) { 116 maxListSize = worker.getApRowCount(); 117 } else { 118 log("network provider worker is null."); 119 } 120 121 // First section: Add the Ethernet item. 122 if (getInternetType() == InternetUpdater.INTERNET_ETHERNET) { 123 log("get Ethernet item which is connected"); 124 listBuilder.addRow(createEthernetRow()); 125 maxListSize--; 126 } 127 128 // Second section: Add the carrier item. 129 if (!mHelper.isAirplaneModeEnabled()) { 130 final boolean hasCarrier = mHelper.hasCarrier(); 131 log("hasCarrier: " + hasCarrier); 132 if (hasCarrier) { 133 mHelper.updateTelephony(); 134 listBuilder.addRow( 135 mHelper.createCarrierRow( 136 worker != null ? worker.getNetworkTypeDescription() : "")); 137 maxListSize--; 138 } 139 } 140 141 // Third section: Add the Wi-Fi toggle item. 142 final boolean isWifiEnabled = mWifiManager.isWifiEnabled(); 143 listBuilder.addRow(createWifiToggleRow(mContext, isWifiEnabled)); 144 maxListSize--; 145 if (!isWifiEnabled) { 146 log("Wi-Fi is disabled"); 147 return listBuilder.build(); 148 } 149 List<WifiSliceItem> wifiList = (worker != null) ? worker.getResults() : null; 150 if (wifiList == null || wifiList.size() <= 0) { 151 log("Wi-Fi list is empty"); 152 return listBuilder.build(); 153 } 154 155 // Fourth section: Add the connected Wi-Fi item. 156 final WifiSliceItem connectedWifiItem = mHelper.getConnectedWifiItem(wifiList); 157 if (connectedWifiItem != null) { 158 log("get Wi-Fi item which is connected"); 159 listBuilder.addRow(getWifiSliceItemRow(connectedWifiItem)); 160 maxListSize--; 161 } 162 163 // Fifth section: Add the Wi-Fi items which are not connected. 164 log("get Wi-Fi items which are not connected. Wi-Fi items : " + wifiList.size()); 165 final List<WifiSliceItem> disconnectedWifiList = wifiList.stream() 166 .filter(item -> item.getConnectedState() != WifiEntry.CONNECTED_STATE_CONNECTED) 167 .limit(maxListSize - 1) 168 .collect(Collectors.toList()); 169 for (WifiSliceItem item : disconnectedWifiList) { 170 listBuilder.addRow(getWifiSliceItemRow(item)); 171 } 172 173 // Sixth section: Add the See All item. 174 log("add See-All"); 175 listBuilder.addRow(getSeeAllRow()); 176 177 return listBuilder.build(); 178 } 179 180 @Override getBroadcastIntent(Context context)181 public PendingIntent getBroadcastIntent(Context context) { 182 final Intent intent = new Intent(getUri().toString()) 183 // The FLAG_RECEIVER_FOREGROUND flag is necessary to avoid the intent delay of 184 // the first sending after the device restarts 185 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) 186 .setData(getUri()) 187 .setClass(context, SliceBroadcastReceiver.class); 188 return PendingIntent.getBroadcast(context, 0 /* requestCode */, intent, 189 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); 190 } 191 192 /** 193 * Update the current carrier's mobile data status. 194 */ 195 @Override onNotifyChange(Intent intent)196 public void onNotifyChange(Intent intent) { 197 final SubscriptionManager subscriptionManager = mHelper.getSubscriptionManager(); 198 if (subscriptionManager == null) { 199 return; 200 } 201 final int defaultSubId = subscriptionManager.getDefaultDataSubscriptionId(); 202 log("defaultSubId:" + defaultSubId); 203 204 if (!defaultSubscriptionIsUsable(defaultSubId)) { 205 return; 206 } 207 208 boolean isToggleAction = intent.hasExtra(EXTRA_TOGGLE_STATE); 209 boolean newState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE, 210 mHelper.isMobileDataEnabled()); 211 212 if (isToggleAction) { 213 // The ToggleAction is used to set mobile data enabled. 214 if (!newState && mSharedPref != null 215 && mSharedPref.getBoolean(PREF_HAS_TURNED_OFF_MOBILE_DATA, true)) { 216 String carrierName = mHelper.getMobileTitle(); 217 if (carrierName.equals(mContext.getString(R.string.mobile_data_settings_title))) { 218 carrierName = mContext.getString( 219 R.string.mobile_data_disable_message_default_carrier); 220 } 221 showMobileDataDisableDialog(getMobileDataDisableDialog(defaultSubId, carrierName)); 222 // If we need to display a reminder dialog box, do nothing here. 223 return; 224 } else { 225 log("setMobileDataEnabled: " + newState); 226 MobileNetworkUtils.setMobileDataEnabled(mContext, defaultSubId, newState, 227 false /* disableOtherSubscriptions */); 228 } 229 } 230 231 final boolean isDataEnabled = 232 isToggleAction ? newState : MobileNetworkUtils.isMobileDataEnabled(mContext); 233 doCarrierNetworkAction(isToggleAction, isDataEnabled, defaultSubId); 234 } 235 236 @VisibleForTesting getMobileDataDisableDialog(int defaultSubId, String carrierName)237 AlertDialog getMobileDataDisableDialog(int defaultSubId, String carrierName) { 238 return new Builder(mContext) 239 .setTitle(R.string.mobile_data_disable_title) 240 .setMessage(mContext.getString(R.string.mobile_data_disable_message, 241 carrierName)) 242 .setNegativeButton(android.R.string.cancel, 243 (dialog, which) -> { 244 // Because the toggle of mobile data will be turned off first, if the 245 // user cancels the operation, we need to update the slice to correct 246 // the toggle state. 247 final NetworkProviderWorker worker = getWorker(); 248 if (worker != null) { 249 worker.updateSlice(); 250 } 251 }) 252 .setPositiveButton( 253 com.android.internal.R.string.alert_windows_notification_turn_off_action, 254 (dialog, which) -> { 255 log("setMobileDataEnabled: false"); 256 MobileNetworkUtils.setMobileDataEnabled(mContext, defaultSubId, 257 false /* enabled */, 258 false /* disableOtherSubscriptions */); 259 doCarrierNetworkAction(true /* isToggleAction */, 260 false /* isDataEanbed */, defaultSubId); 261 if (mSharedPref != null) { 262 SharedPreferences.Editor editor = mSharedPref.edit(); 263 editor.putBoolean(PREF_HAS_TURNED_OFF_MOBILE_DATA, false); 264 editor.apply(); 265 } 266 }) 267 .create(); 268 } 269 showMobileDataDisableDialog(AlertDialog dialog)270 private void showMobileDataDisableDialog(AlertDialog dialog) { 271 if (dialog == null) { 272 log("AlertDialog is null"); 273 return; 274 } 275 276 dialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG); 277 dialog.show(); 278 } 279 280 @VisibleForTesting doCarrierNetworkAction(boolean isToggleAction, boolean isDataEnabled, int subId)281 void doCarrierNetworkAction(boolean isToggleAction, boolean isDataEnabled, int subId) { 282 final NetworkProviderWorker worker = getWorker(); 283 if (worker == null) { 284 return; 285 } 286 287 if (isToggleAction) { 288 worker.setCarrierNetworkEnabledIfNeeded(isDataEnabled, subId); 289 return; 290 } 291 292 if (isDataEnabled) { 293 worker.connectCarrierNetwork(); 294 } 295 } 296 297 @Override getIntent()298 public Intent getIntent() { 299 final String screenTitle = mContext.getText(R.string.provider_internet_settings).toString(); 300 return SliceBuilderUtils.buildSearchResultPageIntent(mContext, 301 NetworkProviderSettings.class.getName(), "" /* key */, screenTitle, 302 SettingsEnums.SLICE, this) 303 .setClassName(mContext.getPackageName(), SubSettings.class.getName()) 304 .setData(getUri()); 305 } 306 307 @Override getBackgroundWorkerClass()308 public Class getBackgroundWorkerClass() { 309 if (isGuestUser(mContext)) return null; 310 311 return NetworkProviderWorker.class; 312 } 313 314 @VisibleForTesting getHelper()315 ProviderModelSliceHelper getHelper() { 316 return new ProviderModelSliceHelper(mContext, this); 317 } 318 319 @VisibleForTesting getWorker()320 NetworkProviderWorker getWorker() { 321 return SliceBackgroundWorker.getInstance(getUri()); 322 } 323 324 @VisibleForTesting getSharedPreference()325 SharedPreferences getSharedPreference() { 326 return mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); 327 } 328 getInternetType()329 private @InternetUpdater.InternetType int getInternetType() { 330 final NetworkProviderWorker worker = getWorker(); 331 if (worker == null) { 332 return InternetUpdater.INTERNET_NETWORKS_AVAILABLE; 333 } 334 return worker.getInternetType(); 335 } 336 337 @VisibleForTesting createEthernetRow()338 ListBuilder.RowBuilder createEthernetRow() { 339 final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder(); 340 final Drawable drawable = mContext.getDrawable(R.drawable.ic_settings_ethernet); 341 if (drawable != null) { 342 drawable.setTintList(Utils.getColorAttr(mContext, android.R.attr.colorAccent)); 343 rowBuilder.setTitleItem(Utils.createIconWithDrawable(drawable), ListBuilder.ICON_IMAGE); 344 } 345 return rowBuilder 346 .setTitle(mContext.getText(R.string.ethernet)) 347 .setSubtitle(mContext.getText(R.string.to_switch_networks_disconnect_ethernet)); 348 } 349 350 /** 351 * @return a {@link ListBuilder.RowBuilder} of the Wi-Fi toggle. 352 */ createWifiToggleRow(Context context, boolean isWifiEnabled)353 protected ListBuilder.RowBuilder createWifiToggleRow(Context context, boolean isWifiEnabled) { 354 final Intent intent = new Intent(WIFI_SLICE_URI.toString()) 355 .setData(WIFI_SLICE_URI) 356 .setClass(context, SliceBroadcastReceiver.class) 357 .putExtra(EXTRA_TOGGLE_STATE, !isWifiEnabled) 358 // The FLAG_RECEIVER_FOREGROUND flag is necessary to avoid the intent delay of 359 // the first sending after the device restarts 360 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 361 final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 362 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 363 final SliceAction toggleSliceAction = SliceAction.createToggle(pendingIntent, 364 null /* actionTitle */, isWifiEnabled); 365 return new ListBuilder.RowBuilder() 366 .setTitle(context.getString(R.string.wifi_settings)) 367 .setPrimaryAction(toggleSliceAction); 368 } 369 getSeeAllRow()370 protected ListBuilder.RowBuilder getSeeAllRow() { 371 final CharSequence title = mContext.getText(R.string.previous_connected_see_all); 372 final IconCompat icon = getSeeAllIcon(); 373 return new ListBuilder.RowBuilder() 374 .setTitleItem(icon, ListBuilder.ICON_IMAGE) 375 .setTitle(title) 376 .setPrimaryAction(getPrimaryAction(icon, title)); 377 } 378 getSeeAllIcon()379 protected IconCompat getSeeAllIcon() { 380 final Drawable drawable = mContext.getDrawable(R.drawable.ic_arrow_forward); 381 if (drawable != null) { 382 drawable.setTint( 383 Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal)); 384 return Utils.createIconWithDrawable(drawable); 385 } 386 return Utils.createIconWithDrawable(new ColorDrawable(Color.TRANSPARENT)); 387 } 388 getPrimaryAction(IconCompat icon, CharSequence title)389 protected SliceAction getPrimaryAction(IconCompat icon, CharSequence title) { 390 final PendingIntent intent = PendingIntent.getActivity(mContext, 0 /* requestCode */, 391 getIntent(), PendingIntent.FLAG_IMMUTABLE /* flags */); 392 return SliceAction.createDeeplink(intent, icon, ListBuilder.ICON_IMAGE, title); 393 } 394 395 @Override getWifiSliceItemLevelIcon(WifiSliceItem wifiSliceItem)396 protected IconCompat getWifiSliceItemLevelIcon(WifiSliceItem wifiSliceItem) { 397 if (wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED 398 && getInternetType() != InternetUpdater.INTERNET_WIFI) { 399 final @ColorInt int tint = Utils.getColorAttrDefaultColor(mContext, 400 android.R.attr.colorControlNormal); 401 final Drawable drawable = mContext.getDrawable( 402 WifiUtils.getInternetIconResource( 403 wifiSliceItem.getLevel(), wifiSliceItem.shouldShowXLevelIcon())); 404 drawable.setTint(tint); 405 return Utils.createIconWithDrawable(drawable); 406 } 407 return super.getWifiSliceItemLevelIcon(wifiSliceItem); 408 } 409 410 /** 411 * Wrap the subscriptionManager call for test mocking. 412 */ 413 @VisibleForTesting defaultSubscriptionIsUsable(int defaultSubId)414 protected boolean defaultSubscriptionIsUsable(int defaultSubId) { 415 return SubscriptionManager.isUsableSubscriptionId(defaultSubId); 416 } 417 } 418