| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.network; |
| |
| import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; |
| |
| import static com.android.settings.slices.CustomSliceRegistry.PROVIDER_MODEL_SLICE_URI; |
| import static com.android.settings.slices.CustomSliceRegistry.WIFI_SLICE_URI; |
| |
| import android.annotation.ColorInt; |
| import android.app.AlertDialog; |
| import android.app.AlertDialog.Builder; |
| import android.app.PendingIntent; |
| import android.app.settings.SettingsEnums; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.graphics.Color; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.telephony.SubscriptionManager; |
| import android.util.Log; |
| import android.view.WindowManager.LayoutParams; |
| |
| import androidx.annotation.VisibleForTesting; |
| import androidx.core.graphics.drawable.IconCompat; |
| import androidx.slice.Slice; |
| import androidx.slice.builders.ListBuilder; |
| import androidx.slice.builders.SliceAction; |
| |
| import com.android.settings.R; |
| import com.android.settings.SubSettings; |
| import com.android.settings.Utils; |
| import com.android.settings.network.telephony.MobileNetworkUtils; |
| import com.android.settings.network.telephony.NetworkProviderWorker; |
| import com.android.settings.slices.CustomSliceable; |
| import com.android.settings.slices.SliceBackgroundWorker; |
| import com.android.settings.slices.SliceBroadcastReceiver; |
| import com.android.settings.slices.SliceBuilderUtils; |
| import com.android.settings.wifi.WifiUtils; |
| import com.android.settings.wifi.slice.WifiSlice; |
| import com.android.settings.wifi.slice.WifiSliceItem; |
| import com.android.wifitrackerlib.WifiEntry; |
| |
| import java.util.List; |
| import java.util.stream.Collectors; |
| |
| /** |
| * {@link CustomSliceable} for Wi-Fi and mobile data connection, used by generic clients. |
| */ |
| // ToDo If the provider model become default design in the future, the code needs to refactor |
| // the whole structure and use new "data object", and then split provider model out of old design. |
| public class ProviderModelSlice extends WifiSlice { |
| |
| private static final String TAG = "ProviderModelSlice"; |
| protected static final String PREF_NAME = "ProviderModelSlice"; |
| protected static final String PREF_HAS_TURNED_OFF_MOBILE_DATA = "PrefHasTurnedOffMobileData"; |
| |
| private final ProviderModelSliceHelper mHelper; |
| private final SharedPreferences mSharedPref; |
| |
| public ProviderModelSlice(Context context) { |
| super(context); |
| mHelper = getHelper(); |
| mSharedPref = getSharedPreference(); |
| } |
| |
| @Override |
| public Uri getUri() { |
| return PROVIDER_MODEL_SLICE_URI; |
| } |
| |
| private static void log(String s) { |
| Log.d(TAG, s); |
| } |
| |
| protected boolean isApRowCollapsed() { |
| return false; |
| } |
| |
| @Override |
| public Slice getSlice() { |
| // The provider model slice step: |
| // First section: Add the Ethernet item. |
| // Second section: Add the carrier item. |
| // Third section: Add the Wi-Fi toggle item. |
| // Fourth section: Add the connected Wi-Fi item. |
| // Fifth section: Add the Wi-Fi items which are not connected. |
| // Sixth section: Add the See All item. |
| final ListBuilder listBuilder = mHelper.createListBuilder(getUri()); |
| int maxListSize = 0; |
| final NetworkProviderWorker worker = getWorker(); |
| if (worker != null) { |
| maxListSize = worker.getApRowCount(); |
| } else { |
| log("network provider worker is null."); |
| } |
| |
| // First section: Add the Ethernet item. |
| if (getInternetType() == InternetUpdater.INTERNET_ETHERNET) { |
| log("get Ethernet item which is connected"); |
| listBuilder.addRow(createEthernetRow()); |
| maxListSize--; |
| } |
| |
| // Second section: Add the carrier item. |
| if (!mHelper.isAirplaneModeEnabled()) { |
| final boolean hasCarrier = mHelper.hasCarrier(); |
| log("hasCarrier: " + hasCarrier); |
| if (hasCarrier) { |
| mHelper.updateTelephony(); |
| listBuilder.addRow( |
| mHelper.createCarrierRow( |
| worker != null ? worker.getNetworkTypeDescription() : "")); |
| maxListSize--; |
| } |
| } |
| |
| // Third section: Add the Wi-Fi toggle item. |
| final boolean isWifiEnabled = mWifiManager.isWifiEnabled(); |
| listBuilder.addRow(createWifiToggleRow(mContext, isWifiEnabled)); |
| maxListSize--; |
| if (!isWifiEnabled) { |
| log("Wi-Fi is disabled"); |
| return listBuilder.build(); |
| } |
| List<WifiSliceItem> wifiList = (worker != null) ? worker.getResults() : null; |
| if (wifiList == null || wifiList.size() <= 0) { |
| log("Wi-Fi list is empty"); |
| return listBuilder.build(); |
| } |
| |
| // Fourth section: Add the connected Wi-Fi item. |
| final WifiSliceItem connectedWifiItem = mHelper.getConnectedWifiItem(wifiList); |
| if (connectedWifiItem != null) { |
| log("get Wi-Fi item which is connected"); |
| listBuilder.addRow(getWifiSliceItemRow(connectedWifiItem)); |
| maxListSize--; |
| } |
| |
| // Fifth section: Add the Wi-Fi items which are not connected. |
| log("get Wi-Fi items which are not connected. Wi-Fi items : " + wifiList.size()); |
| final List<WifiSliceItem> disconnectedWifiList = wifiList.stream() |
| .filter(item -> item.getConnectedState() != WifiEntry.CONNECTED_STATE_CONNECTED) |
| .limit(maxListSize - 1) |
| .collect(Collectors.toList()); |
| for (WifiSliceItem item : disconnectedWifiList) { |
| listBuilder.addRow(getWifiSliceItemRow(item)); |
| } |
| |
| // Sixth section: Add the See All item. |
| log("add See-All"); |
| listBuilder.addRow(getSeeAllRow()); |
| |
| return listBuilder.build(); |
| } |
| |
| @Override |
| public PendingIntent getBroadcastIntent(Context context) { |
| final Intent intent = new Intent(getUri().toString()) |
| // The FLAG_RECEIVER_FOREGROUND flag is necessary to avoid the intent delay of |
| // the first sending after the device restarts |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) |
| .setData(getUri()) |
| .setClass(context, SliceBroadcastReceiver.class); |
| return PendingIntent.getBroadcast(context, 0 /* requestCode */, intent, |
| PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); |
| } |
| |
| /** |
| * Update the current carrier's mobile data status. |
| */ |
| @Override |
| public void onNotifyChange(Intent intent) { |
| final SubscriptionManager subscriptionManager = mHelper.getSubscriptionManager(); |
| if (subscriptionManager == null) { |
| return; |
| } |
| final int defaultSubId = subscriptionManager.getDefaultDataSubscriptionId(); |
| log("defaultSubId:" + defaultSubId); |
| |
| if (!defaultSubscriptionIsUsable(defaultSubId)) { |
| return; |
| } |
| |
| boolean isToggleAction = intent.hasExtra(EXTRA_TOGGLE_STATE); |
| boolean newState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE, |
| mHelper.isMobileDataEnabled()); |
| |
| if (isToggleAction) { |
| // The ToggleAction is used to set mobile data enabled. |
| if (!newState && mSharedPref != null |
| && mSharedPref.getBoolean(PREF_HAS_TURNED_OFF_MOBILE_DATA, true)) { |
| String carrierName = mHelper.getMobileTitle(); |
| if (carrierName.equals(mContext.getString(R.string.mobile_data_settings_title))) { |
| carrierName = mContext.getString( |
| R.string.mobile_data_disable_message_default_carrier); |
| } |
| showMobileDataDisableDialog(getMobileDataDisableDialog(defaultSubId, carrierName)); |
| // If we need to display a reminder dialog box, do nothing here. |
| return; |
| } else { |
| MobileNetworkUtils.setMobileDataEnabled(mContext, defaultSubId, newState, |
| false /* disableOtherSubscriptions */); |
| } |
| } |
| |
| final boolean isDataEnabled = |
| isToggleAction ? newState : MobileNetworkUtils.isMobileDataEnabled(mContext); |
| doCarrierNetworkAction(isToggleAction, isDataEnabled, defaultSubId); |
| } |
| |
| @VisibleForTesting |
| AlertDialog getMobileDataDisableDialog(int defaultSubId, String carrierName) { |
| return new Builder(mContext) |
| .setTitle(R.string.mobile_data_disable_title) |
| .setMessage(mContext.getString(R.string.mobile_data_disable_message, |
| carrierName)) |
| .setNegativeButton(android.R.string.cancel, |
| (dialog, which) -> { |
| // Because the toggle of mobile data will be turned off first, if the |
| // user cancels the operation, we need to update the slice to correct |
| // the toggle state. |
| final NetworkProviderWorker worker = getWorker(); |
| if (worker != null) { |
| worker.updateSlice(); |
| } |
| }) |
| .setPositiveButton( |
| com.android.internal.R.string.alert_windows_notification_turn_off_action, |
| (dialog, which) -> { |
| MobileNetworkUtils.setMobileDataEnabled(mContext, defaultSubId, |
| false /* enabled */, |
| false /* disableOtherSubscriptions */); |
| doCarrierNetworkAction(true /* isToggleAction */, |
| false /* isDataEanbed */, defaultSubId); |
| if (mSharedPref != null) { |
| SharedPreferences.Editor editor = mSharedPref.edit(); |
| editor.putBoolean(PREF_HAS_TURNED_OFF_MOBILE_DATA, false); |
| editor.apply(); |
| } |
| }) |
| .create(); |
| } |
| |
| private void showMobileDataDisableDialog(AlertDialog dialog) { |
| if (dialog == null) { |
| log("AlertDialog is null"); |
| return; |
| } |
| |
| dialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG); |
| dialog.show(); |
| } |
| |
| @VisibleForTesting |
| void doCarrierNetworkAction(boolean isToggleAction, boolean isDataEnabled, int subId) { |
| final NetworkProviderWorker worker = getWorker(); |
| if (worker == null) { |
| return; |
| } |
| |
| if (isToggleAction) { |
| worker.setCarrierNetworkEnabledIfNeeded(isDataEnabled, subId); |
| return; |
| } |
| |
| if (isDataEnabled) { |
| worker.connectCarrierNetwork(); |
| } |
| } |
| |
| @Override |
| public Intent getIntent() { |
| final String screenTitle = mContext.getText(R.string.provider_internet_settings).toString(); |
| return SliceBuilderUtils.buildSearchResultPageIntent(mContext, |
| NetworkProviderSettings.class.getName(), "" /* key */, screenTitle, |
| SettingsEnums.SLICE) |
| .setClassName(mContext.getPackageName(), SubSettings.class.getName()) |
| .setData(getUri()); |
| } |
| |
| @Override |
| public Class getBackgroundWorkerClass() { |
| return NetworkProviderWorker.class; |
| } |
| |
| @VisibleForTesting |
| ProviderModelSliceHelper getHelper() { |
| return new ProviderModelSliceHelper(mContext, this); |
| } |
| |
| @VisibleForTesting |
| NetworkProviderWorker getWorker() { |
| return SliceBackgroundWorker.getInstance(getUri()); |
| } |
| |
| @VisibleForTesting |
| SharedPreferences getSharedPreference() { |
| return mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); |
| } |
| |
| private @InternetUpdater.InternetType int getInternetType() { |
| final NetworkProviderWorker worker = getWorker(); |
| if (worker == null) { |
| return InternetUpdater.INTERNET_NETWORKS_AVAILABLE; |
| } |
| return worker.getInternetType(); |
| } |
| |
| @VisibleForTesting |
| ListBuilder.RowBuilder createEthernetRow() { |
| final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder(); |
| final Drawable drawable = mContext.getDrawable(R.drawable.ic_settings_ethernet); |
| if (drawable != null) { |
| drawable.setTintList(Utils.getColorAttr(mContext, android.R.attr.colorAccent)); |
| rowBuilder.setTitleItem(Utils.createIconWithDrawable(drawable), ListBuilder.ICON_IMAGE); |
| } |
| return rowBuilder |
| .setTitle(mContext.getText(R.string.ethernet)) |
| .setSubtitle(mContext.getText(R.string.to_switch_networks_disconnect_ethernet)); |
| } |
| |
| /** |
| * @return a {@link ListBuilder.RowBuilder} of the Wi-Fi toggle. |
| */ |
| protected ListBuilder.RowBuilder createWifiToggleRow(Context context, boolean isWifiEnabled) { |
| final Intent intent = new Intent(WIFI_SLICE_URI.toString()) |
| .setData(WIFI_SLICE_URI) |
| .setClass(context, SliceBroadcastReceiver.class) |
| .putExtra(EXTRA_TOGGLE_STATE, !isWifiEnabled) |
| // The FLAG_RECEIVER_FOREGROUND flag is necessary to avoid the intent delay of |
| // the first sending after the device restarts |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, |
| PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); |
| final SliceAction toggleSliceAction = SliceAction.createToggle(pendingIntent, |
| null /* actionTitle */, isWifiEnabled); |
| return new ListBuilder.RowBuilder() |
| .setTitle(context.getString(R.string.wifi_settings)) |
| .setPrimaryAction(toggleSliceAction); |
| } |
| |
| protected ListBuilder.RowBuilder getSeeAllRow() { |
| final CharSequence title = mContext.getText(R.string.previous_connected_see_all); |
| final IconCompat icon = getSeeAllIcon(); |
| return new ListBuilder.RowBuilder() |
| .setTitleItem(icon, ListBuilder.ICON_IMAGE) |
| .setTitle(title) |
| .setPrimaryAction(getPrimaryAction(icon, title)); |
| } |
| |
| protected IconCompat getSeeAllIcon() { |
| final Drawable drawable = mContext.getDrawable(R.drawable.ic_arrow_forward); |
| if (drawable != null) { |
| drawable.setTint( |
| Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal)); |
| return Utils.createIconWithDrawable(drawable); |
| } |
| return Utils.createIconWithDrawable(new ColorDrawable(Color.TRANSPARENT)); |
| } |
| |
| protected SliceAction getPrimaryAction(IconCompat icon, CharSequence title) { |
| final PendingIntent intent = PendingIntent.getActivity(mContext, 0 /* requestCode */, |
| getIntent(), PendingIntent.FLAG_IMMUTABLE /* flags */); |
| return SliceAction.createDeeplink(intent, icon, ListBuilder.ICON_IMAGE, title); |
| } |
| |
| @Override |
| protected IconCompat getWifiSliceItemLevelIcon(WifiSliceItem wifiSliceItem) { |
| if (wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED |
| && getInternetType() != InternetUpdater.INTERNET_WIFI) { |
| final @ColorInt int tint = Utils.getColorAttrDefaultColor(mContext, |
| android.R.attr.colorControlNormal); |
| final Drawable drawable = mContext.getDrawable( |
| WifiUtils.getInternetIconResource( |
| wifiSliceItem.getLevel(), wifiSliceItem.shouldShowXLevelIcon())); |
| drawable.setTint(tint); |
| return Utils.createIconWithDrawable(drawable); |
| } |
| return super.getWifiSliceItemLevelIcon(wifiSliceItem); |
| } |
| |
| /** |
| * Wrap the subscriptionManager call for test mocking. |
| */ |
| @VisibleForTesting |
| protected boolean defaultSubscriptionIsUsable(int defaultSubId) { |
| return SubscriptionManager.isUsableSubscriptionId(defaultSubId); |
| } |
| } |