/* * 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 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); } } } } }