diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2021-06-21 14:33:47 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2021-06-21 14:33:47 +0000 |
commit | 1fb62b2592489c1708eb149d52a6064f4524ea79 (patch) | |
tree | 033a2ea54f61c1238a5e634fdcc6ee9b4c81fc3a | |
parent | d2c5b7d21ae8fada5dfc60cab11d29f66b2fee0c (diff) | |
parent | fc6ed7a379bf120a3016a38976a7ea192763e3bf (diff) | |
download | setupwizard-android12-mainline-documentsui-release.tar.gz |
Snap for 7478028 from fc6ed7a379bf120a3016a38976a7ea192763e3bf to mainline-documentsui-releaseandroid-mainline-12.0.0_r26android-mainline-12.0.0_r2aml_doc_310851020android12-mainline-documentsui-release
Change-Id: Ie94082b61c19820fd19fa4f4c984b7916128bca6
42 files changed, 944 insertions, 128 deletions
diff --git a/library/main/Android.bp b/library/main/Android.bp index b7cc78f..f99ecdd 100644 --- a/library/main/Android.bp +++ b/library/main/Android.bp @@ -13,6 +13,10 @@ // limitations under the License. // +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + android_library { name: "car-setup-wizard-lib", srcs: ["src/**/*.java"], @@ -22,5 +26,5 @@ android_library { optimize: { enabled: false, }, - min_sdk_version: "26", + min_sdk_version: "28", } diff --git a/library/main/AndroidManifest-gradle.xml b/library/main/AndroidManifest-gradle.xml new file mode 100644 index 0000000..cc9c1ab --- /dev/null +++ b/library/main/AndroidManifest-gradle.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.car.setupwizardlib"> +</manifest> diff --git a/library/main/build.gradle b/library/main/build.gradle new file mode 100644 index 0000000..412e3b0 --- /dev/null +++ b/library/main/build.gradle @@ -0,0 +1,85 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Library-level build file + +apply plugin: 'com.android.library' + +buildscript { + repositories { + mavenCentral() + } +} + +android { + compileSdkVersion 30 + + defaultConfig { + minSdkVersion 28 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest-gradle.xml' + res.srcDirs = ['res'] + java.srcDirs = ['src'] + } + + test { + manifest.srcFile 'tests/robotests/AndroidManifest.xml' + res.srcDirs += ['tests/robotests/res','tests/robotests/config'] + java.srcDirs = ['tests/robotests/src'] + } + } + + android { + lintOptions { + abortOnError false + } + } + + testOptions { + unitTests { + includeAndroidResources = true + } + } +} + +dependencies { + implementation files('../../../../../../prebuilts/sdk/30/system/android.car-system-stubs.jar') + + implementation 'androidx.car:car:1.0.0-alpha7' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.2.0' + implementation 'androidx.gridlayout:gridlayout:1.0.0' + implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.core:core:1.3.2' + implementation 'androidx.annotation:annotation:1.2.0' + + testImplementation 'com.google.truth:truth:0.41' + testImplementation 'org.mockito:mockito-core:3.6.0' + testImplementation 'org.robolectric:robolectric:4.5.1' +} diff --git a/library/main/res/drawable/car_ic_close.xml b/library/main/res/drawable/car_ic_close.xml new file mode 100644 index 0000000..08b5e36 --- /dev/null +++ b/library/main/res/drawable/car_ic_close.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2020 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="@dimen/car_primary_icon_size" + android:height="@dimen/car_primary_icon_size" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12 19,6.41z" + android:fillColor="@color/car_tint_light"/> +</vector> diff --git a/library/main/res/layout/car_setup_wizard_toolbar.xml b/library/main/res/layout/car_setup_wizard_toolbar.xml index 5c5703b..5ae4e11 100644 --- a/library/main/res/layout/car_setup_wizard_toolbar.xml +++ b/library/main/res/layout/car_setup_wizard_toolbar.xml @@ -37,6 +37,15 @@ android:background="@drawable/button_ripple_bg" android:contentDescription="@string/back_button_content_description" android:src="@drawable/car_ic_arrow_back"/> + + <ImageView + android:id="@+id/close_button" + android:layout_width="@dimen/car_primary_icon_size" + android:layout_height="@dimen/car_primary_icon_size" + android:layout_gravity="center" + android:background="@drawable/button_ripple_bg" + android:contentDescription="@string/close_button_content_description" + android:src="@drawable/car_ic_close"/> </FrameLayout> <TextView diff --git a/library/main/res/values-w1280dp/config.xml b/library/main/res/values-w1240dp-land/config.xml index 98343f3..98343f3 100644 --- a/library/main/res/values-w1280dp/config.xml +++ b/library/main/res/values-w1240dp-land/config.xml diff --git a/library/main/res/values-w1240dp-land/dimens.xml b/library/main/res/values-w1240dp-land/dimens.xml new file mode 100644 index 0000000..257d03a --- /dev/null +++ b/library/main/res/values-w1240dp-land/dimens.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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 + --> + +<resources> + <dimen name="suw_page_margin_horizontal">64dp</dimen> + <!-- The column inner padding of a two-column layout --> + <dimen name="suw_column_inner_padding_horizontal">48dp</dimen> +</resources>
\ No newline at end of file diff --git a/library/main/res/values-w1280dp-port/dimens.xml b/library/main/res/values-w1280dp-port/dimens.xml new file mode 100644 index 0000000..1a3d98e --- /dev/null +++ b/library/main/res/values-w1280dp-port/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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 + --> + +<resources> + <dimen name="suw_page_margin_horizontal">148dp</dimen> +</resources>
\ No newline at end of file diff --git a/library/main/res/values-w1920dp-land/dimens.xml b/library/main/res/values-w1920dp-land/dimens.xml new file mode 100644 index 0000000..6e5add0 --- /dev/null +++ b/library/main/res/values-w1920dp-land/dimens.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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 + --> + +<resources> + <dimen name="suw_page_margin_horizontal">132dp</dimen> + <!-- The column inner padding of a two-column layout --> + <dimen name="suw_column_inner_padding_horizontal">80dp</dimen> +</resources>
\ No newline at end of file diff --git a/library/main/res/values/attrs.xml b/library/main/res/values/attrs.xml index 957f3dd..31d2f5b 100644 --- a/library/main/res/values/attrs.xml +++ b/library/main/res/values/attrs.xml @@ -15,56 +15,81 @@ limitations under the License. --> <resources> + <!-- Attributes related to the visibility of the back button --> + <attr name="showBackButton" format="boolean"/> + + <!-- Attributes related to the visibility and text of the toolbar title --> + <attr name="showToolbarTitle" format="boolean"/> + <attr name="toolbarTitleText" format="string"/> + + <!-- Attributes related to the visibility and text of primary continue button --> + <attr name="showPrimaryToolbarButton" format="boolean"/> + <attr name="primaryToolbarButtonText" format="string"/> + <attr name="primaryToolbarButtonEnabled" format="boolean"/> + <attr name="primaryToolbarButtonFlat" format="boolean"/> + + <!-- Attributes related to the visibility and text of secondary continue button --> + <attr name="showSecondaryToolbarButton" format="boolean"/> + <attr name="secondaryToolbarButtonText" format="string"/> + <attr name="secondaryToolbarButtonEnabled" format="boolean"/> + + <!-- Attributes related to the visibility and indeterminate/determinate state + of the progress bar --> + <attr name="showProgressBar"/> + <attr name="indeterminateProgressBar"/> + <!-- Custom attribute definitions for the CarSetupWizardLayout --> <declare-styleable name="CarSetupWizardLayout"> <!-- Attributes related to the visibility of the back button --> - <attr name="showBackButton" format="boolean"/> + <attr name="showBackButton"/> <!-- Attributes related to the visibility and text of the toolbar title --> - <attr name="showToolbarTitle" format="boolean"/> - <attr name="toolbarTitleText" format="string"/> + <attr name="showToolbarTitle"/> + <attr name="toolbarTitleText"/> <!-- Attributes related to the visibility and text of primary continue button --> - <attr name="showPrimaryToolbarButton" format="boolean"/> - <attr name="primaryToolbarButtonText" format="string"/> - <attr name="primaryToolbarButtonEnabled" format="boolean"/> - <attr name="primaryToolbarButtonFlat" format="boolean"/> + <attr name="showPrimaryToolbarButton"/> + <attr name="primaryToolbarButtonText"/> + <attr name="primaryToolbarButtonEnabled"/> + <attr name="primaryToolbarButtonFlat"/> <!-- Attributes related to the visibility and text of secondary continue button --> - <attr name="showSecondaryToolbarButton" format="boolean"/> - <attr name="secondaryToolbarButtonText" format="string"/> - <attr name="secondaryToolbarButtonEnabled" format="boolean"/> + <attr name="showSecondaryToolbarButton"/> + <attr name="secondaryToolbarButtonText"/> + <attr name="secondaryToolbarButtonEnabled"/> <!-- Attributes related to the visibility and indeterminate/determinate state of the progress bar --> - <attr name="showProgressBar" format="boolean"/> - <attr name="indeterminateProgressBar" format="boolean"/> + <attr name="showProgressBar"/> + <attr name="indeterminateProgressBar" /> </declare-styleable> <!-- Custom attribute definitions for the CarSetupWizardBaseLayout --> <declare-styleable name="CarSetupWizardBaseLayout"> <!-- Attributes related to the visibility of the back button --> - <attr name="showBackButton" format="boolean"/> + <attr name="showBackButton"/> + <!-- Attributes related to the visibility of the close button --> + <attr name="showCloseButton" format="boolean"/> <!-- Attributes related to the visibility and text of the toolbar title --> - <attr name="showToolbarTitle" format="boolean"/> - <attr name="toolbarTitleText" format="string"/> + <attr name="showToolbarTitle"/> + <attr name="toolbarTitleText"/> <!-- Attributes related to the visibility and text of primary continue button --> - <attr name="showPrimaryToolbarButton" format="boolean"/> - <attr name="primaryToolbarButtonText" format="string"/> - <attr name="primaryToolbarButtonEnabled" format="boolean"/> - <attr name="primaryToolbarButtonFlat" format="boolean"/> + <attr name="showPrimaryToolbarButton"/> + <attr name="primaryToolbarButtonText"/> + <attr name="primaryToolbarButtonEnabled"/> + <attr name="primaryToolbarButtonFlat"/> <!-- Attributes related to the visibility and text of secondary continue button --> - <attr name="showSecondaryToolbarButton" format="boolean"/> - <attr name="secondaryToolbarButtonText" format="string"/> - <attr name="secondaryToolbarButtonEnabled" format="boolean"/> + <attr name="showSecondaryToolbarButton"/> + <attr name="secondaryToolbarButtonText" /> + <attr name="secondaryToolbarButtonEnabled"/> <!-- Attributes related to the visibility and indeterminate/determinate state of the progress bar --> - <attr name="showProgressBar" format="boolean"/> - <attr name="indeterminateProgressBar" format="boolean"/> + <attr name="showProgressBar"/> + <attr name="indeterminateProgressBar"/> </declare-styleable> -</resources>
\ No newline at end of file +</resources> diff --git a/library/main/res/values/colors.xml b/library/main/res/values/colors.xml index 6767e03..a3df84d 100644 --- a/library/main/res/values/colors.xml +++ b/library/main/res/values/colors.xml @@ -23,4 +23,17 @@ <color name="blue_400">#6BA5ED</color> <color name="car_suw_tint">@color/blue_400</color> + + <!-- White --> + <color name="suw_color_primary">@android:color/white</color> + <!-- Material grey500 --> + <color name="suw_color_secondary">#9AA0A6</color> + <!-- Material blue300 --> + <color name="suw_color_accent">#8AB4F8</color> + <!-- Material grey200 --> + <color name="suw_color_list_icon">#E8EAED</color> + <!-- Material grey800 --> + <color name="suw_color_divider">#3C4043</color> + <!-- Material grey900 --> + <color name="suw_color_background">#202124</color> </resources> diff --git a/library/main/res/values/dimens.xml b/library/main/res/values/dimens.xml new file mode 100644 index 0000000..db6c833 --- /dev/null +++ b/library/main/res/values/dimens.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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 + --> + +<resources> + <dimen name="suw_padding_3">16dp</dimen> + <dimen name="suw_padding_4">24dp</dimen> + <dimen name="suw_padding_5">32dp</dimen> + <dimen name="suw_padding_6">48dp</dimen> + <dimen name="suw_padding_7">64dp</dimen> + <dimen name="suw_padding_8">96dp</dimen> + <dimen name="suw_padding_9">128dp</dimen> + + <dimen name="suw_page_margin_horizontal">112dp</dimen> + <!-- The column inner padding of a two-column layout. N/A in portrait --> + <dimen name="suw_column_inner_padding_horizontal">0dp</dimen> +</resources>
\ No newline at end of file diff --git a/library/main/res/values/integers.xml b/library/main/res/values/integers.xml new file mode 100644 index 0000000..f7c13cd --- /dev/null +++ b/library/main/res/values/integers.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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 + --> + +<resources> + <!-- The layout_weight of two-column layout --> + <integer name="suw_title_column_weight">5</integer> + <integer name="suw_content_column_weight">7</integer> + + <!-- The layout_weight of single-column layout --> + <integer name="suw_page_content_weight">3</integer> + <integer name="suw_page_filler_weight">1</integer> +</resources>
\ No newline at end of file diff --git a/library/main/res/values/strings.xml b/library/main/res/values/strings.xml index beb37dc..027a683 100644 --- a/library/main/res/values/strings.xml +++ b/library/main/res/values/strings.xml @@ -17,4 +17,7 @@ <resources> <!-- Content description of the back button for accessibility services like Tallback [CHAR LIMIT=120] --> <string name="back_button_content_description">Navigate back</string> + + <!-- Content description of the close button for accessibility services like Tallback [CHAR LIMIT=120] --> + <string name="close_button_content_description">Close</string> </resources>
\ No newline at end of file diff --git a/library/main/src/com/android/car/setupwizardlib/BaseActivity.java b/library/main/src/com/android/car/setupwizardlib/BaseActivity.java index 4fe59e7..edd2b73 100644 --- a/library/main/src/com/android/car/setupwizardlib/BaseActivity.java +++ b/library/main/src/com/android/car/setupwizardlib/BaseActivity.java @@ -16,12 +16,12 @@ package com.android.car.setupwizardlib; -import android.annotation.CallSuper; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; +import androidx.annotation.CallSuper; import androidx.annotation.LayoutRes; import androidx.annotation.StyleRes; import androidx.annotation.VisibleForTesting; diff --git a/library/main/src/com/android/car/setupwizardlib/BaseSetupWizardActivity.java b/library/main/src/com/android/car/setupwizardlib/BaseSetupWizardActivity.java index 3da7df6..36caae0 100644 --- a/library/main/src/com/android/car/setupwizardlib/BaseSetupWizardActivity.java +++ b/library/main/src/com/android/car/setupwizardlib/BaseSetupWizardActivity.java @@ -16,12 +16,12 @@ package com.android.car.setupwizardlib; -import android.annotation.CallSuper; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; +import androidx.annotation.CallSuper; import androidx.annotation.LayoutRes; import androidx.annotation.StyleRes; import androidx.annotation.VisibleForTesting; @@ -97,6 +97,8 @@ abstract class BaseSetupWizardActivity extends FragmentActivity { } }); + mCarSetupWizardLayout.setCloseButtonListener(v-> handleCloseButton()); + resetPrimaryToolbarButtonOnClickListener(); resetSecondaryToolbarButtonOnClickListener(); @@ -255,6 +257,14 @@ abstract class BaseSetupWizardActivity extends FragmentActivity { } /** + * Method to be overwritten by subclasses wanting to implement their own close behavior. + * Default behavior is finishAction. + */ + protected void handleCloseButton() { + finishAction(); + } + + /** * Called when nextAction has been invoked, should be overridden on derived class when it is * needed perform work when nextAction has been invoked. */ @@ -350,6 +360,14 @@ abstract class BaseSetupWizardActivity extends FragmentActivity { } /** + * Sets whether the close button is visible. If this value is {@code true}, clicking the button + * will finish the current flow. + */ + protected void setCloseButtonVisible(boolean visible) { + mCarSetupWizardLayout.setCloseButtonVisible(visible); + } + + /** * Sets whether the toolbar title is visible. */ protected void setToolbarTitleVisible(boolean visible) { diff --git a/library/main/src/com/android/car/setupwizardlib/CarSetupWizardBaseLayout.java b/library/main/src/com/android/car/setupwizardlib/CarSetupWizardBaseLayout.java index 7a7808d..715315a 100644 --- a/library/main/src/com/android/car/setupwizardlib/CarSetupWizardBaseLayout.java +++ b/library/main/src/com/android/car/setupwizardlib/CarSetupWizardBaseLayout.java @@ -16,7 +16,6 @@ package com.android.car.setupwizardlib; -import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; @@ -41,6 +40,7 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.annotation.VisibleForTesting; @@ -59,8 +59,11 @@ import java.util.Objects; class CarSetupWizardBaseLayout extends LinearLayout { private static final String TAG = CarSetupWizardBaseLayout.class.getSimpleName(); private static final int INVALID_COLOR = 0; + // For mirroring an image + private static final float IMAGE_MIRROR_ROTATION = 180.0f; private View mBackButton; + private View mCloseButton; private View mTitleBar; private TextView mToolbarTitle; private PartnerConfigHelper mPartnerConfigHelper; @@ -118,6 +121,7 @@ class CarSetupWizardBaseLayout extends LinearLayout { */ private void init(TypedArray attrArray) { boolean showBackButton; + boolean showCloseButton; boolean showToolbarTitle; String toolbarTitleText; @@ -136,6 +140,8 @@ class CarSetupWizardBaseLayout extends LinearLayout { try { showBackButton = attrArray.getBoolean( R.styleable.CarSetupWizardBaseLayout_showBackButton, true); + showCloseButton = attrArray.getBoolean( + R.styleable.CarSetupWizardBaseLayout_showCloseButton, false); showToolbarTitle = attrArray.getBoolean( R.styleable.CarSetupWizardBaseLayout_showToolbarTitle, false); toolbarTitleText = attrArray.getString( @@ -164,15 +170,39 @@ class CarSetupWizardBaseLayout extends LinearLayout { LayoutInflater inflater = LayoutInflater.from(getContext()); inflater.inflate(R.layout.car_setup_wizard_layout, this); + View toolbar = findViewById(R.id.application_bar); + // The toolbar will not be mirrored in RTL + toolbar.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); - // Set the back button visibility based on the custom attribute. setBackButton(findViewById(R.id.back_button)); + setCloseButton(findViewById(R.id.close_button)); + Drawable drawable = mPartnerConfigHelper.getDrawable( getContext(), PartnerConfig.CONFIG_TOOLBAR_BUTTON_ICON_BACK); if (drawable != null) { ((ImageView) mBackButton).setImageDrawable(drawable); } + + Drawable closeButtonDrawable = mPartnerConfigHelper.getDrawable( + getContext(), PartnerConfig.CONFIG_TOOLBAR_BUTTON_ICON_CLOSE); + if (closeButtonDrawable != null) { + ((ImageView) mCloseButton).setImageDrawable(closeButtonDrawable); + } + + if (shouldMirrorNavIcons()) { + Log.v(TAG, "Mirroring navigation icons"); + mBackButton.setRotation(IMAGE_MIRROR_ROTATION); + mCloseButton.setRotation(IMAGE_MIRROR_ROTATION); + } + + if (showBackButton && showCloseButton) { + Log.w(TAG, "Showing Back and Close button simultaneously is not supported"); + } + + // Set the back button visibility based on the custom attribute. setBackButtonVisible(showBackButton); + // Set the close button visibility based on the custom attribute. + setCloseButtonVisible(showCloseButton); // Se the title bar. setTitleBar(findViewById(R.id.application_bar)); @@ -242,19 +272,25 @@ class CarSetupWizardBaseLayout extends LinearLayout { */ @VisibleForTesting void setViewVisible(View view, boolean visible) { + if (view == null) { + return; + } view.setVisibility(visible ? View.VISIBLE : View.GONE); } // Add or remove the back button touch delegate depending on whether it is visible. @VisibleForTesting - void updateBackButtonTouchDelegate(boolean visible) { + void updateNavigationButtonTouchDelegate(View button, boolean visible) { + if (button == null) { + return; + } if (visible) { // Post this action in the parent's message queue to make sure the parent // lays out its children before getHitRect() is called this.post(() -> { Rect delegateArea = new Rect(); - mBackButton.getHitRect(delegateArea); + button.getHitRect(delegateArea); /* * Update the delegate area based on the difference between the current size and @@ -273,17 +309,16 @@ class CarSetupWizardBaseLayout extends LinearLayout { delegateArea.top -= sizeDifference; // Set the TouchDelegate on the parent view - TouchDelegate touchDelegate = new TouchDelegate(delegateArea, - mBackButton); + TouchDelegate touchDelegate = new TouchDelegate(delegateArea, button); - if (View.class.isInstance(mBackButton.getParent())) { - ((View) mBackButton.getParent()).setTouchDelegate(touchDelegate); + if (View.class.isInstance(button.getParent())) { + ((View) button.getParent()).setTouchDelegate(touchDelegate); } }); } else { // Set the TouchDelegate to null if the back button is not visible. - if (View.class.isInstance(mBackButton.getParent())) { - ((View) mBackButton.getParent()).setTouchDelegate(null); + if (View.class.isInstance(button.getParent())) { + ((View) button.getParent()).setTouchDelegate(null); } } } @@ -312,8 +347,41 @@ class CarSetupWizardBaseLayout extends LinearLayout { * Set the back button visibility to the given visibility. */ public void setBackButtonVisible(boolean visible) { + if (visible) { + setViewVisible(mCloseButton, false); + updateNavigationButtonTouchDelegate(mCloseButton, false); + } setViewVisible(mBackButton, visible); - updateBackButtonTouchDelegate(visible); + updateNavigationButtonTouchDelegate(mBackButton, visible); + } + + public View getCloseButton() { + return mCloseButton; + } + + @VisibleForTesting + final void setCloseButton(View closeButton) { + mCloseButton = closeButton; + } + + /** + * Set the close button onClickListener to given listener. Can be null if the listener should + * be overridden so no callback is made. + */ + public void setCloseButtonListener(@Nullable View.OnClickListener listener) { + mCloseButton.setOnClickListener(listener); + } + + /** + * Set the back button visibility to the given visibility. + */ + public void setCloseButtonVisible(boolean visible) { + if (visible) { + setViewVisible(mBackButton, false); + updateNavigationButtonTouchDelegate(mBackButton, false); + } + setViewVisible(mCloseButton, visible); + updateNavigationButtonTouchDelegate(mCloseButton, visible); } /** @@ -519,7 +587,6 @@ class CarSetupWizardBaseLayout extends LinearLayout { return; } int direction = TextUtils.getLayoutDirectionFromLocale(locale); - setLayoutDirection(direction); mToolbarTitle.setTextLocale(locale); mToolbarTitle.setLayoutDirection(direction); @@ -622,6 +689,15 @@ class CarSetupWizardBaseLayout extends LinearLayout { } } + @VisibleForTesting + boolean shouldMirrorNavIcons() { + return getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL + && mPartnerConfigHelper.getBoolean( + getContext(), + PartnerConfig.CONFIG_TOOLBAR_NAV_ICON_MIRRORING_IN_RTL, + true); + } + /** Sets button type face with partner overlay if exists */ private void setButtonTypeFace(TextView button) { String fontFamily = mPartnerConfigHelper.getString( diff --git a/library/main/src/com/android/car/setupwizardlib/CarSetupWizardCompatLayout.java b/library/main/src/com/android/car/setupwizardlib/CarSetupWizardCompatLayout.java index 2d74aa9..f5191cc 100644 --- a/library/main/src/com/android/car/setupwizardlib/CarSetupWizardCompatLayout.java +++ b/library/main/src/com/android/car/setupwizardlib/CarSetupWizardCompatLayout.java @@ -16,10 +16,11 @@ package com.android.car.setupwizardlib; -import android.annotation.Nullable; import android.content.Context; import android.util.AttributeSet; +import androidx.annotation.Nullable; + /** * This layout applies light theming attributes from the partner overlay. It's functionally * equivalent to CarSetupWizardBaseLayout which is package-private. But in the future, it could be diff --git a/library/main/src/com/android/car/setupwizardlib/CarSetupWizardDesignLayout.java b/library/main/src/com/android/car/setupwizardlib/CarSetupWizardDesignLayout.java index 62e52d9..92269f0 100644 --- a/library/main/src/com/android/car/setupwizardlib/CarSetupWizardDesignLayout.java +++ b/library/main/src/com/android/car/setupwizardlib/CarSetupWizardDesignLayout.java @@ -16,10 +16,11 @@ package com.android.car.setupwizardlib; -import android.annotation.Nullable; import android.content.Context; import android.util.AttributeSet; +import androidx.annotation.Nullable; + import com.android.car.setupwizardlib.partner.PartnerConfig; import com.android.car.setupwizardlib.partner.PartnerConfigHelper; diff --git a/library/main/src/com/android/car/setupwizardlib/CarSetupWizardLayout.java b/library/main/src/com/android/car/setupwizardlib/CarSetupWizardLayout.java index 9d30220..19a1923 100644 --- a/library/main/src/com/android/car/setupwizardlib/CarSetupWizardLayout.java +++ b/library/main/src/com/android/car/setupwizardlib/CarSetupWizardLayout.java @@ -15,7 +15,6 @@ */ package com.android.car.setupwizardlib; -import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; import android.graphics.PorterDuff; @@ -40,6 +39,7 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.annotation.VisibleForTesting; diff --git a/library/main/src/com/android/car/setupwizardlib/partner/PartnerConfig.java b/library/main/src/com/android/car/setupwizardlib/partner/PartnerConfig.java index 00c0ea3..54d1e6e 100644 --- a/library/main/src/com/android/car/setupwizardlib/partner/PartnerConfig.java +++ b/library/main/src/com/android/car/setupwizardlib/partner/PartnerConfig.java @@ -28,6 +28,12 @@ public enum PartnerConfig { CONFIG_TOOLBAR_BUTTON_ICON_BACK( PartnerConfigKey.KEY_TOOLBAR_BUTTON_ICON_BACK, ResourceType.DRAWABLE), + CONFIG_TOOLBAR_BUTTON_ICON_CLOSE( + PartnerConfigKey.KEY_TOOLBAR_BUTTON_ICON_CLOSE, ResourceType.DRAWABLE), + + CONFIG_TOOLBAR_NAV_ICON_MIRRORING_IN_RTL( + PartnerConfigKey.KEY_TOOLBAR_NAV_BUTTON_MIRRORING_IN_RTL, ResourceType.BOOLEAN), + CONFIG_TOOLBAR_BUTTON_FONT_FAMILY( PartnerConfigKey.KEY_TOOLBAR_BUTTON_FONT_FAMILY, ResourceType.STRING), diff --git a/library/main/src/com/android/car/setupwizardlib/partner/PartnerConfigKey.java b/library/main/src/com/android/car/setupwizardlib/partner/PartnerConfigKey.java index 32caceb..7f9dadd 100644 --- a/library/main/src/com/android/car/setupwizardlib/partner/PartnerConfigKey.java +++ b/library/main/src/com/android/car/setupwizardlib/partner/PartnerConfigKey.java @@ -26,6 +26,8 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_IMMERSIVE_MODE, PartnerConfigKey.KEY_TOOLBAR_BG_COLOR, PartnerConfigKey.KEY_TOOLBAR_BUTTON_ICON_BACK, + PartnerConfigKey.KEY_TOOLBAR_BUTTON_ICON_CLOSE, + PartnerConfigKey.KEY_TOOLBAR_NAV_BUTTON_MIRRORING_IN_RTL, PartnerConfigKey.KEY_TOOLBAR_BUTTON_FONT_FAMILY, PartnerConfigKey.KEY_TOOLBAR_BUTTON_PADDING_HORIZONTAL, PartnerConfigKey.KEY_TOOLBAR_BUTTON_PADDING_VERTICAL, @@ -54,6 +56,11 @@ public @interface PartnerConfigKey { String KEY_TOOLBAR_BUTTON_ICON_BACK = "suw_compat_toolbar_button_icon_back"; + String KEY_TOOLBAR_BUTTON_ICON_CLOSE = "suw_compat_toolbar_button_icon_close"; + + String KEY_TOOLBAR_NAV_BUTTON_MIRRORING_IN_RTL = + "suw_compat_toolbar_nav_button_mirroring_in_rtl"; + String KEY_TOOLBAR_BUTTON_FONT_FAMILY = "suw_compat_toolbar_button_font_family"; String KEY_TOOLBAR_BUTTON_TEXT_SIZE = "suw_compat_toolbar_button_text_size"; diff --git a/library/main/src/com/android/car/setupwizardlib/summary/PartnerSummaryActionsCollector.java b/library/main/src/com/android/car/setupwizardlib/summary/PartnerSummaryActionsCollector.java index ed556cd..2dfb04b 100644 --- a/library/main/src/com/android/car/setupwizardlib/summary/PartnerSummaryActionsCollector.java +++ b/library/main/src/com/android/car/setupwizardlib/summary/PartnerSummaryActionsCollector.java @@ -17,7 +17,6 @@ package com.android.car.setupwizardlib.summary; -import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -29,6 +28,8 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.Nullable; + import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -85,14 +86,14 @@ public class PartnerSummaryActionsCollector { ResolveInfo resolveInfo = getSummaryContentProviderResolveInfo(context.getPackageManager()); if (resolveInfo == null) { - Log.e(TAG, "Could not find partner content provider, ignoring partner summary items."); + Log.i(TAG, "Could not find partner content provider, ignoring partner summary items."); return; } mContentProviderUri = getSummaryContentProviderUri(resolveInfo); if (mContentProviderUri == null) { - Log.e(TAG, "Could not fetch content provider URI, ignoring partner summary items."); + Log.i(TAG, "Could not fetch content provider URI, ignoring partner summary items."); } } @@ -309,7 +310,7 @@ public class PartnerSummaryActionsCollector { if (deferredAction != null) { deferredActions.add(deferredAction); } - } catch (NullPointerException e) { + } catch (NullPointerException | IllegalArgumentException e) { Log.e( TAG, "Unable to load the completion or config state for deferred action: " diff --git a/library/main/src/com/android/car/setupwizardlib/summary/SummaryAction.java b/library/main/src/com/android/car/setupwizardlib/summary/SummaryAction.java index aac4ce7..6f9e626 100644 --- a/library/main/src/com/android/car/setupwizardlib/summary/SummaryAction.java +++ b/library/main/src/com/android/car/setupwizardlib/summary/SummaryAction.java @@ -16,7 +16,7 @@ package com.android.car.setupwizardlib.summary; -import android.annotation.NonNull; +import androidx.annotation.NonNull; /** An instance that represents a single summary action item and all of its state. */ public class SummaryAction implements Comparable<SummaryAction> { diff --git a/library/main/src/com/android/car/setupwizardlib/util/CarDrivingStateMonitor.java b/library/main/src/com/android/car/setupwizardlib/util/CarDrivingStateMonitor.java index 759dd81..c58e352 100644 --- a/library/main/src/com/android/car/setupwizardlib/util/CarDrivingStateMonitor.java +++ b/library/main/src/com/android/car/setupwizardlib/util/CarDrivingStateMonitor.java @@ -18,8 +18,13 @@ package com.android.car.setupwizardlib.util; import android.car.Car; import android.car.CarNotConnectedException; +import android.car.VehicleAreaType; +import android.car.VehicleGear; +import android.car.VehiclePropertyIds; import android.car.drivingstate.CarUxRestrictions; import android.car.drivingstate.CarUxRestrictionsManager; +import android.car.hardware.CarPropertyValue; +import android.car.hardware.property.CarPropertyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -43,11 +48,15 @@ public class CarDrivingStateMonitor implements public static final String EXIT_BROADCAST_ACTION = "com.android.car.setupwizardlib.driving_exit"; + public static final String INTENT_EXTRA_REASON = "reason"; + public static final String REASON_GEAR_REVERSAL = "gear_reversal"; + private static final String TAG = "CarDrivingStateMonitor"; private static final long DISCONNECT_DELAY_MS = 700; private Car mCar; private CarUxRestrictionsManager mRestrictionsManager; + private CarPropertyManager mCarPropertyManager; // Need to track the number of times the monitor is started so a single stopMonitor call does // not override them all. private int mMonitorStartedCount; @@ -61,6 +70,25 @@ public class CarDrivingStateMonitor implements @VisibleForTesting final Runnable mDisconnectRunnable = this::disconnectCarMonitor; + private final CarPropertyManager.CarPropertyEventCallback mGearChangeCallback = + new CarPropertyManager.CarPropertyEventCallback() { + @SuppressWarnings("rawtypes") + @Override + public void onChangeEvent(CarPropertyValue value) { + switch (value.getPropertyId()) { + case VehiclePropertyIds.GEAR_SELECTION: + if ((Integer) value.getValue() == VehicleGear.GEAR_REVERSE) { + Log.v(TAG, "Gear has reversed, exiting SetupWizard."); + broadcastGearReversal(); + } + break; + } + } + + @Override + public void onErrorEvent(int propertyId, int zone) {} + }; + private CarDrivingStateMonitor(Context context) { mContext = context.getApplicationContext(); } @@ -79,14 +107,14 @@ public class CarDrivingStateMonitor implements * Starts the monitor listening to driving state changes. */ public synchronized void startMonitor() { - if (isVerboseLoggable()) { - Log.v(TAG, "Starting monitor"); - } mMonitorStartedCount++; if (mMonitorStartedCount == 0) { + Log.w(TAG, "MonitorStartedCount was negative"); return; } mHandler.removeCallbacks(mDisconnectRunnable); + Log.i(TAG, String.format( + "Starting monitor, MonitorStartedCount = %d", mMonitorStartedCount)); if (mCar != null) { if (mCar.isConnected()) { try { @@ -108,18 +136,9 @@ public class CarDrivingStateMonitor implements @Override public void onServiceConnected(ComponentName name, IBinder service) { try { - mRestrictionsManager = (CarUxRestrictionsManager) - mCar.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE); - if (mRestrictionsManager == null) { - Log.e(TAG, "Unable to get CarUxRestrictionsManager"); - return; - } - onUxRestrictionsChanged(mRestrictionsManager.getCurrentCarUxRestrictions()); - mRestrictionsManager.registerListener(CarDrivingStateMonitor.this); - if (mStopMonitorAfterUxCheck) { - mStopMonitorAfterUxCheck = false; - stopMonitor(); - } + registerPropertyManager(); + registerRestrictionsManager(); + } catch (CarNotConnectedException e) { Log.e(TAG, "Car not connected", e); } @@ -169,17 +188,22 @@ public class CarDrivingStateMonitor implements } private void disconnectCarMonitor() { - if (isVerboseLoggable()) { - Log.v(TAG, "Timeout finished, disconnecting Car Monitor"); - } if (mMonitorStartedCount > 0) { + if (isVerboseLoggable()) { + Log.v(TAG, "MonitorStartedCount > 0, do nothing"); + } return; } + Log.i(TAG, "Disconnecting Car Monitor"); try { if (mRestrictionsManager != null) { mRestrictionsManager.unregisterListener(); mRestrictionsManager = null; } + if (mCarPropertyManager != null) { + mCarPropertyManager.unregisterCallback(mGearChangeCallback); + mCarPropertyManager = null; + } } catch (CarNotConnectedException e) { Log.e(TAG, "Car not connected for unregistering listener", e); } @@ -224,8 +248,18 @@ public class CarDrivingStateMonitor implements } private boolean checkIsSetupRestricted(@Nullable CarUxRestrictions restrictionInfo) { - return restrictionInfo != null && (restrictionInfo.getActiveRestrictions() - & CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP) != 0; + if (restrictionInfo == null) { + if (isVerboseLoggable()) { + Log.v(TAG, "checkIsSetupRestricted restrictionInfo is null, returning false"); + } + return false; + } + int activeRestrictions = restrictionInfo.getActiveRestrictions(); + if (isVerboseLoggable()) { + Log.v(TAG, "activeRestrictions are " + activeRestrictions); + } + // There must be at least some restriction in place. + return activeRestrictions != 0; } @Override @@ -274,4 +308,49 @@ public class CarDrivingStateMonitor implements public static void replace(Context context, CarDrivingStateMonitor monitor) { CarHelperRegistry.getRegistry(context).putHelper(CarDrivingStateMonitor.class, monitor); } + + private void registerRestrictionsManager() { + mRestrictionsManager = (CarUxRestrictionsManager) + mCar.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE); + if (mRestrictionsManager == null) { + Log.e(TAG, "Unable to get CarUxRestrictionsManager"); + return; + } + onUxRestrictionsChanged(mRestrictionsManager.getCurrentCarUxRestrictions()); + mRestrictionsManager.registerListener(CarDrivingStateMonitor.this); + if (mStopMonitorAfterUxCheck) { + mStopMonitorAfterUxCheck = false; + stopMonitor(); + } + } + + private void registerPropertyManager() { + mCarPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE); + if (mCarPropertyManager == null) { + Log.e(TAG, "Unable to get CarPropertyManager"); + return; + } + mCarPropertyManager.registerCallback( + mGearChangeCallback, VehiclePropertyIds.GEAR_SELECTION, + CarPropertyManager.SENSOR_RATE_ONCHANGE); + CarPropertyValue<Integer> gearSelection = + mCarPropertyManager.getProperty(Integer.class, VehiclePropertyIds.GEAR_SELECTION, + VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL); + if (gearSelection != null + && gearSelection.getStatus() == CarPropertyValue.STATUS_AVAILABLE) { + if (gearSelection.getValue() == VehicleGear.GEAR_REVERSE) { + Log.v(TAG, "SetupWizard started when gear is in reverse, exiting."); + broadcastGearReversal(); + } + } else { + Log.e(TAG, "GEAR_SELECTION is not available."); + } + } + + private void broadcastGearReversal() { + Intent intent = new Intent(); + intent.setAction(EXIT_BROADCAST_ACTION); + intent.putExtra(INTENT_EXTRA_REASON, REASON_GEAR_REVERSAL); + mContext.sendBroadcast(intent); + } } diff --git a/library/main/src/com/android/car/setupwizardlib/util/CarSetupWizardUiUtils.java b/library/main/src/com/android/car/setupwizardlib/util/CarSetupWizardUiUtils.java index 3611931..4e1d8ca 100644 --- a/library/main/src/com/android/car/setupwizardlib/util/CarSetupWizardUiUtils.java +++ b/library/main/src/com/android/car/setupwizardlib/util/CarSetupWizardUiUtils.java @@ -31,9 +31,13 @@ import androidx.core.util.Preconditions; public final class CarSetupWizardUiUtils { private static final String TAG = CarSetupWizardUiUtils.class.getSimpleName(); - /** Key for immersive mode value pased to 1P apps */ + /** Key for immersive mode value passed to 1P apps */ public static final String IMMERSIVE_MODE_TYPE = "immersiveModeType"; + /** Key indicating whether 1P apps should switch to the new landscape design */ + public static final String EXTRA_NEW_LANDSCAPE_LAYOUT_SUPPORTED = + "extra_new_landscape_layout_supported"; + /** Hide system UI */ public static void hideSystemUI(Activity activity) { maybeHideSystemUI(activity); diff --git a/library/main/src/com/android/car/setupwizardlib/util/CarWizardManagerHelper.java b/library/main/src/com/android/car/setupwizardlib/util/CarWizardManagerHelper.java index 7f5cf7a..ff83b55 100644 --- a/library/main/src/com/android/car/setupwizardlib/util/CarWizardManagerHelper.java +++ b/library/main/src/com/android/car/setupwizardlib/util/CarWizardManagerHelper.java @@ -148,4 +148,25 @@ public final class CarWizardManagerHelper { return Settings.Global.getInt(context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) == 1; } + /** + * Checks whether an intent is running in the initial setup wizard flow. + * + * @param intent The intent to be checked, usually from {@link Activity#getIntent()}. + * @return true if the intent passed in was intended to be used with setup wizard. + */ + public static boolean isInitialSetupWizard(Intent intent) { + return intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false); + } + + /** + * Checks whether an intent is running in the deferred setup wizard flow. + * + * @param originalIntent The original intent that was used to start the step, usually via {@link + * Activity#getIntent()}. + * @return true if the intent passed in was running in deferred setup wizard. + */ + public static boolean isDeferredSetupWizard(Intent originalIntent) { + return originalIntent != null && originalIntent.getBooleanExtra(EXTRA_IS_DEFERRED_SETUP, + false); + } } diff --git a/library/main/src/com/android/car/setupwizardlib/util/ResultCodes.java b/library/main/src/com/android/car/setupwizardlib/util/ResultCodes.java index 604e8b0..89b8d11 100644 --- a/library/main/src/com/android/car/setupwizardlib/util/ResultCodes.java +++ b/library/main/src/com/android/car/setupwizardlib/util/ResultCodes.java @@ -23,7 +23,11 @@ import static android.app.Activity.RESULT_FIRST_USER; */ public final class ResultCodes { public static final int RESULT_SKIP = RESULT_FIRST_USER; + public static final int RESULT_RETRY = RESULT_FIRST_USER + 1; public static final int RESULT_ACTIVITY_NOT_FOUND = RESULT_FIRST_USER + 2; + public static final int RESULT_LIFECYCLE_NOT_MATCHED = RESULT_FIRST_USER + 3; + public static final int RESULT_FLOW_NOT_MATCHED = RESULT_FIRST_USER + 4; + public static final int RESULT_FIRST_SETUP_USER = RESULT_FIRST_USER + 100; private ResultCodes() { diff --git a/library/main/tests/robotests/Android.bp b/library/main/tests/robotests/Android.bp index 13335f3..d5cf533 100644 --- a/library/main/tests/robotests/Android.bp +++ b/library/main/tests/robotests/Android.bp @@ -1,6 +1,10 @@ //############################################################## // CarSetupWizardLib app just for Robolectric test target. # //############################################################## +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + android_app { name: "CarSetupWizardLib", diff --git a/library/main/tests/robotests/AndroidManifest.xml b/library/main/tests/robotests/AndroidManifest.xml index a0b00ba..4471c50 100644 --- a/library/main/tests/robotests/AndroidManifest.xml +++ b/library/main/tests/robotests/AndroidManifest.xml @@ -16,6 +16,6 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.car.setupwizardlib.robotests"> + package="com.android.car.setupwizardlib.test"> </manifest> diff --git a/library/main/tests/robotests/res/values/attrs.xml b/library/main/tests/robotests/res/values/attrs.xml index 4454939..0591552 100644 --- a/library/main/tests/robotests/res/values/attrs.xml +++ b/library/main/tests/robotests/res/values/attrs.xml @@ -19,6 +19,8 @@ <declare-styleable name="CarSetupWizardBaseLayout"> <!-- Attributes related to the visibility of the back button --> <attr name="showBackButton" format="boolean"/> + <!-- Attributes related to the visibility of the close button --> + <attr name="showCloseButton" format="boolean"/> <!-- Attributes related to the visibility and text of the toolbar title --> <attr name="showToolbarTitle" format="boolean"/> diff --git a/library/main/tests/robotests/src/com/android/car/setupwizardlib/BaseCompatActivityTest.java b/library/main/tests/robotests/src/com/android/car/setupwizardlib/BaseCompatActivityTest.java index 632ed6d..7209fc9 100644 --- a/library/main/tests/robotests/src/com/android/car/setupwizardlib/BaseCompatActivityTest.java +++ b/library/main/tests/robotests/src/com/android/car/setupwizardlib/BaseCompatActivityTest.java @@ -38,6 +38,7 @@ import androidx.fragment.app.Fragment; import com.android.car.setupwizardlib.robolectric.BaseRobolectricTest; import com.android.car.setupwizardlib.robolectric.TestHelper; import com.android.car.setupwizardlib.shadows.ShadowCar; +import com.android.car.setupwizardlib.test.R; import org.junit.Before; import org.junit.Test; diff --git a/library/main/tests/robotests/src/com/android/car/setupwizardlib/CarSetupWizardCompatLayoutTest.java b/library/main/tests/robotests/src/com/android/car/setupwizardlib/CarSetupWizardCompatLayoutTest.java index 6ec0c28..3ed02c0 100644 --- a/library/main/tests/robotests/src/com/android/car/setupwizardlib/CarSetupWizardCompatLayoutTest.java +++ b/library/main/tests/robotests/src/com/android/car/setupwizardlib/CarSetupWizardCompatLayoutTest.java @@ -37,6 +37,7 @@ import com.android.car.setupwizardlib.partner.PartnerConfig; import com.android.car.setupwizardlib.partner.ResourceEntry; import com.android.car.setupwizardlib.robolectric.BaseRobolectricTest; import com.android.car.setupwizardlib.robolectric.TestHelper; +import com.android.car.setupwizardlib.shadows.ShadowConfiguration; import org.junit.Before; import org.junit.Test; @@ -45,6 +46,7 @@ import org.mockito.Mockito; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.Shadows; +import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowTextView; import org.robolectric.util.ReflectionHelpers; @@ -56,6 +58,7 @@ import java.util.Locale; * Tests for the CarSetupWizardCompatLayout */ @RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowConfiguration.class) public class CarSetupWizardCompatLayoutTest extends BaseRobolectricTest { private static final Locale LOCALE_EN_US = new Locale("en", "US"); // Hebrew locale can be used to test RTL. @@ -101,8 +104,22 @@ public class CarSetupWizardCompatLayoutTest extends BaseRobolectricTest { } /** + * Test that {@link CarSetupWizardCompatLayout#setCloseButtonListener} does set the close button + * listener. + */ + @Test + public void testSetCloseButtonListener() { + View.OnClickListener spyListener = TestHelper.createSpyListener(); + + mCarSetupWizardCompatLayout.setCloseButtonListener(spyListener); + mCarSetupWizardCompatLayout.getCloseButton().performClick(); + Mockito.verify(spyListener).onClick(mCarSetupWizardCompatLayout.getCloseButton()); + } + + /** * Test that {@link CarSetupWizardCompatLayout#setBackButtonVisible} does set the view - * visible/not visible and calls updateBackButtonTouchDelegate. + * visible/not visible and calls + * {@link CarSetupWizardDesignLayout#updateNavigationButtonTouchDelegate(View, boolean)}. */ @Test public void testSetBackButtonVisibleTrue() { @@ -112,12 +129,18 @@ public class CarSetupWizardCompatLayoutTest extends BaseRobolectricTest { spyCarSetupWizardCompatLayout.setBackButtonVisible(true); View backButton = spyCarSetupWizardCompatLayout.getBackButton(); TestHelper.assertViewVisible(backButton); - Mockito.verify(spyCarSetupWizardCompatLayout).updateBackButtonTouchDelegate(true); + Mockito.verify(spyCarSetupWizardCompatLayout) + .updateNavigationButtonTouchDelegate(backButton, true); + View closeButton = spyCarSetupWizardCompatLayout.getCloseButton(); + TestHelper.assertViewNotVisible(closeButton); + Mockito.verify(spyCarSetupWizardCompatLayout) + .updateNavigationButtonTouchDelegate(closeButton, false); } /** * Test that {@link CarSetupWizardCompatLayout#setBackButtonVisible} does set the view - * visible/not visible and calls updateBackButtonTouchDelegate. + * visible/not visible and calls + * {@link CarSetupWizardDesignLayout#updateNavigationButtonTouchDelegate(View, boolean)}. */ @Test public void testSetBackButtonVisibleFalse() { @@ -127,7 +150,47 @@ public class CarSetupWizardCompatLayoutTest extends BaseRobolectricTest { spyCarSetupWizardCompatLayout.setBackButtonVisible(false); View backButton = spyCarSetupWizardCompatLayout.getBackButton(); TestHelper.assertViewNotVisible(backButton); - Mockito.verify(spyCarSetupWizardCompatLayout).updateBackButtonTouchDelegate(false); + Mockito.verify(spyCarSetupWizardCompatLayout) + .updateNavigationButtonTouchDelegate(backButton, false); + } + + /** + * Test that {@link CarSetupWizardCompatLayout#setCloseButtonVisible} does set the view + * visible/not visible and calls + * {@link CarSetupWizardDesignLayout#updateNavigationButtonTouchDelegate(View, boolean)}. + */ + @Test + public void testSetCloseButtonVisibleTrue() { + CarSetupWizardCompatLayout spyCarSetupWizardCompatLayout = + Mockito.spy(mCarSetupWizardCompatLayout); + + spyCarSetupWizardCompatLayout.setCloseButtonVisible(true); + View closeButton = spyCarSetupWizardCompatLayout.getCloseButton(); + TestHelper.assertViewVisible(closeButton); + Mockito.verify(spyCarSetupWizardCompatLayout) + .updateNavigationButtonTouchDelegate(closeButton, true); + View backButton = spyCarSetupWizardCompatLayout.getBackButton(); + TestHelper.assertViewNotVisible(backButton); + Mockito.verify(spyCarSetupWizardCompatLayout) + .updateNavigationButtonTouchDelegate(backButton, false); + } + + + /** + * Test that {@link CarSetupWizardCompatLayout#setCloseButtonVisible} does set the view + * visible/not visible and calls + * {@link CarSetupWizardDesignLayout#updateNavigationButtonTouchDelegate(View, boolean)}. + */ + @Test + public void testSetCloseButtonVisibleFalse() { + CarSetupWizardCompatLayout spyCarSetupWizardCompatLayout = + Mockito.spy(mCarSetupWizardCompatLayout); + + spyCarSetupWizardCompatLayout.setCloseButtonVisible(false); + View closeButton = spyCarSetupWizardCompatLayout.getCloseButton(); + TestHelper.assertViewNotVisible(closeButton); + Mockito.verify(spyCarSetupWizardCompatLayout) + .updateNavigationButtonTouchDelegate(closeButton, false); } /** @@ -532,6 +595,30 @@ public class CarSetupWizardCompatLayoutTest extends BaseRobolectricTest { assertThat(secondaryButton.getTextSize()).isWithin(TOLERANCE).of(EXCEPTED_TEXT_SIZE); } + @Test + public void test_shouldNotMirrorNavIcons_inLtr() { + Activity activity = Robolectric.buildActivity(CarSetupWizardLayoutTestActivity.class) + .create() + .get(); + + CarSetupWizardCompatLayout layout = activity.findViewById(R.id.car_setup_wizard_layout); + assertThat(layout.shouldMirrorNavIcons()).isFalse(); + } + + @Test + public void test_shouldMirrorNavIcons_inRtl() { + application.getResources().getConfiguration().setLocale(LOCALE_IW_IL); + + Activity activity = Robolectric.buildActivity(CarSetupWizardLayoutTestActivity.class) + .create() + .get(); + + CarSetupWizardCompatLayout layout = activity.findViewById(R.id.car_setup_wizard_layout); + View toolbar = layout.findViewById(R.id.application_bar); + assertThat(toolbar.getTextDirection()).isEqualTo(View.TEXT_DIRECTION_LTR); + assertThat(layout.shouldMirrorNavIcons()).isTrue(); + } + private void setupFakeContentProvider() { FakeOverrideContentProvider.installDefaultProvider(); } diff --git a/library/main/tests/robotests/src/com/android/car/setupwizardlib/CarSetupWizardLayoutAlternativeActivity.java b/library/main/tests/robotests/src/com/android/car/setupwizardlib/CarSetupWizardLayoutAlternativeActivity.java index b20da7c..8fbe9e4 100644 --- a/library/main/tests/robotests/src/com/android/car/setupwizardlib/CarSetupWizardLayoutAlternativeActivity.java +++ b/library/main/tests/robotests/src/com/android/car/setupwizardlib/CarSetupWizardLayoutAlternativeActivity.java @@ -19,6 +19,8 @@ package com.android.car.setupwizardlib; import android.app.Activity; import android.os.Bundle; +import com.android.car.setupwizardlib.test.R; + /** * Activity for CarSetupWizardLayoutTest where primary button isn't shown but secondary button is. */ diff --git a/library/main/tests/robotests/src/com/android/car/setupwizardlib/CarSetupWizardLayoutTestActivity.java b/library/main/tests/robotests/src/com/android/car/setupwizardlib/CarSetupWizardLayoutTestActivity.java index 1894933..6350c44 100644 --- a/library/main/tests/robotests/src/com/android/car/setupwizardlib/CarSetupWizardLayoutTestActivity.java +++ b/library/main/tests/robotests/src/com/android/car/setupwizardlib/CarSetupWizardLayoutTestActivity.java @@ -19,6 +19,8 @@ package com.android.car.setupwizardlib; import android.app.Activity; import android.os.Bundle; +import com.android.car.setupwizardlib.test.R; + /** * Activity for CarSetupWizardLayoutTest */ diff --git a/library/main/tests/robotests/src/com/android/car/setupwizardlib/shadows/ShadowConfiguration.java b/library/main/tests/robotests/src/com/android/car/setupwizardlib/shadows/ShadowConfiguration.java new file mode 100644 index 0000000..39057be --- /dev/null +++ b/library/main/tests/robotests/src/com/android/car/setupwizardlib/shadows/ShadowConfiguration.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.car.setupwizardlib.shadows; + +import android.content.res.Configuration; +import android.view.View; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +import java.util.Locale; + +/** + * Shadow class for {@link Configuration}. + */ +@Implements(Configuration.class) +public class ShadowConfiguration { + + private static final Locale HEBREW_LOCALE = new Locale("iw", "IL"); + private int mLayoutDir = View.LAYOUT_DIRECTION_LTR; + + /** + * Set the layout direction from a {@link Locale}. + */ + @Implementation + public void setLayoutDirection(Locale locale) { + if (locale.getLanguage().equals(HEBREW_LOCALE.getLanguage())) { + mLayoutDir = View.LAYOUT_DIRECTION_RTL; + } + } + + /** Returs the layout direction */ + @Implementation + public int getLayoutDirection() { + return mLayoutDir; + } +} diff --git a/library/main/tests/robotests/src/com/android/car/setupwizardlib/util/CarDrivingStateMonitorTest.java b/library/main/tests/robotests/src/com/android/car/setupwizardlib/util/CarDrivingStateMonitorTest.java index 0afc469..afa28f9 100644 --- a/library/main/tests/robotests/src/com/android/car/setupwizardlib/util/CarDrivingStateMonitorTest.java +++ b/library/main/tests/robotests/src/com/android/car/setupwizardlib/util/CarDrivingStateMonitorTest.java @@ -127,12 +127,32 @@ public class CarDrivingStateMonitorTest extends BaseRobolectricTest { } @Test + public void testOnUxRestrictionsChangedForNonSetup_triggersExit() { + mCarDrivingStateMonitor.startMonitor(); + doReturn(CarUxRestrictions.UX_RESTRICTIONS_NO_VIDEO).when(mMockRestrictions) + .getActiveRestrictions(); + mCarDrivingStateMonitor.onUxRestrictionsChanged(mMockRestrictions); + assertThat(mShadowApplication.getBroadcastIntents().get(0).getAction()) + .isEqualTo(CarDrivingStateMonitor.EXIT_BROADCAST_ACTION); + } + + @Test + public void testStartMonitorWhileDrivingForNonSetup_triggersExit() { + doReturn(CarUxRestrictions.UX_RESTRICTIONS_NO_VIDEO).when(mMockRestrictions) + .getActiveRestrictions(); + mCarDrivingStateMonitor.startMonitor(); + assertThat(mShadowApplication.getBroadcastIntents().get(0).getAction()) + .isEqualTo(CarDrivingStateMonitor.EXIT_BROADCAST_ACTION); + } + + @Test public void testStartMonitor_clearsStopMonitorRunnable() { mCarDrivingStateMonitor.startMonitor(); ShadowCar.setIsConnected(true); mCarDrivingStateMonitor.stopMonitor(); mCarDrivingStateMonitor.startMonitor(); - assertThat(mCarDrivingStateMonitor.mHandler.hasMessagesOrCallbacks()).isFalse(); + assertThat(mCarDrivingStateMonitor.mHandler + .hasCallbacks(mCarDrivingStateMonitor.mDisconnectRunnable)).isFalse(); } @Test diff --git a/library/main/tests/robotests/src/com/android/car/setupwizardlib/util/CarSetupWizardUiUtilsTest.java b/library/main/tests/robotests/src/com/android/car/setupwizardlib/util/CarSetupWizardUiUtilsTest.java index 5933bd4..bacb77a 100644 --- a/library/main/tests/robotests/src/com/android/car/setupwizardlib/util/CarSetupWizardUiUtilsTest.java +++ b/library/main/tests/robotests/src/com/android/car/setupwizardlib/util/CarSetupWizardUiUtilsTest.java @@ -23,7 +23,7 @@ import android.graphics.Color; import android.view.View; import android.view.Window; -import com.android.car.setupwizardlib.robotests.R; +import com.android.car.setupwizardlib.test.R; import org.junit.Before; import org.junit.Test; @@ -56,8 +56,8 @@ public class CarSetupWizardUiUtilsTest { // Note that these colors are defined in the test theme private static final int TEST_THEME = R.style.NavAndStatusBarTestTheme; - private static final int EXPECTED_COLOR_STATUS_BAR = Color.getHtmlColor("#001"); - private static final int EXPECTED_COLOR_NAVIGATION_BAR = Color.getHtmlColor("#002"); + private static final int EXPECTED_COLOR_STATUS_BAR = Integer.decode("#001"); + private static final int EXPECTED_COLOR_NAVIGATION_BAR = Integer.decode("#002"); private Activity mActivity; private Window mWindow; diff --git a/library/utils/Android.bp b/library/utils/Android.bp index 7cd7c0b..f4b5bd9 100644 --- a/library/utils/Android.bp +++ b/library/utils/Android.bp @@ -13,6 +13,10 @@ // limitations under the License. // +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + android_library { name: "car-setup-wizard-lib-utils", srcs: ["src/**/*.java", diff --git a/library/utils/src/com/android/car/setupwizardlib/InitialLockSetupClient.java b/library/utils/src/com/android/car/setupwizardlib/InitialLockSetupClient.java index 4ef851e..6c7830f 100644 --- a/library/utils/src/com/android/car/setupwizardlib/InitialLockSetupClient.java +++ b/library/utils/src/com/android/car/setupwizardlib/InitialLockSetupClient.java @@ -16,11 +16,13 @@ package com.android.car.setupwizardlib; +import android.app.KeyguardManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.AsyncTask; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -28,6 +30,7 @@ import android.os.RemoteException; import android.util.Log; import com.android.car.setupwizardlib.InitialLockSetupConstants.LockTypes; +import com.android.car.setupwizardlib.InitialLockSetupConstants.PasswordComplexity; import com.android.car.setupwizardlib.InitialLockSetupConstants.SetLockCodes; import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags; @@ -56,6 +59,7 @@ public class InitialLockSetupClient implements ServiceConnection { private InitialLockListener mInitialLockListener; private Context mContext; + private KeyguardManager mKeyguardManager; private IInitialLockSetupService mInitialLockSetupService; private ValidateLockAsyncTask mCurrentValidateLockTask; private SaveLockAsyncTask mCurrentSaveLockTask; @@ -65,6 +69,7 @@ public class InitialLockSetupClient implements ServiceConnection { public InitialLockSetupClient(Context context) { mContext = context.getApplicationContext(); + mKeyguardManager = mContext.getSystemService(KeyguardManager.class); } /** @@ -120,8 +125,8 @@ public class InitialLockSetupClient implements ServiceConnection { * Fetches the set of {@link LockConfig}s that define the lock constraints for the device. */ public void getLockConfigs() { - LockConfigsAsyncTask lockConfigsAsyncTask = new LockConfigsAsyncTask(mInitialLockListener, - mInitialLockSetupService); + LockConfigsAsyncTask lockConfigsAsyncTask = new LockConfigsAsyncTask( + mInitialLockListener, mInitialLockSetupService, mKeyguardManager); lockConfigsAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null); } @@ -136,8 +141,8 @@ public class InitialLockSetupClient implements ServiceConnection { && mCurrentValidateLockTask.getStatus() != AsyncTask.Status.FINISHED) { mCurrentValidateLockTask.cancel(true); } - mCurrentValidateLockTask = new ValidateLockAsyncTask( - mInitialLockListener, mInitialLockSetupService, LockTypes.PASSWORD); + mCurrentValidateLockTask = new ValidateLockAsyncTask(mInitialLockListener, + mInitialLockSetupService, mKeyguardManager, LockTypes.PASSWORD); mCurrentValidateLockTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, password); } @@ -151,8 +156,8 @@ public class InitialLockSetupClient implements ServiceConnection { && mCurrentValidateLockTask.getStatus() != AsyncTask.Status.FINISHED) { mCurrentValidateLockTask.cancel(true); } - mCurrentValidateLockTask = new ValidateLockAsyncTask( - mInitialLockListener, mInitialLockSetupService, LockTypes.PIN); + mCurrentValidateLockTask = new ValidateLockAsyncTask(mInitialLockListener, + mInitialLockSetupService, mKeyguardManager, LockTypes.PIN); mCurrentValidateLockTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pin); } @@ -166,8 +171,8 @@ public class InitialLockSetupClient implements ServiceConnection { && mCurrentValidateLockTask.getStatus() != AsyncTask.Status.FINISHED) { mCurrentValidateLockTask.cancel(true); } - mCurrentValidateLockTask = new ValidateLockAsyncTask( - mInitialLockListener, mInitialLockSetupService, LockTypes.PATTERN); + mCurrentValidateLockTask = new ValidateLockAsyncTask(mInitialLockListener, + mInitialLockSetupService, mKeyguardManager, LockTypes.PATTERN); mCurrentValidateLockTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pattern); } @@ -184,7 +189,7 @@ public class InitialLockSetupClient implements ServiceConnection { return; } mCurrentSaveLockTask = new SaveLockAsyncTask(mInitialLockListener, - mInitialLockSetupService, LockTypes.PASSWORD); + mInitialLockSetupService, mKeyguardManager, LockTypes.PASSWORD); mCurrentSaveLockTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, password); } @@ -200,7 +205,7 @@ public class InitialLockSetupClient implements ServiceConnection { return; } mCurrentSaveLockTask = new SaveLockAsyncTask(mInitialLockListener, - mInitialLockSetupService, LockTypes.PIN); + mInitialLockSetupService, mKeyguardManager, LockTypes.PIN); mCurrentSaveLockTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pin); } @@ -217,7 +222,7 @@ public class InitialLockSetupClient implements ServiceConnection { return; } mCurrentSaveLockTask = new SaveLockAsyncTask(mInitialLockListener, - mInitialLockSetupService, LockTypes.PATTERN); + mInitialLockSetupService, mKeyguardManager, LockTypes.PATTERN); mCurrentSaveLockTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pattern); } @@ -282,29 +287,56 @@ public class InitialLockSetupClient implements ServiceConnection { private WeakReference<InitialLockListener> mInitialLockListener; private WeakReference<IInitialLockSetupService> mInitialLockSetupService; + private WeakReference<KeyguardManager> mKeyguardManager; LockConfigsAsyncTask(InitialLockListener initialLockListener, - IInitialLockSetupService initialLockSetupService) { + IInitialLockSetupService initialLockSetupService, + KeyguardManager keyguardManager) { mInitialLockListener = new WeakReference<>(initialLockListener); mInitialLockSetupService = new WeakReference<>(initialLockSetupService); + mKeyguardManager = new WeakReference<>(keyguardManager); } @Override protected Map<Integer, LockConfig> doInBackground(Void... voids) { - IInitialLockSetupService initialLockSetupService = mInitialLockSetupService.get(); - if (initialLockSetupService == null) { - InitialLockSetupClient.logVerbose( - "Lost reference to service in LockConfigsAsyncTask"); - return null; - } LockConfig passwordConfig, pinConfig, patternConfig; - try { - passwordConfig = initialLockSetupService.getLockConfig(LockTypes.PASSWORD); - pinConfig = initialLockSetupService.getLockConfig(LockTypes.PIN); - patternConfig = initialLockSetupService.getLockConfig(LockTypes.PATTERN); - } catch (RemoteException e) { - e.printStackTrace(); - return null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + KeyguardManager km = mKeyguardManager.get(); + if (km == null) { + InitialLockSetupClient.logVerbose( + "Lost reference to keyguardManager in LockConfigsAsyncTask"); + return null; + } + passwordConfig = + new LockConfig( + /* enabled= */ true, + km.getMinLockLength( + /* isPin= */ false, PasswordComplexity.PASSWORD_COMPLEXITY_MEDIUM)); + pinConfig = + new LockConfig( + /* enabled= */ true, + km.getMinLockLength( + /* isPin= */ true, PasswordComplexity.PASSWORD_COMPLEXITY_LOW)); + patternConfig = + new LockConfig( + /* enabled= */ true, + km.getMinLockLength( + /* isPin= */ false, PasswordComplexity.PASSWORD_COMPLEXITY_LOW)); + } else { + IInitialLockSetupService initialLockSetupService = mInitialLockSetupService.get(); + if (initialLockSetupService == null) { + InitialLockSetupClient.logVerbose( + "Lost reference to service in LockConfigsAsyncTask"); + return null; + } + try { + passwordConfig = initialLockSetupService.getLockConfig(LockTypes.PASSWORD); + pinConfig = initialLockSetupService.getLockConfig(LockTypes.PIN); + patternConfig = initialLockSetupService.getLockConfig(LockTypes.PATTERN); + } catch (RemoteException e) { + e.printStackTrace(); + return null; + } } Map<Integer, LockConfig> map = new HashMap<>(); map.put(LockTypes.PASSWORD, passwordConfig); @@ -327,32 +359,64 @@ public class InitialLockSetupClient implements ServiceConnection { private WeakReference<InitialLockListener> mInitialLockListener; private WeakReference<IInitialLockSetupService> mInitialLockSetupService; + private WeakReference<KeyguardManager> mKeyguardManager; private int mLockType; ValidateLockAsyncTask( InitialLockListener initialLockListener, IInitialLockSetupService initialLockSetupService, + KeyguardManager keyguardManager, @LockTypes int lockType) { mInitialLockListener = new WeakReference<>(initialLockListener); mInitialLockSetupService = new WeakReference<>(initialLockSetupService); + mKeyguardManager = new WeakReference<>(keyguardManager); mLockType = lockType; } @Override protected Integer doInBackground(byte[]... passwords) { InitialLockSetupClient.logVerbose("ValidateLockAsyncTask doInBackground"); - IInitialLockSetupService initialLockSetupService = mInitialLockSetupService.get(); - if (initialLockSetupService == null) { - InitialLockSetupClient.logVerbose( - "Lost reference to service in ValidateLockAsyncTask"); - return ValidateLockFlags.INVALID_GENERIC; - } - try { - int output = initialLockSetupService.checkValidLock(mLockType, passwords[0]); - return output; - } catch (RemoteException e) { - e.printStackTrace(); - return ValidateLockFlags.INVALID_GENERIC; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + KeyguardManager km = mKeyguardManager.get(); + if (km == null) { + InitialLockSetupClient.logVerbose( + "Lost reference to keyguardManager in LockConfigsAsyncTask"); + return null; + } + int complexity; + switch (mLockType) { + case LockTypes.PASSWORD: + complexity = PasswordComplexity.PASSWORD_COMPLEXITY_MEDIUM; + break; + case LockTypes.PIN: + complexity = PasswordComplexity.PASSWORD_COMPLEXITY_LOW; + break; + case LockTypes.PATTERN: + complexity = PasswordComplexity.PASSWORD_COMPLEXITY_LOW; + passwords[0] = + InitialLockSetupHelper.getNumericEquivalentByteArray(passwords[0]); + break; + default: + Log.e(TAG, "other lock type, returning generic error"); + return ValidateLockFlags.INVALID_GENERIC; + } + return km.isValidLockPasswordComplexity(mLockType, passwords[0], complexity) + ? 0 + : ValidateLockFlags.INVALID_GENERIC; + } else { + IInitialLockSetupService initialLockSetupService = mInitialLockSetupService.get(); + if (initialLockSetupService == null) { + InitialLockSetupClient.logVerbose( + "Lost reference to service in ValidateLockAsyncTask"); + return ValidateLockFlags.INVALID_GENERIC; + } + try { + int output = initialLockSetupService.checkValidLock(mLockType, passwords[0]); + return output; + } catch (RemoteException e) { + e.printStackTrace(); + return ValidateLockFlags.INVALID_GENERIC; + } } } @@ -373,32 +437,64 @@ public class InitialLockSetupClient implements ServiceConnection { private WeakReference<InitialLockListener> mInitialLockListener; private WeakReference<IInitialLockSetupService> mInitialLockSetupService; + private WeakReference<KeyguardManager> mKeyguardManager; private int mLockType; SaveLockAsyncTask( InitialLockListener initialLockListener, IInitialLockSetupService initialLockSetupService, + KeyguardManager keyguardManager, @LockTypes int lockType) { mInitialLockListener = new WeakReference<>(initialLockListener); mInitialLockSetupService = new WeakReference<>(initialLockSetupService); + mKeyguardManager = new WeakReference<>(keyguardManager); mLockType = lockType; } @Override protected Integer doInBackground(byte[]... passwords) { InitialLockSetupClient.logVerbose("SaveLockAsyncTask doInBackground"); - IInitialLockSetupService initialLockSetupService = mInitialLockSetupService.get(); - if (initialLockSetupService == null) { - InitialLockSetupClient.logVerbose( - "Lost reference to service in SaveLockAsyncTask"); - return SetLockCodes.FAIL_LOCK_GENERIC; - } - try { - int output = initialLockSetupService.setLock(mLockType, passwords[0]); - return output; - } catch (RemoteException e) { - e.printStackTrace(); - return SetLockCodes.FAIL_LOCK_GENERIC; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + KeyguardManager km = mKeyguardManager.get(); + if (km == null) { + InitialLockSetupClient.logVerbose( + "Lost reference to keyguardManager in SaveLockAsyncTask"); + return null; + } + int complexity; + switch (mLockType) { + case LockTypes.PASSWORD: + complexity = PasswordComplexity.PASSWORD_COMPLEXITY_MEDIUM; + break; + case LockTypes.PIN: + complexity = PasswordComplexity.PASSWORD_COMPLEXITY_LOW; + break; + case LockTypes.PATTERN: + complexity = PasswordComplexity.PASSWORD_COMPLEXITY_LOW; + passwords[0] = + InitialLockSetupHelper.getNumericEquivalentByteArray(passwords[0]); + break; + default: + Log.e(TAG, "other lock type, returning generic error"); + return SetLockCodes.FAIL_LOCK_GENERIC; + } + return km.setLock(mLockType, passwords[0], complexity) + ? 1 + : SetLockCodes.FAIL_LOCK_GENERIC; + } else { + IInitialLockSetupService initialLockSetupService = mInitialLockSetupService.get(); + if (initialLockSetupService == null) { + InitialLockSetupClient.logVerbose( + "Lost reference to service in SaveLockAsyncTask"); + return SetLockCodes.FAIL_LOCK_GENERIC; + } + try { + int output = initialLockSetupService.setLock(mLockType, passwords[0]); + return output; + } catch (RemoteException e) { + e.printStackTrace(); + return SetLockCodes.FAIL_LOCK_GENERIC; + } } } diff --git a/library/utils/src/com/android/car/setupwizardlib/InitialLockSetupConstants.java b/library/utils/src/com/android/car/setupwizardlib/InitialLockSetupConstants.java index 7081e78..8ca5c6e 100644 --- a/library/utils/src/com/android/car/setupwizardlib/InitialLockSetupConstants.java +++ b/library/utils/src/com/android/car/setupwizardlib/InitialLockSetupConstants.java @@ -36,12 +36,14 @@ public interface InitialLockSetupConstants { @IntDef({ LockTypes.PASSWORD, LockTypes.PIN, - LockTypes.PATTERN + LockTypes.PATTERN, + LockTypes.NONE }) @interface LockTypes { int PASSWORD = 0; int PIN = 1; int PATTERN = 2; + int NONE = 3; } /** @@ -75,5 +77,19 @@ public interface InitialLockSetupConstants { int FAIL_LOCK_INVALID = -2; int FAIL_LOCK_GENERIC = -3; } + + /** PasswordComplexity as defined in DevicePolicyManager. */ + @IntDef({ + PasswordComplexity.PASSWORD_COMPLEXITY_NONE, + PasswordComplexity.PASSWORD_COMPLEXITY_LOW, + PasswordComplexity.PASSWORD_COMPLEXITY_MEDIUM, + PasswordComplexity.PASSWORD_COMPLEXITY_HIGH, + }) + @interface PasswordComplexity { + int PASSWORD_COMPLEXITY_NONE = 0; + int PASSWORD_COMPLEXITY_LOW = 0x10000; + int PASSWORD_COMPLEXITY_MEDIUM = 0x30000; + int PASSWORD_COMPLEXITY_HIGH = 0x50000; + } } diff --git a/library/utils/src/com/android/car/setupwizardlib/InitialLockSetupHelper.java b/library/utils/src/com/android/car/setupwizardlib/InitialLockSetupHelper.java index 9820680..191ddbc 100644 --- a/library/utils/src/com/android/car/setupwizardlib/InitialLockSetupHelper.java +++ b/library/utils/src/com/android/car/setupwizardlib/InitialLockSetupHelper.java @@ -81,4 +81,13 @@ public class InitialLockSetupHelper { } return charSequence; } + + /** Return an ASCII-equivalent array of character digits for a numeric byte input. */ + public static byte[] getNumericEquivalentByteArray(byte[] input) { + byte[] output = new byte[input.length]; + for (int i = 0; i < input.length; i++) { + output[i] = (byte) (input[i] + 48); + } + return output; + } } |