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