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.uiflows; 18 19 import static com.android.internal.util.Preconditions.checkNotNull; 20 21 import android.app.Notification; 22 import android.app.NotificationManager; 23 import android.app.PendingIntent; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.os.Looper; 29 import android.os.UserHandle; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.managedprovisioning.IntentStore; 33 import com.android.managedprovisioning.ProvisionLogger; 34 import com.android.managedprovisioning.R; 35 import com.android.managedprovisioning.common.Utils; 36 import com.android.managedprovisioning.model.ProvisioningParams; 37 import com.android.managedprovisioning.parser.MessageParser; 38 39 /** 40 * This controller manages all things related to the encryption reboot. 41 * 42 * <p>An encryption reminder can be scheduled using {@link #setEncryptionReminder}. This will store 43 * the provisioning data to disk and enable a HOME intent receiver. After the reboot, the HOME 44 * intent receiver calls {@link #resumeProvisioning} at which point a new provisioning intent is 45 * sent. The reminder can be cancelled using {@link #cancelEncryptionReminder}. 46 */ 47 public class EncryptionController { 48 49 private static final String BOOT_REMINDER_INTENT_STORE_NAME = "boot-reminder"; 50 private static final int NOTIFICATION_ID = 1; 51 52 private final Context mContext; 53 private final IntentStore mIntentStore; 54 private final Utils mUtils; 55 private final MessageParser mMessageParser; 56 private final ComponentName mHomeReceiver; 57 private final ResumeNotificationHelper mResumeNotificationHelper; 58 private final int mUserId; 59 60 private boolean mProvisioningResumed = false; 61 62 private final PackageManager mPackageManager; 63 64 private static EncryptionController sInstance; 65 getInstance(Context context)66 public static synchronized EncryptionController getInstance(Context context) { 67 if (sInstance == null) { 68 sInstance = new EncryptionController(context); 69 } 70 return sInstance; 71 } 72 EncryptionController(Context context)73 private EncryptionController(Context context) { 74 this(context, 75 new IntentStore(context, BOOT_REMINDER_INTENT_STORE_NAME), 76 new Utils(), 77 new MessageParser(), 78 new ComponentName(context, PostEncryptionActivity.class), 79 new ResumeNotificationHelper(context), 80 UserHandle.myUserId()); 81 } 82 83 @VisibleForTesting EncryptionController(Context context, IntentStore intentStore, Utils utils, MessageParser messageParser, ComponentName homeReceiver, ResumeNotificationHelper resumeNotificationHelper, int userId)84 EncryptionController(Context context, IntentStore intentStore, Utils utils, 85 MessageParser messageParser, ComponentName homeReceiver, 86 ResumeNotificationHelper resumeNotificationHelper, int userId) { 87 mContext = checkNotNull(context, "Context must not be null").getApplicationContext(); 88 mIntentStore = checkNotNull(intentStore, "IntentStore must not be null"); 89 mUtils = checkNotNull(utils, "Utils must not be null"); 90 mMessageParser = checkNotNull(messageParser, "MessageParser must not be null"); 91 mHomeReceiver = checkNotNull(homeReceiver, "HomeReceiver must not be null"); 92 mResumeNotificationHelper = checkNotNull(resumeNotificationHelper, 93 "ResumeNotificationHelper must not be null"); 94 mUserId = userId; 95 96 mPackageManager = context.getPackageManager(); 97 } 98 99 /** 100 * Store a resume intent into the {@link IntentStore}. Provisioning will be resumed after reboot 101 * using the stored intent. 102 * 103 * @param resumeIntent the intent to be stored. 104 */ setEncryptionReminder(ProvisioningParams params)105 public void setEncryptionReminder(ProvisioningParams params) { 106 ProvisionLogger.logd("Setting provisioning reminder for action: " 107 + params.provisioningAction); 108 mIntentStore.save(mMessageParser.getIntentFromProvisioningParams(params)); 109 // Only enable the HOME intent receiver for flows inside SUW, as showing the notification 110 // for non-SUW flows is less time cricital. 111 if (!mUtils.isUserSetupCompleted(mContext)) { 112 ProvisionLogger.logd("Enabling PostEncryptionActivity"); 113 mUtils.enableComponent(mHomeReceiver, mUserId); 114 // To ensure that the enabled state has been persisted to disk, we flush the 115 // restrictions. 116 mPackageManager.flushPackageRestrictionsAsUser(mUserId); 117 } 118 } 119 120 /** 121 * Cancel the encryption reminder to avoid further resumption of encryption. 122 */ cancelEncryptionReminder()123 public void cancelEncryptionReminder() { 124 ProvisionLogger.logd("Cancelling provisioning reminder."); 125 mIntentStore.clear(); 126 mUtils.disableComponent(mHomeReceiver, mUserId); 127 } 128 129 /** 130 * Resume provisioning after encryption has happened. 131 * 132 * <p>If the device has already been set up, we show a notification to resume provisioning, 133 * otherwise we continue provisioning direclty. 134 * 135 * <p>Note that this method has to be called on the main thread. 136 */ resumeProvisioning()137 public void resumeProvisioning() { 138 // verify that this method was called on the main thread. 139 if (Looper.myLooper() != Looper.getMainLooper()) { 140 throw new IllegalStateException("resumeProvisioning must be called on the main thread"); 141 } 142 143 if (mProvisioningResumed) { 144 // If provisioning has already been resumed, don't resume it again. 145 // This can happen if the HOME intent receiver was launched multiple times or the 146 // BOOT_COMPLETED was received after the HOME intent receiver had already been launched. 147 return; 148 } 149 150 Intent resumeIntent = mIntentStore.load(); 151 152 if (resumeIntent != null) { 153 mProvisioningResumed = true; 154 String action = resumeIntent.getStringExtra(MessageParser.EXTRA_PROVISIONING_ACTION); 155 ProvisionLogger.logd("Provisioning resumed after encryption with action: " + action); 156 157 if (!mUtils.isPhysicalDeviceEncrypted()) { 158 ProvisionLogger.loge("Device is not encrypted after provisioning with" 159 + " action " + action + " but it should"); 160 return; 161 } 162 resumeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 163 164 if (mUtils.isProfileOwnerAction(action)) { 165 if (mUtils.isUserSetupCompleted(mContext)) { 166 mResumeNotificationHelper.showResumeNotification(resumeIntent); 167 } else { 168 mContext.startActivity(resumeIntent); 169 } 170 } else if (mUtils.isDeviceOwnerAction(action)) { 171 mContext.startActivity(resumeIntent); 172 } else { 173 ProvisionLogger.loge("Unknown intent action loaded from the intent store: " 174 + action); 175 } 176 } 177 } 178 isResumePending()179 public boolean isResumePending() { 180 return mIntentStore.load() != null; 181 } 182 183 @VisibleForTesting 184 public static class ResumeNotificationHelper { 185 private final Context mContext; 186 ResumeNotificationHelper(Context context)187 public ResumeNotificationHelper(Context context) { 188 mContext = context; 189 } 190 191 /** Create and show the provisioning reminder notification. */ showResumeNotification(Intent intent)192 public void showResumeNotification(Intent intent) { 193 NotificationManager notificationManager = (NotificationManager) 194 mContext.getSystemService(Context.NOTIFICATION_SERVICE); 195 final PendingIntent resumePendingIntent = PendingIntent.getActivity( 196 mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 197 final Notification.Builder notify = new Notification.Builder(mContext) 198 .setContentIntent(resumePendingIntent) 199 .setContentTitle(mContext 200 .getString(R.string.continue_provisioning_notify_title)) 201 .setContentText(mContext.getString(R.string.continue_provisioning_notify_text)) 202 .setSmallIcon(com.android.internal.R.drawable.ic_corp_statusbar_icon) 203 .setVisibility(Notification.VISIBILITY_PUBLIC) 204 .setColor(mContext.getResources().getColor( 205 com.android.internal.R.color.system_notification_accent_color)) 206 .setAutoCancel(true); 207 notificationManager.notify(NOTIFICATION_ID, notify.build()); 208 } 209 } 210 } 211