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:
parent
0961c3e61f
commit
19f291660d
@ -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",
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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, ""));
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user