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.car.settings.datausage;
18 
19 import android.car.drivingstate.CarUxRestrictions;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.net.INetworkPolicyManager;
24 import android.net.NetworkTemplate;
25 import android.os.ServiceManager;
26 import android.telephony.SubscriptionInfo;
27 import android.telephony.SubscriptionManager;
28 import android.telephony.SubscriptionPlan;
29 import android.telephony.TelephonyManager;
30 import android.text.Spannable;
31 import android.text.SpannableString;
32 import android.text.TextUtils;
33 import android.text.format.Formatter;
34 import android.text.style.AbsoluteSizeSpan;
35 import android.util.RecurrenceRule;
36 
37 import androidx.annotation.Nullable;
38 import androidx.annotation.VisibleForTesting;
39 
40 import com.android.car.settings.R;
41 import com.android.car.settings.common.FragmentController;
42 import com.android.car.settings.common.Logger;
43 import com.android.car.settings.common.PreferenceController;
44 import com.android.car.settings.network.NetworkUtils;
45 import com.android.settingslib.net.DataUsageController;
46 import com.android.settingslib.utils.StringUtil;
47 
48 import java.util.List;
49 import java.util.concurrent.TimeUnit;
50 
51 /**
52  * Business logic for setting the {@link DataUsageSummaryPreference} with the current data usage and
53  * the appropriate summary text.
54  */
55 public class DataUsageSummaryPreferenceController extends
56         PreferenceController<DataUsageSummaryPreference> {
57     private static final Logger LOG = new Logger(DataUsageSummaryPreferenceController.class);
58 
59     private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1);
60     private static final long MILLIS_IN_AN_HOUR = TimeUnit.HOURS.toMillis(1);
61     private static final long MILLIS_IN_A_MINUTE = TimeUnit.MINUTES.toMillis(1);
62     private static final long MILLIS_IN_A_SECOND = TimeUnit.SECONDS.toMillis(1);
63     private static final int MAX_PROGRESS_BAR_VALUE = 1000;
64 
65     @VisibleForTesting
66     static final long WARNING_AGE = TimeUnit.HOURS.toMillis(6L);
67 
68     private final SubscriptionManager mSubscriptionManager;
69     private final TelephonyManager mTelephonyManager;
70     private final DataUsageController mDataUsageController;
71     private final NetworkTemplate mDefaultTemplate;
72 
73     /** Name of the carrier, or null if not available */
74     @Nullable
75     private CharSequence mCarrierName;
76     /** The number of registered plans, [0,N] */
77     private int mDataplanCount;
78     /** The time of the last update in milliseconds since the epoch, or -1 if unknown */
79     private long mSnapshotTime;
80     /** The size of the first registered plan if one exists. -1 if no information is available. */
81     private long mDataplanSize = -1;
82     /**
83      * Limit to track. Size of the first registered plan if one exists. Otherwise size of data limit
84      * or warning.
85      */
86     private long mDataplanTrackingThreshold;
87     /** The number of bytes used since the start of the cycle. */
88     private long mDataplanUse;
89     /** The ending time of the billing cycle in ms since the epoch */
90     private long mCycleEnd;
91     private Intent mManageSubscriptionIntent;
92 
DataUsageSummaryPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)93     public DataUsageSummaryPreferenceController(Context context, String preferenceKey,
94             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
95         super(context, preferenceKey, fragmentController, uxRestrictions);
96         mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
97         mTelephonyManager = context.getSystemService(TelephonyManager.class);
98         mDataUsageController = new DataUsageController(context);
99 
100         int defaultSubId = DataUsageUtils.getDefaultSubscriptionId(mSubscriptionManager);
101         mDefaultTemplate = DataUsageUtils.getMobileNetworkTemplate(mTelephonyManager, defaultSubId);
102     }
103 
104     @Override
getPreferenceType()105     protected Class<DataUsageSummaryPreference> getPreferenceType() {
106         return DataUsageSummaryPreference.class;
107     }
108 
109     @Override
getAvailabilityStatus()110     protected int getAvailabilityStatus() {
111         return NetworkUtils.hasSim(mTelephonyManager) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
112     }
113 
114     @Override
onCreateInternal()115     protected void onCreateInternal() {
116         getPreference().setMin(0);
117         getPreference().setMax(MAX_PROGRESS_BAR_VALUE);
118     }
119 
120     @Override
updateState(DataUsageSummaryPreference preference)121     protected void updateState(DataUsageSummaryPreference preference) {
122         DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo(
123                 mDefaultTemplate);
124 
125         if (mSubscriptionManager != null) {
126             refreshDataplanInfo(info);
127         }
128 
129         preference.setTitle(getUsageText());
130         preference.setManageSubscriptionIntent(mManageSubscriptionIntent);
131 
132         preference.setDataLimitText(getLimitText(info));
133         preference.setRemainingBillingCycleText(getRemainingBillingCycleTimeText());
134 
135         // Carrier Info has special styling based on when it was last updated.
136         preference.setCarrierInfoText(getCarrierInfoText());
137         long updateAgeMillis = calculateTruncatedUpdateAge(mSnapshotTime);
138         if (updateAgeMillis <= WARNING_AGE) {
139             preference.setCarrierInfoTextStyle(R.style.DataUsageSummaryCarrierInfoTextAppearance);
140         } else {
141             preference.setCarrierInfoTextStyle(
142                     R.style.DataUsageSummaryCarrierInfoWarningTextAppearance);
143         }
144 
145         // Set the progress bar values.
146         preference.setMinLabel(DataUsageUtils.bytesToIecUnits(getContext(), /* byteValue= */ 0));
147         preference.setMaxLabel(
148                 DataUsageUtils.bytesToIecUnits(getContext(), mDataplanTrackingThreshold));
149         preference.setProgress(scaleUsage(mDataplanUse, mDataplanTrackingThreshold));
150     }
151 
getUsageText()152     private CharSequence getUsageText() {
153         Formatter.BytesResult usedResult = Formatter.formatBytes(getContext().getResources(),
154                 mDataplanUse, Formatter.FLAG_CALCULATE_ROUNDED | Formatter.FLAG_IEC_UNITS);
155         SpannableString usageNumberText = new SpannableString(usedResult.value);
156         int textSize = getContext().getResources().getDimensionPixelSize(
157                 R.dimen.usage_number_text_size);
158 
159         // Set the usage text (only the number) to the size defined by usage_number_text_size.
160         usageNumberText.setSpan(new AbsoluteSizeSpan(textSize), /* start= */ 0,
161                 usageNumberText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
162 
163         CharSequence template = getContext().getText(R.string.data_used_formatted);
164         CharSequence usageText = TextUtils.expandTemplate(template, usageNumberText,
165                 usedResult.units);
166         return usageText;
167     }
168 
169 
getLimitText(DataUsageController.DataUsageInfo info)170     private CharSequence getLimitText(DataUsageController.DataUsageInfo info) {
171         if (info.warningLevel > 0 && info.limitLevel > 0) {
172             return TextUtils.expandTemplate(
173                     getContext().getText(R.string.cell_data_warning_and_limit),
174                     DataUsageUtils.bytesToIecUnits(getContext(), info.warningLevel),
175                     DataUsageUtils.bytesToIecUnits(getContext(), info.limitLevel));
176         } else if (info.warningLevel > 0) {
177             return TextUtils.expandTemplate(getContext().getText(R.string.cell_data_warning),
178                     DataUsageUtils.bytesToIecUnits(getContext(), info.warningLevel));
179         } else if (info.limitLevel > 0) {
180             return TextUtils.expandTemplate(getContext().getText(R.string.cell_data_limit),
181                     DataUsageUtils.bytesToIecUnits(getContext(), info.limitLevel));
182         }
183 
184         return null;
185     }
186 
getRemainingBillingCycleTimeText()187     private CharSequence getRemainingBillingCycleTimeText() {
188         long millisLeft = mCycleEnd - System.currentTimeMillis();
189         if (millisLeft <= 0) {
190             return getContext().getString(R.string.billing_cycle_none_left);
191         } else {
192             int daysLeft = (int) (millisLeft / MILLIS_IN_A_DAY);
193             return daysLeft < 1
194                     ? getContext().getString(R.string.billing_cycle_less_than_one_day_left)
195                     : getContext().getResources().getQuantityString(
196                             R.plurals.billing_cycle_days_left, daysLeft, daysLeft);
197         }
198     }
199 
getCarrierInfoText()200     private CharSequence getCarrierInfoText() {
201         if (mDataplanCount > 0 && mSnapshotTime >= 0L) {
202             long updateAgeMillis = calculateTruncatedUpdateAge(mSnapshotTime);
203 
204             int textResourceId;
205             CharSequence updateTime = null;
206             if (updateAgeMillis == 0) {
207                 if (mCarrierName != null) {
208                     textResourceId = R.string.carrier_and_update_now_text;
209                 } else {
210                     textResourceId = R.string.no_carrier_update_now_text;
211                 }
212             } else {
213                 if (mCarrierName != null) {
214                     textResourceId = R.string.carrier_and_update_text;
215                 } else {
216                     textResourceId = R.string.no_carrier_update_text;
217                 }
218                 updateTime = StringUtil.formatElapsedTime(getContext(),
219                         updateAgeMillis, /* withSeconds= */ false);
220             }
221             return TextUtils.expandTemplate(getContext().getText(textResourceId), mCarrierName,
222                     updateTime);
223         }
224 
225         return null;
226     }
227 
refreshDataplanInfo(DataUsageController.DataUsageInfo info)228     private void refreshDataplanInfo(DataUsageController.DataUsageInfo info) {
229         // Reset data before overwriting.
230         mCarrierName = null;
231         mDataplanCount = 0;
232         mSnapshotTime = -1L;
233         mDataplanSize = -1L;
234         mDataplanTrackingThreshold = getSummaryLimit(info);
235         mDataplanUse = info.usageLevel;
236         mCycleEnd = info.cycleEnd;
237 
238         int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
239         SubscriptionInfo subInfo = mSubscriptionManager.getDefaultDataSubscriptionInfo();
240         if (subInfo != null) {
241             mCarrierName = subInfo.getCarrierName();
242             List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans(defaultSubId);
243             SubscriptionPlan primaryPlan = DataUsageUtils.getPrimaryPlan(mSubscriptionManager,
244                     defaultSubId);
245             if (primaryPlan != null) {
246                 mDataplanCount = plans.size();
247                 mDataplanSize = primaryPlan.getDataLimitBytes();
248                 if (mDataplanSize == SubscriptionPlan.BYTES_UNLIMITED) {
249                     mDataplanSize = -1L;
250                 }
251                 mDataplanTrackingThreshold = mDataplanSize;
252                 mDataplanUse = primaryPlan.getDataUsageBytes();
253 
254                 RecurrenceRule rule = primaryPlan.getCycleRule();
255                 if (rule != null && rule.start != null && rule.end != null) {
256                     mCycleEnd = rule.end.toEpochSecond() * MILLIS_IN_A_SECOND;
257                 }
258                 mSnapshotTime = primaryPlan.getDataUsageTime();
259             }
260         }
261         mManageSubscriptionIntent = createManageSubscriptionIntent(defaultSubId);
262     }
263 
264     /**
265      * Create an {@link Intent} that can be launched towards the carrier app
266      * that is currently defining the billing relationship plan through
267      * {@link INetworkPolicyManager#setSubscriptionPlans(int, SubscriptionPlan [], String)}.
268      *
269      * @return ready to launch Intent targeted towards the carrier app, or
270      *         {@code null} if no carrier app is defined, or if the defined
271      *         carrier app provides no management activity.
272      */
createManageSubscriptionIntent(int subId)273     private Intent createManageSubscriptionIntent(int subId) {
274         INetworkPolicyManager networkPolicyManager = INetworkPolicyManager.Stub.asInterface(
275                 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
276         String owner = "";
277         try {
278             owner = networkPolicyManager.getSubscriptionPlansOwner(subId);
279         } catch (Exception ex) {
280             LOG.w("Fail to get subscription plan owner for subId " + subId, ex);
281         }
282 
283         if (TextUtils.isEmpty(owner)) {
284             return null;
285         }
286 
287         List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans(subId);
288         if (plans.isEmpty()) {
289             return null;
290         }
291 
292         Intent intent = new Intent(SubscriptionManager.ACTION_MANAGE_SUBSCRIPTION_PLANS);
293         intent.setPackage(owner);
294         intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
295 
296         if (getContext().getPackageManager().queryIntentActivities(intent,
297                 PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
298             return null;
299         }
300 
301         return intent;
302     }
303 
304     /** Scales the current usage to be an integer between 0 and {@link #MAX_PROGRESS_BAR_VALUE}. */
scaleUsage(long usage, long maxUsage)305     private int scaleUsage(long usage, long maxUsage) {
306         if (maxUsage == 0) {
307             return 0;
308         }
309         return (int) ((usage / (float) maxUsage) * MAX_PROGRESS_BAR_VALUE);
310     }
311 
312     /**
313      * Gets the max displayed limit based on {@link DataUsageController.DataUsageInfo}.
314      *
315      * @return the most appropriate limit for the data usage summary. Use the total usage when it
316      * is higher than the limit and warning level. Use the limit when it is set and less than usage.
317      * Otherwise use warning level.
318      */
getSummaryLimit(DataUsageController.DataUsageInfo info)319     private static long getSummaryLimit(DataUsageController.DataUsageInfo info) {
320         long limit = info.limitLevel;
321         if (limit <= 0) {
322             limit = info.warningLevel;
323         }
324         if (info.usageLevel > limit) {
325             limit = info.usageLevel;
326         }
327         return limit;
328     }
329 
330     /**
331      * Returns the time since the last carrier update, as defined by {@link #mSnapshotTime},
332      * truncated to the nearest day / hour / minute in milliseconds, or 0 if less than 1 min.
333      */
calculateTruncatedUpdateAge(long snapshotTime)334     private long calculateTruncatedUpdateAge(long snapshotTime) {
335         long updateAgeMillis = System.currentTimeMillis() - snapshotTime;
336 
337         // Round to nearest whole unit
338         if (updateAgeMillis >= MILLIS_IN_A_DAY) {
339             return (updateAgeMillis / MILLIS_IN_A_DAY) * MILLIS_IN_A_DAY;
340         } else if (updateAgeMillis >= MILLIS_IN_AN_HOUR) {
341             return (updateAgeMillis / MILLIS_IN_AN_HOUR) * MILLIS_IN_AN_HOUR;
342         } else if (updateAgeMillis >= MILLIS_IN_A_MINUTE) {
343             return (updateAgeMillis / MILLIS_IN_A_MINUTE) * MILLIS_IN_A_MINUTE;
344         } else {
345             return 0;
346         }
347     }
348 }
349