| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.systemui.qs; |
| |
| import android.app.ActivityManager; |
| import android.app.AlertDialog; |
| import android.app.admin.DevicePolicyEventLogger; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.pm.UserInfo; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.UserManager; |
| import android.provider.Settings; |
| import android.text.SpannableStringBuilder; |
| import android.text.method.LinkMovementMethod; |
| import android.text.style.ClickableSpan; |
| import android.util.Log; |
| import android.util.StatsLog; |
| import android.view.ContextThemeWrapper; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.view.ViewGroup; |
| import android.view.Window; |
| import android.widget.ImageView; |
| import android.widget.TextView; |
| |
| import com.android.systemui.Dependency; |
| import com.android.systemui.FontSizeUtils; |
| import com.android.systemui.R; |
| import com.android.systemui.plugins.ActivityStarter; |
| import com.android.systemui.statusbar.phone.SystemUIDialog; |
| import com.android.systemui.statusbar.policy.SecurityController; |
| |
| public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener { |
| protected static final String TAG = "QSSecurityFooter"; |
| protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); |
| |
| private final View mRootView; |
| private final TextView mFooterText; |
| private final ImageView mFooterIcon; |
| private final Context mContext; |
| private final Callback mCallback = new Callback(); |
| private final SecurityController mSecurityController; |
| private final ActivityStarter mActivityStarter; |
| private final Handler mMainHandler; |
| private final View mDivider; |
| |
| private final UserManager mUm; |
| |
| private AlertDialog mDialog; |
| private QSTileHost mHost; |
| protected H mHandler; |
| |
| private boolean mIsVisible; |
| private CharSequence mFooterTextContent = null; |
| private int mFooterTextId; |
| private int mFooterIconId; |
| |
| public QSSecurityFooter(QSPanel qsPanel, Context context) { |
| mRootView = LayoutInflater.from(context) |
| .inflate(R.layout.quick_settings_footer, qsPanel, false); |
| mRootView.setOnClickListener(this); |
| mFooterText = (TextView) mRootView.findViewById(R.id.footer_text); |
| mFooterIcon = (ImageView) mRootView.findViewById(R.id.footer_icon); |
| mFooterIconId = R.drawable.ic_info_outline; |
| mContext = context; |
| mMainHandler = new Handler(Looper.myLooper()); |
| mActivityStarter = Dependency.get(ActivityStarter.class); |
| mSecurityController = Dependency.get(SecurityController.class); |
| mHandler = new H(Dependency.get(Dependency.BG_LOOPER)); |
| mDivider = qsPanel == null ? null : qsPanel.getDivider(); |
| mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); |
| } |
| |
| public void setHostEnvironment(QSTileHost host) { |
| mHost = host; |
| } |
| |
| public void setListening(boolean listening) { |
| if (listening) { |
| mSecurityController.addCallback(mCallback); |
| refreshState(); |
| } else { |
| mSecurityController.removeCallback(mCallback); |
| } |
| } |
| |
| public void onConfigurationChanged() { |
| FontSizeUtils.updateFontSize(mFooterText, R.dimen.qs_tile_text_size); |
| } |
| |
| public View getView() { |
| return mRootView; |
| } |
| |
| public boolean hasFooter() { |
| return mRootView.getVisibility() != View.GONE; |
| } |
| |
| @Override |
| public void onClick(View v) { |
| mHandler.sendEmptyMessage(H.CLICK); |
| } |
| |
| private void handleClick() { |
| showDeviceMonitoringDialog(); |
| DevicePolicyEventLogger |
| .createEvent(StatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED) |
| .write(); |
| } |
| |
| public void showDeviceMonitoringDialog() { |
| mHost.collapsePanels(); |
| // TODO: Delay dialog creation until after panels are collapsed. |
| createDialog(); |
| } |
| |
| public void refreshState() { |
| mHandler.sendEmptyMessage(H.REFRESH_STATE); |
| } |
| |
| private void handleRefreshState() { |
| final boolean isDeviceManaged = mSecurityController.isDeviceManaged(); |
| final UserInfo currentUser = mUm.getUserInfo(ActivityManager.getCurrentUser()); |
| final boolean isDemoDevice = UserManager.isDeviceInDemoMode(mContext) && currentUser != null |
| && currentUser.isDemo(); |
| final boolean hasWorkProfile = mSecurityController.hasWorkProfile(); |
| final boolean hasCACerts = mSecurityController.hasCACertInCurrentUser(); |
| final boolean hasCACertsInWorkProfile = mSecurityController.hasCACertInWorkProfile(); |
| final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled(); |
| final String vpnName = mSecurityController.getPrimaryVpnName(); |
| final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName(); |
| final CharSequence organizationName = mSecurityController.getDeviceOwnerOrganizationName(); |
| final CharSequence workProfileName = mSecurityController.getWorkProfileOrganizationName(); |
| // Update visibility of footer |
| mIsVisible = (isDeviceManaged && !isDemoDevice) || hasCACerts || hasCACertsInWorkProfile || |
| vpnName != null || vpnNameWorkProfile != null; |
| // Update the string |
| mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile, |
| hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName, |
| vpnNameWorkProfile, organizationName, workProfileName); |
| // Update the icon |
| int footerIconId = R.drawable.ic_info_outline; |
| if (vpnName != null || vpnNameWorkProfile != null) { |
| if (mSecurityController.isVpnBranded()) { |
| footerIconId = R.drawable.stat_sys_branded_vpn; |
| } else { |
| footerIconId = R.drawable.stat_sys_vpn_ic; |
| } |
| } |
| if (mFooterIconId != footerIconId) { |
| mFooterIconId = footerIconId; |
| mMainHandler.post(mUpdateIcon); |
| } |
| mMainHandler.post(mUpdateDisplayState); |
| } |
| |
| protected CharSequence getFooterText(boolean isDeviceManaged, boolean hasWorkProfile, |
| boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled, |
| String vpnName, String vpnNameWorkProfile, CharSequence organizationName, |
| CharSequence workProfileName) { |
| if (isDeviceManaged) { |
| if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) { |
| if (organizationName == null) { |
| return mContext.getString( |
| R.string.quick_settings_disclosure_management_monitoring); |
| } |
| return mContext.getString( |
| R.string.quick_settings_disclosure_named_management_monitoring, |
| organizationName); |
| } |
| if (vpnName != null && vpnNameWorkProfile != null) { |
| if (organizationName == null) { |
| return mContext.getString(R.string.quick_settings_disclosure_management_vpns); |
| } |
| return mContext.getString(R.string.quick_settings_disclosure_named_management_vpns, |
| organizationName); |
| } |
| if (vpnName != null || vpnNameWorkProfile != null) { |
| if (organizationName == null) { |
| return mContext.getString( |
| R.string.quick_settings_disclosure_management_named_vpn, |
| vpnName != null ? vpnName : vpnNameWorkProfile); |
| } |
| return mContext.getString( |
| R.string.quick_settings_disclosure_named_management_named_vpn, |
| organizationName, |
| vpnName != null ? vpnName : vpnNameWorkProfile); |
| } |
| if (organizationName == null) { |
| return mContext.getString(R.string.quick_settings_disclosure_management); |
| } |
| return mContext.getString(R.string.quick_settings_disclosure_named_management, |
| organizationName); |
| } // end if(isDeviceManaged) |
| if (hasCACertsInWorkProfile) { |
| if (workProfileName == null) { |
| return mContext.getString( |
| R.string.quick_settings_disclosure_managed_profile_monitoring); |
| } |
| return mContext.getString( |
| R.string.quick_settings_disclosure_named_managed_profile_monitoring, |
| workProfileName); |
| } |
| if (hasCACerts) { |
| return mContext.getString(R.string.quick_settings_disclosure_monitoring); |
| } |
| if (vpnName != null && vpnNameWorkProfile != null) { |
| return mContext.getString(R.string.quick_settings_disclosure_vpns); |
| } |
| if (vpnNameWorkProfile != null) { |
| return mContext.getString(R.string.quick_settings_disclosure_managed_profile_named_vpn, |
| vpnNameWorkProfile); |
| } |
| if (vpnName != null) { |
| if (hasWorkProfile) { |
| return mContext.getString( |
| R.string.quick_settings_disclosure_personal_profile_named_vpn, |
| vpnName); |
| } |
| return mContext.getString(R.string.quick_settings_disclosure_named_vpn, |
| vpnName); |
| } |
| return null; |
| } |
| |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| if (which == DialogInterface.BUTTON_NEGATIVE) { |
| final Intent intent = new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS); |
| mDialog.dismiss(); |
| mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); |
| } |
| } |
| |
| private void createDialog() { |
| final boolean isDeviceManaged = mSecurityController.isDeviceManaged(); |
| final boolean hasWorkProfile = mSecurityController.hasWorkProfile(); |
| final CharSequence deviceOwnerOrganization = |
| mSecurityController.getDeviceOwnerOrganizationName(); |
| final boolean hasCACerts = mSecurityController.hasCACertInCurrentUser(); |
| final boolean hasCACertsInWorkProfile = mSecurityController.hasCACertInWorkProfile(); |
| final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled(); |
| final String vpnName = mSecurityController.getPrimaryVpnName(); |
| final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName(); |
| |
| mDialog = new SystemUIDialog(mContext); |
| mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); |
| View dialogView = LayoutInflater.from( |
| new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_Dialog)) |
| .inflate(R.layout.quick_settings_footer_dialog, null, false); |
| mDialog.setView(dialogView); |
| mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this); |
| |
| // device management section |
| CharSequence managementMessage = getManagementMessage(isDeviceManaged, |
| deviceOwnerOrganization); |
| if (managementMessage == null) { |
| dialogView.findViewById(R.id.device_management_disclosures).setVisibility(View.GONE); |
| } else { |
| dialogView.findViewById(R.id.device_management_disclosures).setVisibility(View.VISIBLE); |
| TextView deviceManagementWarning = |
| (TextView) dialogView.findViewById(R.id.device_management_warning); |
| deviceManagementWarning.setText(managementMessage); |
| mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this); |
| } |
| |
| // ca certificate section |
| CharSequence caCertsMessage = getCaCertsMessage(isDeviceManaged, hasCACerts, |
| hasCACertsInWorkProfile); |
| if (caCertsMessage == null) { |
| dialogView.findViewById(R.id.ca_certs_disclosures).setVisibility(View.GONE); |
| } else { |
| dialogView.findViewById(R.id.ca_certs_disclosures).setVisibility(View.VISIBLE); |
| TextView caCertsWarning = (TextView) dialogView.findViewById(R.id.ca_certs_warning); |
| caCertsWarning.setText(caCertsMessage); |
| // Make "Open trusted credentials"-link clickable |
| caCertsWarning.setMovementMethod(new LinkMovementMethod()); |
| } |
| |
| // network logging section |
| CharSequence networkLoggingMessage = getNetworkLoggingMessage(isNetworkLoggingEnabled); |
| if (networkLoggingMessage == null) { |
| dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.GONE); |
| } else { |
| dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.VISIBLE); |
| TextView networkLoggingWarning = |
| (TextView) dialogView.findViewById(R.id.network_logging_warning); |
| networkLoggingWarning.setText(networkLoggingMessage); |
| } |
| |
| // vpn section |
| CharSequence vpnMessage = getVpnMessage(isDeviceManaged, hasWorkProfile, vpnName, |
| vpnNameWorkProfile); |
| if (vpnMessage == null) { |
| dialogView.findViewById(R.id.vpn_disclosures).setVisibility(View.GONE); |
| } else { |
| dialogView.findViewById(R.id.vpn_disclosures).setVisibility(View.VISIBLE); |
| TextView vpnWarning = (TextView) dialogView.findViewById(R.id.vpn_warning); |
| vpnWarning.setText(vpnMessage); |
| // Make "Open VPN Settings"-link clickable |
| vpnWarning.setMovementMethod(new LinkMovementMethod()); |
| } |
| |
| // Note: if a new section is added, should update configSubtitleVisibility to include |
| // the handling of the subtitle |
| configSubtitleVisibility(managementMessage != null, |
| caCertsMessage != null, |
| networkLoggingMessage != null, |
| vpnMessage != null, |
| dialogView); |
| |
| mDialog.show(); |
| mDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, |
| ViewGroup.LayoutParams.WRAP_CONTENT); |
| } |
| |
| protected void configSubtitleVisibility(boolean showDeviceManagement, boolean showCaCerts, |
| boolean showNetworkLogging, boolean showVpn, View dialogView) { |
| // Device Management title should always been shown |
| // When there is a Device Management message, all subtitles should be shown |
| if (showDeviceManagement) { |
| return; |
| } |
| // Hide the subtitle if there is only 1 message shown |
| int mSectionCountExcludingDeviceMgt = 0; |
| if (showCaCerts) { mSectionCountExcludingDeviceMgt++; } |
| if (showNetworkLogging) { mSectionCountExcludingDeviceMgt++; } |
| if (showVpn) { mSectionCountExcludingDeviceMgt++; } |
| |
| // No work needed if there is no sections or more than 1 section |
| if (mSectionCountExcludingDeviceMgt != 1) { |
| return; |
| } |
| if (showCaCerts) { |
| dialogView.findViewById(R.id.ca_certs_subtitle).setVisibility(View.GONE); |
| } |
| if (showNetworkLogging) { |
| dialogView.findViewById(R.id.network_logging_subtitle).setVisibility(View.GONE); |
| } |
| if (showVpn) { |
| dialogView.findViewById(R.id.vpn_subtitle).setVisibility(View.GONE); |
| } |
| } |
| |
| private String getSettingsButton() { |
| return mContext.getString(R.string.monitoring_button_view_policies); |
| } |
| |
| private String getPositiveButton() { |
| return mContext.getString(R.string.ok); |
| } |
| |
| protected CharSequence getManagementMessage(boolean isDeviceManaged, |
| CharSequence organizationName) { |
| if (!isDeviceManaged) return null; |
| if (organizationName != null) |
| return mContext.getString( |
| R.string.monitoring_description_named_management, organizationName); |
| return mContext.getString(R.string.monitoring_description_management); |
| } |
| |
| protected CharSequence getCaCertsMessage(boolean isDeviceManaged, boolean hasCACerts, |
| boolean hasCACertsInWorkProfile) { |
| if (!(hasCACerts || hasCACertsInWorkProfile)) return null; |
| if (isDeviceManaged) { |
| return mContext.getString(R.string.monitoring_description_management_ca_certificate); |
| } |
| if (hasCACertsInWorkProfile) { |
| return mContext.getString( |
| R.string.monitoring_description_managed_profile_ca_certificate); |
| } |
| return mContext.getString(R.string.monitoring_description_ca_certificate); |
| } |
| |
| protected CharSequence getNetworkLoggingMessage(boolean isNetworkLoggingEnabled) { |
| if (!isNetworkLoggingEnabled) return null; |
| return mContext.getString(R.string.monitoring_description_management_network_logging); |
| } |
| |
| protected CharSequence getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile, |
| String vpnName, String vpnNameWorkProfile) { |
| if (vpnName == null && vpnNameWorkProfile == null) return null; |
| final SpannableStringBuilder message = new SpannableStringBuilder(); |
| if (isDeviceManaged) { |
| if (vpnName != null && vpnNameWorkProfile != null) { |
| message.append(mContext.getString(R.string.monitoring_description_two_named_vpns, |
| vpnName, vpnNameWorkProfile)); |
| } else { |
| message.append(mContext.getString(R.string.monitoring_description_named_vpn, |
| vpnName != null ? vpnName : vpnNameWorkProfile)); |
| } |
| } else { |
| if (vpnName != null && vpnNameWorkProfile != null) { |
| message.append(mContext.getString(R.string.monitoring_description_two_named_vpns, |
| vpnName, vpnNameWorkProfile)); |
| } else if (vpnNameWorkProfile != null) { |
| message.append(mContext.getString( |
| R.string.monitoring_description_managed_profile_named_vpn, |
| vpnNameWorkProfile)); |
| } else if (hasWorkProfile) { |
| message.append(mContext.getString( |
| R.string.monitoring_description_personal_profile_named_vpn, vpnName)); |
| } else { |
| message.append(mContext.getString(R.string.monitoring_description_named_vpn, |
| vpnName)); |
| } |
| } |
| message.append(mContext.getString(R.string.monitoring_description_vpn_settings_separator)); |
| message.append(mContext.getString(R.string.monitoring_description_vpn_settings), |
| new VpnSpan(), 0); |
| return message; |
| } |
| |
| private int getTitle(String deviceOwner) { |
| if (deviceOwner != null) { |
| return R.string.monitoring_title_device_owned; |
| } else { |
| return R.string.monitoring_title; |
| } |
| } |
| |
| private final Runnable mUpdateIcon = new Runnable() { |
| @Override |
| public void run() { |
| mFooterIcon.setImageResource(mFooterIconId); |
| } |
| }; |
| |
| private final Runnable mUpdateDisplayState = new Runnable() { |
| @Override |
| public void run() { |
| if (mFooterTextContent != null) { |
| mFooterText.setText(mFooterTextContent); |
| } |
| mRootView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE); |
| if (mDivider != null) mDivider.setVisibility(mIsVisible ? View.GONE : View.VISIBLE); |
| } |
| }; |
| |
| private class Callback implements SecurityController.SecurityControllerCallback { |
| @Override |
| public void onStateChanged() { |
| refreshState(); |
| } |
| } |
| |
| private class H extends Handler { |
| private static final int CLICK = 0; |
| private static final int REFRESH_STATE = 1; |
| |
| private H(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| String name = null; |
| try { |
| if (msg.what == REFRESH_STATE) { |
| name = "handleRefreshState"; |
| handleRefreshState(); |
| } else if (msg.what == CLICK) { |
| name = "handleClick"; |
| handleClick(); |
| } |
| } catch (Throwable t) { |
| final String error = "Error in " + name; |
| Log.w(TAG, error, t); |
| mHost.warn(error, t); |
| } |
| } |
| } |
| |
| protected class VpnSpan extends ClickableSpan { |
| @Override |
| public void onClick(View widget) { |
| final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS); |
| mDialog.dismiss(); |
| mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); |
| } |
| |
| // for testing, to compare two CharSequences containing VpnSpans |
| @Override |
| public boolean equals(Object object) { |
| return object instanceof VpnSpan; |
| } |
| |
| @Override |
| public int hashCode() { |
| return 314159257; // prime |
| } |
| } |
| } |