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

Commit c68b4be0 authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Migrate NLS access to preferencecontrollers"

parents b7076d5d d41f2db9
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -8587,7 +8587,7 @@
    <string name="recent_conversations">Recent conversations</string>
    <!-- [CHAR LIMIT=20] button title -->
    <string name="conversation_settings_clear_recents">Clear recents</string>
    <string name="conversation_settings_clear_recents">Clear all of the recent ones</string>
    <!-- a11y string -->
    <string name="clear">Clear</string>
+8 −1
Original line number Diff line number Diff line
@@ -17,11 +17,18 @@

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:key="notification_access_permission_detail_settings"
    android:title="@string/manage_notification_access_title">

    <com.android.settingslib.widget.LayoutPreference
        android:key="pref_app_header"
        android:layout="@layout/settings_entity_header"
        settings:controller="com.android.settings.applications.specialaccess.notificationaccess.HeaderPreferenceController"/>

    <com.android.settings.widget.FilterTouchesSwitchPreference
        android:key="notification_access_switch"
        android:title="@string/notification_access_detail_switch"/>
        android:title="@string/notification_access_detail_switch"
        settings:controller="com.android.settings.applications.specialaccess.notificationaccess.ApprovalPreferenceController"/>

</PreferenceScreen>
 No newline at end of file
+135 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.
 */

package com.android.settings.applications.specialaccess.notificationaccess;

import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;

import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreference;

import com.android.settings.core.BasePreferenceController;
import com.android.settings.overlay.FeatureFactory;

public class ApprovalPreferenceController extends BasePreferenceController {

    private static final String TAG = "ApprovalPrefController";

    private PackageInfo mPkgInfo;
    private ComponentName mCn;
    private PreferenceFragmentCompat mParent;
    private NotificationManager mNm;
    private PackageManager mPm;

    public ApprovalPreferenceController(Context context, String key) {
        super(context, key);
    }

    public ApprovalPreferenceController setPkgInfo(PackageInfo pkgInfo) {
        mPkgInfo = pkgInfo;
        return this;
    }

    public ApprovalPreferenceController setCn(ComponentName cn) {
        mCn = cn;
        return this;
    }

    public ApprovalPreferenceController setParent(PreferenceFragmentCompat parent) {
        mParent = parent;
        return this;
    }

    public ApprovalPreferenceController setNm(NotificationManager nm) {
        mNm = nm;
        return this;
    }

    public ApprovalPreferenceController setPm(PackageManager pm) {
        mPm = pm;
        return this;
    }

    @Override
    public int getAvailabilityStatus() {
        return AVAILABLE;
    }

    @Override
    public void updateState(Preference pref) {
        final SwitchPreference preference = (SwitchPreference) pref;
        final CharSequence label = mPkgInfo.applicationInfo.loadLabel(mPm);
        preference.setChecked(isServiceEnabled(mCn));
        preference.setOnPreferenceChangeListener((p, newValue) -> {
            final boolean access = (Boolean) newValue;
            if (!access) {
                if (!isServiceEnabled(mCn)) {
                    return true; // already disabled
                }
                // show a friendly dialog
                new FriendlyWarningDialogFragment()
                        .setServiceInfo(mCn, label, mParent)
                        .show(mParent.getFragmentManager(), "friendlydialog");
                return false;
            } else {
                if (isServiceEnabled(mCn)) {
                    return true; // already enabled
                }
                // show a scary dialog
                new ScaryWarningDialogFragment()
                        .setServiceInfo(mCn, label, mParent)
                        .show(mParent.getFragmentManager(), "dialog");
                return false;
            }
        });
    }

    public void disable(final ComponentName cn) {
        logSpecialPermissionChange(true, cn.getPackageName());
        mNm.setNotificationListenerAccessGranted(cn, false);
        AsyncTask.execute(() -> {
            if (!mNm.isNotificationPolicyAccessGrantedForPackage(
                    cn.getPackageName())) {
                mNm.removeAutomaticZenRules(cn.getPackageName());
            }
        });
    }

