content: /*
   * Copyright (C) 2016 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.wm;
  
  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_UNKNOWN_APP_VISIBILITY;
  import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
  import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
  
  import android.annotation.NonNull;
  import android.util.ArrayMap;
  import android.util.Slog;
  
  import com.android.server.wm.WindowManagerService.H;
  
  import java.io.PrintWriter;
  
  /**
   * Manages the set of {@link AppWindowToken}s for which we don't know yet whether it's visible or
   * not. This happens when starting an activity while the lockscreen is showing. In that case, the
   * keyguard flags an app might set influence it's visibility, so we wait until this is resolved to
   * start the transition to avoid flickers.
   */
  class UnknownAppVisibilityController {
  
      private static final String TAG = TAG_WITH_CLASS_NAME ? "UnknownAppVisibility" : TAG_WM;
  
      /**
       * We are currently waiting until the app is done resuming.
       */
      private static final int UNKNOWN_STATE_WAITING_RESUME = 1;
  
      /**
       * The activity has finished resuming, and we are waiting on the next relayout.
       */
      private static final int UNKNOWN_STATE_WAITING_RELAYOUT = 2;
  
      /**
       * The client called {@link Session#relayout} with the appropriate Keyguard flags and we are
       * waiting until activity manager has updated the visibilities of all the apps.
       */
      private static final int UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE = 3;
  
      // Set of apps for which we don't know yet whether it's visible or not, depending on what kind
      // of lockscreen flags the app might set during its first relayout.
      private final ArrayMap<AppWindowToken, Integer> mUnknownApps = new ArrayMap<>();
  
      private final WindowManagerService mService;
  
      private final DisplayContent mDisplayContent;
  
      UnknownAppVisibilityController(WindowManagerService service, DisplayContent displayContent) {
          mService = service;
          mDisplayContent = displayContent;
      }
  
      boolean allResolved() {
          return mUnknownApps.isEmpty();
      }
  
      void clear() {
          mUnknownApps.clear();
      }
  
      String getDebugMessage() {
          final StringBuilder builder = new StringBuilder();
          for (int i = mUnknownApps.size() - 1; i >= 0; i--) {
              builder.append("app=").append(mUnknownApps.keyAt(i))
                      .append(" state=").append(mUnknownApps.valueAt(i));
              if (i != 0) {
                  builder.append(' ');
              }
          }
          return builder.toString();
      }
  
      void appRemovedOrHidden(@NonNull AppWindowToken appWindow) {
          if (DEBUG_UNKNOWN_APP_VISIBILITY) {
              Slog.d(TAG, "App removed or hidden appWindow=" + appWindow);
          }
          mUnknownApps.remove(appWindow);
      }
  
      /**
       * Notifies that {@param appWindow} has been launched behind Keyguard, and we need to wait until
       * it is resumed and relaid out to resolve the visibility.
       */
      void notifyLaunched(@NonNull AppWindowToken appWindow) {
          if (DEBUG_UNKNOWN_APP_VISIBILITY) {
              Slog.d(TAG, "App launched appWindow=" + appWindow);
          }
          mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_RESUME);
      }
  
      /**
       * Notifies that {@param appWindow} has finished resuming.
       */
      void notifyAppResumedFinished(@NonNull AppWindowToken appWindow) {
          if (mUnknownApps.containsKey(appWindow)
                  && mUnknownApps.get(appWindow) == UNKNOWN_STATE_WAITING_RESUME) {
              if (DEBUG_UNKNOWN_APP_VISIBILITY) {
                  Slog.d(TAG, "App resume finished appWindow=" + appWindow);
              }
              mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_RELAYOUT);
          }
      }
  
      /**
       * Notifies that {@param appWindow} has relaid out.
       */
      void notifyRelayouted(@NonNull AppWindowToken appWindow) {
          if (!mUnknownApps.containsKey(appWindow)) {
              return;
          }
          if (DEBUG_UNKNOWN_APP_VISIBILITY) {
              Slog.d(TAG, "App relayouted appWindow=" + appWindow);
          }
          int state = mUnknownApps.get(appWindow);
          if (state == UNKNOWN_STATE_WAITING_RELAYOUT) {
              mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
              mService.notifyKeyguardFlagsChanged(this::notifyVisibilitiesUpdated,
                      appWindow.getDisplayContent().getDisplayId());
          }
      }
  
      private void notifyVisibilitiesUpdated() {
          if (DEBUG_UNKNOWN_APP_VISIBILITY) {
              Slog.d(TAG, "Visibility updated DONE");
          }
          boolean changed = false;
          for (int i = mUnknownApps.size() - 1; i >= 0; i--) {
              if (mUnknownApps.valueAt(i) == UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE) {
                  mUnknownApps.removeAt(i);
                  changed = true;
              }
          }
          if (changed) {
              mService.mWindowPlacerLocked.performSurfacePlacement();
          }
      }
  
      void dump(PrintWriter pw, String prefix) {
          if (mUnknownApps.isEmpty()) {
              return;
          }
          pw.println(prefix + "Unknown visibilities:");
          for (int i = mUnknownApps.size() - 1; i >= 0; i--) {
              pw.println(prefix + "  app=" + mUnknownApps.keyAt(i)
                      + " state=" + mUnknownApps.valueAt(i));
          }
      }
  }
  