Home | History | Annotate | Download | only in preprovisioning
      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