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.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
29 
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.util.Log;
33 import android.view.View;
34 
35 import androidx.annotation.NonNull;
36 import androidx.annotation.Nullable;
37 import androidx.recyclerview.widget.RecyclerView;
38 
39 import com.android.launcher3.Flags;
40 import com.android.launcher3.LauncherPrefs;
41 import com.android.launcher3.R;
42 import com.android.launcher3.Utilities;
43 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
44 import com.android.launcher3.logging.StatsLogManager;
45 import com.android.launcher3.pm.UserCache;
46 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
47 
48 import java.util.ArrayList;
49 import java.util.function.Predicate;
50 import java.util.stream.Stream;
51 
52 /**
53  * Companion class for {@link ActivityAllAppsContainerView} to manage work tab and personal tab
54  * related
55  * logic based on {@link UserProfileState}?
56  */
57 public class WorkProfileManager extends UserProfileManager
58         implements PersonalWorkSlidingTabStrip.OnActivePageChangedListener {
59     private static final String TAG = "WorkProfileManager";
60     private final ActivityAllAppsContainerView<?> mAllApps;
61     private WorkModeSwitch mWorkModeSwitch;
62     private final Predicate<UserHandle> mWorkProfileMatcher;
63 
WorkProfileManager( UserManager userManager, ActivityAllAppsContainerView allApps, StatsLogManager statsLogManager, UserCache userCache)64     public WorkProfileManager(
65             UserManager userManager, ActivityAllAppsContainerView allApps,
66             StatsLogManager statsLogManager, UserCache userCache) {
67         super(userManager, statsLogManager, userCache);
68         mAllApps = allApps;
69         mWorkProfileMatcher = (user) -> userCache.getUserInfo(user).isWork();
70     }
71 
72     /**
73      * Posts quite mode enable/disable call for work profile user
74      */
setWorkProfileEnabled(boolean enabled)75     public void setWorkProfileEnabled(boolean enabled) {
76         updateCurrentState(STATE_TRANSITION);
77         setQuietMode(!enabled);
78     }
79 
80     @Override
onActivePageChanged(int page)81     public void onActivePageChanged(int page) {
82         updateWorkFAB(page);
83     }
84 
updateWorkFAB(int page)85     private void updateWorkFAB(int page) {
86         if (mWorkModeSwitch != null) {
87             if (page == MAIN || page == SEARCH) {
88                 mWorkModeSwitch.animateVisibility(false);
89             } else if (page == WORK && getCurrentState() == STATE_ENABLED) {
90                 mWorkModeSwitch.animateVisibility(true);
91             }
92         }
93     }
94 
95     /**
96      * Requests work profile state from {@link AllAppsStore} and updates work profile related views
97      */
reset()98     public void reset() {
99         int quietModeFlag;
100         if (Flags.enablePrivateSpace()) {
101             quietModeFlag = FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
102         } else {
103             quietModeFlag = FLAG_QUIET_MODE_ENABLED;
104         }
105         boolean isEnabled = !mAllApps.getAppsStore().hasModelFlag(quietModeFlag);
106         updateCurrentState(isEnabled ? STATE_ENABLED : STATE_DISABLED);
107         if (mWorkModeSwitch != null) {
108             // reset the position of the button and clear IME insets.
109             mWorkModeSwitch.getImeInsets().setEmpty();
110             mWorkModeSwitch.updateTranslationY();
111         }
112     }
113 
updateCurrentState(@serProfileState int currentState)114     private void updateCurrentState(@UserProfileState int currentState) {
115         setCurrentState(currentState);
116         if (getAH() != null) {
117             getAH().mAppsList.updateAdapterItems();
118         }
119         if (mWorkModeSwitch != null) {
120             updateWorkFAB(mAllApps.getCurrentPage());
121         }
122         if (getCurrentState() == STATE_ENABLED) {
123             attachWorkModeSwitch();
124         } else if (getCurrentState() == STATE_DISABLED) {
125             detachWorkModeSwitch();
126         }
127     }
128 
129     /**
130      * Creates and attaches for profile toggle button to {@link ActivityAllAppsContainerView}
131      */
attachWorkModeSwitch()132     public boolean attachWorkModeSwitch() {
133         if (!mAllApps.getAppsStore().hasModelFlag(
134                 FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION)) {
135             Log.e(TAG, "unable to attach work mode switch; Missing required permissions");
136             return false;
137         }
138         if (mWorkModeSwitch == null) {
139             mWorkModeSwitch = (WorkModeSwitch) mAllApps.getLayoutInflater().inflate(
140                     R.layout.work_mode_fab, mAllApps, false);
141         }
142         if (mWorkModeSwitch.getParent() == null) {
143             mAllApps.addView(mWorkModeSwitch);
144         }
145         if (mAllApps.getCurrentPage() != WORK) {
146             mWorkModeSwitch.animateVisibility(false);
147         }
148         if (getAH() != null) {
149             getAH().applyPadding();
150         }
151         mWorkModeSwitch.setOnClickListener(this::onWorkFabClicked);
152         return true;
153     }
154     /**
155      * Removes work profile toggle button from {@link ActivityAllAppsContainerView}
156      */
detachWorkModeSwitch()157     public void detachWorkModeSwitch() {
158         if (mWorkModeSwitch != null && mWorkModeSwitch.getParent() == mAllApps) {
159             mAllApps.removeView(mWorkModeSwitch);
160         }
161         mWorkModeSwitch = null;
162     }
163 
164     @Nullable
getWorkModeSwitch()165     public WorkModeSwitch getWorkModeSwitch() {
166         return mWorkModeSwitch;
167     }
168 
getAH()169     private ActivityAllAppsContainerView.AdapterHolder getAH() {
170         return mAllApps.mAH.get(WORK);
171     }
172 
173     /**
174      * returns whether or not work apps should be visible in work tab.
175      */
shouldShowWorkApps()176     public boolean shouldShowWorkApps() {
177         return getCurrentState() != WorkProfileManager.STATE_DISABLED;
178     }
179 
hasWorkApps()180     public boolean hasWorkApps() {
181         return Stream.of(mAllApps.getAppsStore().getApps()).anyMatch(getItemInfoMatcher());
182     }
183 
184     /**
185      * Adds work profile specific adapter items to adapterItems and returns number of items added
186      */
addWorkItems(ArrayList<AdapterItem> adapterItems)187     public int addWorkItems(ArrayList<AdapterItem> adapterItems) {
188         if (getCurrentState() == WorkProfileManager.STATE_DISABLED) {
189             //add disabled card here.
190             adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_DISABLED_CARD));
191         } else if (getCurrentState() == WorkProfileManager.STATE_ENABLED && !isEduSeen()) {
192             adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_EDU_CARD));
193         }
194         return adapterItems.size();
195     }
196 
isEduSeen()197     private boolean isEduSeen() {
198         return LauncherPrefs.get(mAllApps.getContext()).get(WORK_EDU_STEP) != 0;
199     }
200 
onWorkFabClicked(View view)201     private void onWorkFabClicked(View view) {
202         if (getCurrentState() == STATE_ENABLED && mWorkModeSwitch.isEnabled()) {
203             logEvents(LAUNCHER_TURN_OFF_WORK_APPS_TAP);
204             setWorkProfileEnabled(false);
205         }
206     }
207 
newScrollListener()208     public RecyclerView.OnScrollListener newScrollListener() {
209         return new RecyclerView.OnScrollListener() {
210             int totalDelta = 0;
211             @Override
212             public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState){
213                 if (newState == RecyclerView.SCROLL_STATE_IDLE) {
214                     totalDelta = 0;
215                 }
216             }
217             @Override
218             public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
219                 WorkModeSwitch fab = getWorkModeSwitch();
220                 if (fab == null){
221                     return;
222                 }
223                 totalDelta = Utilities.boundToRange(totalDelta,
224                         -fab.getScrollThreshold(), fab.getScrollThreshold()) + dy;
225                 boolean isScrollAtTop = recyclerView.computeVerticalScrollOffset() == 0;
226                 if ((isScrollAtTop || totalDelta < -fab.getScrollThreshold())) {
227                     fab.extend();
228                 } else if (totalDelta > fab.getScrollThreshold()) {
229                     fab.shrink();
230                 }
231             }
232         };
233     }
234 
235     @Override
getUserMatcher()236     public Predicate<UserHandle> getUserMatcher() {
237         return mWorkProfileMatcher;
238     }
239 }
240