    protected void enable(ComponentName cn) {
        logSpecialPermissionChange(true, cn.getPackageName());
        mNm.setNotificationListenerAccessGranted(cn, true);
    }

    protected boolean isServiceEnabled(ComponentName cn) {
        return mNm.isNotificationListenerAccessGranted(cn);
    }

    @VisibleForTesting
    void logSpecialPermissionChange(boolean enable, String packageName) {
        final int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_NOTIVIEW_ALLOW
                : SettingsEnums.APP_SPECIAL_PERMISSION_NOTIVIEW_DENY;
        FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().action(mContext,
                logCategory, packageName);
    }
}
 No newline at end of file
+107 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.
 */

package com.android.settings.applications.specialaccess.notificationaccess;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.IconDrawableFactory;
import android.view.View;

import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.LayoutPreference;

public class HeaderPreferenceController extends BasePreferenceController
        implements PreferenceControllerMixin, LifecycleObserver {

    private DashboardFragment mFragment;
    private EntityHeaderController mHeaderController;
    private PackageInfo mPackageInfo;
    private PackageManager mPm;
    private CharSequence mServiceName;

    public HeaderPreferenceController(Context context, String key) {
        super(context, key);
    }

    public HeaderPreferenceController setFragment(DashboardFragment fragment) {
        mFragment = fragment;
        return this;
    }

    public HeaderPreferenceController setPackageInfo(PackageInfo packageInfo) {
        mPackageInfo = packageInfo;
        return this;
    }

    public HeaderPreferenceController setPm(PackageManager pm) {
        mPm = pm;
        return this;
    }

    public HeaderPreferenceController setServiceName(CharSequence serviceName) {
        mServiceName = serviceName;
        return this;
    }

    @Override
    public int getAvailabilityStatus() {
        return AVAILABLE;
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        if (mFragment == null) {
            return;
        }
        LayoutPreference pref = screen.findPreference(getPreferenceKey());
        mHeaderController = EntityHeaderController.newInstance(
                mFragment.getActivity(), mFragment, pref.findViewById(R.id.entity_header));
        pref = mHeaderController
                .setRecyclerView(mFragment.getListView(), mFragment.getSettingsLifecycle())
                .setIcon(IconDrawableFactory.newInstance(mFragment.getActivity())
                        .getBadgedIcon(mPackageInfo.applicationInfo))
                .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm))
                .setSummary(mServiceName)
                .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo))
                .setPackageName(mPackageInfo.packageName)
                .setUid(mPackageInfo.applicationInfo.uid)
                .setHasAppInfoLink(true)
                .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
                        EntityHeaderController.ActionType.ACTION_NONE)
                .done(mFragment.getActivity(), mContext);
        pref.findViewById(R.id.entity_header).setVisibility(View.VISIBLE);
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    public void onStart() {
        if (mHeaderController != null) {
            mHeaderController.styleActionBar(mFragment.getActivity());
        }
    }
}
+95 −99
Original line number Diff line number Diff line
@@ -16,52 +16,56 @@

package com.android.settings.applications.specialaccess.notificationaccess;

import static com.android.settings.applications.AppInfoBase.ARG_PACKAGE_NAME;

import android.app.Activity;
import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.Slog;

import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.applications.AppUtils;

