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.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.os.Looper;
26 import android.os.UserHandle;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.managedprovisioning.common.Globals;
30 import com.android.managedprovisioning.common.NotificationHelper;
31 import com.android.managedprovisioning.common.ProvisionLogger;
32 import com.android.managedprovisioning.common.SettingsFacade;
33 import com.android.managedprovisioning.common.Utils;
34 import com.android.managedprovisioning.model.ProvisioningParams;
35 
36 import java.io.File;
37 
38 /**
39  * This controller manages all things related to the encryption reboot.
40  *
41  * <p>An encryption reminder can be scheduled using {@link #setEncryptionReminder}. This will store
42  * the provisioning data to disk and enable a HOME intent receiver. After the reboot, the HOME
43  * intent receiver calls {@link #resumeProvisioning} at which point a new provisioning intent is
44  * sent. The reminder can be cancelled using {@link #cancelEncryptionReminder}.
45  */
46 public class EncryptionController {
47     private final Context mContext;
48     private final Utils mUtils;
49     private final SettingsFacade mSettingsFacade;
50     private final ComponentName mHomeReceiver;
51     private final NotificationHelper mNotificationHelper;
52     private final int mUserId;
53 
54     private boolean mProvisioningResumed = false;
55 
56     private final PackageManager mPackageManager;
57 
58     private static EncryptionController sInstance;
59 
60     private static final String PROVISIONING_PARAMS_FILE_NAME
61             = "encryption_controller_provisioning_params.xml";
62 
getInstance(Context context)63     public static synchronized EncryptionController getInstance(Context context) {
64         if (sInstance == null) {
65             sInstance = new EncryptionController(context);
66         }
67         return sInstance;
68     }
69 
EncryptionController(Context context)70     private EncryptionController(Context context) {
71         this(context,
72                 new Utils(),
73                 new SettingsFacade(),
74                 new ComponentName(context, PostEncryptionActivity.class),
75                 new NotificationHelper(context),
76                 UserHandle.myUserId());
77     }
78 
79     @VisibleForTesting
EncryptionController(Context context, Utils utils, SettingsFacade settingsFacade, ComponentName homeReceiver, NotificationHelper resumeNotificationHelper, int userId)80     EncryptionController(Context context,
81             Utils utils,
82             SettingsFacade settingsFacade,
83             ComponentName homeReceiver,
84             NotificationHelper resumeNotificationHelper,
85             int userId) {
86         mContext = checkNotNull(context, "Context must not be null").getApplicationContext();
87         mSettingsFacade = checkNotNull(settingsFacade);
88         mUtils = checkNotNull(utils, "Utils must not be null");
89         mHomeReceiver = checkNotNull(homeReceiver, "HomeReceiver must not be null");
90         mNotificationHelper = checkNotNull(resumeNotificationHelper,
91                 "ResumeNotificationHelper must not be null");
92         mUserId = userId;
93 
94         mPackageManager = context.getPackageManager();
95     }
96 
97     /**
98      * Store a resume intent into persistent storage. Provisioning will be resumed after reboot
99      * using the stored intent.
100      *
101      * @param params the params to be stored.
102      */
setEncryptionReminder(ProvisioningParams params)103     public void setEncryptionReminder(ProvisioningParams params) {
104         ProvisionLogger.logd("Setting provisioning reminder for action: "
105                 + params.provisioningAction);
106         params.save(getProvisioningParamsFile(mContext));
107         // Only enable the HOME intent receiver for flows inside SUW, as showing the notification
108         // for non-SUW flows is less time cricital.
109         if (!mSettingsFacade.isUserSetupCompleted(mContext)) {
110             ProvisionLogger.logd("Enabling PostEncryptionActivity");
111             mUtils.enableComponent(mHomeReceiver, mUserId);
112             // To ensure that the enabled state has been persisted to disk, we flush the
113             // restrictions.
114             mPackageManager.flushPackageRestrictionsAsUser(mUserId);
115         }
116     }
117 
118     /**
119      * Cancel the encryption reminder to avoid further resumption of encryption.
120      */
cancelEncryptionReminder()121     public void cancelEncryptionReminder() {
122         ProvisionLogger.logd("Cancelling provisioning reminder.");
123         getProvisioningParamsFile(mContext).delete();
124         mUtils.disableComponent(mHomeReceiver, mUserId);
125     }
126 
127     /**
128      * Resume provisioning after encryption has happened.
129      *
130      * <p>If the device has already been set up, we show a notification to resume provisioning,
131      * otherwise we continue provisioning direclty.
132      *
133      * <p>Note that this method has to be called on the main thread.
134      */
resumeProvisioning()135     public void resumeProvisioning() {
136         // verify that this method was called on the main thread.
137         if (Looper.myLooper() != Looper.getMainLooper()) {
138             throw new IllegalStateException("resumeProvisioning must be called on the main thread");
139         }
140 
141         if (mProvisioningResumed) {
142             // If provisioning has already been resumed, don't resume it again.
143             // This can happen if the HOME intent receiver was launched multiple times or the
144             // BOOT_COMPLETED was received after the HOME intent receiver had already been launched.
145             return;
146         }
147 
148         ProvisioningParams params = ProvisioningParams.load(getProvisioningParamsFile(mContext));
149 
150         if (params != null) {
151             Intent resumeIntent = new Intent(Globals.ACTION_RESUME_PROVISIONING);
152             resumeIntent.putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, params);
153             mProvisioningResumed = true;
154             String action = params.provisioningAction;
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 (mSettingsFacade.isUserSetupCompleted(mContext)) {
166                     mNotificationHelper.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     @VisibleForTesting
getProvisioningParamsFile(Context context)180     File getProvisioningParamsFile(Context context) {
181         return new File(context.getFilesDir(), PROVISIONING_PARAMS_FILE_NAME);
182     }
183 }
184