• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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