[automerger skipped] Merge Android 12L am: 524748a371 -s ours am: abdf9ae1d1 -s ours am: d49534499b -s ours

am skip reason: Merged-In I41b0f264d344daeb60050231160084c448083f16 with SHA-1 2bac0b4304 is already in history

Original change: https://android-review.googlesource.com/c/platform/external/setupcompat/+/2011692

Change-Id: Ie32a488eae88268af2b5b9c61e9aa8c8db029c9a
diff --git a/main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl b/main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl
index 09f7c9a..e8cb7e5 100644
--- a/main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl
+++ b/main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl
@@ -26,4 +26,6 @@
   oneway void validateActivity(String screenName, in Bundle arguments) = 0;
 
   oneway void logMetric(int metricType, in Bundle arguments, in Bundle extras) = 1;
+
+  oneway void onFocusStatusChanged(in Bundle bundle) = 2;
 }
diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
index e5ba0c5..4a88f5b 100644
--- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
+++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
@@ -29,9 +29,13 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import androidx.annotation.VisibleForTesting;
+import com.google.android.setupcompat.internal.FocusChangedMetricHelper;
 import com.google.android.setupcompat.internal.LifecycleFragment;
 import com.google.android.setupcompat.internal.PersistableBundles;
+import com.google.android.setupcompat.internal.SetupCompatServiceInvoker;
 import com.google.android.setupcompat.internal.TemplateLayout;
 import com.google.android.setupcompat.logging.CustomEvent;
 import com.google.android.setupcompat.logging.MetricKey;
@@ -84,6 +88,10 @@
     init(null, R.attr.sucLayoutTheme);
   }
 
+  @VisibleForTesting
+  final ViewTreeObserver.OnWindowFocusChangeListener windowFocusChangeListener =
+      this::onFocusChanged;
+
   public PartnerCustomizationLayout(Context context, AttributeSet attrs) {
     super(context, attrs);
     init(attrs, R.attr.sucLayoutTheme);
@@ -203,14 +211,17 @@
   protected void onAttachedToWindow() {
     super.onAttachedToWindow();
     LifecycleFragment.attachNow(activity);
+    if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2
+        && WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
+      getViewTreeObserver().addOnWindowFocusChangeListener(windowFocusChangeListener);
+    }
     getMixin(FooterBarMixin.class).onAttachedToWindow();
   }
 
   @Override
   protected void onDetachedFromWindow() {
     super.onDetachedFromWindow();
-    if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP
-        && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+    if (VERSION.SDK_INT >= Build.VERSION_CODES.Q
         && WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
       FooterBarMixin footerBarMixin = getMixin(FooterBarMixin.class);
       footerBarMixin.onDetachedFromWindow();
@@ -233,6 +244,10 @@
           getContext(),
           CustomEvent.create(MetricKey.get("SetupCompatMetrics", activity), persistableBundle));
     }
+
+    if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
+      getViewTreeObserver().removeOnWindowFocusChangeListener(windowFocusChangeListener);
+    }
   }
 
   public static Activity lookupActivityFromContext(Context context) {
@@ -297,4 +312,16 @@
   public boolean useFullDynamicColor() {
     return shouldApplyDynamicColor() && useFullDynamicColorAttr;
   }
+
+  /**
+   * Invoke the method onFocusStatusChanged when onWindowFocusChangeListener receive onFocusChanged.
+   */
+  private void onFocusChanged(boolean hasFocus) {
+    SetupCompatServiceInvoker.get(getContext())
+        .onFocusStatusChanged(
+            FocusChangedMetricHelper.getScreenName(activity),
+            FocusChangedMetricHelper.getExtraBundle(
+                activity, PartnerCustomizationLayout.this, hasFocus));
+  }
 }
