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 66 public static synchronized EncryptionController getInstance(Context context) { 67 if (sInstance == null) { 68 sInstance = new EncryptionController(context); 69 } 70 return sInstance; 71 } 72 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 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 */ 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 */ 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 */ 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 179 public boolean isResumePending() { 180 return mIntentStore.load() != null; 181 } 182 183 @VisibleForTesting 184 public static class ResumeNotificationHelper { 185 private final Context mContext; 186 187 public ResumeNotificationHelper(Context context) { 188 mContext = context; 189 } 190 191 /** Create and show the provisioning reminder notification. */ 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