Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8e179a40 authored by Jason Parks's avatar Jason Parks
Browse files

Parental Controls disclaimer

Bug: 161861348
Test: atest SystemUITests && manual
Change-Id: Ie6844de48d851431c6f76c25c28398f99d5dc52f
parent e1608458
Loading
Loading
Loading
Loading
+60 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
** Copyright 2020, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clipToPadding="false">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="?android:attr/dialogPreferredPadding"
        android:paddingRight="?android:attr/dialogPreferredPadding"
        android:paddingLeft="?android:attr/dialogPreferredPadding"
        android:orientation="vertical">
        <ImageView
            android:id="@+id/parental_controls_icon"
            android:layout_width="36dip"
            android:layout_height="36dip"
            android:layout_gravity="center_horizontal"

        />
        <LinearLayout
            android:id="@+id/parental_controls_disclosures"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="?android:attr/dialogPreferredPadding"
            android:orientation="vertical">
            <TextView
                android:id="@+id/parental_controls_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/monitoring_title_device_owned"
                style="@style/DeviceManagementDialogTitle"
                android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
            />
            <TextView
                android:id="@+id/parental_controls_warning"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textAppearance="@style/TextAppearance.DeviceManagementDialog.Content"
                android:text="@string/monitoring_description_parental_controls"
            />
        </LinearLayout>
    </LinearLayout>
</ScrollView>
+10 −0
Original line number Diff line number Diff line
@@ -1286,6 +1286,9 @@
    <!-- Footer vpn present text [CHAR LIMIT=50] -->
    <string name="branded_vpn_footer">Network may be monitored</string>

    <!-- Disclosure at the bottom of Quick Settings that indicates that parental controls are enabled. [CHAR LIMIT=100] -->
    <string name="quick_settings_disclosure_parental_controls">This device is managed by your parent</string>

    <!-- Disclosure at the bottom of Quick Settings that indicates that the user's device belongs to their organization, and the organization can monitor network traffic on that device. [CHAR LIMIT=100] -->
    <string name="quick_settings_disclosure_management_monitoring">Your organization owns this device and may monitor network traffic</string>

@@ -1359,6 +1362,9 @@
    <!-- Monitoring dialog label for button opening a page with more information on the admin's abilities [CHAR LIMIT=30] -->
    <string name="monitoring_button_view_policies">View Policies</string>

    <!-- Monitoring dialog label for button opening a page with more information on parental controls [CHAR LIMIT=30] -->
    <string name="monitoring_button_view_controls">View controls</string>

    <!-- Dialog that a user can access via Quick Settings. The dialog describes what the IT admin can monitor (and the changes they can make) on the user's device. [CHAR LIMIT=NONE]-->
    <string name="monitoring_description_named_management">This device belongs to <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g>.\n\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your IT admin.</string>

@@ -1428,6 +1434,10 @@
    <!-- Monitoring dialog VPN with profile owner text [CHAR LIMIT=400] -->
    <string name="monitoring_description_vpn_profile_owned">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour admin is capable of monitoring your network activity including emails, apps, and websites.\n\nFor more information, contact your admin.\n\nYou\'re also connected to a VPN, which can monitor your network activity.</string>

    <!-- Dialog that a user can access via Quick Settings. [CHAR LIMIT=NONE]-->
    <string name="monitoring_description_parental_controls">This device is managed by your parent. Your parent can see and manage information such as the apps you use, your location, and your screen time.</string>


    <!-- Name for a generic legacy VPN connection [CHAR LIMIT=20] -->
    <string name="legacy_vpn_name">VPN</string>

+60 −10
Original line number Diff line number Diff line
@@ -16,11 +16,13 @@
package com.android.systemui.qs;

import android.app.AlertDialog;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyEventLogger;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -39,6 +41,8 @@ import android.view.Window;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.VisibleForTesting;

