1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.os; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.content.Context; 21 import android.util.Log; 22 23 /** 24 * Advisory wakelock-like mechanism by which processes that should not be interrupted for 25 * OTA/update purposes can so advise the OS. This is particularly relevant for headless 26 * or kiosk-like operation. 27 * 28 * @hide 29 */ 30 public class UpdateLock { 31 private static final boolean DEBUG = false; 32 private static final String TAG = "UpdateLock"; 33 34 private static IUpdateLock sService; checkService()35 private static void checkService() { 36 if (sService == null) { 37 sService = IUpdateLock.Stub.asInterface( 38 ServiceManager.getService(Context.UPDATE_LOCK_SERVICE)); 39 } 40 } 41 42 IBinder mToken; 43 int mCount = 0; 44 boolean mRefCounted = true; 45 boolean mHeld = false; 46 final String mTag; 47 48 /** 49 * Broadcast Intent action sent when the global update lock state changes, 50 * i.e. when the first locker acquires an update lock, or when the last 51 * locker releases theirs. The broadcast is sticky but is sent only to 52 * registered receivers. 53 */ 54 @UnsupportedAppUsage 55 public static final String UPDATE_LOCK_CHANGED = "android.os.UpdateLock.UPDATE_LOCK_CHANGED"; 56 57 /** 58 * Boolean Intent extra on the UPDATE_LOCK_CHANGED sticky broadcast, indicating 59 * whether now is an appropriate time to interrupt device activity with an 60 * update operation. True means that updates are okay right now; false indicates 61 * that perhaps later would be a better time. 62 */ 63 @UnsupportedAppUsage 64 public static final String NOW_IS_CONVENIENT = "nowisconvenient"; 65 66 /** 67 * Long Intent extra on the UPDATE_LOCK_CHANGED sticky broadcast, marking the 68 * wall-clock time [in UTC] at which the broadcast was sent. Note that this is 69 * in the System.currentTimeMillis() time base, which may be non-monotonic especially 70 * around reboots. 71 */ 72 @UnsupportedAppUsage 73 public static final String TIMESTAMP = "timestamp"; 74 75 /** 76 * Construct an UpdateLock instance. 77 * @param tag An arbitrary string used to identify this lock instance in dump output. 78 */ UpdateLock(String tag)79 public UpdateLock(String tag) { 80 mTag = tag; 81 mToken = new Binder(); 82 } 83 84 /** 85 * Change the refcount behavior of this update lock. 86 */ setReferenceCounted(boolean isRefCounted)87 public void setReferenceCounted(boolean isRefCounted) { 88 if (DEBUG) { 89 Log.v(TAG, "setting refcounted=" + isRefCounted + " : " + this); 90 } 91 mRefCounted = isRefCounted; 92 } 93 94 /** 95 * Is this lock currently held? 96 */ 97 @UnsupportedAppUsage isHeld()98 public boolean isHeld() { 99 synchronized (mToken) { 100 return mHeld; 101 } 102 } 103 104 /** 105 * Acquire an update lock. 106 */ 107 @UnsupportedAppUsage acquire()108 public void acquire() { 109 if (DEBUG) { 110 Log.v(TAG, "acquire() : " + this, new RuntimeException("here")); 111 } 112 checkService(); 113 synchronized (mToken) { 114 acquireLocked(); 115 } 116 } 117 acquireLocked()118 private void acquireLocked() { 119 if (!mRefCounted || mCount++ == 0) { 120 if (sService != null) { 121 try { 122 sService.acquireUpdateLock(mToken, mTag); 123 } catch (RemoteException e) { 124 Log.e(TAG, "Unable to contact service to acquire"); 125 } 126 } 127 mHeld = true; 128 } 129 } 130 131 /** 132 * Release this update lock. 133 */ 134 @UnsupportedAppUsage release()135 public void release() { 136 if (DEBUG) Log.v(TAG, "release() : " + this, new RuntimeException("here")); 137 checkService(); 138 synchronized (mToken) { 139 releaseLocked(); 140 } 141 } 142 releaseLocked()143 private void releaseLocked() { 144 if (!mRefCounted || --mCount == 0) { 145 if (sService != null) { 146 try { 147 sService.releaseUpdateLock(mToken); 148 } catch (RemoteException e) { 149 Log.e(TAG, "Unable to contact service to release"); 150 } 151 } 152 mHeld = false; 153 } 154 if (mCount < 0) { 155 throw new RuntimeException("UpdateLock under-locked"); 156 } 157 } 158 159 @Override finalize()160 protected void finalize() throws Throwable { 161 synchronized (mToken) { 162 // if mHeld is true, sService must be non-null 163 if (mHeld) { 164 Log.wtf(TAG, "UpdateLock finalized while still held"); 165 try { 166 sService.releaseUpdateLock(mToken); 167 } catch (RemoteException e) { 168 Log.e(TAG, "Unable to contact service to release"); 169 } 170 } 171 } 172 } 173 } 174