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