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