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.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