diff options
| -rw-r--r-- | Android.bp | 1 | ||||
| -rw-r--r-- | api/system-current.txt | 18 | ||||
| -rw-r--r-- | core/java/android/app/SystemServiceRegistry.java | 13 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 14 | ||||
| -rw-r--r-- | core/java/android/os/ISystemUpdateManager.aidl | 27 | ||||
| -rw-r--r-- | core/java/android/os/SystemUpdateManager.java | 152 | ||||
| -rw-r--r-- | core/res/AndroidManifest.xml | 5 | ||||
| -rw-r--r-- | services/core/java/com/android/server/SystemUpdateManagerService.java | 255 | ||||
| -rw-r--r-- | services/java/com/android/server/SystemServer.java | 9 |
9 files changed, 493 insertions, 1 deletions
diff --git a/Android.bp b/Android.bp index 641ce5fdff3c..d1332bb4fe87 100644 --- a/Android.bp +++ b/Android.bp @@ -230,6 +230,7 @@ java_library { "core/java/android/os/ISchedulingPolicyService.aidl", "core/java/android/os/IStatsCompanionService.aidl", "core/java/android/os/IStatsManager.aidl", + "core/java/android/os/ISystemUpdateManager.aidl", "core/java/android/os/IThermalEventListener.aidl", "core/java/android/os/IThermalService.aidl", "core/java/android/os/IUpdateLock.aidl", diff --git a/api/system-current.txt b/api/system-current.txt index dcea0044272f..a1ec2c44cd49 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -131,6 +131,7 @@ package android { field public static final java.lang.String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE"; field public static final java.lang.String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES"; field public static final java.lang.String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES"; + field public static final java.lang.String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO"; field public static final java.lang.String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL"; field public static final java.lang.String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL"; field public static final java.lang.String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS"; @@ -761,6 +762,7 @@ package android.content { field public static final java.lang.String OEM_LOCK_SERVICE = "oem_lock"; field public static final java.lang.String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block"; field public static final java.lang.String STATS_MANAGER = "stats"; + field public static final java.lang.String SYSTEM_UPDATE_SERVICE = "system_update"; field public static final java.lang.String VR_SERVICE = "vrmanager"; field public static final java.lang.String WIFI_RTT_SERVICE = "rttmanager"; field public static final java.lang.String WIFI_SCANNING_SERVICE = "wifiscanner"; @@ -3503,6 +3505,22 @@ package android.os { method public abstract void onResult(android.os.Bundle); } + public class SystemUpdateManager { + method public android.os.Bundle retrieveSystemUpdateInfo(); + method public void updateSystemUpdateInfo(android.os.PersistableBundle); + field public static final java.lang.String KEY_IS_SECURITY_UPDATE = "is_security_update"; + field public static final java.lang.String KEY_STATUS = "status"; + field public static final java.lang.String KEY_TARGET_BUILD_FINGERPRINT = "target_build_fingerprint"; + field public static final java.lang.String KEY_TARGET_SECURITY_PATCH_LEVEL = "target_security_patch_level"; + field public static final java.lang.String KEY_TITLE = "title"; + field public static final int STATUS_IDLE = 1; // 0x1 + field public static final int STATUS_IN_PROGRESS = 3; // 0x3 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + field public static final int STATUS_WAITING_DOWNLOAD = 2; // 0x2 + field public static final int STATUS_WAITING_INSTALL = 4; // 0x4 + field public static final int STATUS_WAITING_REBOOT = 5; // 0x5 + } + public class UpdateEngine { ctor public UpdateEngine(); method public void applyPayload(java.lang.String, long, long, java.lang.String[]); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 33277eae0520..fb8d1017e205 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -112,6 +112,7 @@ import android.os.IBinder; import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; +import android.os.ISystemUpdateManager; import android.os.IUserManager; import android.os.IncidentManager; import android.os.PowerManager; @@ -119,6 +120,7 @@ import android.os.Process; import android.os.RecoverySystem; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import android.os.SystemUpdateManager; import android.os.SystemVibrator; import android.os.UserHandle; import android.os.UserManager; @@ -485,6 +487,17 @@ final class SystemServiceRegistry { return new StorageStatsManager(ctx, service); }}); + registerService(Context.SYSTEM_UPDATE_SERVICE, SystemUpdateManager.class, + new CachedServiceFetcher<SystemUpdateManager>() { + @Override + public SystemUpdateManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow( + Context.SYSTEM_UPDATE_SERVICE); + ISystemUpdateManager service = ISystemUpdateManager.Stub.asInterface(b); + return new SystemUpdateManager(service); + }}); + registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class, new CachedServiceFetcher<TelephonyManager>() { @Override diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 265f7c7425db..f69aab01d821 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3024,7 +3024,8 @@ public abstract class Context { //@hide: INCIDENT_SERVICE, //@hide: STATS_COMPANION_SERVICE, COMPANION_DEVICE_SERVICE, - CROSS_PROFILE_APPS_SERVICE + CROSS_PROFILE_APPS_SERVICE, + //@hide: SYSTEM_UPDATE_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -3242,6 +3243,17 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.os.SystemUpdateManager} for accessing the system update + * manager service. + * + * @see #getSystemService(String) + * @hide + */ + @SystemApi + public static final String SYSTEM_UPDATE_SERVICE = "system_update"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a * {@link android.view.WindowManager} for accessing the system's window * manager. * diff --git a/core/java/android/os/ISystemUpdateManager.aidl b/core/java/android/os/ISystemUpdateManager.aidl new file mode 100644 index 000000000000..f7f50791f528 --- /dev/null +++ b/core/java/android/os/ISystemUpdateManager.aidl @@ -0,0 +1,27 @@ +/* //device/java/android/android/os/ISystemUpdateInfo.aidl +** +** 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.os; + +import android.os.Bundle; +import android.os.PersistableBundle; + +/** @hide */ +interface ISystemUpdateManager { + Bundle retrieveSystemUpdateInfo(); + void updateSystemUpdateInfo(in PersistableBundle data); +} diff --git a/core/java/android/os/SystemUpdateManager.java b/core/java/android/os/SystemUpdateManager.java new file mode 100644 index 000000000000..ce3e225975f0 --- /dev/null +++ b/core/java/android/os/SystemUpdateManager.java @@ -0,0 +1,152 @@ +/* + * 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 android.os; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; + +/** + * Allows querying and posting system update information. + * + * {@hide} + */ +@SystemApi +@SystemService(Context.SYSTEM_UPDATE_SERVICE) +public class SystemUpdateManager { + private static final String TAG = "SystemUpdateManager"; + + /** The status key of the system update info, expecting an int value. */ + @SystemApi + public static final String KEY_STATUS = "status"; + + /** The title of the current update, expecting a String value. */ + @SystemApi + public static final String KEY_TITLE = "title"; + + /** Whether it is a security update, expecting a boolean value. */ + @SystemApi + public static final String KEY_IS_SECURITY_UPDATE = "is_security_update"; + + /** The build fingerprint after installing the current update, expecting a String value. */ + @SystemApi + public static final String KEY_TARGET_BUILD_FINGERPRINT = "target_build_fingerprint"; + + /** The security patch level after installing the current update, expecting a String value. */ + @SystemApi + public static final String KEY_TARGET_SECURITY_PATCH_LEVEL = "target_security_patch_level"; + + /** + * The KEY_STATUS value that indicates there's no update status info available. + */ + @SystemApi + public static final int STATUS_UNKNOWN = 0; + + /** + * The KEY_STATUS value that indicates there's no pending update. + */ + @SystemApi + public static final int STATUS_IDLE = 1; + + /** + * The KEY_STATUS value that indicates an update is available for download, but pending user + * approval to start. + */ + @SystemApi + public static final int STATUS_WAITING_DOWNLOAD = 2; + + /** + * The KEY_STATUS value that indicates an update is in progress (i.e. downloading or installing + * has started). + */ + @SystemApi + public static final int STATUS_IN_PROGRESS = 3; + + /** + * The KEY_STATUS value that indicates an update is available for install. + */ + @SystemApi + public static final int STATUS_WAITING_INSTALL = 4; + + /** + * The KEY_STATUS value that indicates an update will be installed after a reboot. This applies + * to both of A/B and non-A/B OTAs. + */ + @SystemApi + public static final int STATUS_WAITING_REBOOT = 5; + + private final ISystemUpdateManager mService; + + /** @hide */ + public SystemUpdateManager(ISystemUpdateManager service) { + mService = checkNotNull(service, "missing ISystemUpdateManager"); + } + + /** + * Queries the current pending system update info. + * + * <p>Requires the {@link android.Manifest.permission#READ_SYSTEM_UPDATE_INFO} or + * {@link android.Manifest.permission#RECOVERY} permission. + * + * @return A {@code Bundle} that contains the pending system update information in key-value + * pairs. + * + * @throws SecurityException if the caller is not allowed to read the info. + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_SYSTEM_UPDATE_INFO, + android.Manifest.permission.RECOVERY, + }) + public Bundle retrieveSystemUpdateInfo() { + try { + return mService.retrieveSystemUpdateInfo(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Allows a system updater to publish the pending update info. + * + * <p>The reported info will not persist across reboots. Because only the reporting updater + * understands the criteria to determine a successful/failed update. + * + * <p>Requires the {@link android.Manifest.permission#RECOVERY} permission. + * + * @param infoBundle The {@code PersistableBundle} that contains the system update information, + * such as the current update status. {@link #KEY_STATUS} is required in the bundle. + * + * @throws IllegalArgumentException if @link #KEY_STATUS} does not exist. + * @throws SecurityException if the caller is not allowed to update the info. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.RECOVERY) + public void updateSystemUpdateInfo(PersistableBundle infoBundle) { + if (infoBundle == null || !infoBundle.containsKey(KEY_STATUS)) { + throw new IllegalArgumentException("Missing status in the bundle"); + } + try { + mService.updateSystemUpdateInfo(infoBundle); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ba30981e6377..bf9d79bc75dd 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2313,6 +2313,11 @@ <permission android:name="android.permission.RECOVERY" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to read system update info. + @hide --> + <permission android:name="android.permission.READ_SYSTEM_UPDATE_INFO" + android:protectionLevel="signature" /> + <!-- Allows the system to bind to an application's task services @hide --> <permission android:name="android.permission.BIND_JOB_SERVICE" diff --git a/services/core/java/com/android/server/SystemUpdateManagerService.java b/services/core/java/com/android/server/SystemUpdateManagerService.java new file mode 100644 index 000000000000..6c1ffdd32d18 --- /dev/null +++ b/services/core/java/com/android/server/SystemUpdateManagerService.java @@ -0,0 +1,255 @@ +/* + * 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; + +import static android.os.SystemUpdateManager.KEY_STATUS; +import static android.os.SystemUpdateManager.STATUS_IDLE; +import static android.os.SystemUpdateManager.STATUS_UNKNOWN; + +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import android.Manifest; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.ISystemUpdateManager; +import android.os.PersistableBundle; +import android.os.SystemUpdateManager; +import android.provider.Settings; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class SystemUpdateManagerService extends ISystemUpdateManager.Stub { + + private static final String TAG = "SystemUpdateManagerService"; + + private static final int UID_UNKNOWN = -1; + + private static final String INFO_FILE = "system-update-info.xml"; + private static final int INFO_FILE_VERSION = 0; + private static final String TAG_INFO = "info"; + private static final String KEY_VERSION = "version"; + private static final String KEY_UID = "uid"; + private static final String KEY_BOOT_COUNT = "boot-count"; + private static final String KEY_INFO_BUNDLE = "info-bundle"; + + private final Context mContext; + private final AtomicFile mFile; + private final Object mLock = new Object(); + private int mLastUid = UID_UNKNOWN; + private int mLastStatus = STATUS_UNKNOWN; + + public SystemUpdateManagerService(Context context) { + mContext = context; + mFile = new AtomicFile(new File(Environment.getDataSystemDirectory(), INFO_FILE)); + + // Populate mLastUid and mLastStatus. + synchronized (mLock) { + loadSystemUpdateInfoLocked(); + } + } + + @Override + public void updateSystemUpdateInfo(PersistableBundle infoBundle) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.RECOVERY, TAG); + + int status = infoBundle.getInt(KEY_STATUS, STATUS_UNKNOWN); + if (status == STATUS_UNKNOWN) { + Slog.w(TAG, "Invalid status info. Ignored"); + return; + } + + // There could be multiple updater apps running on a device. But only one at most should + // be active (i.e. with a pending update), with the rest reporting idle status. We will + // only accept the reported status if any of the following conditions holds: + // a) none has been reported before; + // b) the current on-file status was last reported by the same caller; + // c) an active update is being reported. + int uid = Binder.getCallingUid(); + if (mLastUid == UID_UNKNOWN || mLastUid == uid || status != STATUS_IDLE) { + synchronized (mLock) { + saveSystemUpdateInfoLocked(infoBundle, uid); + } + } else { + Slog.i(TAG, "Inactive updater reporting IDLE status. Ignored"); + } + } + + @Override + public Bundle retrieveSystemUpdateInfo() { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.READ_SYSTEM_UPDATE_INFO) + == PackageManager.PERMISSION_DENIED + && mContext.checkCallingOrSelfPermission(Manifest.permission.RECOVERY) + == PackageManager.PERMISSION_DENIED) { + throw new SecurityException("Can't read system update info. Requiring " + + "READ_SYSTEM_UPDATE_INFO or RECOVERY permission."); + } + + synchronized (mLock) { + return loadSystemUpdateInfoLocked(); + } + } + + // Reads and validates the info file. Returns the loaded info bundle on success; or a default + // info bundle with UNKNOWN status. + private Bundle loadSystemUpdateInfoLocked() { + PersistableBundle loadedBundle = null; + try (FileInputStream fis = mFile.openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, StandardCharsets.UTF_8.name()); + loadedBundle = readInfoFileLocked(parser); + } catch (FileNotFoundException e) { + Slog.i(TAG, "No existing info file " + mFile.getBaseFile()); + } catch (XmlPullParserException e) { + Slog.e(TAG, "Failed to parse the info file:", e); + } catch (IOException e) { + Slog.e(TAG, "Failed to read the info file:", e); + } + + // Validate the loaded bundle. + if (loadedBundle == null) { + return removeInfoFileAndGetDefaultInfoBundleLocked(); + } + + int version = loadedBundle.getInt(KEY_VERSION, -1); + if (version == -1) { + Slog.w(TAG, "Invalid info file (invalid version). Ignored"); + return removeInfoFileAndGetDefaultInfoBundleLocked(); + } + + int lastUid = loadedBundle.getInt(KEY_UID, -1); + if (lastUid == -1) { + Slog.w(TAG, "Invalid info file (invalid UID). Ignored"); + return removeInfoFileAndGetDefaultInfoBundleLocked(); + } + + int lastBootCount = loadedBundle.getInt(KEY_BOOT_COUNT, -1); + if (lastBootCount == -1 || lastBootCount != getBootCount()) { + Slog.w(TAG, "Outdated info file. Ignored"); + return removeInfoFileAndGetDefaultInfoBundleLocked(); + } + + PersistableBundle infoBundle = loadedBundle.getPersistableBundle(KEY_INFO_BUNDLE); + if (infoBundle == null) { + Slog.w(TAG, "Invalid info file (missing info). Ignored"); + return removeInfoFileAndGetDefaultInfoBundleLocked(); + } + + int lastStatus = infoBundle.getInt(KEY_STATUS, STATUS_UNKNOWN); + if (lastStatus == STATUS_UNKNOWN) { + Slog.w(TAG, "Invalid info file (invalid status). Ignored"); + return removeInfoFileAndGetDefaultInfoBundleLocked(); + } + + // Everything looks good upon reaching this point. + mLastStatus = lastStatus; + mLastUid = lastUid; + return new Bundle(infoBundle); + } + + private void saveSystemUpdateInfoLocked(PersistableBundle infoBundle, int uid) { + // Wrap the incoming bundle with extra info (e.g. version, uid, boot count). We use nested + // PersistableBundle to avoid manually parsing XML attributes when loading the info back. + PersistableBundle outBundle = new PersistableBundle(); + outBundle.putPersistableBundle(KEY_INFO_BUNDLE, infoBundle); + outBundle.putInt(KEY_VERSION, INFO_FILE_VERSION); + outBundle.putInt(KEY_UID, uid); + outBundle.putInt(KEY_BOOT_COUNT, getBootCount()); + + // Only update the info on success. + if (writeInfoFileLocked(outBundle)) { + mLastUid = uid; + mLastStatus = infoBundle.getInt(KEY_STATUS); + } + } + + // Performs I/O work only, without validating the loaded info. + @Nullable + private PersistableBundle readInfoFileLocked(XmlPullParser parser) + throws XmlPullParserException, IOException { + int type; + while ((type = parser.next()) != END_DOCUMENT) { + if (type == START_TAG && TAG_INFO.equals(parser.getName())) { + return PersistableBundle.restoreFromXml(parser); + } + } + return null; + } + + private boolean writeInfoFileLocked(PersistableBundle outBundle) { + FileOutputStream fos = null; + try { + fos = mFile.startWrite(); + + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + + out.startTag(null, TAG_INFO); + outBundle.saveToXml(out); + out.endTag(null, TAG_INFO); + + out.endDocument(); + mFile.finishWrite(fos); + return true; + } catch (IOException | XmlPullParserException e) { + Slog.e(TAG, "Failed to save the info file:", e); + if (fos != null) { + mFile.failWrite(fos); + } + } + return false; + } + + private Bundle removeInfoFileAndGetDefaultInfoBundleLocked() { + if (mFile.exists()) { + Slog.i(TAG, "Removing info file"); + mFile.delete(); + } + + mLastStatus = STATUS_UNKNOWN; + mLastUid = UID_UNKNOWN; + Bundle infoBundle = new Bundle(); + infoBundle.putInt(KEY_STATUS, STATUS_UNKNOWN); + return infoBundle; + } + + private int getBootCount() { + return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0); + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e660c50fcbc1..94a356e65878 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1172,6 +1172,15 @@ public final class SystemServer { } traceEnd(); + traceBeginAndSlog("StartSystemUpdateManagerService"); + try { + ServiceManager.addService(Context.SYSTEM_UPDATE_SERVICE, + new SystemUpdateManagerService(context)); + } catch (Throwable e) { + reportWtf("starting SystemUpdateManagerService", e); + } + traceEnd(); + traceBeginAndSlog("StartUpdateLockService"); try { ServiceManager.addService(Context.UPDATE_LOCK_SERVICE, |