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 MobileNetworkUtils.setMobileDataEnabled(mContext, defaultSubId, newState, 226 false /* disableOtherSubscriptions */); 227 } 228 } 229 230 final boolean isDataEnabled = 231 isToggleAction ? newState : MobileNetworkUtils.isMobileDataEnabled(mContext); 232 doCarrierNetworkAction(isToggleAction, isDataEnabled, defaultSubId); 233 } 234 235 @VisibleForTesting getMobileDataDisableDialog(int defaultSubId, String carrierName)236 AlertDialog getMobileDataDisableDialog(int defaultSubId, String carrierName) { 237 return new Builder(mContext) 238 .setTitle(R.string.mobile_data_disable_title) 239 .setMessage(mContext.getString(R.string.mobile_data_disable_message, 240 carrierName)) 241 .setNegativeButton(android.R.string.cancel, 242 (dialog, which) -> { 243 // Because the toggle of mobile data will be turned off first, if the 244 // user cancels the operation, we need to update the slice to correct 245 // the toggle state. 246 final NetworkProviderWorker worker = getWorker(); 247 if (worker != null) { 248 worker.updateSlice(); 249 } 250 }) 251 .setPositiveButton( 252 com.android.internal.R.string.alert_windows_notification_turn_off_action, 253 (dialog, which) -> { 254 MobileNetworkUtils.setMobileDataEnabled(mContext, defaultSubId, 255 false /* enabled */, 256 false /* disableOtherSubscriptions */); 257 doCarrierNetworkAction(true /* isToggleAction */, 258 false /* isDataEanbed */, defaultSubId); 259 if (mSharedPref != null) { 260 SharedPreferences.Editor editor = mSharedPref.edit(); 261 editor.putBoolean(PREF_HAS_TURNED_OFF_MOBILE_DATA, false); 262 editor.apply(); 263 } 264 }) 265 .create(); 266 } 267 showMobileDataDisableDialog(AlertDialog dialog)268 private void showMobileDataDisableDialog(AlertDialog dialog) { 269 if (dialog == null) { 270 log("AlertDialog is null"); 271 return; 272 } 273 274 dialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG); 275 dialog.show(); 276 } 277 278 @VisibleForTesting doCarrierNetworkAction(boolean isToggleAction, boolean isDataEnabled, int subId)279 void doCarrierNetworkAction(boolean isToggleAction, boolean isDataEnabled, int subId) { 280 final NetworkProviderWorker worker = getWorker(); 281 if (worker == null) { 282 return; 283 } 284 285 if (isToggleAction) { 286 worker.setCarrierNetworkEnabledIfNeeded(isDataEnabled, subId); 287 return; 288 } 289 290 if (isDataEnabled) { 291 worker.connectCarrierNetwork(); 292 } 293 } 294 295 @Override getIntent()296 public Intent getIntent() { 297 final String screenTitle = mContext.getText(R.string.provider_internet_settings).toString(); 298 return SliceBuilderUtils.buildSearchResultPageIntent(mContext, 299 NetworkProviderSettings.class.getName(), "" /* key */, screenTitle, 300 SettingsEnums.SLICE, this) 301 .setClassName(mContext.getPackageName(), SubSettings.class.getName()) 302 .setData(getUri()); 303 } 304 305 @Override getBackgroundWorkerClass()306 public Class getBackgroundWorkerClass() { 307 if (isGuestUser(mContext)) return null; 308 309 return NetworkProviderWorker.class; 310 } 311 312 @VisibleForTesting getHelper()313 ProviderModelSliceHelper getHelper() { 314 return new ProviderModelSliceHelper(mContext, this); 315 } 316 317 @VisibleForTesting getWorker()318 NetworkProviderWorker getWorker() { 319 return SliceBackgroundWorker.getInstance(getUri()); 320 } 321 322 @VisibleForTesting getSharedPreference()323 SharedPreferences getSharedPreference() { 324 return mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); 325 } 326 getInternetType()327 private @InternetUpdater.InternetType int getInternetType() { 328 final NetworkProviderWorker worker = getWorker(); 329 if (worker == null) { 330 return InternetUpdater.INTERNET_NETWORKS_AVAILABLE; 331 } 332 return worker.getInternetType(); 333 } 334 335 @VisibleForTesting createEthernetRow()336 ListBuilder.RowBuilder createEthernetRow() { 337 final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder(); 338 final Drawable drawable = mContext.getDrawable(R.drawable.ic_settings_ethernet); 339 if (drawable != null) { 340 drawable.setTintList(Utils.getColorAttr(mContext, android.R.attr.colorAccent)); 341 rowBuilder.setTitleItem(Utils.createIconWithDrawable(drawable), ListBuilder.ICON_IMAGE); 342 } 343 return rowBuilder 344 .setTitle(mContext.getText(R.string.ethernet)) 345 .setSubtitle(mContext.getText(R.string.to_switch_networks_disconnect_ethernet)); 346 } 347 348 /** 349 * @return a {@link ListBuilder.RowBuilder} of the Wi-Fi toggle. 350 */ createWifiToggleRow(Context context, boolean isWifiEnabled)351 protected ListBuilder.RowBuilder createWifiToggleRow(Context context, boolean isWifiEnabled) { 352 final Intent intent = new Intent(WIFI_SLICE_URI.toString()) 353 .setData(WIFI_SLICE_URI) 354 .setClass(context, SliceBroadcastReceiver.class) 355 .putExtra(EXTRA_TOGGLE_STATE, !isWifiEnabled) 356 // The FLAG_RECEIVER_FOREGROUND flag is necessary to avoid the intent delay of 357 // the first sending after the device restarts 358 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 359 final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 360 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 361 final SliceAction toggleSliceAction = SliceAction.createToggle(pendingIntent, 362 null /* actionTitle */, isWifiEnabled); 363 return new ListBuilder.RowBuilder() 364 .setTitle(context.getString(R.string.wifi_settings)) 365 .setPrimaryAction(toggleSliceAction); 366 } 367 getSeeAllRow()368 protected ListBuilder.RowBuilder getSeeAllRow() { 369 final CharSequence title = mContext.getText(R.string.previous_connected_see_all); 370 final IconCompat icon = getSeeAllIcon(); 371 return new ListBuilder.RowBuilder() 372 .setTitleItem(icon, ListBuilder.ICON_IMAGE) 373 .setTitle(title) 374 .setPrimaryAction(getPrimaryAction(icon, title)); 375 } 376 getSeeAllIcon()377 protected IconCompat getSeeAllIcon() { 378 final Drawable drawable = mContext.getDrawable(R.drawable.ic_arrow_forward); 379 if (drawable != null) { 380 drawable.setTint( 381 Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal)); 382 return Utils.createIconWithDrawable(drawable); 383 } 384 return Utils.createIconWithDrawable(new ColorDrawable(Color.TRANSPARENT)); 385 } 386 getPrimaryAction(IconCompat icon, CharSequence title)387 protected SliceAction getPrimaryAction(IconCompat icon, CharSequence title) { 388 final PendingIntent intent = PendingIntent.getActivity(mContext, 0 /* requestCode */, 389 getIntent(), PendingIntent.FLAG_IMMUTABLE /* flags */); 390 return SliceAction.createDeeplink(intent, icon, ListBuilder.ICON_IMAGE, title); 391 } 392 393 @Override getWifiSliceItemLevelIcon(WifiSliceItem wifiSliceItem)394 protected IconCompat getWifiSliceItemLevelIcon(WifiSliceItem wifiSliceItem) { 395 if (wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED 396 && getInternetType() != InternetUpdater.INTERNET_WIFI) { 397 final @ColorInt int tint = Utils.getColorAttrDefaultColor(mContext, 398 android.R.attr.colorControlNormal); 399 final Drawable drawable = mContext.getDrawable( 400 WifiUtils.getInternetIconResource( 401 wifiSliceItem.getLevel(), wifiSliceItem.shouldShowXLevelIcon())); 402 drawable.setTint(tint); 403 return Utils.createIconWithDrawable(drawable); 404 } 405 return super.getWifiSliceItemLevelIcon(wifiSliceItem); 406 } 407 408 /** 409 * Wrap the subscriptionManager call for test mocking. 410 */ 411 @VisibleForTesting defaultSubscriptionIsUsable(int defaultSubId)412 protected boolean defaultSubscriptionIsUsable(int defaultSubId) { 413 return SubscriptionManager.isUsableSubscriptionId(defaultSubId); 414 } 415 } 416