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