• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
QSSecurityFooter(QSPanel qsPanel, Context context)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 
setHostEnvironment(QSTileHost host)92     public void setHostEnvironment(QSTileHost host) {
93         mHost = host;
94     }
95 
setListening(boolean listening)96     public void setListening(boolean listening) {
97         if (listening) {
98             mSecurityController.addCallback(mCallback);
99         } else {
100             mSecurityController.removeCallback(mCallback);
101         }
102     }
103 
onConfigurationChanged()104     public void onConfigurationChanged() {
105         FontSizeUtils.updateFontSize(mFooterText, R.dimen.qs_tile_text_size);
106     }
107 
getView()108     public View getView() {
109         return mRootView;
110     }
111 
hasFooter()112     public boolean hasFooter() {
113         return mRootView.getVisibility() != View.GONE;
114     }
115 
116     @Override
onClick(View v)117     public void onClick(View v) {
118         mHandler.sendEmptyMessage(H.CLICK);
119     }
120 
handleClick()121     private void handleClick() {
122         showDeviceMonitoringDialog();
123     }
124 
showDeviceMonitoringDialog()125     public void showDeviceMonitoringDialog() {
126         mHost.collapsePanels();
127         // TODO: Delay dialog creation until after panels are collapsed.
128         createDialog();
129     }
130 
refreshState()131     public void refreshState() {
132         mHandler.sendEmptyMessage(H.REFRESH_STATE);
133     }
134 
handleRefreshState()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 
getFooterText(boolean isDeviceManaged, boolean hasWorkProfile, boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled, String vpnName, String vpnNameWorkProfile, CharSequence organizationName, CharSequence workProfileName)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
onClick(DialogInterface dialog, int which)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 
createDialog()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 
configSubtitleVisibility(boolean showDeviceManagement, boolean showCaCerts, boolean showNetworkLogging, boolean showVpn, View dialogView)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 
getSettingsButton()359     private String getSettingsButton() {
360         return mContext.getString(R.string.monitoring_button_view_policies);
361     }
362 
getPositiveButton()363     private String getPositiveButton() {
364         return mContext.getString(R.string.ok);
365     }
366 
getManagementMessage(boolean isDeviceManaged, CharSequence organizationName)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 
getCaCertsMessage(boolean isDeviceManaged, boolean hasCACerts, boolean hasCACertsInWorkProfile)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 
getNetworkLoggingMessage(boolean isNetworkLoggingEnabled)389     protected CharSequence getNetworkLoggingMessage(boolean isNetworkLoggingEnabled) {
390         if (!isNetworkLoggingEnabled) return null;
391         return mContext.getString(R.string.monitoring_description_management_network_logging);
392     }
393 
getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile, String vpnName, String vpnNameWorkProfile)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 
getTitle(String deviceOwner)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
onStateChanged()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 
H(Looper looper)465         private H(Looper looper) {
466             super(looper);
467         }
468 
469         @Override
handleMessage(Message msg)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
onClick(View widget)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
equals(Object object)498         public boolean equals(Object object) {
499             return object instanceof VpnSpan;
500         }
501 
502         @Override
hashCode()503         public int hashCode() {
504             return 314159257; // prime
505         }
506     }
507 }
508