1 /* 2 * Copyright (C) 2023 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 17 package com.android.intentresolver; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.os.AsyncTask; 24 import android.os.UserHandle; 25 import android.os.UserManager; 26 import android.text.TextUtils; 27 28 import androidx.annotation.VisibleForTesting; 29 30 /** Monitor for runtime conditions that may disable work profile display. */ 31 public class WorkProfileAvailabilityManager { 32 private final UserManager mUserManager; 33 private final UserHandle mWorkProfileUserHandle; 34 private final Runnable mOnWorkProfileStateUpdated; 35 36 private BroadcastReceiver mWorkProfileStateReceiver; 37 38 private boolean mIsWaitingToEnableWorkProfile; 39 private boolean mWorkProfileHasBeenEnabled; 40 WorkProfileAvailabilityManager( UserManager userManager, UserHandle workProfileUserHandle, Runnable onWorkProfileStateUpdated)41 public WorkProfileAvailabilityManager( 42 UserManager userManager, 43 UserHandle workProfileUserHandle, 44 Runnable onWorkProfileStateUpdated) { 45 mUserManager = userManager; 46 mWorkProfileUserHandle = workProfileUserHandle; 47 mWorkProfileHasBeenEnabled = isWorkProfileEnabled(); 48 mOnWorkProfileStateUpdated = onWorkProfileStateUpdated; 49 } 50 51 /** 52 * Register a {@link BroadcastReceiver}, if we haven't already, to be notified about work 53 * profile availability changes. 54 * 55 * TODO: this takes the context for testing, because we don't have a context on hand when we 56 * set up this component's default "override" in {@link ChooserActivityOverrideData#reset()}. 57 * The use of these overrides in our testing design is questionable and can hopefully be 58 * improved someday; then this context should be injected in our constructor & held as `final`. 59 * 60 * TODO: consider injecting an optional `Lifecycle` so that this component can automatically 61 * manage its own registration/unregistration. (This would be optional because registration of 62 * the receiver is conditional on having `shouldShowTabs()` in our session.) 63 */ registerWorkProfileStateReceiver(Context context)64 public void registerWorkProfileStateReceiver(Context context) { 65 if (mWorkProfileStateReceiver != null) { 66 return; 67 } 68 mWorkProfileStateReceiver = createWorkProfileStateReceiver(); 69 70 IntentFilter filter = new IntentFilter(); 71 filter.addAction(Intent.ACTION_USER_UNLOCKED); 72 filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); 73 filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); 74 context.registerReceiverAsUser( 75 mWorkProfileStateReceiver, UserHandle.ALL, filter, null, null); 76 } 77 78 /** 79 * Unregister any {@link BroadcastReceiver} currently waiting for a work-enabled broadcast. 80 * 81 * TODO: this takes the context for testing, because we don't have a context on hand when we 82 * set up this component's default "override" in {@link ChooserActivityOverrideData#reset()}. 83 * The use of these overrides in our testing design is questionable and can hopefully be 84 * improved someday; then this context should be injected in our constructor & held as `final`. 85 */ unregisterWorkProfileStateReceiver(Context context)86 public void unregisterWorkProfileStateReceiver(Context context) { 87 if (mWorkProfileStateReceiver == null) { 88 return; 89 } 90 context.unregisterReceiver(mWorkProfileStateReceiver); 91 mWorkProfileStateReceiver = null; 92 } 93 isQuietModeEnabled()94 public boolean isQuietModeEnabled() { 95 return mUserManager.isQuietModeEnabled(mWorkProfileUserHandle); 96 } 97 98 // TODO: why do clients only care about the result of `isQuietModeEnabled()`, even though 99 // internally (in `isWorkProfileEnabled()`) we also check this 'unlocked' condition? 100 @VisibleForTesting isWorkProfileUserUnlocked()101 public boolean isWorkProfileUserUnlocked() { 102 return mUserManager.isUserUnlocked(mWorkProfileUserHandle); 103 } 104 105 /** 106 * Request that quiet mode be enabled (or disabled) for the work profile. 107 * TODO: this is only used to disable quiet mode; should that be hard-coded? 108 */ requestQuietModeEnabled(boolean enabled)109 public void requestQuietModeEnabled(boolean enabled) { 110 AsyncTask.THREAD_POOL_EXECUTOR.execute( 111 () -> mUserManager.requestQuietModeEnabled(enabled, mWorkProfileUserHandle)); 112 mIsWaitingToEnableWorkProfile = true; 113 } 114 115 /** 116 * Stop waiting for a work-enabled broadcast. 117 * TODO: this seems strangely low-level to include as part of the public API. Maybe some 118 * responsibilities need to be pulled over from the client? 119 */ markWorkProfileEnabledBroadcastReceived()120 public void markWorkProfileEnabledBroadcastReceived() { 121 mIsWaitingToEnableWorkProfile = false; 122 } 123 isWaitingToEnableWorkProfile()124 public boolean isWaitingToEnableWorkProfile() { 125 return mIsWaitingToEnableWorkProfile; 126 } 127 isWorkProfileEnabled()128 private boolean isWorkProfileEnabled() { 129 return (mWorkProfileUserHandle != null) 130 && !isQuietModeEnabled() 131 && isWorkProfileUserUnlocked(); 132 } 133 createWorkProfileStateReceiver()134 private BroadcastReceiver createWorkProfileStateReceiver() { 135 return new BroadcastReceiver() { 136 @Override 137 public void onReceive(Context context, Intent intent) { 138 String action = intent.getAction(); 139 if (!TextUtils.equals(action, Intent.ACTION_USER_UNLOCKED) 140 && !TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) 141 && !TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_AVAILABLE)) { 142 return; 143 } 144 145 if (intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1) 146 != mWorkProfileUserHandle.getIdentifier()) { 147 return; 148 } 149 150 if (isWorkProfileEnabled()) { 151 if (mWorkProfileHasBeenEnabled) { 152 return; 153 } 154 mWorkProfileHasBeenEnabled = true; 155 mIsWaitingToEnableWorkProfile = false; 156 } else { 157 // Must be an UNAVAILABLE broadcast, so we watch for the next availability. 158 // TODO: confirm the above reasoning (& handling of "UNAVAILABLE" in general). 159 mWorkProfileHasBeenEnabled = false; 160 } 161 162 mOnWorkProfileStateUpdated.run(); 163 } 164 }; 165 } 166 } 167