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