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