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 java.util.Objects.requireNonNull; 20 21 import android.app.Activity; 22 import android.content.Intent; 23 import android.os.Bundle; 24 import android.os.Parcelable; 25 26 import androidx.annotation.Nullable; 27 import androidx.lifecycle.ViewModelProvider; 28 29 import com.android.managedprovisioning.R; 30 import com.android.managedprovisioning.common.RetryLaunchViewModel.Config; 31 import com.android.managedprovisioning.common.RetryLaunchViewModel.LaunchActivityEvent; 32 import com.android.managedprovisioning.common.RetryLaunchViewModel.RetryLaunchViewModelFactory; 33 34 /** 35 * An {@link Activity} which tries to start the {@link #EXTRA_INTENT_TO_LAUNCH} intent 36 * {@link #EXTRA_MAX_RETRIES} times every {@link #EXTRA_RETRY_PERIOD_MS} milliseconds. 37 * 38 * <p>This {@link Activity} is meant to be used in cases where there is a possibility the {@link 39 * #EXTRA_INTENT_TO_LAUNCH} intent may not be available the first time, for example if the app 40 * that resolves the {@link Intent} is getting updated. 41 * 42 * <p>This {@link Activity} forwards the result code of the {@link Activity} that was resolved from 43 * the {@link #EXTRA_INTENT_TO_LAUNCH}. Upon failure to launch the {@link Intent}, this {@link 44 * Activity} returns {@link #RESULT_CANCELED}. 45 */ 46 public final class RetryLaunchActivity extends SetupGlifLayoutActivity { 47 48 /** 49 * A {@link Parcelable} extra describing the {@link Intent} to be launched. 50 * 51 * <p>This is a required extra. Not supplying it will throw an {@link IllegalStateException}. 52 */ 53 public static final String EXTRA_INTENT_TO_LAUNCH = 54 "com.android.managedprovisioning.extra.INTENT_TO_LAUNCH"; 55 56 /** 57 * An {@code int} extra which determines how many times to retry the activity relaunch. 58 * 59 * <p>Must be a non-negative number. 60 * 61 * <p>Default value is {@code 3}. 62 */ 63 public static final String EXTRA_MAX_RETRIES = 64 "com.android.managedprovisioning.extra.MAX_RETRIES"; 65 66 /** 67 * A {@code long} extra which determines how long to wait between each retry, in milliseconds. 68 * 69 * <p>Must be a non-negative number. 70 * 71 * <p>Default value is {@code 60000} (one minute). 72 */ 73 public static final String EXTRA_RETRY_PERIOD_MS = 74 "com.android.managedprovisioning.extra.RETRY_PERIOD_MS"; 75 76 private static final int DEFAULT_MAX_RETRIES = 3; 77 78 private static final long DEFAULT_RETRY_PERIOD_MS = 60_000L; 79 80 private static final int LAUNCH_ACTIVITY_REQUEST_CODE = 1; 81 82 private RetryLaunchViewModel mViewModel; 83 84 @Override onCreate(Bundle savedInstanceState)85 protected void onCreate(Bundle savedInstanceState) { 86 super.onCreate(savedInstanceState); 87 if (!getIntent().hasExtra(EXTRA_INTENT_TO_LAUNCH)) { 88 throw new IllegalStateException("EXTRA_INTENT must be supplied."); 89 } 90 Intent activityIntent = getIntent().getParcelableExtra(EXTRA_INTENT_TO_LAUNCH); 91 requireNonNull(activityIntent); 92 93 Config config = createConfigFromIntent(getIntent()); 94 mViewModel = createViewModelWithIntent(activityIntent, config); 95 setupViewModelObservation(); 96 97 if (savedInstanceState == null) { 98 mViewModel.tryStartActivity(); 99 } 100 } 101 createConfigFromIntent(Intent intent)102 private Config createConfigFromIntent(Intent intent) { 103 final int maxRetries = getMaxRetries(intent); 104 final long retryPeriodMs = getRetryPeriodMs(intent); 105 return new Config() { 106 @Override 107 public long getLaunchActivityRetryMillis() { 108 return retryPeriodMs; 109 } 110 111 @Override 112 public int getLaunchActivityMaxRetries() { 113 return maxRetries; 114 } 115 }; 116 } 117 118 private int getMaxRetries(Intent intent) { 119 int maxRetries = intent.getIntExtra(EXTRA_MAX_RETRIES, DEFAULT_MAX_RETRIES); 120 if (maxRetries < 0) { 121 ProvisionLogger.loge("Invalid value passed for " + EXTRA_MAX_RETRIES + ". Expected a " 122 + "non-negative value but got " + maxRetries); 123 maxRetries = DEFAULT_MAX_RETRIES; 124 } 125 return maxRetries; 126 } 127 128 private long getRetryPeriodMs(Intent intent) { 129 long retryPeriodMs = intent.getLongExtra(EXTRA_RETRY_PERIOD_MS, DEFAULT_RETRY_PERIOD_MS); 130 if (retryPeriodMs < 0) { 131 ProvisionLogger.loge("Invalid value passed for " + EXTRA_RETRY_PERIOD_MS + ". Expected " 132 + "a non-negative value but got " + retryPeriodMs); 133 retryPeriodMs = DEFAULT_RETRY_PERIOD_MS; 134 } 135 return retryPeriodMs; 136 } 137 138 private void setupViewModelObservation() { 139 mViewModel.observeViewModelEvents().observe(this, viewModelEvent -> { 140 switch (viewModelEvent.getType()) { 141 case RetryLaunchViewModel.VIEW_MODEL_EVENT_LAUNCH_ACTIVITY: 142 if (!mViewModel.isWaitingForActivityResult()) { 143 launchActivity(((LaunchActivityEvent) viewModelEvent).getIntent()); 144 mViewModel.markWaitingForActivityResult(); 145 } 146 break; 147 case RetryLaunchViewModel.VIEW_MODEL_EVENT_WAITING_FOR_RETRY: 148 initializeUi(); 149 break; 150 case RetryLaunchViewModel.VIEW_MODEL_EVENT_LAUNCH_FAILURE: 151 finishWithCancelResult(); 152 break; 153 } 154 }); 155 } 156 157 private RetryLaunchViewModel createViewModelWithIntent(Intent activityIntent, Config config) { 158 return new ViewModelProvider(this, 159 new RetryLaunchViewModelFactory( 160 getApplication(), 161 activityIntent, 162 config, 163 mUtils)) 164 .get(RetryLaunchViewModel.class); 165 } 166 167 @Override 168 protected void onStop() { 169 super.onStop(); 170 mViewModel.stopLaunchRetries(); 171 } 172 173 @Override 174 protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 175 if (requestCode != LAUNCH_ACTIVITY_REQUEST_CODE) { 176 super.onActivityResult(requestCode, resultCode, data); 177 return; 178 } 179 ProvisionLogger.logi("Retry launcher activity result code: " + resultCode); 180 finishWithResult(resultCode, data); 181 } 182 183 private void initializeUi() { 184 int headerResId = R.string.just_a_sec; 185 int titleResId = R.string.just_a_sec; 186 initializeLayoutParams(R.layout.empty_loading_layout, headerResId); 187 setTitle(titleResId); 188 } 189 190 private void launchActivity(Intent intent) { 191 getTransitionHelper().startActivityForResultWithTransition( 192 this, 193 intent, 194 LAUNCH_ACTIVITY_REQUEST_CODE); 195 } 196 197 private void finishWithResult(int resultCode, Intent data) { 198 setResult(resultCode, data); 199 getTransitionHelper().finishActivity(this); 200 } 201 202 private void finishWithCancelResult() { 203 finishWithResult(RESULT_CANCELED, /*data=*/ null); 204 } 205 } 206