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 package com.android.settings.network;
17 
18 import static com.android.settings.network.MobileIconGroupExtKt.maybeToHtml;
19 import static com.android.settings.network.telephony.MobileNetworkUtils.NO_CELL_DATA_TYPE_ICON;
20 
21 import android.app.PendingIntent;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.graphics.Color;
25 import android.graphics.drawable.ColorDrawable;
26 import android.graphics.drawable.Drawable;
27 import android.net.Uri;
28 import android.telephony.AccessNetworkConstants;
29 import android.telephony.NetworkRegistrationInfo;
30 import android.telephony.ServiceState;
31 import android.telephony.SignalStrength;
32 import android.telephony.SubscriptionInfo;
33 import android.telephony.SubscriptionManager;
34 import android.telephony.TelephonyManager;
35 import android.text.TextUtils;
36 import android.util.Log;
37 
38 import androidx.annotation.Nullable;
39 import androidx.annotation.VisibleForTesting;
40 import androidx.core.graphics.drawable.IconCompat;
41 import androidx.slice.builders.ListBuilder;
42 import androidx.slice.builders.SliceAction;
43 
44 import com.android.settings.R;
45 import com.android.settings.Utils;
46 import com.android.settings.network.telephony.MobileNetworkUtils;
47 import com.android.settings.slices.CustomSliceable;
48 import com.android.settings.wifi.slice.WifiSliceItem;
49 import com.android.settingslib.WirelessUtils;
50 import com.android.settingslib.net.SignalStrengthUtil;
51 import com.android.settingslib.utils.ThreadUtils;
52 import com.android.wifitrackerlib.WifiEntry;
53 
54 import java.util.Arrays;
55 import java.util.List;
56 import java.util.Optional;
57 import java.util.Set;
58 import java.util.concurrent.Semaphore;
59 import java.util.concurrent.atomic.AtomicReference;
60 import java.util.stream.Collectors;
61 
62 /**
63  * The helper is for slice of carrier and non-Carrier, used by ProviderModelSlice.
64  * TODO: Remove the class in U because Settings does not use slice anymore.
65  */
66 public class ProviderModelSliceHelper {
67     private static final String TAG = "ProviderModelSlice";
68     private final SubscriptionManager mSubscriptionManager;
69     private TelephonyManager mTelephonyManager;
70     protected final Context mContext;
71     private CustomSliceable mSliceable;
72 
ProviderModelSliceHelper(Context context, CustomSliceable sliceable)73     public ProviderModelSliceHelper(Context context, CustomSliceable sliceable) {
74         mContext = context;
75         mSliceable = sliceable;
76         mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
77         mTelephonyManager = context.getSystemService(TelephonyManager.class);
78     }
79 
80     /**
81      * @return whether there is the carrier item in the slice.
82      */
hasCarrier()83     public boolean hasCarrier() {
84         if (isAirplaneModeEnabled()
85                 || mSubscriptionManager == null || mTelephonyManager == null
86                 || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) {
87             return false;
88         }
89         return true;
90     }
91 
92     /**
93      * @return whether the MobileData's is enabled.
94      */
isMobileDataEnabled()95     public boolean isMobileDataEnabled() {
96         return mTelephonyManager.isDataEnabled();
97     }
98 
99     /**
100      * To check the carrier data status.
101      *
102      * @return whether the carrier data is active.
103      */
isDataSimActive()104     public boolean isDataSimActive() {
105         return MobileNetworkUtils.activeNetworkIsCellular(mContext);
106     }
107 
108     /**
109      * @return whether the ServiceState's data state is in-service.
110      */
isDataStateInService()111     public boolean isDataStateInService() {
112         final ServiceState serviceState = mTelephonyManager.getServiceState();
113         NetworkRegistrationInfo regInfo =
114                 (serviceState == null) ? null : serviceState.getNetworkRegistrationInfo(
115                         NetworkRegistrationInfo.DOMAIN_PS,
116                         AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
117         return (regInfo == null) ? false : regInfo.isRegistered();
118     }
119 
120     /**
121      * @return whether the ServiceState's voice state is in-service.
122      */
isVoiceStateInService()123     public boolean isVoiceStateInService() {
124         final ServiceState serviceState = mTelephonyManager.getServiceState();
125         return serviceState != null
126                 && serviceState.getState() == serviceState.STATE_IN_SERVICE;
127     }
128 
129     /**
130      * To get the signal bar icon with level.
131      *
132      * @return The Drawable which is a signal bar icon with level.
133      */
getDrawableWithSignalStrength()134     public Drawable getDrawableWithSignalStrength() {
135         final SignalStrength strength = mTelephonyManager.getSignalStrength();
136         int level = (strength == null) ? 0 : strength.getLevel();
137         int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
138         if (mSubscriptionManager != null && shouldInflateSignalStrength(
139                 mSubscriptionManager.getDefaultDataSubscriptionId())) {
140             level += 1;
141             numLevels += 1;
142         }
143         return MobileNetworkUtils.getSignalStrengthIcon(mContext, level, numLevels,
144                 NO_CELL_DATA_TYPE_ICON, false, false);
145     }
146 
147     /**
148      * To update the telephony with subid.
149      */
updateTelephony()150     public void updateTelephony() {
151         if (mSubscriptionManager == null || mSubscriptionManager.getDefaultDataSubscriptionId()
152                 == mSubscriptionManager.INVALID_SUBSCRIPTION_ID) {
153             return;
154         }
155         mTelephonyManager = mTelephonyManager.createForSubscriptionId(
156                 mSubscriptionManager.getDefaultDataSubscriptionId());
157     }
158 
createListBuilder(Uri uri)159     protected ListBuilder createListBuilder(Uri uri) {
160         final ListBuilder builder = new ListBuilder(mContext, uri, ListBuilder.INFINITY)
161                 .setAccentColor(-1)
162                 .setKeywords(getKeywords());
163         return builder;
164     }
165 
166     @Nullable
getConnectedWifiItem(List<WifiSliceItem> wifiList)167     protected WifiSliceItem getConnectedWifiItem(List<WifiSliceItem> wifiList) {
168         if (wifiList == null) {
169             return null;
170         }
171         Optional<WifiSliceItem> item = wifiList.stream()
172                 .filter(x -> x.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED)
173                 .findFirst();
174         return item.isPresent() ? item.get() : null;
175     }
176 
createCarrierRow(String networkTypeDescription)177     protected ListBuilder.RowBuilder createCarrierRow(String networkTypeDescription) {
178         final String title = getMobileTitle();
179         final CharSequence summary = getMobileSummary(networkTypeDescription);
180         Drawable drawable = mContext.getDrawable(
181                 R.drawable.ic_signal_strength_zero_bar_no_internet);
182         try {
183             drawable = getMobileDrawable(drawable);
184         } catch (Throwable e) {
185             e.printStackTrace();
186         }
187         final IconCompat levelIcon = Utils.createIconWithDrawable(drawable);
188         final PendingIntent rowIntent = mSliceable.getBroadcastIntent(mContext);
189         final SliceAction primaryAction = SliceAction.create(rowIntent,
190                 levelIcon, ListBuilder.ICON_IMAGE, title);
191         final SliceAction toggleAction = SliceAction.createToggle(rowIntent,
192                 "mobile_toggle" /* actionTitle */, isMobileDataEnabled());
193         final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
194                 .setTitle(title)
195                 .setTitleItem(levelIcon, ListBuilder.ICON_IMAGE)
196                 .addEndItem(toggleAction)
197                 .setPrimaryAction(primaryAction)
198                 .setSubtitle(summary);
199         return rowBuilder;
200     }
201 
getPrimarySliceAction(String intentAction)202     protected SliceAction getPrimarySliceAction(String intentAction) {
203         return SliceAction.createDeeplink(
204                 getPrimaryAction(intentAction),
205                 Utils.createIconWithDrawable(new ColorDrawable(Color.TRANSPARENT)),
206                 ListBuilder.ICON_IMAGE, mContext.getText(R.string.summary_placeholder));
207     }
208 
isAirplaneModeEnabled()209     protected boolean isAirplaneModeEnabled() {
210         return WirelessUtils.isAirplaneModeOn(mContext);
211     }
212 
getSubscriptionManager()213     protected SubscriptionManager getSubscriptionManager() {
214         return mSubscriptionManager;
215     }
216 
log(String s)217     private static void log(String s) {
218         Log.d(TAG, s);
219     }
220 
getPrimaryAction(String intentAction)221     private PendingIntent getPrimaryAction(String intentAction) {
222         final Intent intent = new Intent(intentAction)
223                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
224         return PendingIntent.getActivity(mContext, 0 /* requestCode */,
225                 intent, PendingIntent.FLAG_IMMUTABLE /* flags */);
226     }
227 
shouldInflateSignalStrength(int subId)228     private boolean shouldInflateSignalStrength(int subId) {
229         return SignalStrengthUtil.shouldInflateSignalStrength(mContext, subId);
230     }
231 
232     @VisibleForTesting
getMobileDrawable(Drawable drawable)233     Drawable getMobileDrawable(Drawable drawable) throws Throwable {
234         // set color and drawable
235         if (mTelephonyManager == null) {
236             log("mTelephonyManager == null");
237             return drawable;
238         }
239         if (isDataStateInService() || isVoiceStateInService()) {
240             Semaphore lock = new Semaphore(0);
241             AtomicReference<Drawable> shared = new AtomicReference<>();
242             ThreadUtils.postOnMainThread(() -> {
243                 shared.set(getDrawableWithSignalStrength());
244                 lock.release();
245             });
246             lock.acquire();
247             drawable = shared.get();
248         }
249 
250         drawable.setTint(
251                 Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal));
252         if (isDataSimActive()) {
253             drawable.setTint(Utils.getColorAccentDefaultColor(mContext));
254         }
255         return drawable;
256     }
257 
getMobileSummary(String networkTypeDescription)258     private CharSequence getMobileSummary(String networkTypeDescription) {
259         if (!isMobileDataEnabled()) {
260             return mContext.getString(R.string.mobile_data_off_summary);
261         }
262         if (!isDataStateInService()) {
263             return mContext.getString(R.string.mobile_data_no_connection);
264         }
265         String summary = networkTypeDescription;
266         if (isDataSimActive()) {
267             summary = mContext.getString(R.string.preference_summary_default_combination,
268                     mContext.getString(R.string.mobile_data_connection_active),
269                     networkTypeDescription);
270         }
271         return maybeToHtml(summary);
272     }
273 
getMobileTitle()274     protected String getMobileTitle() {
275         String title = mContext.getText(R.string.mobile_data_settings_title).toString();
276         if (mSubscriptionManager == null) {
277             return title;
278         }
279         final SubscriptionInfo defaultSubscription = mSubscriptionManager.getActiveSubscriptionInfo(
280                 mSubscriptionManager.getDefaultDataSubscriptionId());
281         if (defaultSubscription != null) {
282             title = SubscriptionUtil.getUniqueSubscriptionDisplayName(
283                     defaultSubscription, mContext).toString();
284         }
285         return title;
286     }
287 
getKeywords()288     private Set<String> getKeywords() {
289         final String keywords = mContext.getString(R.string.keywords_internet);
290         return Arrays.stream(TextUtils.split(keywords, ","))
291                 .map(String::trim)
292                 .collect(Collectors.toSet());
293     }
294 }
295