import com.android.settings.SettingsActivity;
import com.android.settings.applications.manageapplications.ManageApplications;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.applications.ApplicationsState;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class NotificationAccessDetails extends AppInfoBase {
public class NotificationAccessDetails extends DashboardFragment {
    private static final String TAG = "NotifAccessDetails";
    private static final String SWITCH_PREF_KEY = "notification_access_switch";

    private boolean mCreated;
    private ComponentName mComponentName;
    private CharSequence mServiceName;
    protected PackageInfo mPackageInfo;
    protected int mUserId;
    protected String mPackageName;
    protected RestrictedLockUtils.EnforcedAdmin mAppsControlDisallowedAdmin;
    protected boolean mAppsControlDisallowedBySystem;
    private boolean mIsNls;

    private NotificationManager mNm;
    private PackageManager mPm;

    @Override
    public void onCreate(Bundle savedInstanceState) {
    public void onAttach(Context context) {
        super.onAttach(context);
        final Intent intent = getIntent();
        if (mComponentName == null && intent != null) {
            String cn = intent.getStringExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME);
@@ -73,38 +77,20 @@ public class NotificationAccessDetails extends AppInfoBase {
                }
            }
        }
        super.onCreate(savedInstanceState);
        mNm = getContext().getSystemService(NotificationManager.class);
        mPm = getPackageManager();
        addPreferencesFromResource(R.xml.notification_access_permission_details);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (mCreated) {
            Log.w(TAG, "onActivityCreated: ignoring duplicate call");
            return;
        }
        mCreated = true;
        if (mPackageInfo == null) return;
        retrieveAppEntry();
        loadNotificationListenerService();
        final Activity activity = getActivity();
        final Preference pref = EntityHeaderController
                .newInstance(activity, this, null /* header */)
                .setRecyclerView(getListView(), getSettingsLifecycle())
                .setIcon(IconDrawableFactory.newInstance(getContext())
                        .getBadgedIcon(mPackageInfo.applicationInfo))
                .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm))
                .setSummary(mServiceName)
                .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo))
                .setPackageName(mPackageName)
                .setUid(mPackageInfo.applicationInfo.uid)
                .setHasAppInfoLink(true)
                .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
                        EntityHeaderController.ActionType.ACTION_NONE)
                .done(activity, getPrefContext());
        getPreferenceScreen().addPreference(pref);
        use(ApprovalPreferenceController.class)
                .setPkgInfo(mPackageInfo)
                .setCn(mComponentName)
                .setNm(context.getSystemService(NotificationManager.class))
                .setPm(context.getPackageManager())
                .setParent(this);
        use(HeaderPreferenceController.class)
                .setFragment(this)
                .setPackageInfo(mPackageInfo)
                .setPm(context.getPackageManager())
                .setServiceName(mServiceName);
    }

    @Override
