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