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