/* * 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.compat.annotation.UnsupportedAppUsage; 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. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 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. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 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. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 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? */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isHeld() { synchronized (mToken) { return mHeld; } } /** * Acquire an update lock. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 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. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 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"); } } } } }