import com.android.internal.util.FrameworkStatsLog;
import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
@@ -156,15 +160,16 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen
                mSecurityController.getWorkProfileOrganizationName();
        final boolean isProfileOwnerOfOrganizationOwnedDevice =
                mSecurityController.isProfileOwnerOfOrganizationOwnedDevice();
        final boolean isParentalControlsEnabled = mSecurityController.isParentalControlsEnabled();
        // Update visibility of footer
        mIsVisible = (isDeviceManaged && !isDemoDevice) || hasCACerts || hasCACertsInWorkProfile
                || vpnName != null || vpnNameWorkProfile != null
                || isProfileOwnerOfOrganizationOwnedDevice;
                || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled;
        // Update the string
        mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile,
                hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
                vpnNameWorkProfile, organizationName, workProfileOrganizationName,
                isProfileOwnerOfOrganizationOwnedDevice);
                isProfileOwnerOfOrganizationOwnedDevice, isParentalControlsEnabled);
        // Update the icon
        int footerIconId = R.drawable.ic_info_outline;
        if (vpnName != null || vpnNameWorkProfile != null) {
@@ -185,7 +190,10 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen
            boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
            String vpnName, String vpnNameWorkProfile, CharSequence organizationName,
            CharSequence workProfileOrganizationName,
            boolean isProfileOwnerOfOrganizationOwnedDevice) {
            boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isParentalControlsEnabled) {
        if (isParentalControlsEnabled) {
            return mContext.getString(R.string.quick_settings_disclosure_parental_controls);
        }
        if (isDeviceManaged || DEBUG_FORCE_VISIBLE) {
            if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) {
                if (organizationName == null) {
@@ -268,6 +276,27 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen
    }

    private void createDialog() {
        mDialog = new SystemUIDialog(mContext);
        mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
        mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getNegativeButton(), this);

        mDialog.setView(createDialogView());

        mDialog.show();
        mDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    @VisibleForTesting
    View createDialogView() {
        if (mSecurityController.isParentalControlsEnabled()) {
            return createParentalControlsDialogView();
        }
        return createOrganizationDialogView();
    }

    private View createOrganizationDialogView() {
        final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
        boolean isProfileOwnerOfOrganizationOwnedDevice =
                mSecurityController.isProfileOwnerOfOrganizationOwnedDevice();
@@ -282,13 +311,10 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen
        final String vpnName = mSecurityController.getPrimaryVpnName();
        final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName();

        mDialog = new SystemUIDialog(mContext);
        mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);

        View dialogView = LayoutInflater.from(
                new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_Dialog))
                .inflate(R.layout.quick_settings_footer_dialog, null, false);
        mDialog.setView(dialogView);
        mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);

        // device management section
        CharSequence managementMessage = getManagementMessage(isDeviceManaged,
@@ -353,9 +379,26 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen
                vpnMessage != null,
                dialogView);

        mDialog.show();
        mDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        return dialogView;
    }

    private View createParentalControlsDialogView() {
        View dialogView = LayoutInflater.from(
                new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_Dialog))
                .inflate(R.layout.quick_settings_footer_dialog_parental_controls, null, false);

        DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
        Drawable icon = mSecurityController.getIcon(info);
        if (icon != null) {
            ImageView imageView = (ImageView) dialogView.findViewById(R.id.parental_controls_icon);
            imageView.setImageDrawable(icon);
        }

        TextView parentalControlsTitle =
                (TextView) dialogView.findViewById(R.id.parental_controls_title);
        parentalControlsTitle.setText(mSecurityController.getLabel(info));

        return dialogView;
    }

    protected void configSubtitleVisibility(boolean showDeviceManagement, boolean showCaCerts,
@@ -394,6 +437,13 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen
        return mContext.getString(R.string.ok);
    }

    private String getNegativeButton() {
        if (mSecurityController.isParentalControlsEnabled()) {
            return mContext.getString(R.string.monitoring_button_view_controls);
        }
        return null;
    }

    protected CharSequence getManagementMessage(boolean isDeviceManaged,
            CharSequence organizationName, boolean isProfileOwnerOfOrganizationOwnedDevice,
            CharSequence workProfileOrganizationName) {
+12 −0
Original line number Diff line number Diff line
@@ -15,6 +15,9 @@
 */
package com.android.systemui.statusbar.policy;

import android.app.admin.DeviceAdminInfo;
import android.graphics.drawable.Drawable;

import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;

@@ -40,6 +43,15 @@ public interface SecurityController extends CallbackController<SecurityControlle
    boolean hasCACertInCurrentUser();
    boolean hasCACertInWorkProfile();
    void onUserSwitched(int newUserId);
    /** Whether or not parental controls is enabled */
    boolean isParentalControlsEnabled();
    /** DeviceAdminInfo for active admin */
    DeviceAdminInfo getDeviceAdminInfo();
    /** Icon for admin */
    Drawable getIcon(DeviceAdminInfo info);
    /** Label for admin */
    CharSequence getLabel(DeviceAdminInfo info);


    public interface SecurityControllerCallback {
        void onStateChanged();
+51 −0
Original line number Diff line number Diff line
@@ -16,15 +16,19 @@
package com.android.systemui.statusbar.policy;

import android.app.ActivityManager;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.IConnectivityManager;
@@ -53,7 +57,10 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.CurrentUserTracker;

import org.xmlpull.v1.XmlPullParserException;

import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.Executor;
@@ -306,6 +313,50 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
        fireCallbacks();
    }

    @Override
    public boolean isParentalControlsEnabled() {
        return getProfileOwnerOrDeviceOwnerSupervisionComponent() != null;
    }

    @Override
    public DeviceAdminInfo getDeviceAdminInfo() {
        return getDeviceAdminInfo(getProfileOwnerOrDeviceOwnerComponent());
    }

    @Override
    public Drawable getIcon(DeviceAdminInfo info) {
        return (info == null) ? null : info.loadIcon(mPackageManager);
    }

    @Override
    public CharSequence getLabel(DeviceAdminInfo info) {
        return (info == null) ? null : info.loadLabel(mPackageManager);
    }

    private ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent() {
        UserHandle currentUser = new UserHandle(mCurrentUserId);
        return mDevicePolicyManager
               .getProfileOwnerOrDeviceOwnerSupervisionComponent(currentUser);
    }

    // Returns the ComponentName of the current DO/PO. Right now it only checks the supervision
    // component but can be changed to check for other DO/POs. This change would make getIcon()
    // and getLabel() work for all admins.
    private ComponentName getProfileOwnerOrDeviceOwnerComponent() {
        return getProfileOwnerOrDeviceOwnerSupervisionComponent();
    }

    private DeviceAdminInfo getDeviceAdminInfo(ComponentName componentName) {
        try {
            ResolveInfo resolveInfo = new ResolveInfo();
            resolveInfo.activityInfo = mPackageManager.getReceiverInfo(componentName,
                    PackageManager.GET_META_DATA);
            return new DeviceAdminInfo(mContext, resolveInfo);
        } catch (NameNotFoundException | XmlPullParserException | IOException e) {
            return null;
        }
    }

    private void refreshCACerts(int userId) {
        mBgExecutor.execute(() -> {
            Pair<Integer, Boolean> idWithCert = null;
Loading