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 
getInstance(Context context)73     public static synchronized EncryptionController getInstance(Context context) {
74         if (sInstance == null) {
75             sInstance = new EncryptionController(context);
76         }
77         return sInstance;
78     }
79 
EncryptionController(Context context)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
EncryptionController(Context context, Utils utils, SettingsFacade settingsFacade, ComponentName homeReceiver, ResumeNotificationHelper resumeNotificationHelper, int userId)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      */
setEncryptionReminder(ProvisioningParams params)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      */
cancelEncryptionReminder()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      */
resumeProvisioning()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
getProvisioningParamsFile(Context context)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 
ResumeNotificationHelper(Context context)198         public ResumeNotificationHelper(Context context) {
199             mContext = context;
200         }
201 
202         /** Create and show the provisioning reminder notification. */
showResumeNotification(Intent intent)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