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