Collapse private space container and animate header.
- Just opposite of how it will expand. - RecyclerView.SmoothScroller is needed to scroll the container. - Need to separate the lock button because this way I can use animateLayout changes and it itself was its own drawable. Separated into icon and textView in a viewGroup. - Give the background the 10padding on the left and right so that when in animation, the icon can adjust the padding/margins there. - Using propertySetter to set animation - Animates the alpha of the settings alpha - updated test to account for the nested child views the test needs to inspect bug: 299294792 test: manual: Expand + Collapse Video: https://drive.google.com/file/d/1Og66eqmXv3THn0wO4_x6Tfp2AbwFWUwZ/view?usp=sharing Flag: ACONFIG com.android.launcher3.Flags.private_space_animation TEAMFOOD Change-Id: I96f1d172a481522d23b4cee996ddec65961fce78
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (C) 2024 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="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="?attr/materialColorOnPrimaryFixed"
|
||||
android:pathData="M263.72,864Q234,864 213,842.85Q192,821.7 192,792L192,408Q192,378.3 213.15,357.15Q234.3,336 264,336L288,336L288,240Q288,160.32 344.23,104.16Q400.45,48 480.23,48Q560,48 616,104.16Q672,160.32 672,240L672,336L696,336Q725.7,336 746.85,357.15Q768,378.3 768,408L768,792Q768,821.7 746.84,842.85Q725.68,864 695.96,864L263.72,864ZM264,792L696,792Q696,792 696,792Q696,792 696,792L696,408Q696,408 696,408Q696,408 696,408L264,408Q264,408 264,408Q264,408 264,408L264,792Q264,792 264,792Q264,792 264,792ZM480.21,672Q510,672 531,650.79Q552,629.58 552,599.79Q552,570 530.79,549Q509.58,528 479.79,528Q450,528 429,549.21Q408,570.42 408,600.21Q408,630 429.21,651Q450.42,672 480.21,672ZM360,336L600,336L600,240Q600,190 565,155Q530,120 480,120Q430,120 395,155Q360,190 360,240L360,336ZM264,792Q264,792 264,792Q264,792 264,792L264,408Q264,408 264,408Q264,408 264,408L264,408Q264,408 264,408Q264,408 264,408L264,792Q264,792 264,792Q264,792 264,792L264,792Z"/>
|
||||
</vector>
|
||||
@@ -15,13 +15,10 @@
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/ps_button_height"
|
||||
android:width="@dimen/ps_button_width"
|
||||
android:height="@dimen/ps_button_height"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M20,0L20,0A20,20 0,0 1,40 20L40,20A20,20 0,0 1,20 40L20,40A20,20 0,0 1,0 20L0,20A20,20 0,0 1,20 0z"
|
||||
android:fillColor="?attr/materialColorSurfaceBright"/>
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M10,10h20v20h-20z"/>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2024 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.
|
||||
-->
|
||||
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="4dp">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/ps_lock_corner_radius" />
|
||||
<solid android:color="?attr/materialColorPrimaryFixedDim" />
|
||||
<padding
|
||||
android:left="@dimen/ps_lock_button_background_padding"
|
||||
android:right="@dimen/ps_lock_button_background_padding" />
|
||||
</shape>
|
||||
</inset>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (C) 2024 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.
|
||||
-->
|
||||
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="4dp">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/ps_lock_corner_radius" />
|
||||
<solid android:color="?attr/materialColorSurfaceBright" />
|
||||
</shape>
|
||||
</inset>
|
||||
@@ -18,6 +18,7 @@
|
||||
<RelativeLayout
|
||||
android:id="@+id/ps_header_layout"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/ps_header_height"
|
||||
android:background="@drawable/bg_ps_header"
|
||||
@@ -25,27 +26,53 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/ps_lock_unlock_button"
|
||||
<LinearLayout
|
||||
android:id="@+id/settingsAndLockGroup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/ps_header_image_height"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_marginEnd="@dimen/ps_header_layout_margin"
|
||||
android:contentDescription="@string/ps_container_lock_unlock_button" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/ps_settings_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/ps_header_image_height"
|
||||
android:layout_toStartOf="@+id/ps_lock_unlock_button"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_marginEnd="@dimen/ps_header_settings_icon_margin_end"
|
||||
android:src="@drawable/bg_ps_settings_button"
|
||||
android:contentDescription="@string/ps_container_settings" />
|
||||
|
||||
android:animateLayoutChanges="true">
|
||||
<ImageButton
|
||||
android:id="@+id/ps_settings_button"
|
||||
android:layout_width="@dimen/ps_header_image_height"
|
||||
android:layout_height="@dimen/ps_header_image_height"
|
||||
android:background="@drawable/ps_settings_background"
|
||||
android:layout_marginEnd="@dimen/ps_header_settings_icon_margin_end"
|
||||
android:src="@drawable/ic_ps_settings"
|
||||
android:contentDescription="@string/ps_container_settings" />
|
||||
<LinearLayout
|
||||
android:id="@+id/ps_lock_unlock_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/ps_header_image_height"
|
||||
android:background="@drawable/ps_lock_background"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginEnd="@dimen/ps_header_layout_margin"
|
||||
android:contentDescription="@string/ps_container_lock_unlock_button">
|
||||
<ImageView
|
||||
android:id="@+id/lock_icon"
|
||||
android:layout_width="@dimen/ps_lock_icon_size"
|
||||
android:layout_height="@dimen/ps_lock_icon_size"
|
||||
android:layout_marginTop="@dimen/ps_lock_icon_margin_top"
|
||||
android:layout_marginBottom="@dimen/ps_lock_icon_margin_bottom"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/ic_lock"
|
||||
app:tint="@color/material_color_primary_fixed_dim"
|
||||
android:scaleType="center"/>
|
||||
<TextView
|
||||
android:id="@+id/lock_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/ps_lock_icon_text_margin_start_expanded"
|
||||
android:layout_marginEnd="@dimen/ps_lock_icon_text_margin_end_expanded"
|
||||
android:textColor="@color/material_color_on_primary_fixed"
|
||||
android:textSize="14sp"
|
||||
android:text="@string/ps_container_lock_title"
|
||||
android:visibility="gone"
|
||||
style="@style/TextHeadline"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:id="@+id/ps_transition_image"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -63,6 +90,7 @@
|
||||
android:layout_height="@dimen/ps_header_text_height"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toStartOf="@+id/settingsAndLockGroup"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/ps_header_layout_margin"
|
||||
android:text="@string/ps_container_title"
|
||||
|
||||
+10
-3
@@ -476,15 +476,22 @@
|
||||
<dimen name="ps_container_corner_radius">24dp</dimen>
|
||||
<dimen name="ps_header_height">64dp</dimen>
|
||||
<dimen name="ps_header_relative_layout_height">48dp</dimen>
|
||||
<dimen name="ps_header_image_height">36dp</dimen>
|
||||
<dimen name="ps_header_image_height">48dp</dimen>
|
||||
<dimen name="ps_header_text_height">24dp</dimen>
|
||||
<dimen name="ps_header_layout_margin">16dp</dimen>
|
||||
<dimen name="ps_header_settings_icon_margin_end">8dp</dimen>
|
||||
<dimen name="ps_header_text_size">16sp</dimen>
|
||||
<dimen name="ps_button_height">36dp</dimen>
|
||||
<dimen name="ps_button_width">36dp</dimen>
|
||||
<dimen name="ps_button_height">40dp</dimen>
|
||||
<dimen name="ps_button_width">40dp</dimen>
|
||||
<dimen name="ps_lock_button_width">89dp</dimen>
|
||||
<dimen name="ps_app_divider_padding">16dp</dimen>
|
||||
<dimen name="ps_lock_corner_radius">20dp</dimen>
|
||||
<dimen name="ps_lock_icon_size">20dp</dimen>
|
||||
<dimen name="ps_lock_icon_margin_top">10dp</dimen>
|
||||
<dimen name="ps_lock_icon_margin_bottom">10dp</dimen>
|
||||
<dimen name="ps_lock_icon_text_margin_start_expanded">8dp</dimen>
|
||||
<dimen name="ps_lock_icon_text_margin_end_expanded">6dp</dimen>
|
||||
<dimen name="ps_lock_button_background_padding">10dp</dimen>
|
||||
|
||||
<!-- WindowManagerProxy -->
|
||||
<dimen name="max_width_and_height_of_small_display_cutout">136px</dimen>
|
||||
|
||||
@@ -465,6 +465,7 @@
|
||||
<string name="ps_container_settings">Private Space Settings</string>
|
||||
<!-- Description for Private Space Lock/Unlock button -->
|
||||
<string name="ps_container_lock_unlock_button">Lock/Unlock Private Space</string>
|
||||
<string name="ps_container_lock_title">Lock</string>
|
||||
<!-- Description for Private Space Transition button -->
|
||||
<string name="ps_container_transition">Private Space Transitioning</string>
|
||||
<!-- Title for Private Space install app icon -->
|
||||
|
||||
@@ -441,5 +441,6 @@
|
||||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:textColor">@color/material_color_on_surface</item>
|
||||
<item name="android:fontFamily">google-sans-text-medium</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -294,6 +294,12 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
mIconLoadRequest.cancel();
|
||||
mIconLoadRequest = null;
|
||||
}
|
||||
// Reset any shifty arrangements in case animation is disrupted.
|
||||
setPivotY(0);
|
||||
setAlpha(1);
|
||||
setScaleY(1);
|
||||
setTranslationY(0);
|
||||
setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private void cancelDotScaleAnim() {
|
||||
|
||||
@@ -201,7 +201,6 @@ public abstract class FastScrollRecyclerView extends RecyclerView {
|
||||
if (mScrollbar != null) {
|
||||
mScrollbar.reattachThumbToScroll();
|
||||
}
|
||||
// Emphasized interpolators with 500ms duration
|
||||
smoothScrollBy(0, getAvailableScrollHeight(), Interpolators.EMPHASIZED, duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +258,7 @@ public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> ex
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
holder.itemView.setVisibility(View.VISIBLE);
|
||||
switch (holder.getItemViewType()) {
|
||||
case VIEW_TYPE_ICON: {
|
||||
AdapterItem adapterItem = mApps.getAdapterItems().get(position);
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
|
||||
package com.android.launcher3.allapps;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
|
||||
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
|
||||
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
|
||||
import static com.android.launcher3.allapps.PrivateProfileManager.STATE_DISABLED;
|
||||
import static com.android.launcher3.allapps.PrivateProfileManager.STATE_ENABLED;
|
||||
import static com.android.launcher3.allapps.PrivateProfileManager.STATE_TRANSITION;
|
||||
@@ -24,21 +29,37 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_SETTINGS_TAP;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP;
|
||||
|
||||
import android.view.View;
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.LayoutTransition;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.allapps.UserProfileManager.UserProfileState;
|
||||
import com.android.launcher3.anim.AnimatedPropertySetter;
|
||||
import com.android.launcher3.anim.PropertySetter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Controller which returns views to be added to Private Space Header based upon
|
||||
* {@link UserProfileState}
|
||||
*/
|
||||
public class PrivateSpaceHeaderViewController {
|
||||
private static final int ANIMATION_DURATION = 2000;
|
||||
private static final int EXPAND_SCROLL_DURATION = 2000;
|
||||
private static final int EXPAND_COLLAPSE_DURATION = 800;
|
||||
private static final int SETTINGS_OPACITY_DURATION = 160;
|
||||
private final ActivityAllAppsContainerView mAllApps;
|
||||
private final PrivateProfileManager mPrivateProfileManager;
|
||||
|
||||
@@ -50,10 +71,17 @@ public class PrivateSpaceHeaderViewController {
|
||||
|
||||
/** Add Private Space Header view elements based upon {@link UserProfileState} */
|
||||
public void addPrivateSpaceHeaderViewElements(RelativeLayout parent) {
|
||||
// Set the transition duration for the settings and lock button to animate.
|
||||
ViewGroup settingsAndLockGroup = parent.findViewById(R.id.settingsAndLockGroup);
|
||||
LayoutTransition settingsAndLockTransition = settingsAndLockGroup.getLayoutTransition();
|
||||
settingsAndLockTransition.enableTransitionType(LayoutTransition.CHANGING);
|
||||
settingsAndLockTransition.setDuration(EXPAND_COLLAPSE_DURATION);
|
||||
|
||||
//Add quietMode image and action for lock/unlock button
|
||||
ImageButton quietModeButton = parent.findViewById(R.id.ps_lock_unlock_button);
|
||||
assert quietModeButton != null;
|
||||
addQuietModeButton(quietModeButton);
|
||||
ViewGroup lockButton =
|
||||
parent.findViewById(R.id.ps_lock_unlock_button);
|
||||
assert lockButton != null;
|
||||
addLockButton(parent, lockButton);
|
||||
|
||||
//Trigger lock/unlock action from header.
|
||||
addHeaderOnClickListener(parent);
|
||||
@@ -69,74 +97,181 @@ public class PrivateSpaceHeaderViewController {
|
||||
addTransitionImage(transitionView);
|
||||
}
|
||||
|
||||
private void addQuietModeButton(ImageButton quietModeButton) {
|
||||
/**
|
||||
* Adds the quietModeButton and attach onClickListener for the header to animate different
|
||||
* states when clicked.
|
||||
*/
|
||||
private void addLockButton(ViewGroup psHeader, ViewGroup lockButton) {
|
||||
TextView lockText = lockButton.findViewById(R.id.lock_text);
|
||||
switch (mPrivateProfileManager.getCurrentState()) {
|
||||
case STATE_ENABLED -> {
|
||||
quietModeButton.setVisibility(View.VISIBLE);
|
||||
quietModeButton.setImageResource(R.drawable.bg_ps_lock_button);
|
||||
quietModeButton.setOnClickListener(view -> lockAction());
|
||||
lockText.setVisibility(VISIBLE);
|
||||
lockButton.setVisibility(VISIBLE);
|
||||
lockButton.setOnClickListener(view -> lockAction(psHeader));
|
||||
}
|
||||
case STATE_DISABLED -> {
|
||||
quietModeButton.setVisibility(View.VISIBLE);
|
||||
quietModeButton.setImageResource(R.drawable.bg_ps_unlock_button);
|
||||
quietModeButton.setOnClickListener(view -> unLockAction());
|
||||
lockText.setVisibility(GONE);
|
||||
lockButton.setVisibility(VISIBLE);
|
||||
lockButton.setOnClickListener(view -> unlockAction(psHeader));
|
||||
}
|
||||
default -> quietModeButton.setVisibility(View.GONE);
|
||||
default -> lockButton.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void addHeaderOnClickListener(RelativeLayout header) {
|
||||
if (mPrivateProfileManager.getCurrentState() == STATE_DISABLED) {
|
||||
header.setOnClickListener(view -> unLockAction());
|
||||
header.setOnClickListener(view -> unlockAction(header));
|
||||
} else {
|
||||
header.setOnClickListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void unLockAction() {
|
||||
private void unlockAction(ViewGroup psHeader) {
|
||||
mPrivateProfileManager.logEvents(LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP);
|
||||
mPrivateProfileManager.unlockPrivateProfile((this::onPrivateProfileUnlocked));
|
||||
mPrivateProfileManager.unlockPrivateProfile((() -> onPrivateProfileUnlocked(psHeader)));
|
||||
}
|
||||
|
||||
private void lockAction() {
|
||||
private void lockAction(ViewGroup psHeader) {
|
||||
mPrivateProfileManager.logEvents(LAUNCHER_PRIVATE_SPACE_LOCK_TAP);
|
||||
mPrivateProfileManager.lockPrivateProfile();
|
||||
if (Flags.enablePrivateSpace() && Flags.privateSpaceAnimation()) {
|
||||
updatePrivateStateAnimator(false, psHeader);
|
||||
} else {
|
||||
mPrivateProfileManager.lockPrivateProfile();
|
||||
}
|
||||
}
|
||||
|
||||
private void addPrivateSpaceSettingsButton(ImageButton settingsButton) {
|
||||
if (mPrivateProfileManager.getCurrentState() == STATE_ENABLED
|
||||
&& mPrivateProfileManager.isPrivateSpaceSettingsAvailable()) {
|
||||
settingsButton.setVisibility(View.VISIBLE);
|
||||
settingsButton.setVisibility(VISIBLE);
|
||||
settingsButton.setAlpha(1f);
|
||||
settingsButton.setOnClickListener(
|
||||
view -> {
|
||||
mPrivateProfileManager.logEvents(LAUNCHER_PRIVATE_SPACE_SETTINGS_TAP);
|
||||
mPrivateProfileManager.openPrivateSpaceSettings();
|
||||
});
|
||||
} else {
|
||||
settingsButton.setVisibility(View.GONE);
|
||||
settingsButton.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void addTransitionImage(ImageView transitionImage) {
|
||||
if (mPrivateProfileManager.getCurrentState() == STATE_TRANSITION) {
|
||||
transitionImage.setVisibility(View.VISIBLE);
|
||||
transitionImage.setVisibility(VISIBLE);
|
||||
} else {
|
||||
transitionImage.setVisibility(View.GONE);
|
||||
transitionImage.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPrivateProfileUnlocked() {
|
||||
private void onPrivateProfileUnlocked(ViewGroup header) {
|
||||
// If we are on main adapter view, we apply the PS Container expansion animation and
|
||||
// then scroll down to load the entire container, making animation visible.
|
||||
ActivityAllAppsContainerView<?>.AdapterHolder mainAdapterHolder =
|
||||
(ActivityAllAppsContainerView<?>.AdapterHolder) mAllApps.mAH.get(MAIN);
|
||||
if (Flags.enablePrivateSpace() && Flags.privateSpaceAnimation()
|
||||
&& mAllApps.getActiveRecyclerView() == mainAdapterHolder.mRecyclerView) {
|
||||
mAllApps.getActiveRecyclerView().scrollToBottomWithMotion(ANIMATION_DURATION);
|
||||
// Animate the text and settings icon.
|
||||
updatePrivateStateAnimator(true, header);
|
||||
mAllApps.getActiveRecyclerView().scrollToBottomWithMotion(EXPAND_SCROLL_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
/** Finds the private space header to scroll to and set the private space icons to GONE. */
|
||||
private void collapse() {
|
||||
AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
|
||||
for (int i = allAppsRecyclerView.getChildCount() - 1; i > 0; i--) {
|
||||
int adapterPosition = allAppsRecyclerView.getChildAdapterPosition(
|
||||
allAppsRecyclerView.getChildAt(i));
|
||||
List<BaseAllAppsAdapter.AdapterItem> allAppsAdapters = allAppsRecyclerView.getApps()
|
||||
.getAdapterItems();
|
||||
if (adapterPosition < 0 || adapterPosition >= allAppsAdapters.size()) {
|
||||
continue;
|
||||
}
|
||||
// Scroll to the private space header.
|
||||
if (allAppsAdapters.get(adapterPosition).viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
|
||||
// Note: SmoothScroller is meant to be used once.
|
||||
RecyclerView.SmoothScroller smoothScroller =
|
||||
new LinearSmoothScroller(mAllApps.getContext()) {
|
||||
@Override protected int getVerticalSnapPreference() {
|
||||
return LinearSmoothScroller.SNAP_TO_END;
|
||||
}
|
||||
};
|
||||
smoothScroller.setTargetPosition(adapterPosition);
|
||||
RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
|
||||
if (layoutManager != null) {
|
||||
layoutManager.startSmoothScroll(smoothScroller);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Make the private space apps gone to "collapse".
|
||||
if (allAppsAdapters.get(adapterPosition).decorationInfo != null) {
|
||||
allAppsRecyclerView.getChildAt(i).setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrivateProfileManager getPrivateProfileManager() {
|
||||
return mPrivateProfileManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls up to the private space header and animates the collapsing of the text.
|
||||
*/
|
||||
private ValueAnimator animateCollapseAnimation(ViewGroup lockButton) {
|
||||
float from = 1;
|
||||
float to = 0;
|
||||
ValueAnimator collapseAnim = ValueAnimator.ofFloat(from, to);
|
||||
collapseAnim.setDuration(EXPAND_COLLAPSE_DURATION);
|
||||
collapseAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
// scroll up
|
||||
collapse();
|
||||
// Animate the collapsing of the text.
|
||||
lockButton.findViewById(R.id.lock_text).setVisibility(GONE);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
mPrivateProfileManager.lockPrivateProfile();
|
||||
}
|
||||
});
|
||||
return collapseAnim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using PropertySetter{@link PropertySetter}, we can update the view's attributes within an
|
||||
* animation. At the moment, collapsing, setting alpha changes, and animating the text is done
|
||||
* here.
|
||||
*/
|
||||
private void updatePrivateStateAnimator(boolean expand, ViewGroup psHeader) {
|
||||
PropertySetter setter = new AnimatedPropertySetter();
|
||||
ViewGroup lockButton = psHeader.findViewById(R.id.ps_lock_unlock_button);
|
||||
ImageButton settingsButton = psHeader.findViewById(R.id.ps_settings_button);
|
||||
updateSettingsGearAlpha(settingsButton, expand, setter);
|
||||
AnimatorSet animatorSet = setter.buildAnim();
|
||||
animatorSet.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
// Animate the collapsing of the text at the same time while updating lock button.
|
||||
lockButton.findViewById(R.id.lock_text).setVisibility(expand ? VISIBLE : GONE);
|
||||
}
|
||||
});
|
||||
// Play the collapsing together of the stateAnimator to avoid being unable to scroll to the
|
||||
// header. Otherwise the smooth scrolling will scroll higher when played with the state
|
||||
// animator.
|
||||
if (!expand) {
|
||||
animatorSet.playTogether(animateCollapseAnimation(lockButton));
|
||||
}
|
||||
animatorSet.setDuration(EXPAND_COLLAPSE_DURATION);
|
||||
animatorSet.start();
|
||||
}
|
||||
|
||||
/** Change the settings gear alpha when expanded or collapsed. */
|
||||
private void updateSettingsGearAlpha(ImageButton settingsButton, boolean expand,
|
||||
PropertySetter setter) {
|
||||
float toAlpha = expand ? 1 : 0;
|
||||
setter.setFloat(settingsButton, VIEW_ALPHA, toAlpha, Interpolators.LINEAR)
|
||||
.setDuration(SETTINGS_OPACITY_DURATION).setStartDelay(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import static com.android.launcher3.allapps.UserProfileManager.STATE_TRANSITION;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -33,8 +34,11 @@ import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
@@ -59,7 +63,6 @@ public class PrivateSpaceHeaderViewControllerTest {
|
||||
private static final int PS_TRANSITION_IMAGE_COUNT = 1;
|
||||
|
||||
private Context mContext;
|
||||
private LayoutInflater mLayoutInflater;
|
||||
private PrivateSpaceHeaderViewController mPsHeaderViewController;
|
||||
private RelativeLayout mPsHeaderLayout;
|
||||
@Mock
|
||||
@@ -71,16 +74,15 @@ public class PrivateSpaceHeaderViewControllerTest {
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = new ActivityContextWrapper(getApplicationContext());
|
||||
mLayoutInflater = LayoutInflater.from(getApplicationContext());
|
||||
mPsHeaderViewController = new PrivateSpaceHeaderViewController(mAllApps,
|
||||
mPrivateProfileManager);
|
||||
mPsHeaderLayout = (RelativeLayout) mLayoutInflater.inflate(R.layout.private_space_header,
|
||||
null);
|
||||
mPsHeaderLayout = (RelativeLayout) LayoutInflater.from(mContext).inflate(
|
||||
R.layout.private_space_header, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void privateProfileDisabled_psHeaderContainsLockedView() throws Exception {
|
||||
Bitmap unlockButton = getBitmap(mContext.getDrawable(R.drawable.bg_ps_unlock_button));
|
||||
Bitmap unlockButton = getBitmap(mContext.getDrawable(R.drawable.ic_lock));
|
||||
when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED);
|
||||
|
||||
mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
|
||||
@@ -93,11 +95,15 @@ public class PrivateSpaceHeaderViewControllerTest {
|
||||
if (view.getId() == R.id.ps_container_header) {
|
||||
totalContainerHeaderView += 1;
|
||||
assertEquals(View.VISIBLE, view.getVisibility());
|
||||
} else if (view.getId() == R.id.ps_lock_unlock_button
|
||||
&& view instanceof ImageView imageView) {
|
||||
} else if (view.getId() == R.id.settingsAndLockGroup) {
|
||||
ImageView lockIcon = view.findViewById(R.id.lock_icon);
|
||||
assertTrue(getBitmap(lockIcon.getDrawable()).sameAs(unlockButton));
|
||||
assertEquals(View.VISIBLE, lockIcon.getVisibility());
|
||||
|
||||
// Verify textView shouldn't be showing when disabled.
|
||||
TextView lockText = view.findViewById(R.id.lock_text);
|
||||
assertEquals(View.GONE, lockText.getVisibility());
|
||||
totalLockUnlockButtonView += 1;
|
||||
assertEquals(View.VISIBLE, view.getVisibility());
|
||||
getBitmap(imageView.getDrawable()).sameAs(unlockButton);
|
||||
} else {
|
||||
assertEquals(View.GONE, view.getVisibility());
|
||||
}
|
||||
@@ -108,8 +114,8 @@ public class PrivateSpaceHeaderViewControllerTest {
|
||||
|
||||
@Test
|
||||
public void privateProfileEnabled_psHeaderContainsUnlockedView() throws Exception {
|
||||
Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_lock_button));
|
||||
Bitmap settingsImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_settings_button));
|
||||
Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.ic_lock));
|
||||
Bitmap settingsImage = getBitmap(mContext.getDrawable(R.drawable.ic_ps_settings));
|
||||
when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
|
||||
when(mPrivateProfileManager.isPrivateSpaceSettingsAvailable()).thenReturn(true);
|
||||
|
||||
@@ -124,16 +130,20 @@ public class PrivateSpaceHeaderViewControllerTest {
|
||||
if (view.getId() == R.id.ps_container_header) {
|
||||
totalContainerHeaderView += 1;
|
||||
assertEquals(View.VISIBLE, view.getVisibility());
|
||||
} else if (view.getId() == R.id.ps_lock_unlock_button
|
||||
&& view instanceof ImageView imageView) {
|
||||
totalLockUnlockButtonView += 1;
|
||||
assertEquals(View.VISIBLE, view.getVisibility());
|
||||
getBitmap(imageView.getDrawable()).sameAs(lockImage);
|
||||
} else if (view.getId() == R.id.ps_settings_button
|
||||
&& view instanceof ImageView imageView) {
|
||||
} else if (view.getId() == R.id.settingsAndLockGroup) {
|
||||
// Look for settings button.
|
||||
ImageButton settingsButton = view.findViewById(R.id.ps_settings_button);
|
||||
assertEquals(View.VISIBLE, settingsButton.getVisibility());
|
||||
totalSettingsImageView += 1;
|
||||
assertEquals(View.VISIBLE, view.getVisibility());
|
||||
getBitmap(imageView.getDrawable()).sameAs(settingsImage);
|
||||
assertTrue(getBitmap(settingsButton.getDrawable()).sameAs(settingsImage));
|
||||
|
||||
// Look for lock_icon and lock_text.
|
||||
ImageView lockIcon = view.findViewById(R.id.lock_icon);
|
||||
assertTrue(getBitmap(lockIcon.getDrawable()).sameAs(lockImage));
|
||||
assertEquals(View.VISIBLE, lockIcon.getVisibility());
|
||||
TextView lockText = view.findViewById(R.id.lock_text);
|
||||
assertEquals(View.VISIBLE, lockText.getVisibility());
|
||||
totalLockUnlockButtonView += 1;
|
||||
} else {
|
||||
assertEquals(View.GONE, view.getVisibility());
|
||||
}
|
||||
@@ -146,7 +156,7 @@ public class PrivateSpaceHeaderViewControllerTest {
|
||||
@Test
|
||||
public void privateProfileEnabledAndNoSettingsIntent_psHeaderContainsUnlockedView()
|
||||
throws Exception {
|
||||
Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_lock_button));
|
||||
Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.ic_lock));
|
||||
when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
|
||||
when(mPrivateProfileManager.isPrivateSpaceSettingsAvailable()).thenReturn(false);
|
||||
|
||||
@@ -161,11 +171,18 @@ public class PrivateSpaceHeaderViewControllerTest {
|
||||
if (view.getId() == R.id.ps_container_header) {
|
||||
totalContainerHeaderView += 1;
|
||||
assertEquals(View.VISIBLE, view.getVisibility());
|
||||
} else if (view.getId() == R.id.ps_lock_unlock_button
|
||||
&& view instanceof ImageView imageView) {
|
||||
} else if (view.getId() == R.id.settingsAndLockGroup) {
|
||||
// Ensure there is no settings button.
|
||||
ImageButton settingsImage = view.findViewById(R.id.ps_settings_button);
|
||||
assertEquals(View.GONE, settingsImage.getVisibility());
|
||||
|
||||
// Check lock icon and lock text is there.
|
||||
ImageView lockIcon = view.findViewById(R.id.lock_icon);
|
||||
assertTrue(getBitmap(lockIcon.getDrawable()).sameAs(lockImage));
|
||||
assertEquals(View.VISIBLE, lockIcon.getVisibility());
|
||||
TextView lockText = view.findViewById(R.id.lock_text);
|
||||
assertEquals(View.VISIBLE, lockText.getVisibility());
|
||||
totalLockUnlockButtonView += 1;
|
||||
assertEquals(View.VISIBLE, view.getVisibility());
|
||||
getBitmap(imageView.getDrawable()).sameAs(lockImage);
|
||||
} else {
|
||||
assertEquals(View.GONE, view.getVisibility());
|
||||
}
|
||||
@@ -194,7 +211,10 @@ public class PrivateSpaceHeaderViewControllerTest {
|
||||
&& view instanceof ImageView imageView) {
|
||||
totalLockUnlockButtonView += 1;
|
||||
assertEquals(View.VISIBLE, view.getVisibility());
|
||||
getBitmap(imageView.getDrawable()).sameAs(transitionImage);
|
||||
assertTrue(getBitmap(imageView.getDrawable()).sameAs(transitionImage));
|
||||
} else if (view.getId() == R.id.settingsAndLockGroup) {
|
||||
LinearLayout lockUnlockButton = view.findViewById(R.id.ps_lock_unlock_button);
|
||||
assertEquals(View.GONE, lockUnlockButton.getVisibility());
|
||||
} else {
|
||||
assertEquals(View.GONE, view.getVisibility());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user