From 28114281657c41fdbd62b1b40817648e1cb835cd Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Wed, 23 Jun 2021 22:16:25 +0800 Subject: [PATCH 01/11] Replace the AnimatedImagePreference with IllustrationPreference. Bug: 190585192 Test: manual test Change-Id: I4a6d1f5bbfd50f5586bfa39a8586442bd7b2a78d --- res/layout/preference_animated_image.xml | 45 ----- .../AnimatedImagePreference.java | 181 ------------------ .../ToggleFeaturePreferenceFragment.java | 15 +- .../AnimatedImagePreferenceTest.java | 155 --------------- 4 files changed, 7 insertions(+), 389 deletions(-) delete mode 100644 res/layout/preference_animated_image.xml delete mode 100644 src/com/android/settings/accessibility/AnimatedImagePreference.java delete mode 100644 tests/robotests/src/com/android/settings/accessibility/AnimatedImagePreferenceTest.java diff --git a/res/layout/preference_animated_image.xml b/res/layout/preference_animated_image.xml deleted file mode 100644 index 64cfc982ef7..00000000000 --- a/res/layout/preference_animated_image.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/com/android/settings/accessibility/AnimatedImagePreference.java b/src/com/android/settings/accessibility/AnimatedImagePreference.java deleted file mode 100644 index c707e5cc0a7..00000000000 --- a/src/com/android/settings/accessibility/AnimatedImagePreference.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2019 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.settings.accessibility; - -import android.content.Context; -import android.graphics.drawable.Animatable; -import android.graphics.drawable.Animatable2; -import android.graphics.drawable.AnimationDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; - -import com.android.settings.R; - -import com.airbnb.lottie.LottieAnimationView; -import com.airbnb.lottie.LottieDrawable; - -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.util.Objects; - -/** - * A custom {@link ImageView} preference for showing animated or static image, such as animated webp and static png. - */ -public class AnimatedImagePreference extends Preference { - - private static final String TAG = "AnimatedImagePreference"; - private Uri mImageUri; - private int mMaxHeight = -1; - - private final Animatable2.AnimationCallback mAnimationCallback = - new Animatable2.AnimationCallback() { - @Override - public void onAnimationEnd(Drawable drawable) { - ((Animatable2) drawable).start(); - } - }; - - AnimatedImagePreference(Context context) { - super(context); - setLayoutResource(R.layout.preference_animated_image); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder holder) { - super.onBindViewHolder(holder); - - final ImageView imageView = holder.itemView.findViewById(R.id.animated_img); - final LottieAnimationView lottieView = holder.itemView.findViewById(R.id.lottie_view); - if (imageView == null || lottieView == null) { - return; - } - - if (mImageUri != null) { - resetAnimations(imageView, lottieView); - hideAllChildViews(holder.itemView); - - imageView.setImageURI(mImageUri); - if (imageView.getDrawable() != null) { - startAnimationWith(imageView); - } else { - // The lottie image from the raw folder also returns null. - startLottieAnimationWith(lottieView); - } - } - - if (mMaxHeight > -1) { - imageView.setMaxHeight(mMaxHeight); - lottieView.setMaxHeight(mMaxHeight); - } - } - - /** - * Sets image uri to display image in {@link ImageView} - * - * @param imageUri the Uri of an image - */ - public void setImageUri(Uri imageUri) { - if (imageUri != null && !imageUri.equals(mImageUri)) { - mImageUri = imageUri; - notifyChanged(); - } - } - - /** - * Sets the maximum height of the view. - * - * @param maxHeight the maximum height of ImageView in terms of pixels. - */ - public void setMaxHeight(int maxHeight) { - if (maxHeight != mMaxHeight) { - mMaxHeight = maxHeight; - notifyChanged(); - } - } - - private void startAnimationWith(ImageView imageView) { - startAnimation(imageView.getDrawable()); - - imageView.setVisibility(View.VISIBLE); - } - - private void startLottieAnimationWith(LottieAnimationView lottieView) { - final InputStream inputStream = getInputStreamFromUri(mImageUri); - Objects.requireNonNull(inputStream, "Invalid resource."); - lottieView.setAnimation(inputStream, /* cacheKey= */ null); - lottieView.setRepeatCount(LottieDrawable.INFINITE); - lottieView.playAnimation(); - - lottieView.setVisibility(View.VISIBLE); - } - - private void startAnimation(Drawable drawable) { - if (!(drawable instanceof Animatable)) { - return; - } - - if (drawable instanceof Animatable2) { - ((Animatable2) drawable).registerAnimationCallback(mAnimationCallback); - } else if (drawable instanceof AnimationDrawable) { - ((AnimationDrawable) drawable).setOneShot(false); - } - - ((Animatable) drawable).start(); - } - - private void resetAnimations(ImageView imageView, LottieAnimationView lottieView) { - resetAnimation(imageView.getDrawable()); - - lottieView.cancelAnimation(); - } - - private void resetAnimation(Drawable drawable) { - if (!(drawable instanceof Animatable)) { - return; - } - - if (drawable instanceof Animatable2) { - ((Animatable2) drawable).clearAnimationCallbacks(); - } - - ((Animatable) drawable).stop(); - } - - private InputStream getInputStreamFromUri(Uri uri) { - try { - return getContext().getContentResolver().openInputStream(uri); - } catch (FileNotFoundException e) { - Log.w(TAG, "Cannot find content uri: " + uri, e); - return null; - } - } - - private void hideAllChildViews(View itemView) { - final ViewGroup viewGroup = (ViewGroup) itemView; - for (int i = 0; i < viewGroup.getChildCount(); i++) { - viewGroup.getChildAt(i).setVisibility(View.GONE); - } - } -} diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java index 640ae532161..510f8d3484a 100644 --- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java @@ -59,6 +59,7 @@ import com.android.settings.widget.SettingsMainSwitchBar; import com.android.settings.widget.SettingsMainSwitchPreference; import com.android.settingslib.HelpUtils; import com.android.settingslib.accessibility.AccessibilityUtils; +import com.android.settingslib.widget.IllustrationPreference; import com.android.settingslib.widget.OnMainSwitchChangeListener; import com.google.android.setupcompat.util.WizardManagerHelper; @@ -398,15 +399,13 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference return; } - final int screenHalfHeight = AccessibilityUtil.getScreenHeightPixels(getPrefContext()) / 2; - final AnimatedImagePreference animatedImagePreference = - new AnimatedImagePreference(getPrefContext()); - animatedImagePreference.setImageUri(mImageUri); - animatedImagePreference.setSelectable(false); - animatedImagePreference.setMaxHeight(screenHalfHeight); - animatedImagePreference.setKey(KEY_ANIMATED_IMAGE); + final IllustrationPreference illustrationPreference = + new IllustrationPreference(getPrefContext()); + illustrationPreference.setImageUri(mImageUri); + illustrationPreference.setSelectable(false); + illustrationPreference.setKey(KEY_ANIMATED_IMAGE); - getPreferenceScreen().addPreference(animatedImagePreference); + getPreferenceScreen().addPreference(illustrationPreference); } private void initToggleServiceSwitchPreference() { diff --git a/tests/robotests/src/com/android/settings/accessibility/AnimatedImagePreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/AnimatedImagePreferenceTest.java deleted file mode 100644 index c7e5b13be83..00000000000 --- a/tests/robotests/src/com/android/settings/accessibility/AnimatedImagePreferenceTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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.settings.accessibility; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -import android.content.ContentResolver; -import android.content.Context; -import android.graphics.drawable.AnimatedImageDrawable; -import android.graphics.drawable.AnimatedVectorDrawable; -import android.graphics.drawable.AnimationDrawable; -import android.net.Uri; -import android.view.ViewGroup; -import android.widget.ImageView; - -import androidx.preference.PreferenceViewHolder; - -import com.android.settings.R; - -import com.airbnb.lottie.LottieAnimationView; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -import java.io.InputStream; - -/** Tests for {@link AnimatedImagePreference}. */ -@RunWith(RobolectricTestRunner.class) -public class AnimatedImagePreferenceTest { - private final Context mContext = RuntimeEnvironment.application; - private Uri mImageUri; - private PreferenceViewHolder mViewHolder; - private AnimatedImagePreference mAnimatedImagePreference; - - @Mock - private ViewGroup mRootView; - - @Spy - private ImageView mImageView; - - @Before - public void init() { - MockitoAnnotations.initMocks(this); - - mViewHolder = spy(PreferenceViewHolder.createInstanceForTests(mRootView)); - doReturn(new LottieAnimationView(mContext)).when(mRootView).findViewById(R.id.lottie_view); - mImageView = spy(new ImageView(mContext)); - - mAnimatedImagePreference = new AnimatedImagePreference(mContext); - mImageUri = new Uri.Builder().build(); - } - - @Test - public void playAnimation_animatedImageDrawable_success() { - final AnimatedImageDrawable drawable = mock(AnimatedImageDrawable.class); - doReturn(mImageView).when(mRootView).findViewById(R.id.animated_img); - doReturn(drawable).when(mImageView).getDrawable(); - - mAnimatedImagePreference.setImageUri(mImageUri); - mAnimatedImagePreference.onBindViewHolder(mViewHolder); - - verify(drawable).start(); - } - - @Test - public void playAnimation_animatedVectorDrawable_success() { - final AnimatedVectorDrawable drawable = mock(AnimatedVectorDrawable.class); - doReturn(mImageView).when(mRootView).findViewById(R.id.animated_img); - doReturn(drawable).when(mImageView).getDrawable(); - - mAnimatedImagePreference.setImageUri(mImageUri); - mAnimatedImagePreference.onBindViewHolder(mViewHolder); - - verify(drawable).start(); - } - - @Test - public void playAnimation_animationDrawable_success() { - final AnimationDrawable drawable = mock(AnimationDrawable.class); - doReturn(mImageView).when(mRootView).findViewById(R.id.animated_img); - doReturn(drawable).when(mImageView).getDrawable(); - - mAnimatedImagePreference.setImageUri(mImageUri); - mAnimatedImagePreference.onBindViewHolder(mViewHolder); - - verify(drawable).start(); - } - - @Test - public void setImageUri_viewNotExist_setFail() { - doReturn(null).when(mRootView).findViewById(R.id.animated_img); - - mAnimatedImagePreference.setImageUri(mImageUri); - mAnimatedImagePreference.onBindViewHolder(mViewHolder); - - verify(mImageView, never()).setImageURI(mImageUri); - } - - @Test - public void setMaxHeight_success() { - final int maxHeight = 100; - doReturn(mImageView).when(mRootView).findViewById(R.id.animated_img); - - mAnimatedImagePreference.setMaxHeight(maxHeight); - mAnimatedImagePreference.onBindViewHolder(mViewHolder); - - assertThat(mImageView.getMaxHeight()).isEqualTo(maxHeight); - } - - @Test - public void setImageUriAndRebindViewHolder_lottieImageFromRawFolder_setAnimation() { - final int fakeLottieResId = 111111; - final Uri lottieImageUri = - new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(mContext.getPackageName()) - .appendPath(String.valueOf(fakeLottieResId)) - .build(); - final LottieAnimationView lottieView = spy(new LottieAnimationView(mContext)); - doReturn(mImageView).when(mRootView).findViewById(R.id.animated_img); - doReturn(lottieView).when(mRootView).findViewById(R.id.lottie_view); - - mAnimatedImagePreference.setImageUri(lottieImageUri); - mAnimatedImagePreference.onBindViewHolder(mViewHolder); - - verify(lottieView).setAnimation(any(InputStream.class), eq(null)); - } -} From e8de94a21dde56dfcc60d2caf6706d8e5b666849 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Mon, 28 Jun 2021 11:33:10 +0800 Subject: [PATCH 02/11] Fix 'No Apps' UI issues of ManageApplications Fixes below UI issues - "No Apps" may not show in fragments of profile tab. Fix it by using ConstraintLayout to specify alignments of each view and removing extra padding. -- "No Apps" may flicker by moving position. The flicker is from unnecessary visibility changes. This change integrates empty view visibility control in LoadingViewController to simplify code and avoid unnecessary visibility changes. Bug: 189390795 Bug: 183398721 Test: atest com.android.settings.deviceinfo make RunSettingsRoboTests -j ROBOTEST_FILTER=com.android.settings.deviceinfo Manual visual, observe UI Settings -> Storage -> Games Settings -> Notifications -> App Settings Settings > Apps > Special app access > Media management apps Change-Id: I634209c6f8466e2adae703226902190bbdf470b9 --- res/layout/manage_applications_apps.xml | 62 ++++++++--------- res/values/dimens.xml | 2 - .../applications/RunningServices.java | 6 +- .../ManageApplications.java | 52 +++++--------- .../widget/LoadingViewController.java | 68 ++++++++++++++++--- .../ManageApplicationsTest.java | 28 +------- 6 files changed, 115 insertions(+), 103 deletions(-) diff --git a/res/layout/manage_applications_apps.xml b/res/layout/manage_applications_apps.xml index d814164647a..7ea88038734 100644 --- a/res/layout/manage_applications_apps.xml +++ b/res/layout/manage_applications_apps.xml @@ -14,7 +14,7 @@ limitations under the License. --> - + android:elevation="2dp" + settings:layout_constraintTop_toTopOf="parent"/> - + android:layout_height="wrap_content" + android:clipToPadding="false" + android:scrollbars="none" + android:visibility="invisible" + settings:fastScrollEnabled="true" + settings:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable" + settings:fastScrollHorizontalTrackDrawable="@drawable/line_drawable" + settings:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable" + settings:fastScrollVerticalTrackDrawable="@drawable/line_drawable" + settings:layout_constraintTop_toBottomOf="@id/pinned_header"/> - + - - - - - - - + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 908b6401aed..33fe1f572ac 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -149,8 +149,6 @@ 182dp 32dp 24dp - - 80dp 88dip diff --git a/src/com/android/settings/applications/RunningServices.java b/src/com/android/settings/applications/RunningServices.java index 4d13241126f..b1689d5c591 100644 --- a/src/com/android/settings/applications/RunningServices.java +++ b/src/com/android/settings/applications/RunningServices.java @@ -72,7 +72,11 @@ public class RunningServices extends SettingsPreferenceFragment { public void onResume() { super.onResume(); boolean haveData = mRunningProcessesView.doResume(this, mRunningProcessesAvail); - mLoadingViewController.handleLoadingContainer(haveData /* done */, false /* animate */); + if (haveData) { + mLoadingViewController.showContent(false /* animate */); + } else { + mLoadingViewController.showLoadingView(); + } } @Override diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index 6d675240b64..43e929b8054 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -208,7 +208,6 @@ public class ManageApplications extends InstrumentedFragment private ApplicationsAdapter mApplications; private View mLoadingContainer; - private View mListContainer; private SearchView mSearchView; // Size resource used for packages whose size computation failed for some reason @@ -402,25 +401,21 @@ public class ManageApplications extends InstrumentedFragment mRootView = inflater.inflate(R.layout.manage_applications_apps, null); mLoadingContainer = mRootView.findViewById(R.id.loading_container); - mListContainer = mRootView.findViewById(R.id.list_container); - if (mListContainer != null) { - // Create adapter and list view here - mEmptyView = mListContainer.findViewById(android.R.id.empty); + mEmptyView = mRootView.findViewById(android.R.id.empty); + mRecyclerView = mRootView.findViewById(R.id.apps_list); - mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter, - savedInstanceState); - if (savedInstanceState != null) { - mApplications.mHasReceivedLoadEntries = - savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false); - mApplications.mHasReceivedBridgeCallback = - savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false); - } - mRecyclerView = mListContainer.findViewById(R.id.apps_list); - mRecyclerView.setItemAnimator(null); - mRecyclerView.setLayoutManager(new LinearLayoutManager( - getContext(), RecyclerView.VERTICAL, false /* reverseLayout */)); - mRecyclerView.setAdapter(mApplications); + mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter, + savedInstanceState); + if (savedInstanceState != null) { + mApplications.mHasReceivedLoadEntries = + savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false); + mApplications.mHasReceivedBridgeCallback = + savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false); } + mRecyclerView.setItemAnimator(null); + mRecyclerView.setLayoutManager(new LinearLayoutManager( + getContext(), RecyclerView.VERTICAL, false /* reverseLayout */)); + mRecyclerView.setAdapter(mApplications); // We have to do this now because PreferenceFrameLayout looks at it // only when the view is added. @@ -985,16 +980,8 @@ public class ManageApplications extends InstrumentedFragment // overlapped by floating filter. if (hasFilter) { mManageApplications.mSpinnerHeader.setVisibility(View.VISIBLE); - mManageApplications.mRecyclerView.setPadding(0 /* left */, - mContext.getResources().getDimensionPixelSize( - R.dimen.app_bar_height) /* top */, - 0 /* right */, - 0 /* bottom */); } else { mManageApplications.mSpinnerHeader.setVisibility(View.GONE); - mManageApplications.mRecyclerView.setPadding(0 /* left */, 0 /* top */, - 0 /* right */, - 0 /* bottom */); } } } @@ -1044,7 +1031,8 @@ public class ManageApplications extends InstrumentedFragment mManageApplications = manageApplications; mLoadingViewController = new LoadingViewController( mManageApplications.mLoadingContainer, - mManageApplications.mListContainer + mManageApplications.mRecyclerView, + mManageApplications.mEmptyView ); mContext = manageApplications.getActivity(); mIconDrawableFactory = IconDrawableFactory.newInstance(mContext); @@ -1303,11 +1291,9 @@ public class ManageApplications extends InstrumentedFragment mOriginalEntries = entries; notifyDataSetChanged(); if (getItemCount() == 0) { - mManageApplications.mRecyclerView.setVisibility(View.GONE); - mManageApplications.mEmptyView.setVisibility(View.VISIBLE); + mLoadingViewController.showEmpty(false /* animate */); } else { - mManageApplications.mEmptyView.setVisibility(View.GONE); - mManageApplications.mRecyclerView.setVisibility(View.VISIBLE); + mLoadingViewController.showContent(false /* animate */); if (mManageApplications.mSearchView != null && mManageApplications.mSearchView.isVisibleToUser()) { @@ -1324,10 +1310,6 @@ public class ManageApplications extends InstrumentedFragment mLastIndex = -1; } - if (mSession.getAllApps().size() != 0 - && mManageApplications.mListContainer.getVisibility() != View.VISIBLE) { - mLoadingViewController.showContent(true /* animate */); - } if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) { // No enabled or disabled filters for usage access. return; diff --git a/src/com/android/settings/widget/LoadingViewController.java b/src/com/android/settings/widget/LoadingViewController.java index 294e55e7ea8..66eebf387ba 100644 --- a/src/com/android/settings/widget/LoadingViewController.java +++ b/src/com/android/settings/widget/LoadingViewController.java @@ -22,34 +22,66 @@ import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import androidx.annotation.Nullable; + /** - * A helper class that manages show/hide loading spinner. + * A helper class that manages show/hide loading spinner, content view and empty view (optional). */ public class LoadingViewController { private static final long DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS = 100L; - public final Handler mFgHandler; - public final View mLoadingView; - public final View mContentView; + private final Handler mFgHandler; + private final View mLoadingView; + private final View mContentView; + private final View mEmptyView; public LoadingViewController(View loadingView, View contentView) { + this(loadingView, contentView, null /* emptyView*/); + } + + public LoadingViewController(View loadingView, View contentView, @Nullable View emptyView) { mLoadingView = loadingView; mContentView = contentView; + mEmptyView = emptyView; mFgHandler = new Handler(Looper.getMainLooper()); } private Runnable mShowLoadingContainerRunnable = new Runnable() { public void run() { - handleLoadingContainer(false /* done */, false /* animate */); + showLoadingView(); } }; + /** + * Shows content view and hides loading view & empty view. + */ public void showContent(boolean animate) { // Cancel any pending task to show the loading animation and show the list of // apps directly. mFgHandler.removeCallbacks(mShowLoadingContainerRunnable); - handleLoadingContainer(true /* show */, animate); + handleLoadingContainer(true /* showContent */, false /* showEmpty*/, animate); + } + + /** + * Shows empty view and hides loading view & content view. + */ + public void showEmpty(boolean animate) { + if (mEmptyView == null) { + return; + } + + // Cancel any pending task to show the loading animation and show the list of + // apps directly. + mFgHandler.removeCallbacks(mShowLoadingContainerRunnable); + handleLoadingContainer(false /* showContent */, true /* showEmpty */, animate); + } + + /** + * Shows loading view and hides content view & empty view. + */ + public void showLoadingView() { + handleLoadingContainer(false /* showContent */, false /* showEmpty */, false /* animate */); } public void showLoadingViewDelayed() { @@ -57,8 +89,9 @@ public class LoadingViewController { mShowLoadingContainerRunnable, DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS); } - public void handleLoadingContainer(boolean done, boolean animate) { - handleLoadingContainer(mLoadingView, mContentView, done, animate); + private void handleLoadingContainer(boolean showContent, boolean showEmpty, boolean animate) { + handleLoadingContainer(mLoadingView, mContentView, mEmptyView, + showContent, showEmpty, animate); } /** @@ -75,6 +108,25 @@ public class LoadingViewController { setViewShown(content, done, animate); } + /** + * Show/hide loading view and content view and empty view. + * + * @param loading The loading spinner view + * @param content The content view + * @param empty The empty view shows no item summary to users. + * @param showContent If true, content is set visible and loading is set invisible. + * @param showEmpty If true, empty is set visible and loading is set invisible. + * @param animate Whether or not content/loading views should animate in/out. + */ + public static void handleLoadingContainer(View loading, View content, View empty, + boolean showContent, boolean showEmpty, boolean animate) { + if (empty != null) { + setViewShown(empty, showEmpty, animate); + } + setViewShown(content, showContent, animate); + setViewShown(loading, !showContent && !showEmpty, animate); + } + private static void setViewShown(final View view, boolean shown, boolean animate) { if (animate) { Animation animation = AnimationUtils.loadAnimation(view.getContext(), diff --git a/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java b/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java index 86f5fe83c6c..25eca7adb22 100644 --- a/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java +++ b/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java @@ -28,7 +28,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -49,7 +48,6 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; import android.widget.SearchView; import androidx.fragment.app.FragmentActivity; @@ -155,22 +153,6 @@ public class ManageApplicationsTest { assertThat(mMenu.findItem(R.id.sort_order_frequent_notification).isVisible()).isFalse(); } - @Test - public void onCreateView_shouldNotShowLoadingContainer() { - ReflectionHelpers.setField(mFragment, "mResetAppsHelper", mock(ResetAppsHelper.class)); - doNothing().when(mFragment).createHeader(); - - final LayoutInflater layoutInflater = mock(LayoutInflater.class); - final View view = mock(View.class); - final View loadingContainer = mock(View.class); - when(layoutInflater.inflate(anyInt(), eq(null))).thenReturn(view); - when(view.findViewById(R.id.loading_container)).thenReturn(loadingContainer); - - mFragment.onCreateView(layoutInflater, mock(ViewGroup.class), null); - - verify(loadingContainer, never()).setVisibility(View.VISIBLE); - } - @Test public void onCreateOptionsMenu_shouldSetSearchQueryListener() { final SearchView searchView = mock(SearchView.class); @@ -221,7 +203,6 @@ public class ManageApplicationsTest { @Test public void updateLoading_appLoaded_shouldNotDelayCallToHandleLoadingContainer() { ReflectionHelpers.setField(mFragment, "mLoadingContainer", mock(View.class)); - ReflectionHelpers.setField(mFragment, "mListContainer", mock(View.class)); final ManageApplications.ApplicationsAdapter adapter = spy(new ManageApplications.ApplicationsAdapter(mState, mFragment, AppFilterRegistry.getInstance().get(FILTER_APPS_ALL), new Bundle())); @@ -243,7 +224,6 @@ public class ManageApplicationsTest { @Test public void updateLoading_appNotLoaded_shouldDelayCallToHandleLoadingContainer() { ReflectionHelpers.setField(mFragment, "mLoadingContainer", mock(View.class)); - ReflectionHelpers.setField(mFragment, "mListContainer", mock(View.class)); final ManageApplications.ApplicationsAdapter adapter = spy(new ManageApplications.ApplicationsAdapter(mState, mFragment, AppFilterRegistry.getInstance().get(FILTER_APPS_ALL), new Bundle())); @@ -272,7 +252,6 @@ public class ManageApplicationsTest { when(listContainer.getVisibility()).thenReturn(View.INVISIBLE); when(listContainer.getContext()).thenReturn(context); ReflectionHelpers.setField(mFragment, "mLoadingContainer", loadingContainer); - ReflectionHelpers.setField(mFragment, "mListContainer", listContainer); final ManageApplications.ApplicationsAdapter adapter = spy(new ManageApplications.ApplicationsAdapter(mState, mFragment, AppFilterRegistry.getInstance().get(FILTER_APPS_ALL), new Bundle())); @@ -296,7 +275,7 @@ public class ManageApplicationsTest { adapter.onRebuildComplete(null); - verify(loadingViewController).showContent(true /* animate */); + verify(loadingViewController).showEmpty(false /* animate */); } @Test @@ -304,15 +283,16 @@ public class ManageApplicationsTest { final String query = "Test"; final RecyclerView recyclerView = mock(RecyclerView.class); final View emptyView = mock(View.class); + final View loadingContainer = mock(View.class); ReflectionHelpers.setField(mFragment, "mRecyclerView", recyclerView); ReflectionHelpers.setField(mFragment, "mEmptyView", emptyView); + ReflectionHelpers.setField(mFragment, "mLoadingContainer", loadingContainer); final SearchView searchView = mock(SearchView.class); ReflectionHelpers.setField(mFragment, "mSearchView", searchView); when(searchView.isVisibleToUser()).thenReturn(true); when(searchView.getQuery()).thenReturn(query); final View listContainer = mock(View.class); when(listContainer.getVisibility()).thenReturn(View.VISIBLE); - ReflectionHelpers.setField(mFragment, "mListContainer", listContainer); ReflectionHelpers.setField( mFragment, "mFilterAdapter", mock(ManageApplications.FilterSpinnerAdapter.class)); final ArrayList appList = new ArrayList<>(); @@ -491,8 +471,6 @@ public class ManageApplicationsTest { mFragment.mFilterAdapter.updateFilterView(true); assertThat(mFragment.mSpinnerHeader.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mFragment.mRecyclerView.getPaddingTop()).isEqualTo( - mContext.getResources().getDimensionPixelSize(R.dimen.app_bar_height)); } @Test From 5ee0809cbb37176e1e9404ae90b5408e99fb954c Mon Sep 17 00:00:00 2001 From: Quang Luong Date: Mon, 28 Jun 2021 12:40:44 -0700 Subject: [PATCH 03/11] Reword "Internet won't autoconnect" to "Mobile data won't autoconnect" Bug: 191548741 Test: build Change-Id: Idce71feeb627451ef99c180d29c0cd3420c4be31 --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index c19c8dc6711..3dbfa845566 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -13353,7 +13353,7 @@ No connection - Internet won\u0027t auto\u2011connect + Mobile data won\u0027t auto\u2011connect No other networks available From 218187bcce4e65162a459911c63b2db59c617a7c Mon Sep 17 00:00:00 2001 From: Curtis Belmonte Date: Tue, 29 Jun 2021 13:57:53 -0700 Subject: [PATCH 04/11] Fix buttons and icons for fingerprint enroll intro Updates the labels of buttons and the colors of icons shown on the fingerprint enroll intro screen to be consistent with the face enroll intro screen and match the latest mocks. Note that this commit removes some unused strings but does not add any new ones requiring l10n. Test: Manually tested face enroll from Setup Wizard and Settings Fixes: 192381823 Change-Id: Id991d4f29d6de82acd538f1853bc8a5e50cd2637 --- .../fingerprint_enroll_introduction.xml | 4 ++-- res/values/strings.xml | 8 ------- .../FingerprintEnrollIntroduction.java | 6 +++++- .../SetupFingerprintEnrollIntroduction.java | 21 ------------------- 4 files changed, 7 insertions(+), 32 deletions(-) diff --git a/res/layout/fingerprint_enroll_introduction.xml b/res/layout/fingerprint_enroll_introduction.xml index 65c1497d9ff..5f828799269 100644 --- a/res/layout/fingerprint_enroll_introduction.xml +++ b/res/layout/fingerprint_enroll_introduction.xml @@ -98,7 +98,7 @@ android:paddingTop="24dp"> Cancel No thanks - - No thanks - - Continue I agree - - Skip - - Next Skip fingerprint? diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java index 8c3b1ceb89a..a75fb0f4526 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java @@ -64,9 +64,13 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { super.onCreate(savedInstanceState); final ImageView iconFingerprint = findViewById(R.id.icon_fingerprint); + final ImageView iconDeviceLocked = findViewById(R.id.icon_device_locked); + final ImageView iconTrashCan = findViewById(R.id.icon_trash_can); final ImageView iconInfo = findViewById(R.id.icon_info); final ImageView iconLink = findViewById(R.id.icon_link); iconFingerprint.getDrawable().setColorFilter(getIconColorFilter()); + iconDeviceLocked.getDrawable().setColorFilter(getIconColorFilter()); + iconTrashCan.getDrawable().setColorFilter(getIconColorFilter()); iconInfo.getDrawable().setColorFilter(getIconColorFilter()); iconLink.getDrawable().setColorFilter(getIconColorFilter()); @@ -87,7 +91,7 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { @StringRes int getNegativeButtonTextId() { - return R.string.security_settings_fingerprint_enroll_introduction_skip; + return R.string.security_settings_fingerprint_enroll_introduction_no_thanks; } @StringRes diff --git a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java index e1059119b16..4bd8afd98ed 100644 --- a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java @@ -26,7 +26,6 @@ import android.os.UserHandle; import android.view.View; import com.android.internal.widget.LockPatternUtils; -import com.android.settings.R; import com.android.settings.SetupWizardUtils; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricUtils; @@ -34,8 +33,6 @@ import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.SetupChooseLockGeneric; import com.android.settings.password.SetupSkipDialog; -import com.google.android.setupcompat.template.FooterButton; - public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntroduction { /** * Returns the number of fingerprint enrolled. @@ -56,11 +53,6 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu } } - @Override - int getNegativeButtonTextId() { - return R.string.security_settings_face_enroll_introduction_cancel; - } - @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -78,19 +70,6 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu return intent; } - @Override - protected void initViews() { - super.initViews(); - - FooterButton nextButton = getNextButton(); - nextButton.setText( - this, R.string.security_settings_fingerprint_enroll_introduction_continue_setup); - - final FooterButton cancelButton = getCancelButton(); - cancelButton.setText( - this, R.string.security_settings_fingerprint_enroll_introduction_cancel_setup); - } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // if lock was already present, do not return intent data since it must have been From eba8857094c0a1deabaedba92f9713fddb7b0781 Mon Sep 17 00:00:00 2001 From: Peter Wang Date: Tue, 29 Jun 2021 15:08:00 -0700 Subject: [PATCH 05/11] Add a cancel string for ToggleSubscriptionDialog to handle Tamil translation Bug: 185553806 Test: Build Change-Id: I90149c9e4459747d8cd1c35d00c29f81710eb6f4 --- res/values/strings.xml | 2 ++ .../telephony/ToggleSubscriptionDialogActivity.java | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index aeaea6116b8..0f98a5a3e23 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -12892,6 +12892,8 @@ Restart No thanks + + Cancel Switch diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java index 0064e6ccfd2..e67ac42af47 100644 --- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java +++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java @@ -338,7 +338,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc getString(R.string.sim_action_restart_title), getString(R.string.sim_action_enable_dsds_text), getString(R.string.sim_action_reboot), - getString(R.string.cancel)); + getString(R.string.sim_action_cancel)); } /* Displays the SIM toggling confirmation dialog. */ @@ -359,7 +359,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc title, null, getString(R.string.yes), - getString(R.string.cancel)); + getString(R.string.sim_action_cancel)); } private void showEnableSimConfirmDialog() { @@ -384,7 +384,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc getSwitchSubscriptionTitle(), getSwitchDialogBodyMsg(activeSub, isBetweenEsim), getSwitchDialogPosBtnText(), - getString(android.R.string.cancel)); + getString(R.string.sim_action_cancel)); } private void showNonSwitchSimConfirmDialog() { @@ -395,7 +395,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc getEnableSubscriptionTitle(), null /* msg */, getString(R.string.yes), - getString(android.R.string.cancel)); + getString(R.string.sim_action_cancel)); } private String getSwitchDialogPosBtnText() { From 437c037830f9c971c4f612f1845ab423b865db7d Mon Sep 17 00:00:00 2001 From: Ahaan Ugale Date: Tue, 29 Jun 2021 17:16:25 -0700 Subject: [PATCH 06/11] Password settings: Fix duplicate title For the autofill service settings, "Autofill service" is both the section title and the preference title. This change removes the duplicate preference title and promotes the app label from the summary string to the title in its place. Fix: 192403526 Test: manual - single profile, personal+work profile, change service and re-check Change-Id: Ia012232ba2856e0757289982bc3045d948ff4aa8 --- res/xml/accounts_dashboard_settings.xml | 1 - .../accounts_personal_dashboard_settings.xml | 1 - res/xml/accounts_work_dashboard_settings.xml | 1 - .../DefaultAppPreferenceController.java | 19 +++++++++++++++++-- .../DefaultAutofillPreferenceController.java | 5 +++++ 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/res/xml/accounts_dashboard_settings.xml b/res/xml/accounts_dashboard_settings.xml index c8627e7e9e7..71bfc189609 100644 --- a/res/xml/accounts_dashboard_settings.xml +++ b/res/xml/accounts_dashboard_settings.xml @@ -37,7 +37,6 @@ Date: Tue, 22 Jun 2021 21:36:18 +0800 Subject: [PATCH 07/11] Change the illustration in one-hand mode page. - Different one-hand modes need to display different illustrations. Fix: 191334556 Test: rebotest and see the UI Change-Id: Id6edba40142e008c618fdb3536424edd2b77f9b0 --- res/raw/lottie_swipe_for_notifications.json | 0 .../settings/gestures/OneHandedSettings.java | 37 ++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 res/raw/lottie_swipe_for_notifications.json diff --git a/res/raw/lottie_swipe_for_notifications.json b/res/raw/lottie_swipe_for_notifications.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/com/android/settings/gestures/OneHandedSettings.java b/src/com/android/settings/gestures/OneHandedSettings.java index 6d1cbfd35d9..51c6b663671 100644 --- a/src/com/android/settings/gestures/OneHandedSettings.java +++ b/src/com/android/settings/gestures/OneHandedSettings.java @@ -16,19 +16,18 @@ package com.android.settings.gestures; +import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Context; import android.os.Bundle; import android.os.UserHandle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; import com.android.internal.accessibility.AccessibilityShortcutController; import com.android.settings.R; import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment; import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.widget.IllustrationPreference; /** * Fragment for One-handed mode settings @@ -37,13 +36,27 @@ import com.android.settings.search.BaseSearchIndexProvider; * providing basic accessibility shortcut service setup. */ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment { + private static final String ONE_HANDED_SHORTCUT_KEY = "one_handed_shortcuts_preference"; + private static final String ONE_HANDED_ILLUSTRATION_KEY = "one_handed_header"; private String mFeatureName; + private OneHandedSettingsUtils mUtils; @Override protected void updatePreferenceStates() { OneHandedSettingsUtils.setUserId(UserHandle.myUserId()); super.updatePreferenceStates(); + + final IllustrationPreference preference = + (IllustrationPreference) getPreferenceScreen().findPreference( + ONE_HANDED_ILLUSTRATION_KEY); + if (preference != null) { + final boolean isSwipeDownNotification = + OneHandedSettingsUtils.isSwipeDownNotificationEnabled(getContext()); + preference.setLottieAnimationResId( + isSwipeDownNotification ? R.raw.lottie_swipe_for_notifications + : R.raw.lottie_one_hand_mode); + } } @Override @@ -69,9 +82,21 @@ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return super.onCreateView(inflater, container, savedInstanceState); + public void onStart() { + super.onStart(); + mUtils = new OneHandedSettingsUtils(this.getContext()); + mUtils.registerToggleAwareObserver(uri -> { + Activity activity = getActivity(); + if (activity != null) { + activity.runOnUiThread(() -> updatePreferenceStates()); + } + }); + } + + @Override + public void onStop() { + super.onStop(); + mUtils.unregisterToggleAwareObserver(); } @Override From e722fbe277dce58d8bdf933d434a4774a3b93161 Mon Sep 17 00:00:00 2001 From: Stanley Wang Date: Tue, 29 Jun 2021 20:24:01 +0800 Subject: [PATCH 08/11] Update Adaptive connectivity page. - Use MainSwitchPreference. - Use the TopIntroPreference to display the summary. - Use the IllustrationPreference to display the stastic illustration. - Add new illustrations for adaptive connectivity. Fix: 178673083 Test: robotest and test the switch preference manually Change-Id: I2376f4433b61ce1d3da287de92660fbeea5dd64e --- .../ic_enhanced_connectivity.xml | 91 +++++++++++++++++++ res/drawable/ic_enhanced_connectivity.xml | 91 +++++++++++++++++++ res/xml/adaptive_connectivity_settings.xml | 20 ++-- ...onnectivityTogglePreferenceController.java | 8 +- 4 files changed, 196 insertions(+), 14 deletions(-) create mode 100644 res/drawable-night/ic_enhanced_connectivity.xml create mode 100644 res/drawable/ic_enhanced_connectivity.xml diff --git a/res/drawable-night/ic_enhanced_connectivity.xml b/res/drawable-night/ic_enhanced_connectivity.xml new file mode 100644 index 00000000000..cd256676316 --- /dev/null +++ b/res/drawable-night/ic_enhanced_connectivity.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/drawable/ic_enhanced_connectivity.xml b/res/drawable/ic_enhanced_connectivity.xml new file mode 100644 index 00000000000..45767bd7d44 --- /dev/null +++ b/res/drawable/ic_enhanced_connectivity.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/adaptive_connectivity_settings.xml b/res/xml/adaptive_connectivity_settings.xml index ff9bdb0443e..d5941ad0507 100644 --- a/res/xml/adaptive_connectivity_settings.xml +++ b/res/xml/adaptive_connectivity_settings.xml @@ -19,20 +19,18 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/adaptive_connectivity_title"> - + - + + diff --git a/src/com/android/settings/network/AdaptiveConnectivityTogglePreferenceController.java b/src/com/android/settings/network/AdaptiveConnectivityTogglePreferenceController.java index e1e56a8a6fa..e3d779ce938 100644 --- a/src/com/android/settings/network/AdaptiveConnectivityTogglePreferenceController.java +++ b/src/com/android/settings/network/AdaptiveConnectivityTogglePreferenceController.java @@ -22,12 +22,14 @@ import android.provider.Settings; import androidx.preference.PreferenceScreen; -import com.android.settings.core.TogglePreferenceController; +import com.android.settings.widget.SettingsMainSwitchPreferenceController; /** - * {@link TogglePreferenceController} that controls whether Adaptive connectivity option is enabled. + * {@link SettingsMainSwitchPreferenceController} + * that controls whether Adaptive connectivity option is enabled. */ -public class AdaptiveConnectivityTogglePreferenceController extends TogglePreferenceController { +public class AdaptiveConnectivityTogglePreferenceController extends + SettingsMainSwitchPreferenceController { private final WifiManager mWifiManager; From 1eba90f572e6d95aaa905f3bbf4f8c729cd5cf31 Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Wed, 30 Jun 2021 16:27:11 +0800 Subject: [PATCH 09/11] Update the material next style for the banner of Auto Click. Action: Apply the illustrationPreference from SettingsLib to the banner. Bug: 190585192 Bug: 192413239 Test: manual test Change-Id: I7d78804b4fa89735edfb73f01f31771821bcd8c2 --- .../accessibility_autoclick_preview.xml | 30 ------------------- res/xml/accessibility_autoclick_settings.xml | 9 +++--- 2 files changed, 4 insertions(+), 35 deletions(-) delete mode 100644 res/layout/accessibility_autoclick_preview.xml diff --git a/res/layout/accessibility_autoclick_preview.xml b/res/layout/accessibility_autoclick_preview.xml deleted file mode 100644 index 4fa3f8ff8a0..00000000000 --- a/res/layout/accessibility_autoclick_preview.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - diff --git a/res/xml/accessibility_autoclick_settings.xml b/res/xml/accessibility_autoclick_settings.xml index ee27cea24e5..06b3dced4e2 100644 --- a/res/xml/accessibility_autoclick_settings.xml +++ b/res/xml/accessibility_autoclick_settings.xml @@ -19,14 +19,13 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/accessibility_autoclick_preference_title"> - + settings:searchable="false" + settings:lottie_rawRes="@drawable/accessibility_dwell" /> Date: Wed, 30 Jun 2021 14:53:13 +0000 Subject: [PATCH 10/11] Revert "Return enrollment consent status to caller." This reverts commit eb1dac69f091e728171f5b8b91a6c31881edbef9. Reason for revert: Based on bisection, this CL is the root cause for bug 192420564, which breaks Setup Wizard. Bug: 192420564 Change-Id: I8d9aee7fe2415e134fcc981b0548bd9ce300db55 --- .../biometrics/BiometricEnrollActivity.java | 128 ++++++++---------- .../MultiBiometricEnrollHelper.java | 8 ++ .../biometrics/ParentalConsentHelper.java | 2 - 3 files changed, 63 insertions(+), 75 deletions(-) diff --git a/src/com/android/settings/biometrics/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java index 3b8f25507e9..db5e003456a 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java +++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java @@ -71,7 +71,6 @@ public class BiometricEnrollActivity extends InstrumentedActivity { private static final int REQUEST_CHOOSE_OPTIONS = 3; // prompt hand phone back to parent after enrollment private static final int REQUEST_HANDOFF_PARENT = 4; - private static final int REQUEST_SINGLE_ENROLL = 5; public static final int RESULT_SKIP = BiometricEnrollBase.RESULT_SKIP; @@ -79,13 +78,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity { // this only applies to fingerprint. public static final String EXTRA_SKIP_INTRO = "skip_intro"; - // Intent extra. If true, parental consent will be requested before user enrollment. - public static final String EXTRA_REQUIRE_PARENTAL_CONSENT = "require_consent"; - - // If EXTRA_REQUIRE_PARENTAL_CONSENT was used to start the activity then the result - // intent will include this extra containing a bundle of the form: - // "modality" -> consented (boolean). - public static final String EXTRA_PARENTAL_CONSENT_STATUS = "consent_status"; + // TODO: temporary while waiting for team to add real flag + public static final String EXTRA_TEMP_REQUIRE_PARENTAL_CONSENT = "require_consent"; private static final String SAVED_STATE_CONFIRMING_CREDENTIALS = "confirming_credentials"; private static final String SAVED_STATE_ENROLL_ACTION_LOGGED = "enroll_action_logged"; @@ -195,7 +189,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { // TODO(b/188847063): replace with real flag when ready mParentalOptionsRequired = intent.getBooleanExtra( - BiometricEnrollActivity.EXTRA_REQUIRE_PARENTAL_CONSENT, false); + BiometricEnrollActivity.EXTRA_TEMP_REQUIRE_PARENTAL_CONSENT, false); if (mParentalOptionsRequired && mParentalOptions == null) { mParentalConsentHelper = new ParentalConsentHelper( @@ -207,6 +201,19 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } private void startEnroll() { + // TODO(b/188847063): This can be deleted, but log it now until it's wired up for real. + if (mParentalOptionsRequired) { + if (mParentalOptions == null) { + throw new IllegalStateException("consent options required, but not set"); + } + Log.d(TAG, "consent for face: " + + ParentalConsentHelper.hasFaceConsent(mParentalOptions)); + Log.d(TAG, "consent for fingerprint: " + + ParentalConsentHelper.hasFingerprintConsent(mParentalOptions)); + } else { + Log.d(TAG, "startEnroll without requiring consent"); + } + // Default behavior is to enroll BIOMETRIC_WEAK or above. See ACTION_BIOMETRIC_ENROLL. final int authenticators = getIntent().getIntExtra( EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK); @@ -227,38 +234,21 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } } - boolean canUseFace = mHasFeatureFace; - boolean canUseFingerprint = mHasFeatureFingerprint; - if (mParentalOptionsRequired) { - if (mParentalOptions == null) { - throw new IllegalStateException("consent options required, but not set"); - } - canUseFace = canUseFace - && ParentalConsentHelper.hasFaceConsent(mParentalOptions); - canUseFingerprint = canUseFingerprint - && ParentalConsentHelper.hasFingerprintConsent(mParentalOptions); - } - // This will need to be updated if the device has sensors other than BIOMETRIC_STRONG if (!setupWizard && authenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) { launchCredentialOnlyEnroll(); - } else if (canUseFace && canUseFingerprint) { + } else if (mHasFeatureFace && mHasFeatureFingerprint) { if (mParentalOptionsRequired && mGkPwHandle != null) { launchFaceAndFingerprintEnroll(); } else { setOrConfirmCredentialsNow(); } - } else if (canUseFingerprint) { + } else if (mHasFeatureFingerprint) { launchFingerprintOnlyEnroll(); - } else if (canUseFace) { + } else if (mHasFeatureFace) { launchFaceOnlyEnroll(); - } else { // no modalities available - if (mParentalOptionsRequired) { - Log.d(TAG, "No consent for any modality: skipping enrollment"); - setResult(RESULT_OK, newResultIntent()); - } else { - Log.e(TAG, "Unknown state, finishing (was SUW: " + setupWizard + ")"); - } + } else { + Log.e(TAG, "Unknown state, finishing (was SUW: " + setupWizard + ")"); finish(); } } @@ -285,7 +275,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { if (mParentalConsentHelper != null) { handleOnActivityResultWhileConsenting(requestCode, resultCode, data); } else { - handleOnActivityResultWhileEnrolling(requestCode, resultCode, data); + handleOnActivityResultWhileEnrollingMultiple(requestCode, resultCode, data); } } @@ -315,10 +305,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity { final boolean isStillPrompting = mParentalConsentHelper.launchNext( this, REQUEST_CHOOSE_OPTIONS, resultCode, data); if (!isStillPrompting) { - Log.d(TAG, "Enrollment consent options set, starting enrollment"); - mParentalOptions = mParentalConsentHelper.getConsentResult(); - mParentalConsentHelper = null; - startEnroll(); + Log.d(TAG, "Enrollment options set, requesting handoff"); + launchHandoffToParent(); } } else { Log.d(TAG, "Unknown or cancelled parental consent"); @@ -326,6 +314,18 @@ public class BiometricEnrollActivity extends InstrumentedActivity { finish(); } break; + case REQUEST_HANDOFF_PARENT: + if (resultCode == RESULT_OK) { + Log.d(TAG, "Enrollment options set, starting enrollment"); + mParentalOptions = mParentalConsentHelper.getConsentResult(); + mParentalConsentHelper = null; + startEnroll(); + } else { + Log.d(TAG, "Unknown or cancelled handoff"); + setResult(RESULT_CANCELED); + finish(); + } + break; default: Log.w(TAG, "Unknown consenting requestCode: " + requestCode + ", finishing"); finish(); @@ -333,13 +333,9 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } // handles responses while multi biometric enrollment is pending - private void handleOnActivityResultWhileEnrolling( + private void handleOnActivityResultWhileEnrollingMultiple( int requestCode, int resultCode, Intent data) { - if (requestCode == REQUEST_HANDOFF_PARENT) { - Log.d(TAG, "Enrollment complete, requesting handoff, result: " + resultCode); - setResult(RESULT_OK, newResultIntent()); - finish(); - } else if (mMultiBiometricEnrollHelper == null) { + if (mMultiBiometricEnrollHelper == null) { overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); switch (requestCode) { @@ -359,37 +355,15 @@ public class BiometricEnrollActivity extends InstrumentedActivity { finish(); } break; - case REQUEST_SINGLE_ENROLL: - finishOrLaunchHandToParent(resultCode); - break; default: Log.w(TAG, "Unknown enrolling requestCode: " + requestCode + ", finishing"); finish(); } } else { - Log.d(TAG, "RequestCode: " + requestCode + " resultCode: " + resultCode); - BiometricUtils.removeGatekeeperPasswordHandle(this, mGkPwHandle); - finishOrLaunchHandToParent(resultCode); + mMultiBiometricEnrollHelper.onActivityResult(requestCode, resultCode, data); } } - private void finishOrLaunchHandToParent(int resultCode) { - if (mParentalOptionsRequired) { - launchHandoffToParent(); - } else { - setResult(resultCode); - finish(); - } - } - - private Intent newResultIntent() { - final Intent intent = new Intent(); - final Bundle consentStatus = mParentalOptions.deepCopy(); - intent.putExtra(EXTRA_PARENTAL_CONSENT_STATUS, consentStatus); - Log.v(TAG, "Result consent status: " + consentStatus); - return intent; - } - private static boolean isSuccessfulConfirmOrChooseCredential(int requestCode, int resultCode) { final boolean okChoose = requestCode == REQUEST_CHOOSE_LOCK && resultCode == ChooseLockPattern.RESULT_FINISHED; @@ -410,8 +384,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity { super.onStop(); if (mConfirmingCredentials - || mParentalOptionsRequired - || mMultiBiometricEnrollHelper != null) { + || mMultiBiometricEnrollHelper != null + || mParentalConsentHelper != null) { return; } @@ -421,6 +395,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } } + private void setOrConfirmCredentialsNow() { if (!mConfirmingCredentials) { mConfirmingCredentials = true; @@ -479,14 +454,21 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } } - // This should only be used to launch enrollment for single-sensor devices. - private void launchSingleSensorEnrollActivity(@NonNull Intent intent, int requestCode) { + /** + * This should only be used to launch enrollment for single-sensor devices, which use + * FLAG_ACTIVITY_FORWARD_RESULT path. + * + * @param intent Enrollment activity that should be started (e.g. FaceEnrollIntroduction.class, + * etc). + */ + private void launchSingleSensorEnrollActivity(@NonNull Intent intent) { + intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); byte[] hardwareAuthToken = null; if (this instanceof InternalActivity) { hardwareAuthToken = getIntent().getByteArrayExtra( ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); } - BiometricUtils.launchEnrollForResult(this, intent, requestCode, hardwareAuthToken, + BiometricUtils.launchEnrollForResult(this, intent, 0 /* requestCode */, hardwareAuthToken, mGkPwHandle, mUserId); } @@ -495,7 +477,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { // If only device credential was specified, ask the user to only set that up. intent = new Intent(this, ChooseLockGeneric.class); intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); - launchSingleSensorEnrollActivity(intent, 0 /* requestCode */); + launchSingleSensorEnrollActivity(intent); } private void launchFingerprintOnlyEnroll() { @@ -507,12 +489,12 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } else { intent = BiometricUtils.getFingerprintIntroIntent(this, getIntent()); } - launchSingleSensorEnrollActivity(intent, REQUEST_SINGLE_ENROLL); + launchSingleSensorEnrollActivity(intent); } private void launchFaceOnlyEnroll() { final Intent intent = BiometricUtils.getFaceIntroIntent(this, getIntent()); - launchSingleSensorEnrollActivity(intent, REQUEST_SINGLE_ENROLL); + launchSingleSensorEnrollActivity(intent); } private void launchFaceAndFingerprintEnroll() { diff --git a/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java b/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java index 44d75c5808d..74d7c535e05 100644 --- a/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java +++ b/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java @@ -20,6 +20,7 @@ import android.app.PendingIntent; import android.content.Intent; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; +import android.util.Log; import androidx.annotation.NonNull; import androidx.fragment.app.FragmentActivity; @@ -106,4 +107,11 @@ public class MultiBiometricEnrollHelper { hardwareAuthToken, mGkPwHandle, mUserId); })); } + + void onActivityResult(int requestCode, int resultCode, Intent data) { + Log.d(TAG, "RequestCode: " + requestCode + " resultCode: " + resultCode); + BiometricUtils.removeGatekeeperPasswordHandle(mActivity, mGkPwHandle); + mActivity.setResult(resultCode); + mActivity.finish(); + } } diff --git a/src/com/android/settings/biometrics/ParentalConsentHelper.java b/src/com/android/settings/biometrics/ParentalConsentHelper.java index 6c4004e5c55..905a95527ef 100644 --- a/src/com/android/settings/biometrics/ParentalConsentHelper.java +++ b/src/com/android/settings/biometrics/ParentalConsentHelper.java @@ -46,7 +46,6 @@ public class ParentalConsentHelper { private static final String KEY_FACE_CONSENT = "face"; private static final String KEY_FINGERPRINT_CONSENT = "fingerprint"; - private static final String KEY_IRIS_CONSENT = "iris"; private final boolean mRequireFace; private final boolean mRequireFingerprint; @@ -154,7 +153,6 @@ public class ParentalConsentHelper { result.putBoolean(KEY_FACE_CONSENT, mConsentFace != null ? mConsentFace : false); result.putBoolean(KEY_FINGERPRINT_CONSENT, mConsentFingerprint != null ? mConsentFingerprint : false); - result.putBoolean(KEY_IRIS_CONSENT, false); return result; } From b61b7396614af63d48e46996ca437efc4d6268bf Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Wed, 30 Jun 2021 12:59:28 -0400 Subject: [PATCH 11/11] Properly set all data fields So stale data doesn't stick around when the view is recycled Test: manual: - dismiss a notification - set a short timer (a custom view notif) - check history after timer expires (so there are 2 notifs in 'recently dismissed') - stop the timer and check history - there are now 3 notifs and no stale text in any of them Fixes: 192294011 Change-Id: I229a0388a703b9df0d92eec81066a9a2f67e183e --- .../notification/history/NotificationSbnViewHolder.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/com/android/settings/notification/history/NotificationSbnViewHolder.java b/src/com/android/settings/notification/history/NotificationSbnViewHolder.java index c98b036d7e2..166ee5d4ffc 100644 --- a/src/com/android/settings/notification/history/NotificationSbnViewHolder.java +++ b/src/com/android/settings/notification/history/NotificationSbnViewHolder.java @@ -65,9 +65,6 @@ public class NotificationSbnViewHolder extends RecyclerView.ViewHolder { } void setTitle(CharSequence title) { - if (title == null) { - return; - } mTitle.setText(title); }