@@ -112,9 +98,7 @@ public class NotificationAccessDetails extends AppInfoBase {
        return SettingsEnums.NOTIFICATION_ACCESS_DETAIL;
    }

    @Override
    protected boolean refreshUi() {
        final Context context = getContext();
        if (mComponentName == null) {
            // No service given
            Slog.d(TAG, "No component name provided");
@@ -130,72 +114,74 @@ public class NotificationAccessDetails extends AppInfoBase {
            Slog.d(TAG, "NLSes aren't allowed in work profiles");
            return false;
        }
        updatePreference(findPreference(SWITCH_PREF_KEY));
        return true;
    }

    @Override
    protected AlertDialog createDialog(int id, int errorCode) {
        return null;
    }

    public void updatePreference(SwitchPreference preference) {
        final CharSequence label = mPackageInfo.applicationInfo.loadLabel(mPm);
        preference.setChecked(isServiceEnabled(mComponentName));
        preference.setOnPreferenceChangeListener((p, newValue) -> {
            final boolean access = (Boolean) newValue;
            if (!access) {
                if (!isServiceEnabled(mComponentName)) {
                    return true; // already disabled
                }
                // show a friendly dialog
                new FriendlyWarningDialogFragment()
                        .setServiceInfo(mComponentName, label, this)
                        .show(getFragmentManager(), "friendlydialog");
                return false;
            } else {
                if (isServiceEnabled(mComponentName)) {
                    return true; // already enabled
    public void onResume() {
        super.onResume();
        mAppsControlDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
                getActivity(), UserManager.DISALLOW_APPS_CONTROL, mUserId);
        mAppsControlDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction(
                getActivity(), UserManager.DISALLOW_APPS_CONTROL, mUserId);

        if (!refreshUi()) {
            setIntentAndFinish(true /* appChanged */);
        }
                // show a scary dialog
                new ScaryWarningDialogFragment()
                        .setServiceInfo(mComponentName, label, this)
                        .show(getFragmentManager(), "dialog");
                return false;
    }
        });

    protected void setIntentAndFinish(boolean appChanged) {
        Log.i(TAG, "appChanged=" + appChanged);
        Intent intent = new Intent();
        intent.putExtra(ManageApplications.APP_CHG, appChanged);
        SettingsActivity sa = (SettingsActivity) getActivity();
        sa.finishPreferencePanel(Activity.RESULT_OK, intent);
    }

    @VisibleForTesting
    void logSpecialPermissionChange(boolean enable, String packageName) {
        int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_NOTIVIEW_ALLOW
                : SettingsEnums.APP_SPECIAL_PERMISSION_NOTIVIEW_DENY;
        FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(),
                logCategory, packageName);
    protected void retrieveAppEntry() {
        final Bundle args = getArguments();
        mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null;
        Intent intent = (args == null) ?
                getIntent() : (Intent) args.getParcelable("intent");
        if (mPackageName == null) {
            if (intent != null && intent.getData() != null) {
                mPackageName = intent.getData().getSchemeSpecificPart();
            }
        }
        if (intent != null && intent.hasExtra(Intent.EXTRA_USER_HANDLE)) {
            mUserId = ((UserHandle) intent.getParcelableExtra(
                    Intent.EXTRA_USER_HANDLE)).getIdentifier();
        } else {
            mUserId = UserHandle.myUserId();
        }

    public void disable(final ComponentName cn) {
        logSpecialPermissionChange(true, cn.getPackageName());
        mNm.setNotificationListenerAccessGranted(cn, false);
        AsyncTask.execute(() -> {
            if (!mNm.isNotificationPolicyAccessGrantedForPackage(
                    cn.getPackageName())) {
                mNm.removeAutomaticZenRules(cn.getPackageName());
        try {
            mPackageInfo = mPm.getPackageInfoAsUser(mPackageName,
                    PackageManager.MATCH_DISABLED_COMPONENTS |
                            PackageManager.GET_SIGNING_CERTIFICATES |
                            PackageManager.GET_PERMISSIONS, mUserId);
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "Exception when retrieving package:" + mPackageName, e);
        }
        });
        refreshUi();
    }

    protected void enable(ComponentName cn) {
        logSpecialPermissionChange(true, cn.getPackageName());
        mNm.setNotificationListenerAccessGranted(cn, true);
        refreshUi();
    // Dialogs only have access to the parent fragment, not the controller, so pass the information
    // along to keep business logic out of this file
    public void disable(final ComponentName cn) {
        final PreferenceScreen screen = getPreferenceScreen();
        ApprovalPreferenceController controller = use(ApprovalPreferenceController.class);
        controller.disable(cn);
        controller.updateState(screen.findPreference(controller.getPreferenceKey()));
    }

    protected boolean isServiceEnabled(ComponentName cn) {
        return mNm.isNotificationListenerAccessGranted(cn);
    protected void enable(ComponentName cn) {
        final PreferenceScreen screen = getPreferenceScreen();
        ApprovalPreferenceController controller = use(ApprovalPreferenceController.class);
        controller.enable(cn);
        controller.updateState(screen.findPreference(controller.getPreferenceKey()));
    }

    // To save binder calls, load this in the fragment rather than each preference controller
    protected void loadNotificationListenerService() {
        mIsNls = false;

@@ -218,4 +204,14 @@ public class NotificationAccessDetails extends AppInfoBase {
            }
        }
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.notification_access_permission_details;
    }

    @Override
    protected String getLogTag() {
        return TAG;
    }
}
 No newline at end of file
Loading