1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.allapps;
17 
18 import android.content.Context;
19 import android.graphics.Rect;
20 import android.util.AttributeSet;
21 import android.view.WindowInsets;
22 import android.widget.LinearLayout;
23 import android.widget.TextView;
24 
25 import androidx.annotation.NonNull;
26 import androidx.core.graphics.Insets;
27 import androidx.core.view.WindowInsetsCompat;
28 
29 import com.android.launcher3.DeviceProfile;
30 import com.android.launcher3.Insettable;
31 import com.android.launcher3.R;
32 import com.android.launcher3.Utilities;
33 import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
34 import com.android.launcher3.model.StringCache;
35 import com.android.launcher3.views.ActivityContext;
36 /**
37  * Work profile toggle switch shown at the bottom of AllApps work tab
38  */
39 public class WorkModeSwitch extends LinearLayout implements Insettable,
40         KeyboardInsetAnimationCallback.KeyboardInsetListener {
41 
42     private static final int FLAG_FADE_ONGOING = 1 << 1;
43     private static final int FLAG_TRANSLATION_ONGOING = 1 << 2;
44     private static final int FLAG_PROFILE_TOGGLE_ONGOING = 1 << 3;
45     private static final int SCROLL_THRESHOLD_DP = 10;
46 
47     private final Rect mInsets = new Rect();
48     private final Rect mImeInsets = new Rect();
49     private int mFlags;
50     private final ActivityContext mActivityContext;
51     private final Context mContext;
52 
53     // Threshold when user scrolls up/down to determine when should button extend/collapse
54     private final int mScrollThreshold;
55     private TextView mTextView;
56 
57 
WorkModeSwitch(@onNull Context context)58     public WorkModeSwitch(@NonNull Context context) {
59         this(context, null, 0);
60     }
61 
WorkModeSwitch(@onNull Context context, @NonNull AttributeSet attrs)62     public WorkModeSwitch(@NonNull Context context, @NonNull AttributeSet attrs) {
63         this(context, attrs, 0);
64     }
65 
WorkModeSwitch(@onNull Context context, @NonNull AttributeSet attrs, int defStyleAttr)66     public WorkModeSwitch(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
67         super(context, attrs, defStyleAttr);
68         mContext = context;
69         mScrollThreshold = Utilities.dpToPx(SCROLL_THRESHOLD_DP);
70         mActivityContext = ActivityContext.lookupContext(getContext());
71     }
72 
73     @Override
onFinishInflate()74     protected void onFinishInflate() {
75         super.onFinishInflate();
76 
77         mTextView = findViewById(R.id.pause_text);
78         setSelected(true);
79         KeyboardInsetAnimationCallback keyboardInsetAnimationCallback =
80                 new KeyboardInsetAnimationCallback(this);
81         setWindowInsetsAnimationCallback(keyboardInsetAnimationCallback);
82 
83         setInsets(mActivityContext.getDeviceProfile().getInsets());
84         updateStringFromCache();
85     }
86 
87     @Override
setInsets(Rect insets)88     public void setInsets(Rect insets) {
89         mInsets.set(insets);
90         updateTranslationY();
91         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
92         if (lp != null) {
93             int bottomMargin = getResources().getDimensionPixelSize(R.dimen.work_fab_margin_bottom);
94             DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
95             if (mActivityContext.getAppsView().isSearchBarFloating()) {
96                 bottomMargin += dp.hotseatQsbHeight;
97             }
98 
99             if (!dp.isGestureMode && dp.isTaskbarPresent) {
100                 bottomMargin += dp.taskbarHeight;
101             }
102 
103             lp.bottomMargin = bottomMargin;
104         }
105     }
106 
107     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)108     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
109         super.onLayout(changed, left, top, right, bottom);
110         boolean isRtl = Utilities.isRtl(getResources());
111         int shift = mActivityContext.getDeviceProfile().getAllAppsIconStartMargin(mContext);
112         setTranslationX(isRtl ? shift : -shift);
113     }
114 
115     @Override
isEnabled()116     public boolean isEnabled() {
117         return super.isEnabled() && getVisibility() == VISIBLE && mFlags == 0;
118     }
119 
animateVisibility(boolean visible)120     public void animateVisibility(boolean visible) {
121         clearAnimation();
122         if (visible) {
123             setFlag(FLAG_FADE_ONGOING);
124             setVisibility(VISIBLE);
125             extend();
126             animate().alpha(1).withEndAction(() -> removeFlag(FLAG_FADE_ONGOING)).start();
127         } else if (getVisibility() != GONE) {
128             setFlag(FLAG_FADE_ONGOING);
129             animate().alpha(0).withEndAction(() -> {
130                 removeFlag(FLAG_FADE_ONGOING);
131                 setVisibility(GONE);
132             }).start();
133         }
134     }
135 
136     @Override
onApplyWindowInsets(WindowInsets insets)137     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
138         WindowInsetsCompat windowInsetsCompat =
139                 WindowInsetsCompat.toWindowInsetsCompat(insets, this);
140         if (windowInsetsCompat.isVisible(WindowInsetsCompat.Type.ime())) {
141             setInsets(mImeInsets, windowInsetsCompat.getInsets(WindowInsetsCompat.Type.ime()));
142         } else {
143             mImeInsets.setEmpty();
144         }
145         updateTranslationY();
146         return super.onApplyWindowInsets(insets);
147     }
148 
updateTranslationY()149     void updateTranslationY() {
150         setTranslationY(-mImeInsets.bottom);
151     }
152 
153     @Override
setTranslationY(float translationY)154     public void setTranslationY(float translationY) {
155         // Always translate at least enough for nav bar insets.
156         super.setTranslationY(Math.min(translationY, -mInsets.bottom));
157     }
158 
setInsets(Rect rect, Insets insets)159     private void setInsets(Rect rect, Insets insets) {
160         rect.set(insets.left, insets.top, insets.right, insets.bottom);
161     }
162 
getImeInsets()163     public Rect getImeInsets() {
164         return mImeInsets;
165     }
166 
167     @Override
onTranslationStart()168     public void onTranslationStart() {
169         setFlag(FLAG_TRANSLATION_ONGOING);
170     }
171 
172     @Override
onTranslationEnd()173     public void onTranslationEnd() {
174         removeFlag(FLAG_TRANSLATION_ONGOING);
175     }
176 
setFlag(int flag)177     private void setFlag(int flag) {
178         mFlags |= flag;
179     }
180 
removeFlag(int flag)181     private void removeFlag(int flag) {
182         mFlags &= ~flag;
183     }
184 
extend()185     public void extend() {
186         mTextView.setVisibility(VISIBLE);
187     }
188 
shrink()189     public void shrink(){
190         mTextView.setVisibility(GONE);
191     }
192 
getScrollThreshold()193     public int getScrollThreshold() {
194         return mScrollThreshold;
195     }
196 
updateStringFromCache()197     public void updateStringFromCache(){
198         StringCache cache = mActivityContext.getStringCache();
199         if (cache != null) {
200             mTextView.setText(cache.workProfilePauseButton);
201         }
202     }
203 }
204