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 QSSecurityFooter(QSPanel qsPanel, Context context)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 setHostEnvironment(QSTileHost host)85 public void setHostEnvironment(QSTileHost host) { 86 mHost = host; 87 } 88 setListening(boolean listening)89 public void setListening(boolean listening) { 90 if (listening) { 91 mSecurityController.addCallback(mCallback); 92 } else { 93 mSecurityController.removeCallback(mCallback); 94 } 95 } 96 onConfigurationChanged()97 public void onConfigurationChanged() { 98 FontSizeUtils.updateFontSize(mFooterText, R.dimen.qs_tile_text_size); 99 } 100 getView()101 public View getView() { 102 return mRootView; 103 } 104 hasFooter()105 public boolean hasFooter() { 106 return mRootView.getVisibility() != View.GONE; 107 } 108 109 @Override onClick(View v)110 public void onClick(View v) { 111 mHandler.sendEmptyMessage(H.CLICK); 112 } 113 handleClick()114 private void handleClick() { 115 showDeviceMonitoringDialog(); 116 } 117 showDeviceMonitoringDialog()118 public void showDeviceMonitoringDialog() { 119 mHost.collapsePanels(); 120 // TODO: Delay dialog creation until after panels are collapsed. 121 createDialog(); 122 } 123 refreshState()124 public void refreshState() { 125 mHandler.sendEmptyMessage(H.REFRESH_STATE); 126 } 127 handleRefreshState()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 getFooterText(boolean isDeviceManaged, boolean hasWorkProfile, boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled, String vpnName, String vpnNameWorkProfile, CharSequence organizationName, CharSequence workProfileName)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 onClick(DialogInterface dialog, int which)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 createDialog()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 getSettingsButton()307 private String getSettingsButton() { 308 return mContext.getString(R.string.monitoring_button_view_policies); 309 } 310 getPositiveButton()311 private String getPositiveButton() { 312 return mContext.getString(R.string.quick_settings_done); 313 } 314 getManagementMessage(boolean isDeviceManaged, CharSequence organizationName)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 getCaCertsMessage(boolean isDeviceManaged, boolean hasCACerts, boolean hasCACertsInWorkProfile)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 getNetworkLoggingMessage(boolean isNetworkLoggingEnabled)337 protected CharSequence getNetworkLoggingMessage(boolean isNetworkLoggingEnabled) { 338 if (!isNetworkLoggingEnabled) return null; 339 return mContext.getString(R.string.monitoring_description_management_network_logging); 340 } 341 getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile, String vpnName, String vpnNameWorkProfile)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 getTitle(String deviceOwner)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 onStateChanged()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 H(Looper looper)413 private H(Looper looper) { 414 super(looper); 415 } 416 417 @Override handleMessage(Message msg)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 onClick(View widget)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 equals(Object object)446 public boolean equals(Object object) { 447 return object instanceof VpnSpan; 448 } 449 450 @Override hashCode()451 public int hashCode() { 452 return 314159257; // prime 453 } 454 } 455 } 456