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