1 /*
2  * Copyright (C) 2014 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.systemui.qs;
17 
18 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
19 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT;
20 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_CA_CERT;
21 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NAMED_VPN;
22 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NETWORK;
23 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TITLE;
24 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN;
25 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MONITORING_CA_CERT_SUBTITLE;
26 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MONITORING_NETWORK_SUBTITLE;
27 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MONITORING_VPN_SUBTITLE;
28 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_NAMED_MANAGEMENT;
29 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN;
30 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_VIEW_POLICIES;
31 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_CA_CERT;
32 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NAMED_VPN;
33 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NETWORK;
34 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT;
35 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MONITORING;
36 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MULTIPLE_VPNS;
37 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_NAMED_VPN;
38 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT;
39 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MONITORING;
40 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS;
41 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_NAMED_VPN;
42 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_WORK_PROFILE_MONITORING;
43 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_PERSONAL_PROFILE_NAMED_VPN;
44 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_MONITORING;
45 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NAMED_VPN;
46 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NETWORK;
47 
48 import android.app.AlertDialog;
49 import android.app.Dialog;
50 import android.app.admin.DeviceAdminInfo;
51 import android.app.admin.DevicePolicyManager;
52 import android.content.Context;
53 import android.content.DialogInterface;
54 import android.content.Intent;
55 import android.content.pm.UserInfo;
56 import android.graphics.drawable.Drawable;
57 import android.os.Handler;
58 import android.os.Looper;
59 import android.os.UserManager;
60 import android.provider.DeviceConfig;
61 import android.provider.Settings;
62 import android.text.SpannableStringBuilder;
63 import android.text.method.LinkMovementMethod;
64 import android.text.style.ClickableSpan;
65 import android.util.Log;
66 import android.view.LayoutInflater;
67 import android.view.View;
68 import android.view.Window;
69 import android.widget.ImageView;
70 import android.widget.TextView;
71 
72 import androidx.annotation.Nullable;
73 import androidx.annotation.VisibleForTesting;
74 
75 import com.android.internal.jank.InteractionJankMonitor;
76 import com.android.systemui.animation.DialogCuj;
77 import com.android.systemui.animation.DialogTransitionAnimator;
78 import com.android.systemui.animation.Expandable;
79 import com.android.systemui.common.shared.model.ContentDescription;
80 import com.android.systemui.common.shared.model.Icon;
81 import com.android.systemui.dagger.SysUISingleton;
82 import com.android.systemui.dagger.qualifiers.Application;
83 import com.android.systemui.dagger.qualifiers.Background;
84 import com.android.systemui.dagger.qualifiers.Main;
85 import com.android.systemui.plugins.ActivityStarter;
86 import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig;
87 import com.android.systemui.res.R;
88 import com.android.systemui.security.data.model.SecurityModel;
89 import com.android.systemui.settings.UserTracker;
90 import com.android.systemui.statusbar.phone.SystemUIDialog;
91 import com.android.systemui.statusbar.policy.SecurityController;
92 
93 import java.util.concurrent.atomic.AtomicBoolean;
94 import java.util.function.Supplier;
95 
96 import javax.inject.Inject;
97 
98 /** Helper class for the configuration of the QS security footer button. */
99 @SysUISingleton
100 public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
101     protected static final String TAG = "QSSecurityFooterUtils";
102     protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
103     private static final boolean DEBUG_FORCE_VISIBLE = false;
104 
105     private static final String INTERACTION_JANK_TAG = "managed_device_info";
106 
107     @Application private Context mContext;
108     private final DevicePolicyManager mDpm;
109 
110     private final SecurityController mSecurityController;
111     private final ActivityStarter mActivityStarter;
112     private final Handler mMainHandler;
113     private final UserTracker mUserTracker;
114     private final DialogTransitionAnimator mDialogTransitionAnimator;
115 
116     private final AtomicBoolean mShouldUseSettingsButton = new AtomicBoolean(false);
117 
118     protected Handler mBgHandler;
119     private AlertDialog mDialog;
120 
121     private Supplier<String> mManagementTitleSupplier = () ->
122             mContext == null ? null : mContext.getString(R.string.monitoring_title_device_owned);
123 
124     private Supplier<String> mManagementMessageSupplier = () ->
125             mContext == null ? null : mContext.getString(
126                     R.string.quick_settings_disclosure_management);
127 
128     private Supplier<String> mManagementMonitoringStringSupplier = () ->
129             mContext == null ? null : mContext.getString(
130                     R.string.quick_settings_disclosure_management_monitoring);
131 
132     private Supplier<String> mManagementMultipleVpnStringSupplier = () ->
133             mContext == null ? null : mContext.getString(
134                     R.string.quick_settings_disclosure_management_vpns);
135 
136     private Supplier<String> mWorkProfileMonitoringStringSupplier = () ->
137             mContext == null ? null : mContext.getString(
138                     R.string.quick_settings_disclosure_managed_profile_monitoring);
139 
140     private Supplier<String> mWorkProfileNetworkStringSupplier = () ->
141             mContext == null ? null : mContext.getString(
142                     R.string.quick_settings_disclosure_managed_profile_network_activity);
143 
144     private Supplier<String> mMonitoringSubtitleCaCertStringSupplier = () ->
145             mContext == null ? null : mContext.getString(
146                     R.string.monitoring_subtitle_ca_certificate);
147 
148     private Supplier<String> mMonitoringSubtitleNetworkStringSupplier = () ->
149             mContext == null ? null : mContext.getString(
150                     R.string.monitoring_subtitle_network_logging);
151 
152     private Supplier<String> mMonitoringSubtitleVpnStringSupplier = () ->
153             mContext == null ? null : mContext.getString(R.string.monitoring_subtitle_vpn);
154 
155     private Supplier<String> mViewPoliciesButtonStringSupplier = () ->
156             mContext == null ? null : mContext.getString(R.string.monitoring_button_view_policies);
157 
158     private Supplier<String> mManagementDialogStringSupplier = () ->
159             mContext == null ? null : mContext.getString(
160                     R.string.monitoring_description_management);
161 
162     private Supplier<String> mManagementDialogCaCertStringSupplier = () ->
163             mContext == null ? null : mContext.getString(
164                     R.string.monitoring_description_management_ca_certificate);
165 
166     private Supplier<String> mWorkProfileDialogCaCertStringSupplier = () ->
167             mContext == null ? null : mContext.getString(
168                     R.string.monitoring_description_managed_profile_ca_certificate);
169 
170     private Supplier<String> mManagementDialogNetworkStringSupplier = () ->
171             mContext == null ? null : mContext.getString(
172                     R.string.monitoring_description_management_network_logging);
173 
174     private Supplier<String> mWorkProfileDialogNetworkStringSupplier = () ->
175             mContext == null ? null : mContext.getString(
176                     R.string.monitoring_description_managed_profile_network_logging);
177 
178     @Inject
QSSecurityFooterUtils( @pplication Context context, DevicePolicyManager devicePolicyManager, UserTracker userTracker, @Main Handler mainHandler, ActivityStarter activityStarter, SecurityController securityController, @Background Looper bgLooper, DialogTransitionAnimator dialogTransitionAnimator)179     QSSecurityFooterUtils(
180             @Application Context context, DevicePolicyManager devicePolicyManager,
181             UserTracker userTracker, @Main Handler mainHandler, ActivityStarter activityStarter,
182             SecurityController securityController, @Background Looper bgLooper,
183             DialogTransitionAnimator dialogTransitionAnimator) {
184         mContext = context;
185         mDpm = devicePolicyManager;
186         mUserTracker = userTracker;
187         mMainHandler = mainHandler;
188         mActivityStarter = activityStarter;
189         mSecurityController = securityController;
190         mBgHandler = new Handler(bgLooper);
191         mDialogTransitionAnimator = dialogTransitionAnimator;
192     }
193 
194     /** Show the device monitoring dialog. */
showDeviceMonitoringDialog(Context quickSettingsContext, @Nullable Expandable expandable)195     public void showDeviceMonitoringDialog(Context quickSettingsContext,
196             @Nullable Expandable expandable) {
197         createDialog(quickSettingsContext, expandable);
198     }
199 
200     /**
201      * Return the {@link SecurityButtonConfig} of the security button, or {@code null} if no
202      * security button should be shown.
203      */
204     @Nullable
getButtonConfig(SecurityModel securityModel)205     public SecurityButtonConfig getButtonConfig(SecurityModel securityModel) {
206         final boolean isDeviceManaged = securityModel.isDeviceManaged();
207         final UserInfo currentUser = mUserTracker.getUserInfo();
208         final boolean isDemoDevice = UserManager.isDeviceInDemoMode(mContext) && currentUser != null
209                 && currentUser.isDemo();
210         final boolean hasWorkProfile = securityModel.getHasWorkProfile();
211         final boolean hasCACerts = securityModel.getHasCACertInCurrentUser();
212         final boolean hasCACertsInWorkProfile = securityModel.getHasCACertInWorkProfile();
213         final boolean isNetworkLoggingEnabled = securityModel.isNetworkLoggingEnabled();
214         final String vpnName = securityModel.getPrimaryVpnName();
215         final String vpnNameWorkProfile = securityModel.getWorkProfileVpnName();
216         final CharSequence organizationName = securityModel.getDeviceOwnerOrganizationName();
217         final CharSequence workProfileOrganizationName =
218                 securityModel.getWorkProfileOrganizationName();
219         final boolean isProfileOwnerOfOrganizationOwnedDevice =
220                 securityModel.isProfileOwnerOfOrganizationOwnedDevice();
221         final boolean isParentalControlsEnabled = securityModel.isParentalControlsEnabled();
222         final boolean isWorkProfileOn = securityModel.isWorkProfileOn();
223         final boolean hasDisclosableWorkProfilePolicy = hasCACertsInWorkProfile
224                 || vpnNameWorkProfile != null || (hasWorkProfile && isNetworkLoggingEnabled);
225         // Update visibility of footer
226         boolean isVisible = (isDeviceManaged && !isDemoDevice)
227                 || hasCACerts
228                 || vpnName != null
229                 || isProfileOwnerOfOrganizationOwnedDevice
230                 || isParentalControlsEnabled
231                 || (hasDisclosableWorkProfilePolicy && isWorkProfileOn);
232         if (!isVisible && !DEBUG_FORCE_VISIBLE) {
233             return null;
234         }
235 
236         // Update the view to be untappable if the device is an organization-owned device with a
237         // managed profile and there is either:
238         // a) no policy set which requires a privacy disclosure.
239         // b) a specific work policy set but the work profile is turned off.
240         boolean isClickable = !(isProfileOwnerOfOrganizationOwnedDevice
241                 && (!hasDisclosableWorkProfilePolicy || !isWorkProfileOn));
242 
243         String text = getFooterText(isDeviceManaged, hasWorkProfile,
244                 hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
245                 vpnNameWorkProfile, organizationName, workProfileOrganizationName,
246                 isProfileOwnerOfOrganizationOwnedDevice, isParentalControlsEnabled,
247                 isWorkProfileOn).toString();
248 
249         Icon icon;
250         ContentDescription contentDescription = null;
251         if (isParentalControlsEnabled && securityModel.getDeviceAdminIcon() != null) {
252             icon = new Icon.Loaded(securityModel.getDeviceAdminIcon(), contentDescription);
253         } else if (vpnName != null || vpnNameWorkProfile != null) {
254             if (securityModel.isVpnBranded()) {
255                 icon = new Icon.Resource(R.drawable.stat_sys_branded_vpn, contentDescription);
256             } else {
257                 icon = new Icon.Resource(R.drawable.stat_sys_vpn_ic, contentDescription);
258             }
259         } else {
260             icon = new Icon.Resource(R.drawable.ic_info_outline, contentDescription);
261         }
262 
263         return new SecurityButtonConfig(icon, text, isClickable);
264     }
265 
266     @Nullable
getFooterText(boolean isDeviceManaged, boolean hasWorkProfile, boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled, String vpnName, String vpnNameWorkProfile, CharSequence organizationName, CharSequence workProfileOrganizationName, boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isParentalControlsEnabled, boolean isWorkProfileOn)267     protected CharSequence getFooterText(boolean isDeviceManaged, boolean hasWorkProfile,
268             boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
269             String vpnName, String vpnNameWorkProfile, CharSequence organizationName,
270             CharSequence workProfileOrganizationName,
271             boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isParentalControlsEnabled,
272             boolean isWorkProfileOn) {
273         if (isParentalControlsEnabled) {
274             return mContext.getString(R.string.quick_settings_disclosure_parental_controls);
275         }
276         if (isDeviceManaged || DEBUG_FORCE_VISIBLE) {
277             return getManagedDeviceFooterText(hasCACerts, hasCACertsInWorkProfile,
278                     isNetworkLoggingEnabled, vpnName, vpnNameWorkProfile, organizationName);
279         }
280         return getManagedAndPersonalProfileFooterText(hasWorkProfile, hasCACerts,
281                 hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName, vpnNameWorkProfile,
282                 workProfileOrganizationName, isProfileOwnerOfOrganizationOwnedDevice,
283                 isWorkProfileOn);
284     }
285 
getManagedDeviceFooterText( boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled, String vpnName, String vpnNameWorkProfile, CharSequence organizationName)286     private String getManagedDeviceFooterText(
287             boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
288             String vpnName, String vpnNameWorkProfile, CharSequence organizationName) {
289         if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) {
290             return getManagedDeviceMonitoringText(organizationName);
291         }
292         if (vpnName != null || vpnNameWorkProfile != null) {
293             return getManagedDeviceVpnText(vpnName, vpnNameWorkProfile, organizationName);
294         }
295         return getMangedDeviceGeneralText(organizationName);
296     }
297 
getManagedDeviceMonitoringText(CharSequence organizationName)298     private String getManagedDeviceMonitoringText(CharSequence organizationName) {
299         if (organizationName == null) {
300             return mDpm.getResources().getString(
301                     QS_MSG_MANAGEMENT_MONITORING, mManagementMonitoringStringSupplier);
302         }
303         return mDpm.getResources().getString(
304                 QS_MSG_NAMED_MANAGEMENT_MONITORING,
305                 () -> mContext.getString(
306                         R.string.quick_settings_disclosure_named_management_monitoring,
307                         organizationName),
308                 organizationName);
309     }
310 
getManagedDeviceVpnText( String vpnName, String vpnNameWorkProfile, CharSequence organizationName)311     private String getManagedDeviceVpnText(
312             String vpnName, String vpnNameWorkProfile, CharSequence organizationName) {
313         if (vpnName != null && vpnNameWorkProfile != null) {
314             if (organizationName == null) {
315                 return mDpm.getResources().getString(
316                         QS_MSG_MANAGEMENT_MULTIPLE_VPNS, mManagementMultipleVpnStringSupplier);
317             }
318             return mDpm.getResources().getString(
319                     QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS,
320                     () -> mContext.getString(
321                             R.string.quick_settings_disclosure_named_management_vpns,
322                             organizationName),
323                     organizationName);
324         }
325         String name = vpnName != null ? vpnName : vpnNameWorkProfile;
326         if (organizationName == null) {
327             return mDpm.getResources().getString(
328                     QS_MSG_MANAGEMENT_NAMED_VPN,
329                     () -> mContext.getString(
330                             R.string.quick_settings_disclosure_management_named_vpn,
331                             name),
332                     name);
333         }
334         return mDpm.getResources().getString(
335                 QS_MSG_NAMED_MANAGEMENT_NAMED_VPN,
336                 () -> mContext.getString(
337                         R.string.quick_settings_disclosure_named_management_named_vpn,
338                         organizationName,
339                         name),
340                 organizationName,
341                 name);
342     }
343 
getMangedDeviceGeneralText(CharSequence organizationName)344     private String getMangedDeviceGeneralText(CharSequence organizationName) {
345         if (organizationName == null) {
346             return mDpm.getResources().getString(QS_MSG_MANAGEMENT, mManagementMessageSupplier);
347         }
348         if (isFinancedDevice()) {
349             return mContext.getString(
350                     R.string.quick_settings_financed_disclosure_named_management,
351                     organizationName);
352         } else {
353             return mDpm.getResources().getString(
354                     QS_MSG_NAMED_MANAGEMENT,
355                     () -> mContext.getString(
356                             R.string.quick_settings_disclosure_named_management,
357                             organizationName),
358                     organizationName);
359         }
360     }
361 
getManagedAndPersonalProfileFooterText(boolean hasWorkProfile, boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled, String vpnName, String vpnNameWorkProfile, CharSequence workProfileOrganizationName, boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isWorkProfileOn)362     private String getManagedAndPersonalProfileFooterText(boolean hasWorkProfile,
363             boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
364             String vpnName, String vpnNameWorkProfile, CharSequence workProfileOrganizationName,
365             boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isWorkProfileOn) {
366         if (hasCACerts || (hasCACertsInWorkProfile && isWorkProfileOn)) {
367             return getMonitoringText(
368                     hasCACerts, hasCACertsInWorkProfile, workProfileOrganizationName,
369                     isWorkProfileOn);
370         }
371         if (vpnName != null || (vpnNameWorkProfile != null && isWorkProfileOn)) {
372             return getVpnText(hasWorkProfile, vpnName, vpnNameWorkProfile, isWorkProfileOn);
373         }
374         if (hasWorkProfile && isNetworkLoggingEnabled && isWorkProfileOn) {
375             return getManagedProfileNetworkActivityText();
376         }
377         if (isProfileOwnerOfOrganizationOwnedDevice) {
378             return getMangedDeviceGeneralText(workProfileOrganizationName);
379         }
380         return null;
381     }
382 
getMonitoringText(boolean hasCACerts, boolean hasCACertsInWorkProfile, CharSequence workProfileOrganizationName, boolean isWorkProfileOn)383     private String getMonitoringText(boolean hasCACerts, boolean hasCACertsInWorkProfile,
384             CharSequence workProfileOrganizationName, boolean isWorkProfileOn) {
385         if (hasCACertsInWorkProfile && isWorkProfileOn) {
386             if (workProfileOrganizationName == null) {
387                 return mDpm.getResources().getString(
388                         QS_MSG_WORK_PROFILE_MONITORING, mWorkProfileMonitoringStringSupplier);
389             }
390             return mDpm.getResources().getString(
391                     QS_MSG_NAMED_WORK_PROFILE_MONITORING,
392                     () -> mContext.getString(
393                             R.string.quick_settings_disclosure_named_managed_profile_monitoring,
394                             workProfileOrganizationName),
395                     workProfileOrganizationName);
396         }
397         if (hasCACerts) {
398             return mContext.getString(R.string.quick_settings_disclosure_monitoring);
399         }
400         return null;
401     }
402 
getVpnText(boolean hasWorkProfile, String vpnName, String vpnNameWorkProfile, boolean isWorkProfileOn)403     private String getVpnText(boolean hasWorkProfile, String vpnName, String vpnNameWorkProfile,
404             boolean isWorkProfileOn) {
405         if (vpnName != null && vpnNameWorkProfile != null) {
406             return mContext.getString(R.string.quick_settings_disclosure_vpns);
407         }
408         if (vpnNameWorkProfile != null && isWorkProfileOn) {
409             return mDpm.getResources().getString(
410                     QS_MSG_WORK_PROFILE_NAMED_VPN,
411                     () -> mContext.getString(
412                             R.string.quick_settings_disclosure_managed_profile_named_vpn,
413                             vpnNameWorkProfile),
414                     vpnNameWorkProfile);
415         }
416         if (vpnName != null) {
417             if (hasWorkProfile) {
418                 return mDpm.getResources().getString(
419                         QS_MSG_PERSONAL_PROFILE_NAMED_VPN,
420                         () -> mContext.getString(
421                                 R.string.quick_settings_disclosure_personal_profile_named_vpn,
422                                 vpnName),
423                         vpnName);
424             }
425             return mContext.getString(R.string.quick_settings_disclosure_named_vpn,
426                     vpnName);
427         }
428         return null;
429     }
430 
getManagedProfileNetworkActivityText()431     private String getManagedProfileNetworkActivityText() {
432         return mDpm.getResources().getString(
433                 QS_MSG_WORK_PROFILE_NETWORK, mWorkProfileNetworkStringSupplier);
434     }
435 
436     @Override
onClick(DialogInterface dialog, int which)437     public void onClick(DialogInterface dialog, int which) {
438         if (which == DialogInterface.BUTTON_NEGATIVE) {
439             final Intent intent = new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS);
440             dialog.dismiss();
441             // This dismisses the shade on opening the activity
442             mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
443         }
444     }
445 
createDialog(Context quickSettingsContext, @Nullable Expandable expandable)446     private void createDialog(Context quickSettingsContext, @Nullable Expandable expandable) {
447         mShouldUseSettingsButton.set(false);
448         mBgHandler.post(() -> {
449             String settingsButtonText = getSettingsButton();
450             final View dialogView = createDialogView(quickSettingsContext);
451             mMainHandler.post(() -> {
452                 mDialog = new SystemUIDialog(quickSettingsContext, 0);
453                 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
454                 mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
455                 mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, mShouldUseSettingsButton.get()
456                         ? settingsButtonText : getNegativeButton(), this);
457 
458                 mDialog.setView(dialogView);
459                 DialogTransitionAnimator.Controller controller =
460                         expandable != null ? expandable.dialogTransitionController(new DialogCuj(
461                                 InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG))
462                                 : null;
463                 if (controller != null) {
464                     mDialogTransitionAnimator.show(mDialog, controller);
465                 } else {
466                     mDialog.show();
467                 }
468             });
469         });
470     }
471 
472     @VisibleForTesting
getDialog()473     Dialog getDialog() {
474         return mDialog;
475     }
476 
477     @VisibleForTesting
createDialogView(Context quickSettingsContext)478     View createDialogView(Context quickSettingsContext) {
479         if (mSecurityController.isParentalControlsEnabled()) {
480             return createParentalControlsDialogView(quickSettingsContext);
481         }
482         return createOrganizationDialogView(quickSettingsContext);
483     }
484 
createOrganizationDialogView(Context quickSettingsContext)485     private View createOrganizationDialogView(Context quickSettingsContext) {
486         final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
487         final boolean hasWorkProfile = mSecurityController.hasWorkProfile();
488         final CharSequence deviceOwnerOrganization =
489                 mSecurityController.getDeviceOwnerOrganizationName();
490         final boolean hasCACerts = mSecurityController.hasCACertInCurrentUser();
491         final boolean hasCACertsInWorkProfile = mSecurityController.hasCACertInWorkProfile();
492         final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled();
493         final String vpnName = mSecurityController.getPrimaryVpnName();
494         final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName();
495 
496         View dialogView = LayoutInflater.from(quickSettingsContext)
497                 .inflate(R.layout.quick_settings_footer_dialog, null, false);
498 
499         // device management section
500         TextView deviceManagementSubtitle =
501                 dialogView.findViewById(R.id.device_management_subtitle);
502         deviceManagementSubtitle.setText(getManagementTitle(deviceOwnerOrganization));
503 
504         CharSequence managementMessage = getManagementMessage(isDeviceManaged,
505                 deviceOwnerOrganization);
506         if (managementMessage == null) {
507             dialogView.findViewById(R.id.device_management_disclosures).setVisibility(View.GONE);
508         } else {
509             dialogView.findViewById(R.id.device_management_disclosures).setVisibility(View.VISIBLE);
510             TextView deviceManagementWarning =
511                     (TextView) dialogView.findViewById(R.id.device_management_warning);
512             deviceManagementWarning.setText(managementMessage);
513             mShouldUseSettingsButton.set(true);
514         }
515 
516         // ca certificate section
517         CharSequence caCertsMessage = getCaCertsMessage(isDeviceManaged, hasCACerts,
518                 hasCACertsInWorkProfile);
519         if (caCertsMessage == null) {
520             dialogView.findViewById(R.id.ca_certs_disclosures).setVisibility(View.GONE);
521         } else {
522             dialogView.findViewById(R.id.ca_certs_disclosures).setVisibility(View.VISIBLE);
523             TextView caCertsWarning = (TextView) dialogView.findViewById(R.id.ca_certs_warning);
524             caCertsWarning.setText(caCertsMessage);
525             // Make "Open trusted credentials"-link clickable
526             caCertsWarning.setMovementMethod(new LinkMovementMethod());
527 
528             TextView caCertsSubtitle = (TextView) dialogView.findViewById(R.id.ca_certs_subtitle);
529             String caCertsSubtitleMessage = mDpm.getResources().getString(
530                     QS_DIALOG_MONITORING_CA_CERT_SUBTITLE, mMonitoringSubtitleCaCertStringSupplier);
531             caCertsSubtitle.setText(caCertsSubtitleMessage);
532 
533         }
534 
535         // network logging section
536         CharSequence networkLoggingMessage = getNetworkLoggingMessage(isDeviceManaged,
537                 isNetworkLoggingEnabled);
538         if (networkLoggingMessage == null) {
539             dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.GONE);
540         } else {
541             dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.VISIBLE);
542             TextView networkLoggingWarning =
543                     (TextView) dialogView.findViewById(R.id.network_logging_warning);
544             networkLoggingWarning.setText(networkLoggingMessage);
545 
546             TextView networkLoggingSubtitle = (TextView) dialogView.findViewById(
547                     R.id.network_logging_subtitle);
548             String networkLoggingSubtitleMessage = mDpm.getResources().getString(
549                     QS_DIALOG_MONITORING_NETWORK_SUBTITLE,
550                     mMonitoringSubtitleNetworkStringSupplier);
551             networkLoggingSubtitle.setText(networkLoggingSubtitleMessage);
552         }
553 
554         // vpn section
555         CharSequence vpnMessage = getVpnMessage(isDeviceManaged, hasWorkProfile, vpnName,
556                 vpnNameWorkProfile);
557         if (vpnMessage == null) {
558             dialogView.findViewById(R.id.vpn_disclosures).setVisibility(View.GONE);
559         } else {
560             dialogView.findViewById(R.id.vpn_disclosures).setVisibility(View.VISIBLE);
561             TextView vpnWarning = (TextView) dialogView.findViewById(R.id.vpn_warning);
562             vpnWarning.setText(vpnMessage);
563             // Make "Open VPN Settings"-link clickable
564             vpnWarning.setMovementMethod(new LinkMovementMethod());
565 
566             TextView vpnSubtitle = (TextView) dialogView.findViewById(R.id.vpn_subtitle);
567             String vpnSubtitleMessage = mDpm.getResources().getString(
568                     QS_DIALOG_MONITORING_VPN_SUBTITLE, mMonitoringSubtitleVpnStringSupplier);
569             vpnSubtitle.setText(vpnSubtitleMessage);
570         }
571 
572         // Note: if a new section is added, should update configSubtitleVisibility to include
573         // the handling of the subtitle
574         configSubtitleVisibility(managementMessage != null,
575                 caCertsMessage != null,
576                 networkLoggingMessage != null,
577                 vpnMessage != null,
578                 dialogView);
579 
580         return dialogView;
581     }
582 
createParentalControlsDialogView(Context quickSettingsContext)583     private View createParentalControlsDialogView(Context quickSettingsContext) {
584         View dialogView = LayoutInflater.from(quickSettingsContext)
585                 .inflate(R.layout.quick_settings_footer_dialog_parental_controls, null, false);
586 
587         DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
588         Drawable icon = mSecurityController.getIcon(info);
589         if (icon != null) {
590             ImageView imageView = (ImageView) dialogView.findViewById(R.id.parental_controls_icon);
591             imageView.setImageDrawable(icon);
592         }
593 
594         TextView parentalControlsTitle =
595                 (TextView) dialogView.findViewById(R.id.parental_controls_title);
596         parentalControlsTitle.setText(mSecurityController.getLabel(info));
597 
598         return dialogView;
599     }
600 
configSubtitleVisibility(boolean showDeviceManagement, boolean showCaCerts, boolean showNetworkLogging, boolean showVpn, View dialogView)601     protected void configSubtitleVisibility(boolean showDeviceManagement, boolean showCaCerts,
602             boolean showNetworkLogging, boolean showVpn, View dialogView) {
603         // Device Management title should always been shown
604         // When there is a Device Management message, all subtitles should be shown
605         if (showDeviceManagement) {
606             return;
607         }
608         // Hide the subtitle if there is only 1 message shown
609         int mSectionCountExcludingDeviceMgt = 0;
610         if (showCaCerts) {
611             mSectionCountExcludingDeviceMgt++;
612         }
613         if (showNetworkLogging) {
614             mSectionCountExcludingDeviceMgt++;
615         }
616         if (showVpn) {
617             mSectionCountExcludingDeviceMgt++;
618         }
619 
620         // No work needed if there is no sections or more than 1 section
621         if (mSectionCountExcludingDeviceMgt != 1) {
622             return;
623         }
624         if (showCaCerts) {
625             dialogView.findViewById(R.id.ca_certs_subtitle).setVisibility(View.GONE);
626         }
627         if (showNetworkLogging) {
628             dialogView.findViewById(R.id.network_logging_subtitle).setVisibility(View.GONE);
629         }
630         if (showVpn) {
631             dialogView.findViewById(R.id.vpn_subtitle).setVisibility(View.GONE);
632         }
633     }
634 
635     // This should not be called on the main thread to avoid making an IPC.
636     @VisibleForTesting
getSettingsButton()637     String getSettingsButton() {
638         return mDpm.getResources().getString(
639                 QS_DIALOG_VIEW_POLICIES, mViewPoliciesButtonStringSupplier);
640     }
641 
getPositiveButton()642     private String getPositiveButton() {
643         return mContext.getString(R.string.ok);
644     }
645 
646     @Nullable
getNegativeButton()647     private String getNegativeButton() {
648         if (mSecurityController.isParentalControlsEnabled()) {
649             return mContext.getString(R.string.monitoring_button_view_controls);
650         }
651         return null;
652     }
653 
654     @Nullable
getManagementMessage(boolean isDeviceManaged, CharSequence organizationName)655     protected CharSequence getManagementMessage(boolean isDeviceManaged,
656             CharSequence organizationName) {
657         if (!isDeviceManaged) {
658             return null;
659         }
660         if (organizationName != null) {
661             if (isFinancedDevice()) {
662                 return mContext.getString(R.string.monitoring_financed_description_named_management,
663                         organizationName, organizationName);
664             } else {
665                 return mDpm.getResources().getString(
666                         QS_DIALOG_NAMED_MANAGEMENT,
667                         () -> mContext.getString(
668                                 R.string.monitoring_description_named_management,
669                                 organizationName),
670                         organizationName);
671             }
672         }
673         return mDpm.getResources().getString(QS_DIALOG_MANAGEMENT, mManagementDialogStringSupplier);
674     }
675 
676     @Nullable
getCaCertsMessage(boolean isDeviceManaged, boolean hasCACerts, boolean hasCACertsInWorkProfile)677     protected CharSequence getCaCertsMessage(boolean isDeviceManaged, boolean hasCACerts,
678             boolean hasCACertsInWorkProfile) {
679         if (!(hasCACerts || hasCACertsInWorkProfile)) return null;
680         if (isDeviceManaged) {
681             return mDpm.getResources().getString(
682                     QS_DIALOG_MANAGEMENT_CA_CERT, mManagementDialogCaCertStringSupplier);
683         }
684         if (hasCACertsInWorkProfile) {
685             return mDpm.getResources().getString(
686                     QS_DIALOG_WORK_PROFILE_CA_CERT, mWorkProfileDialogCaCertStringSupplier);
687         }
688         return mContext.getString(R.string.monitoring_description_ca_certificate);
689     }
690 
691     @Nullable
getNetworkLoggingMessage(boolean isDeviceManaged, boolean isNetworkLoggingEnabled)692     protected CharSequence getNetworkLoggingMessage(boolean isDeviceManaged,
693             boolean isNetworkLoggingEnabled) {
694         if (!isNetworkLoggingEnabled) return null;
695         if (isDeviceManaged) {
696             return mDpm.getResources().getString(
697                     QS_DIALOG_MANAGEMENT_NETWORK, mManagementDialogNetworkStringSupplier);
698         } else {
699             return mDpm.getResources().getString(
700                     QS_DIALOG_WORK_PROFILE_NETWORK, mWorkProfileDialogNetworkStringSupplier);
701         }
702     }
703 
704     @Nullable
getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile, String vpnName, String vpnNameWorkProfile)705     protected CharSequence getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile,
706             String vpnName, String vpnNameWorkProfile) {
707         if (vpnName == null && vpnNameWorkProfile == null) return null;
708         final SpannableStringBuilder message = new SpannableStringBuilder();
709         if (isDeviceManaged) {
710             if (vpnName != null && vpnNameWorkProfile != null) {
711                 String namedVpns = mDpm.getResources().getString(
712                         QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN,
713                         () -> mContext.getString(
714                                 R.string.monitoring_description_two_named_vpns,
715                                 vpnName, vpnNameWorkProfile),
716                         vpnName, vpnNameWorkProfile);
717                 message.append(namedVpns);
718             } else {
719                 String name = vpnName != null ? vpnName : vpnNameWorkProfile;
720                 String namedVp = mDpm.getResources().getString(
721                         QS_DIALOG_MANAGEMENT_NAMED_VPN,
722                         () -> mContext.getString(
723                                 R.string.monitoring_description_managed_device_named_vpn, name),
724                         name);
725                 message.append(namedVp);
726             }
727         } else {
728             if (vpnName != null && vpnNameWorkProfile != null) {
729                 String namedVpns = mDpm.getResources().getString(
730                         QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN,
731                         () -> mContext.getString(
732                                 R.string.monitoring_description_two_named_vpns,
733                                 vpnName, vpnNameWorkProfile),
734                         vpnName, vpnNameWorkProfile);
735                 message.append(namedVpns);
736             } else if (vpnNameWorkProfile != null) {
737                 String namedVpn = mDpm.getResources().getString(
738                         QS_DIALOG_WORK_PROFILE_NAMED_VPN,
739                         () -> mContext.getString(
740                                 R.string.monitoring_description_managed_profile_named_vpn,
741                                 vpnNameWorkProfile),
742                         vpnNameWorkProfile);
743                 message.append(namedVpn);
744             } else if (hasWorkProfile) {
745                 String namedVpn = mDpm.getResources().getString(
746                         QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN,
747                         () -> mContext.getString(
748                                 R.string.monitoring_description_personal_profile_named_vpn,
749                                 vpnName),
750                         vpnName);
751                 message.append(namedVpn);
752             } else {
753                 message.append(mContext.getString(R.string.monitoring_description_named_vpn,
754                         vpnName));
755             }
756         }
757         message.append(mContext.getString(R.string.monitoring_description_vpn_settings_separator));
758         message.append(mContext.getString(R.string.monitoring_description_vpn_settings),
759                 new VpnSpan(), 0);
760         return message;
761     }
762 
763     @VisibleForTesting
getManagementTitle(CharSequence deviceOwnerOrganization)764     CharSequence getManagementTitle(CharSequence deviceOwnerOrganization) {
765         if (deviceOwnerOrganization != null && isFinancedDevice()) {
766             return mContext.getString(R.string.monitoring_title_financed_device,
767                     deviceOwnerOrganization);
768         } else {
769             return mDpm.getResources().getString(
770                     QS_DIALOG_MANAGEMENT_TITLE,
771                     mManagementTitleSupplier);
772         }
773     }
774 
775     protected class VpnSpan extends ClickableSpan {
776         @Override
onClick(View widget)777         public void onClick(View widget) {
778             final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS);
779             mDialog.dismiss();
780             // This dismisses the shade on opening the activity
781             mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
782         }
783 
784         // for testing, to compare two CharSequences containing VpnSpans
785         @Override
equals(Object object)786         public boolean equals(Object object) {
787             return object instanceof VpnSpan;
788         }
789 
790         @Override
hashCode()791         public int hashCode() {
792             return 314159257; // prime
793         }
794     }
795 
796     // TODO(b/259908270): remove and inline direct call to mSecurityController.isFinancedDevice()
isFinancedDevice()797     private boolean isFinancedDevice() {
798         if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
799                 DevicePolicyManager.ADD_ISFINANCED_DEVICE_FLAG,
800                 DevicePolicyManager.ADD_ISFINANCED_FEVICE_DEFAULT)) {
801             return mSecurityController.isFinancedDevice();
802         } else {
803             return mSecurityController.isDeviceManaged()
804                     && mSecurityController.getDeviceOwnerType(
805                     mSecurityController.getDeviceOwnerComponentOnAnyUser())
806                     == DEVICE_OWNER_TYPE_FINANCED;
807         }
808     }
809 }
810