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
17package android.os;
18
19import android.content.Context;
20import 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 */
29public class UpdateLock {
30    private static final boolean DEBUG = false;
31    private static final String TAG = "UpdateLock";
32
33    private static IUpdateLock sService;
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     */
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     */
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     */
93    public boolean isHeld() {
94        synchronized (mToken) {
95            return mHeld;
96        }
97    }
98
99    /**
100     * Acquire an update lock.
101     */
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
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     */
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
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
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