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