1 /* 2 * Copyright (C) 2019 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.telephony; 18 19 import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; 20 21 import android.annotation.ColorInt; 22 import android.app.PendingIntent; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.database.ContentObserver; 27 import android.net.Uri; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.telephony.SubscriptionInfo; 31 import android.telephony.SubscriptionManager; 32 import android.telephony.TelephonyManager; 33 34 import androidx.core.graphics.drawable.IconCompat; 35 import androidx.slice.Slice; 36 import androidx.slice.builders.ListBuilder; 37 import androidx.slice.builders.SliceAction; 38 39 import com.android.settings.R; 40 import com.android.settings.Utils; 41 import com.android.settings.network.MobileDataContentObserver; 42 import com.android.settings.network.SubscriptionUtil; 43 import com.android.settings.slices.CustomSliceRegistry; 44 import com.android.settings.slices.CustomSliceable; 45 import com.android.settings.slices.SliceBackgroundWorker; 46 import com.android.settingslib.WirelessUtils; 47 48 import com.google.common.annotations.VisibleForTesting; 49 50 import java.io.IOException; 51 import java.util.List; 52 53 /** 54 * Custom {@link Slice} for Mobile Data. 55 * <p> 56 * We make a custom slice instead of using {@link MobileDataPreferenceController} because the 57 * pref controller is generalized across any carrier, and thus does not control a specific 58 * subscription. We attempt to reuse any telephony-specific code from the preference controller. 59 * 60 * </p> 61 * 62 */ 63 public class MobileDataSlice implements CustomSliceable { 64 65 private final Context mContext; 66 private final SubscriptionManager mSubscriptionManager; 67 private final TelephonyManager mTelephonyManager; 68 MobileDataSlice(Context context)69 public MobileDataSlice(Context context) { 70 mContext = context; 71 mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); 72 mTelephonyManager = mContext.getSystemService(TelephonyManager.class); 73 } 74 75 @Override getSlice()76 public Slice getSlice() { 77 final IconCompat icon = IconCompat.createWithResource(mContext, 78 R.drawable.ic_network_cell); 79 final String title = mContext.getText(R.string.mobile_data_settings_title).toString(); 80 @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext); 81 82 // Return null until we can show a disabled-action Slice, blaming Airplane mode. 83 if (isAirplaneModeEnabled()) { 84 return null; 85 } 86 87 // Return null until we can show a disabled-action Slice. 88 if (!isMobileDataAvailable()) { 89 return null; 90 } 91 92 final CharSequence summary = getSummary(); 93 final PendingIntent toggleAction = getBroadcastIntent(mContext); 94 final PendingIntent primaryAction = getPrimaryAction(); 95 final SliceAction primarySliceAction = SliceAction.createDeeplink(primaryAction, icon, 96 ListBuilder.ICON_IMAGE, title); 97 final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction, 98 null /* actionTitle */, isMobileDataEnabled()); 99 final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder() 100 .setTitle(title) 101 .addEndItem(toggleSliceAction) 102 .setPrimaryAction(primarySliceAction); 103 if (!Utils.isSettingsIntelligence(mContext)) { 104 rowBuilder.setSubtitle(summary); 105 } 106 107 final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), 108 ListBuilder.INFINITY) 109 .setAccentColor(color) 110 .addRow(rowBuilder); 111 return listBuilder.build(); 112 } 113 114 @Override getUri()115 public Uri getUri() { 116 return CustomSliceRegistry.MOBILE_DATA_SLICE_URI; 117 } 118 119 @Override onNotifyChange(Intent intent)120 public void onNotifyChange(Intent intent) { 121 final boolean newState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE, 122 isMobileDataEnabled()); 123 124 final int defaultSubId = getDefaultSubscriptionId(mSubscriptionManager); 125 if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 126 return; // No subscription - do nothing. 127 } 128 129 MobileNetworkUtils.setMobileDataEnabled(mContext, defaultSubId, newState, 130 false /* disableOtherSubscriptions */); 131 // Do not notifyChange on Uri. The service takes longer to update the current value than it 132 // does for the Slice to check the current value again. Let {@link WifiScanWorker} 133 // handle it. 134 } 135 136 @Override getIntentFilter()137 public IntentFilter getIntentFilter() { 138 final IntentFilter filter = new IntentFilter(); 139 filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); 140 return filter; 141 } 142 143 @Override getIntent()144 public Intent getIntent() { 145 return new Intent(mContext, MobileNetworkActivity.class); 146 } 147 148 @Override getSliceHighlightMenuRes()149 public int getSliceHighlightMenuRes() { 150 return R.string.menu_key_network; 151 } 152 153 @Override getBackgroundWorkerClass()154 public Class<? extends SliceBackgroundWorker> getBackgroundWorkerClass() { 155 return MobileDataWorker.class; 156 } 157 getDefaultSubscriptionId(SubscriptionManager subscriptionManager)158 protected static int getDefaultSubscriptionId(SubscriptionManager subscriptionManager) { 159 final SubscriptionInfo defaultSubscription = subscriptionManager.getActiveSubscriptionInfo( 160 subscriptionManager.getDefaultDataSubscriptionId()); 161 if (defaultSubscription == null) { 162 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; // No default subscription 163 } 164 165 return defaultSubscription.getSubscriptionId(); 166 } 167 getSummary()168 private CharSequence getSummary() { 169 final SubscriptionInfo defaultSubscription = mSubscriptionManager.getActiveSubscriptionInfo( 170 mSubscriptionManager.getDefaultDataSubscriptionId()); 171 if (defaultSubscription == null) { 172 return null; // no summary text 173 } 174 175 return SubscriptionUtil.getUniqueSubscriptionDisplayName(defaultSubscription, mContext); 176 } 177 getPrimaryAction()178 private PendingIntent getPrimaryAction() { 179 final Intent intent = getIntent(); 180 return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 181 PendingIntent.FLAG_IMMUTABLE); 182 } 183 184 /** 185 * @return {@code true} when mobile data is not supported by the current device. 186 */ isMobileDataAvailable()187 private boolean isMobileDataAvailable() { 188 final List<SubscriptionInfo> subInfoList = 189 SubscriptionUtil.getSelectableSubscriptionInfoList(mContext); 190 191 return !(subInfoList == null || subInfoList.isEmpty()); 192 } 193 194 @VisibleForTesting isAirplaneModeEnabled()195 boolean isAirplaneModeEnabled() { 196 return WirelessUtils.isAirplaneModeOn(mContext); 197 } 198 199 @VisibleForTesting isMobileDataEnabled()200 boolean isMobileDataEnabled() { 201 if (mTelephonyManager == null) { 202 return false; 203 } 204 205 return mTelephonyManager.isDataEnabled(); 206 } 207 208 /** 209 * Listener for mobile data state changes. 210 * 211 * <p> 212 * Listen to individual subscription changes since there is no framework broadcast. 213 * 214 * This worker registers a ContentObserver in the background and updates the MobileData 215 * Slice when the value changes. 216 */ 217 public static class MobileDataWorker extends SliceBackgroundWorker<Void> { 218 219 DataContentObserver mMobileDataObserver; 220 MobileDataWorker(Context context, Uri uri)221 public MobileDataWorker(Context context, Uri uri) { 222 super(context, uri); 223 final Handler handler = new Handler(Looper.getMainLooper()); 224 mMobileDataObserver = new DataContentObserver(handler, this); 225 } 226 227 @Override onSlicePinned()228 protected void onSlicePinned() { 229 final SubscriptionManager subscriptionManager = 230 getContext().getSystemService(SubscriptionManager.class); 231 mMobileDataObserver.register(getContext(), 232 getDefaultSubscriptionId(subscriptionManager)); 233 } 234 235 @Override onSliceUnpinned()236 protected void onSliceUnpinned() { 237 mMobileDataObserver.unRegister(getContext()); 238 } 239 240 @Override close()241 public void close() throws IOException { 242 mMobileDataObserver = null; 243 } 244 updateSlice()245 public void updateSlice() { 246 notifySliceChange(); 247 } 248 249 public class DataContentObserver extends ContentObserver { 250 251 private final MobileDataWorker mSliceBackgroundWorker; 252 DataContentObserver(Handler handler, MobileDataWorker backgroundWorker)253 public DataContentObserver(Handler handler, MobileDataWorker backgroundWorker) { 254 super(handler); 255 mSliceBackgroundWorker = backgroundWorker; 256 } 257 258 @Override onChange(boolean selfChange)259 public void onChange(boolean selfChange) { 260 mSliceBackgroundWorker.updateSlice(); 261 } 262 register(Context context, int subId)263 public void register(Context context, int subId) { 264 final Uri uri = MobileDataContentObserver.getObservableUri(context, subId); 265 context.getContentResolver().registerContentObserver(uri, false, this); 266 } 267 unRegister(Context context)268 public void unRegister(Context context) { 269 context.getContentResolver().unregisterContentObserver(this); 270 } 271 } 272 } 273 } 274