|  | /* | 
|  | * 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"); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } |