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