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