diff --git a/Android.mk b/Android.mk index 79f6220ca16c2..5572c504194f1 100644 --- a/Android.mk +++ b/Android.mk @@ -127,6 +127,7 @@ LOCAL_SRC_FILES += \ core/java/android/os/IPermissionController.aidl \ core/java/android/os/IPowerManager.aidl \ core/java/android/os/IRemoteCallback.aidl \ + core/java/android/os/IUpdateLock.aidl \ core/java/android/os/IVibratorService.aidl \ core/java/android/service/wallpaper/IWallpaperConnection.aidl \ core/java/android/service/wallpaper/IWallpaperEngine.aidl \ diff --git a/api/current.txt b/api/current.txt index 8af75dbac2855..5babf232f7095 100644 --- a/api/current.txt +++ b/api/current.txt @@ -15343,6 +15343,7 @@ package android.os { method public abstract void acquired(); method public void cleanup(android.os.IBinder, boolean); method public void dump(); + method public void dump(java.io.PrintWriter); method public boolean isAcquired(); method public void release(android.os.IBinder); method public abstract void released(); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index a1198dedef13b..111f45e2358f4 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1644,6 +1644,17 @@ public abstract class Context { */ public static final String THROTTLE_SERVICE = "throttle"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.os.IUpdateLock} for managing runtime sequences that + * must not be interrupted by headless OTA application or similar. + * + * @hide + * @see #getSystemService + * @see android.os.UpdateLock + */ + public static final String UPDATE_LOCK_SERVICE = "updatelock"; + /** * Use with {@link #getSystemService} to retrieve a {@link * android.net.NetworkManagementService} for handling management of diff --git a/core/java/android/os/IUpdateLock.aidl b/core/java/android/os/IUpdateLock.aidl new file mode 100644 index 0000000000000..4492fb8b0099d --- /dev/null +++ b/core/java/android/os/IUpdateLock.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2012 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; + +/** + * Direct interface to the UpdateLockService's functionality + * + * {@hide} + */ +interface IUpdateLock { + void acquireUpdateLock(IBinder token, String tag); + void releaseUpdateLock(IBinder token); +} diff --git a/core/java/android/os/TokenWatcher.java b/core/java/android/os/TokenWatcher.java index ac3cc92cfcac7..9b3a2d689693d 100755 --- a/core/java/android/os/TokenWatcher.java +++ b/core/java/android/os/TokenWatcher.java @@ -16,6 +16,8 @@ package android.os; +import java.io.PrintWriter; +import java.util.ArrayList; import java.util.WeakHashMap; import java.util.Set; import android.util.Log; @@ -115,15 +117,31 @@ public abstract class TokenWatcher public void dump() { + ArrayList a = dumpInternal(); + for (String s : a) { + Log.i(mTag, s); + } + } + + public void dump(PrintWriter pw) { + ArrayList a = dumpInternal(); + for (String s : a) { + pw.println(s); + } + } + + private ArrayList dumpInternal() { + ArrayList a = new ArrayList(); synchronized (mTokens) { Set keys = mTokens.keySet(); - Log.i(mTag, "Token count: " + mTokens.size()); + a.add("Token count: " + mTokens.size()); int i = 0; for (IBinder b: keys) { - Log.i(mTag, "[" + i + "] " + mTokens.get(b).tag + " - " + b); + a.add("[" + i + "] " + mTokens.get(b).tag + " - " + b); i++; } } + return a; } private Runnable mNotificationTask = new Runnable() { diff --git a/core/java/android/os/UpdateLock.java b/core/java/android/os/UpdateLock.java new file mode 100644 index 0000000000000..40603268fa261 --- /dev/null +++ b/core/java/android/os/UpdateLock.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2012 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.content.Context; +import android.util.Log; + +/** + * Advisory wakelock-like mechanism by which processes that should not be interrupted for + * OTA/update purposes can so advise the OS. This is particularly relevant for headless + * or kiosk-like operation. + * + * @hide + */ +public class UpdateLock { + private static final boolean DEBUG = false; + private static final String TAG = "UpdateLock"; + + private static IUpdateLock sService; + private static void checkService() { + if (sService == null) { + sService = IUpdateLock.Stub.asInterface( + ServiceManager.getService(Context.UPDATE_LOCK_SERVICE)); + } + } + + IBinder mToken; + int mCount = 0; + boolean mRefCounted = true; + boolean mHeld = false; + final String mTag; + + /** + * Broadcast Intent action sent when the global update lock state changes, + * i.e. when the first locker acquires an update lock, or when the last + * locker releases theirs. The broadcast is sticky but is sent only to + * registered receivers. + */ + public static final String UPDATE_LOCK_CHANGED = "android.os.UpdateLock.UPDATE_LOCK_CHANGED"; + + /** + * Boolean Intent extra on the UPDATE_LOCK_CHANGED sticky broadcast, indicating + * whether now is an appropriate time to interrupt device activity with an + * update operation. True means that updates are okay right now; false indicates + * that perhaps later would be a better time. + */ + public static final String NOW_IS_CONVENIENT = "nowisconvenient"; + + /** + * Long Intent extra on the UPDATE_LOCK_CHANGED sticky broadcast, marking the + * wall-clock time [in UTC] at which the broadcast was sent. Note that this is + * in the System.currentTimeMillis() time base, which may be non-monotonic especially + * around reboots. + */ + public static final String TIMESTAMP = "timestamp"; + + /** + * Construct an UpdateLock instance. + * @param tag An arbitrary string used to identify this lock instance in dump output. + */ + public UpdateLock(String tag) { + mTag = tag; + mToken = new Binder(); + } + + /** + * Change the refcount behavior of this update lock. + */ + public void setReferenceCounted(boolean isRefCounted) { + if (DEBUG) { + Log.v(TAG, "setting refcounted=" + isRefCounted + " : " + this); + } + mRefCounted = isRefCounted; + } + + /** + * Is this lock currently held? + */ + public boolean isHeld() { + synchronized (mToken) { + return mHeld; + } + } + + /** + * Acquire an update lock. + */ + public void acquire() { + if (DEBUG) { + Log.v(TAG, "acquire() : " + this, new RuntimeException("here")); + } + checkService(); + synchronized (mToken) { + acquireLocked(); + } + } + + private void acquireLocked() { + if (!mRefCounted || mCount++ == 0) { + if (sService != null) { + try { + sService.acquireUpdateLock(mToken, mTag); + } catch (RemoteException e) { + Log.e(TAG, "Unable to contact service to acquire"); + } + } + mHeld = true; + } + } + + /** + * Release this update lock. + */ + public void release() { + if (DEBUG) Log.v(TAG, "release() : " + this, new RuntimeException("here")); + checkService(); + synchronized (mToken) { + releaseLocked(); + } + } + + private void releaseLocked() { + if (!mRefCounted || --mCount == 0) { + if (sService != null) { + try { + sService.releaseUpdateLock(mToken); + } catch (RemoteException e) { + Log.e(TAG, "Unable to contact service to release"); + } + } + mHeld = false; + } + if (mCount < 0) { + throw new RuntimeException("UpdateLock under-locked"); + } + } + + @Override + protected void finalize() throws Throwable { + synchronized (mToken) { + // if mHeld is true, sService must be non-null + if (mHeld) { + Log.wtf(TAG, "UpdateLock finalized while still held"); + try { + sService.releaseUpdateLock(mToken); + } catch (RemoteException e) { + Log.e(TAG, "Unable to contact service to release"); + } + } + } + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d4d29ae0badd3..17d2212de4ede 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -121,6 +121,7 @@ + @@ -1538,6 +1539,13 @@ android:label="@string/permlab_accessContentProvidersExternally" android:description="@string/permdesc_accessContentProvidersExternally" android:protectionLevel="signature" /> + + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index f548165cb7e71..c8df649605d07 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2263,6 +2263,10 @@ content providers from outside an ApplicationThread. [CHAR LIMIT=NONE] --> Allows the holder to access content providers from the shell. Should never be needed for normal apps. + + discourage automatic device updates + + Allows the holder to offer information to the system about when would be a good time for a noninteractive reboot to upgrade the device. Do you want the browser to remember this password? diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index c9b59975c32f8..423dad623bbb8 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -402,6 +402,14 @@ class ServerThread extends Thread { reportWtf("starting ThrottleService", e); } + try { + Slog.i(TAG, "UpdateLock Service"); + ServiceManager.addService(Context.UPDATE_LOCK_SERVICE, + new UpdateLockService(context)); + } catch (Throwable e) { + reportWtf("starting UpdateLockService", e); + } + if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) { try { /* diff --git a/services/java/com/android/server/UpdateLockService.java b/services/java/com/android/server/UpdateLockService.java new file mode 100644 index 0000000000000..5df19281f2584 --- /dev/null +++ b/services/java/com/android/server/UpdateLockService.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 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 android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.IUpdateLock; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.TokenWatcher; +import android.os.UpdateLock; +import android.util.Slog; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +public class UpdateLockService extends IUpdateLock.Stub { + static final boolean DEBUG = false; + static final String TAG = "UpdateLockService"; + + // signatureOrSystem required to use update locks + static final String PERMISSION = "android.permission.UPDATE_LOCK"; + + Context mContext; + LockWatcher mLocks; + + class LockWatcher extends TokenWatcher { + LockWatcher(Handler h, String tag) { + super(h, tag); + } + + public void acquired() { + if (DEBUG) { + Slog.d(TAG, "first acquire; broadcasting convenient=false"); + } + sendLockChangedBroadcast(false); + } + public void released() { + if (DEBUG) { + Slog.d(TAG, "last release; broadcasting convenient=true"); + } + sendLockChangedBroadcast(true); + } + } + + UpdateLockService(Context context) { + mContext = context; + mLocks = new LockWatcher(new Handler(), "UpdateLocks"); + + // Consider just-booting to be a reasonable time to allow + // interruptions for update installation etc. + sendLockChangedBroadcast(true); + } + + void sendLockChangedBroadcast(boolean state) { + // Safe early during boot because this broadcast only goes to registered receivers. + long oldIdent = Binder.clearCallingIdentity(); + try { + Intent intent = new Intent(UpdateLock.UPDATE_LOCK_CHANGED) + .putExtra(UpdateLock.NOW_IS_CONVENIENT, state) + .putExtra(UpdateLock.TIMESTAMP, System.currentTimeMillis()) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mContext.sendStickyBroadcast(intent); + } finally { + Binder.restoreCallingIdentity(oldIdent); + } + } + + @Override + public void acquireUpdateLock(IBinder token, String tag) throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "acquire(" + token + ") by " + makeTag(tag)); + } + + mContext.enforceCallingOrSelfPermission(PERMISSION, "acquireUpdateLock"); + mLocks.acquire(token, makeTag(tag)); + } + + @Override + public void releaseUpdateLock(IBinder token) throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "release(" + token + ')'); + } + + mContext.enforceCallingOrSelfPermission(PERMISSION, "releaseUpdateLock"); + mLocks.release(token); + }; + + private String makeTag(String tag) { + return "{tag=" + tag + + " uid=" + Binder.getCallingUid() + + " pid=" + Binder.getCallingPid() + '}'; + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump update lock service from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + mLocks.dump(pw); + } +}