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.managedprovisioning.common;
18 
19 import static com.android.managedprovisioning.common.RetryLaunchViewModel.LaunchActivityFailureEvent.REASON_EXCEEDED_MAXIMUM_NUMBER_ACTIVITY_LAUNCH_RETRIES;
20 
21 import static java.util.Objects.requireNonNull;
22 
23 import android.app.Application;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.UserHandle;
29 
30 import androidx.annotation.NonNull;
31 import androidx.lifecycle.AndroidViewModel;
32 import androidx.lifecycle.MutableLiveData;
33 import androidx.lifecycle.ViewModel;
34 import androidx.lifecycle.ViewModelProvider;
35 
36 import java.util.Objects;
37 
38 final class RetryLaunchViewModel extends AndroidViewModel {
39     static final int VIEW_MODEL_EVENT_LAUNCH_ACTIVITY = 1;
40     static final int VIEW_MODEL_EVENT_LAUNCH_FAILURE = 2;
41     static final int VIEW_MODEL_EVENT_WAITING_FOR_RETRY = 3;
42 
43     private final MutableLiveData<ViewModelEvent> mObservableEvents = new MutableLiveData<>();
44     private final Runnable mRunnable = RetryLaunchViewModel.this::tryStartActivity;
45     private final Handler mHandler;
46     private final CanLaunchActivityChecker mCanLaunchActivityChecker;
47     private final Config mConfig;
48     private final Intent mActivityIntent;
49 
50     private int mNumberOfStartUpdaterTries = 0;
51     private boolean mIsWaitingForActivityResult;
52 
RetryLaunchViewModel( @onNull Application application, Intent activityIntent, Handler handler, CanLaunchActivityChecker canLaunchActivityChecker, Config config)53     RetryLaunchViewModel(
54             @NonNull Application application,
55             Intent activityIntent,
56             Handler handler,
57             CanLaunchActivityChecker canLaunchActivityChecker,
58             Config config) {
59         super(application);
60         mActivityIntent = requireNonNull(activityIntent);
61         mHandler = requireNonNull(handler);
62         mCanLaunchActivityChecker = requireNonNull(canLaunchActivityChecker);
63         mConfig = requireNonNull(config);
64     }
65 
observeViewModelEvents()66     MutableLiveData<ViewModelEvent> observeViewModelEvents() {
67         return mObservableEvents;
68     }
69 
70     /**
71      * Tries to start the role holder updater.
72      * <ol>
73      * <li>If the activity can be launched, it is launched.</li>
74      * <li>If the activity cannot be currently launched (e.g. if the app it belongs to is being
75      * updated), then we schedule a retry, up to {@link Config#getLaunchActivityMaxRetries()}
76      * times total.</li>
77      * <li>If we exceed the max retry thresholds, we post a failure event.</li>
78      * </ol>
79      *
80      * @see LaunchActivityEvent
81      * @see LaunchActivityFailureEvent
82      */
tryStartActivity()83     void tryStartActivity() {
84         boolean canLaunchActivity = mCanLaunchActivityChecker.canLaunchActivity(
85                 getApplication().getApplicationContext(), mActivityIntent);
86         if (canLaunchActivity) {
87             launchActivity(mActivityIntent);
88         } else {
89             ProvisionLogger.loge("Cannot launch activity " + mActivityIntent.getAction());
90             tryRescheduleActivityLaunch();
91         }
92     }
93 
stopLaunchRetries()94     void stopLaunchRetries() {
95         mHandler.removeCallbacks(mRunnable);
96     }
97 
isWaitingForActivityResult()98     boolean isWaitingForActivityResult() {
99         return mIsWaitingForActivityResult;
100     }
101 
markWaitingForActivityResult()102     void markWaitingForActivityResult() {
103         mIsWaitingForActivityResult = true;
104     }
105 
106     /**
107      * Tries to reschedule the role holder updater launch.
108      */
tryRescheduleActivityLaunch()109     private void tryRescheduleActivityLaunch() {
110         if (canRetryLaunchActivity(mNumberOfStartUpdaterTries)) {
111             scheduleRetryLaunchActivity();
112             mObservableEvents.postValue(new LaunchActivityWaitingForRetryEvent());
113         } else {
114             ProvisionLogger.loge("Exceeded maximum number of activity launch retries.");
115             mObservableEvents.postValue(
116                     new LaunchActivityFailureEvent(
117                             REASON_EXCEEDED_MAXIMUM_NUMBER_ACTIVITY_LAUNCH_RETRIES));
118         }
119     }
120 
canRetryLaunchActivity(int numTries)121     private boolean canRetryLaunchActivity(int numTries) {
122         return numTries < mConfig.getLaunchActivityMaxRetries();
123     }
124 
launchActivity(Intent intent)125     private void launchActivity(Intent intent) {
126         mObservableEvents.postValue(new LaunchActivityEvent(intent));
127     }
128 
scheduleRetryLaunchActivity()129     private void scheduleRetryLaunchActivity() {
130         mHandler.postDelayed(mRunnable, mConfig.getLaunchActivityRetryMillis());
131         mNumberOfStartUpdaterTries++;
132     }
133 
134     static class LaunchActivityEvent extends ViewModelEvent {
135         private final Intent mIntent;
136 
LaunchActivityEvent(Intent intent)137         LaunchActivityEvent(Intent intent) {
138             super(VIEW_MODEL_EVENT_LAUNCH_ACTIVITY);
139             mIntent = requireNonNull(intent);
140         }
141 
getIntent()142         Intent getIntent() {
143             return mIntent;
144         }
145 
146         @Override
equals(Object o)147         public boolean equals(Object o) {
148             if (this == o) return true;
149             if (!(o instanceof LaunchActivityEvent)) return false;
150             LaunchActivityEvent that = (LaunchActivityEvent) o;
151             return Objects.equals(mIntent.getAction(), that.mIntent.getAction())
152                     && Objects.equals(mIntent.getExtras(), that.mIntent.getExtras());
153         }
154 
155         @Override
hashCode()156         public int hashCode() {
157             return Objects.hash(mIntent.getAction(), mIntent.getExtras());
158         }
159 
160         @Override
toString()161         public String toString() {
162             return "LaunchActivityEvent{"
163                     + "mIntent=" + mIntent + '}';
164         }
165     }
166 
167     static class LaunchActivityFailureEvent extends ViewModelEvent {
168         static final int REASON_EXCEEDED_MAXIMUM_NUMBER_ACTIVITY_LAUNCH_RETRIES = 1;
169 
170         private final int mReason;
171 
LaunchActivityFailureEvent(int reason)172         LaunchActivityFailureEvent(int reason) {
173             super(VIEW_MODEL_EVENT_LAUNCH_FAILURE);
174             mReason = reason;
175         }
176 
getReason()177         int getReason() {
178             return mReason;
179         }
180 
181         @Override
equals(Object o)182         public boolean equals(Object o) {
183             if (this == o) return true;
184             if (!(o instanceof LaunchActivityFailureEvent)) return false;
185             LaunchActivityFailureEvent that = (LaunchActivityFailureEvent) o;
186             return mReason == that.mReason;
187         }
188 
189         @Override
hashCode()190         public int hashCode() {
191             return Objects.hash(mReason);
192         }
193 
194         @Override
toString()195         public String toString() {
196             return "LaunchActivityFailureEvent{"
197                     + "mReason=" + mReason + '}';
198         }
199     }
200 
201     static class LaunchActivityWaitingForRetryEvent extends ViewModelEvent {
LaunchActivityWaitingForRetryEvent()202         LaunchActivityWaitingForRetryEvent() {
203             super(VIEW_MODEL_EVENT_WAITING_FOR_RETRY);
204         }
205 
206         @Override
equals(Object o)207         public boolean equals(Object o) {
208             if (this == o) return true;
209             if (!(o instanceof LaunchActivityWaitingForRetryEvent)) return false;
210             return true;
211         }
212 
213         @Override
hashCode()214         public int hashCode() {
215             return Objects.hash(VIEW_MODEL_EVENT_WAITING_FOR_RETRY);
216         }
217 
218         @Override
toString()219         public String toString() {
220             return "LaunchActivityWaitingForRetryEvent{}";
221         }
222     }
223 
224     static class RetryLaunchViewModelFactory implements ViewModelProvider.Factory {
225         private final Application mApplication;
226         private final Intent mActivityIntent;
227         private final Config mConfig;
228         private final Utils mUtils;
229 
RetryLaunchViewModelFactory( Application application, Intent activityIntent, Config config, Utils utils)230         RetryLaunchViewModelFactory(
231                 Application application,
232                 Intent activityIntent,
233                 Config config,
234                 Utils utils) {
235             mApplication = requireNonNull(application);
236             mActivityIntent = requireNonNull(activityIntent);
237             mConfig = requireNonNull(config);
238             mUtils = requireNonNull(utils);
239         }
240 
241         @Override
create(Class<T> aClass)242         public <T extends ViewModel> T create(Class<T> aClass) {
243             return (T) new RetryLaunchViewModel(
244                     mApplication,
245                     mActivityIntent,
246                     new Handler(Looper.getMainLooper()),
247                     new DefaultCanLaunchActivityChecker(mUtils),
248                     mConfig);
249         }
250     }
251 
252     interface CanLaunchActivityChecker {
canLaunchActivity(Context context, Intent intent)253         boolean canLaunchActivity(Context context, Intent intent);
254     }
255 
256     interface Config {
getLaunchActivityRetryMillis()257         long getLaunchActivityRetryMillis();
258 
getLaunchActivityMaxRetries()259         int getLaunchActivityMaxRetries();
260     }
261 
262     static class DefaultCanLaunchActivityChecker implements CanLaunchActivityChecker {
263 
264         private final Utils mUtils;
265 
DefaultCanLaunchActivityChecker(Utils utils)266         DefaultCanLaunchActivityChecker(Utils utils) {
267             mUtils = requireNonNull(utils);
268         }
269 
270         @Override
canLaunchActivity(Context context, Intent intent)271         public boolean canLaunchActivity(Context context, Intent intent) {
272             return mUtils.canResolveIntentAsUser(context, intent, UserHandle.USER_SYSTEM);
273         }
274 
275     }
276 }
277