From 6c88511ac5a52419b7bcf38d1a5ae01466721437 Mon Sep 17 00:00:00 2001 From: Setup Wizard Team Date: Fri, 12 Aug 2022 09:08:24 +0000 Subject: Import updated Android SetupCompat Library 467157122 Copied from google3/third_party/java_src/android_libs/setupcompat Test: mm Included changes: - 467157122 # Add partner Config - 461549997 Create the get default theme api on setupcompat - 458989792 Clear saved default text color before set the default tex... - 453366665 Should check the apply dynamic color before set the defau... - 450569563 Fix the footer button reverse after vertical footer button. PiperOrigin-RevId: 467157122 Bug: 242683307 Change-Id: I248c17ca9bc4565c43990447ce965b65fc2c5a5a --- .../setupcompat/template/FooterBarMixin.java | 13 ++-- .../android/setupcompat/view/ButtonBarLayout.java | 79 ++++++++++++++++++++-- .../setupcompat/partnerconfig/PartnerConfig.java | 14 ++++ .../partnerconfig/PartnerConfigHelper.java | 37 +++++++++- .../partnerconfig/PartnerConfigKey.java | 16 +++++ 5 files changed, 148 insertions(+), 11 deletions(-) diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java index 4d2a0c9..b77eacf 100644 --- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java @@ -174,6 +174,8 @@ public class FooterBarMixin implements Mixin { TemplateLayout layout, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { context = layout.getContext(); footerStub = layout.findManagedViewById(R.id.suc_layout_footer); + FooterButtonStyleUtils.clearSavedDefaultTextColor(); + this.applyPartnerResources = layout instanceof PartnerCustomizationLayout && ((PartnerCustomizationLayout) layout).shouldApplyPartnerResource(); @@ -224,7 +226,6 @@ public class FooterBarMixin implements Mixin { /* isVisible= */ true, /* isUsingXml= */ true); } - FooterButtonStyleUtils.clearSavedDefaultTextColor(); } protected boolean isFooterButtonAlignedEnd() { @@ -581,10 +582,12 @@ public class FooterBarMixin implements Mixin { @CallSuper protected void onFooterButtonInflated(Button button, @ColorInt int defaultButtonBackgroundColor) { // Try to set default background - if (defaultButtonBackgroundColor != 0) { - FooterButtonStyleUtils.updateButtonBackground(button, defaultButtonBackgroundColor); - } else { - // TODO: get button background color from activity theme + if (!applyDynamicColor) { + if (defaultButtonBackgroundColor != 0) { + FooterButtonStyleUtils.updateButtonBackground(button, defaultButtonBackgroundColor); + } else { + // TODO: get button background color from activity theme + } } buttonContainer.addView(button); autoSetButtonBarVisibility(); diff --git a/main/java/com/google/android/setupcompat/view/ButtonBarLayout.java b/main/java/com/google/android/setupcompat/view/ButtonBarLayout.java index 1157fae..1ef7b39 100644 --- a/main/java/com/google/android/setupcompat/view/ButtonBarLayout.java +++ b/main/java/com/google/android/setupcompat/view/ButtonBarLayout.java @@ -24,6 +24,9 @@ import android.widget.LinearLayout; import com.google.android.setupcompat.R; import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; import com.google.android.setupcompat.template.FooterActionButton; +import com.google.android.setupcompat.util.Logger; +import java.util.ArrayList; +import java.util.Collections; /** * An extension of LinearLayout that automatically switches to vertical orientation when it can't @@ -33,6 +36,8 @@ import com.google.android.setupcompat.template.FooterActionButton; */ public class ButtonBarLayout extends LinearLayout { + private static final Logger LOG = new Logger(ButtonBarLayout.class); + private boolean stacked = false; private int originalPaddingLeft; private int originalPaddingRight; @@ -82,6 +87,8 @@ public class ButtonBarLayout extends LinearLayout { return; } this.stacked = stacked; + boolean isUnstack = false; + int primaryStyleButtonCount = 0; int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); @@ -94,16 +101,34 @@ public class ButtonBarLayout extends LinearLayout { Float weight = (Float) child.getTag(R.id.suc_customization_original_weight); if (weight != null) { childParams.weight = weight; + } else { + // If the tag in the child is gone, it will be unstack and the child in the container will + // be disorder. + isUnstack = true; + } + if (isPrimaryButtonStyle(child)) { + primaryStyleButtonCount++; } } child.setLayoutParams(childParams); } setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL); - - // Reverse the child order, so that the primary button is towards the top when vertical - for (int i = childCount - 1; i >= 0; i--) { - bringChildToFront(getChildAt(i)); + if (isUnstack) { + LOG.w("Reorder the FooterActionButtons in the container"); + ArrayList childViewsInContainerInOrder = + getChildViewsInContainerInOrder( + childCount, /* isOnePrimaryButton= */ (primaryStyleButtonCount <= 1)); + for (int i = 0; i < childCount; i++) { + View view = childViewsInContainerInOrder.get(i); + if (view != null) { + bringChildToFront(view); + } + } + } else { + for (int i = childCount - 1; i >= 0; i--) { + bringChildToFront(getChildAt(i)); + } } if (stacked) { @@ -122,6 +147,52 @@ public class ButtonBarLayout extends LinearLayout { } } + private boolean isPrimaryButtonStyle(View child) { + return child instanceof FooterActionButton + && ((FooterActionButton) child).isPrimaryButtonStyle(); + } + + /** + * Return a array which store child views in the container and in the order (secondary button, + * space view, primary button), if only one primary button, the child views will replace null + * value in specific proper position, if there are two primary buttons, expected get the original + * child by the order (space view, secondary button, primary button), so insert the space view to + * the middle in the array. + */ + private ArrayList getChildViewsInContainerInOrder( + int childCount, boolean isOnePrimaryButton) { + int childViewsInContainerCount = 3; + int secondaryButtonIndex = 0; + int spaceViewIndex = 1; + int primaryButtonIndex = 2; + + ArrayList childFooterButtons = new ArrayList<>(); + + if (isOnePrimaryButton) { + childFooterButtons.addAll(Collections.nCopies(childViewsInContainerCount, null)); + } + + for (int i = 0; i < childCount; i++) { + View childAt = getChildAt(i); + if (isOnePrimaryButton) { + if (isPrimaryButtonStyle(childAt)) { + childFooterButtons.set(primaryButtonIndex, childAt); + } else if (!(childAt instanceof FooterActionButton)) { + childFooterButtons.set(spaceViewIndex, childAt); + } else { + childFooterButtons.set(secondaryButtonIndex, childAt); + } + } else { + if (!(childAt instanceof FooterActionButton)) { + childFooterButtons.add(spaceViewIndex, childAt); + } else { + childFooterButtons.add(getChildAt(i)); + } + } + } + return childFooterButtons; + } + private boolean isFooterButtonsEventlyWeighted(Context context) { int childCount = getChildCount(); int primayButtonCount = 0; diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java index c9a1966..ea0dfd7 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java @@ -251,6 +251,20 @@ public enum PartnerConfig { CONFIG_DESCRIPTION_TEXT_MARGIN_BOTTOM( PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_BOTTOM, ResourceType.DIMENSION), + // Font size of the account name + CONFIG_ACCOUNT_NAME_TEXT_SIZE( + PartnerConfigKey.KEY_ACCOUNT_NAME_TEXT_SIZE, ResourceType.DIMENSION), + + // Font family of the account name + CONFIG_ACCOUNT_NAME_FONT_FAMILY(PartnerConfigKey.KEY_ACCOUNT_NAME_FONT_FAMILY, ResourceType.STRING), + + // Margin end of the account avatar + CONFIG_ACCOUNT_AVATAR_MARGIN_END( + PartnerConfigKey.KEY_ACCOUNT_AVATAR_MARGIN_END, ResourceType.DIMENSION), + + // Size of account avatar + CONFIG_ACCOUNT_AVATAR_SIZE(PartnerConfigKey.KEY_ACCOUNT_AVATAR_MAX_SIZE, ResourceType.DIMENSION), + // Text size of the body content text CONFIG_CONTENT_TEXT_SIZE(PartnerConfigKey.KEY_CONTENT_TEXT_SIZE, ResourceType.DIMENSION), diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java index aca9a07..0db37ae 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java @@ -69,6 +69,9 @@ public class PartnerConfigHelper { @VisibleForTesting public static final String IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD = "isNeutralButtonStyleEnabled"; + @VisibleForTesting + public static final String GET_SUW_DEFAULT_THEME_STRING_METHOD = "suwDefaultThemeString"; + @VisibleForTesting public static final String SUW_PACKAGE_NAME = "com.google.android.setupwizard"; @VisibleForTesting public static final String MATERIAL_YOU_RESOURCE_SUFFIX = "_material_you"; @@ -82,6 +85,8 @@ public class PartnerConfigHelper { @VisibleForTesting public static Bundle applyNeutralButtonStyleBundle = null; + @VisibleForTesting public static Bundle suwDefaultThemeBundle = null; + private static PartnerConfigHelper instance = null; @VisibleForTesting Bundle resultBundle = null; @@ -93,8 +98,7 @@ public class PartnerConfigHelper { private static int savedConfigUiMode; - @VisibleForTesting - public static int savedOrientation = Configuration.ORIENTATION_PORTRAIT; + @VisibleForTesting public static int savedOrientation = Configuration.ORIENTATION_PORTRAIT; /** * When testing related to fake PartnerConfigHelper instance, should sync the following saved @@ -622,6 +626,7 @@ public class PartnerConfigHelper { applyMaterialYouConfigBundle = null; applyDynamicColorBundle = null; applyNeutralButtonStyleBundle = null; + suwDefaultThemeBundle = null; } /** @@ -711,6 +716,34 @@ public class PartnerConfigHelper { && applyMaterialYouConfigBundle.getBoolean(IS_MATERIAL_YOU_STYLE_ENABLED_METHOD, false)); } + /** + * Returns default glif theme name string from setupwizard, or if the setupwizard has not + * supported this api, return a null string. + */ + @Nullable + public static String getSuwDefaultThemeString(@NonNull Context context) { + if (suwDefaultThemeBundle == null || suwDefaultThemeBundle.isEmpty()) { + try { + suwDefaultThemeBundle = + context + .getContentResolver() + .call( + getContentUri(), + GET_SUW_DEFAULT_THEME_STRING_METHOD, + /* arg= */ null, + /* extras= */ null); + } catch (IllegalArgumentException | SecurityException exception) { + Log.w(TAG, "SetupWizard default theme status unknown; return as null."); + suwDefaultThemeBundle = null; + return null; + } + } + if (suwDefaultThemeBundle == null || suwDefaultThemeBundle.isEmpty()) { + return null; + } + return suwDefaultThemeBundle.getString(GET_SUW_DEFAULT_THEME_STRING_METHOD); + } + /** Returns true if the SetupWizard supports the dynamic color during setup flow. */ public static boolean isSetupWizardDynamicColorEnabled(@NonNull Context context) { if (applyDynamicColorBundle == null) { diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java index 9554ff3..a5f9c3a 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java @@ -86,6 +86,10 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_DESCRIPTION_LINK_FONT_FAMILY, PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_TOP, PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_BOTTOM, + PartnerConfigKey.KEY_ACCOUNT_NAME_TEXT_SIZE, + PartnerConfigKey.KEY_ACCOUNT_NAME_FONT_FAMILY, + PartnerConfigKey.KEY_ACCOUNT_AVATAR_MARGIN_END, + PartnerConfigKey.KEY_ACCOUNT_AVATAR_MAX_SIZE, PartnerConfigKey.KEY_CONTENT_TEXT_SIZE, PartnerConfigKey.KEY_CONTENT_TEXT_COLOR, PartnerConfigKey.KEY_CONTENT_LINK_TEXT_COLOR, @@ -338,6 +342,18 @@ public @interface PartnerConfigKey { // Margin bottom of the header text String KEY_DESCRIPTION_TEXT_MARGIN_BOTTOM = "setup_design_description_text_margin_bottom"; + // Font size of the account name + String KEY_ACCOUNT_NAME_TEXT_SIZE = "setup_design_account_name_text_size"; + + // Font family of the account name + String KEY_ACCOUNT_NAME_FONT_FAMILY = "setup_design_account_name_font_family"; + + // Margin end of the account avatar + String KEY_ACCOUNT_AVATAR_MARGIN_END = "setup_design_account_avatar_margin_end"; + + // Size of the account avatar + String KEY_ACCOUNT_AVATAR_MAX_SIZE = "setup_design_account_avatar_size"; + // Text size of the body content text String KEY_CONTENT_TEXT_SIZE = "setup_design_content_text_size"; -- cgit v1.2.3 From 2bc2bf601f1ef043a90a2c32e7b324efc247f095 Mon Sep 17 00:00:00 2001 From: Setup Wizard Team Date: Tue, 30 Aug 2022 13:20:01 +0800 Subject: Import updated Android SetupCompat Library 470898494 Copied from google3/third_party/java_src/android_libs/setupcompat Test: mm Included changes: - 470898494 Update javadoc for isAnySetupWizard and isPortalSetupWiza... Bug: 245264183 PiperOrigin-RevId: 470898494 Change-Id: I62825661a5b260c3612420536e4d830341886a9c --- .../google/android/setupcompat/util/WizardManagerHelper.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java index 90de25e..f28cd6d 100644 --- a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java +++ b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java @@ -186,7 +186,8 @@ public final class WizardManagerHelper { } /** - * Checks whether an intent is running in the portal setup wizard flow. + * Checks whether an intent is running in the portal setup wizard flow. This API is supported + * since S. * * @param originalIntent The original intent that was used to start the step, usually via {@link * Activity#getIntent()}. @@ -230,8 +231,11 @@ public final class WizardManagerHelper { } /** - * Returns true if the intent passed in indicates that it is running in any setup wizard flow, - * including initial setup and deferred setup etc. + * Since Q, returns true if the intent passed in indicates that it is running in setup wizard + * flows, including initial, predeferred, deferred. Since S, it also supports portal setup. + * + *

