Managed System Updates API

Adding API to install a system update from a file on the device.

Test: manual in TestDPC, CTS tests for negative cases: atest com.android.cts.devicepolicy.DeviceOwnerTest#testInstallUpdate

Fixes: 116511569

Change-Id: I34b5c6344301a9d2d64c98dedc4ed5e4a75c57d1
This commit is contained in:
Neda Topoljanac 2018-10-22 18:12:16 +01:00 committed by Rubin Xu
parent 0961c3e61f
commit 19f291660d
12 changed files with 670 additions and 3 deletions

View File

@ -92,6 +92,7 @@ java_defaults {
"core/java/android/app/IWallpaperManagerCallback.aidl",
"core/java/android/app/admin/IDeviceAdminService.aidl",
"core/java/android/app/admin/IDevicePolicyManager.aidl",
"core/java/android/app/admin/StartInstallingUpdateCallback.aidl",
"core/java/android/app/trust/IStrongAuthTracker.aidl",
"core/java/android/app/trust/ITrustManager.aidl",
"core/java/android/app/trust/ITrustListener.aidl",

View File

@ -6598,6 +6598,7 @@ package android.app.admin {
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, int);
method public void installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallUpdateCallback);
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isAffiliatedUser();
@ -6840,6 +6841,16 @@ package android.app.admin {
field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
}
public static abstract class DevicePolicyManager.InstallUpdateCallback {
ctor public DevicePolicyManager.InstallUpdateCallback();
method public void onInstallUpdateError(int, java.lang.String);
field public static final int UPDATE_ERROR_BATTERY_LOW = 5; // 0x5
field public static final int UPDATE_ERROR_FILE_NOT_FOUND = 4; // 0x4
field public static final int UPDATE_ERROR_INCORRECT_OS_VERSION = 2; // 0x2
field public static final int UPDATE_ERROR_UNKNOWN = 1; // 0x1
field public static final int UPDATE_ERROR_UPDATE_FILE_INVALID = 3; // 0x3
}
public static abstract interface DevicePolicyManager.OnClearApplicationUserDataListener {
method public abstract void onApplicationUserDataCleared(java.lang.String, boolean);
}

View File

