content: /*
   * Copyright (C) 2006 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.view;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.Message;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.os.RemoteException;
  
  import java.util.HashMap;
  
  /**
   * Safe identifier for a window.  This currently allows you to retrieve and observe
   * the input focus state of the window.  Most applications will
   * not use this, instead relying on the simpler (and more efficient) methods available
   * on {@link View}.  This classes is useful when window input interactions need to be
   * done across processes: the class itself is a Parcelable that can be passed to other
   * processes for them to interact with your window, and it provides a limited safe API
   * that doesn't allow the other process to negatively harm your window.
   */
  public class WindowId implements Parcelable {
      @NonNull
      private final IWindowId mToken;
  
      /**
       * Subclass for observing changes to the focus state of an {@link WindowId}.
       * You should use the same instance of this class for observing multiple
       * {@link WindowId} objects, since this class is fairly heavy-weight -- the
       * base class includes all of the mechanisms for connecting to and receiving updates
       * from the window.
       */
      public static abstract class FocusObserver {
          final IWindowFocusObserver.Stub mIObserver = new IWindowFocusObserver.Stub() {
  
              @Override
              public void focusGained(IBinder inputToken) {
                  WindowId token;
                  synchronized (mRegistrations) {
                      token = mRegistrations.get(inputToken);
                  }
                  if (mHandler != null) {
                      mHandler.sendMessage(mHandler.obtainMessage(1, token));
                  } else {
                      onFocusGained(token);
                  }
              }
  
              @Override
              public void focusLost(IBinder inputToken) {
                  WindowId token;
                  synchronized (mRegistrations) {
                      token = mRegistrations.get(inputToken);
                  }
                  if (mHandler != null) {
                      mHandler.sendMessage(mHandler.obtainMessage(2, token));
                  } else {
                      onFocusLost(token);
                  }
              }
          };
  
          final HashMap<IBinder, WindowId> mRegistrations = new HashMap<>();
  
          class H extends Handler {
              @Override
              public void handleMessage(Message msg) {
                  switch (msg.what) {
                      case 1:
                          onFocusGained((WindowId)msg.obj);
                          break;
                      case 2:
                          onFocusLost((WindowId)msg.obj);
                          break;
                      default:
                          super.handleMessage(msg);
                  }
              }
          }
  
          final Handler mHandler;
  
          /**
           * Construct a new observer.  This observer will be configured so that all
           * of its callbacks are dispatched on the current calling thread.
           */
          public FocusObserver() {
              mHandler = new H();
          }
  
          /**
           * Called when one of the monitored windows gains input focus.
           */
          public abstract void onFocusGained(WindowId token);
  
          /**
           * Called when one of the monitored windows loses input focus.
           */
          public abstract void onFocusLost(WindowId token);
      }
  
      /**
       * Retrieve the current focus state of the associated window.
       */
      public boolean isFocused() {
          try {
              return mToken.isFocused();
          } catch (RemoteException e) {
              return false;
          }
      }
  
      /**
       * Start monitoring for changes in the focus state of the window.
       */
      public void registerFocusObserver(FocusObserver observer) {
          synchronized (observer.mRegistrations) {
              if (observer.mRegistrations.containsKey(mToken.asBinder())) {
                  throw new IllegalStateException(
                          "Focus observer already registered with input token");
              }
              observer.mRegistrations.put(mToken.asBinder(), this);
              try {
                  mToken.registerFocusObserver(observer.mIObserver);
              } catch (RemoteException e) {
              }
          }
      }
  
      /**
       * Stop monitoring changes in the focus state of the window.
       */
      public void unregisterFocusObserver(FocusObserver observer) {
          synchronized (observer.mRegistrations) {
              if (observer.mRegistrations.remove(mToken.asBinder()) == null) {
                  throw new IllegalStateException("Focus observer not registered with input token");
              }
              try {
                  mToken.unregisterFocusObserver(observer.mIObserver);
              } catch (RemoteException e) {
              }
          }
      }
  
      /**
       * Comparison operator on two IntentSender objects, such that true
       * is returned then they both represent the same operation from the
       * same package.
       */
      @Override
      public boolean equals(@Nullable Object otherObj) {
          if (otherObj instanceof WindowId) {
              return mToken.asBinder().equals(((WindowId) otherObj).mToken.asBinder());
          }
          return false;
      }
  
      @Override
      public int hashCode() {
          return mToken.asBinder().hashCode();
      }
  
      @Override
      public String toString() {
          StringBuilder sb = new StringBuilder(128);
          sb.append("IntentSender{");
          sb.append(Integer.toHexString(System.identityHashCode(this)));
          sb.append(": ");
          sb.append(mToken.asBinder());
          sb.append('}');
          return sb.toString();
      }
  
      public int describeContents() {
          return 0;
      }
  
      public void writeToParcel(Parcel out, int flags) {
          out.writeStrongBinder(mToken.asBinder());
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<WindowId> CREATOR = new Parcelable.Creator<WindowId>() {
          @Override
          public WindowId createFromParcel(Parcel in) {
              IBinder target = in.readStrongBinder();
              return target != null ? new WindowId(target) : null;
          }
  
          @Override
          public WindowId[] newArray(int size) {
              return new WindowId[size];
          }
      };
  
      /** @hide */
      @NonNull
      public IWindowId getTarget() {
          return mToken;
      }
  
      /** @hide */
      public WindowId(@NonNull IWindowId target) {
          mToken = target;
      }
  
      /** @hide */
      public WindowId(@NonNull IBinder target) {
          mToken = IWindowId.Stub.asInterface(target);
      }
  }
  