1 /* 2 * Copyright 2016, 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.provisioning; 18 19 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE; 20 21 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_PROVISIONING_ACTIVITY_TIME_MS; 22 import static com.android.internal.util.Preconditions.checkNotNull; 23 24 import static java.util.Objects.requireNonNull; 25 26 import android.Manifest.permission; 27 import android.annotation.IntDef; 28 import android.app.Activity; 29 import android.app.admin.DevicePolicyManager; 30 import android.content.ComponentName; 31 import android.content.Intent; 32 import android.content.pm.PackageManager; 33 import android.content.pm.ResolveInfo; 34 import android.os.Bundle; 35 import android.os.UserHandle; 36 import android.view.ViewGroup; 37 38 import androidx.annotation.VisibleForTesting; 39 40 import com.android.managedprovisioning.ManagedProvisioningScreens; 41 import com.android.managedprovisioning.R; 42 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences; 43 import com.android.managedprovisioning.common.PolicyComplianceUtils; 44 import com.android.managedprovisioning.common.ProvisionLogger; 45 import com.android.managedprovisioning.common.SettingsFacade; 46 import com.android.managedprovisioning.common.ThemeHelper; 47 import com.android.managedprovisioning.common.ThemeHelper.DefaultNightModeChecker; 48 import com.android.managedprovisioning.common.ThemeHelper.DefaultSetupWizardBridge; 49 import com.android.managedprovisioning.common.Utils; 50 import com.android.managedprovisioning.finalization.PreFinalizationController; 51 import com.android.managedprovisioning.finalization.UserProvisioningStateHelper; 52 import com.android.managedprovisioning.model.ProvisioningParams; 53 import com.android.managedprovisioning.provisioning.TransitionAnimationHelper.TransitionAnimationCallback; 54 import com.android.managedprovisioning.provisioning.TransitionAnimationHelper.TransitionAnimationStateManager; 55 56 import com.airbnb.lottie.LottieAnimationView; 57 import com.google.android.setupcompat.util.WizardManagerHelper; 58 import com.google.android.setupdesign.util.Partner; 59 60 import java.lang.annotation.Retention; 61 import java.lang.annotation.RetentionPolicy; 62 import java.util.Collections; 63 import java.util.HashMap; 64 import java.util.List; 65 import java.util.Map; 66 67 /** 68 * Progress activity shown whilst provisioning is ongoing. 69 * 70 * <p>This activity registers for updates of the provisioning process from the 71 * {@link ProvisioningManager}. It shows progress updates as provisioning progresses and handles 72 * showing of cancel and error dialogs.</p> 73 */ 74 public class ProvisioningActivity extends AbstractProvisioningActivity 75 implements TransitionAnimationCallback, TransitionAnimationStateManager { 76 private static final int RESULT_CODE_COMPLETE_DEVICE_FINANCE = 121; 77 /* 78 * Returned after the work profile has been completed. Note this is before launching the DPC. 79 */ 80 @VisibleForTesting 81 static final int RESULT_CODE_WORK_PROFILE_CREATED = 122; 82 /* 83 * Returned after the device owner has been set. Note this is before launching the DPC. 84 */ 85 @VisibleForTesting 86 static final int RESULT_CODE_DEVICE_OWNER_SET = 123; 87 88 static final int PROVISIONING_MODE_WORK_PROFILE = 1; 89 static final int PROVISIONING_MODE_FULLY_MANAGED_DEVICE = 2; 90 static final int PROVISIONING_MODE_WORK_PROFILE_ON_FULLY_MANAGED_DEVICE = 3; 91 static final int PROVISIONING_MODE_FINANCED_DEVICE = 4; 92 static final int PROVISIONING_MODE_WORK_PROFILE_ON_ORG_OWNED_DEVICE = 5; 93 private ViewGroup mButtonFooterContainer; 94 95 @IntDef(prefix = { "PROVISIONING_MODE_" }, value = { 96 PROVISIONING_MODE_WORK_PROFILE, 97 PROVISIONING_MODE_FULLY_MANAGED_DEVICE, 98 PROVISIONING_MODE_WORK_PROFILE_ON_FULLY_MANAGED_DEVICE, 99 PROVISIONING_MODE_FINANCED_DEVICE, 100 PROVISIONING_MODE_WORK_PROFILE_ON_ORG_OWNED_DEVICE 101 }) 102 @Retention(RetentionPolicy.SOURCE) 103 @interface ProvisioningMode {} 104 105 private static final Map<Integer, Integer> PROVISIONING_MODE_TO_PROGRESS_LABEL = 106 Collections.unmodifiableMap(new HashMap<Integer, Integer>() {{ 107 put(PROVISIONING_MODE_WORK_PROFILE, 108 R.string.work_profile_provisioning_progress_label); 109 put(PROVISIONING_MODE_FULLY_MANAGED_DEVICE, 110 R.string.fully_managed_device_provisioning_progress_label); 111 put(PROVISIONING_MODE_WORK_PROFILE_ON_FULLY_MANAGED_DEVICE, 112 R.string.fully_managed_device_provisioning_progress_label); 113 put(PROVISIONING_MODE_FINANCED_DEVICE, R.string.just_a_sec); 114 put(PROVISIONING_MODE_WORK_PROFILE_ON_ORG_OWNED_DEVICE, 115 R.string.work_profile_provisioning_progress_label); 116 }}); 117 118 private UserProvisioningStateHelper mUserProvisioningStateHelper; 119 private PolicyComplianceUtils mPolicyComplianceUtils; 120 private ProvisioningManager mProvisioningManager; 121 private ProvisioningActivityBridge mBridge; 122 ProvisioningActivity()123 public ProvisioningActivity() { 124 this( 125 /* provisioningManager */ null, // defined in getProvisioningManager() 126 new Utils(), 127 /* userProvisioningStateHelper */ null, // defined in onCreate() 128 new PolicyComplianceUtils(), 129 new SettingsFacade(), 130 new ThemeHelper(new DefaultNightModeChecker(), new DefaultSetupWizardBridge())); 131 } 132 133 @VisibleForTesting ProvisioningActivity(ProvisioningManager provisioningManager, Utils utils, UserProvisioningStateHelper userProvisioningStateHelper, PolicyComplianceUtils policyComplianceUtils, SettingsFacade settingsFacade, ThemeHelper themeHelper)134 public ProvisioningActivity(ProvisioningManager provisioningManager, 135 Utils utils, 136 UserProvisioningStateHelper userProvisioningStateHelper, 137 PolicyComplianceUtils policyComplianceUtils, 138 SettingsFacade settingsFacade, 139 ThemeHelper themeHelper) { 140 super(utils, settingsFacade, themeHelper); 141 mProvisioningManager = provisioningManager; 142 mUserProvisioningStateHelper = userProvisioningStateHelper; 143 mPolicyComplianceUtils = checkNotNull(policyComplianceUtils); 144 } 145 146 @Override onCreate(Bundle savedInstanceState)147 protected void onCreate(Bundle savedInstanceState) { 148 super.onCreate(savedInstanceState); 149 mBridge = createBridge(); 150 mBridge.initiateUi(/* activity= */ this); 151 152 // assign this Activity as the view store owner to access saved state and receive updates 153 getProvisioningManager().setViewModelStoreOwner(this); 154 155 if (mUserProvisioningStateHelper == null) { 156 mUserProvisioningStateHelper = new UserProvisioningStateHelper(this); 157 } 158 159 if (mState == STATE_PROVISIONING_FINALIZED) { 160 updateProvisioningFinalizedScreen(); 161 } 162 163 writeSharedPreferences(); 164 } 165 writeSharedPreferences()166 private void writeSharedPreferences() { 167 ManagedProvisioningSharedPreferences sharedPreferences = 168 new ManagedProvisioningSharedPreferences(this); 169 sharedPreferences.writeNavigationBarColor(getWindow().getNavigationBarColor()); 170 sharedPreferences.writeNavigationBarDividerColor( 171 getWindow().getNavigationBarDividerColor()); 172 sharedPreferences.writeTextPrimaryColor(mUtils.getTextPrimaryColor(this)); 173 sharedPreferences.writeTextSecondaryColor(mUtils.getTextSecondaryColor(this)); 174 sharedPreferences.writeBackgroundColor(mUtils.getBackgroundColor(this)); 175 sharedPreferences.writeAccentColor(mUtils.getAccentColor(this)); 176 sharedPreferences.writeNotificationBackgroundColor( 177 Partner.getColor(this, R.color.setup_notification_bg_color)); 178 } 179 createBridge()180 protected ProvisioningActivityBridge createBridge() { 181 return ProvisioningActivityBridgeImpl.builder() 182 .setParams(mParams) 183 .setUtils(mUtils) 184 .setProvisioningMode(getProvisioningMode()) 185 .setProvisioningManager(getProvisioningManager()) 186 .setTransitionAnimationCallback(this) 187 .setInitializeLayoutParamsConsumer( 188 ProvisioningActivity.this::initializeLayoutParams) 189 .setShouldSkipEducationScreens(shouldSkipEducationScreens()) 190 .setProgressLabelResId(getProgressLabelResId()) 191 .setBridgeCallbacks(createCallbacks()) 192 .setStateManager(this) 193 .build(); 194 } 195 getProgressLabelResId()196 protected Integer getProgressLabelResId() { 197 return PROVISIONING_MODE_TO_PROGRESS_LABEL.get(getProvisioningMode()); 198 } 199 createCallbacks()200 protected final ProvisioningActivityBridgeCallbacks createCallbacks() { 201 return new ProvisioningActivityBridgeCallbacks() { 202 @Override 203 public void onNextButtonClicked() { 204 ProvisioningActivity.this.onNextButtonClicked(); 205 } 206 207 @Override 208 public void onAbortButtonClicked() { 209 ProvisioningActivity.this.onAbortButtonClicked(); 210 } 211 212 @Override 213 public boolean isProvisioningFinalized() { 214 return mState == STATE_PROVISIONING_FINALIZED; 215 } 216 }; 217 } 218 219 @Override 220 protected ProvisioningManager getProvisioningManager() { 221 if (mProvisioningManager == null) { 222 mProvisioningManager = ProvisioningManager.getInstance(this); 223 } 224 return mProvisioningManager; 225 } 226 227 @VisibleForTesting 228 protected void setProvisioningManager(ProvisioningManager provisioningManager) { 229 mProvisioningManager = requireNonNull(provisioningManager); 230 } 231 232 @Override 233 public void preFinalizationCompleted() { 234 if (mState == STATE_PROVISIONING_COMPLETED || mState == STATE_PROVISIONING_FINALIZED) { 235 return; 236 } 237 238 if (!validatePolicyComplianceExists()) { 239 ProvisionLogger.loge("POLICY_COMPLIANCE handler not implemented by the admin app."); 240 error(R.string.cant_set_up_device, 241 R.string.contact_your_admin_for_help, 242 /* resetRequired */ mParams.isOrganizationOwnedProvisioning); 243 return; 244 } 245 246 ProvisionLogger.logi("ProvisioningActivity pre-finalization completed"); 247 248 // TODO(183094412): Decouple state from AbstractProvisioningActivity 249 mState = STATE_PROVISIONING_COMPLETED; 250 251 if (shouldSkipEducationScreens() 252 || mBridge.shouldShowButtonsWhenPreProvisioningCompletes()) { 253 updateProvisioningFinalizedScreen(); 254 } 255 } 256 257 // Enforces DPCs to implement the POLICY_COMPLIANCE handler for NFC and financed device 258 // provisioning, since we no longer set up the DPC on setup wizard's exit procedure. 259 // No need to verify it for the other flows, as that was already done earlier. 260 // TODO(b/177849035): Remove NFC and financed device-specific logic 261 private boolean validatePolicyComplianceExists() { 262 if (!mParams.isNfc && !mUtils.isFinancedDeviceAction(mParams.provisioningAction)) { 263 return true; 264 } 265 return mPolicyComplianceUtils.isPolicyComplianceActivityResolvableForUser( 266 this, mParams, mUtils, UserHandle.SYSTEM); 267 } 268 269 protected final void updateProvisioningFinalizedScreen() { 270 mBridge.onProvisioningFinalized(/* activity= */ this); 271 272 // TODO(183094412): Decouple state from AbstractProvisioningActivity 273 mState = STATE_PROVISIONING_FINALIZED; 274 } 275 276 @VisibleForTesting 277 protected void onNextButtonClicked() { 278 markDeviceManagementEstablishedAndFinish(); 279 } 280 281 @VisibleForTesting 282 protected void onAbortButtonClicked() { 283 final Intent intent = new Intent(this, 284 getActivityForScreen(ManagedProvisioningScreens.RESET_AND_RETURN_DEVICE)); 285 WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent); 286 intent.putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, mParams); 287 getTransitionHelper().startActivityWithTransition(this, intent); 288 } 289 290 private void finishActivity() { 291 if (mParams.provisioningAction.equals(ACTION_PROVISION_FINANCED_DEVICE)) { 292 setResult(RESULT_CODE_COMPLETE_DEVICE_FINANCE); 293 } else { 294 setResult(Activity.RESULT_OK); 295 } 296 maybeLaunchNfcUserSetupCompleteIntent(); 297 getTransitionHelper().finishActivity(this); 298 } 299 300 private void markDeviceManagementEstablishedAndFinish() { 301 new PreFinalizationController(this, mUserProvisioningStateHelper) 302 .deviceManagementEstablished(mParams); 303 if (mParams.flowType == ProvisioningParams.FLOW_TYPE_ADMIN_INTEGRATED) { 304 if (mUtils.isProfileOwnerAction(mParams.provisioningAction)) { 305 setResult(RESULT_CODE_WORK_PROFILE_CREATED); 306 } else if (mUtils.isDeviceOwnerAction(mParams.provisioningAction)) { 307 setResult(RESULT_CODE_DEVICE_OWNER_SET); 308 } else if (mUtils.isFinancedDeviceAction(mParams.provisioningAction)) { 309 setResult(RESULT_CODE_COMPLETE_DEVICE_FINANCE); 310 } 311 getTransitionHelper().finishActivity(this); 312 } else { 313 finishActivity(); 314 } 315 } 316 317 private void maybeLaunchNfcUserSetupCompleteIntent() { 318 if (mParams != null && mParams.isNfc) { 319 // Start SetupWizard to complete the intent. 320 final Intent intent = new Intent(DevicePolicyManager.ACTION_STATE_USER_SETUP_COMPLETE) 321 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 322 final PackageManager pm = getPackageManager(); 323 List<ResolveInfo> ris = pm.queryIntentActivities(intent, 0); 324 325 // Look for the first legitimate component protected by the permission 326 ComponentName targetComponent = null; 327 for (ResolveInfo ri : ris) { 328 if (ri.activityInfo == null) { 329 continue; 330 } 331 if (!permission.BIND_DEVICE_ADMIN.equals(ri.activityInfo.permission)) { 332 ProvisionLogger.loge("Component " + ri.activityInfo.getComponentName() 333 + " is not protected by " + permission.BIND_DEVICE_ADMIN); 334 } else if (pm.checkPermission(permission.DISPATCH_PROVISIONING_MESSAGE, 335 ri.activityInfo.packageName) != PackageManager.PERMISSION_GRANTED) { 336 ProvisionLogger.loge("Package " + ri.activityInfo.packageName 337 + " does not have " + permission.DISPATCH_PROVISIONING_MESSAGE); 338 } else { 339 targetComponent = ri.activityInfo.getComponentName(); 340 break; 341 } 342 } 343 344 if (targetComponent == null) { 345 ProvisionLogger.logw("No activity accepts intent ACTION_STATE_USER_SETUP_COMPLETE"); 346 return; 347 } 348 349 intent.setComponent(targetComponent); 350 getTransitionHelper().startActivityWithTransition(this, intent); 351 ProvisionLogger.logi("Launched ACTION_STATE_USER_SETUP_COMPLETE with component " 352 + targetComponent); 353 } 354 } 355 356 @Override 357 protected int getMetricsCategory() { 358 return PROVISIONING_PROVISIONING_ACTIVITY_TIME_MS; 359 } 360 361 @Override 362 protected void decideCancelProvisioningDialog() { 363 if ((mState == STATE_PROVISIONING_COMPLETED || mState == STATE_PROVISIONING_FINALIZED) 364 && !mParams.isOrganizationOwnedProvisioning) { 365 return; 366 } 367 368 if (getUtils().isDeviceOwnerAction(mParams.provisioningAction) 369 || mParams.isOrganizationOwnedProvisioning) { 370 showCancelProvisioningDialog(/* resetRequired = */true); 371 } else { 372 showCancelProvisioningDialog(/* resetRequired = */false); 373 } 374 } 375 376 @Override 377 protected void onStart() { 378 super.onStart(); 379 mBridge.onStart(this); 380 } 381 382 @Override 383 protected void onStop() { 384 super.onStop(); 385 mBridge.onStop(); 386 // remove this Activity as the view store owner to avoid memory leaks 387 if (isFinishing()) { 388 getProvisioningManager().clearViewModelStoreOwner(); 389 } 390 } 391 392 @Override 393 public void onAllTransitionsShown() { 394 if (mState == STATE_PROVISIONING_COMPLETED) { 395 updateProvisioningFinalizedScreen(); 396 } 397 } 398 399 @Override 400 public void onAnimationSetup(LottieAnimationView animationView) { 401 getThemeHelper().setupAnimationDynamicColors(this, animationView, getIntent()); 402 } 403 404 @Override 405 public void saveState(TransitionAnimationHelper.TransitionAnimationState state) { 406 getProvisioningManager().saveTransitionAnimationState(state); 407 } 408 409 @Override 410 public TransitionAnimationHelper.TransitionAnimationState restoreState() { 411 return getProvisioningManager().restoreTransitionAnimationState(); 412 } 413 414 @Override 415 protected boolean isWaitingScreen() { 416 return shouldSkipEducationScreens(); 417 } 418 419 protected @ProvisioningMode int getProvisioningMode() { 420 int provisioningMode = 0; 421 final boolean isProfileOwnerAction = 422 mUtils.isProfileOwnerAction(mParams.provisioningAction); 423 if (isProfileOwnerAction) { 424 if (getSystemService(DevicePolicyManager.class).isDeviceManaged()) { 425 provisioningMode = PROVISIONING_MODE_WORK_PROFILE_ON_FULLY_MANAGED_DEVICE; 426 } else if (mParams.isOrganizationOwnedProvisioning) { 427 provisioningMode = PROVISIONING_MODE_WORK_PROFILE_ON_ORG_OWNED_DEVICE; 428 } else { 429 provisioningMode = PROVISIONING_MODE_WORK_PROFILE; 430 } 431 } else if (mUtils.isDeviceOwnerAction(mParams.provisioningAction)) { 432 provisioningMode = PROVISIONING_MODE_FULLY_MANAGED_DEVICE; 433 } else if (mUtils.isFinancedDeviceAction(mParams.provisioningAction)) { 434 provisioningMode = PROVISIONING_MODE_FINANCED_DEVICE; 435 } 436 return provisioningMode; 437 } 438 439 protected boolean shouldSkipEducationScreens() { 440 return mParams.skipEducationScreens 441 || getProvisioningMode() == PROVISIONING_MODE_WORK_PROFILE_ON_FULLY_MANAGED_DEVICE 442 || getProvisioningMode() == PROVISIONING_MODE_FINANCED_DEVICE; 443 } 444 } 445