@ -441,10 +441,10 @@ package android.app {
}
public class KeyguardManager {
method public void setPrivateNotificationsAllowed(boolean);
method public boolean getPrivateNotificationsAllowed();
method public android.content.Intent createConfirmFactoryResetCredentialIntent(java.lang.CharSequence, java.lang.CharSequence, java.lang.CharSequence);
method public boolean getPrivateNotificationsAllowed();
method public void requestDismissKeyguard(android.app.Activity, java.lang.CharSequence, android.app.KeyguardManager.KeyguardDismissCallback);
method public void setPrivateNotificationsAllowed(boolean);
}
public class Notification implements android.os.Parcelable {

View File

@ -53,6 +53,7 @@ import android.net.ProxyInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.Process;
@ -87,6 +88,7 @@ import com.android.internal.util.Preconditions;
import com.android.org.conscrypt.TrustedCertificateStore;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -1929,6 +1931,48 @@ public class DevicePolicyManager {
*/
public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3;
/**
* Callback used in {@link #installSystemUpdate} to indicate that there was an error while
* trying to install an update.
*/
public abstract static class InstallUpdateCallback {
/** Represents an unknown error while trying to install an update. */
public static final int UPDATE_ERROR_UNKNOWN = 1;
/** Represents the update file being intended for different OS version. */
public static final int UPDATE_ERROR_INCORRECT_OS_VERSION = 2;
/**
* Represents the update file being wrong, i.e. payloads are mismatched, wrong compressions
* method.
*/
public static final int UPDATE_ERROR_UPDATE_FILE_INVALID = 3;
/** Represents that the file could not be found. */
public static final int UPDATE_ERROR_FILE_NOT_FOUND = 4;
/** Represents the battery being too low to apply an update. */
public static final int UPDATE_ERROR_BATTERY_LOW = 5;
/** Method invoked when there was an error while installing an update. */
public void onInstallUpdateError(
@InstallUpdateCallbackErrorConstants int errorCode, String errorMessage) {
}
}
/**
* @hide
*/
@IntDef(prefix = { "UPDATE_ERROR_" }, value = {
InstallUpdateCallback.UPDATE_ERROR_UNKNOWN,
InstallUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION,
InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
InstallUpdateCallback.UPDATE_ERROR_FILE_NOT_FOUND,
InstallUpdateCallback.UPDATE_ERROR_BATTERY_LOW
})
@Retention(RetentionPolicy.SOURCE)
public @interface InstallUpdateCallbackErrorConstants {}
/**
* Return true if the given administrator component is currently active (enabled) in the system.
*
@ -6796,7 +6840,6 @@ public class DevicePolicyManager {
@Retention(RetentionPolicy.SOURCE)
public @interface CreateAndManageUserFlags {}
/**
* Called by a device owner to create a user with the specified name and a given component of
* the calling package as profile owner. The UserHandle returned by this method should not be
@ -9826,6 +9869,62 @@ public class DevicePolicyManager {
}
}
/**
* Called by device owner to install a system update from the given file. The device will be
* rebooted in order to finish installing the update. Note that if the device is rebooted, this
* doesn't necessarily mean that the update has been applied successfully. The caller should
* additionally check the system version with {@link android.os.Build#FINGERPRINT} or {@link
* android.os.Build.VERSION}. If an error occurs during processing the OTA before the reboot,
* the caller will be notified by {@link InstallUpdateCallback}. If device does not have
* sufficient battery level, the installation will fail with error {@link
* InstallUpdateCallback#UPDATE_ERROR_BATTERY_LOW}.
*
* @param admin The {@link DeviceAdminReceiver} that this request is associated with.
* @param updateFilePath An Uri of the file that contains the update. The file should be
* readable by the calling app.
* @param executor The executor through which the callback should be invoked.
* @param callback A callback object that will inform the caller when installing an update
* fails.
*/
public void installSystemUpdate(
@NonNull ComponentName admin, @NonNull Uri updateFilePath,
@NonNull @CallbackExecutor Executor executor,
@NonNull InstallUpdateCallback callback) {
throwIfParentInstance("installUpdate");
if (mService == null) {
return;
}
try (ParcelFileDescriptor fileDescriptor = mContext.getContentResolver()
.openFileDescriptor(updateFilePath, "r")) {
mService.installUpdateFromFile(
admin, fileDescriptor, new StartInstallingUpdateCallback.Stub() {
@Override
public void onStartInstallingUpdateError(
int errorCode, String errorMessage) {
executeCallback(errorCode, errorMessage, executor, callback);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (FileNotFoundException e) {
Log.w(TAG, e);
executeCallback(
InstallUpdateCallback.UPDATE_ERROR_FILE_NOT_FOUND, Log.getStackTraceString(e),
executor, callback);
} catch (IOException e) {
Log.w(TAG, e);
executeCallback(
InstallUpdateCallback.UPDATE_ERROR_UNKNOWN, Log.getStackTraceString(e),
executor, callback);
}
}
private void executeCallback(int errorCode, String errorMessage,
@NonNull @CallbackExecutor Executor executor,
@NonNull InstallUpdateCallback callback) {
executor.execute(() -> callback.onInstallUpdateError(errorCode, errorMessage));
}
/**
* Returns the system-wide Private DNS mode.
*

View File

@ -20,6 +20,7 @@ package android.app.admin;
import android.app.admin.NetworkEvent;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.admin.StartInstallingUpdateCallback;
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.PasswordMetrics;
@ -419,4 +420,6 @@ interface IDevicePolicyManager {
String getGlobalPrivateDnsHost(in ComponentName admin);
void grantDeviceIdsAccessToProfileOwner(in ComponentName who, int userId);
void installUpdateFromFile(in ComponentName admin, in ParcelFileDescriptor updateFileDescriptor, in StartInstallingUpdateCallback listener);
}

View File

@ -0,0 +1,27 @@
/*
**
** Copyright 2018, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package android.app.admin;
/**
* Callback used between {@link DevicePolicyManager} and {@link DevicePolicyManagerService} to
* indicate that starting installing an update is finished.
* {@hide}
*/
oneway interface StartInstallingUpdateCallback {
void onStartInstallingUpdateError(int errorCode, String errorMessage);
}

View File

@ -0,0 +1,268 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.devicepolicy;
import android.app.admin.DevicePolicyManager.InstallUpdateCallback;
import android.app.admin.StartInstallingUpdateCallback;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.os.UpdateEngine;
import android.os.UpdateEngineCallback;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
/**
* Used for installing an update on <a href="https://source.android.com/devices/tech/ota/ab">AB
* devices.</a>
* <p>This logic is specific to GOTA and should be modified by OEMs using a different AB update
* system.</p>
*/
class AbUpdateInstaller extends UpdateInstaller {
private static final String PAYLOAD_BIN = "payload.bin";
private static final String PAYLOAD_PROPERTIES_TXT = "payload_properties.txt";
//https://en.wikipedia.org/wiki/Zip_(file_format)#Local_file_header
private static final int OFFSET_TO_FILE_NAME = 30;
// kDownloadStateInitializationError constant from system/update_engine/common/error_code.h.
private static final int DOWNLOAD_STATE_INITIALIZATION_ERROR = 20;
private long mSizeForUpdate;
private long mOffsetForUpdate;
private List<String> mProperties;
private Enumeration<? extends ZipEntry> mEntries;
private ZipFile mPackedUpdateFile;
private static final Map<Integer, Integer> errorCodesMap = buildErrorCodesMap();
private static final Map<Integer, String> errorStringsMap = buildErrorStringsMap();
public static final String UNKNOWN_ERROR = "Unknown error with error code = ";
private boolean mUpdateInstalled;
private static Map<Integer, Integer> buildErrorCodesMap() {
Map<Integer, Integer> map = new HashMap<>();
map.put(UpdateEngine.ErrorCodeConstants.ERROR, InstallUpdateCallback.UPDATE_ERROR_UNKNOWN);
map.put(
DOWNLOAD_STATE_INITIALIZATION_ERROR,
InstallUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION);
// Error constants corresponding to errors related to bad update file.
map.put(
UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
map.put(
UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
map.put(
UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
map.put(
UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR,
InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
// Error constants corresponding to errors related to devices bad state.
map.put(
UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
InstallUpdateCallback.UPDATE_ERROR_UNKNOWN);
map.put(
UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
InstallUpdateCallback.UPDATE_ERROR_UNKNOWN);
map.put(
UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
InstallUpdateCallback.UPDATE_ERROR_UNKNOWN);
map.put(
UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
InstallUpdateCallback.UPDATE_ERROR_UNKNOWN);
return map;
}
private static Map<Integer, String> buildErrorStringsMap() {
Map<Integer, String> map = new HashMap<>();
map.put(UpdateEngine.ErrorCodeConstants.ERROR, UNKNOWN_ERROR);
map.put(
DOWNLOAD_STATE_INITIALIZATION_ERROR,
"The delta update payload was targeted for another version or the source partition"
+ "was modified after it was installed");
map.put(
UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
"Failed to finish the configured postinstall works.");
map.put(
UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
"Failed to open one of the partitions it tried to write to or read data from.");
map.put(
UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
"Payload mismatch error.");
map.put(
UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
"Failed to read the payload data from the given URL.");
map.put(
UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR, "Payload hash error.");
map.put(
UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
"Payload size mismatch error.");
map.put(
UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
"Failed to verify the signature of the payload.");
map.put(
UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
"The payload has been successfully installed,"
+ "but the active slot was not flipped.");
return map;
}
AbUpdateInstaller(Context context, ParcelFileDescriptor updateFileDescriptor,
StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector,
DevicePolicyConstants constants) {
super(context, updateFileDescriptor, callback, injector, constants);
mUpdateInstalled = false;
}
@Override
public void installUpdateInThread() {
if (mUpdateInstalled) {
throw new IllegalStateException("installUpdateInThread can be called only once.");
}
try {
setState();
applyPayload(Paths.get(mCopiedUpdateFile.getAbsolutePath()).toUri().toString());
} catch (ZipException e) {
Log.w(UpdateInstaller.TAG, e);
notifyCallbackOnError(
InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
Log.getStackTraceString(e));
} catch (IOException e) {
Log.w(UpdateInstaller.TAG, e);
notifyCallbackOnError(
InstallUpdateCallback.UPDATE_ERROR_UNKNOWN, Log.getStackTraceString(e));
}
}
private void setState() throws IOException {
mUpdateInstalled = true;
mPackedUpdateFile = new ZipFile(mCopiedUpdateFile);
mProperties = new ArrayList<>();
mSizeForUpdate = -1;
mOffsetForUpdate = 0;
mEntries = mPackedUpdateFile.entries();
}
private UpdateEngine buildBoundUpdateEngine() {
UpdateEngine updateEngine = new UpdateEngine();
updateEngine.bind(new DelegatingUpdateEngineCallback(this, updateEngine));
return updateEngine;
}
private void applyPayload(String updatePath) throws IOException {
if (!updateStateForPayload()) {
return;
}
String[] headerKeyValuePairs = mProperties.stream().toArray(String[]::new);
if (mSizeForUpdate == -1) {
Log.w(UpdateInstaller.TAG, "Failed to find payload entry in the given package.");
notifyCallbackOnError(
InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
"Failed to find payload entry in the given package.");
return;
}
UpdateEngine updateEngine = buildBoundUpdateEngine();
updateEngine.applyPayload(
updatePath, mOffsetForUpdate, mSizeForUpdate, headerKeyValuePairs);
}
private boolean updateStateForPayload() throws IOException {
long offset = 0;
while (mEntries.hasMoreElements()) {
ZipEntry entry = mEntries.nextElement();
String name = entry.getName();
offset += buildOffsetForEntry(entry, name);
if (entry.isDirectory()) {
offset -= entry.getCompressedSize();
continue;
}
if (PAYLOAD_BIN.equals(name)) {
if (entry.getMethod() != ZipEntry.STORED) {
Log.w(UpdateInstaller.TAG, "Invalid compression method.");
notifyCallbackOnError(
InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
"Invalid compression method.");
return false;
}
mSizeForUpdate = entry.getCompressedSize();
mOffsetForUpdate = offset - entry.getCompressedSize();
} else if (PAYLOAD_PROPERTIES_TXT.equals(name)) {
updatePropertiesForEntry(entry);
}
}
return true;
}
private long buildOffsetForEntry(ZipEntry entry, String name) {
return OFFSET_TO_FILE_NAME + name.length() + entry.getCompressedSize()
+ (entry.getExtra() == null ? 0 : entry.getExtra().length);
}
private void updatePropertiesForEntry(ZipEntry entry) throws IOException {
try (BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(mPackedUpdateFile.getInputStream(entry)))) {
String line;
/* Neither @line nor @mProperties are size constraint since there is a few properties
with limited size. */
while ((line = bufferedReader.readLine()) != null) {
mProperties.add(line);
}
}
}
private static class DelegatingUpdateEngineCallback extends UpdateEngineCallback {
private UpdateInstaller mUpdateInstaller;
private UpdateEngine mUpdateEngine;
DelegatingUpdateEngineCallback(
UpdateInstaller updateInstaller, UpdateEngine updateEngine) {
mUpdateInstaller = updateInstaller;
mUpdateEngine = updateEngine;
}
@Override
public void onStatusUpdate(int statusCode, float percentage) {
return;
}
@Override
public void onPayloadApplicationComplete(int errorCode) {
mUpdateEngine.unbind();
if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS) {
mUpdateInstaller.notifyCallbackOnSuccess();
} else {
mUpdateInstaller.notifyCallbackOnError(
errorCodesMap.getOrDefault(
errorCode, InstallUpdateCallback.UPDATE_ERROR_UNKNOWN),
errorStringsMap.getOrDefault(errorCode, UNKNOWN_ERROR + errorCode));
}
}
}
}

View File

@ -17,7 +17,9 @@ package com.android.server.devicepolicy;
import android.app.admin.DevicePolicyManager;
import android.app.admin.IDevicePolicyManager;
import android.app.admin.StartInstallingUpdateCallback;
import android.content.ComponentName;
import android.os.ParcelFileDescriptor;
import com.android.server.SystemService;
@ -91,4 +93,8 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
@Override
public void grantDeviceIdsAccessToProfileOwner(ComponentName who, int userId) { }
@Override
public void installUpdateFromFile(ComponentName admin,
ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {}
}

View File

@ -42,6 +42,12 @@ public class DevicePolicyConstants {
private static final String DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY =
"das_died_service_stable_connection_threshold_sec";
private static final String BATTERY_THRESHOLD_NOT_CHARGING_KEY =
"battery_threshold_not_charging";
private static final String BATTERY_THRESHOLD_CHARGING_KEY =
"battery_threshold_charging";
/**
* The back-off before re-connecting, when a service binding died, due to the owner
* crashing repeatedly.
@ -63,6 +69,17 @@ public class DevicePolicyConstants {
*/
public final long DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC;
/**
* Battery threshold for installing system update while the device is not charging.
*/
public final int BATTERY_THRESHOLD_NOT_CHARGING;
/**
* Battery threshold for installing system update while the device is charging.
*/
public final int BATTERY_THRESHOLD_CHARGING;
private DevicePolicyConstants(String settings) {
final KeyValueListParser parser = new KeyValueListParser(',');
@ -87,6 +104,12 @@ public class DevicePolicyConstants {
DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY,
TimeUnit.MINUTES.toSeconds(2));
int batteryThresholdNotCharging = parser.getInt(
BATTERY_THRESHOLD_NOT_CHARGING_KEY, 40);
int batteryThresholdCharging = parser.getInt(
BATTERY_THRESHOLD_CHARGING_KEY, 20);
// Set minimum: 5 seconds.
dasDiedServiceReconnectBackoffSec = Math.max(5, dasDiedServiceReconnectBackoffSec);
@ -103,6 +126,8 @@ public class DevicePolicyConstants {
DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC = dasDiedServiceReconnectMaxBackoffSec;
DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC =
dasDiedServiceStableConnectionThresholdSec;
BATTERY_THRESHOLD_NOT_CHARGING = batteryThresholdNotCharging;
BATTERY_THRESHOLD_CHARGING = batteryThresholdCharging;
}
public static DevicePolicyConstants loadFromString(String settings) {

View File

@ -113,6 +113,7 @@ import android.app.admin.NetworkEvent;
import android.app.admin.PasswordMetrics;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
import android.app.admin.StartInstallingUpdateCallback;
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
import android.app.backup.IBackupManager;
@ -379,6 +380,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
private static final Set<String> GLOBAL_SETTINGS_DEPRECATED;
private static final Set<String> SYSTEM_SETTINGS_WHITELIST;
private static final Set<Integer> DA_DISALLOWED_POLICIES;
private static final String AB_DEVICE_KEY = "ro.build.ab_update";
static {
SECURE_SETTINGS_WHITELIST = new ArraySet<>();
SECURE_SETTINGS_WHITELIST.add(Settings.Secure.DEFAULT_INPUT_METHOD);
@ -13315,4 +13318,30 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return mInjector.settingsGlobalGetString(PRIVATE_DNS_SPECIFIER);
}
@Override
public void installUpdateFromFile(ComponentName admin,
ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback) {
enforceDeviceOwner(admin);
final long id = mInjector.binderClearCallingIdentity();
try {
UpdateInstaller updateInstaller;
if (isDeviceAB()) {
updateInstaller = new AbUpdateInstaller(
mContext, updateFileDescriptor, callback, mInjector, mConstants);
} else {
updateInstaller = new NonAbUpdateInstaller(
mContext, updateFileDescriptor, callback, mInjector, mConstants);
}
updateInstaller.startInstallUpdate();
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
}
private boolean isDeviceAB() {
return "true".equalsIgnoreCase(android.os.SystemProperties
.get(AB_DEVICE_KEY, ""));
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.devicepolicy;
import android.app.admin.DevicePolicyManager;
import android.app.admin.StartInstallingUpdateCallback;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.os.RecoverySystem;
import android.util.Log;
import java.io.IOException;
/**
* Used for installing an update for <a href="https://source.android.com/devices/tech/ota/nonab">non
* AB</a> devices.
*/
class NonAbUpdateInstaller extends UpdateInstaller {
NonAbUpdateInstaller(Context context,
ParcelFileDescriptor updateFileDescriptor,
StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector,
DevicePolicyConstants constants) {
super(context, updateFileDescriptor, callback, injector, constants);
}
@Override
public void installUpdateInThread() {
try {
RecoverySystem.installPackage(mContext, mCopiedUpdateFile);
notifyCallbackOnSuccess();
} catch (IOException e) {
Log.w(TAG, "IO error while trying to install non AB update.", e);
notifyCallbackOnError(
DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UNKNOWN,
Log.getStackTraceString(e));
}
}
}

View File

@ -0,0 +1,146 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.devicepolicy;
import android.app.admin.DevicePolicyManager;
import android.app.admin.StartInstallingUpdateCallback;
import android.content.Context;
import android.os.BatteryManager;
import android.os.Environment;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
abstract class UpdateInstaller {
private StartInstallingUpdateCallback mCallback;
private ParcelFileDescriptor mUpdateFileDescriptor;
private DevicePolicyConstants mConstants;
protected Context mContext;
protected File mCopiedUpdateFile;
static final String TAG = "UpdateInstaller";
private DevicePolicyManagerService.Injector mInjector;
protected UpdateInstaller(Context context, ParcelFileDescriptor updateFileDescriptor,
StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector,
DevicePolicyConstants constants) {
mContext = context;
mCallback = callback;
mUpdateFileDescriptor = updateFileDescriptor;
mInjector = injector;
mConstants = constants;
}
public abstract void installUpdateInThread();
public void startInstallUpdate() {
if (!checkIfBatteryIsSufficient()) {
notifyCallbackOnError(
DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_BATTERY_LOW,
"The battery level must be above "
+ mConstants.BATTERY_THRESHOLD_NOT_CHARGING + " while not charging or"
+ "above " + mConstants.BATTERY_THRESHOLD_CHARGING + " while charging");
return;
}
Thread thread = new Thread(() -> {
mCopiedUpdateFile = copyUpdateFileToDataOtaPackageDir();
if (mCopiedUpdateFile == null) {
notifyCallbackOnError(
DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UNKNOWN,
"Error while copying file.");
return;
}
installUpdateInThread();
});
thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
}
private boolean checkIfBatteryIsSufficient() {
BatteryManager batteryManager =
(BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE);
if (batteryManager != null) {
int chargePercentage = batteryManager
.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
return batteryManager.isCharging()
? chargePercentage >= mConstants.BATTERY_THRESHOLD_CHARGING
: chargePercentage >= mConstants.BATTERY_THRESHOLD_NOT_CHARGING;
}
return false;
}
private File copyUpdateFileToDataOtaPackageDir() {
try {
File destination = createNewFileWithPermissions();
copyToFile(destination);
return destination;
} catch (IOException e) {
Log.w(TAG, "Failed to copy update file to OTA directory", e);
notifyCallbackOnError(
DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UNKNOWN,
Log.getStackTraceString(e));
return null;
}
}
private File createNewFileWithPermissions() throws IOException {
File destination = File.createTempFile(
"update", ".zip", new File(Environment.getDataDirectory() + "/ota_package"));
FileUtils.setPermissions(
/* path= */ destination,
/* mode= */ FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IROTH,
/* uid= */ -1, /* gid= */ -1);
return destination;
}
private void copyToFile(File destination) throws IOException {
try (OutputStream out = new FileOutputStream(destination);
InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(
mUpdateFileDescriptor)) {
FileUtils.copy(in, out);
}
}
void cleanupUpdateFile() {
if (mCopiedUpdateFile.exists()) {
mCopiedUpdateFile.delete();
}
}
protected void notifyCallbackOnError(int errorCode, String errorMessage) {
cleanupUpdateFile();
try {
mCallback.onStartInstallingUpdateError(errorCode, errorMessage);
} catch (RemoteException e) {
Log.d(TAG, "Error while calling callback", e);
}
}
protected void notifyCallbackOnSuccess() {
cleanupUpdateFile();
mInjector.powerManagerReboot(PowerManager.REBOOT_REQUESTED_BY_DEVICE_OWNER);
}
}