+
diff --git a/main/java/com/google/android/setupcompat/internal/ExecutorProvider.java b/main/java/com/google/android/setupcompat/internal/ExecutorProvider.java
index 3c707ae..28ced66 100644
--- a/main/java/com/google/android/setupcompat/internal/ExecutorProvider.java
+++ b/main/java/com/google/android/setupcompat/internal/ExecutorProvider.java
@@ -33,7 +33,6 @@
 public final class ExecutorProvider<T extends Executor> {
 
   private static final int SETUP_METRICS_LOGGER_MAX_QUEUED = 50;
-  private static final int SETUP_COMPAT_BINDBACK_MAX_QUEUED = 1;
   /**
    * Creates a single threaded {@link ExecutorService} with a maximum pool size {@code maxSize}.
    * Jobs submitted when the pool is full causes {@link
@@ -43,11 +42,6 @@
       new ExecutorProvider<>(
           createSizeBoundedExecutor("SetupCompatServiceInvoker", SETUP_METRICS_LOGGER_MAX_QUEUED));
 
-  public static final ExecutorProvider<ExecutorService> setupCompatExecutor =
-      new ExecutorProvider<>(
-          createSizeBoundedExecutor(
-              "SetupBindbackServiceExecutor", SETUP_COMPAT_BINDBACK_MAX_QUEUED));
-
   private final T executor;
 
   @Nullable private T injectedExecutor;
diff --git a/main/java/com/google/android/setupcompat/internal/FocusChangedMetricHelper.java b/main/java/com/google/android/setupcompat/internal/FocusChangedMetricHelper.java
new file mode 100644
index 0000000..39ade19
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/internal/FocusChangedMetricHelper.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 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.google.android.setupcompat.internal;
+
+import android.app.Activity;
+import android.os.Bundle;
+import androidx.annotation.StringDef;
+import com.google.android.setupcompat.internal.FocusChangedMetricHelper.Constants.ExtraKey;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A help class response to generate extra bundle and capture screen name for interruption metric.
+ */
+public class FocusChangedMetricHelper {
+  private FocusChangedMetricHelper() {}
+
+  public static final String getScreenName(Activity activity) {
+    return activity.getComponentName().toShortString();
+  }
+
+  public static final Bundle getExtraBundle(
+      Activity activity, TemplateLayout layout, boolean hasFocus) {
+    Bundle bundle = new Bundle();
+
+    bundle.putString(ExtraKey.PACKAGE_NAME, activity.getComponentName().getPackageName());
+    bundle.putString(ExtraKey.SCREEN_NAME, activity.getComponentName().getShortClassName());
+    bundle.putInt(ExtraKey.HASH_CODE, layout.hashCode());
+    bundle.putBoolean(ExtraKey.HAS_FOCUS, hasFocus);
+    bundle.putLong(ExtraKey.TIME_IN_MILLIS, System.currentTimeMillis());
+
+    return bundle;
+  }
+
+  /**
+   * Constant values used by {@link
+   * com.google.android.setupcompat.internal.FocusChangedMetricHelper}.
+   */
+  public static final class Constants {
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef({
+      ExtraKey.PACKAGE_NAME,
+      ExtraKey.SCREEN_NAME,
+      ExtraKey.HASH_CODE,
+      ExtraKey.HAS_FOCUS,
+      ExtraKey.TIME_IN_MILLIS
+    })
+    public @interface ExtraKey {
+
+      /** This key will be used to save the package name. */
+      String PACKAGE_NAME = "packageName";
+
+      /** This key will be used to save the activity name. */
+      String SCREEN_NAME = "screenName";
+
+      /**
+       * This key will be used to save the has code of {@link
+       * com.google.android.setupcompat.PartnerCustomizationLayout}.
+       */
+      String HASH_CODE = "hash";
+
+      /**
+       * This key will be used to save whether the window which is including the {@link
+       * com.google.android.setupcompat.PartnerCustomizationLayout}. has focus or not.
+       */
+      String HAS_FOCUS = "focus";
+
+      /** This key will be use to save the time stamp in milliseconds. */
+      String TIME_IN_MILLIS = "timeInMillis";
+    }
+
+    private Constants() {}
+  }
+}
diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
index 149da54..ed9c0e3 100644
--- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
+++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
@@ -54,12 +54,23 @@
 
   public void bindBack(String screenName, Bundle bundle) {
     try {
-      setupCompatExecutor.execute(() -> invokeBindBack(screenName, bundle));
+      loggingExecutor.execute(() -> invokeBindBack(screenName, bundle));
     } catch (RejectedExecutionException e) {
       LOG.e(String.format("Screen %s bind back fail.", screenName), e);
     }
   }
 
+  /**
+   * Help invoke the {@link ISetupCompatService#onFocusStatusChanged} using {@code loggingExecutor}.
+   */
+  public void onFocusStatusChanged(String screenName, Bundle bundle) {
+    try {
+      loggingExecutor.execute(() -> invokeOnWindowFocusChanged(screenName, bundle));
+    } catch (RejectedExecutionException e) {
+      LOG.e(String.format("Screen %s report focus changed failed.", screenName), e);
+    }
+  }
+
   private void invokeLogMetric(
       @MetricType int metricType, @SuppressWarnings("unused") Bundle args) {
     try {
@@ -76,6 +87,29 @@
     }
   }
 
+  private void invokeOnWindowFocusChanged(String screenName, Bundle bundle) {
+    try {
+      ISetupCompatService setupCompatService =
+          SetupCompatServiceProvider.get(
+              context, waitTimeInMillisForServiceConnection, TimeUnit.MILLISECONDS);
+      if (setupCompatService != null) {
+        setupCompatService.onFocusStatusChanged(bundle);
+      } else {
+        LOG.w(
+            "Report focusChange failed since service reference is null. Are the permission valid?");
+      }
+    } catch (InterruptedException
+        | TimeoutException
+        | RemoteException
+        | UnsupportedOperationException e) {
+      LOG.e(
+          String.format(
+              "Exception occurred while %s trying report windowFocusChange to SetupWizard.",
+              screenName),
+          e);
+    }
+  }
+
   private void invokeBindBack(String screenName, Bundle bundle) {
     try {
       ISetupCompatService setupCompatService =
@@ -96,14 +130,12 @@
   private SetupCompatServiceInvoker(Context context) {
     this.context = context;
     this.loggingExecutor = ExecutorProvider.setupCompatServiceInvoker.get();
-    this.setupCompatExecutor = ExecutorProvider.setupCompatExecutor.get();
     this.waitTimeInMillisForServiceConnection = MAX_WAIT_TIME_FOR_CONNECTION_MS;
   }
 
   private final Context context;
 
   private final ExecutorService loggingExecutor;
-  private final ExecutorService setupCompatExecutor;
   private final long waitTimeInMillisForServiceConnection;
 
   public static synchronized SetupCompatServiceInvoker get(Context context) {
diff --git a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
index ef2aa6b..476d45b 100644
--- a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
+++ b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
@@ -35,7 +35,6 @@
 import android.view.ViewGroup;
 import android.widget.Button;
 import androidx.annotation.ColorInt;
-import androidx.annotation.VisibleForTesting;
 import com.google.android.setupcompat.R;
 import com.google.android.setupcompat.internal.FooterButtonPartnerConfig;
 import com.google.android.setupcompat.internal.Preconditions;
@@ -429,7 +428,7 @@
     defaultTextColor.clear();
   }
 
-  @VisibleForTesting
+  /** Gets {@code GradientDrawable} from given {@code button}. */
   public static GradientDrawable getGradientDrawable(Button button) {
     // RippleDrawable is available after sdk 21, InsetDrawable#getDrawable is available after
     // sdk 19. So check the sdk is higher than sdk 21 and since Stencil customization provider only
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
index 3525fa1..3c35d29 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
@@ -60,6 +60,9 @@
       "isExtendedPartnerConfigEnabled";
 
   @VisibleForTesting
+  public static final String IS_MATERIAL_YOU_STYLE_ENABLED_METHOD = "IsMaterialYouStyleEnabled";
+
+  @VisibleForTesting
   public static final String IS_DYNAMIC_COLOR_ENABLED_METHOD = "isDynamicColorEnabled";
 
   @VisibleForTesting
@@ -69,6 +72,8 @@
 
   @VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null;
 
+  @VisibleForTesting public static Bundle applyMaterialYouConfigBundle = null;
+
   @VisibleForTesting public static Bundle applyDynamicColorBundle = null;
 
   @VisibleForTesting public static Bundle applyNeutralButtonStyleBundle = null;
@@ -570,6 +575,7 @@
     instance = null;
     suwDayNightEnabledBundle = null;
     applyExtendedPartnerConfigBundle = null;
+    applyMaterialYouConfigBundle = null;
     applyDynamicColorBundle = null;
     applyNeutralButtonStyleBundle = null;
   }
@@ -628,6 +634,29 @@
             IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD, false));
   }
 
+  /** Returns true if the SetupWizard is flow enabled "Material You(Glifv4)" style. */
+  public static boolean shouldApplyMaterialYouStyle(@NonNull Context context) {
+    if (applyMaterialYouConfigBundle == null) {
+      try {
+        applyMaterialYouConfigBundle =
+            context
+                .getContentResolver()
+                .call(
+                    getContentUri(),
+                    IS_MATERIAL_YOU_STYLE_ENABLED_METHOD,
+                    /* arg= */ null,
+                    /* extras= */ null);
+      } catch (IllegalArgumentException | SecurityException exception) {
+        Log.w(TAG, "SetupWizard Material You configs supporting status unknown; return as false.");
+        applyMaterialYouConfigBundle = null;
+        return false;
+      }
+    }
+
+    return (applyMaterialYouConfigBundle != null
+        && applyMaterialYouConfigBundle.getBoolean(IS_MATERIAL_YOU_STYLE_ENABLED_METHOD, false));
+  }
+
   /** Returns true if the SetupWizard supports the dynamic color during setup flow. */
   public static boolean isSetupWizardDynamicColorEnabled(@NonNull Context context) {
     if (applyDynamicColorBundle == null) {