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