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