Pre-Q, it is running in three setup wizard flows, including initial, predeferred, deferred + * setup. * * @param originalIntent The original intent that was used to start the step, usually via {@link * Activity#getIntent()}. -- cgit v1.2.3 From d1ac9605f7a457bab0ef9440383280df03c39f17 Mon Sep 17 00:00:00 2001 From: Setup Wizard Team Date: Tue, 18 Oct 2022 21:32:23 +0800 Subject: Import updated Android SetupCompat Library 481904900 Copied from google3/third_party/java_src/android_libs/setupcompat Test: mm Included changes: - 481904900 Fetch setup wizard session id and expose it to legacy apis - 477084524 [Quick start] add ENABLE_QUICK_START_FLOW_FOR_DEBUG flag ... PiperOrigin-RevId: 481904900 Bug:256049980 Change-Id: Ie7623a4b6fd2101fb6acd5be7584a9e4cbf0e3e4 --- .../android/setupcompat/util/BuildCompatUtils.java | 33 ++++++++++++++-------- .../partnerconfig/PartnerConfigHelper.java | 1 - 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java index 3c4e2a2..090e1df 100644 --- a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java +++ b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java @@ -34,6 +34,7 @@ public final class BuildCompatUtils { public static boolean isAtLeastR() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R; } + /** * Implementation of BuildCompat.isAtLeastS() suitable for use in Setup * @@ -44,6 +45,16 @@ public final class BuildCompatUtils { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S; } + /** + * Implementation of BuildCompat.isAtLeastT() suitable for use in Setup + * + * @return Whether the current OS version is higher or equal to T. + */ + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU) + public static boolean isAtLeastT() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU; + } + /** * Implementation of BuildCompat.isAtLeast*() suitable for use in Setup * @@ -60,26 +71,24 @@ public final class BuildCompatUtils { *

Supported configurations: * *

    - *
  • For current Android release: while new API is not finalized yet (CODENAME = "Tiramisu", - * SDK_INT = 33) - *
  • For current Android release: when new API is finalized (CODENAME = "REL", SDK_INT = 32) - *
  • For next Android release (CODENAME = "U", SDK_INT = 34+) + *
  • For current Android release: while new API is not finalized yet (CODENAME = + * "UpsideDownCake", SDK_INT = 33) + *
  • For current Android release: when new API is finalized (CODENAME = "REL", SDK_INT = 34) + *
  • For next Android release (CODENAME = "V", SDK_INT = 35+) *
* *

Note that Build.VERSION_CODES.T cannot be used here until final SDK is available in all * channels, because it is equal to Build.VERSION_CODES.CUR_DEVELOPMENT before API finalization. * - * @return Whether the current OS version is higher or equal to T. + * @return Whether the current OS version is higher or equal to U. */ - public static boolean isAtLeastT() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - return true; - } - return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 33) + public static boolean isAtLeastU() { + System.out.println("Build.VERSION.CODENAME=" + Build.VERSION.CODENAME); + return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 34) || (Build.VERSION.CODENAME.length() == 1 - && Build.VERSION.CODENAME.charAt(0) >= 'T' + && Build.VERSION.CODENAME.charAt(0) >= 'U' && Build.VERSION.CODENAME.charAt(0) <= 'Z') - || (Build.VERSION.CODENAME.equals("Tiramisu") && Build.VERSION.SDK_INT >= 32); + || (Build.VERSION.CODENAME.equals("UpsideDownCake") && Build.VERSION.SDK_INT >= 33); } private BuildCompatUtils() {} diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java index 0db37ae..1b73098 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java @@ -46,7 +46,6 @@ public class PartnerConfigHelper { private static final String TAG = PartnerConfigHelper.class.getSimpleName(); - @VisibleForTesting public static final String SUW_AUTHORITY = "com.google.android.setupwizard.partner"; @VisibleForTesting public static final String SUW_GET_PARTNER_CONFIG_METHOD = "getOverlayConfig"; -- cgit v1.2.3 From 5d28e004e26cfac3ef07e30e33608305e14ce3dc Mon Sep 17 00:00:00 2001 From: Alex Li Date: Thu, 3 Nov 2022 07:10:31 +0000 Subject: [SetupCompat] Add proguard_flag_files for compat library. Bug: 254388464 Bug: 258123637 Test: manual Change-Id: I0f568d846c0c58b9f31a7a75bc08c473ec80723d --- Android.bp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Android.bp b/Android.bp index d3a42a7..26a0270 100644 --- a/Android.bp +++ b/Android.bp @@ -84,4 +84,7 @@ android_library { ], min_sdk_version: "14", sdk_version: "current", + optimize: { + proguard_flags_files: ["proguard.flags"], + } } -- cgit v1.2.3 From f924416e1c6459f212064e1c593d9072b080fa0a Mon Sep 17 00:00:00 2001 From: Setup Wizard Team Date: Wed, 30 Nov 2022 09:16:03 +0800 Subject: Import updated Android SetupCompat Library 491776566 Copied from google3/third_party/java_src/android_libs/setupcompat Test: mm Included changes: - 491776566 [Bts] Verify calling application permission and signature. - 487151605 [BTS] Basic Abstract class interface without verify caller PiperOrigin-RevId: 491776566 Change-Id: I62825661a5b260c3612420536e4d830341886a9c --- .../android/setupcompat/bts/IBtsTaskService.aidl | 38 +++ .../setupcompat/bts/IBtsTaskServiceCallback.aidl | 33 +++ .../setupcompat/bts/AbstractSetupBtsReceiver.java | 68 +++++ .../setupcompat/bts/AbstractSetupBtsService.java | 282 +++++++++++++++++++++ .../setupcompat/internal/Preconditions.java | 10 + 5 files changed, 431 insertions(+) create mode 100644 bts/aidl/com/google/android/setupcompat/bts/IBtsTaskService.aidl create mode 100644 bts/aidl/com/google/android/setupcompat/bts/IBtsTaskServiceCallback.aidl create mode 100644 bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsReceiver.java create mode 100644 bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java diff --git a/bts/aidl/com/google/android/setupcompat/bts/IBtsTaskService.aidl b/bts/aidl/com/google/android/setupcompat/bts/IBtsTaskService.aidl new file mode 100644 index 0000000..1ab625f --- /dev/null +++ b/bts/aidl/com/google/android/setupcompat/bts/IBtsTaskService.aidl @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 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.bts; + +import com.google.android.setupcompat.bts.IBtsTaskServiceCallback; + +/** + * Declare the interface for BTS task service. + * + * The SetupWizard will bind BtsTaskService when specific event triggerred. The service callback + * using {@link IBtsTaskServiceCallback#onTaskFinished} to notify SetupWizard the task is already + * completed and SetupWizard will unbind the service. + * + * If the service can't be complete before end of SetupWizard, the SetupWizard still unbind the + * service since the background task is no longer helpful for SetupWizard. + */ +interface IBtsTaskService { + + /** + * Set the callback for the client to notify the job already completed and can + * be disconnected. + */ + oneway void setCallback(IBtsTaskServiceCallback callback) = 1; +} \ No newline at end of file diff --git a/bts/aidl/com/google/android/setupcompat/bts/IBtsTaskServiceCallback.aidl b/bts/aidl/com/google/android/setupcompat/bts/IBtsTaskServiceCallback.aidl new file mode 100644 index 0000000..c6ed3e5 --- /dev/null +++ b/bts/aidl/com/google/android/setupcompat/bts/IBtsTaskServiceCallback.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 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.bts; + +import android.os.Bundle; + +/** + * Declare the callback interface for BTS task service to notice the SUW the + * status of task service. + */ +interface IBtsTaskServiceCallback { + + /** + * Called when the task is finished. + * + * @param bundle The metrics bundle. + */ + void onTaskFinished(in Bundle bundle) = 1; +} \ No newline at end of file diff --git a/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsReceiver.java b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsReceiver.java new file mode 100644 index 0000000..9e15e30 --- /dev/null +++ b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsReceiver.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 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.bts; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import androidx.annotation.NonNull; +import com.google.android.setupcompat.internal.Preconditions; +import com.google.android.setupcompat.util.Logger; +import java.util.concurrent.Executor; + +/** Class to receive broadcast intent from SUW, and execute the client's task in the executor. */ +public abstract class AbstractSetupBtsReceiver extends BroadcastReceiver { + private static final Logger LOG = new Logger(AbstractSetupBtsReceiver.class); + + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null && getIntentAction().equals(intent.getAction())) { + Executor executor = getExecutor(); + String simpleClassName = this.getClass().getSimpleName(); + if (executor != null) { + executor.execute( + () -> { + Preconditions.ensureNotOnMainThread(simpleClassName + "::onStartTask"); + onStartTask(); + }); + } + } else { + LOG.w( + "[" + + this.getClass().getSimpleName() + + "] Unauthorized binder uid=" + + Binder.getCallingUid() + + ", intentAction=" + + (intent == null ? "(null)" : intent.getAction())); + } + } + + /** + * Gets the intent action that expected to execute the task. Use to avoid the receiver launch + * unexpectedly. + */ + @NonNull + protected abstract String getIntentAction(); + + /** Returns the executor used to execute the task. */ + @NonNull + protected abstract Executor getExecutor(); + + /** Tasks can be done before activity launched, in order to remove the loading before activity. */ + protected abstract void onStartTask(); +} diff --git a/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java new file mode 100644 index 0000000..c441b2d --- /dev/null +++ b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2022 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.bts; + +import android.app.Service; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; +import android.os.Binder; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import com.google.android.setupcompat.internal.Preconditions; +import com.google.android.setupcompat.util.Logger; +import java.util.concurrent.Executor; + +/** Class to handle service binding from SUW, and execute the client's task in the executor. */ +public abstract class AbstractSetupBtsService extends Service { + private static final Logger LOG = new Logger(AbstractSetupBtsService.class); + + private static final String SETUP_WIZARD_PACKAGE_NAME = "com.google.android.setupwizard"; + + private static final String BTS_STARTER_FOR_TEST = + "com.google.android.apps.setupwizard.sample.bts.starter"; + + private static final String SETUP_BTS_PERMISSION = "com.google.android.setupwizard.SETUP_BTS"; + + @VisibleForTesting + static final String SETUP_WIZARD_RELEASE_CERTIFICATE_STRING = + "308204433082032ba003020102020900c2e08746644a308d300d06092a864886f70d01010405003074310b300" + + "9060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d" + + "4d6f756e7461696e205669657731143012060355040a130b476f6f676c6520496e632e3110300e06035" + + "5040b1307416e64726f69643110300e06035504031307416e64726f6964301e170d3038303832313233" + + "313333345a170d3336303130373233313333345a3074310b30090603550406130255533113301106035" + + "50408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e2056696577311430" + + "12060355040a130b476f6f676c6520496e632e3110300e060355040b1307416e64726f69643110300e0" + + "6035504031307416e64726f696430820120300d06092a864886f70d01010105000382010d0030820108" + + "0282010100ab562e00d83ba208ae0a966f124e29da11f2ab56d08f58e2cca91303e9b754d372f640a71" + + "b1dcb130967624e4656a7776a92193db2e5bfb724a91e77188b0e6a47a43b33d9609b77183145ccdf7b" + + "2e586674c9e1565b1f4c6a5955bff251a63dabf9c55c27222252e875e4f8154a645f897168c0b1bfc61" + + "2eabf785769bb34aa7984dc7e2ea2764cae8307d8c17154d7ee5f64a51a44a602c249054157dc02cd5f" + + "5c0e55fbef8519fbe327f0b1511692c5a06f19d18385f5c4dbc2d6b93f68cc2979c70e18ab93866b3bd" + + "5db8999552a0e3b4c99df58fb918bedc182ba35e003c1b4b10dd244a8ee24fffd333872ab5221985eda" + + "b0fc0d0b145b6aa192858e79020103a381d93081d6301d0603551d0e04160414c77d8cc2211756259a7" + + "fd382df6be398e4d786a53081a60603551d2304819e30819b8014c77d8cc2211756259a7fd382df6be3" + + "98e4d786a5a178a4763074310b3009060355040613025553311330110603550408130a43616c69666f7" + + "26e6961311630140603550407130d4d6f756e7461696e205669657731143012060355040a130b476f6f" + + "676c6520496e632e3110300e060355040b1307416e64726f69643110300e06035504031307416e64726" + + "f6964820900c2e08746644a308d300c0603551d13040530030101ff300d06092a864886f70d01010405" + + "0003820101006dd252ceef85302c360aaace939bcff2cca904bb5d7a1661f8ae46b2994204d0ff4a68c" + + "7ed1a531ec4595a623ce60763b167297a7ae35712c407f208f0cb109429124d7b106219c084ca3eb3f9" + + "ad5fb871ef92269a8be28bf16d44c8d9a08e6cb2f005bb3fe2cb96447e868e731076ad45b33f6009ea1" + + "9c161e62641aa99271dfd5228c5c587875ddb7f452758d661f6cc0cccb7352e424cc4365c523532f732" + + "5137593c4ae341f4db41edda0d0b1071a7c440f0fe9ea01cb627ca674369d084bd2fd911ff06cdbf2cf" + + "a10dc0f893ae35762919048c7efc64c7144178342f70581c9de573af55b390dd7fdb9418631895d5f75" + + "9f30112687ff621410c069308a"; + + @VisibleForTesting + static final String SETUP_WIZARD_DEBUG_CERTIFICATE_STRING = + "308204a830820390a003020102020900d585b86c7dd34ef5300d06092a864886f70d0101040500308194310b3" + + "009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130" + + "d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b13" + + "07416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011" + + "613616e64726f696440616e64726f69642e636f6d301e170d3038303431353233333635365a170d3335" + + "303930313233333635365a308194310b3009060355040613025553311330110603550408130a43616c6" + + "9666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307" + + "416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f6" + + "9643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d30820120" + + "300d06092a864886f70d01010105000382010d00308201080282010100d6ce2e080abfe2314dd18db3c" + + "fd3185cb43d33fa0c74e1bdb6d1db8913f62c5c39df56f846813d65bec0f3ca426b07c5a8ed5a3990c1" + + "67e76bc999b927894b8f0b22001994a92915e572c56d2a301ba36fc5fc113ad6cb9e7435a16d23ab7df" + + "aeee165e4df1f0a8dbda70a869d516c4e9d051196ca7c0c557f175bc375f948c56aae86089ba44f8aa6" + + "a4dd9a7dbf2c0a352282ad06b8cc185eb15579eef86d080b1d6189c0f9af98b1c2ebd107ea45abdb68a" + + "3c7838a5e5488c76c53d40b121de7bbd30e620c188ae1aa61dbbc87dd3c645f2f55f3d4c375ec4070a9" + + "3f7151d83670c16a971abe5ef2d11890e1b8aef3298cf066bf9e6ce144ac9ae86d1c1b0f020103a381f" + + "c3081f9301d0603551d0e041604148d1cc5be954c433c61863a15b04cbc03f24fe0b23081c90603551d" + + "230481c13081be80148d1cc5be954c433c61863a15b04cbc03f24fe0b2a1819aa48197308194310b300" + + "9060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d" + + "4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b130" + + "7416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d01090116" + + "13616e64726f696440616e64726f69642e636f6d820900d585b86c7dd34ef5300c0603551d130405300" + + "30101ff300d06092a864886f70d0101040500038201010019d30cf105fb78923f4c0d7dd223233d4096" + + "7acfce00081d5bd7c6e9d6ed206b0e11209506416ca244939913d26b4aa0e0f524cad2bb5c6e4ca1016" + + "a15916ea1ec5dc95a5e3a010036f49248d5109bbf2e1e618186673a3be56daf0b77b1c229e3c255e3e8" + + "4c905d2387efba09cbf13b202b4e5a22c93263484a23d2fc29fa9f1939759733afd8aa160f4296c2d01" + + "63e8182859c6643e9c1962fa0c18333335bc090ff9a6b22ded1ad444229a539a94eefadabd065ced24b" + + "3e51e5dd7b66787bef12fe97fba484c423fb4ff8cc494c02f0f5051612ff6529393e8e46eac5bb21f27" + + "7c151aa5f2aa627d1e89da70ab6033569de3b9897bfff7ca9da3e1243f60b"; + + @VisibleForTesting IBtsTaskServiceCallback callback; + + @Nullable + @Override + public IBinder onBind(Intent intent) { + if (verifyIntentAction(intent)) { + return binder; + } else { + LOG.w( + "[" + + this.getClass().getSimpleName() + + "] Unauthorized binder uid=" + + Binder.getCallingUid() + + ", intentAction=" + + (intent == null ? "(null)" : intent.getAction())); + return null; + } + } + + @Override + public boolean onUnbind(Intent intent) { + if (verifyIntentAction(intent)) { + callback = null; + } + return super.onUnbind(intent); + } + + private boolean verifyIntentAction(Intent intent) { + if (intent != null + && intent.getAction() != null + && intent.getAction().equals(getIntentAction())) { + return true; + } + + return false; + } + + /** + * Called when the task is finished. + * + * @param succeed whether the task success or not. + * @param failedReason A simple phrase to explain the failed reason. Like "No network". Null if + * task is success. + */ + protected void onTaskFinished(boolean succeed, @Nullable String failedReason) { + LOG.atDebug("onTaskFinished callback " + ((callback == null) ? "is null." : "is not null.")); + if (callback != null) { + try { + callback.onTaskFinished(Bundle.EMPTY); + } catch (RemoteException e) { + LOG.e( + "[" + this.getClass().getSimpleName() + "] Fail to invoke remove method onJobFinished"); + } + } + } + + /** + * Gets the intent action that expected to execute the task. Use to avoid the receiver launch + * unexpectedly. + */ + @NonNull + protected abstract String getIntentAction(); + + /** Returns the executor used to execute the task. */ + @NonNull + protected abstract Executor getExecutor(); + + /** Tasks can be done before activity launched, in order to remove the loading before activity. */ + protected abstract void onStartTask(); + + @VisibleForTesting + final IBtsTaskService.Stub binder = + new IBtsTaskService.Stub() { + @Override + public void setCallback(IBtsTaskServiceCallback callback) { + LOG.atDebug("setCallback called."); + if (verifyCallingApp()) { + AbstractSetupBtsService.this.callback = callback; + Executor executor = getExecutor(); + + if (executor != null) { + executor.execute( + () -> { + Preconditions.ensureNotOnMainThread( + AbstractSetupBtsService.this.getClass().getSimpleName() + "::onStartTask"); + onStartTask(); + }); + } + } else { + if (callback != null) { + try { + callback.onTaskFinished(Bundle.EMPTY); + } catch (RemoteException e) { + LOG.e("Error occurred while invoke remote method onTaskFinished"); + } + } + LOG.e( + "BTS service bound with untrusted application, callingUid=" + + Binder.getCallingUid()); + } + } + }; + + @VisibleForTesting + boolean verifyCallingApp() { + if (verifyCallingPackageName() && verifyCallingSignature() && verifyCallingAppPermission()) { + LOG.atInfo("Trusted caller=" + getPackageManager().getNameForUid(Binder.getCallingUid())); + return true; + } else { + LOG.e("Untrusted caller=" + getPackageManager().getNameForUid(Binder.getCallingUid())); + return false; + } + } + + @VisibleForTesting + boolean verifyCallingPackageName() { + String packageName = getPackageManager().getNameForUid(Binder.getCallingUid()); + if (SETUP_WIZARD_PACKAGE_NAME.equals(packageName) || BTS_STARTER_FOR_TEST.equals(packageName)) { + LOG.atDebug("Package name match to SetupWizard"); + return true; + } else { + LOG.w("Untrusted package:" + packageName); + return false; + } + } + + @VisibleForTesting + boolean verifyCallingSignature() { + String packageName = getPackageManager().getNameForUid(Binder.getCallingUid()); + if (Build.VERSION.SDK_INT >= VERSION_CODES.P) { + try { + PackageInfo info = + getPackageManager() + .getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES); + for (Signature signature : info.signingInfo.getApkContentsSigners()) { + if (SETUP_WIZARD_RELEASE_CERTIFICATE_STRING.equals(signature.toCharsString()) + || SETUP_WIZARD_DEBUG_CERTIFICATE_STRING.equals(signature.toCharsString())) { + return true; + } + } + } catch (NameNotFoundException | NullPointerException e) { + LOG.e("Exception occurred while verify signature", e); + } + } else { + LOG.w("Signature verify is not support before Android P."); + return false; + } + + LOG.w("Signature not match to SetupWizard"); + return false; + } + + @VisibleForTesting + boolean verifyCallingAppPermission() { + int checkPermission = + checkPermission(SETUP_BTS_PERMISSION, Binder.getCallingPid(), Binder.getCallingUid()); + if (PackageManager.PERMISSION_GRANTED == checkPermission) { + LOG.atDebug( + "permission:" + + SETUP_BTS_PERMISSION + + ", grant pid=" + + Binder.getCallingPid() + + ", uid=" + + Binder.getCallingUid() + + ", checkPermission=" + + checkPermission); + return true; + } else { + return false; + } + } +} diff --git a/main/java/com/google/android/setupcompat/internal/Preconditions.java b/main/java/com/google/android/setupcompat/internal/Preconditions.java index 259377c..1346c24 100644 --- a/main/java/com/google/android/setupcompat/internal/Preconditions.java +++ b/main/java/com/google/android/setupcompat/internal/Preconditions.java @@ -61,4 +61,14 @@ public final class Preconditions { } throw new IllegalStateException(whichMethod + " must be called from the UI thread."); } + /** + * Ensure that this method is not called from the main thread, otherwise an exception will be + * thrown. + */ + public static void ensureNotOnMainThread(String whichMethod) { + if (Looper.myLooper() != Looper.getMainLooper()) { + return; + } + throw new IllegalThreadStateException(whichMethod + " cannot be called from the UI thread."); + } } -- cgit v1.2.3 From 4ad07dc499d36781c8bcbb6117d7da7acadc1774 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 6 Feb 2023 16:36:37 -0800 Subject: Fix isAtLeastU for post-U codenames isAtLeastU assumed that unknown codenames were a single letter, which hasn't been true since Tiramisu. Copy the implementation from the platform's frameworks/libs/modules-utils/java/com/android/modules/utils/build/SdkLevel.java. Test: atest --host SettingsGoogleRoboTests Bug: 264658905 Change-Id: I6311ffe2fce9f337df853b57de3e41b581e263ee --- .../android/setupcompat/util/BuildCompatUtils.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java index 090e1df..8e6009d 100644 --- a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java +++ b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java @@ -74,7 +74,7 @@ public final class BuildCompatUtils { *

  • For current Android release: while new API is not finalized yet (CODENAME = * "UpsideDownCake", SDK_INT = 33) *
  • For current Android release: when new API is finalized (CODENAME = "REL", SDK_INT = 34) - *
  • For next Android release (CODENAME = "V", SDK_INT = 35+) + *
  • For next Android release (CODENAME = "VanillaIceCream", SDK_INT = 35+) * * *

    Note that Build.VERSION_CODES.T cannot be used here until final SDK is available in all @@ -85,10 +85,18 @@ public final class BuildCompatUtils { public static boolean isAtLeastU() { System.out.println("Build.VERSION.CODENAME=" + Build.VERSION.CODENAME); return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 34) - || (Build.VERSION.CODENAME.length() == 1 - && Build.VERSION.CODENAME.charAt(0) >= 'U' - && Build.VERSION.CODENAME.charAt(0) <= 'Z') - || (Build.VERSION.CODENAME.equals("UpsideDownCake") && Build.VERSION.SDK_INT >= 33); + || isAtLeastPreReleaseCodename("UpsideDownCake"); + } + + private static boolean isAtLeastPreReleaseCodename(String codename) { + // Special case "REL", which means the build is not a pre-release build. + if (Build.VERSION.CODENAME.equals("REL")) { + return false; + } + + // Otherwise lexically compare them. Return true if the build codename is equal to or + // greater than the requested codename. + return Build.VERSION.CODENAME.compareTo(codename) >= 0; } private BuildCompatUtils() {} -- cgit v1.2.3 From 7606f5baa481222cbc229e1960c01b93e4034186 Mon Sep 17 00:00:00 2001 From: Setup Wizard Team Date: Tue, 7 Feb 2023 03:01:14 +0000 Subject: Import updated Android SetupCompat Library 507643535 Copied from google3/third_party/java_src/android_libs/setupcompat Test: mm Included changes: - 507643535 #setup_design_lib Add partner config of some padding widt... - 506214813 [BTS] Implement the BTS metric collection part. - 503473855 Modify ScreenKey interface (no breaking) and added some s... - 501596520 Set the default button alignment for tablet. - 501169727 fixed static check warning and javadoc error - 501142395 [Setup Metrics API] Added timestamp for setup metrics eve... - 500656391 Automated g4 rollback of changelist 499247612. - 500638673 Remove System.out log from isAtLeastU - 499736394 Implementation embedding screen in library - change the l... - 499399571 Added system property to allow set transition settings fr... - 499247612 Automated g4 rollback of changelist 499108484. - 499108484 Log layout type for loading layout - 498304141 [Setup Metrics Collection] remove package name length che... - 497903322 [Setup Metrics Collection] Implement SetupMetricsLogger a... - 495450627 [BTS] Allow SetupWizard debug key to execute BtsService t... PiperOrigin-RevId: 507643535 Change-Id: Ie761cb2cc3317a84b44e5058e8a3ee4d7c9c2a7d --- .../setupcompat/bts/AbstractSetupBtsService.java | 19 +- .../google/android/setupcompat/bts/Constants.java | 35 +++ .../setupcompat/PartnerCustomizationLayout.java | 32 ++- .../android/setupcompat/logging/ScreenKey.java | 178 +++++++++++++++ .../android/setupcompat/logging/SetupMetric.java | 254 +++++++++++++++++++++ .../setupcompat/logging/SetupMetricsLogger.java | 21 ++ .../logging/internal/MetricBundleConverter.java | 9 + .../internal/SetupMetricsLoggingConstants.java | 54 ++++- .../setupcompat/template/FooterBarMixin.java | 7 +- .../android/setupcompat/util/BuildCompatUtils.java | 1 - main/res/values/attrs.xml | 1 + .../partnerconfig/PartnerConfigHelper.java | 122 ++++++++++ 12 files changed, 720 insertions(+), 13 deletions(-) create mode 100644 bts/java/com/google/android/setupcompat/bts/Constants.java create mode 100644 main/java/com/google/android/setupcompat/logging/ScreenKey.java create mode 100644 main/java/com/google/android/setupcompat/logging/SetupMetric.java diff --git a/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java index c441b2d..eb10693 100644 --- a/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java +++ b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java @@ -108,8 +108,15 @@ public abstract class AbstractSetupBtsService extends Service { + "3e51e5dd7b66787bef12fe97fba484c423fb4ff8cc494c02f0f5051612ff6529393e8e46eac5bb21f27" + "7c151aa5f2aa627d1e89da70ab6033569de3b9897bfff7ca9da3e1243f60b"; + @VisibleForTesting boolean allowDebugKeys = false; + @VisibleForTesting IBtsTaskServiceCallback callback; + /** Allow debug signature calling app when developing stage. */ + protected void setAllowDebugKeys(boolean allowed) { + allowDebugKeys = allowed; + } + @Nullable @Override public IBinder onBind(Intent intent) { @@ -156,7 +163,10 @@ public abstract class AbstractSetupBtsService extends Service { LOG.atDebug("onTaskFinished callback " + ((callback == null) ? "is null." : "is not null.")); if (callback != null) { try { - callback.onTaskFinished(Bundle.EMPTY); + Bundle metricBundle = new Bundle(); + metricBundle.putBoolean(Constants.EXTRA_KEY_TASK_SUCCEED, succeed); + metricBundle.putString(Constants.EXTRA_KEY_TASK_FAILED_REASON, failedReason); + callback.onTaskFinished(metricBundle); } catch (RemoteException e) { LOG.e( "[" + this.getClass().getSimpleName() + "] Fail to invoke remove method onJobFinished"); @@ -225,7 +235,8 @@ public abstract class AbstractSetupBtsService extends Service { @VisibleForTesting boolean verifyCallingPackageName() { String packageName = getPackageManager().getNameForUid(Binder.getCallingUid()); - if (SETUP_WIZARD_PACKAGE_NAME.equals(packageName) || BTS_STARTER_FOR_TEST.equals(packageName)) { + if (SETUP_WIZARD_PACKAGE_NAME.equals(packageName) + || (allowDebugKeys && BTS_STARTER_FOR_TEST.equals(packageName))) { LOG.atDebug("Package name match to SetupWizard"); return true; } else { @@ -242,9 +253,11 @@ public abstract class AbstractSetupBtsService extends Service { PackageInfo info = getPackageManager() .getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES); + for (Signature signature : info.signingInfo.getApkContentsSigners()) { if (SETUP_WIZARD_RELEASE_CERTIFICATE_STRING.equals(signature.toCharsString()) - || SETUP_WIZARD_DEBUG_CERTIFICATE_STRING.equals(signature.toCharsString())) { + || (allowDebugKeys + && SETUP_WIZARD_DEBUG_CERTIFICATE_STRING.equals(signature.toCharsString()))) { return true; } } diff --git a/bts/java/com/google/android/setupcompat/bts/Constants.java b/bts/java/com/google/android/setupcompat/bts/Constants.java new file mode 100644 index 0000000..7adcc33 --- /dev/null +++ b/bts/java/com/google/android/setupcompat/bts/Constants.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 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.bts; + +/** Constant values used by {@link com.google.android.setupcompat.bts.AbstractSetupBtsService}. */ +public class Constants { + + /** + * The extra key for {@link AbstractSetupBtsService} to send the task result to SUW for metric + * collection. + */ + public static final String EXTRA_KEY_TASK_SUCCEED = "succeed"; + + /** + * The extra key for {@link com.google.android.setupcompat.bts.AbstractSetupBtsService} to send + * the failed reason to SUW for metric collection. + */ + public static final String EXTRA_KEY_TASK_FAILED_REASON = "failed_reason"; + + private Constants() {} +} diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java index 37cc358..21928c8 100644 --- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java +++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java @@ -76,6 +76,8 @@ public class PartnerCustomizationLayout extends TemplateLayout { private Activity activity; + private PersistableBundle layoutTypeBundle; + @CanIgnoreReturnValue public PartnerCustomizationLayout(Context context) { this(context, 0, 0); @@ -92,10 +94,6 @@ public class PartnerCustomizationLayout extends TemplateLayout { init(null, R.attr.sucLayoutTheme); } - @VisibleForTesting - final ViewTreeObserver.OnWindowFocusChangeListener windowFocusChangeListener = - this::onFocusChanged; - @CanIgnoreReturnValue public PartnerCustomizationLayout(Context context, AttributeSet attrs) { super(context, attrs); @@ -109,6 +107,10 @@ public class PartnerCustomizationLayout extends TemplateLayout { init(attrs, defStyleAttr); } + @VisibleForTesting + final ViewTreeObserver.OnWindowFocusChangeListener windowFocusChangeListener = + this::onFocusChanged; + private void init(AttributeSet attrs, int defStyleAttr) { if (isInEditMode()) { return; @@ -242,9 +244,15 @@ public class PartnerCustomizationLayout extends TemplateLayout { ? secondaryButton.getMetrics("SecondaryFooterButton") : PersistableBundle.EMPTY; + PersistableBundle layoutTypeMetrics = + (layoutTypeBundle != null) ? layoutTypeBundle : PersistableBundle.EMPTY; + PersistableBundle persistableBundle = PersistableBundles.mergeBundles( - footerBarMixin.getLoggingMetrics(), primaryButtonMetrics, secondaryButtonMetrics); + footerBarMixin.getLoggingMetrics(), + primaryButtonMetrics, + secondaryButtonMetrics, + layoutTypeMetrics); SetupMetricsLogger.logCustomEvent( getContext(), @@ -256,6 +264,20 @@ public class PartnerCustomizationLayout extends TemplateLayout { } } + /** + * PartnerCustomizationLayout is a template layout for different type of GlifLayout. + * This method allows each type of layout to report its "GlifLayoutType". + */ + public void setLayoutTypeMetrics(PersistableBundle bundle) { + this.layoutTypeBundle = bundle; + } + + /** Returns a {@link PersistableBundle} contains key "GlifLayoutType". */ + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + public PersistableBundle getLayoutTypeMetrics() { + return this.layoutTypeBundle; + } + public static Activity lookupActivityFromContext(Context context) { if (context instanceof Activity) { return (Activity) context; diff --git a/main/java/com/google/android/setupcompat/logging/ScreenKey.java b/main/java/com/google/android/setupcompat/logging/ScreenKey.java new file mode 100644 index 0000000..4fba32b --- /dev/null +++ b/main/java/com/google/android/setupcompat/logging/ScreenKey.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2022 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.logging; + +import static com.google.android.setupcompat.internal.Validations.assertLengthInRange; + +import android.content.Context; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import com.google.android.setupcompat.internal.Preconditions; +import com.google.android.setupcompat.util.ObjectUtils; +import java.util.regex.Pattern; + +/** + * A screen key represents a validated “string key” that is associated with the values reported by + * the API consumer. + */ +public class ScreenKey implements Parcelable { + + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SCREEN_KEY_BUNDLE_NAME_KEY = "ScreenKey_name"; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SCREEN_KEY_BUNDLE_PACKAGE_KEY = "ScreenKey_package"; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SCREEN_KEY_BUNDLE_VERSION_KEY = "ScreenKey_version"; + private static final int INVALID_VERSION = -1; + private static final int VERSION = 1; + + /** + * Creates a new instance of {@link ScreenKey}. + * + * @param name screen name to identify what the metric belongs to. It should be in the range of + * 5-50 characters, only alphanumeric characters are allowed. + * @param context context associated to metric screen, uses to generate package name. + */ + public static ScreenKey of(@NonNull String name, @NonNull Context context) { + Preconditions.checkNotNull(context, "Context can not be null."); + return ScreenKey.of(name, context.getPackageName()); + } + + private static ScreenKey of(@NonNull String name, @NonNull String packageName) { + Preconditions.checkArgument( + SCREEN_PACKAGENAME_PATTERN.matcher(packageName).matches(), + "Invalid ScreenKey#package, only alpha numeric characters are allowed."); + assertLengthInRange( + name, "ScreenKey.name", MIN_SCREEN_NAME_LENGTH, MAX_SCREEN_NAME_LENGTH); + Preconditions.checkArgument( + SCREEN_NAME_PATTERN.matcher(name).matches(), + "Invalid ScreenKey#name, only alpha numeric characters are allowed."); + + return new ScreenKey(name, packageName); + } + + /** + * Converts {@link ScreenKey} into {@link Bundle}. + * Throw {@link NullPointerException} if the screenKey is null. + */ + public static Bundle toBundle(ScreenKey screenKey) { + Preconditions.checkNotNull(screenKey, "ScreenKey cannot be null."); + Bundle bundle = new Bundle(); + bundle.putInt(SCREEN_KEY_BUNDLE_VERSION_KEY, VERSION); + bundle.putString(SCREEN_KEY_BUNDLE_NAME_KEY, screenKey.getName()); + bundle.putString(SCREEN_KEY_BUNDLE_PACKAGE_KEY, screenKey.getPackageName()); + return bundle; + } + + /** + * Converts {@link Bundle} into {@link ScreenKey}. + * Throw {@link NullPointerException} if the bundle is null. + * Throw {@link IllegalArgumentException} if the bundle version is unsupported. + */ + public static ScreenKey fromBundle(Bundle bundle) { + Preconditions.checkNotNull(bundle, "Bundle cannot be null"); + + int version = bundle.getInt(SCREEN_KEY_BUNDLE_VERSION_KEY, INVALID_VERSION); + if (version == 1) { + return ScreenKey.of( + bundle.getString(SCREEN_KEY_BUNDLE_NAME_KEY), + bundle.getString(SCREEN_KEY_BUNDLE_PACKAGE_KEY)); + } else { + // Invalid version + throw new IllegalArgumentException("Unsupported version: " + version); + } + } + + public static final Creator CREATOR = + new Creator<>() { + @Override + public ScreenKey createFromParcel(Parcel in) { + return new ScreenKey(in.readString(), in.readString()); + } + + @Override + public ScreenKey[] newArray(int size) { + return new ScreenKey[size]; + } + }; + + /** Returns the name of the screen key. */ + public String getName() { + return name; + } + + /** Returns the package name of the screen key. */ + public String getPackageName() { + return packageName; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeString(name); + parcel.writeString(packageName); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ScreenKey)) { + return false; + } + ScreenKey screenKey = (ScreenKey) o; + return ObjectUtils.equals(name, screenKey.name) + && ObjectUtils.equals(packageName, screenKey.packageName); + } + + @Override + public int hashCode() { + return ObjectUtils.hashCode(name, packageName); + } + + @NonNull + @Override + public String toString() { + return "ScreenKey {name=" + + getName() + + ", package=" + + getPackageName() + + "}"; + } + + private ScreenKey(String name, String packageName) { + this.name = name; + this.packageName = packageName; + } + + private final String name; + private final String packageName; + + private static final int MIN_SCREEN_NAME_LENGTH = 5; + private static final int MAX_SCREEN_NAME_LENGTH = 50; + private static final Pattern SCREEN_NAME_PATTERN = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]+"); + private static final Pattern SCREEN_PACKAGENAME_PATTERN = + Pattern.compile("^([a-z]+[.])+[a-zA-Z][a-zA-Z0-9]+"); +} diff --git a/main/java/com/google/android/setupcompat/logging/SetupMetric.java b/main/java/com/google/android/setupcompat/logging/SetupMetric.java new file mode 100644 index 0000000..4015a53 --- /dev/null +++ b/main/java/com/google/android/setupcompat/logging/SetupMetric.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2022 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.logging; + +import android.annotation.TargetApi; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import com.google.android.setupcompat.internal.ClockProvider; +import com.google.android.setupcompat.internal.PersistableBundles; +import com.google.android.setupcompat.internal.Preconditions; +import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.EventType; +import com.google.android.setupcompat.util.ObjectUtils; + +/** + * This class represents a setup metric event at a particular point in time. + * The event is identified by {@link EventType} along with a string name. It can include + * additional key-value pairs providing more attributes associated with the given event. Only + * primitive values are supported for now (int, long, boolean, String). + */ +@TargetApi(VERSION_CODES.Q) +public class SetupMetric implements Parcelable { + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SETUP_METRIC_BUNDLE_VERSION_KEY = "SetupMetric_version"; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SETUP_METRIC_BUNDLE_NAME_KEY = "SetupMetric_name"; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SETUP_METRIC_BUNDLE_TYPE_KEY = "SetupMetric_type"; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SETUP_METRIC_BUNDLE_VALUES_KEY = "SetupMetric_values"; + private static final int VERSION = 1; + private static final int INVALID_VERSION = -1; + + public static final String SETUP_METRIC_BUNDLE_OPTIN_KEY = "opt_in"; + public static final String SETUP_METRIC_BUNDLE_ERROR_KEY = "error"; + public static final String SETUP_METRIC_BUNDLE_TIMESTAMP_KEY = "timestamp"; + + + /** + * A convenient function to create a setup event with event type {@link EventType#IMPRESSION} + * @param name A name represents this impression + * @return A {@link SetupMetric} + * @throws IllegalArgumentException if the {@code name} is empty. + */ + @NonNull + public static SetupMetric ofImpression(@NonNull String name) { + Bundle bundle = new Bundle(); + bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); + return new SetupMetric(VERSION, name, EventType.IMPRESSION, + PersistableBundles.fromBundle(bundle)); + } + + /** + * A convenient function to create a setup event with event type {@link EventType#OPT_IN} + * @param name A name represents this opt-in + * @param status Opt-in status in {@code true} or {@code false} + * @return A {@link SetupMetric} + * @throws IllegalArgumentException if the {@code name} is empty. + */ + @NonNull + public static SetupMetric ofOptIn(@NonNull String name, boolean status) { + Bundle bundle = new Bundle(); + bundle.putBoolean(SETUP_METRIC_BUNDLE_OPTIN_KEY, status); + bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); + return new SetupMetric(VERSION, name, EventType.OPT_IN, PersistableBundles.fromBundle(bundle)); + } + + /** + * A convenient function to create a setup event with event type + * {@link EventType#WAITING_START} + * @param name A task name causes this waiting duration + * @return A {@link SetupMetric} + * @throws IllegalArgumentException if the {@code name} is empty. + */ + @NonNull + public static SetupMetric ofWaitingStart(@NonNull String name) { + Bundle bundle = new Bundle(); + bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); + return new SetupMetric(VERSION, name, EventType.WAITING_START, + PersistableBundles.fromBundle(bundle)); + } + + /** + * A convenient function to create a setup event with event type + * {@link EventType#WAITING_END} + * @param name A task name causes this waiting duration + * @return A {@link SetupMetric} + * @throws IllegalArgumentException if the {@code name} is empty. + */ + @NonNull + public static SetupMetric ofWaitingEnd(@NonNull String name) { + Bundle bundle = new Bundle(); + bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); + return new SetupMetric(VERSION, name, EventType.WAITING_END, + PersistableBundles.fromBundle(bundle)); + } + + /** + * A convenient function to create a setup event with event type {@link EventType#ERROR} + * @param name A name represents this error + * @param errorCode A error code + * @return A {@link SetupMetric} + * @throws IllegalArgumentException if the {@code name} is empty. + */ + @NonNull + public static SetupMetric ofError(@NonNull String name, int errorCode) { + Bundle bundle = new Bundle(); + bundle.putInt(SETUP_METRIC_BUNDLE_ERROR_KEY, errorCode); + bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); + return new SetupMetric(VERSION, name, EventType.ERROR, PersistableBundles.fromBundle(bundle)); + } + + /** Converts {@link SetupMetric} into {@link Bundle}. */ + @NonNull + public static Bundle toBundle(@NonNull SetupMetric setupMetric) { + Preconditions.checkNotNull(setupMetric, "SetupMetric cannot be null."); + Bundle bundle = new Bundle(); + bundle.putInt(SETUP_METRIC_BUNDLE_VERSION_KEY, VERSION); + bundle.putString(SETUP_METRIC_BUNDLE_NAME_KEY, setupMetric.name); + bundle.putInt(SETUP_METRIC_BUNDLE_TYPE_KEY, setupMetric.type); + bundle.putBundle( + SETUP_METRIC_BUNDLE_VALUES_KEY, PersistableBundles.toBundle(setupMetric.values)); + return bundle; + } + + /** + * Converts {@link Bundle} into {@link SetupMetric}. + * Throw {@link IllegalArgumentException} if the bundle version is unsupported. + */ + @NonNull + public static SetupMetric fromBundle(@NonNull Bundle bundle) { + Preconditions.checkNotNull(bundle, "Bundle cannot be null"); + int version = bundle.getInt(SETUP_METRIC_BUNDLE_VERSION_KEY, INVALID_VERSION); + if (version == 1) { + return new SetupMetric( + bundle.getInt(SETUP_METRIC_BUNDLE_VERSION_KEY), + bundle.getString(SETUP_METRIC_BUNDLE_NAME_KEY), + bundle.getInt(SETUP_METRIC_BUNDLE_TYPE_KEY), + PersistableBundles.fromBundle(bundle.getBundle(SETUP_METRIC_BUNDLE_VALUES_KEY))); + } else { + throw new IllegalArgumentException("Unsupported version: " + version); + } + } + + private SetupMetric( + int version, String name, @EventType int type, @NonNull PersistableBundle values) { + Preconditions.checkArgument( + name != null && name.length() != 0, + "name cannot be null or empty."); + this.version = version; + this.name = name; + this.type = type; + this.values = values; + } + + private final int version; + private final String name; + @EventType private final int type; + private final PersistableBundle values; + + public int getVersion() { + return version; + } + + public String getName() { + return name; + } + + @EventType + public int getType() { + return type; + } + + public PersistableBundle getValues() { + return values; + } + + public static final Creator CREATOR = + new Creator<>() { + @Override + public SetupMetric createFromParcel(@NonNull Parcel in) { + return new SetupMetric(in.readInt(), + in.readString(), + in.readInt(), + in.readPersistableBundle(SetupMetric.class.getClassLoader())); + } + + @Override + public SetupMetric[] newArray(int size) { + return new SetupMetric[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(name); + parcel.writeInt(type); + parcel.writePersistableBundle(values); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SetupMetric)) { + return false; + } + SetupMetric that = (SetupMetric) o; + return ObjectUtils.equals(name, that.name) + && ObjectUtils.equals(type, that.type) + && PersistableBundles.equals(values, that.values); + } + + @Override + public int hashCode() { + return ObjectUtils.hashCode(name, type, values); + } + + @NonNull + @Override + public String toString() { + return "SetupMetric {name=" + + getName() + + ", type=" + + getType() + + ", bundle=" + + getValues().toString() + + "}"; + } +} diff --git a/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java b/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java index 8d696e0..786494e 100644 --- a/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java +++ b/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java @@ -22,11 +22,14 @@ import com.google.android.setupcompat.internal.Preconditions; import com.google.android.setupcompat.internal.SetupCompatServiceInvoker; import com.google.android.setupcompat.logging.internal.MetricBundleConverter; import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricType; +import com.google.android.setupcompat.util.Logger; import java.util.concurrent.TimeUnit; /** SetupMetricsLogger provides an easy way to log custom metrics to SetupWizard. */ public class SetupMetricsLogger { + private static final Logger LOG = new Logger("SetupMetricsLogger"); + /** Logs an instance of {@link CustomEvent} to SetupWizard. */ public static void logCustomEvent(@NonNull Context context, @NonNull CustomEvent customEvent) { Preconditions.checkNotNull(context, "Context cannot be null."); @@ -71,4 +74,22 @@ public class SetupMetricsLogger { MetricType.DURATION_EVENT, MetricBundleConverter.createBundleForLoggingTimer(timerName, timeInMillis)); } + + /** + * Logs setup collection metrics (go/suw-metrics-collection-api) + */ + public static void logMetrics( + @NonNull Context context, @NonNull ScreenKey screenKey, @NonNull SetupMetric... metrics) { + Preconditions.checkNotNull(context, "Context cannot be null."); + Preconditions.checkNotNull(screenKey, "ScreenKey cannot be null."); + Preconditions.checkNotNull(metrics, "SetupMetric cannot be null."); + + for (SetupMetric metric : metrics) { + LOG.atDebug("Log metric: " + screenKey + ", " + metric); + + SetupCompatServiceInvoker.get(context).logMetricEvent( + MetricType.SETUP_COLLECTION_EVENT, + MetricBundleConverter.createBundleForLoggingSetupMetric(screenKey, metric)); + } + } } diff --git a/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java b/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java index e1a3909..8e5ba20 100644 --- a/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java +++ b/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java @@ -3,6 +3,8 @@ package com.google.android.setupcompat.logging.internal; import android.os.Bundle; import com.google.android.setupcompat.logging.CustomEvent; import com.google.android.setupcompat.logging.MetricKey; +import com.google.android.setupcompat.logging.ScreenKey; +import com.google.android.setupcompat.logging.SetupMetric; import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricBundleKeys; /** Collection of helper methods for reading and writing {@link CustomEvent}, {@link MetricKey}. */ @@ -28,6 +30,13 @@ public final class MetricBundleConverter { return bundle; } + public static Bundle createBundleForLoggingSetupMetric(ScreenKey screenKey, SetupMetric metric) { + Bundle bundle = new Bundle(); + bundle.putParcelable(MetricBundleKeys.SCREEN_KEY_BUNDLE, ScreenKey.toBundle(screenKey)); + bundle.putParcelable(MetricBundleKeys.SETUP_METRIC_BUNDLE, SetupMetric.toBundle(metric)); + return bundle; + } + private MetricBundleConverter() { throw new AssertionError("Cannot instantiate MetricBundleConverter"); } diff --git a/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java b/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java index 57a7272..d4995b7 100644 --- a/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java +++ b/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java @@ -20,6 +20,8 @@ import android.content.Context; import androidx.annotation.IntDef; import androidx.annotation.StringDef; import com.google.android.setupcompat.logging.MetricKey; +import com.google.android.setupcompat.logging.ScreenKey; +import com.google.android.setupcompat.logging.SetupMetric; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -28,7 +30,12 @@ public interface SetupMetricsLoggingConstants { /** Enumeration of supported metric types logged to SetupWizard. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({MetricType.CUSTOM_EVENT, MetricType.COUNTER_EVENT, MetricType.DURATION_EVENT}) + @IntDef({ + MetricType.CUSTOM_EVENT, + MetricType.DURATION_EVENT, + MetricType.COUNTER_EVENT, + MetricType.SETUP_COLLECTION_EVENT, + MetricType.INTERNAL}) @interface MetricType { /** * MetricType constant used when logging {@link @@ -47,10 +54,39 @@ public interface SetupMetricsLoggingConstants { */ int COUNTER_EVENT = 3; + /** + * MetricType constant used when logging setup metric using {@link + * com.google.android.setupcompat.logging.SetupMetricsLogger#logMetrics(Context, ScreenKey, + * SetupMetric...)}. + */ + int SETUP_COLLECTION_EVENT = 4; + /** MetricType constant used for internal logging purposes. */ int INTERNAL = 100; } + /** + * Enumeration of supported EventType of {@link MetricType#SETUP_COLLECTION_EVENT} logged to + * SetupWizard. (go/suw-metrics-collection-api) + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + EventType.UNKNOWN, + EventType.IMPRESSION, + EventType.OPT_IN, + EventType.WAITING_START, + EventType.WAITING_END, + EventType.ERROR, + }) + @interface EventType { + int UNKNOWN = 1; + int IMPRESSION = 2; + int OPT_IN = 3; + int WAITING_START = 4; + int WAITING_END = 5; + int ERROR = 6; + } + /** Keys of the bundle used while logging data to SetupWizard. */ @Retention(RetentionPolicy.SOURCE) @StringDef({ @@ -59,7 +95,9 @@ public interface SetupMetricsLoggingConstants { MetricBundleKeys.CUSTOM_EVENT, MetricBundleKeys.CUSTOM_EVENT_BUNDLE, MetricBundleKeys.TIME_MILLIS_LONG, - MetricBundleKeys.COUNTER_INT + MetricBundleKeys.COUNTER_INT, + MetricBundleKeys.SCREEN_KEY_BUNDLE, + MetricBundleKeys.SETUP_METRIC_BUNDLE, }) @interface MetricBundleKeys { /** @@ -104,5 +142,17 @@ public interface SetupMetricsLoggingConstants { * com.google.android.setupcompat.logging.CustomEvent}. */ String CUSTOM_EVENT_BUNDLE = "CustomEvent_bundle"; + + /** + * This key will be used when {@code metricType} is {@link MetricType#SETUP_COLLECTION_EVENT} + * with the value being a Bundle which can be used to read {@link ScreenKey} + */ + String SCREEN_KEY_BUNDLE = "ScreenKey_bundle"; + + /** + * This key will be used when {@code metricType} is {@link MetricType#SETUP_COLLECTION_EVENT} + * with the value being a Bundle which can be used to read {@link SetupMetric} + */ + String SETUP_METRIC_BUNDLE = "SetupMetric_bundle"; } } diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java index b77eacf..2268b1e 100644 --- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java @@ -73,6 +73,7 @@ public class FooterBarMixin implements Mixin { @VisibleForTesting final boolean applyPartnerResources; @VisibleForTesting final boolean applyDynamicColor; @VisibleForTesting final boolean useFullDynamicColor; + @VisibleForTesting final boolean footerButtonAlignEnd; @VisibleForTesting public LinearLayout buttonContainer; private FooterButton primaryButton; @@ -206,6 +207,8 @@ public class FooterBarMixin implements Mixin { a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterBackground, 0); footerBarSecondaryBackgroundColor = a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarSecondaryFooterBackground, 0); + footerButtonAlignEnd = + a.getBoolean(R.styleable.SucFooterBarMixin_sucFooterBarButtonAlignEnd, false); int primaryBtn = a.getResourceId(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterButton, 0); @@ -234,7 +237,7 @@ public class FooterBarMixin implements Mixin { return PartnerConfigHelper.get(context) .getBoolean(context, PartnerConfig.CONFIG_FOOTER_BUTTON_ALIGNED_END, false); } else { - return false; + return footerButtonAlignEnd; } } @@ -617,7 +620,7 @@ public class FooterBarMixin implements Mixin { return overrideTheme; } - @VisibleForTesting + /** Returns the {@link LinearLayout} of button container. */ public LinearLayout getButtonContainer() { return buttonContainer; } diff --git a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java index 090e1df..d1b0ccb 100644 --- a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java +++ b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java @@ -83,7 +83,6 @@ public final class BuildCompatUtils { * @return Whether the current OS version is higher or equal to U. */ public static boolean isAtLeastU() { - System.out.println("Build.VERSION.CODENAME=" + Build.VERSION.CODENAME); return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 34) || (Build.VERSION.CODENAME.length() == 1 && Build.VERSION.CODENAME.charAt(0) >= 'U' diff --git a/main/res/values/attrs.xml b/main/res/values/attrs.xml index 07f87ed..0aaea8b 100644 --- a/main/res/values/attrs.xml +++ b/main/res/values/attrs.xml @@ -82,6 +82,7 @@ + diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java index 1b73098..96a4317 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java @@ -68,12 +68,19 @@ public class PartnerConfigHelper { @VisibleForTesting public static final String IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD = "isNeutralButtonStyleEnabled"; + @VisibleForTesting + public static final String IS_EMBEDDED_ACTIVITY_ONE_PANE_ENABLED_METHOD = + "isEmbeddedActivityOnePaneEnabled"; + @VisibleForTesting public static final String GET_SUW_DEFAULT_THEME_STRING_METHOD = "suwDefaultThemeString"; @VisibleForTesting public static final String SUW_PACKAGE_NAME = "com.google.android.setupwizard"; @VisibleForTesting public static final String MATERIAL_YOU_RESOURCE_SUFFIX = "_material_you"; + @VisibleForTesting + public static final String EMBEDDED_ACTIVITY_RESOURCE_SUFFIX = "_embedded_activity"; + @VisibleForTesting static Bundle suwDayNightEnabledBundle = null; @VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null; @@ -84,6 +91,8 @@ public class PartnerConfigHelper { @VisibleForTesting public static Bundle applyNeutralButtonStyleBundle = null; + @VisibleForTesting public static Bundle applyEmbeddedActivityOnePaneBundle = null; + @VisibleForTesting public static Bundle suwDefaultThemeBundle = null; private static PartnerConfigHelper instance = null; @@ -97,8 +106,16 @@ public class PartnerConfigHelper { private static int savedConfigUiMode; + private static boolean savedConfigEmbeddedActivityMode; + + @VisibleForTesting static Bundle applyTransitionBundle = null; + @VisibleForTesting public static int savedOrientation = Configuration.ORIENTATION_PORTRAIT; + /** The method name to get if transition settings is set from client. */ + public static final String APPLY_GLIF_THEME_CONTROLLED_TRANSITION_METHOD = + "applyGlifThemeControlledTransition"; + /** * When testing related to fake PartnerConfigHelper instance, should sync the following saved * config with testing environment. @@ -117,6 +134,8 @@ public class PartnerConfigHelper { private static boolean isValidInstance(@NonNull Context context) { Configuration currentConfig = context.getResources().getConfiguration(); if (instance == null) { + savedConfigEmbeddedActivityMode = + isEmbeddedActivityOnePaneEnabled(context) && BuildCompatUtils.isAtLeastU(); savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; savedOrientation = currentConfig.orientation; savedScreenWidth = currentConfig.screenWidthDp; @@ -126,7 +145,10 @@ public class PartnerConfigHelper { boolean uiModeChanged = isSetupWizardDayNightEnabled(context) && (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != savedConfigUiMode; + boolean embeddedActivityModeChanged = + isEmbeddedActivityOnePaneEnabled(context) && BuildCompatUtils.isAtLeastU(); if (uiModeChanged + || embeddedActivityModeChanged != savedConfigEmbeddedActivityMode || currentConfig.orientation != savedOrientation || currentConfig.screenWidthDp != savedScreenWidth || currentConfig.screenHeightDp != savedScreenHeight) { @@ -554,6 +576,11 @@ public class PartnerConfigHelper { ResourceEntry adjustResourceEntry = adjustResourceEntryDefaultValue( context, ResourceEntry.fromBundle(context, resourceEntryBundle)); + ; + if (BuildCompatUtils.isAtLeastU() && isEmbeddedActivityOnePaneEnabled(context)) { + adjustResourceEntry = embeddedActivityResourceEntryDefaultValue(context, adjustResourceEntry); + } + return adjustResourceEntryDayNightMode(context, adjustResourceEntry); } @@ -617,6 +644,42 @@ public class PartnerConfigHelper { return inputResourceEntry; } + // Check the embedded acitvity flag and replace the inputResourceEntry.resourceName & + // inputResourceEntry.resourceId after U. + ResourceEntry embeddedActivityResourceEntryDefaultValue( + Context context, ResourceEntry inputResourceEntry) { + // If not overlay resource + try { + if (SUW_PACKAGE_NAME.equals(inputResourceEntry.getPackageName())) { + String resourceTypeName = + inputResourceEntry + .getResources() + .getResourceTypeName(inputResourceEntry.getResourceId()); + // try to update resourceName & resourceId + String embeddedActivityResourceName = + inputResourceEntry.getResourceName().concat(EMBEDDED_ACTIVITY_RESOURCE_SUFFIX); + int embeddedActivityResourceId = + inputResourceEntry + .getResources() + .getIdentifier( + embeddedActivityResourceName, + resourceTypeName, + inputResourceEntry.getPackageName()); + if (embeddedActivityResourceId != 0) { + Log.i(TAG, "use embedded activity resource:" + embeddedActivityResourceName); + return new ResourceEntry( + inputResourceEntry.getPackageName(), + embeddedActivityResourceName, + embeddedActivityResourceId, + inputResourceEntry.getResources()); + } + } + } catch (NotFoundException ex) { + // fall through + } + return inputResourceEntry; + } + @VisibleForTesting public static synchronized void resetInstance() { instance = null; @@ -625,7 +688,9 @@ public class PartnerConfigHelper { applyMaterialYouConfigBundle = null; applyDynamicColorBundle = null; applyNeutralButtonStyleBundle = null; + applyEmbeddedActivityOnePaneBundle = null; suwDefaultThemeBundle = null; + applyTransitionBundle = null; } /** @@ -766,6 +831,32 @@ public class PartnerConfigHelper { && applyDynamicColorBundle.getBoolean(IS_DYNAMIC_COLOR_ENABLED_METHOD, false)); } + /** Returns true if the SetupWizard supports the one-pane embedded activity during setup flow. */ + public static boolean isEmbeddedActivityOnePaneEnabled(@NonNull Context context) { + if (applyEmbeddedActivityOnePaneBundle == null) { + try { + applyEmbeddedActivityOnePaneBundle = + context + .getContentResolver() + .call( + getContentUri(), + IS_EMBEDDED_ACTIVITY_ONE_PANE_ENABLED_METHOD, + /* arg= */ null, + /* extras= */ null); + } catch (IllegalArgumentException | SecurityException exception) { + Log.w( + TAG, + "SetupWizard one-pane support in embedded activity status unknown; return as false."); + applyEmbeddedActivityOnePaneBundle = null; + return false; + } + } + + return (applyEmbeddedActivityOnePaneBundle != null + && applyEmbeddedActivityOnePaneBundle.getBoolean( + IS_EMBEDDED_ACTIVITY_ONE_PANE_ENABLED_METHOD, false)); + } + /** Returns true if the SetupWizard supports the neutral button style during setup flow. */ public static boolean isNeutralButtonStyleEnabled(@NonNull Context context) { if (applyNeutralButtonStyleBundle == null) { @@ -789,6 +880,37 @@ public class PartnerConfigHelper { && applyNeutralButtonStyleBundle.getBoolean(IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD, false)); } + /** + * Returns the system property to indicate the transition settings is set by Glif theme rather + * than the client. + */ + public static boolean isGlifThemeControlledTransitionApplied(@NonNull Context context) { + if (applyTransitionBundle == null + || applyTransitionBundle.isEmpty()) { + try { + applyTransitionBundle = + context + .getContentResolver() + .call( + getContentUri(), + APPLY_GLIF_THEME_CONTROLLED_TRANSITION_METHOD, + /* arg= */ null, + /* extras= */ null); + } catch (IllegalArgumentException | SecurityException exception) { + Log.w( + TAG, + "applyGlifThemeControlledTransition unknown; return applyGlifThemeControlledTransition" + + " as default value"); + } + } + if (applyTransitionBundle != null + && !applyTransitionBundle.isEmpty()) { + return applyTransitionBundle.getBoolean( + APPLY_GLIF_THEME_CONTROLLED_TRANSITION_METHOD, true); + } + return true; + } + @VisibleForTesting static Uri getContentUri() { return new Uri.Builder() -- cgit v1.2.3 From 86b046d8e70d158e7a221ae222ba69ac8fc40b4f Mon Sep 17 00:00:00 2001 From: Safayat Ullah Date: Tue, 14 Feb 2023 08:20:50 +0000 Subject: Revert "Import updated Android SetupCompat Library 507643535" Revert submission 21343156-skyeye-metric-api Reason for revert: DroidMonitor-triggered revert due to breakage https://android-build.googleplex.com/builds/quarterdeck?build-cop, bug b/269218608 BUG: 269218608 Reverted changes: /q/submissionid:21343156-skyeye-metric-api Change-Id: I3f7e473d1691038152646ada93899efae6b824fb --- .../setupcompat/bts/AbstractSetupBtsService.java | 19 +- .../google/android/setupcompat/bts/Constants.java | 35 --- .../setupcompat/PartnerCustomizationLayout.java | 32 +-- .../android/setupcompat/logging/ScreenKey.java | 178 --------------- .../android/setupcompat/logging/SetupMetric.java | 254 --------------------- .../setupcompat/logging/SetupMetricsLogger.java | 21 -- .../logging/internal/MetricBundleConverter.java | 9 - .../internal/SetupMetricsLoggingConstants.java | 54 +---- .../setupcompat/template/FooterBarMixin.java | 7 +- .../android/setupcompat/util/BuildCompatUtils.java | 1 + main/res/values/attrs.xml | 1 - .../partnerconfig/PartnerConfigHelper.java | 122 ---------- 12 files changed, 13 insertions(+), 720 deletions(-) delete mode 100644 bts/java/com/google/android/setupcompat/bts/Constants.java delete mode 100644 main/java/com/google/android/setupcompat/logging/ScreenKey.java delete mode 100644 main/java/com/google/android/setupcompat/logging/SetupMetric.java diff --git a/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java index eb10693..c441b2d 100644 --- a/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java +++ b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java @@ -108,15 +108,8 @@ public abstract class AbstractSetupBtsService extends Service { + "3e51e5dd7b66787bef12fe97fba484c423fb4ff8cc494c02f0f5051612ff6529393e8e46eac5bb21f27" + "7c151aa5f2aa627d1e89da70ab6033569de3b9897bfff7ca9da3e1243f60b"; - @VisibleForTesting boolean allowDebugKeys = false; - @VisibleForTesting IBtsTaskServiceCallback callback; - /** Allow debug signature calling app when developing stage. */ - protected void setAllowDebugKeys(boolean allowed) { - allowDebugKeys = allowed; - } - @Nullable @Override public IBinder onBind(Intent intent) { @@ -163,10 +156,7 @@ public abstract class AbstractSetupBtsService extends Service { LOG.atDebug("onTaskFinished callback " + ((callback == null) ? "is null." : "is not null.")); if (callback != null) { try { - Bundle metricBundle = new Bundle(); - metricBundle.putBoolean(Constants.EXTRA_KEY_TASK_SUCCEED, succeed); - metricBundle.putString(Constants.EXTRA_KEY_TASK_FAILED_REASON, failedReason); - callback.onTaskFinished(metricBundle); + callback.onTaskFinished(Bundle.EMPTY); } catch (RemoteException e) { LOG.e( "[" + this.getClass().getSimpleName() + "] Fail to invoke remove method onJobFinished"); @@ -235,8 +225,7 @@ public abstract class AbstractSetupBtsService extends Service { @VisibleForTesting boolean verifyCallingPackageName() { String packageName = getPackageManager().getNameForUid(Binder.getCallingUid()); - if (SETUP_WIZARD_PACKAGE_NAME.equals(packageName) - || (allowDebugKeys && BTS_STARTER_FOR_TEST.equals(packageName))) { + if (SETUP_WIZARD_PACKAGE_NAME.equals(packageName) || BTS_STARTER_FOR_TEST.equals(packageName)) { LOG.atDebug("Package name match to SetupWizard"); return true; } else { @@ -253,11 +242,9 @@ public abstract class AbstractSetupBtsService extends Service { PackageInfo info = getPackageManager() .getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES); - for (Signature signature : info.signingInfo.getApkContentsSigners()) { if (SETUP_WIZARD_RELEASE_CERTIFICATE_STRING.equals(signature.toCharsString()) - || (allowDebugKeys - && SETUP_WIZARD_DEBUG_CERTIFICATE_STRING.equals(signature.toCharsString()))) { + || SETUP_WIZARD_DEBUG_CERTIFICATE_STRING.equals(signature.toCharsString())) { return true; } } diff --git a/bts/java/com/google/android/setupcompat/bts/Constants.java b/bts/java/com/google/android/setupcompat/bts/Constants.java deleted file mode 100644 index 7adcc33..0000000 --- a/bts/java/com/google/android/setupcompat/bts/Constants.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2023 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.bts; - -/** Constant values used by {@link com.google.android.setupcompat.bts.AbstractSetupBtsService}. */ -public class Constants { - - /** - * The extra key for {@link AbstractSetupBtsService} to send the task result to SUW for metric - * collection. - */ - public static final String EXTRA_KEY_TASK_SUCCEED = "succeed"; - - /** - * The extra key for {@link com.google.android.setupcompat.bts.AbstractSetupBtsService} to send - * the failed reason to SUW for metric collection. - */ - public static final String EXTRA_KEY_TASK_FAILED_REASON = "failed_reason"; - - private Constants() {} -} diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java index 21928c8..37cc358 100644 --- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java +++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java @@ -76,8 +76,6 @@ public class PartnerCustomizationLayout extends TemplateLayout { private Activity activity; - private PersistableBundle layoutTypeBundle; - @CanIgnoreReturnValue public PartnerCustomizationLayout(Context context) { this(context, 0, 0); @@ -94,6 +92,10 @@ public class PartnerCustomizationLayout extends TemplateLayout { init(null, R.attr.sucLayoutTheme); } + @VisibleForTesting + final ViewTreeObserver.OnWindowFocusChangeListener windowFocusChangeListener = + this::onFocusChanged; + @CanIgnoreReturnValue public PartnerCustomizationLayout(Context context, AttributeSet attrs) { super(context, attrs); @@ -107,10 +109,6 @@ public class PartnerCustomizationLayout extends TemplateLayout { init(attrs, defStyleAttr); } - @VisibleForTesting - final ViewTreeObserver.OnWindowFocusChangeListener windowFocusChangeListener = - this::onFocusChanged; - private void init(AttributeSet attrs, int defStyleAttr) { if (isInEditMode()) { return; @@ -244,15 +242,9 @@ public class PartnerCustomizationLayout extends TemplateLayout { ? secondaryButton.getMetrics("SecondaryFooterButton") : PersistableBundle.EMPTY; - PersistableBundle layoutTypeMetrics = - (layoutTypeBundle != null) ? layoutTypeBundle : PersistableBundle.EMPTY; - PersistableBundle persistableBundle = PersistableBundles.mergeBundles( - footerBarMixin.getLoggingMetrics(), - primaryButtonMetrics, - secondaryButtonMetrics, - layoutTypeMetrics); + footerBarMixin.getLoggingMetrics(), primaryButtonMetrics, secondaryButtonMetrics); SetupMetricsLogger.logCustomEvent( getContext(), @@ -264,20 +256,6 @@ public class PartnerCustomizationLayout extends TemplateLayout { } } - /** - * PartnerCustomizationLayout is a template layout for different type of GlifLayout. - * This method allows each type of layout to report its "GlifLayoutType". - */ - public void setLayoutTypeMetrics(PersistableBundle bundle) { - this.layoutTypeBundle = bundle; - } - - /** Returns a {@link PersistableBundle} contains key "GlifLayoutType". */ - @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) - public PersistableBundle getLayoutTypeMetrics() { - return this.layoutTypeBundle; - } - public static Activity lookupActivityFromContext(Context context) { if (context instanceof Activity) { return (Activity) context; diff --git a/main/java/com/google/android/setupcompat/logging/ScreenKey.java b/main/java/com/google/android/setupcompat/logging/ScreenKey.java deleted file mode 100644 index 4fba32b..0000000 --- a/main/java/com/google/android/setupcompat/logging/ScreenKey.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2022 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.logging; - -import static com.google.android.setupcompat.internal.Validations.assertLengthInRange; - -import android.content.Context; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import com.google.android.setupcompat.internal.Preconditions; -import com.google.android.setupcompat.util.ObjectUtils; -import java.util.regex.Pattern; - -/** - * A screen key represents a validated “string key” that is associated with the values reported by - * the API consumer. - */ -public class ScreenKey implements Parcelable { - - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final String SCREEN_KEY_BUNDLE_NAME_KEY = "ScreenKey_name"; - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final String SCREEN_KEY_BUNDLE_PACKAGE_KEY = "ScreenKey_package"; - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final String SCREEN_KEY_BUNDLE_VERSION_KEY = "ScreenKey_version"; - private static final int INVALID_VERSION = -1; - private static final int VERSION = 1; - - /** - * Creates a new instance of {@link ScreenKey}. - * - * @param name screen name to identify what the metric belongs to. It should be in the range of - * 5-50 characters, only alphanumeric characters are allowed. - * @param context context associated to metric screen, uses to generate package name. - */ - public static ScreenKey of(@NonNull String name, @NonNull Context context) { - Preconditions.checkNotNull(context, "Context can not be null."); - return ScreenKey.of(name, context.getPackageName()); - } - - private static ScreenKey of(@NonNull String name, @NonNull String packageName) { - Preconditions.checkArgument( - SCREEN_PACKAGENAME_PATTERN.matcher(packageName).matches(), - "Invalid ScreenKey#package, only alpha numeric characters are allowed."); - assertLengthInRange( - name, "ScreenKey.name", MIN_SCREEN_NAME_LENGTH, MAX_SCREEN_NAME_LENGTH); - Preconditions.checkArgument( - SCREEN_NAME_PATTERN.matcher(name).matches(), - "Invalid ScreenKey#name, only alpha numeric characters are allowed."); - - return new ScreenKey(name, packageName); - } - - /** - * Converts {@link ScreenKey} into {@link Bundle}. - * Throw {@link NullPointerException} if the screenKey is null. - */ - public static Bundle toBundle(ScreenKey screenKey) { - Preconditions.checkNotNull(screenKey, "ScreenKey cannot be null."); - Bundle bundle = new Bundle(); - bundle.putInt(SCREEN_KEY_BUNDLE_VERSION_KEY, VERSION); - bundle.putString(SCREEN_KEY_BUNDLE_NAME_KEY, screenKey.getName()); - bundle.putString(SCREEN_KEY_BUNDLE_PACKAGE_KEY, screenKey.getPackageName()); - return bundle; - } - - /** - * Converts {@link Bundle} into {@link ScreenKey}. - * Throw {@link NullPointerException} if the bundle is null. - * Throw {@link IllegalArgumentException} if the bundle version is unsupported. - */ - public static ScreenKey fromBundle(Bundle bundle) { - Preconditions.checkNotNull(bundle, "Bundle cannot be null"); - - int version = bundle.getInt(SCREEN_KEY_BUNDLE_VERSION_KEY, INVALID_VERSION); - if (version == 1) { - return ScreenKey.of( - bundle.getString(SCREEN_KEY_BUNDLE_NAME_KEY), - bundle.getString(SCREEN_KEY_BUNDLE_PACKAGE_KEY)); - } else { - // Invalid version - throw new IllegalArgumentException("Unsupported version: " + version); - } - } - - public static final Creator CREATOR = - new Creator<>() { - @Override - public ScreenKey createFromParcel(Parcel in) { - return new ScreenKey(in.readString(), in.readString()); - } - - @Override - public ScreenKey[] newArray(int size) { - return new ScreenKey[size]; - } - }; - - /** Returns the name of the screen key. */ - public String getName() { - return name; - } - - /** Returns the package name of the screen key. */ - public String getPackageName() { - return packageName; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int i) { - parcel.writeString(name); - parcel.writeString(packageName); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ScreenKey)) { - return false; - } - ScreenKey screenKey = (ScreenKey) o; - return ObjectUtils.equals(name, screenKey.name) - && ObjectUtils.equals(packageName, screenKey.packageName); - } - - @Override - public int hashCode() { - return ObjectUtils.hashCode(name, packageName); - } - - @NonNull - @Override - public String toString() { - return "ScreenKey {name=" - + getName() - + ", package=" - + getPackageName() - + "}"; - } - - private ScreenKey(String name, String packageName) { - this.name = name; - this.packageName = packageName; - } - - private final String name; - private final String packageName; - - private static final int MIN_SCREEN_NAME_LENGTH = 5; - private static final int MAX_SCREEN_NAME_LENGTH = 50; - private static final Pattern SCREEN_NAME_PATTERN = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]+"); - private static final Pattern SCREEN_PACKAGENAME_PATTERN = - Pattern.compile("^([a-z]+[.])+[a-zA-Z][a-zA-Z0-9]+"); -} diff --git a/main/java/com/google/android/setupcompat/logging/SetupMetric.java b/main/java/com/google/android/setupcompat/logging/SetupMetric.java deleted file mode 100644 index 4015a53..0000000 --- a/main/java/com/google/android/setupcompat/logging/SetupMetric.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (C) 2022 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.logging; - -import android.annotation.TargetApi; -import android.os.Build.VERSION_CODES; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.PersistableBundle; -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import com.google.android.setupcompat.internal.ClockProvider; -import com.google.android.setupcompat.internal.PersistableBundles; -import com.google.android.setupcompat.internal.Preconditions; -import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.EventType; -import com.google.android.setupcompat.util.ObjectUtils; - -/** - * This class represents a setup metric event at a particular point in time. - * The event is identified by {@link EventType} along with a string name. It can include - * additional key-value pairs providing more attributes associated with the given event. Only - * primitive values are supported for now (int, long, boolean, String). - */ -@TargetApi(VERSION_CODES.Q) -public class SetupMetric implements Parcelable { - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final String SETUP_METRIC_BUNDLE_VERSION_KEY = "SetupMetric_version"; - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final String SETUP_METRIC_BUNDLE_NAME_KEY = "SetupMetric_name"; - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final String SETUP_METRIC_BUNDLE_TYPE_KEY = "SetupMetric_type"; - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final String SETUP_METRIC_BUNDLE_VALUES_KEY = "SetupMetric_values"; - private static final int VERSION = 1; - private static final int INVALID_VERSION = -1; - - public static final String SETUP_METRIC_BUNDLE_OPTIN_KEY = "opt_in"; - public static final String SETUP_METRIC_BUNDLE_ERROR_KEY = "error"; - public static final String SETUP_METRIC_BUNDLE_TIMESTAMP_KEY = "timestamp"; - - - /** - * A convenient function to create a setup event with event type {@link EventType#IMPRESSION} - * @param name A name represents this impression - * @return A {@link SetupMetric} - * @throws IllegalArgumentException if the {@code name} is empty. - */ - @NonNull - public static SetupMetric ofImpression(@NonNull String name) { - Bundle bundle = new Bundle(); - bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); - return new SetupMetric(VERSION, name, EventType.IMPRESSION, - PersistableBundles.fromBundle(bundle)); - } - - /** - * A convenient function to create a setup event with event type {@link EventType#OPT_IN} - * @param name A name represents this opt-in - * @param status Opt-in status in {@code true} or {@code false} - * @return A {@link SetupMetric} - * @throws IllegalArgumentException if the {@code name} is empty. - */ - @NonNull - public static SetupMetric ofOptIn(@NonNull String name, boolean status) { - Bundle bundle = new Bundle(); - bundle.putBoolean(SETUP_METRIC_BUNDLE_OPTIN_KEY, status); - bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); - return new SetupMetric(VERSION, name, EventType.OPT_IN, PersistableBundles.fromBundle(bundle)); - } - - /** - * A convenient function to create a setup event with event type - * {@link EventType#WAITING_START} - * @param name A task name causes this waiting duration - * @return A {@link SetupMetric} - * @throws IllegalArgumentException if the {@code name} is empty. - */ - @NonNull - public static SetupMetric ofWaitingStart(@NonNull String name) { - Bundle bundle = new Bundle(); - bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); - return new SetupMetric(VERSION, name, EventType.WAITING_START, - PersistableBundles.fromBundle(bundle)); - } - - /** - * A convenient function to create a setup event with event type - * {@link EventType#WAITING_END} - * @param name A task name causes this waiting duration - * @return A {@link SetupMetric} - * @throws IllegalArgumentException if the {@code name} is empty. - */ - @NonNull - public static SetupMetric ofWaitingEnd(@NonNull String name) { - Bundle bundle = new Bundle(); - bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); - return new SetupMetric(VERSION, name, EventType.WAITING_END, - PersistableBundles.fromBundle(bundle)); - } - - /** - * A convenient function to create a setup event with event type {@link EventType#ERROR} - * @param name A name represents this error - * @param errorCode A error code - * @return A {@link SetupMetric} - * @throws IllegalArgumentException if the {@code name} is empty. - */ - @NonNull - public static SetupMetric ofError(@NonNull String name, int errorCode) { - Bundle bundle = new Bundle(); - bundle.putInt(SETUP_METRIC_BUNDLE_ERROR_KEY, errorCode); - bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); - return new SetupMetric(VERSION, name, EventType.ERROR, PersistableBundles.fromBundle(bundle)); - } - - /** Converts {@link SetupMetric} into {@link Bundle}. */ - @NonNull - public static Bundle toBundle(@NonNull SetupMetric setupMetric) { - Preconditions.checkNotNull(setupMetric, "SetupMetric cannot be null."); - Bundle bundle = new Bundle(); - bundle.putInt(SETUP_METRIC_BUNDLE_VERSION_KEY, VERSION); - bundle.putString(SETUP_METRIC_BUNDLE_NAME_KEY, setupMetric.name); - bundle.putInt(SETUP_METRIC_BUNDLE_TYPE_KEY, setupMetric.type); - bundle.putBundle( - SETUP_METRIC_BUNDLE_VALUES_KEY, PersistableBundles.toBundle(setupMetric.values)); - return bundle; - } - - /** - * Converts {@link Bundle} into {@link SetupMetric}. - * Throw {@link IllegalArgumentException} if the bundle version is unsupported. - */ - @NonNull - public static SetupMetric fromBundle(@NonNull Bundle bundle) { - Preconditions.checkNotNull(bundle, "Bundle cannot be null"); - int version = bundle.getInt(SETUP_METRIC_BUNDLE_VERSION_KEY, INVALID_VERSION); - if (version == 1) { - return new SetupMetric( - bundle.getInt(SETUP_METRIC_BUNDLE_VERSION_KEY), - bundle.getString(SETUP_METRIC_BUNDLE_NAME_KEY), - bundle.getInt(SETUP_METRIC_BUNDLE_TYPE_KEY), - PersistableBundles.fromBundle(bundle.getBundle(SETUP_METRIC_BUNDLE_VALUES_KEY))); - } else { - throw new IllegalArgumentException("Unsupported version: " + version); - } - } - - private SetupMetric( - int version, String name, @EventType int type, @NonNull PersistableBundle values) { - Preconditions.checkArgument( - name != null && name.length() != 0, - "name cannot be null or empty."); - this.version = version; - this.name = name; - this.type = type; - this.values = values; - } - - private final int version; - private final String name; - @EventType private final int type; - private final PersistableBundle values; - - public int getVersion() { - return version; - } - - public String getName() { - return name; - } - - @EventType - public int getType() { - return type; - } - - public PersistableBundle getValues() { - return values; - } - - public static final Creator CREATOR = - new Creator<>() { - @Override - public SetupMetric createFromParcel(@NonNull Parcel in) { - return new SetupMetric(in.readInt(), - in.readString(), - in.readInt(), - in.readPersistableBundle(SetupMetric.class.getClassLoader())); - } - - @Override - public SetupMetric[] newArray(int size) { - return new SetupMetric[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeString(name); - parcel.writeInt(type); - parcel.writePersistableBundle(values); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof SetupMetric)) { - return false; - } - SetupMetric that = (SetupMetric) o; - return ObjectUtils.equals(name, that.name) - && ObjectUtils.equals(type, that.type) - && PersistableBundles.equals(values, that.values); - } - - @Override - public int hashCode() { - return ObjectUtils.hashCode(name, type, values); - } - - @NonNull - @Override - public String toString() { - return "SetupMetric {name=" - + getName() - + ", type=" - + getType() - + ", bundle=" - + getValues().toString() - + "}"; - } -} diff --git a/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java b/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java index 786494e..8d696e0 100644 --- a/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java +++ b/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java @@ -22,14 +22,11 @@ import com.google.android.setupcompat.internal.Preconditions; import com.google.android.setupcompat.internal.SetupCompatServiceInvoker; import com.google.android.setupcompat.logging.internal.MetricBundleConverter; import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricType; -import com.google.android.setupcompat.util.Logger; import java.util.concurrent.TimeUnit; /** SetupMetricsLogger provides an easy way to log custom metrics to SetupWizard. */ public class SetupMetricsLogger { - private static final Logger LOG = new Logger("SetupMetricsLogger"); - /** Logs an instance of {@link CustomEvent} to SetupWizard. */ public static void logCustomEvent(@NonNull Context context, @NonNull CustomEvent customEvent) { Preconditions.checkNotNull(context, "Context cannot be null."); @@ -74,22 +71,4 @@ public class SetupMetricsLogger { MetricType.DURATION_EVENT, MetricBundleConverter.createBundleForLoggingTimer(timerName, timeInMillis)); } - - /** - * Logs setup collection metrics (go/suw-metrics-collection-api) - */ - public static void logMetrics( - @NonNull Context context, @NonNull ScreenKey screenKey, @NonNull SetupMetric... metrics) { - Preconditions.checkNotNull(context, "Context cannot be null."); - Preconditions.checkNotNull(screenKey, "ScreenKey cannot be null."); - Preconditions.checkNotNull(metrics, "SetupMetric cannot be null."); - - for (SetupMetric metric : metrics) { - LOG.atDebug("Log metric: " + screenKey + ", " + metric); - - SetupCompatServiceInvoker.get(context).logMetricEvent( - MetricType.SETUP_COLLECTION_EVENT, - MetricBundleConverter.createBundleForLoggingSetupMetric(screenKey, metric)); - } - } } diff --git a/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java b/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java index 8e5ba20..e1a3909 100644 --- a/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java +++ b/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java @@ -3,8 +3,6 @@ package com.google.android.setupcompat.logging.internal; import android.os.Bundle; import com.google.android.setupcompat.logging.CustomEvent; import com.google.android.setupcompat.logging.MetricKey; -import com.google.android.setupcompat.logging.ScreenKey; -import com.google.android.setupcompat.logging.SetupMetric; import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricBundleKeys; /** Collection of helper methods for reading and writing {@link CustomEvent}, {@link MetricKey}. */ @@ -30,13 +28,6 @@ public final class MetricBundleConverter { return bundle; } - public static Bundle createBundleForLoggingSetupMetric(ScreenKey screenKey, SetupMetric metric) { - Bundle bundle = new Bundle(); - bundle.putParcelable(MetricBundleKeys.SCREEN_KEY_BUNDLE, ScreenKey.toBundle(screenKey)); - bundle.putParcelable(MetricBundleKeys.SETUP_METRIC_BUNDLE, SetupMetric.toBundle(metric)); - return bundle; - } - private MetricBundleConverter() { throw new AssertionError("Cannot instantiate MetricBundleConverter"); } diff --git a/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java b/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java index d4995b7..57a7272 100644 --- a/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java +++ b/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java @@ -20,8 +20,6 @@ import android.content.Context; import androidx.annotation.IntDef; import androidx.annotation.StringDef; import com.google.android.setupcompat.logging.MetricKey; -import com.google.android.setupcompat.logging.ScreenKey; -import com.google.android.setupcompat.logging.SetupMetric; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -30,12 +28,7 @@ public interface SetupMetricsLoggingConstants { /** Enumeration of supported metric types logged to SetupWizard. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({ - MetricType.CUSTOM_EVENT, - MetricType.DURATION_EVENT, - MetricType.COUNTER_EVENT, - MetricType.SETUP_COLLECTION_EVENT, - MetricType.INTERNAL}) + @IntDef({MetricType.CUSTOM_EVENT, MetricType.COUNTER_EVENT, MetricType.DURATION_EVENT}) @interface MetricType { /** * MetricType constant used when logging {@link @@ -54,39 +47,10 @@ public interface SetupMetricsLoggingConstants { */ int COUNTER_EVENT = 3; - /** - * MetricType constant used when logging setup metric using {@link - * com.google.android.setupcompat.logging.SetupMetricsLogger#logMetrics(Context, ScreenKey, - * SetupMetric...)}. - */ - int SETUP_COLLECTION_EVENT = 4; - /** MetricType constant used for internal logging purposes. */ int INTERNAL = 100; } - /** - * Enumeration of supported EventType of {@link MetricType#SETUP_COLLECTION_EVENT} logged to - * SetupWizard. (go/suw-metrics-collection-api) - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef({ - EventType.UNKNOWN, - EventType.IMPRESSION, - EventType.OPT_IN, - EventType.WAITING_START, - EventType.WAITING_END, - EventType.ERROR, - }) - @interface EventType { - int UNKNOWN = 1; - int IMPRESSION = 2; - int OPT_IN = 3; - int WAITING_START = 4; - int WAITING_END = 5; - int ERROR = 6; - } - /** Keys of the bundle used while logging data to SetupWizard. */ @Retention(RetentionPolicy.SOURCE) @StringDef({ @@ -95,9 +59,7 @@ public interface SetupMetricsLoggingConstants { MetricBundleKeys.CUSTOM_EVENT, MetricBundleKeys.CUSTOM_EVENT_BUNDLE, MetricBundleKeys.TIME_MILLIS_LONG, - MetricBundleKeys.COUNTER_INT, - MetricBundleKeys.SCREEN_KEY_BUNDLE, - MetricBundleKeys.SETUP_METRIC_BUNDLE, + MetricBundleKeys.COUNTER_INT }) @interface MetricBundleKeys { /** @@ -142,17 +104,5 @@ public interface SetupMetricsLoggingConstants { * com.google.android.setupcompat.logging.CustomEvent}. */ String CUSTOM_EVENT_BUNDLE = "CustomEvent_bundle"; - - /** - * This key will be used when {@code metricType} is {@link MetricType#SETUP_COLLECTION_EVENT} - * with the value being a Bundle which can be used to read {@link ScreenKey} - */ - String SCREEN_KEY_BUNDLE = "ScreenKey_bundle"; - - /** - * This key will be used when {@code metricType} is {@link MetricType#SETUP_COLLECTION_EVENT} - * with the value being a Bundle which can be used to read {@link SetupMetric} - */ - String SETUP_METRIC_BUNDLE = "SetupMetric_bundle"; } } diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java index 2268b1e..b77eacf 100644 --- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java @@ -73,7 +73,6 @@ public class FooterBarMixin implements Mixin { @VisibleForTesting final boolean applyPartnerResources; @VisibleForTesting final boolean applyDynamicColor; @VisibleForTesting final boolean useFullDynamicColor; - @VisibleForTesting final boolean footerButtonAlignEnd; @VisibleForTesting public LinearLayout buttonContainer; private FooterButton primaryButton; @@ -207,8 +206,6 @@ public class FooterBarMixin implements Mixin { a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterBackground, 0); footerBarSecondaryBackgroundColor = a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarSecondaryFooterBackground, 0); - footerButtonAlignEnd = - a.getBoolean(R.styleable.SucFooterBarMixin_sucFooterBarButtonAlignEnd, false); int primaryBtn = a.getResourceId(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterButton, 0); @@ -237,7 +234,7 @@ public class FooterBarMixin implements Mixin { return PartnerConfigHelper.get(context) .getBoolean(context, PartnerConfig.CONFIG_FOOTER_BUTTON_ALIGNED_END, false); } else { - return footerButtonAlignEnd; + return false; } } @@ -620,7 +617,7 @@ public class FooterBarMixin implements Mixin { return overrideTheme; } - /** Returns the {@link LinearLayout} of button container. */ + @VisibleForTesting public LinearLayout getButtonContainer() { return buttonContainer; } diff --git a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java index d1b0ccb..090e1df 100644 --- a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java +++ b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java @@ -83,6 +83,7 @@ public final class BuildCompatUtils { * @return Whether the current OS version is higher or equal to U. */ public static boolean isAtLeastU() { + System.out.println("Build.VERSION.CODENAME=" + Build.VERSION.CODENAME); return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 34) || (Build.VERSION.CODENAME.length() == 1 && Build.VERSION.CODENAME.charAt(0) >= 'U' diff --git a/main/res/values/attrs.xml b/main/res/values/attrs.xml index 0aaea8b..07f87ed 100644 --- a/main/res/values/attrs.xml +++ b/main/res/values/attrs.xml @@ -82,7 +82,6 @@ - diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java index 96a4317..1b73098 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java @@ -68,19 +68,12 @@ public class PartnerConfigHelper { @VisibleForTesting public static final String IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD = "isNeutralButtonStyleEnabled"; - @VisibleForTesting - public static final String IS_EMBEDDED_ACTIVITY_ONE_PANE_ENABLED_METHOD = - "isEmbeddedActivityOnePaneEnabled"; - @VisibleForTesting public static final String GET_SUW_DEFAULT_THEME_STRING_METHOD = "suwDefaultThemeString"; @VisibleForTesting public static final String SUW_PACKAGE_NAME = "com.google.android.setupwizard"; @VisibleForTesting public static final String MATERIAL_YOU_RESOURCE_SUFFIX = "_material_you"; - @VisibleForTesting - public static final String EMBEDDED_ACTIVITY_RESOURCE_SUFFIX = "_embedded_activity"; - @VisibleForTesting static Bundle suwDayNightEnabledBundle = null; @VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null; @@ -91,8 +84,6 @@ public class PartnerConfigHelper { @VisibleForTesting public static Bundle applyNeutralButtonStyleBundle = null; - @VisibleForTesting public static Bundle applyEmbeddedActivityOnePaneBundle = null; - @VisibleForTesting public static Bundle suwDefaultThemeBundle = null; private static PartnerConfigHelper instance = null; @@ -106,16 +97,8 @@ public class PartnerConfigHelper { private static int savedConfigUiMode; - private static boolean savedConfigEmbeddedActivityMode; - - @VisibleForTesting static Bundle applyTransitionBundle = null; - @VisibleForTesting public static int savedOrientation = Configuration.ORIENTATION_PORTRAIT; - /** The method name to get if transition settings is set from client. */ - public static final String APPLY_GLIF_THEME_CONTROLLED_TRANSITION_METHOD = - "applyGlifThemeControlledTransition"; - /** * When testing related to fake PartnerConfigHelper instance, should sync the following saved * config with testing environment. @@ -134,8 +117,6 @@ public class PartnerConfigHelper { private static boolean isValidInstance(@NonNull Context context) { Configuration currentConfig = context.getResources().getConfiguration(); if (instance == null) { - savedConfigEmbeddedActivityMode = - isEmbeddedActivityOnePaneEnabled(context) && BuildCompatUtils.isAtLeastU(); savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; savedOrientation = currentConfig.orientation; savedScreenWidth = currentConfig.screenWidthDp; @@ -145,10 +126,7 @@ public class PartnerConfigHelper { boolean uiModeChanged = isSetupWizardDayNightEnabled(context) && (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != savedConfigUiMode; - boolean embeddedActivityModeChanged = - isEmbeddedActivityOnePaneEnabled(context) && BuildCompatUtils.isAtLeastU(); if (uiModeChanged - || embeddedActivityModeChanged != savedConfigEmbeddedActivityMode || currentConfig.orientation != savedOrientation || currentConfig.screenWidthDp != savedScreenWidth || currentConfig.screenHeightDp != savedScreenHeight) { @@ -576,11 +554,6 @@ public class PartnerConfigHelper { ResourceEntry adjustResourceEntry = adjustResourceEntryDefaultValue( context, ResourceEntry.fromBundle(context, resourceEntryBundle)); - ; - if (BuildCompatUtils.isAtLeastU() && isEmbeddedActivityOnePaneEnabled(context)) { - adjustResourceEntry = embeddedActivityResourceEntryDefaultValue(context, adjustResourceEntry); - } - return adjustResourceEntryDayNightMode(context, adjustResourceEntry); } @@ -644,42 +617,6 @@ public class PartnerConfigHelper { return inputResourceEntry; } - // Check the embedded acitvity flag and replace the inputResourceEntry.resourceName & - // inputResourceEntry.resourceId after U. - ResourceEntry embeddedActivityResourceEntryDefaultValue( - Context context, ResourceEntry inputResourceEntry) { - // If not overlay resource - try { - if (SUW_PACKAGE_NAME.equals(inputResourceEntry.getPackageName())) { - String resourceTypeName = - inputResourceEntry - .getResources() - .getResourceTypeName(inputResourceEntry.getResourceId()); - // try to update resourceName & resourceId - String embeddedActivityResourceName = - inputResourceEntry.getResourceName().concat(EMBEDDED_ACTIVITY_RESOURCE_SUFFIX); - int embeddedActivityResourceId = - inputResourceEntry - .getResources() - .getIdentifier( - embeddedActivityResourceName, - resourceTypeName, - inputResourceEntry.getPackageName()); - if (embeddedActivityResourceId != 0) { - Log.i(TAG, "use embedded activity resource:" + embeddedActivityResourceName); - return new ResourceEntry( - inputResourceEntry.getPackageName(), - embeddedActivityResourceName, - embeddedActivityResourceId, - inputResourceEntry.getResources()); - } - } - } catch (NotFoundException ex) { - // fall through - } - return inputResourceEntry; - } - @VisibleForTesting public static synchronized void resetInstance() { instance = null; @@ -688,9 +625,7 @@ public class PartnerConfigHelper { applyMaterialYouConfigBundle = null; applyDynamicColorBundle = null; applyNeutralButtonStyleBundle = null; - applyEmbeddedActivityOnePaneBundle = null; suwDefaultThemeBundle = null; - applyTransitionBundle = null; } /** @@ -831,32 +766,6 @@ public class PartnerConfigHelper { && applyDynamicColorBundle.getBoolean(IS_DYNAMIC_COLOR_ENABLED_METHOD, false)); } - /** Returns true if the SetupWizard supports the one-pane embedded activity during setup flow. */ - public static boolean isEmbeddedActivityOnePaneEnabled(@NonNull Context context) { - if (applyEmbeddedActivityOnePaneBundle == null) { - try { - applyEmbeddedActivityOnePaneBundle = - context - .getContentResolver() - .call( - getContentUri(), - IS_EMBEDDED_ACTIVITY_ONE_PANE_ENABLED_METHOD, - /* arg= */ null, - /* extras= */ null); - } catch (IllegalArgumentException | SecurityException exception) { - Log.w( - TAG, - "SetupWizard one-pane support in embedded activity status unknown; return as false."); - applyEmbeddedActivityOnePaneBundle = null; - return false; - } - } - - return (applyEmbeddedActivityOnePaneBundle != null - && applyEmbeddedActivityOnePaneBundle.getBoolean( - IS_EMBEDDED_ACTIVITY_ONE_PANE_ENABLED_METHOD, false)); - } - /** Returns true if the SetupWizard supports the neutral button style during setup flow. */ public static boolean isNeutralButtonStyleEnabled(@NonNull Context context) { if (applyNeutralButtonStyleBundle == null) { @@ -880,37 +789,6 @@ public class PartnerConfigHelper { && applyNeutralButtonStyleBundle.getBoolean(IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD, false)); } - /** - * Returns the system property to indicate the transition settings is set by Glif theme rather - * than the client. - */ - public static boolean isGlifThemeControlledTransitionApplied(@NonNull Context context) { - if (applyTransitionBundle == null - || applyTransitionBundle.isEmpty()) { - try { - applyTransitionBundle = - context - .getContentResolver() - .call( - getContentUri(), - APPLY_GLIF_THEME_CONTROLLED_TRANSITION_METHOD, - /* arg= */ null, - /* extras= */ null); - } catch (IllegalArgumentException | SecurityException exception) { - Log.w( - TAG, - "applyGlifThemeControlledTransition unknown; return applyGlifThemeControlledTransition" - + " as default value"); - } - } - if (applyTransitionBundle != null - && !applyTransitionBundle.isEmpty()) { - return applyTransitionBundle.getBoolean( - APPLY_GLIF_THEME_CONTROLLED_TRANSITION_METHOD, true); - } - return true; - } - @VisibleForTesting static Uri getContentUri() { return new Uri.Builder() -- cgit v1.2.3 From 534db6a965794ea080ed2203baeccccc912e9546 Mon Sep 17 00:00:00 2001 From: David Liu Date: Wed, 15 Feb 2023 06:55:44 +0000 Subject: Revert "Revert "Import updated Android SetupCompat Library 507643535"" This reverts commit 86b046d8e70d158e7a221ae222ba69ac8fc40b4f. Reason for revert: Only submit setupcompat changes. Does not add new dependency. BUG: 268572395 Change-Id: I761f5eabeb086fac07a8d08e588cbf7614f4f764 --- .../setupcompat/bts/AbstractSetupBtsService.java | 19 +- .../google/android/setupcompat/bts/Constants.java | 35 +++ .../setupcompat/PartnerCustomizationLayout.java | 32 ++- .../android/setupcompat/logging/ScreenKey.java | 178 +++++++++++++++ .../android/setupcompat/logging/SetupMetric.java | 254 +++++++++++++++++++++ .../setupcompat/logging/SetupMetricsLogger.java | 21 ++ .../logging/internal/MetricBundleConverter.java | 9 + .../internal/SetupMetricsLoggingConstants.java | 54 ++++- .../setupcompat/template/FooterBarMixin.java | 7 +- .../android/setupcompat/util/BuildCompatUtils.java | 1 - main/res/values/attrs.xml | 1 + .../partnerconfig/PartnerConfigHelper.java | 122 ++++++++++ 12 files changed, 720 insertions(+), 13 deletions(-) create mode 100644 bts/java/com/google/android/setupcompat/bts/Constants.java create mode 100644 main/java/com/google/android/setupcompat/logging/ScreenKey.java create mode 100644 main/java/com/google/android/setupcompat/logging/SetupMetric.java diff --git a/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java index c441b2d..eb10693 100644 --- a/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java +++ b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java @@ -108,8 +108,15 @@ public abstract class AbstractSetupBtsService extends Service { + "3e51e5dd7b66787bef12fe97fba484c423fb4ff8cc494c02f0f5051612ff6529393e8e46eac5bb21f27" + "7c151aa5f2aa627d1e89da70ab6033569de3b9897bfff7ca9da3e1243f60b"; + @VisibleForTesting boolean allowDebugKeys = false; + @VisibleForTesting IBtsTaskServiceCallback callback; + /** Allow debug signature calling app when developing stage. */ + protected void setAllowDebugKeys(boolean allowed) { + allowDebugKeys = allowed; + } + @Nullable @Override public IBinder onBind(Intent intent) { @@ -156,7 +163,10 @@ public abstract class AbstractSetupBtsService extends Service { LOG.atDebug("onTaskFinished callback " + ((callback == null) ? "is null." : "is not null.")); if (callback != null) { try { - callback.onTaskFinished(Bundle.EMPTY); + Bundle metricBundle = new Bundle(); + metricBundle.putBoolean(Constants.EXTRA_KEY_TASK_SUCCEED, succeed); + metricBundle.putString(Constants.EXTRA_KEY_TASK_FAILED_REASON, failedReason); + callback.onTaskFinished(metricBundle); } catch (RemoteException e) { LOG.e( "[" + this.getClass().getSimpleName() + "] Fail to invoke remove method onJobFinished"); @@ -225,7 +235,8 @@ public abstract class AbstractSetupBtsService extends Service { @VisibleForTesting boolean verifyCallingPackageName() { String packageName = getPackageManager().getNameForUid(Binder.getCallingUid()); - if (SETUP_WIZARD_PACKAGE_NAME.equals(packageName) || BTS_STARTER_FOR_TEST.equals(packageName)) { + if (SETUP_WIZARD_PACKAGE_NAME.equals(packageName) + || (allowDebugKeys && BTS_STARTER_FOR_TEST.equals(packageName))) { LOG.atDebug("Package name match to SetupWizard"); return true; } else { @@ -242,9 +253,11 @@ public abstract class AbstractSetupBtsService extends Service { PackageInfo info = getPackageManager() .getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES); + for (Signature signature : info.signingInfo.getApkContentsSigners()) { if (SETUP_WIZARD_RELEASE_CERTIFICATE_STRING.equals(signature.toCharsString()) - || SETUP_WIZARD_DEBUG_CERTIFICATE_STRING.equals(signature.toCharsString())) { + || (allowDebugKeys + && SETUP_WIZARD_DEBUG_CERTIFICATE_STRING.equals(signature.toCharsString()))) { return true; } } diff --git a/bts/java/com/google/android/setupcompat/bts/Constants.java b/bts/java/com/google/android/setupcompat/bts/Constants.java new file mode 100644 index 0000000..7adcc33 --- /dev/null +++ b/bts/java/com/google/android/setupcompat/bts/Constants.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 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.bts; + +/** Constant values used by {@link com.google.android.setupcompat.bts.AbstractSetupBtsService}. */ +public class Constants { + + /** + * The extra key for {@link AbstractSetupBtsService} to send the task result to SUW for metric + * collection. + */ + public static final String EXTRA_KEY_TASK_SUCCEED = "succeed"; + + /** + * The extra key for {@link com.google.android.setupcompat.bts.AbstractSetupBtsService} to send + * the failed reason to SUW for metric collection. + */ + public static final String EXTRA_KEY_TASK_FAILED_REASON = "failed_reason"; + + private Constants() {} +} diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java index 37cc358..21928c8 100644 --- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java +++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java @@ -76,6 +76,8 @@ public class PartnerCustomizationLayout extends TemplateLayout { private Activity activity; + private PersistableBundle layoutTypeBundle; + @CanIgnoreReturnValue public PartnerCustomizationLayout(Context context) { this(context, 0, 0); @@ -92,10 +94,6 @@ public class PartnerCustomizationLayout extends TemplateLayout { init(null, R.attr.sucLayoutTheme); } - @VisibleForTesting - final ViewTreeObserver.OnWindowFocusChangeListener windowFocusChangeListener = - this::onFocusChanged; - @CanIgnoreReturnValue public PartnerCustomizationLayout(Context context, AttributeSet attrs) { super(context, attrs); @@ -109,6 +107,10 @@ public class PartnerCustomizationLayout extends TemplateLayout { init(attrs, defStyleAttr); } + @VisibleForTesting + final ViewTreeObserver.OnWindowFocusChangeListener windowFocusChangeListener = + this::onFocusChanged; + private void init(AttributeSet attrs, int defStyleAttr) { if (isInEditMode()) { return; @@ -242,9 +244,15 @@ public class PartnerCustomizationLayout extends TemplateLayout { ? secondaryButton.getMetrics("SecondaryFooterButton") : PersistableBundle.EMPTY; + PersistableBundle layoutTypeMetrics = + (layoutTypeBundle != null) ? layoutTypeBundle : PersistableBundle.EMPTY; + PersistableBundle persistableBundle = PersistableBundles.mergeBundles( - footerBarMixin.getLoggingMetrics(), primaryButtonMetrics, secondaryButtonMetrics); + footerBarMixin.getLoggingMetrics(), + primaryButtonMetrics, + secondaryButtonMetrics, + layoutTypeMetrics); SetupMetricsLogger.logCustomEvent( getContext(), @@ -256,6 +264,20 @@ public class PartnerCustomizationLayout extends TemplateLayout { } } + /** + * PartnerCustomizationLayout is a template layout for different type of GlifLayout. + * This method allows each type of layout to report its "GlifLayoutType". + */ + public void setLayoutTypeMetrics(PersistableBundle bundle) { + this.layoutTypeBundle = bundle; + } + + /** Returns a {@link PersistableBundle} contains key "GlifLayoutType". */ + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + public PersistableBundle getLayoutTypeMetrics() { + return this.layoutTypeBundle; + } + public static Activity lookupActivityFromContext(Context context) { if (context instanceof Activity) { return (Activity) context; diff --git a/main/java/com/google/android/setupcompat/logging/ScreenKey.java b/main/java/com/google/android/setupcompat/logging/ScreenKey.java new file mode 100644 index 0000000..4fba32b --- /dev/null +++ b/main/java/com/google/android/setupcompat/logging/ScreenKey.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2022 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.logging; + +import static com.google.android.setupcompat.internal.Validations.assertLengthInRange; + +import android.content.Context; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import com.google.android.setupcompat.internal.Preconditions; +import com.google.android.setupcompat.util.ObjectUtils; +import java.util.regex.Pattern; + +/** + * A screen key represents a validated “string key” that is associated with the values reported by + * the API consumer. + */ +public class ScreenKey implements Parcelable { + + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SCREEN_KEY_BUNDLE_NAME_KEY = "ScreenKey_name"; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SCREEN_KEY_BUNDLE_PACKAGE_KEY = "ScreenKey_package"; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SCREEN_KEY_BUNDLE_VERSION_KEY = "ScreenKey_version"; + private static final int INVALID_VERSION = -1; + private static final int VERSION = 1; + + /** + * Creates a new instance of {@link ScreenKey}. + * + * @param name screen name to identify what the metric belongs to. It should be in the range of + * 5-50 characters, only alphanumeric characters are allowed. + * @param context context associated to metric screen, uses to generate package name. + */ + public static ScreenKey of(@NonNull String name, @NonNull Context context) { + Preconditions.checkNotNull(context, "Context can not be null."); + return ScreenKey.of(name, context.getPackageName()); + } + + private static ScreenKey of(@NonNull String name, @NonNull String packageName) { + Preconditions.checkArgument( + SCREEN_PACKAGENAME_PATTERN.matcher(packageName).matches(), + "Invalid ScreenKey#package, only alpha numeric characters are allowed."); + assertLengthInRange( + name, "ScreenKey.name", MIN_SCREEN_NAME_LENGTH, MAX_SCREEN_NAME_LENGTH); + Preconditions.checkArgument( + SCREEN_NAME_PATTERN.matcher(name).matches(), + "Invalid ScreenKey#name, only alpha numeric characters are allowed."); + + return new ScreenKey(name, packageName); + } + + /** + * Converts {@link ScreenKey} into {@link Bundle}. + * Throw {@link NullPointerException} if the screenKey is null. + */ + public static Bundle toBundle(ScreenKey screenKey) { + Preconditions.checkNotNull(screenKey, "ScreenKey cannot be null."); + Bundle bundle = new Bundle(); + bundle.putInt(SCREEN_KEY_BUNDLE_VERSION_KEY, VERSION); + bundle.putString(SCREEN_KEY_BUNDLE_NAME_KEY, screenKey.getName()); + bundle.putString(SCREEN_KEY_BUNDLE_PACKAGE_KEY, screenKey.getPackageName()); + return bundle; + } + + /** + * Converts {@link Bundle} into {@link ScreenKey}. + * Throw {@link NullPointerException} if the bundle is null. + * Throw {@link IllegalArgumentException} if the bundle version is unsupported. + */ + public static ScreenKey fromBundle(Bundle bundle) { + Preconditions.checkNotNull(bundle, "Bundle cannot be null"); + + int version = bundle.getInt(SCREEN_KEY_BUNDLE_VERSION_KEY, INVALID_VERSION); + if (version == 1) { + return ScreenKey.of( + bundle.getString(SCREEN_KEY_BUNDLE_NAME_KEY), + bundle.getString(SCREEN_KEY_BUNDLE_PACKAGE_KEY)); + } else { + // Invalid version + throw new IllegalArgumentException("Unsupported version: " + version); + } + } + + public static final Creator CREATOR = + new Creator<>() { + @Override + public ScreenKey createFromParcel(Parcel in) { + return new ScreenKey(in.readString(), in.readString()); + } + + @Override + public ScreenKey[] newArray(int size) { + return new ScreenKey[size]; + } + }; + + /** Returns the name of the screen key. */ + public String getName() { + return name; + } + + /** Returns the package name of the screen key. */ + public String getPackageName() { + return packageName; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeString(name); + parcel.writeString(packageName); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ScreenKey)) { + return false; + } + ScreenKey screenKey = (ScreenKey) o; + return ObjectUtils.equals(name, screenKey.name) + && ObjectUtils.equals(packageName, screenKey.packageName); + } + + @Override + public int hashCode() { + return ObjectUtils.hashCode(name, packageName); + } + + @NonNull + @Override + public String toString() { + return "ScreenKey {name=" + + getName() + + ", package=" + + getPackageName() + + "}"; + } + + private ScreenKey(String name, String packageName) { + this.name = name; + this.packageName = packageName; + } + + private final String name; + private final String packageName; + + private static final int MIN_SCREEN_NAME_LENGTH = 5; + private static final int MAX_SCREEN_NAME_LENGTH = 50; + private static final Pattern SCREEN_NAME_PATTERN = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]+"); + private static final Pattern SCREEN_PACKAGENAME_PATTERN = + Pattern.compile("^([a-z]+[.])+[a-zA-Z][a-zA-Z0-9]+"); +} diff --git a/main/java/com/google/android/setupcompat/logging/SetupMetric.java b/main/java/com/google/android/setupcompat/logging/SetupMetric.java new file mode 100644 index 0000000..4015a53 --- /dev/null +++ b/main/java/com/google/android/setupcompat/logging/SetupMetric.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2022 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.logging; + +import android.annotation.TargetApi; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import com.google.android.setupcompat.internal.ClockProvider; +import com.google.android.setupcompat.internal.PersistableBundles; +import com.google.android.setupcompat.internal.Preconditions; +import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.EventType; +import com.google.android.setupcompat.util.ObjectUtils; + +/** + * This class represents a setup metric event at a particular point in time. + * The event is identified by {@link EventType} along with a string name. It can include + * additional key-value pairs providing more attributes associated with the given event. Only + * primitive values are supported for now (int, long, boolean, String). + */ +@TargetApi(VERSION_CODES.Q) +public class SetupMetric implements Parcelable { + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SETUP_METRIC_BUNDLE_VERSION_KEY = "SetupMetric_version"; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SETUP_METRIC_BUNDLE_NAME_KEY = "SetupMetric_name"; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SETUP_METRIC_BUNDLE_TYPE_KEY = "SetupMetric_type"; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String SETUP_METRIC_BUNDLE_VALUES_KEY = "SetupMetric_values"; + private static final int VERSION = 1; + private static final int INVALID_VERSION = -1; + + public static final String SETUP_METRIC_BUNDLE_OPTIN_KEY = "opt_in"; + public static final String SETUP_METRIC_BUNDLE_ERROR_KEY = "error"; + public static final String SETUP_METRIC_BUNDLE_TIMESTAMP_KEY = "timestamp"; + + + /** + * A convenient function to create a setup event with event type {@link EventType#IMPRESSION} + * @param name A name represents this impression + * @return A {@link SetupMetric} + * @throws IllegalArgumentException if the {@code name} is empty. + */ + @NonNull + public static SetupMetric ofImpression(@NonNull String name) { + Bundle bundle = new Bundle(); + bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); + return new SetupMetric(VERSION, name, EventType.IMPRESSION, + PersistableBundles.fromBundle(bundle)); + } + + /** + * A convenient function to create a setup event with event type {@link EventType#OPT_IN} + * @param name A name represents this opt-in + * @param status Opt-in status in {@code true} or {@code false} + * @return A {@link SetupMetric} + * @throws IllegalArgumentException if the {@code name} is empty. + */ + @NonNull + public static SetupMetric ofOptIn(@NonNull String name, boolean status) { + Bundle bundle = new Bundle(); + bundle.putBoolean(SETUP_METRIC_BUNDLE_OPTIN_KEY, status); + bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); + return new SetupMetric(VERSION, name, EventType.OPT_IN, PersistableBundles.fromBundle(bundle)); + } + + /** + * A convenient function to create a setup event with event type + * {@link EventType#WAITING_START} + * @param name A task name causes this waiting duration + * @return A {@link SetupMetric} + * @throws IllegalArgumentException if the {@code name} is empty. + */ + @NonNull + public static SetupMetric ofWaitingStart(@NonNull String name) { + Bundle bundle = new Bundle(); + bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); + return new SetupMetric(VERSION, name, EventType.WAITING_START, + PersistableBundles.fromBundle(bundle)); + } + + /** + * A convenient function to create a setup event with event type + * {@link EventType#WAITING_END} + * @param name A task name causes this waiting duration + * @return A {@link SetupMetric} + * @throws IllegalArgumentException if the {@code name} is empty. + */ + @NonNull + public static SetupMetric ofWaitingEnd(@NonNull String name) { + Bundle bundle = new Bundle(); + bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); + return new SetupMetric(VERSION, name, EventType.WAITING_END, + PersistableBundles.fromBundle(bundle)); + } + + /** + * A convenient function to create a setup event with event type {@link EventType#ERROR} + * @param name A name represents this error + * @param errorCode A error code + * @return A {@link SetupMetric} + * @throws IllegalArgumentException if the {@code name} is empty. + */ + @NonNull + public static SetupMetric ofError(@NonNull String name, int errorCode) { + Bundle bundle = new Bundle(); + bundle.putInt(SETUP_METRIC_BUNDLE_ERROR_KEY, errorCode); + bundle.putLong(SETUP_METRIC_BUNDLE_TIMESTAMP_KEY, ClockProvider.timeInMillis()); + return new SetupMetric(VERSION, name, EventType.ERROR, PersistableBundles.fromBundle(bundle)); + } + + /** Converts {@link SetupMetric} into {@link Bundle}. */ + @NonNull + public static Bundle toBundle(@NonNull SetupMetric setupMetric) { + Preconditions.checkNotNull(setupMetric, "SetupMetric cannot be null."); + Bundle bundle = new Bundle(); + bundle.putInt(SETUP_METRIC_BUNDLE_VERSION_KEY, VERSION); + bundle.putString(SETUP_METRIC_BUNDLE_NAME_KEY, setupMetric.name); + bundle.putInt(SETUP_METRIC_BUNDLE_TYPE_KEY, setupMetric.type); + bundle.putBundle( + SETUP_METRIC_BUNDLE_VALUES_KEY, PersistableBundles.toBundle(setupMetric.values)); + return bundle; + } + + /** + * Converts {@link Bundle} into {@link SetupMetric}. + * Throw {@link IllegalArgumentException} if the bundle version is unsupported. + */ + @NonNull + public static SetupMetric fromBundle(@NonNull Bundle bundle) { + Preconditions.checkNotNull(bundle, "Bundle cannot be null"); + int version = bundle.getInt(SETUP_METRIC_BUNDLE_VERSION_KEY, INVALID_VERSION); + if (version == 1) { + return new SetupMetric( + bundle.getInt(SETUP_METRIC_BUNDLE_VERSION_KEY), + bundle.getString(SETUP_METRIC_BUNDLE_NAME_KEY), + bundle.getInt(SETUP_METRIC_BUNDLE_TYPE_KEY), + PersistableBundles.fromBundle(bundle.getBundle(SETUP_METRIC_BUNDLE_VALUES_KEY))); + } else { + throw new IllegalArgumentException("Unsupported version: " + version); + } + } + + private SetupMetric( + int version, String name, @EventType int type, @NonNull PersistableBundle values) { + Preconditions.checkArgument( + name != null && name.length() != 0, + "name cannot be null or empty."); + this.version = version; + this.name = name; + this.type = type; + this.values = values; + } + + private final int version; + private final String name; + @EventType private final int type; + private final PersistableBundle values; + + public int getVersion() { + return version; + } + + public String getName() { + return name; + } + + @EventType + public int getType() { + return type; + } + + public PersistableBundle getValues() { + return values; + } + + public static final Creator CREATOR = + new Creator<>() { + @Override + public SetupMetric createFromParcel(@NonNull Parcel in) { + return new SetupMetric(in.readInt(), + in.readString(), + in.readInt(), + in.readPersistableBundle(SetupMetric.class.getClassLoader())); + } + + @Override + public SetupMetric[] newArray(int size) { + return new SetupMetric[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(name); + parcel.writeInt(type); + parcel.writePersistableBundle(values); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SetupMetric)) { + return false; + } + SetupMetric that = (SetupMetric) o; + return ObjectUtils.equals(name, that.name) + && ObjectUtils.equals(type, that.type) + && PersistableBundles.equals(values, that.values); + } + + @Override + public int hashCode() { + return ObjectUtils.hashCode(name, type, values); + } + + @NonNull + @Override + public String toString() { + return "SetupMetric {name=" + + getName() + + ", type=" + + getType() + + ", bundle=" + + getValues().toString() + + "}"; + } +} diff --git a/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java b/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java index 8d696e0..786494e 100644 --- a/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java +++ b/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java @@ -22,11 +22,14 @@ import com.google.android.setupcompat.internal.Preconditions; import com.google.android.setupcompat.internal.SetupCompatServiceInvoker; import com.google.android.setupcompat.logging.internal.MetricBundleConverter; import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricType; +import com.google.android.setupcompat.util.Logger; import java.util.concurrent.TimeUnit; /** SetupMetricsLogger provides an easy way to log custom metrics to SetupWizard. */ public class SetupMetricsLogger { + private static final Logger LOG = new Logger("SetupMetricsLogger"); + /** Logs an instance of {@link CustomEvent} to SetupWizard. */ public static void logCustomEvent(@NonNull Context context, @NonNull CustomEvent customEvent) { Preconditions.checkNotNull(context, "Context cannot be null."); @@ -71,4 +74,22 @@ public class SetupMetricsLogger { MetricType.DURATION_EVENT, MetricBundleConverter.createBundleForLoggingTimer(timerName, timeInMillis)); } + + /** + * Logs setup collection metrics (go/suw-metrics-collection-api) + */ + public static void logMetrics( + @NonNull Context context, @NonNull ScreenKey screenKey, @NonNull SetupMetric... metrics) { + Preconditions.checkNotNull(context, "Context cannot be null."); + Preconditions.checkNotNull(screenKey, "ScreenKey cannot be null."); + Preconditions.checkNotNull(metrics, "SetupMetric cannot be null."); + + for (SetupMetric metric : metrics) { + LOG.atDebug("Log metric: " + screenKey + ", " + metric); + + SetupCompatServiceInvoker.get(context).logMetricEvent( + MetricType.SETUP_COLLECTION_EVENT, + MetricBundleConverter.createBundleForLoggingSetupMetric(screenKey, metric)); + } + } } diff --git a/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java b/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java index e1a3909..8e5ba20 100644 --- a/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java +++ b/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java @@ -3,6 +3,8 @@ package com.google.android.setupcompat.logging.internal; import android.os.Bundle; import com.google.android.setupcompat.logging.CustomEvent; import com.google.android.setupcompat.logging.MetricKey; +import com.google.android.setupcompat.logging.ScreenKey; +import com.google.android.setupcompat.logging.SetupMetric; import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricBundleKeys; /** Collection of helper methods for reading and writing {@link CustomEvent}, {@link MetricKey}. */ @@ -28,6 +30,13 @@ public final class MetricBundleConverter { return bundle; } + public static Bundle createBundleForLoggingSetupMetric(ScreenKey screenKey, SetupMetric metric) { + Bundle bundle = new Bundle(); + bundle.putParcelable(MetricBundleKeys.SCREEN_KEY_BUNDLE, ScreenKey.toBundle(screenKey)); + bundle.putParcelable(MetricBundleKeys.SETUP_METRIC_BUNDLE, SetupMetric.toBundle(metric)); + return bundle; + } + private MetricBundleConverter() { throw new AssertionError("Cannot instantiate MetricBundleConverter"); } diff --git a/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java b/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java index 57a7272..d4995b7 100644 --- a/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java +++ b/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java @@ -20,6 +20,8 @@ import android.content.Context; import androidx.annotation.IntDef; import androidx.annotation.StringDef; import com.google.android.setupcompat.logging.MetricKey; +import com.google.android.setupcompat.logging.ScreenKey; +import com.google.android.setupcompat.logging.SetupMetric; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -28,7 +30,12 @@ public interface SetupMetricsLoggingConstants { /** Enumeration of supported metric types logged to SetupWizard. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({MetricType.CUSTOM_EVENT, MetricType.COUNTER_EVENT, MetricType.DURATION_EVENT}) + @IntDef({ + MetricType.CUSTOM_EVENT, + MetricType.DURATION_EVENT, + MetricType.COUNTER_EVENT, + MetricType.SETUP_COLLECTION_EVENT, + MetricType.INTERNAL}) @interface MetricType { /** * MetricType constant used when logging {@link @@ -47,10 +54,39 @@ public interface SetupMetricsLoggingConstants { */ int COUNTER_EVENT = 3; + /** + * MetricType constant used when logging setup metric using {@link + * com.google.android.setupcompat.logging.SetupMetricsLogger#logMetrics(Context, ScreenKey, + * SetupMetric...)}. + */ + int SETUP_COLLECTION_EVENT = 4; + /** MetricType constant used for internal logging purposes. */ int INTERNAL = 100; } + /** + * Enumeration of supported EventType of {@link MetricType#SETUP_COLLECTION_EVENT} logged to + * SetupWizard. (go/suw-metrics-collection-api) + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + EventType.UNKNOWN, + EventType.IMPRESSION, + EventType.OPT_IN, + EventType.WAITING_START, + EventType.WAITING_END, + EventType.ERROR, + }) + @interface EventType { + int UNKNOWN = 1; + int IMPRESSION = 2; + int OPT_IN = 3; + int WAITING_START = 4; + int WAITING_END = 5; + int ERROR = 6; + } + /** Keys of the bundle used while logging data to SetupWizard. */ @Retention(RetentionPolicy.SOURCE) @StringDef({ @@ -59,7 +95,9 @@ public interface SetupMetricsLoggingConstants { MetricBundleKeys.CUSTOM_EVENT, MetricBundleKeys.CUSTOM_EVENT_BUNDLE, MetricBundleKeys.TIME_MILLIS_LONG, - MetricBundleKeys.COUNTER_INT + MetricBundleKeys.COUNTER_INT, + MetricBundleKeys.SCREEN_KEY_BUNDLE, + MetricBundleKeys.SETUP_METRIC_BUNDLE, }) @interface MetricBundleKeys { /** @@ -104,5 +142,17 @@ public interface SetupMetricsLoggingConstants { * com.google.android.setupcompat.logging.CustomEvent}. */ String CUSTOM_EVENT_BUNDLE = "CustomEvent_bundle"; + + /** + * This key will be used when {@code metricType} is {@link MetricType#SETUP_COLLECTION_EVENT} + * with the value being a Bundle which can be used to read {@link ScreenKey} + */ + String SCREEN_KEY_BUNDLE = "ScreenKey_bundle"; + + /** + * This key will be used when {@code metricType} is {@link MetricType#SETUP_COLLECTION_EVENT} + * with the value being a Bundle which can be used to read {@link SetupMetric} + */ + String SETUP_METRIC_BUNDLE = "SetupMetric_bundle"; } } diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java index b77eacf..2268b1e 100644 --- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java @@ -73,6 +73,7 @@ public class FooterBarMixin implements Mixin { @VisibleForTesting final boolean applyPartnerResources; @VisibleForTesting final boolean applyDynamicColor; @VisibleForTesting final boolean useFullDynamicColor; + @VisibleForTesting final boolean footerButtonAlignEnd; @VisibleForTesting public LinearLayout buttonContainer; private FooterButton primaryButton; @@ -206,6 +207,8 @@ public class FooterBarMixin implements Mixin { a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterBackground, 0); footerBarSecondaryBackgroundColor = a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarSecondaryFooterBackground, 0); + footerButtonAlignEnd = + a.getBoolean(R.styleable.SucFooterBarMixin_sucFooterBarButtonAlignEnd, false); int primaryBtn = a.getResourceId(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterButton, 0); @@ -234,7 +237,7 @@ public class FooterBarMixin implements Mixin { return PartnerConfigHelper.get(context) .getBoolean(context, PartnerConfig.CONFIG_FOOTER_BUTTON_ALIGNED_END, false); } else { - return false; + return footerButtonAlignEnd; } } @@ -617,7 +620,7 @@ public class FooterBarMixin implements Mixin { return overrideTheme; } - @VisibleForTesting + /** Returns the {@link LinearLayout} of button container. */ public LinearLayout getButtonContainer() { return buttonContainer; } diff --git a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java index 090e1df..d1b0ccb 100644 --- a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java +++ b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java @@ -83,7 +83,6 @@ public final class BuildCompatUtils { * @return Whether the current OS version is higher or equal to U. */ public static boolean isAtLeastU() { - System.out.println("Build.VERSION.CODENAME=" + Build.VERSION.CODENAME); return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 34) || (Build.VERSION.CODENAME.length() == 1 && Build.VERSION.CODENAME.charAt(0) >= 'U' diff --git a/main/res/values/attrs.xml b/main/res/values/attrs.xml index 07f87ed..0aaea8b 100644 --- a/main/res/values/attrs.xml +++ b/main/res/values/attrs.xml @@ -82,6 +82,7 @@ + diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java index 1b73098..96a4317 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java @@ -68,12 +68,19 @@ public class PartnerConfigHelper { @VisibleForTesting public static final String IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD = "isNeutralButtonStyleEnabled"; + @VisibleForTesting + public static final String IS_EMBEDDED_ACTIVITY_ONE_PANE_ENABLED_METHOD = + "isEmbeddedActivityOnePaneEnabled"; + @VisibleForTesting public static final String GET_SUW_DEFAULT_THEME_STRING_METHOD = "suwDefaultThemeString"; @VisibleForTesting public static final String SUW_PACKAGE_NAME = "com.google.android.setupwizard"; @VisibleForTesting public static final String MATERIAL_YOU_RESOURCE_SUFFIX = "_material_you"; + @VisibleForTesting + public static final String EMBEDDED_ACTIVITY_RESOURCE_SUFFIX = "_embedded_activity"; + @VisibleForTesting static Bundle suwDayNightEnabledBundle = null; @VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null; @@ -84,6 +91,8 @@ public class PartnerConfigHelper { @VisibleForTesting public static Bundle applyNeutralButtonStyleBundle = null; + @VisibleForTesting public static Bundle applyEmbeddedActivityOnePaneBundle = null; + @VisibleForTesting public static Bundle suwDefaultThemeBundle = null; private static PartnerConfigHelper instance = null; @@ -97,8 +106,16 @@ public class PartnerConfigHelper { private static int savedConfigUiMode; + private static boolean savedConfigEmbeddedActivityMode; + + @VisibleForTesting static Bundle applyTransitionBundle = null; + @VisibleForTesting public static int savedOrientation = Configuration.ORIENTATION_PORTRAIT; + /** The method name to get if transition settings is set from client. */ + public static final String APPLY_GLIF_THEME_CONTROLLED_TRANSITION_METHOD = + "applyGlifThemeControlledTransition"; + /** * When testing related to fake PartnerConfigHelper instance, should sync the following saved * config with testing environment. @@ -117,6 +134,8 @@ public class PartnerConfigHelper { private static boolean isValidInstance(@NonNull Context context) { Configuration currentConfig = context.getResources().getConfiguration(); if (instance == null) { + savedConfigEmbeddedActivityMode = + isEmbeddedActivityOnePaneEnabled(context) && BuildCompatUtils.isAtLeastU(); savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; savedOrientation = currentConfig.orientation; savedScreenWidth = currentConfig.screenWidthDp; @@ -126,7 +145,10 @@ public class PartnerConfigHelper { boolean uiModeChanged = isSetupWizardDayNightEnabled(context) && (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != savedConfigUiMode; + boolean embeddedActivityModeChanged = + isEmbeddedActivityOnePaneEnabled(context) && BuildCompatUtils.isAtLeastU(); if (uiModeChanged + || embeddedActivityModeChanged != savedConfigEmbeddedActivityMode || currentConfig.orientation != savedOrientation || currentConfig.screenWidthDp != savedScreenWidth || currentConfig.screenHeightDp != savedScreenHeight) { @@ -554,6 +576,11 @@ public class PartnerConfigHelper { ResourceEntry adjustResourceEntry = adjustResourceEntryDefaultValue( context, ResourceEntry.fromBundle(context, resourceEntryBundle)); + ; + if (BuildCompatUtils.isAtLeastU() && isEmbeddedActivityOnePaneEnabled(context)) { + adjustResourceEntry = embeddedActivityResourceEntryDefaultValue(context, adjustResourceEntry); + } + return adjustResourceEntryDayNightMode(context, adjustResourceEntry); } @@ -617,6 +644,42 @@ public class PartnerConfigHelper { return inputResourceEntry; } + // Check the embedded acitvity flag and replace the inputResourceEntry.resourceName & + // inputResourceEntry.resourceId after U. + ResourceEntry embeddedActivityResourceEntryDefaultValue( + Context context, ResourceEntry inputResourceEntry) { + // If not overlay resource + try { + if (SUW_PACKAGE_NAME.equals(inputResourceEntry.getPackageName())) { + String resourceTypeName = + inputResourceEntry + .getResources() + .getResourceTypeName(inputResourceEntry.getResourceId()); + // try to update resourceName & resourceId + String embeddedActivityResourceName = + inputResourceEntry.getResourceName().concat(EMBEDDED_ACTIVITY_RESOURCE_SUFFIX); + int embeddedActivityResourceId = + inputResourceEntry + .getResources() + .getIdentifier( + embeddedActivityResourceName, + resourceTypeName, + inputResourceEntry.getPackageName()); + if (embeddedActivityResourceId != 0) { + Log.i(TAG, "use embedded activity resource:" + embeddedActivityResourceName); + return new ResourceEntry( + inputResourceEntry.getPackageName(), + embeddedActivityResourceName, + embeddedActivityResourceId, + inputResourceEntry.getResources()); + } + } + } catch (NotFoundException ex) { + // fall through + } + return inputResourceEntry; + } + @VisibleForTesting public static synchronized void resetInstance() { instance = null; @@ -625,7 +688,9 @@ public class PartnerConfigHelper { applyMaterialYouConfigBundle = null; applyDynamicColorBundle = null; applyNeutralButtonStyleBundle = null; + applyEmbeddedActivityOnePaneBundle = null; suwDefaultThemeBundle = null; + applyTransitionBundle = null; } /** @@ -766,6 +831,32 @@ public class PartnerConfigHelper { && applyDynamicColorBundle.getBoolean(IS_DYNAMIC_COLOR_ENABLED_METHOD, false)); } + /** Returns true if the SetupWizard supports the one-pane embedded activity during setup flow. */ + public static boolean isEmbeddedActivityOnePaneEnabled(@NonNull Context context) { + if (applyEmbeddedActivityOnePaneBundle == null) { + try { + applyEmbeddedActivityOnePaneBundle = + context + .getContentResolver() + .call( + getContentUri(), + IS_EMBEDDED_ACTIVITY_ONE_PANE_ENABLED_METHOD, + /* arg= */ null, + /* extras= */ null); + } catch (IllegalArgumentException | SecurityException exception) { + Log.w( + TAG, + "SetupWizard one-pane support in embedded activity status unknown; return as false."); + applyEmbeddedActivityOnePaneBundle = null; + return false; + } + } + + return (applyEmbeddedActivityOnePaneBundle != null + && applyEmbeddedActivityOnePaneBundle.getBoolean( + IS_EMBEDDED_ACTIVITY_ONE_PANE_ENABLED_METHOD, false)); + } + /** Returns true if the SetupWizard supports the neutral button style during setup flow. */ public static boolean isNeutralButtonStyleEnabled(@NonNull Context context) { if (applyNeutralButtonStyleBundle == null) { @@ -789,6 +880,37 @@ public class PartnerConfigHelper { && applyNeutralButtonStyleBundle.getBoolean(IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD, false)); } + /** + * Returns the system property to indicate the transition settings is set by Glif theme rather + * than the client. + */ + public static boolean isGlifThemeControlledTransitionApplied(@NonNull Context context) { + if (applyTransitionBundle == null + || applyTransitionBundle.isEmpty()) { + try { + applyTransitionBundle = + context + .getContentResolver() + .call( + getContentUri(), + APPLY_GLIF_THEME_CONTROLLED_TRANSITION_METHOD, + /* arg= */ null, + /* extras= */ null); + } catch (IllegalArgumentException | SecurityException exception) { + Log.w( + TAG, + "applyGlifThemeControlledTransition unknown; return applyGlifThemeControlledTransition" + + " as default value"); + } + } + if (applyTransitionBundle != null + && !applyTransitionBundle.isEmpty()) { + return applyTransitionBundle.getBoolean( + APPLY_GLIF_THEME_CONTROLLED_TRANSITION_METHOD, true); + } + return true; + } + @VisibleForTesting static Uri getContentUri() { return new Uri.Builder() -- cgit v1.2.3 From e1b6a73e3916d90a5662a3b6875b19d5c8d72902 Mon Sep 17 00:00:00 2001 From: Setup Wizard Team Date: Fri, 10 Mar 2023 10:26:53 +0800 Subject: Import updated Android SetupCompat Library 515497412 Copied from google3/third_party/java_src/android_libs/setupcompat Test: mm Bug:272641376 Included changes: - 515497412 [Sonic] Allow setup wizard is binding AbstractSetupBtsSer... - 514565477 [BTS] Remove AbstractSetupBtsReceiver becasue the Receive... - 511277098 Create BaseScreenLoggingActivity provides the basic imple... PiperOrigin-RevId: 515497412 Change-Id: Idfb8953bf8205b78627c9081a9ef6b52d462234b --- .../setupcompat/bts/AbstractSetupBtsReceiver.java | 68 ---------------------- .../setupcompat/bts/AbstractSetupBtsService.java | 6 +- .../setupcompat/logging/SetupMetricsLogger.java | 51 +++++++++++++++- 3 files changed, 54 insertions(+), 71 deletions(-) delete mode 100644 bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsReceiver.java diff --git a/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsReceiver.java b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsReceiver.java deleted file mode 100644 index 9e15e30..0000000 --- a/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsReceiver.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2022 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.bts; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.Binder; -import androidx.annotation.NonNull; -import com.google.android.setupcompat.internal.Preconditions; -import com.google.android.setupcompat.util.Logger; -import java.util.concurrent.Executor; - -/** Class to receive broadcast intent from SUW, and execute the client's task in the executor. */ -public abstract class AbstractSetupBtsReceiver extends BroadcastReceiver { - private static final Logger LOG = new Logger(AbstractSetupBtsReceiver.class); - - @Override - public void onReceive(Context context, Intent intent) { - if (intent != null && getIntentAction().equals(intent.getAction())) { - Executor executor = getExecutor(); - String simpleClassName = this.getClass().getSimpleName(); - if (executor != null) { - executor.execute( - () -> { - Preconditions.ensureNotOnMainThread(simpleClassName + "::onStartTask"); - onStartTask(); - }); - } - } else { - LOG.w( - "[" - + this.getClass().getSimpleName() - + "] Unauthorized binder uid=" - + Binder.getCallingUid() - + ", intentAction=" - + (intent == null ? "(null)" : intent.getAction())); - } - } - - /** - * Gets the intent action that expected to execute the task. Use to avoid the receiver launch - * unexpectedly. - */ - @NonNull - protected abstract String getIntentAction(); - - /** Returns the executor used to execute the task. */ - @NonNull - protected abstract Executor getExecutor(); - - /** Tasks can be done before activity launched, in order to remove the loading before activity. */ - protected abstract void onStartTask(); -} diff --git a/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java index eb10693..80066ab 100644 --- a/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java +++ b/bts/java/com/google/android/setupcompat/bts/AbstractSetupBtsService.java @@ -256,7 +256,7 @@ public abstract class AbstractSetupBtsService extends Service { for (Signature signature : info.signingInfo.getApkContentsSigners()) { if (SETUP_WIZARD_RELEASE_CERTIFICATE_STRING.equals(signature.toCharsString()) - || (allowDebugKeys + || (isAllowDebugKeysOrBuild() && SETUP_WIZARD_DEBUG_CERTIFICATE_STRING.equals(signature.toCharsString()))) { return true; } @@ -273,6 +273,10 @@ public abstract class AbstractSetupBtsService extends Service { return false; } + private boolean isAllowDebugKeysOrBuild() { + return Build.TYPE.equals("userdebug") || Build.TYPE.equals("eng") || allowDebugKeys; + } + @VisibleForTesting boolean verifyCallingAppPermission() { int checkPermission = diff --git a/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java b/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java index 786494e..fab38a2 100644 --- a/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java +++ b/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java @@ -16,8 +16,10 @@ package com.google.android.setupcompat.logging; +import android.annotation.SuppressLint; import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.google.android.setupcompat.internal.Preconditions; import com.google.android.setupcompat.internal.SetupCompatServiceInvoker; import com.google.android.setupcompat.logging.internal.MetricBundleConverter; @@ -25,7 +27,10 @@ import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConsta import com.google.android.setupcompat.util.Logger; import java.util.concurrent.TimeUnit; -/** SetupMetricsLogger provides an easy way to log custom metrics to SetupWizard. */ +/** + * SetupMetricsLogger provides an easy way to log custom metrics to SetupWizard. + * (go/suw-metrics-collection-api) + */ public class SetupMetricsLogger { private static final Logger LOG = new Logger("SetupMetricsLogger"); @@ -76,7 +81,7 @@ public class SetupMetricsLogger { } /** - * Logs setup collection metrics (go/suw-metrics-collection-api) + * Logs setup collection metrics */ public static void logMetrics( @NonNull Context context, @NonNull ScreenKey screenKey, @NonNull SetupMetric... metrics) { @@ -92,4 +97,46 @@ public class SetupMetricsLogger { MetricBundleConverter.createBundleForLoggingSetupMetric(screenKey, metric)); } } + + /** + * A non-static method to log setup collection metrics calling + * {@link #logMetrics(Context, ScreenKey, SetupMetric...)} as the actual implementation. This + * function is useful when performing unit tests in caller's implementation. + *

    + * For unit testing, caller uses {@link #setInstanceForTesting(SetupMetricsLogger)} to inject the + * mocked SetupMetricsLogger instance and use {@link SetupMetricsLogger#get(Context)} to get the + * SetupMetricsLogger. And verify the this function is called with expected parameters. + * + * @see #logMetrics(Context, ScreenKey, SetupMetric...) + */ + public void logMetrics(@NonNull ScreenKey screenKey, @NonNull SetupMetric... metrics) { + SetupMetricsLogger.logMetrics(context, screenKey, metrics); + } + + private SetupMetricsLogger(Context context) { + this.context = context; + } + + private final Context context; + + /** Use this function to get a singleton of {@link SetupMetricsLogger} */ + public static synchronized SetupMetricsLogger get(Context context) { + if (instance == null) { + instance = new SetupMetricsLogger(context.getApplicationContext()); + } + + return instance; + } + + @VisibleForTesting + public static void setInstanceForTesting(SetupMetricsLogger testInstance) { + instance = testInstance; + } + + // The instance is coming from Application context which alive during the application activate and + // it's not depend on the activities life cycle, so we can avoid memory leak. However linter + // cannot distinguish Application context or activity context, so we add @SuppressLint to avoid + // lint error. + @SuppressLint("StaticFieldLeak") + private static SetupMetricsLogger instance; } -- cgit v1.2.3