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 android.app.AlertDialog; 19 import android.content.Context; 20 import android.content.DialogInterface; 21 import android.content.Intent; 22 import android.os.Handler; 23 import android.os.Looper; 24 import android.os.Message; 25 import android.provider.Settings; 26 import android.text.SpannableStringBuilder; 27 import android.text.method.LinkMovementMethod; 28 import android.text.style.ClickableSpan; 29 import android.util.Log; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.view.View.OnClickListener; 34 import android.view.Window; 35 import android.widget.ImageView; 36 import android.widget.TextView; 37 38 import com.android.systemui.Dependency; 39 import com.android.systemui.FontSizeUtils; 40 import com.android.systemui.R; 41 import com.android.systemui.plugins.ActivityStarter; 42 import com.android.systemui.statusbar.phone.SystemUIDialog; 43 import com.android.systemui.statusbar.policy.SecurityController; 44 45 import static android.provider.Settings.ACTION_VPN_SETTINGS; 46 47 public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener { 48 protected static final String TAG = "QSSecurityFooter"; 49 protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 50 51 private final View mRootView; 52 private final TextView mFooterText; 53 private final ImageView mFooterIcon; 54 private final Context mContext; 55 private final Callback mCallback = new Callback(); 56 private final SecurityController mSecurityController; 57 private final ActivityStarter mActivityStarter; 58 private final Handler mMainHandler; 59 private final View mDivider; 60 61 private AlertDialog mDialog; 62 private QSTileHost mHost; 63 protected H mHandler; 64 65 private boolean mIsVisible; 66 private CharSequence mFooterTextContent = null; 67 private int mFooterTextId; 68 private int mFooterIconId; 69 70 public QSSecurityFooter(QSPanel qsPanel, Context context) { 71 mRootView = LayoutInflater.from(context) 72 .inflate(R.layout.quick_settings_footer, qsPanel, false); 73 mRootView.setOnClickListener(this); 74 mFooterText = (TextView) mRootView.findViewById(R.id.footer_text); 75 mFooterIcon = (ImageView) mRootView.findViewById(R.id.footer_icon); 76 mFooterIconId = R.drawable.ic_info_outline; 77 mContext = context; 78 mMainHandler = new Handler(Looper.getMainLooper()); 79 mActivityStarter = Dependency.get(ActivityStarter.class); 80 mSecurityController = Dependency.get(SecurityController.class); 81 mHandler = new H(Dependency.get(Dependency.BG_LOOPER)); 82 mDivider = qsPanel == null ? null : qsPanel.getDivider(); 83 } 84 85 public void setHostEnvironment(QSTileHost host) { 86 mHost = host; 87 } 88 89 public void setListening(boolean listening) { 90 if (listening) { 91 mSecurityController.addCallback(mCallback); 92 } else { 93 mSecurityController.removeCallback(mCallback); 94 } 95 } 96 97 public void onConfigurationChanged() { 98 FontSizeUtils.updateFontSize(mFooterText, R.dimen.qs_tile_text_size); 99 } 100 101 public View getView() { 102 return mRootView; 103 } 104 105 public boolean hasFooter() { 106 return mRootView.getVisibility() != View.GONE; 107 } 108 109 @Override 110 public void onClick(View v) { 111 mHandler.sendEmptyMessage(H.CLICK); 112 } 113 114 private void handleClick() { 115 showDeviceMonitoringDialog(); 116 } 117 118 public void showDeviceMonitoringDialog() { 119 mHost.collapsePanels(); 120 // TODO: Delay dialog creation until after panels are collapsed. 121 createDialog(); 122 } 123 124 public void refreshState() { 125 mHandler.sendEmptyMessage(H.REFRESH_STATE); 126 } 127 128 private void handleRefreshState() { 129 final boolean isDeviceManaged = mSecurityController.isDeviceManaged(); 130 final boolean hasWorkProfile = mSecurityController.hasWorkProfile(); 131 final boolean hasCACerts = mSecurityController.hasCACertInCurrentUser(); 132 final boolean hasCACertsInWorkProfile = mSecurityController.hasCACertInWorkProfile(); 133 final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled(); 134 final String vpnName = mSecurityController.getPrimaryVpnName(); 135 final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName(); 136 final CharSequence organizationName = mSecurityController.getDeviceOwnerOrganizationName(); 137 final CharSequence workProfileName = mSecurityController.getWorkProfileOrganizationName(); 138 // Update visibility of footer 139 mIsVisible = isDeviceManaged || hasCACerts || hasCACertsInWorkProfile || 140 vpnName != null || vpnNameWorkProfile != null; 141 // Update the string 142 mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile, 143 hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName, 144 vpnNameWorkProfile, organizationName, workProfileName); 145 // Update the icon 146 int footerIconId = vpnName != null || vpnNameWorkProfile != null 147 ? R.drawable.ic_qs_vpn 148 : R.drawable.ic_info_outline; 149 if (mFooterIconId != footerIconId) { 150 mFooterIconId = footerIconId; 151 mMainHandler.post(mUpdateIcon); 152 } 153 mMainHandler.post(mUpdateDisplayState); 154 } 155 156 protected CharSequence getFooterText(boolean isDeviceManaged, boolean hasWorkProfile, 157 boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled, 158 String vpnName, String vpnNameWorkProfile, CharSequence organizationName, 159 CharSequence workProfileName) { 160 if (isDeviceManaged) { 161 if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) { 162 if (organizationName == null) { 163 return mContext.getString( 164 R.string.quick_settings_disclosure_management_monitoring); 165 } 166 return mContext.getString( 167 R.string.quick_settings_disclosure_named_management_monitoring, 168 organizationName); 169 } 170 if (vpnName != null && vpnNameWorkProfile != null) { 171 if (organizationName == null) { 172 return mContext.getString(R.string.quick_settings_disclosure_management_vpns); 173 } 174 return mContext.getString(R.string.quick_settings_disclosure_named_management_vpns, 175 organizationName); 176 } 177 if (vpnName != null || vpnNameWorkProfile != null) { 178 if (organizationName == null) { 179 return mContext.getString( 180 R.string.quick_settings_disclosure_management_named_vpn, 181 vpnName != null ? vpnName : vpnNameWorkProfile); 182 } 183 return mContext.getString( 184 R.string.quick_settings_disclosure_named_management_named_vpn, 185 organizationName, 186 vpnName != null ? vpnName : vpnNameWorkProfile); 187 } 188 if (organizationName == null) { 189 return mContext.getString(R.string.quick_settings_disclosure_management); 190 } 191 return mContext.getString(R.string.quick_settings_disclosure_named_management, 192 organizationName); 193 } // end if(isDeviceManaged) 194 if (hasCACertsInWorkProfile) { 195 if (workProfileName == null) { 196 return mContext.getString( 197 R.string.quick_settings_disclosure_managed_profile_monitoring); 198 } 199 return mContext.getString( 200 R.string.quick_settings_disclosure_named_managed_profile_monitoring, 201 workProfileName); 202 } 203 if (hasCACerts) { 204 return mContext.getString(R.string.quick_settings_disclosure_monitoring); 205 } 206 if (vpnName != null && vpnNameWorkProfile != null) { 207 return mContext.getString(R.string.quick_settings_disclosure_vpns); 208 } 209 if (vpnNameWorkProfile != null) { 210 return mContext.getString(R.string.quick_settings_disclosure_managed_profile_named_vpn, 211 vpnNameWorkProfile); 212 } 213 if (vpnName != null) { 214 if (hasWorkProfile) { 215 return mContext.getString( 216 R.string.quick_settings_disclosure_personal_profile_named_vpn, 217 vpnName); 218 } 219 return mContext.getString(R.string.quick_settings_disclosure_named_vpn, 220 vpnName); 221 } 222 return null; 223 } 224 225 @Override 226 public void onClick(DialogInterface dialog, int which) { 227 if (which == DialogInterface.BUTTON_NEGATIVE) { 228 final Intent intent = new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS); 229 mDialog.dismiss(); 230 mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); 231 } 232 } 233 234 private void createDialog() { 235 final boolean isDeviceManaged = mSecurityController.isDeviceManaged(); 236 final boolean hasWorkProfile = mSecurityController.hasWorkProfile(); 237 final CharSequence deviceOwnerOrganization = 238 mSecurityController.getDeviceOwnerOrganizationName(); 239 final boolean hasCACerts = mSecurityController.hasCACertInCurrentUser(); 240 final boolean hasCACertsInWorkProfile = mSecurityController.hasCACertInWorkProfile(); 241 final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled(); 242 final String vpnName = mSecurityController.getPrimaryVpnName(); 243 final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName(); 244 245 mDialog = new SystemUIDialog(mContext); 246 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 247 View dialogView = LayoutInflater.from(mContext) 248 .inflate(R.layout.quick_settings_footer_dialog, null, false); 249 mDialog.setView(dialogView); 250 mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this); 251 252 // device management section 253 CharSequence managementMessage = getManagementMessage(isDeviceManaged, 254 deviceOwnerOrganization); 255 if (managementMessage == null) { 256 dialogView.findViewById(R.id.device_management_disclosures).setVisibility(View.GONE); 257 } else { 258 dialogView.findViewById(R.id.device_management_disclosures).setVisibility(View.VISIBLE); 259 TextView deviceManagementWarning = 260 (TextView) dialogView.findViewById(R.id.device_management_warning); 261 deviceManagementWarning.setText(managementMessage); 262 mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this); 263 } 264 265 // ca certificate section 266 CharSequence caCertsMessage = getCaCertsMessage(isDeviceManaged, hasCACerts, 267 hasCACertsInWorkProfile); 268 if (caCertsMessage == null) { 269 dialogView.findViewById(R.id.ca_certs_disclosures).setVisibility(View.GONE); 270 } else { 271 dialogView.findViewById(R.id.ca_certs_disclosures).setVisibility(View.VISIBLE); 272 TextView caCertsWarning = (TextView) dialogView.findViewById(R.id.ca_certs_warning); 273 caCertsWarning.setText(caCertsMessage); 274 // Make "Open trusted credentials"-link clickable 275 caCertsWarning.setMovementMethod(new LinkMovementMethod()); 276 } 277 278 // network logging section 279 CharSequence networkLoggingMessage = getNetworkLoggingMessage(isNetworkLoggingEnabled); 280 if (networkLoggingMessage == null) { 281 dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.GONE); 282 } else { 283 dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.VISIBLE); 284 TextView networkLoggingWarning = 285 (TextView) dialogView.findViewById(R.id.network_logging_warning); 286 networkLoggingWarning.setText(networkLoggingMessage); 287 } 288 289 // vpn section 290 CharSequence vpnMessage = getVpnMessage(isDeviceManaged, hasWorkProfile, vpnName, 291 vpnNameWorkProfile); 292 if (vpnMessage == null) { 293 dialogView.findViewById(R.id.vpn_disclosures).setVisibility(View.GONE); 294 } else { 295 dialogView.findViewById(R.id.vpn_disclosures).setVisibility(View.VISIBLE); 296 TextView vpnWarning = (TextView) dialogView.findViewById(R.id.vpn_warning); 297 vpnWarning.setText(vpnMessage); 298 // Make "Open VPN Settings"-link clickable 299 vpnWarning.setMovementMethod(new LinkMovementMethod()); 300 } 301 302 mDialog.show(); 303 mDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, 304 ViewGroup.LayoutParams.WRAP_CONTENT); 305 } 306 307 private String getSettingsButton() { 308 return mContext.getString(R.string.monitoring_button_view_policies); 309 } 310 311 private String getPositiveButton() { 312 return mContext.getString(R.string.quick_settings_done); 313 } 314 315 protected CharSequence getManagementMessage(boolean isDeviceManaged, 316 CharSequence organizationName) { 317 if (!isDeviceManaged) return null; 318 if (organizationName != null) 319 return mContext.getString( 320 R.string.monitoring_description_named_management, organizationName); 321 return mContext.getString(R.string.monitoring_description_management); 322 } 323 324 protected CharSequence getCaCertsMessage(boolean isDeviceManaged, boolean hasCACerts, 325 boolean hasCACertsInWorkProfile) { 326 if (!(hasCACerts || hasCACertsInWorkProfile)) return null; 327 if (isDeviceManaged) { 328 return mContext.getString(R.string.monitoring_description_management_ca_certificate); 329 } 330 if (hasCACertsInWorkProfile) { 331 return mContext.getString( 332 R.string.monitoring_description_managed_profile_ca_certificate); 333 } 334 return mContext.getString(R.string.monitoring_description_ca_certificate); 335 } 336 337 protected CharSequence getNetworkLoggingMessage(boolean isNetworkLoggingEnabled) { 338 if (!isNetworkLoggingEnabled) return null; 339 return mContext.getString(R.string.monitoring_description_management_network_logging); 340 } 341 342 protected CharSequence getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile, 343 String vpnName, String vpnNameWorkProfile) { 344 if (vpnName == null && vpnNameWorkProfile == null) return null; 345 final SpannableStringBuilder message = new SpannableStringBuilder(); 346 if (isDeviceManaged) { 347 if (vpnName != null && vpnNameWorkProfile != null) { 348 message.append(mContext.getString(R.string.monitoring_description_two_named_vpns, 349 vpnName, vpnNameWorkProfile)); 350 } else { 351 message.append(mContext.getString(R.string.monitoring_description_named_vpn, 352 vpnName != null ? vpnName : vpnNameWorkProfile)); 353 } 354 } else { 355 if (vpnName != null && vpnNameWorkProfile != null) { 356 message.append(mContext.getString(R.string.monitoring_description_two_named_vpns, 357 vpnName, vpnNameWorkProfile)); 358 } else if (vpnNameWorkProfile != null) { 359 message.append(mContext.getString( 360 R.string.monitoring_description_managed_profile_named_vpn, 361 vpnNameWorkProfile)); 362 } else if (hasWorkProfile) { 363 message.append(mContext.getString( 364 R.string.monitoring_description_personal_profile_named_vpn, vpnName)); 365 } else { 366 message.append(mContext.getString(R.string.monitoring_description_named_vpn, 367 vpnName)); 368 } 369 } 370 message.append(mContext.getString(R.string.monitoring_description_vpn_settings_separator)); 371 message.append(mContext.getString(R.string.monitoring_description_vpn_settings), 372 new VpnSpan(), 0); 373 return message; 374 } 375 376 private int getTitle(String deviceOwner) { 377 if (deviceOwner != null) { 378 return R.string.monitoring_title_device_owned; 379 } else { 380 return R.string.monitoring_title; 381 } 382 } 383 384 private final Runnable mUpdateIcon = new Runnable() { 385 @Override 386 public void run() { 387 mFooterIcon.setImageResource(mFooterIconId); 388 } 389 }; 390 391 private final Runnable mUpdateDisplayState = new Runnable() { 392 @Override 393 public void run() { 394 if (mFooterTextContent != null) { 395 mFooterText.setText(mFooterTextContent); 396 } 397 mRootView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE); 398 if (mDivider != null) mDivider.setVisibility(mIsVisible ? View.GONE : View.VISIBLE); 399 } 400 }; 401 402 private class Callback implements SecurityController.SecurityControllerCallback { 403 @Override 404 public void onStateChanged() { 405 refreshState(); 406 } 407 } 408 409 private class H extends Handler { 410 private static final int CLICK = 0; 411 private static final int REFRESH_STATE = 1; 412 413 private H(Looper looper) { 414 super(looper); 415 } 416 417 @Override 418 public void handleMessage(Message msg) { 419 String name = null; 420 try { 421 if (msg.what == REFRESH_STATE) { 422 name = "handleRefreshState"; 423 handleRefreshState(); 424 } else if (msg.what == CLICK) { 425 name = "handleClick"; 426 handleClick(); 427 } 428 } catch (Throwable t) { 429 final String error = "Error in " + name; 430 Log.w(TAG, error, t); 431 mHost.warn(error, t); 432 } 433 } 434 } 435 436 protected class VpnSpan extends ClickableSpan { 437 @Override 438 public void onClick(View widget) { 439 final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS); 440 mDialog.dismiss(); 441 mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); 442 } 443 444 // for testing, to compare two CharSequences containing VpnSpans 445 @Override 446 public boolean equals(Object object) { 447 return object instanceof VpnSpan; 448 } 449 450 @Override 451 public int hashCode() { 452 return 314159257; // prime 453 } 454 } 455 } 456