content: /*
   * Copyright (C) 2007 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 android.os;
  
  import android.util.Log;
  
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.Set;
  import java.util.WeakHashMap;
  
  /**
   * A TokenWatcher watches a collection of {@link IBinder}s. IBinders are added
   * to the collection by calling {@link #acquire}, and removed by calling {@link
   * #release}. IBinders are also implicitly removed when they become weakly
   * reachable. Each IBinder may be added at most once.
   *
   * The {@link #acquired} method is invoked by posting to the specified handler
   * whenever the size of the watched collection becomes nonzero.  The {@link
   * #released} method is invoked on the specified handler whenever the size of
   * the watched collection becomes zero.
   */
  public abstract class TokenWatcher
  {
      /**
       * Construct the TokenWatcher
       *
       * @param h A handler to call {@link #acquired} and {@link #released}
       * on.  If you don't care, just call it like this, although your thread
       * will have to be a Looper thread.
       * <code>new TokenWatcher(new Handler())</code>
       * @param tag A debugging tag for this TokenWatcher
       */
      public TokenWatcher(Handler h, String tag)
      {
          mHandler = h;
          mTag = tag != null ? tag : "TokenWatcher";
      }
  
      /**
       * Called when the number of active tokens goes from 0 to 1.
       */
      public abstract void acquired();
  
      /**
       * Called when the number of active tokens goes from 1 to 0.
       */
      public abstract void released();
  
      /**
       * Record that this token has been acquired.  When acquire is called, and
       * the current count is 0, the acquired method is called on the given
       * handler.
       *
       * Note that the same {@code token} can only be acquired once. If this
       * {@code token} has already been acquired, 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.
       */
      public void acquire(IBinder token, String tag)
      {
          synchronized (mTokens) {
              if (mTokens.containsKey(token)) {
                  return;
              }
  
              // explicitly checked to avoid bogus sendNotification calls because
              // of the WeakHashMap and the GC
              int oldSize = mTokens.size();
  
              Death d = new Death(token, tag);
              try {
                  token.linkToDeath(d, 0);
              } catch (RemoteException e) {
                  return;
              }
              mTokens.put(token, d);
  
              if (oldSize == 0 && !mAcquired) {
                  sendNotificationLocked(true);
                  mAcquired = true;
              }
          }
      }
  
      public void cleanup(IBinder token, boolean unlink)
      {
          synchronized (mTokens) {
              Death d = mTokens.remove(token);
              if (unlink && d != null) {
                  d.token.unlinkToDeath(d, 0);
                  d.token = null;
              }
  
              if (mTokens.size() == 0 && mAcquired) {
                  sendNotificationLocked(false);
                  mAcquired = false;
              }
          }
      }
  
      public void release(IBinder token)
      {
          cleanup(token, true);
      }
  
      public boolean isAcquired()
      {
          synchronized (mTokens) {
              return mAcquired;
          }
      }
  
      public void dump()
      {
          ArrayList<String> a = dumpInternal();
          for (String s : a) {
              Log.i(mTag, s);
          }
      }
  
      public void dump(PrintWriter pw) {
          ArrayList<String> a = dumpInternal();
          for (String s : a) {
              pw.println(s);
          }
      }
  
      private ArrayList<String> dumpInternal() {
          ArrayList<String> a = new ArrayList<String>();
          synchronized (mTokens) {
              Set<IBinder> keys = mTokens.keySet();
              a.add("Token count: " + mTokens.size());
              int i = 0;
              for (IBinder b: keys) {
                  a.add("[" + i + "] " + mTokens.get(b).tag + " - " + b);
                  i++;
              }
          }
          return a;
      }
  
      private Runnable mNotificationTask = new Runnable() {
          public void run()
          {
              int value;
              synchronized (mTokens) {
                  value = mNotificationQueue;
                  mNotificationQueue = -1;
              }
              if (value == 1) {
                  acquired();
              }
              else if (value == 0) {
                  released();
              }
          }
      };
  
      private void sendNotificationLocked(boolean on)
      {
          int value = on ? 1 : 0;
          if (mNotificationQueue == -1) {
              // empty
              mNotificationQueue = value;
              mHandler.post(mNotificationTask);
          }
          else if (mNotificationQueue != value) {
              // it's a pair, so cancel it
              mNotificationQueue = -1;
              mHandler.removeCallbacks(mNotificationTask);
          }
          // else, same so do nothing -- maybe we should warn?
      }
  
      private class Death implements IBinder.DeathRecipient
      {
          IBinder token;
          String tag;
  
          Death(IBinder token, String tag)
          {
              this.token = token;
              this.tag = tag;
          }
  
          public void binderDied()
          {
              cleanup(token, false);
          }
  
          protected void finalize() throws Throwable
          {
              try {
                  if (token != null) {
                      Log.w(mTag, "cleaning up leaked reference: " + tag);
                      release(token);
                  }
              }
              finally {
                  super.finalize();
              }
          }
      }
  
      private WeakHashMap<IBinder,Death> mTokens = new WeakHashMap<IBinder,Death>();
      private Handler mHandler;
      private String mTag;
      private int mNotificationQueue = -1;
      private volatile boolean mAcquired = false;
  }
  