blob: a3c97cdeae4ac96db70f56755952881d59843f07 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.managedprovisioning.preprovisioning;
import static com.android.managedprovisioning.preprovisioning.RoleHolderUpdaterViewModel.LaunchRoleHolderUpdaterFailureEvent.REASON_EXCEEDED_MAXIMUM_NUMBER_UPDATER_LAUNCH_RETRIES;
import static com.android.managedprovisioning.preprovisioning.RoleHolderUpdaterViewModel.LaunchRoleHolderUpdaterFailureEvent.REASON_EXCEEDED_MAXIMUM_NUMBER_UPDATE_RETRIES;
import static java.util.Objects.requireNonNull;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import com.android.managedprovisioning.common.DeviceManagementRoleHolderUpdaterHelper;
import com.android.managedprovisioning.common.ProvisionLogger;
import com.android.managedprovisioning.common.Utils;
import com.android.managedprovisioning.common.ViewModelEvent;
import java.util.Objects;
final class RoleHolderUpdaterViewModel extends AndroidViewModel {
static final int VIEW_MODEL_EVENT_LAUNCH_UPDATER = 1;
static final int VIEW_MODEL_EVENT_LAUNCH_FAILURE = 2;
private static final int LAUNCH_ROLE_HOLDER_UPDATER_RETRY_PERIOD_MS = 60 * 1000;
private static final int LAUNCH_ROLE_HOLDER_UPDATER_MAX_RETRIES = 3;
private static final int ROLE_HOLDER_UPDATE_MAX_RETRIES = 3;
private final MutableLiveData<ViewModelEvent> mObservableEvents = new MutableLiveData<>();
private final Runnable mRunnable = RoleHolderUpdaterViewModel.this::tryStartRoleHolderUpdater;
private final Handler mHandler;
private final CanLaunchRoleHolderUpdaterChecker mCanLaunchRoleHolderUpdaterChecker;
private final Config mConfig;
private final DeviceManagementRoleHolderUpdaterHelper mRoleHolderUpdaterHelper;
private int mNumberOfStartUpdaterTries = 0;
private int mNumberOfUpdateTries = 0;
RoleHolderUpdaterViewModel(
@NonNull Application application,
Handler handler,
CanLaunchRoleHolderUpdaterChecker canLaunchRoleHolderUpdaterChecker,
Config config,
DeviceManagementRoleHolderUpdaterHelper roleHolderUpdaterHelper) {
super(application);
mHandler = requireNonNull(handler);
mCanLaunchRoleHolderUpdaterChecker = requireNonNull(canLaunchRoleHolderUpdaterChecker);
mConfig = requireNonNull(config);
mRoleHolderUpdaterHelper = requireNonNull(roleHolderUpdaterHelper);
}
MutableLiveData<ViewModelEvent> observeViewModelEvents() {
return mObservableEvents;
}
/**
* Tries to start the role holder updater.
* <ol>
* <li>If the role holder updater can be launched, it is launched. It then tries to download the
* role holder. If it fails to download it, it tries {@link #ROLE_HOLDER_UPDATE_MAX_RETRIES}
* times total.</li>
* <li>If the role holder updater cannot be currently launched (e.g. if it's being updated
* itself), then we schedule a retry, up to {@link #LAUNCH_ROLE_HOLDER_UPDATER_MAX_RETRIES}
* times total.</li>
* <li>If we exceed either of the max retry thresholds, we post a failure event.</li>
* </ol>
*
* @see LaunchRoleHolderUpdaterEvent
* @see LaunchRoleHolderUpdaterFailureEvent
*/
void tryStartRoleHolderUpdater() {
Intent intent = mRoleHolderUpdaterHelper.createRoleHolderUpdaterIntent();
boolean canLaunchRoleHolderUpdater =
mCanLaunchRoleHolderUpdaterChecker.canLaunchRoleHolderUpdater(
getApplication().getApplicationContext(), intent);
if (canLaunchRoleHolderUpdater
&& canRetryUpdate(mNumberOfUpdateTries)) {
launchRoleHolderUpdater(intent);
} else if (canLaunchRoleHolderUpdater) {
ProvisionLogger.loge("Exceeded maximum number of update retries.");
mObservableEvents.postValue(
new LaunchRoleHolderUpdaterFailureEvent(
REASON_EXCEEDED_MAXIMUM_NUMBER_UPDATE_RETRIES));
} else {
ProvisionLogger.loge("Cannot launch role holder updater.");
tryRescheduleRoleHolderUpdater();
}
}
void stopLaunchRetries() {
mHandler.removeCallbacks(mRunnable);
}
/**
* Tries to reschedule the role holder updater launch.
*/
private void tryRescheduleRoleHolderUpdater() {
if (canRetryLaunchRoleHolderUpdater(mNumberOfStartUpdaterTries)) {
scheduleRetryLaunchRoleHolderUpdater();
} else {
ProvisionLogger.loge("Exceeded maximum number of role holder updater launch retries.");
mObservableEvents.postValue(
new LaunchRoleHolderUpdaterFailureEvent(
REASON_EXCEEDED_MAXIMUM_NUMBER_UPDATER_LAUNCH_RETRIES));
}
}
private boolean canRetryUpdate(int numTries) {
return numTries < mConfig.getRoleHolderUpdateMaxRetries();
}
private boolean canRetryLaunchRoleHolderUpdater(int numTries) {
return numTries < mConfig.getLaunchRoleHolderMaxRetries();
}
private void launchRoleHolderUpdater(Intent intent) {
mObservableEvents.postValue(new LaunchRoleHolderUpdaterEvent(intent));
mNumberOfUpdateTries++;
}
private void scheduleRetryLaunchRoleHolderUpdater() {
mHandler.postDelayed(mRunnable, mConfig.getLaunchRoleHolderUpdaterPeriodMillis());
mNumberOfStartUpdaterTries++;
}
static class LaunchRoleHolderUpdaterEvent extends ViewModelEvent {
private final Intent mIntent;
LaunchRoleHolderUpdaterEvent(Intent intent) {
super(VIEW_MODEL_EVENT_LAUNCH_UPDATER);
mIntent = requireNonNull(intent);
}
Intent getIntent() {
return mIntent;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof LaunchRoleHolderUpdaterEvent)) return false;
LaunchRoleHolderUpdaterEvent that = (LaunchRoleHolderUpdaterEvent) o;
return Objects.equals(mIntent.getAction(), that.mIntent.getAction())
&& Objects.equals(mIntent.getExtras(), that.mIntent.getExtras());
}
@Override
public int hashCode() {
return Objects.hash(mIntent.getAction(), mIntent.getExtras());
}
@Override
public String toString() {
return "LaunchRoleHolderUpdaterEvent{"
+ "mIntent=" + mIntent + '}';
}
}
static class LaunchRoleHolderUpdaterFailureEvent extends ViewModelEvent {
static final int REASON_EXCEEDED_MAXIMUM_NUMBER_UPDATE_RETRIES = 1;
static final int REASON_EXCEEDED_MAXIMUM_NUMBER_UPDATER_LAUNCH_RETRIES = 2;
private final int mReason;
LaunchRoleHolderUpdaterFailureEvent(int reason) {
super(VIEW_MODEL_EVENT_LAUNCH_FAILURE);
mReason = reason;
}
int getReason() {
return mReason;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof LaunchRoleHolderUpdaterFailureEvent)) return false;
LaunchRoleHolderUpdaterFailureEvent that = (LaunchRoleHolderUpdaterFailureEvent) o;
return mReason == that.mReason;
}
@Override
public int hashCode() {
return Objects.hash(mReason);
}
@Override
public String toString() {
return "LaunchRoleHolderUpdaterFailureEvent{"
+ "mReason=" + mReason + '}';
}
}
static class RoleHolderUpdaterViewModelFactory implements ViewModelProvider.Factory {
private final Application mApplication;
private final Utils mUtils;
private final DeviceManagementRoleHolderUpdaterHelper mRoleHolderUpdaterHelper;
RoleHolderUpdaterViewModelFactory(
Application application,
Utils utils,
DeviceManagementRoleHolderUpdaterHelper roleHolderUpdaterHelper) {
mApplication = requireNonNull(application);
mRoleHolderUpdaterHelper = requireNonNull(roleHolderUpdaterHelper);
mUtils = requireNonNull(utils);
}
@Override
public <T extends ViewModel> T create(Class<T> aClass) {
DefaultConfig config = new DefaultConfig();
return (T) new RoleHolderUpdaterViewModel(
mApplication,
new Handler(Looper.getMainLooper()),
new DefaultCanLaunchRoleHolderUpdaterChecker(mUtils),
config,
mRoleHolderUpdaterHelper);
}
}
interface CanLaunchRoleHolderUpdaterChecker {
boolean canLaunchRoleHolderUpdater(Context context, Intent intent);
}
interface Config {
int getLaunchRoleHolderUpdaterPeriodMillis();
int getLaunchRoleHolderMaxRetries();
int getRoleHolderUpdateMaxRetries();
}
static class DefaultConfig implements Config {
@Override
public int getLaunchRoleHolderUpdaterPeriodMillis() {
return LAUNCH_ROLE_HOLDER_UPDATER_RETRY_PERIOD_MS;
}
@Override
public int getLaunchRoleHolderMaxRetries() {
return LAUNCH_ROLE_HOLDER_UPDATER_MAX_RETRIES;
}
@Override
public int getRoleHolderUpdateMaxRetries() {
return ROLE_HOLDER_UPDATE_MAX_RETRIES;
}
}
static class DefaultCanLaunchRoleHolderUpdaterChecker implements
CanLaunchRoleHolderUpdaterChecker {
private final Utils mUtils;
DefaultCanLaunchRoleHolderUpdaterChecker(Utils utils) {
mUtils = requireNonNull(utils);
}
@Override
public boolean canLaunchRoleHolderUpdater(Context context, Intent intent) {
return mUtils.canResolveIntentAsUser(context, intent, UserHandle.USER_SYSTEM);
}
}
}