• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.imsserviceentitlement;
18 
19 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED;
20 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED;
21 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED;
22 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED;
23 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__INCOMPATIBLE;
24 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__SUCCESSFUL;
25 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__TIMEOUT;
26 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT;
27 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT;
28 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__ACTIVATION;
29 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__UNKNOWN_PURPOSE;
30 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__UPDATE;
31 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOWIFI;
32 
33 import android.app.Activity;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.os.CountDownTimer;
37 import android.text.TextUtils;
38 import android.util.Log;
39 
40 import androidx.annotation.MainThread;
41 import androidx.annotation.Nullable;
42 import androidx.annotation.StringRes;
43 
44 import com.android.imsserviceentitlement.entitlement.EntitlementResult;
45 import com.android.imsserviceentitlement.ts43.Ts43VowifiStatus;
46 import com.android.imsserviceentitlement.utils.ImsUtils;
47 import com.android.imsserviceentitlement.utils.TelephonyUtils;
48 
49 import java.time.Duration;
50 
51 /**
52  * The driver for WFC activation workflow: go/vowifi-entitlement-status-analysis.
53  *
54  * <p>One {@link WfcActivationActivity} owns one and only one controller instance.
55  */
56 public class WfcActivationController {
57     private static final String TAG = "IMSSE-WfcActivationController";
58 
59     // Entitlement status update retry
60     private static final int ENTITLEMENT_STATUS_UPDATE_RETRY_MAX = 6;
61     private static final long ENTITLEMENT_STATUS_UPDATE_RETRY_INTERVAL_MS =
62             Duration.ofSeconds(5).toMillis();
63 
64     // Dependencies
65     private final WfcActivationUi mActivationUi;
66     private final TelephonyUtils mTelephonyUtils;
67     private final ImsEntitlementApi mImsEntitlementApi;
68     private final ImsUtils mImsUtils;
69     private final Intent mStartIntent;
70 
71     // States
72     private int mEvaluateTimes = 0;
73 
74     // States for metrics
75     private long mStartTime;
76     private long mDurationMillis;
77     private int mPurpose = IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__UNKNOWN_PURPOSE;
78     private int mAppResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT;
79 
80     @MainThread
WfcActivationController( Context context, WfcActivationUi wfcActivationUi, ImsEntitlementApi imsEntitlementApi, Intent intent)81     public WfcActivationController(
82             Context context,
83             WfcActivationUi wfcActivationUi,
84             ImsEntitlementApi imsEntitlementApi,
85             Intent intent) {
86         this.mStartIntent = intent;
87         this.mActivationUi = wfcActivationUi;
88         this.mImsEntitlementApi = imsEntitlementApi;
89         this.mTelephonyUtils = new TelephonyUtils(context, getSubId());
90         this.mImsUtils = ImsUtils.getInstance(context, getSubId());
91     }
92 
93     /** Indicates the controller to start WFC activation or emergency address update flow. */
94     @MainThread
startFlow()95     public void startFlow() {
96         showGeneralWaitingUi();
97         evaluateEntitlementStatus();
98         if (isActivationFlow()) {
99             mPurpose = IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__ACTIVATION;
100         } else {
101             mPurpose = IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__UPDATE;
102         }
103         mStartTime = mTelephonyUtils.getUptimeMillis();
104     }
105 
106     /** Evaluates entitlement status for activation or update. */
107     @MainThread
evaluateEntitlementStatus()108     public void evaluateEntitlementStatus() {
109         if (!mTelephonyUtils.isNetworkConnected()) {
110             handleInitialEntitlementStatus(null);
111             return;
112         }
113         EntitlementUtils.entitlementCheck(
114                 mImsEntitlementApi, result -> handleInitialEntitlementStatus(result));
115     }
116 
117     /**
118      * Indicates the controller to re-evaluate WFC entitlement status after activation flow finished
119      * successfully (ie. not canceled) by user.
120      */
121     @MainThread
finishFlow()122     public void finishFlow() {
123         showGeneralWaitingUi();
124         reevaluateEntitlementStatus();
125     }
126 
127     /** Re-evaluate entitlement status after updating. */
128     @MainThread
reevaluateEntitlementStatus()129     public void reevaluateEntitlementStatus() {
130         EntitlementUtils.entitlementCheck(
131                 mImsEntitlementApi, result -> handleReevaluationEntitlementStatus(result));
132     }
133 
134     /** The interface for handling the entitlement check result. */
135     public interface EntitlementResultCallback {
onEntitlementResult(EntitlementResult result)136         void onEntitlementResult(EntitlementResult result);
137     }
138 
139     /** Indicates the controller to finish on-going tasks and get ready to be destroyed. */
140     @MainThread
finish()141     public void finish() {
142         EntitlementUtils.cancelEntitlementCheck();
143 
144         // If no duration set, set now.
145         if (mDurationMillis == 0L) {
146             mDurationMillis = mTelephonyUtils.getUptimeMillis() - mStartTime;
147         }
148         // If no result set, it must be cancelled by user pressing back button.
149         if (mAppResult == IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT) {
150             mAppResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED;
151         }
152         ImsServiceEntitlementStatsLog.write(
153                 IMS_SERVICE_ENTITLEMENT_UPDATED,
154                 /* carrier_id= */ mTelephonyUtils.getCarrierId(),
155                 /* actual_carrier_id= */ mTelephonyUtils.getSpecificCarrierId(),
156                 mPurpose,
157                 IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOWIFI,
158                 mAppResult,
159                 mDurationMillis);
160     }
161 
162     /**
163      * Returns {@code true} if the app is launched for WFC activation; {@code false} for emergency
164      * address update.
165      */
isActivationFlow()166     private boolean isActivationFlow() {
167         return ActivityConstants.isActivationFlow(mStartIntent);
168     }
169 
getSubId()170     private int getSubId() {
171         return ActivityConstants.getSubId(mStartIntent);
172     }
173 
174     /** Returns UI title string resource ID based on {@link #isActivationFlow()}. */
175     @StringRes
getUiTitle()176     private int getUiTitle() {
177         int intention = ActivityConstants.getLaunchIntention(mStartIntent);
178         if (intention == ActivityConstants.LAUNCH_APP_ACTIVATE) {
179             return R.string.activate_title;
180         }
181         if (intention == ActivityConstants.LAUNCH_APP_SHOW_TC) {
182             return R.string.tos_title;
183         }
184         // LAUNCH_APP_UPDATE or otherwise
185         return R.string.e911_title;
186     }
187 
188     /** Returns general error string resource ID based on {@link #isActivationFlow()}. */
189     @StringRes
getGeneralErrorText()190     private int getGeneralErrorText() {
191         int intention = ActivityConstants.getLaunchIntention(mStartIntent);
192         if (intention == ActivityConstants.LAUNCH_APP_ACTIVATE) {
193             return R.string.wfc_activation_error;
194         } else if (intention == ActivityConstants.LAUNCH_APP_SHOW_TC) {
195             return R.string.show_terms_and_condition_error;
196         }
197         // LAUNCH_APP_UPDATE or otherwise
198         return R.string.address_update_error;
199     }
200 
showErrorUi(@tringRes int errorMessage)201     private void showErrorUi(@StringRes int errorMessage) {
202         mActivationUi.showActivationUi(
203                 getUiTitle(), errorMessage, false, R.string.ok, WfcActivationUi.RESULT_FAILURE, 0);
204     }
205 
showGeneralErrorUi()206     private void showGeneralErrorUi() {
207         showErrorUi(getGeneralErrorText());
208     }
209 
showGeneralWaitingUi()210     private void showGeneralWaitingUi() {
211         mActivationUi.showActivationUi(getUiTitle(), R.string.progress_text, true, 0, 0, 0);
212     }
213 
214     @MainThread
handleInitialEntitlementStatus(@ullable EntitlementResult result)215     private void handleInitialEntitlementStatus(@Nullable EntitlementResult result) {
216         Log.d(TAG, "Initial entitlement result: " + result);
217         if (result == null) {
218             showGeneralErrorUi();
219             finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED);
220             return;
221         }
222         if (isActivationFlow()) {
223             handleEntitlementStatusForActivation(result);
224         } else {
225             handleEntitlementStatusForUpdating(result);
226         }
227     }
228 
229     @MainThread
handleEntitlementStatusForActivation(EntitlementResult result)230     private void handleEntitlementStatusForActivation(EntitlementResult result) {
231         Ts43VowifiStatus vowifiStatus = result.getVowifiStatus();
232         if (vowifiStatus.vowifiEntitled()) {
233             finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__SUCCESSFUL);
234             mActivationUi.setResultAndFinish(Activity.RESULT_OK);
235         } else {
236             if (vowifiStatus.serverDataMissing()) {
237                 if (!TextUtils.isEmpty(result.getTermsAndConditionsWebUrl())) {
238                     mActivationUi.showWebview(
239                             result.getTermsAndConditionsWebUrl(), /* postData= */ null);
240                 } else {
241                     mActivationUi.showWebview(
242                             result.getEmergencyAddressWebUrl(),
243                             result.getEmergencyAddressWebData());
244                 }
245             } else if (vowifiStatus.incompatible()) {
246                 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__INCOMPATIBLE);
247                 showErrorUi(R.string.failure_contact_carrier);
248             } else {
249                 Log.e(TAG, "Unexpected status. Show error UI.");
250                 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT);
251                 showGeneralErrorUi();
252             }
253         }
254     }
255 
256     @MainThread
handleEntitlementStatusForUpdating(EntitlementResult result)257     private void handleEntitlementStatusForUpdating(EntitlementResult result) {
258         Ts43VowifiStatus vowifiStatus = result.getVowifiStatus();
259         if (vowifiStatus.vowifiEntitled()) {
260             int launchIntention = ActivityConstants.getLaunchIntention(mStartIntent);
261             if (launchIntention == ActivityConstants.LAUNCH_APP_SHOW_TC) {
262                 mActivationUi.showWebview(
263                         result.getTermsAndConditionsWebUrl(), /* postData= */ null);
264             } else {
265                 mActivationUi.showWebview(
266                         result.getEmergencyAddressWebUrl(), result.getEmergencyAddressWebData());
267             }
268         } else {
269             if (vowifiStatus.incompatible()) {
270                 showErrorUi(R.string.failure_contact_carrier);
271                 turnOffWfc(() -> {
272                     finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__INCOMPATIBLE);
273                 });
274             } else {
275                 Log.e(TAG, "Unexpected status. Show error UI.");
276                 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT);
277                 showGeneralErrorUi();
278             }
279         }
280     }
281 
282     @MainThread
handleReevaluationEntitlementStatus(@ullable EntitlementResult result)283     private void handleReevaluationEntitlementStatus(@Nullable EntitlementResult result) {
284         Log.d(TAG, "Reevaluation entitlement result: " + result);
285         if (result == null) { // Network issue
286             showGeneralErrorUi();
287             finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED);
288             return;
289         }
290         if (isActivationFlow()) {
291             handleEntitlementStatusAfterActivation(result);
292         } else {
293             handleEntitlementStatusAfterUpdating(result);
294         }
295     }
296 
297     @MainThread
handleEntitlementStatusAfterActivation(EntitlementResult result)298     private void handleEntitlementStatusAfterActivation(EntitlementResult result) {
299         Ts43VowifiStatus vowifiStatus = result.getVowifiStatus();
300         if (vowifiStatus.vowifiEntitled()) {
301             mActivationUi.setResultAndFinish(Activity.RESULT_OK);
302             finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__SUCCESSFUL);
303         } else {
304             if (vowifiStatus.serverDataMissing()) {
305                 // Check again after 5s, max retry 6 times
306                 if (mEvaluateTimes < ENTITLEMENT_STATUS_UPDATE_RETRY_MAX) {
307                     mEvaluateTimes += 1;
308                     postDelay(
309                             getEntitlementStatusUpdateRetryIntervalMs(),
310                             this::reevaluateEntitlementStatus);
311                 } else {
312                     mEvaluateTimes = 0;
313                     showGeneralErrorUi();
314                     finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__TIMEOUT);
315                 }
316             } else {
317                 // These should never happen, but nothing else we can do. Show general error.
318                 Log.e(TAG, "Unexpected status. Show error UI.");
319                 showGeneralErrorUi();
320                 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT);
321             }
322         }
323     }
324 
getEntitlementStatusUpdateRetryIntervalMs()325     private long getEntitlementStatusUpdateRetryIntervalMs() {
326         return ENTITLEMENT_STATUS_UPDATE_RETRY_INTERVAL_MS;
327     }
328 
329     @MainThread
handleEntitlementStatusAfterUpdating(EntitlementResult result)330     private void handleEntitlementStatusAfterUpdating(EntitlementResult result) {
331         Ts43VowifiStatus vowifiStatus = result.getVowifiStatus();
332         if (vowifiStatus.vowifiEntitled()) {
333             mActivationUi.setResultAndFinish(Activity.RESULT_OK);
334             finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__SUCCESSFUL);
335         } else if (vowifiStatus.serverDataMissing()) {
336             // Some carrier allows de-activating in updating flow.
337             turnOffWfc(() -> {
338                 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED);
339                 mActivationUi.setResultAndFinish(Activity.RESULT_OK);
340             });
341         } else {
342             Log.e(TAG, "Unexpected status. Show error UI.");
343             showGeneralErrorUi();
344             finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT);
345         }
346     }
347 
348     /** Runs {@code action} on caller's thread after {@code delayMillis} ms. */
postDelay(long delayMillis, Runnable action)349     private static void postDelay(long delayMillis, Runnable action) {
350         new CountDownTimer(delayMillis, delayMillis + 100) {
351             // Use a countDownInterval bigger than millisInFuture so onTick never fires.
352             @Override
353             public void onTick(long millisUntilFinished) {
354                 // Do nothing
355             }
356 
357             @Override
358             public void onFinish() {
359                 action.run();
360             }
361         }.start();
362     }
363 
364     /** Turns WFC off and then runs {@code action} on main thread. */
365     @MainThread
turnOffWfc(Runnable action)366     private void turnOffWfc(Runnable action) {
367         ImsUtils.turnOffWfc(mImsUtils, action);
368     }
369 
finishStatsLog(int result)370     private void finishStatsLog(int result) {
371         mAppResult = result;
372         mDurationMillis = mTelephonyUtils.getUptimeMillis() - mStartTime;
373     }
374 }
375