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