• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP;
19 import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
20 import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
21 import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.WORK;
22 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD;
23 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD;
24 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TURN_OFF_WORK_APPS_TAP;
25 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
26 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
27 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
28 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
29 
30 import android.os.Build;
31 import android.os.Process;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.util.Log;
35 import android.view.View;
36 
37 import androidx.annotation.IntDef;
38 import androidx.annotation.NonNull;
39 import androidx.annotation.Nullable;
40 import androidx.annotation.RequiresApi;
41 import androidx.recyclerview.widget.RecyclerView;
42 
43 import com.android.launcher3.LauncherPrefs;
44 import com.android.launcher3.R;
45 import com.android.launcher3.Utilities;
46 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
47 import com.android.launcher3.logging.StatsLogManager;
48 import com.android.launcher3.model.data.ItemInfo;
49 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
50 
51 import java.lang.annotation.Retention;
52 import java.lang.annotation.RetentionPolicy;
53 import java.util.ArrayList;
54 import java.util.function.Predicate;
55 
56 /**
57  * Companion class for {@link ActivityAllAppsContainerView} to manage work tab and personal tab
58  * related
59  * logic based on {@link WorkProfileState}?
60  */
61 public class WorkProfileManager implements PersonalWorkSlidingTabStrip.OnActivePageChangedListener {
62     private static final String TAG = "WorkProfileManager";
63 
64     public static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
65 
66     public static final int STATE_ENABLED = 1;
67     public static final int STATE_DISABLED = 2;
68     public static final int STATE_TRANSITION = 3;
69 
70     /**
71      * Work profile manager states
72      */
73     @IntDef(value = {
74             STATE_ENABLED,
75             STATE_DISABLED,
76             STATE_TRANSITION
77     })
78     @Retention(RetentionPolicy.SOURCE)
79     public @interface WorkProfileState { }
80 
81     private final UserManager mUserManager;
82     private final ActivityAllAppsContainerView<?> mAllApps;
83     private final Predicate<ItemInfo> mMatcher;
84     private final StatsLogManager mStatsLogManager;
85 
86     private WorkModeSwitch mWorkModeSwitch;
87 
88     @WorkProfileState
89     private int mCurrentState;
90 
WorkProfileManager( UserManager userManager, ActivityAllAppsContainerView allApps, StatsLogManager statsLogManager)91     public WorkProfileManager(
92             UserManager userManager, ActivityAllAppsContainerView allApps,
93             StatsLogManager statsLogManager) {
94         mUserManager = userManager;
95         mAllApps = allApps;
96         mMatcher = mAllApps.mPersonalMatcher.negate();
97         mStatsLogManager = statsLogManager;
98     }
99 
100     /**
101      * Posts quite mode enable/disable call for work profile user
102      */
103     @RequiresApi(Build.VERSION_CODES.P)
setWorkProfileEnabled(boolean enabled)104     public void setWorkProfileEnabled(boolean enabled) {
105         updateCurrentState(STATE_TRANSITION);
106         UI_HELPER_EXECUTOR.post(() -> {
107             for (UserHandle userProfile : mUserManager.getUserProfiles()) {
108                 if (Process.myUserHandle().equals(userProfile)) {
109                     continue;
110                 }
111                 mUserManager.requestQuietModeEnabled(!enabled, userProfile);
112             }
113         });
114     }
115 
116     @Override
onActivePageChanged(int page)117     public void onActivePageChanged(int page) {
118         updateWorkFAB(page);
119     }
120 
updateWorkFAB(int page)121     private void updateWorkFAB(int page) {
122         if (mWorkModeSwitch != null) {
123             if (page == MAIN || page == SEARCH) {
124                 mWorkModeSwitch.animateVisibility(false);
125             } else if (page == WORK && mCurrentState == STATE_ENABLED) {
126                 mWorkModeSwitch.animateVisibility(true);
127             }
128         }
129     }
130 
131     /**
132      * Requests work profile state from {@link AllAppsStore} and updates work profile related views
133      */
reset()134     public void reset() {
135         boolean isEnabled = !mAllApps.getAppsStore().hasModelFlag(FLAG_QUIET_MODE_ENABLED);
136         updateCurrentState(isEnabled ? STATE_ENABLED : STATE_DISABLED);
137         if (mWorkModeSwitch != null) {
138             // reset the position of the button and clear IME insets.
139             mWorkModeSwitch.getImeInsets().setEmpty();
140             mWorkModeSwitch.updateTranslationY();
141         }
142     }
143 
updateCurrentState(@orkProfileState int currentState)144     private void updateCurrentState(@WorkProfileState int currentState) {
145         mCurrentState = currentState;
146         if (getAH() != null) {
147             getAH().mAppsList.updateAdapterItems();
148         }
149         if (mWorkModeSwitch != null) {
150             updateWorkFAB(mAllApps.getCurrentPage());
151         }
152         if (mCurrentState == STATE_ENABLED) {
153             attachWorkModeSwitch();
154         } else if (mCurrentState == STATE_DISABLED) {
155             detachWorkModeSwitch();
156         }
157     }
158 
159     /**
160      * Creates and attaches for profile toggle button to {@link ActivityAllAppsContainerView}
161      */
attachWorkModeSwitch()162     public boolean attachWorkModeSwitch() {
163         if (!mAllApps.getAppsStore().hasModelFlag(
164                 FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION)) {
165             Log.e(TAG, "unable to attach work mode switch; Missing required permissions");
166             return false;
167         }
168         if (mWorkModeSwitch == null) {
169             mWorkModeSwitch = (WorkModeSwitch) mAllApps.getLayoutInflater().inflate(
170                     R.layout.work_mode_fab, mAllApps, false);
171         }
172         if (mWorkModeSwitch.getParent() == null) {
173             mAllApps.addView(mWorkModeSwitch);
174         }
175         if (mAllApps.getCurrentPage() != WORK) {
176             mWorkModeSwitch.animateVisibility(false);
177         }
178         if (getAH() != null) {
179             getAH().applyPadding();
180         }
181         mWorkModeSwitch.setOnClickListener(this::onWorkFabClicked);
182         return true;
183     }
184     /**
185      * Removes work profile toggle button from {@link ActivityAllAppsContainerView}
186      */
detachWorkModeSwitch()187     public void detachWorkModeSwitch() {
188         if (mWorkModeSwitch != null && mWorkModeSwitch.getParent() == mAllApps) {
189             mAllApps.removeView(mWorkModeSwitch);
190         }
191         mWorkModeSwitch = null;
192     }
193 
getMatcher()194     public Predicate<ItemInfo> getMatcher() {
195         return mMatcher;
196     }
197 
198     @Nullable
getWorkModeSwitch()199     public WorkModeSwitch getWorkModeSwitch() {
200         return mWorkModeSwitch;
201     }
202 
getAH()203     private ActivityAllAppsContainerView.AdapterHolder getAH() {
204         return mAllApps.mAH.get(WORK);
205     }
206 
getCurrentState()207     public int getCurrentState() {
208         return mCurrentState;
209     }
210 
211     /**
212      * returns whether or not work apps should be visible in work tab.
213      */
shouldShowWorkApps()214     public boolean shouldShowWorkApps() {
215         return mCurrentState != WorkProfileManager.STATE_DISABLED;
216     }
217 
218     /**
219      * Adds work profile specific adapter items to adapterItems and returns number of items added
220      */
addWorkItems(ArrayList<AdapterItem> adapterItems)221     public int addWorkItems(ArrayList<AdapterItem> adapterItems) {
222         if (mCurrentState == WorkProfileManager.STATE_DISABLED) {
223             //add disabled card here.
224             adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_DISABLED_CARD));
225         } else if (mCurrentState == WorkProfileManager.STATE_ENABLED && !isEduSeen()) {
226             adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_EDU_CARD));
227         }
228         return adapterItems.size();
229     }
230 
isEduSeen()231     private boolean isEduSeen() {
232         return LauncherPrefs.get(mAllApps.getContext()).get(WORK_EDU_STEP) != 0;
233     }
234 
onWorkFabClicked(View view)235     private void onWorkFabClicked(View view) {
236         if (Utilities.ATLEAST_P && mCurrentState == STATE_ENABLED && mWorkModeSwitch.isEnabled()) {
237             mStatsLogManager.logger().log(LAUNCHER_TURN_OFF_WORK_APPS_TAP);
238             setWorkProfileEnabled(false);
239         }
240     }
241 
newScrollListener()242     public RecyclerView.OnScrollListener newScrollListener() {
243         return new RecyclerView.OnScrollListener() {
244             int totalDelta = 0;
245             @Override
246             public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState){
247                 if (newState == RecyclerView.SCROLL_STATE_IDLE) {
248                     totalDelta = 0;
249                 }
250             }
251             @Override
252             public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
253                 WorkModeSwitch fab = getWorkModeSwitch();
254                 if (fab == null){
255                     return;
256                 }
257                 totalDelta = Utilities.boundToRange(totalDelta,
258                         -fab.getScrollThreshold(), fab.getScrollThreshold()) + dy;
259                 boolean isScrollAtTop = recyclerView.computeVerticalScrollOffset() == 0;
260                 if ((isScrollAtTop || totalDelta < -fab.getScrollThreshold())) {
261                     fab.extend();
262                 } else if (totalDelta > fab.getScrollThreshold()) {
263                     fab.shrink();
264                 }
265             }
266         };
267     }
268 }
269