Implement the work scheduler view and update colors of FAB
Update colors of the FAB to spec: https://www.figma.com/design/uMzPkNMZpb7EyfHDo8usIa/V-%E2%80%A2-Toast-Butter?node-id=3784-112229&node-type=instance&m=dev Exported the brief case icon from figma since it is not available on go/icons. Color of the brief case icon is onPrimary. Have place holder string in launcher but implement the actual string in NL. bug:361589193 Test - manual: video: https://drive.google.com/file/d/1CIs8qdtV1jUvbq57CcgAXDBPPHHX5CKJ/view?usp=sharing Flag: com.android.launcher3.work_scheduler_in_work_profile Change-Id: Ia98e9c4394f6ddfa7009653034929f9afbfeac8c
This commit is contained in:
@@ -26,6 +26,4 @@
|
||||
|
||||
<!-- Turn on work apps button -->
|
||||
<color name="work_turn_on_stroke">?attr/materialColorPrimary</color>
|
||||
<color name="work_fab_bg_color">?attr/materialColorPrimaryFixedDim</color>
|
||||
<color name="work_fab_icon_color">?attr/materialColorOnPrimaryFixed</color>
|
||||
</resources>
|
||||
@@ -94,6 +94,4 @@
|
||||
|
||||
<!-- Turn on work apps button -->
|
||||
<color name="work_turn_on_stroke">?attr/materialColorPrimary</color>
|
||||
<color name="work_fab_bg_color">?attr/materialColorPrimaryFixedDim</color>
|
||||
<color name="work_fab_icon_color">?attr/materialColorOnPrimaryFixed</color>
|
||||
</resources>
|
||||
@@ -16,9 +16,9 @@
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/textColorHint">
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v1.17L10.83,8L20,8v9.17l1.98,1.98c0,-0.05 0.02,-0.1 0.02,-0.16L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2zM19,19L8,8 6,6 2.81,2.81 1.39,4.22 3.3,6.13C2.54,6.41 2.01,7.14 2.01,8L2,19c0,1.11 0.89,2 2,2h14.17l1.61,1.61 1.41,-1.41 -0.37,-0.37L19,19zM4,19L4,8h1.17l11,11L4,19z" />
|
||||
</vector>
|
||||
android:pathData="M16,6H20C21.11,6 22,6.89 22,8V18.99C22,19.021 21.994,19.05 21.989,19.077C21.984,19.102 21.98,19.126 21.98,19.15L20,17.17V8H10.83L8,5.17V4C8,2.89 8.89,2 10,2H14C15.11,2 16,2.89 16,4V6ZM10,6H14V4H10V6ZM19,19L8,8L6,6L2.81,2.81L1.39,4.22L3.3,6.13C2.54,6.41 2.01,7.14 2.01,8L2,19C2,20.11 2.89,21 4,21H18.17L19.78,22.61L21.19,21.2L20.82,20.83L19,19ZM4,8V19H16.17L5.17,8H4Z"
|
||||
android:fillColor="?attr/materialColorOnPrimary"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="?attr/materialColorOnPrimary"
|
||||
android:pathData="M612,668L668,612L520,464L520,280L440,280L440,496L612,668ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880Z"/>
|
||||
</vector>
|
||||
@@ -18,7 +18,7 @@
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/work_fab_radius" />
|
||||
<solid android:color="@color/work_fab_bg_color" />
|
||||
<solid android:color="?attr/materialColorPrimary" />
|
||||
<padding
|
||||
android:left="@dimen/work_mode_fab_background_horizontal_padding"
|
||||
android:right="@dimen/work_mode_fab_background_horizontal_padding"/>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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.
|
||||
-->
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/accent_ripple_color">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/work_fab_radius" />
|
||||
<solid android:color="?attr/materialColorPrimary" />
|
||||
<padding
|
||||
android:padding="@dimen/work_scheduler_background_padding" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -12,11 +12,9 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<com.android.launcher3.allapps.WorkModeSwitch
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/work_mode_toggle"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_height="@dimen/work_fab_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:minHeight="@dimen/work_fab_height"
|
||||
@@ -31,7 +29,6 @@
|
||||
android:layout_marginVertical="@dimen/work_fab_icon_vertical_margin"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/ic_corp_off"
|
||||
android:tint="@color/work_fab_icon_color"
|
||||
android:layout_marginStart="@dimen/work_fab_icon_start_margin_expanded"
|
||||
android:scaleType="center"/>
|
||||
<TextView
|
||||
@@ -39,7 +36,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxWidth="@dimen/work_fab_width"
|
||||
android:textColor="@color/work_fab_icon_color"
|
||||
android:textColor="?attr/materialColorOnPrimary"
|
||||
android:textSize="14sp"
|
||||
android:includeFontPadding="false"
|
||||
android:textDirection="locale"
|
||||
@@ -48,4 +45,4 @@
|
||||
android:layout_marginEnd="@dimen/work_fab_text_end_margin"
|
||||
android:maxLines="1"
|
||||
style="@style/TextHeadline"/>
|
||||
</com.android.launcher3.allapps.WorkModeSwitch>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
<com.android.launcher3.allapps.WorkUtilityView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true">
|
||||
<ImageButton
|
||||
android:id="@+id/work_scheduler"
|
||||
android:layout_width="@dimen/work_scheduler_size"
|
||||
android:layout_height="@dimen/work_scheduler_size"
|
||||
android:layout_marginBottom="@dimen/work_scheduler_bottom_margin"
|
||||
android:contentDescription="@string/work_scheduler_button_content_description"
|
||||
android:src="@drawable/ic_schedule"
|
||||
android:layout_gravity="end"
|
||||
android:background="@drawable/work_scheduler_background" />
|
||||
<include layout="@layout/work_mode_fab" />
|
||||
</com.android.launcher3.allapps.WorkUtilityView>
|
||||
@@ -53,10 +53,5 @@
|
||||
<color name="widget_picker_add_button_text_color_dark">
|
||||
@android:color/system_accent1_800</color>
|
||||
|
||||
<color name="work_fab_bg_color">
|
||||
@android:color/system_accent1_200</color>
|
||||
<color name="work_fab_icon_color">
|
||||
@android:color/system_accent1_900</color>
|
||||
|
||||
<color name="material_color_on_surface">@android:color/system_neutral1_100</color>
|
||||
</resources>
|
||||
@@ -104,11 +104,6 @@
|
||||
<color name="widget_picker_add_button_text_color_light">
|
||||
@android:color/system_accent1_0</color>
|
||||
|
||||
<color name="work_fab_bg_color">
|
||||
@android:color/system_accent1_200</color>
|
||||
<color name="work_fab_icon_color">
|
||||
@android:color/system_accent1_900</color>
|
||||
|
||||
<color name="overview_foreground_scrim_color">@android:color/system_neutral1_1000</color>
|
||||
|
||||
<color name="material_color_on_surface">@android:color/system_neutral1_900</color>
|
||||
|
||||
@@ -98,8 +98,6 @@
|
||||
<color name="preload_icon_background_color_dark">#40484D</color>
|
||||
|
||||
<color name="work_turn_on_stroke">?android:attr/colorAccent</color>
|
||||
<color name="work_fab_bg_color">#A8C7FA</color>
|
||||
<color name="work_fab_icon_color">#041E49</color>
|
||||
|
||||
<color name="widget_picker_primary_surface_color_light">#EFEDED</color>
|
||||
<color name="widget_picker_secondary_surface_color_light">#FAF9F8</color>
|
||||
|
||||
@@ -166,6 +166,9 @@
|
||||
<dimen name="work_fab_margin">16dp</dimen>
|
||||
<dimen name="work_fab_margin_bottom">20dp</dimen>
|
||||
<dimen name="work_mode_fab_background_horizontal_padding">16dp</dimen>
|
||||
<dimen name="work_scheduler_background_padding">16dp</dimen>
|
||||
<dimen name="work_scheduler_bottom_margin">8dp</dimen>
|
||||
<dimen name="work_scheduler_size">56dp</dimen>
|
||||
<dimen name="work_profile_footer_padding">20dp</dimen>
|
||||
<dimen name="work_edu_card_margin">16dp</dimen>
|
||||
<dimen name="work_edu_card_radius">16dp</dimen>
|
||||
|
||||
@@ -465,6 +465,8 @@
|
||||
<string name="work_profile_edu_accept">Got it</string>
|
||||
<!-- Info icon unicode for alpha scroller when work edu card is present -->
|
||||
<string name="work_profile_edu_section" translatable="false">\u24D8</string>
|
||||
<!-- Intent for work profiler scheduler -->
|
||||
<string name="work_profile_scheduler_intent" translatable="false"/>
|
||||
|
||||
<!--- heading shown when user opens work apps tab while work apps are paused -->
|
||||
<string name="work_apps_paused_title">Work apps are paused</string>
|
||||
@@ -483,6 +485,8 @@
|
||||
<string name="work_apps_pause_btn_text">Pause work apps</string>
|
||||
<!-- button string shown enable work profile -->
|
||||
<string name="work_apps_enable_btn_text">Unpause</string>
|
||||
<!-- Label for the work profile scheduler button in the work profile screen. [CHAR_LIMIT=40] -->
|
||||
<string name="work_scheduler_button_content_description">Work apps schedule</string>
|
||||
|
||||
<!-- A hint shown in launcher settings develop options filter box -->
|
||||
<string name="developer_options_filter_hint">Filter</string>
|
||||
|
||||
@@ -726,7 +726,7 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
|
||||
post(() -> mAH.get(AdapterHolder.WORK).applyPadding());
|
||||
|
||||
} else {
|
||||
mWorkManager.detachWorkModeSwitch();
|
||||
mWorkManager.detachWorkUtilityViews();
|
||||
mViewPager = null;
|
||||
}
|
||||
|
||||
@@ -1257,8 +1257,8 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
|
||||
/** Called in Launcher#bindStringCache() to update the UI when cache is updated. */
|
||||
public void updateWorkUI() {
|
||||
setDeviceManagementResources();
|
||||
if (mWorkManager.getWorkModeSwitch() != null) {
|
||||
mWorkManager.getWorkModeSwitch().updateStringFromCache();
|
||||
if (mWorkManager.getWorkUtilityView() != null) {
|
||||
mWorkManager.getWorkUtilityView().updateStringFromCache();
|
||||
}
|
||||
inflateWorkCardsIfNeeded();
|
||||
}
|
||||
@@ -1581,8 +1581,8 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
|
||||
void applyPadding() {
|
||||
if (mRecyclerView != null) {
|
||||
int bottomOffset = 0;
|
||||
if (isWork() && mWorkManager.getWorkModeSwitch() != null) {
|
||||
bottomOffset = mInsets.bottom + mWorkManager.getWorkModeSwitch().getHeight();
|
||||
if (isWork() && mWorkManager.getWorkUtilityView() != null) {
|
||||
bottomOffset = mInsets.bottom + mWorkManager.getWorkUtilityView().getHeight();
|
||||
} else if (isMain() && mPrivateProfileManager != null) {
|
||||
Optional<AdapterItem> privateSpaceHeaderItem = mAppsList.getAdapterItems()
|
||||
.stream()
|
||||
|
||||
@@ -58,7 +58,7 @@ public class WorkProfileManager extends UserProfileManager
|
||||
implements PersonalWorkSlidingTabStrip.OnActivePageChangedListener {
|
||||
private static final String TAG = "WorkProfileManager";
|
||||
private final ActivityAllAppsContainerView<?> mAllApps;
|
||||
private WorkModeSwitch mWorkModeSwitch;
|
||||
private WorkUtilityView mWorkUtilityView;
|
||||
private final Predicate<UserHandle> mWorkProfileMatcher;
|
||||
|
||||
public WorkProfileManager(
|
||||
@@ -79,15 +79,15 @@ public class WorkProfileManager extends UserProfileManager
|
||||
|
||||
@Override
|
||||
public void onActivePageChanged(int page) {
|
||||
updateWorkFAB(page);
|
||||
updateWorkUtilityViews(page);
|
||||
}
|
||||
|
||||
private void updateWorkFAB(int page) {
|
||||
if (mWorkModeSwitch != null) {
|
||||
private void updateWorkUtilityViews(int page) {
|
||||
if (mWorkUtilityView != null) {
|
||||
if (page == MAIN || page == SEARCH) {
|
||||
mWorkModeSwitch.animateVisibility(false);
|
||||
mWorkUtilityView.animateVisibility(false);
|
||||
} else if (page == WORK && getCurrentState() == STATE_ENABLED) {
|
||||
mWorkModeSwitch.animateVisibility(true);
|
||||
mWorkUtilityView.animateVisibility(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,10 +104,10 @@ public class WorkProfileManager extends UserProfileManager
|
||||
}
|
||||
boolean isEnabled = !mAllApps.getAppsStore().hasModelFlag(quietModeFlag);
|
||||
updateCurrentState(isEnabled ? STATE_ENABLED : STATE_DISABLED);
|
||||
if (mWorkModeSwitch != null) {
|
||||
if (mWorkUtilityView != null) {
|
||||
// reset the position of the button and clear IME insets.
|
||||
mWorkModeSwitch.getImeInsets().setEmpty();
|
||||
mWorkModeSwitch.updateTranslationY();
|
||||
mWorkUtilityView.getImeInsets().setEmpty();
|
||||
mWorkUtilityView.updateTranslationY();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,54 +116,54 @@ public class WorkProfileManager extends UserProfileManager
|
||||
if (getAH() != null) {
|
||||
getAH().mAppsList.updateAdapterItems();
|
||||
}
|
||||
if (mWorkModeSwitch != null) {
|
||||
updateWorkFAB(mAllApps.getCurrentPage());
|
||||
if (mWorkUtilityView != null) {
|
||||
updateWorkUtilityViews(mAllApps.getCurrentPage());
|
||||
}
|
||||
if (getCurrentState() == STATE_ENABLED) {
|
||||
attachWorkModeSwitch();
|
||||
attachWorkUtilityViews();
|
||||
} else if (getCurrentState() == STATE_DISABLED) {
|
||||
detachWorkModeSwitch();
|
||||
detachWorkUtilityViews();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and attaches for profile toggle button to {@link ActivityAllAppsContainerView}
|
||||
*/
|
||||
public boolean attachWorkModeSwitch() {
|
||||
public boolean attachWorkUtilityViews() {
|
||||
if (!mAllApps.getAppsStore().hasModelFlag(
|
||||
FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION)) {
|
||||
Log.e(TAG, "unable to attach work mode switch; Missing required permissions");
|
||||
return false;
|
||||
}
|
||||
if (mWorkModeSwitch == null) {
|
||||
mWorkModeSwitch = (WorkModeSwitch) mAllApps.getLayoutInflater().inflate(
|
||||
R.layout.work_mode_fab, mAllApps, false);
|
||||
if (mWorkUtilityView == null) {
|
||||
mWorkUtilityView = (WorkUtilityView) mAllApps.getLayoutInflater().inflate(
|
||||
R.layout.work_mode_utility_view, mAllApps, false);
|
||||
}
|
||||
if (mWorkModeSwitch.getParent() == null) {
|
||||
mAllApps.addView(mWorkModeSwitch);
|
||||
if (mWorkUtilityView.getParent() == null) {
|
||||
mAllApps.addView(mWorkUtilityView);
|
||||
}
|
||||
if (mAllApps.getCurrentPage() != WORK) {
|
||||
mWorkModeSwitch.animateVisibility(false);
|
||||
mWorkUtilityView.animateVisibility(false);
|
||||
}
|
||||
if (getAH() != null) {
|
||||
getAH().applyPadding();
|
||||
}
|
||||
mWorkModeSwitch.setOnClickListener(this::onWorkFabClicked);
|
||||
mWorkUtilityView.setOnClickListener(this::onWorkFabClicked);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Removes work profile toggle button from {@link ActivityAllAppsContainerView}
|
||||
*/
|
||||
public void detachWorkModeSwitch() {
|
||||
if (mWorkModeSwitch != null && mWorkModeSwitch.getParent() == mAllApps) {
|
||||
mAllApps.removeView(mWorkModeSwitch);
|
||||
public void detachWorkUtilityViews() {
|
||||
if (mWorkUtilityView != null && mWorkUtilityView.getParent() == mAllApps) {
|
||||
mAllApps.removeView(mWorkUtilityView);
|
||||
}
|
||||
mWorkModeSwitch = null;
|
||||
mWorkUtilityView = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public WorkModeSwitch getWorkModeSwitch() {
|
||||
return mWorkModeSwitch;
|
||||
public WorkUtilityView getWorkUtilityView() {
|
||||
return mWorkUtilityView;
|
||||
}
|
||||
|
||||
private ActivityAllAppsContainerView.AdapterHolder getAH() {
|
||||
@@ -199,7 +199,7 @@ public class WorkProfileManager extends UserProfileManager
|
||||
}
|
||||
|
||||
private void onWorkFabClicked(View view) {
|
||||
if (getCurrentState() == STATE_ENABLED && mWorkModeSwitch.isEnabled()) {
|
||||
if (getCurrentState() == STATE_ENABLED && mWorkUtilityView.isEnabled()) {
|
||||
logEvents(LAUNCHER_TURN_OFF_WORK_APPS_TAP);
|
||||
setWorkProfileEnabled(false);
|
||||
}
|
||||
@@ -216,7 +216,7 @@ public class WorkProfileManager extends UserProfileManager
|
||||
}
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
WorkModeSwitch fab = getWorkModeSwitch();
|
||||
WorkUtilityView fab = getWorkUtilityView();
|
||||
if (fab == null){
|
||||
return;
|
||||
}
|
||||
|
||||
+110
-29
@@ -21,11 +21,13 @@ import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
@@ -36,6 +38,7 @@ import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.Insettable;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
@@ -43,10 +46,13 @@ import com.android.launcher3.anim.AnimatedPropertySetter;
|
||||
import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
|
||||
import com.android.launcher3.model.StringCache;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Work profile toggle switch shown at the bottom of AllApps work tab
|
||||
* Work profile utility ViewGroup that is shown at the bottom of AllApps work tab
|
||||
*/
|
||||
public class WorkModeSwitch extends LinearLayout implements Insettable,
|
||||
public class WorkUtilityView extends LinearLayout implements Insettable,
|
||||
KeyboardInsetAnimationCallback.KeyboardInsetListener {
|
||||
|
||||
private static final int TEXT_EXPAND_OPACITY_DURATION = 300;
|
||||
@@ -54,10 +60,14 @@ public class WorkModeSwitch extends LinearLayout implements Insettable,
|
||||
private static final int EXPAND_COLLAPSE_DURATION = 300;
|
||||
private static final int TEXT_ALPHA_EXPAND_DELAY = 80;
|
||||
private static final int TEXT_ALPHA_COLLAPSE_DELAY = 0;
|
||||
private static final int WORK_SCHEDULER_OPACITY_DURATION =
|
||||
(int) (EXPAND_COLLAPSE_DURATION * 0.75f);
|
||||
private static final int FLAG_FADE_ONGOING = 1 << 1;
|
||||
private static final int FLAG_TRANSLATION_ONGOING = 1 << 2;
|
||||
private static final int FLAG_IS_EXPAND = 1 << 3;
|
||||
private static final int SCROLL_THRESHOLD_DP = 10;
|
||||
private static final float WORK_SCHEDULER_SCALE_MIN = 0.25f;
|
||||
private static final float WORK_SCHEDULER_SCALE_MAX = 1f;
|
||||
|
||||
private final Rect mInsets = new Rect();
|
||||
private final Rect mImeInsets = new Rect();
|
||||
@@ -67,22 +77,25 @@ public class WorkModeSwitch extends LinearLayout implements Insettable,
|
||||
private final int mTextMarginStart;
|
||||
private final int mTextMarginEnd;
|
||||
private final int mIconMarginStart;
|
||||
private final String mWorkSchedulerIntentAction;
|
||||
|
||||
// Threshold when user scrolls up/down to determine when should button extend/collapse
|
||||
private final int mScrollThreshold;
|
||||
private TextView mTextView;
|
||||
private ImageView mIcon;
|
||||
private ValueAnimator mPauseFABAnim;
|
||||
private TextView mPauseText;
|
||||
private ImageView mWorkIcon;
|
||||
private ImageButton mSchedulerButton;
|
||||
|
||||
public WorkModeSwitch(@NonNull Context context) {
|
||||
public WorkUtilityView(@NonNull Context context) {
|
||||
this(context, null, 0);
|
||||
}
|
||||
|
||||
public WorkModeSwitch(@NonNull Context context, @NonNull AttributeSet attrs) {
|
||||
public WorkUtilityView(@NonNull Context context, @NonNull AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public WorkModeSwitch(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
|
||||
public WorkUtilityView(@NonNull Context context, @NonNull AttributeSet attrs,
|
||||
int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mContext = context;
|
||||
mScrollThreshold = Utilities.dpToPx(SCROLL_THRESHOLD_DP);
|
||||
@@ -93,14 +106,17 @@ public class WorkModeSwitch extends LinearLayout implements Insettable,
|
||||
R.dimen.work_fab_text_end_margin);
|
||||
mIconMarginStart = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.work_fab_icon_start_margin_expanded);
|
||||
mWorkSchedulerIntentAction = mContext.getResources().getString(
|
||||
R.string.work_profile_scheduler_intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
mTextView = findViewById(R.id.pause_text);
|
||||
mIcon = findViewById(R.id.work_icon);
|
||||
mPauseText = findViewById(R.id.pause_text);
|
||||
mWorkIcon = findViewById(R.id.work_icon);
|
||||
mSchedulerButton = findViewById(R.id.work_scheduler);
|
||||
setSelected(true);
|
||||
KeyboardInsetAnimationCallback keyboardInsetAnimationCallback =
|
||||
new KeyboardInsetAnimationCallback(this);
|
||||
@@ -109,6 +125,12 @@ public class WorkModeSwitch extends LinearLayout implements Insettable,
|
||||
addFlag(FLAG_IS_EXPAND);
|
||||
setInsets(mActivityContext.getDeviceProfile().getInsets());
|
||||
updateStringFromCache();
|
||||
mSchedulerButton.setVisibility(GONE);
|
||||
if (shouldUseScheduler()) {
|
||||
mSchedulerButton.setVisibility(VISIBLE);
|
||||
mSchedulerButton.setOnClickListener(view ->
|
||||
mContext.startActivity(new Intent(mWorkSchedulerIntentAction)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -183,15 +205,63 @@ public class WorkModeSwitch extends LinearLayout implements Insettable,
|
||||
super.setTranslationY(Math.min(translationY, -mInsets.bottom));
|
||||
}
|
||||
|
||||
private ValueAnimator animateSchedulerScale(boolean isExpanding) {
|
||||
float scaleFrom = isExpanding ? WORK_SCHEDULER_SCALE_MIN : WORK_SCHEDULER_SCALE_MAX;
|
||||
float scaleTo = isExpanding ? WORK_SCHEDULER_SCALE_MAX : WORK_SCHEDULER_SCALE_MIN;
|
||||
ValueAnimator schedulerScaleAnim = ObjectAnimator.ofFloat(scaleFrom, scaleTo);
|
||||
schedulerScaleAnim.setDuration(EXPAND_COLLAPSE_DURATION);
|
||||
schedulerScaleAnim.setInterpolator(Interpolators.STANDARD);
|
||||
schedulerScaleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
float scale = (float) valueAnimator.getAnimatedValue();
|
||||
mSchedulerButton.setScaleX(scale);
|
||||
mSchedulerButton.setScaleY(scale);
|
||||
}
|
||||
});
|
||||
schedulerScaleAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
if (isExpanding) {
|
||||
mSchedulerButton.setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void animatePillTransition(boolean isExpanding) {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (!isExpanding) {
|
||||
mSchedulerButton.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
return schedulerScaleAnim;
|
||||
}
|
||||
|
||||
private ValueAnimator animateSchedulerAlpha(boolean isExpanding) {
|
||||
float alphaFrom = isExpanding ? 0 : 1;
|
||||
float alphaTo = isExpanding ? 1 : 0;
|
||||
ValueAnimator schedulerAlphaAnim = ObjectAnimator.ofFloat(alphaFrom, alphaTo);
|
||||
schedulerAlphaAnim.setDuration(WORK_SCHEDULER_OPACITY_DURATION);
|
||||
schedulerAlphaAnim.setStartDelay(isExpanding ? 0 :
|
||||
EXPAND_COLLAPSE_DURATION - WORK_SCHEDULER_OPACITY_DURATION);
|
||||
schedulerAlphaAnim.setInterpolator(Interpolators.STANDARD);
|
||||
schedulerAlphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
mSchedulerButton.setAlpha((float) valueAnimator.getAnimatedValue());
|
||||
}
|
||||
});
|
||||
return schedulerAlphaAnim;
|
||||
}
|
||||
|
||||
private void animateWorkUtilityViews(boolean isExpanding) {
|
||||
if (!shouldAnimate(isExpanding)) {
|
||||
return;
|
||||
}
|
||||
AnimatorSet animatorSet = new AnimatedPropertySetter().buildAnim();
|
||||
mTextView.measure(0,0);
|
||||
int currentWidth = mTextView.getWidth();
|
||||
int fullWidth = mTextView.getMeasuredWidth();
|
||||
mPauseText.measure(0,0);
|
||||
int currentWidth = mPauseText.getWidth();
|
||||
int fullWidth = mPauseText.getMeasuredWidth();
|
||||
float from = isExpanding ? 0 : currentWidth;
|
||||
float to = isExpanding ? fullWidth : 0;
|
||||
mPauseFABAnim = ObjectAnimator.ofFloat(from, to);
|
||||
@@ -203,15 +273,15 @@ public class WorkModeSwitch extends LinearLayout implements Insettable,
|
||||
float translation = (float) valueAnimator.getAnimatedValue();
|
||||
float translationFraction = translation / fullWidth;
|
||||
ViewGroup.MarginLayoutParams textViewLayoutParams =
|
||||
(ViewGroup.MarginLayoutParams) mTextView.getLayoutParams();
|
||||
(ViewGroup.MarginLayoutParams) mPauseText.getLayoutParams();
|
||||
textViewLayoutParams.width = (int) translation;
|
||||
textViewLayoutParams.setMarginStart((int) (mTextMarginStart * translationFraction));
|
||||
textViewLayoutParams.setMarginEnd((int) (mTextMarginEnd * translationFraction));
|
||||
mTextView.setLayoutParams(textViewLayoutParams);
|
||||
mPauseText.setLayoutParams(textViewLayoutParams);
|
||||
ViewGroup.MarginLayoutParams iconLayoutParams =
|
||||
(ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
|
||||
(ViewGroup.MarginLayoutParams) mWorkIcon.getLayoutParams();
|
||||
iconLayoutParams.setMarginStart((int) (mIconMarginStart * translationFraction));
|
||||
mIcon.setLayoutParams(iconLayoutParams);
|
||||
mWorkIcon.setLayoutParams(iconLayoutParams);
|
||||
}
|
||||
});
|
||||
mPauseFABAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@@ -220,21 +290,28 @@ public class WorkModeSwitch extends LinearLayout implements Insettable,
|
||||
if (isExpanding) {
|
||||
addFlag(FLAG_IS_EXPAND);
|
||||
} else {
|
||||
mTextView.setVisibility(GONE);
|
||||
mPauseText.setVisibility(GONE);
|
||||
removeFlag(FLAG_IS_EXPAND);
|
||||
}
|
||||
mTextView.setHorizontallyScrolling(false);
|
||||
mTextView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
mPauseText.setHorizontallyScrolling(false);
|
||||
mPauseText.setEllipsize(TextUtils.TruncateAt.END);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
mTextView.setHorizontallyScrolling(true);
|
||||
mTextView.setVisibility(VISIBLE);
|
||||
mTextView.setEllipsize(null);
|
||||
mPauseText.setHorizontallyScrolling(true);
|
||||
mPauseText.setVisibility(VISIBLE);
|
||||
mPauseText.setEllipsize(null);
|
||||
}
|
||||
});
|
||||
animatorSet.playTogether(mPauseFABAnim, updatePauseTextAlpha(isExpanding));
|
||||
ArrayList<Animator> animatorList = new ArrayList<>();
|
||||
animatorList.add(mPauseFABAnim);
|
||||
animatorList.add(updatePauseTextAlpha(isExpanding));
|
||||
if (shouldUseScheduler()) {
|
||||
animatorList.add(animateSchedulerScale(isExpanding));
|
||||
animatorList.add(animateSchedulerAlpha(isExpanding));
|
||||
}
|
||||
animatorSet.playTogether(animatorList);
|
||||
animatorSet.start();
|
||||
}
|
||||
|
||||
@@ -250,7 +327,7 @@ public class WorkModeSwitch extends LinearLayout implements Insettable,
|
||||
alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
mTextView.setAlpha((float) valueAnimator.getAnimatedValue());
|
||||
mPauseText.setAlpha((float) valueAnimator.getAnimatedValue());
|
||||
}
|
||||
});
|
||||
return alphaAnim;
|
||||
@@ -287,11 +364,11 @@ public class WorkModeSwitch extends LinearLayout implements Insettable,
|
||||
}
|
||||
|
||||
public void extend() {
|
||||
animatePillTransition(true);
|
||||
animateWorkUtilityViews(true);
|
||||
}
|
||||
|
||||
public void shrink(){
|
||||
animatePillTransition(false);
|
||||
public void shrink() {
|
||||
animateWorkUtilityViews(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -310,7 +387,11 @@ public class WorkModeSwitch extends LinearLayout implements Insettable,
|
||||
public void updateStringFromCache(){
|
||||
StringCache cache = mActivityContext.getStringCache();
|
||||
if (cache != null) {
|
||||
mTextView.setText(cache.workProfilePauseButton);
|
||||
mPauseText.setText(cache.workProfilePauseButton);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldUseScheduler() {
|
||||
return Flags.workSchedulerInWorkProfile() && !mWorkSchedulerIntentAction.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -158,16 +158,16 @@ public class TaplWorkProfileTest extends AbstractLauncherUiTest<Launcher> {
|
||||
|
||||
|
||||
waitForLauncherCondition("work profile initial state check failed", launcher ->
|
||||
manager.getWorkModeSwitch() != null
|
||||
manager.getWorkUtilityView() != null
|
||||
&& manager.getCurrentState() == WorkProfileManager.STATE_ENABLED
|
||||
&& manager.getWorkModeSwitch().isEnabled(),
|
||||
&& manager.getWorkUtilityView().isEnabled(),
|
||||
LauncherInstrumentation.WAIT_TIME_MS);
|
||||
|
||||
//start work profile toggle OFF test
|
||||
executeOnLauncher(l -> {
|
||||
// Ensure updates are not deferred so notification happens when apps pause.
|
||||
l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
|
||||
l.getAppsView().getWorkManager().getWorkModeSwitch().performClick();
|
||||
l.getAppsView().getWorkManager().getWorkUtilityView().performClick();
|
||||
});
|
||||
|
||||
waitForLauncherCondition("Work profile toggle OFF failed", launcher -> {
|
||||
|
||||
Reference in New Issue
Block a user