1 /*
2  * Copyright (C) 2018 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.server.devicepolicy;
18 
19 import android.annotation.Nullable;
20 import android.app.admin.DevicePolicyEventLogger;
21 import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback;
22 import android.app.admin.StartInstallingUpdateCallback;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.os.BatteryManager;
27 import android.os.Environment;
28 import android.os.FileUtils;
29 import android.os.ParcelFileDescriptor;
30 import android.os.PowerManager;
31 import android.os.Process;
32 import android.os.RemoteException;
33 import android.stats.devicepolicy.DevicePolicyEnums;
34 import android.util.Log;
35 
36 import java.io.File;
37 import java.io.FileOutputStream;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.OutputStream;
41 
42 abstract class UpdateInstaller {
43     private StartInstallingUpdateCallback mCallback;
44     private ParcelFileDescriptor mUpdateFileDescriptor;
45     private DevicePolicyConstants mConstants;
46     protected Context mContext;
47 
48     @Nullable protected File mCopiedUpdateFile;
49 
50     static final String TAG = "UpdateInstaller";
51     private DevicePolicyManagerService.Injector mInjector;
52 
UpdateInstaller(Context context, ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector, DevicePolicyConstants constants)53     protected UpdateInstaller(Context context, ParcelFileDescriptor updateFileDescriptor,
54             StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector,
55             DevicePolicyConstants constants) {
56         mContext = context;
57         mCallback = callback;
58         mUpdateFileDescriptor = updateFileDescriptor;
59         mInjector = injector;
60         mConstants = constants;
61     }
62 
installUpdateInThread()63     public abstract void installUpdateInThread();
64 
startInstallUpdate()65     public void startInstallUpdate() {
66         mCopiedUpdateFile = null;
67         if (!isBatteryLevelSufficient()) {
68             notifyCallbackOnError(
69                     InstallSystemUpdateCallback.UPDATE_ERROR_BATTERY_LOW,
70                     "The battery level must be above "
71                             + mConstants.BATTERY_THRESHOLD_NOT_CHARGING + " while not charging or"
72                             + "above " + mConstants.BATTERY_THRESHOLD_CHARGING + " while charging");
73             return;
74         }
75         Thread thread = new Thread(() -> {
76             mCopiedUpdateFile = copyUpdateFileToDataOtaPackageDir();
77             if (mCopiedUpdateFile == null) {
78                 notifyCallbackOnError(
79                         InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN,
80                         "Error while copying file.");
81                 return;
82             }
83             installUpdateInThread();
84         });
85         thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND);
86         thread.start();
87     }
88 
isBatteryLevelSufficient()89     private boolean isBatteryLevelSufficient() {
90         Intent batteryStatus = mContext.registerReceiver(
91                 /* receiver= */ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
92         float batteryPercentage = calculateBatteryPercentage(batteryStatus);
93         boolean isBatteryPluggedIn =
94                 batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, /* defaultValue= */ -1) > 0;
95         return isBatteryPluggedIn
96                 ? batteryPercentage >= mConstants.BATTERY_THRESHOLD_CHARGING
97                 : batteryPercentage >= mConstants.BATTERY_THRESHOLD_NOT_CHARGING;
98     }
99 
calculateBatteryPercentage(Intent batteryStatus)100     private float calculateBatteryPercentage(Intent batteryStatus) {
101         int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, /* defaultValue= */ -1);
102         int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, /* defaultValue= */ -1);
103         return 100 * level / (float) scale;
104     }
105 
copyUpdateFileToDataOtaPackageDir()106     private File copyUpdateFileToDataOtaPackageDir() {
107         try {
108             File destination = createNewFileWithPermissions();
109             copyToFile(destination);
110             return destination;
111         } catch (IOException e) {
112             Log.w(TAG, "Failed to copy update file to OTA directory", e);
113             notifyCallbackOnError(
114                     InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN,
115                     Log.getStackTraceString(e));
116             return null;
117         }
118     }
119 
createNewFileWithPermissions()120     private File createNewFileWithPermissions() throws IOException {
121         File destination = File.createTempFile(
122                 "update", ".zip", new File(Environment.getDataDirectory() + "/ota_package"));
123         FileUtils.setPermissions(
124                 /* path= */ destination,
125                 /* mode= */ FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IROTH,
126                 /* uid= */ -1, /* gid= */ -1);
127         return destination;
128     }
129 
copyToFile(File destination)130     private void copyToFile(File destination) throws IOException {
131         try (OutputStream out = new FileOutputStream(destination);
132              InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(
133                      mUpdateFileDescriptor)) {
134             FileUtils.copy(in, out);
135         }
136     }
137 
cleanupUpdateFile()138     void cleanupUpdateFile() {
139         if (mCopiedUpdateFile != null && mCopiedUpdateFile.exists()) {
140             mCopiedUpdateFile.delete();
141         }
142     }
143 
notifyCallbackOnError(int errorCode, String errorMessage)144     protected void notifyCallbackOnError(int errorCode, String errorMessage) {
145         cleanupUpdateFile();
146         DevicePolicyEventLogger
147                 .createEvent(DevicePolicyEnums.INSTALL_SYSTEM_UPDATE_ERROR)
148                 .setInt(errorCode)
149                 .write();
150         try {
151             mCallback.onStartInstallingUpdateError(errorCode, errorMessage);
152         } catch (RemoteException e) {
153             Log.d(TAG, "Error while calling callback", e);
154         }
155     }
156 
notifyCallbackOnSuccess()157     protected void notifyCallbackOnSuccess() {
158         cleanupUpdateFile();
159         mInjector.powerManagerReboot(PowerManager.REBOOT_REQUESTED_BY_DEVICE_OWNER);
160     }
161 }
162