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