com.afwsamples.testdpc.common.preference.DpcPreferenceHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.afwsamples.testdpc.common.preference.DpcPreferenceHelper.java

Source

/*
 * Copyright (C) 2016 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.afwsamples.testdpc.common.preference;

import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.annotation.IntDef;
import android.support.annotation.StringRes;
import android.support.v4.os.BuildCompat;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceViewHolder;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;

import com.afwsamples.testdpc.R;
import com.afwsamples.testdpc.common.Util;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

/**
 * Helper class to check preference constraints declared in the XML file and disable the preference
 * with an informative message if the constraint does not hold. The API level, admin type (device
 * or profile owner) and user type (primary, managed profile, etc.) can be used as constraints.
 *
 * @attr ref android.R.styleable#DpcPreference_minSdkVersion
 * @attr ref android.R.styleable#DpcPreference_admin
 * @attr ref android.R.styleable#DpcPreference_user
 */
public class DpcPreferenceHelper {
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true, value = { ADMIN_NONE, ADMIN_DEVICE_OWNER, ADMIN_PROFILE_OWNER })
    public @interface AdminKind {
    }

    public static final int ADMIN_NONE = 0x1;
    public static final int ADMIN_DEVICE_OWNER = 0x2;
    public static final int ADMIN_PROFILE_OWNER = 0x4;
    public static final int ADMIN_ANY = ADMIN_NONE | ADMIN_DEVICE_OWNER | ADMIN_PROFILE_OWNER;
    public static final int ADMIN_NOT_NONE = ADMIN_ANY & ~ADMIN_NONE;
    public static final int ADMIN_NOT_DEVICE_OWNER = ADMIN_ANY & ~ADMIN_DEVICE_OWNER;
    public static final int ADMIN_NOT_PROFILE_OWNER = ADMIN_ANY & ~ADMIN_PROFILE_OWNER;
    public static final @AdminKind int ADMIN_DEFAULT = ADMIN_NOT_NONE;
    public static final int NO_CUSTOM_CONSTRIANT = 0;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true, value = { USER_PRIMARY_USER, USER_SECONDARY_USER, USER_MANAGED_PROFILE })
    public @interface UserKind {
    }

    public static final int USER_PRIMARY_USER = 0x1;
    public static final int USER_SECONDARY_USER = 0x2;
    public static final int USER_MANAGED_PROFILE = 0x4;
    public static final int USER_ANY = USER_PRIMARY_USER | USER_SECONDARY_USER | USER_MANAGED_PROFILE;
    public static final int USER_NOT_PRIMARY_USER = USER_ANY & ~USER_PRIMARY_USER;
    public static final int USER_NOT_SECONDARY_USER = USER_ANY & ~USER_SECONDARY_USER;
    public static final int USER_NOT_MANAGED_PROFILE = USER_ANY & ~USER_MANAGED_PROFILE;
    public static final @UserKind int USER_DEFAULT = USER_ANY;

    private static final int NUM_ADMIN_KINDS = Integer.bitCount(ADMIN_ANY);
    private static final int NUM_USER_KINDS = Integer.bitCount(USER_ANY);

    private Context mContext;
    private Preference mPreference;

    private CharSequence mConstraintViolationSummary = null;
    private CustomConstraint mCustomConstraint = null;
    private int mMinSdkVersion;
    private @AdminKind int mAdminConstraint;
    private @UserKind int mUserConstraint;

    /**
     * Update this method as {@link Build.VERSION_CODES} and Android releases are updated.
     *
     * @return The version SDK int or {@link Build.VERSION_CODES.CUR_DEVELOPMENT} if the SDK int is
     *         not yet assigned.
     */
    private int getDeviceSdkInt() {
        if (BuildCompat.isAtLeastO()) {
            return Build.VERSION_CODES.O;
        }
        return Build.VERSION.SDK_INT;
    }

    public DpcPreferenceHelper(Context context, Preference preference, AttributeSet attrs) {
        mContext = context;
        mPreference = preference;

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DpcPreference);

        mMinSdkVersion = a.getInt(R.styleable.DpcPreference_minSdkVersion, 0);
        if (attrs == null) {
            // Be more lenient when creating the preference from code
            mMinSdkVersion = Build.VERSION_CODES.LOLLIPOP;
        }
        if (mMinSdkVersion == 0) {
            throw new RuntimeException("testdpc:minSdkVersion must be specified.");
        }

        // noinspection ResourceType
        mAdminConstraint = a.getInt(R.styleable.DpcPreference_admin, ADMIN_DEFAULT);
        // noinspection ResourceType
        mUserConstraint = a.getInt(R.styleable.DpcPreference_user, USER_DEFAULT);

        a.recycle();
    }

    /**
     * Override the summary with any constraint violation messages.
     */
    public void onBindViewHolder(PreferenceViewHolder holder) {
        if (!constraintsMet()) {
            final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
            if (summaryView != null) {
                summaryView.setText(mConstraintViolationSummary);
                summaryView.setVisibility(View.VISIBLE);
            }
        }
    }

    /**
     * Set the minimum required API level constraint.
     *
     * @param version The minimum required version.
     */
    public void setMinSdkVersion(int version) {
        mMinSdkVersion = version;
        disableIfConstraintsNotMet();
    }

    /**
     * Set constraints on the admin.
     *
     * @param adminConstraint The admins for which the preference is enabled.
     */
    public void setAdminConstraint(@AdminKind int adminConstraint) {
        mAdminConstraint = adminConstraint;
        disableIfConstraintsNotMet();
    }

    /**
     * Clear constraints on the admin.
     */
    public void clearAdminConstraint() {
        setAdminConstraint(ADMIN_DEFAULT);
    }

    /**
     * Set constraints on the user.
     *
     * @param userConstraint The users for which the preference is enabled.
     */
    public void setUserConstraint(@UserKind int userConstraint) {
        mUserConstraint = userConstraint;
        disableIfConstraintsNotMet();
    }

    /**
     * Clear constraints on the user.
     */
    public void clearUserConstraint() {
        setUserConstraint(USER_DEFAULT);
    }

    /**
     * Clear the admin and user constraints for this preference.
     * <p/>
     * Custom constraints will remain.
     */
    public void clearNonCustomConstraints() {
        clearAdminConstraint();
        clearUserConstraint();
    }

    /**
     * Specify a custom constraint by setting a {{@link CustomConstraint}} object. The enabled
     * state of the preference will be updated accordingly.
     */
    public void setCustomConstraint(CustomConstraint customConstraint) {
        mCustomConstraint = customConstraint;
        disableIfConstraintsNotMet();
    }

    /**
     * Remove any custom constraints set by {@link #setCustomConstraint(CustomConstraint)}.
     * <p/>
     * This method is safe to call if there is no current custom constraint.
     */
    public void clearCustomConstraint() {
        setCustomConstraint(null);
    }

    public void disableIfConstraintsNotMet() {
        mConstraintViolationSummary = findConstraintViolation();
        mPreference.setEnabled(constraintsMet());
    }

    /**
     * Check for constraint violations.
     *
     * @return A string describing the constraint violation or {@code null} if no violations were
     * found.
     */
    private CharSequence findConstraintViolation() {
        if (getDeviceSdkInt() < mMinSdkVersion) {
            // FIXME: Remove this special checking once O is out.
            if (mMinSdkVersion >= 26) {
                return mContext.getString(R.string.requires_preview_release);
            }
            return mContext.getString(R.string.requires_android_api_level, mMinSdkVersion);
        }

        if (!isEnabledForAdmin(getCurrentAdmin())) {
            return getAdminConstraintSummary();
        }

        if (!isEnabledForUser(getCurrentUser())) {
            return getUserConstraintSummary();
        }

        if (mCustomConstraint != null) {
            @StringRes
            int strRes = mCustomConstraint.validateConstraint();
            if (strRes != NO_CUSTOM_CONSTRIANT) {
                return mContext.getString(strRes);
            }
        }
        return null;
    }

    private int getCurrentAdmin() {
        final DevicePolicyManager dpm = (DevicePolicyManager) mContext
                .getSystemService(Context.DEVICE_POLICY_SERVICE);
        final String packageName = mContext.getPackageName();

        if (dpm.isDeviceOwnerApp(packageName)) {
            return ADMIN_DEVICE_OWNER;
        }
        if (dpm.isProfileOwnerApp(packageName)) {
            return ADMIN_PROFILE_OWNER;
        }
        return ADMIN_NONE;
    }

    private int getCurrentUser() {
        if (Util.isPrimaryUser(mContext)) {
            return USER_PRIMARY_USER;
        }

        if (Util.isManagedProfileOwner(mContext)) {

            return USER_MANAGED_PROFILE;
        }

        return USER_SECONDARY_USER;
    }

    private boolean isEnabledForAdmin(@AdminKind int admin) {
        return (mAdminConstraint & admin) == admin;
    }

    private boolean isEnabledForUser(@UserKind int user) {
        return (mUserConstraint & user) == user;
    }

    private String getAdminConstraintSummary() {
        final List<String> admins = new ArrayList<>(NUM_ADMIN_KINDS);

        if (isEnabledForAdmin(ADMIN_DEVICE_OWNER)) {
            admins.add(mContext.getString(R.string.device_owner));
        }
        if (isEnabledForAdmin(ADMIN_PROFILE_OWNER)) {
            admins.add(mContext.getString(R.string.profile_owner));
        }

        return joinRequirementList(admins);
    }

    private String getUserConstraintSummary() {
        final List<String> users = new ArrayList<>(NUM_USER_KINDS);

        if (isEnabledForUser(USER_PRIMARY_USER)) {
            users.add(mContext.getString(R.string.primary_user));
        }
        if (isEnabledForUser(USER_SECONDARY_USER)) {
            users.add(mContext.getString(R.string.secondary_user));
        }
        if (isEnabledForUser(USER_MANAGED_PROFILE)) {
            users.add(mContext.getString(R.string.managed_profile));
        }

        return joinRequirementList(users);
    }

    private String joinRequirementList(List<String> items) {
        final StringBuilder sb = new StringBuilder(mContext.getString(R.string.requires));
        final String lastItem = items.remove(items.size() - 1);
        sb.append(TextUtils.join(mContext.getString(R.string.requires_delimiter), items));
        if (!items.isEmpty()) {
            sb.append(mContext.getString(R.string.requires_or));
        }
        sb.append(lastItem);
        return sb.toString();
    }

    /**
     * Return whether the preference's constraints are met.
     *
     * @return True if there are no violations of the preference's constraints.
     */
    public boolean constraintsMet() {
        return TextUtils.isEmpty(mConstraintViolationSummary);
    }
}