blob: a3e58f802845c9aebb992ccd603cc6020ab1a838 [file] [log] [blame]
/*
* Copyright (C) 2018 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 com.android.server.utils;
import android.annotation.UserIdInt;
import android.os.Handler;
import android.os.IBinder;
import android.os.TokenWatcher;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
import java.io.PrintWriter;
/**
* Multi-user aware {@link TokenWatcher}.
*
* {@link UserTokenWatcher} is thread-safe.
*/
public final class UserTokenWatcher {
private final Callback mCallback;
private final Handler mHandler;
private final String mTag;
@GuardedBy("mWatchers")
private final SparseArray<TokenWatcher> mWatchers = new SparseArray<>(1);
public UserTokenWatcher(Callback callback, Handler handler, String tag) {
mCallback = callback;
mHandler = handler;
mTag = tag;
}
/**
* Record that this token has been acquired for the given user. When acquire is called, and
* the user's count goes from 0 to 1, the acquired callback is called on the given
* handler.
*
* Note that the same {@code token} can only be acquired once per user. If this
* {@code token} has already been acquired for the given user, no action is taken. The first
* subsequent call to {@link #release} will release this {@code token}
* immediately.
*
* @param token An IBinder object.
* @param tag A string used by the {@link #dump} method for debugging,
* to see who has references.
* @param userId A user id
*/
public void acquire(IBinder token, String tag, @UserIdInt int userId) {
synchronized (mWatchers) {
TokenWatcher watcher = mWatchers.get(userId);
if (watcher == null) {
watcher = new InnerTokenWatcher(userId, mHandler, mTag);
mWatchers.put(userId, watcher);
}
watcher.acquire(token, tag);
}
}
/**
* Record that this token has been released for the given user. When release is called, and
* the user's count goes from 1 to 0, the released callback is called on the given
* handler.
*
* @param token An IBinder object.
* @param userId A user id
*/
public void release(IBinder token, @UserIdInt int userId) {
synchronized (mWatchers) {
TokenWatcher watcher = mWatchers.get(userId);
if (watcher != null) {
watcher.release(token);
}
}
}
/**
* Returns whether the given user has any registered tokens that have not been cleaned up.
*
* @return true, if the given user has registered tokens.
*/
public boolean isAcquired(@UserIdInt int userId) {
synchronized (mWatchers) {
TokenWatcher watcher = mWatchers.get(userId);
return watcher != null && watcher.isAcquired();
}
}
/**
* Dumps the current state.
*/
public void dump(PrintWriter pw) {
synchronized (mWatchers) {
for (int i = 0; i < mWatchers.size(); i++) {
int userId = mWatchers.keyAt(i);
TokenWatcher watcher = mWatchers.valueAt(i);
if (watcher.isAcquired()) {
pw.print("User ");
pw.print(userId);
pw.println(":");
watcher.dump(new IndentingPrintWriter(pw, " "));
}
}
}
}
/**
* Callback for {@link UserTokenWatcher}.
*/
public interface Callback {
/**
* Reports that the first token has been acquired for the given user.
*/
void acquired(@UserIdInt int userId);
/**
* Reports that the last token has been release for the given user.
*/
void released(@UserIdInt int userId);
}
private final class InnerTokenWatcher extends TokenWatcher {
private final int mUserId;
private InnerTokenWatcher(int userId, Handler handler, String tag) {
super(handler, tag);
this.mUserId = userId;
}
@Override
public void acquired() {
// We MUST NOT hold any locks while invoking the callbacks.
mCallback.acquired(mUserId);
}
@Override
public void released() {
// We MUST NOT hold any locks while invoking the callbacks.
mCallback.released(mUserId);
synchronized (mWatchers) {
final TokenWatcher watcher = mWatchers.get(mUserId);
if (watcher != null && !watcher.isAcquired()) {
mWatchers.remove(mUserId);
}
}
}
}
}