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