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.launcher3.graphics;
  
  import static android.view.View.MeasureSpec.EXACTLY;
  import static android.view.View.MeasureSpec.makeMeasureSpec;
  import static android.view.View.VISIBLE;
  
  import android.annotation.TargetApi;
  import android.app.Fragment;
  import android.content.Context;
  import android.content.Intent;
  import android.content.res.TypedArray;
  import android.graphics.Bitmap;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Rect;
  import android.graphics.drawable.AdaptiveIconDrawable;
  import android.graphics.drawable.ColorDrawable;
  import android.os.Build;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.Process;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.ContextThemeWrapper;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.ViewGroup;
  import android.widget.TextClock;
  
  import com.android.launcher3.BubbleTextView;
  import com.android.launcher3.CellLayout;
  import com.android.launcher3.DeviceProfile;
  import com.android.launcher3.Hotseat;
  import com.android.launcher3.InsettableFrameLayout;
  import com.android.launcher3.InvariantDeviceProfile;
  import com.android.launcher3.LauncherSettings.Favorites;
  import com.android.launcher3.R;
  import com.android.launcher3.WorkspaceItemInfo;
  import com.android.launcher3.Utilities;
  import com.android.launcher3.WorkspaceLayoutManager;
  import com.android.launcher3.allapps.SearchUiManager;
  import com.android.launcher3.config.FeatureFlags;
  import com.android.launcher3.icons.BaseIconFactory;
  import com.android.launcher3.icons.BitmapInfo;
  import com.android.launcher3.icons.BitmapRenderer;
  import com.android.launcher3.views.ActivityContext;
  import com.android.launcher3.views.BaseDragLayer;
  
  import java.util.concurrent.Callable;
  import java.util.concurrent.CountDownLatch;
  
  /**
   * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
   * Steps:
   *   1) Create a dummy icon info with just white icon
   *   2) Inflate a strip down layout definition for Launcher
   *   3) Place appropriate elements like icons and first-page qsb
   *   4) Measure and draw the view on a canvas
   */
  @TargetApi(Build.VERSION_CODES.O)
  public class LauncherPreviewRenderer implements Callable<Bitmap> {
  
      private static final String TAG = "LauncherPreviewRenderer";
  
      private final Handler mUiHandler;
      private final Context mContext;
      private final InvariantDeviceProfile mIdp;
      private final DeviceProfile mDp;
      private final Rect mInsets;
  
      private final WorkspaceItemInfo mWorkspaceItemInfo;
  
      public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp) {
          mUiHandler = new Handler(Looper.getMainLooper());
          mContext = context;
          mIdp = idp;
          mDp = idp.portraitProfile.copy(context);
  
          // TODO: get correct insets once display cutout API is available.
          mInsets = new Rect();
          mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2;
          mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2;
          mDp.updateInsets(mInsets);
  
          BaseIconFactory iconFactory =
                  new BaseIconFactory(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize) { };
          BitmapInfo iconInfo = iconFactory.createBadgedIconBitmap(new AdaptiveIconDrawable(
                          new ColorDrawable(Color.WHITE), new ColorDrawable(Color.WHITE)),
                  Process.myUserHandle(),
                  Build.VERSION.SDK_INT);
  
          mWorkspaceItemInfo = new WorkspaceItemInfo();
          mWorkspaceItemInfo.applyFrom(iconInfo);
          mWorkspaceItemInfo.intent = new Intent();
          mWorkspaceItemInfo.contentDescription = mWorkspaceItemInfo.title =
                  context.getString(R.string.label_application);
      }
  
      @Override
      public Bitmap call() {
          return BitmapRenderer.createHardwareBitmap(mDp.widthPx, mDp.heightPx, c -> {
  
              if (Looper.myLooper() == Looper.getMainLooper()) {
                  new MainThreadRenderer(mContext).renderScreenShot(c);
              } else {
                  CountDownLatch latch = new CountDownLatch(1);
                  Utilities.postAsyncCallback(mUiHandler, () -> {
                      new MainThreadRenderer(mContext).renderScreenShot(c);
                      latch.countDown();
                  });
  
                  try {
                      latch.await();
                  } catch (Exception e) {
                      Log.e(TAG, "Error drawing on main thread", e);
                  }
              }
          });
      }
  
      private class MainThreadRenderer extends ContextThemeWrapper
              implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
  
          private final LayoutInflater mHomeElementInflater;
          private final InsettableFrameLayout mRootView;
  
          private final Hotseat mHotseat;
          private final CellLayout mWorkspace;
  
          MainThreadRenderer(Context context) {
              super(context, R.style.AppTheme);
  
              mHomeElementInflater = LayoutInflater.from(
                      new ContextThemeWrapper(this, R.style.HomeScreenElementTheme));
              mHomeElementInflater.setFactory2(this);
  
              mRootView = (InsettableFrameLayout) mHomeElementInflater.inflate(
                      R.layout.launcher_preview_layout, null, false);
              mRootView.setInsets(mInsets);
              measureView(mRootView, mDp.widthPx, mDp.heightPx);
  
              mHotseat = mRootView.findViewById(R.id.hotseat);
              mHotseat.resetLayout(false);
  
              mWorkspace = mRootView.findViewById(R.id.workspace);
              mWorkspace.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
                      mDp.workspacePadding.top,
                      mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
                      mDp.workspacePadding.bottom);
          }
  
          @Override
          public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
              if ("TextClock".equals(name)) {
                  // Workaround for TextClock accessing handler for unregistering ticker.
                  return new TextClock(context, attrs) {
  
                      @Override
                      public Handler getHandler() {
                          return mUiHandler;
                      }
                  };
              } else if (!"fragment".equals(name)) {
                  return null;
              }
  
              TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PreviewFragment);
              FragmentWithPreview f = (FragmentWithPreview) Fragment.instantiate(
                      context, ta.getString(R.styleable.PreviewFragment_android_name));
              f.enterPreviewMode(context);
              f.onInit(null);
  
              View view = f.onCreateView(LayoutInflater.from(context), (ViewGroup) parent, null);
              view.setId(ta.getInt(R.styleable.PreviewFragment_android_id, View.NO_ID));
              return view;
          }
  
          @Override
          public View onCreateView(String name, Context context, AttributeSet attrs) {
              return onCreateView(null, name, context, attrs);
          }
  
          @Override
          public BaseDragLayer getDragLayer() {
              throw new UnsupportedOperationException();
          }
  
          @Override
          public DeviceProfile getDeviceProfile() {
              return mDp;
          }
  
          @Override
          public Hotseat getHotseat() {
              return mHotseat;
          }
  
          @Override
          public CellLayout getScreenWithId(int screenId) {
              return mWorkspace;
          }
  
          private void inflateAndAddIcon(WorkspaceItemInfo info) {
              BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate(
                      R.layout.app_icon, mWorkspace, false);
              icon.applyFromWorkspaceItem(info);
              addInScreenFromBind(icon, info);
          }
  
          private void dispatchVisibilityAggregated(View view, boolean isVisible) {
              // Similar to View.dispatchVisibilityAggregated implementation.
              final boolean thisVisible = view.getVisibility() == VISIBLE;
              if (thisVisible || !isVisible) {
                  view.onVisibilityAggregated(isVisible);
              }
  
              if (view instanceof ViewGroup) {
                  isVisible = thisVisible && isVisible;
                  ViewGroup vg = (ViewGroup) view;
                  int count = vg.getChildCount();
  
                  for (int i = 0; i < count; i++) {
                      dispatchVisibilityAggregated(vg.getChildAt(i), isVisible);
                  }
              }
          }
  
          private void renderScreenShot(Canvas canvas) {
              // Add hotseat icons
              for (int i = 0; i < mIdp.numHotseatIcons; i++) {
                  WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
                  info.container = Favorites.CONTAINER_HOTSEAT;
                  info.screenId = i;
                  inflateAndAddIcon(info);
              }
  
              // Add workspace icons
              for (int i = 0; i < mIdp.numColumns; i++) {
                  WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
                  info.container = Favorites.CONTAINER_DESKTOP;
                  info.screenId = 0;
                  info.cellX = i;
                  info.cellY = mIdp.numRows - 1;
                  inflateAndAddIcon(info);
              }
  
              // Add first page QSB
              if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
                  View qsb = mHomeElementInflater.inflate(
                          R.layout.search_container_workspace, mWorkspace, false);
                  CellLayout.LayoutParams lp =
                          new CellLayout.LayoutParams(0, 0, mWorkspace.getCountX(), 1);
                  lp.canReorder = false;
                  mWorkspace.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
              }
  
              // Setup search view
              SearchUiManager searchUiManager =
                      mRootView.findViewById(R.id.search_container_all_apps);
              mRootView.findViewById(R.id.apps_view).setTranslationY(
                      mDp.heightPx - searchUiManager.getScrollRangeDelta(mInsets));
  
              measureView(mRootView, mDp.widthPx, mDp.heightPx);
              dispatchVisibilityAggregated(mRootView, true);
              measureView(mRootView, mDp.widthPx, mDp.heightPx);
              // Additional measure for views which use auto text size API
              measureView(mRootView, mDp.widthPx, mDp.heightPx);
  
              mRootView.draw(canvas);
              dispatchVisibilityAggregated(mRootView, false);
          }
      }
  
      private static void measureView(View view, int width, int height) {
          view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
          view.layout(0, 0, width, height);
      }
  }
  