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