public class

CompatPermissionManager

extends java.lang.Object

 java.lang.Object

↳androidx.slice.compat.CompatPermissionManager

Gradle dependencies

compile group: 'androidx.slice', name: 'slice-core', version: '1.1.0-alpha02'

  • groupId: androidx.slice
  • artifactId: slice-core
  • version: 1.1.0-alpha02

Artifact androidx.slice:slice-core:1.1.0-alpha02 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.slice:slice-core com.android.support:slices-core

Summary

Fields
public static final java.lang.StringALL_SUFFIX

Constructors
publicCompatPermissionManager(Context context, java.lang.String prefsName, int myUid, java.lang.String autoGrantPermissions[])

Methods
public intcheckSlicePermission(Uri uri, int pid, int uid)

public voidgrantSlicePermission(Uri uri, java.lang.String toPkg)

public voidrevokeSlicePermission(Uri uri, java.lang.String toPkg)

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final java.lang.String ALL_SUFFIX

Constructors

public CompatPermissionManager(Context context, java.lang.String prefsName, int myUid, java.lang.String autoGrantPermissions[])

Methods

public int checkSlicePermission(Uri uri, int pid, int uid)

public void grantSlicePermission(Uri uri, java.lang.String toPkg)

public void revokeSlicePermission(Uri uri, java.lang.String toPkg)

Source

/*
 * Copyright 2018 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 androidx.slice.compat;

import static androidx.core.content.PermissionChecker.PERMISSION_DENIED;
import static androidx.core.content.PermissionChecker.PERMISSION_GRANTED;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.text.TextUtils;

import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.collection.ArraySet;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
public class CompatPermissionManager {

    private static final String TAG = "CompatPermissionManager";
    public static final String ALL_SUFFIX = "_all";

    private final Context mContext;
    private final String mPrefsName;
    private final int mMyUid;
    private final String[] mAutoGrantPermissions;

    public CompatPermissionManager(Context context, String prefsName, int myUid,
            String[] autoGrantPermissions) {
        mContext = context;
        mPrefsName = prefsName;
        mMyUid = myUid;
        mAutoGrantPermissions = autoGrantPermissions;
    }

    private SharedPreferences getPrefs() {
        return mContext.getSharedPreferences(mPrefsName, Context.MODE_PRIVATE);
    }

    @SuppressLint("WrongConstant")
    public int checkSlicePermission(Uri uri, int pid, int uid) {
        if (uid == mMyUid) {
            return PERMISSION_GRANTED;
        }
        String[] pkgs = mContext.getPackageManager().getPackagesForUid(uid);
        for (String pkg : pkgs) {
            if (checkSlicePermission(uri, pkg) == PERMISSION_GRANTED) {
                return PERMISSION_GRANTED;
            }
        }
        for (String autoGrantPermission : mAutoGrantPermissions) {
            if (mContext.checkPermission(autoGrantPermission, pid, uid) == PERMISSION_GRANTED) {
                for (String pkg : pkgs) {
                    grantSlicePermission(uri, pkg);
                }
                return PERMISSION_GRANTED;
            }
        }
        // Fall back to allowing uri permissions through.
        return mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    }

    private int checkSlicePermission(Uri uri, String pkg) {
        PermissionState state = getPermissionState(pkg, uri.getAuthority());
        return state.hasAccess(uri.getPathSegments()) ? PERMISSION_GRANTED : PERMISSION_DENIED;
    }

    public void grantSlicePermission(Uri uri, String toPkg) {
        PermissionState state = getPermissionState(toPkg, uri.getAuthority());
        if (state.addPath(uri.getPathSegments())) {
            persist(state);
        }
    }

    public void revokeSlicePermission(Uri uri, String toPkg) {
        PermissionState state = getPermissionState(toPkg, uri.getAuthority());
        if (state.removePath(uri.getPathSegments())) {
            persist(state);
        }
    }

    private synchronized void persist(PermissionState state) {
        getPrefs().edit()
                .putStringSet(state.getKey(), state.toPersistable())
                .putBoolean(state.getKey() + ALL_SUFFIX, state.hasAllPermissions())
                .apply();
    }

    private PermissionState getPermissionState(String pkg, String authority) {
        String key = pkg + "_" + authority;
        Set<String> grant = getPrefs().getStringSet(key, Collections.<String>emptySet());
        boolean hasAllPermissions = getPrefs().getBoolean(key + ALL_SUFFIX, false);
        return new PermissionState(grant, key, hasAllPermissions);
    }

    public static class PermissionState {

        private final ArraySet<String[]> mPaths = new ArraySet<>();
        private final String mKey;

        PermissionState(Set<String> grant, String key, boolean hasAllPermissions) {
            if (hasAllPermissions) {
                mPaths.add(new String[0]);
            } else {
                for (String g : grant) {
                    mPaths.add(decodeSegments(g));
                }
            }
            mKey = key;
        }

        public boolean hasAllPermissions() {
            return hasAccess(Collections.<String>emptyList());
        }

        public String getKey() {
            return mKey;
        }

        public Set<String> toPersistable() {
            ArraySet<String> ret = new ArraySet<>();
            for (String[] path : mPaths) {
                ret.add(encodeSegments(path));
            }
            return ret;
        }

        public boolean hasAccess(List<String> path) {
            String[] inPath = path.toArray(new String[path.size()]);
            for (String[] p : mPaths) {
                if (isPathPrefixMatch(p, inPath)) {
                    return true;
                }
            }
            return false;
        }

        boolean addPath(List<String> path) {
            String[] pathSegs = path.toArray(new String[path.size()]);
            for (int i = mPaths.size() - 1; i >= 0; i--) {
                String[] existing = mPaths.valueAt(i);
                if (isPathPrefixMatch(existing, pathSegs)) {
                    // Nothing to add here.
                    return false;
                }
                if (isPathPrefixMatch(pathSegs, existing)) {
                    mPaths.removeAt(i);
                }
            }
            mPaths.add(pathSegs);
            return true;
        }

        boolean removePath(List<String> path) {
            boolean changed = false;
            String[] pathSegs = path.toArray(new String[path.size()]);
            for (int i = mPaths.size() - 1; i >= 0; i--) {
                String[] existing = mPaths.valueAt(i);
                if (isPathPrefixMatch(pathSegs, existing)) {
                    changed = true;
                    mPaths.removeAt(i);
                }
            }
            return changed;
        }

        private boolean isPathPrefixMatch(String[] prefix, String[] path) {
            final int prefixSize = prefix.length;
            if (path.length < prefixSize) return false;

            for (int i = 0; i < prefixSize; i++) {
                if (!Objects.equals(path[i], prefix[i])) {
                    return false;
                }
            }

            return true;
        }

        private String encodeSegments(String[] s) {
            String[] out = new String[s.length];
            for (int i = 0; i < s.length; i++) {
                out[i] = Uri.encode(s[i]);
            }
            return TextUtils.join("/", out);
        }

        private String[] decodeSegments(String s) {
            String[] sets = s.split("/", -1);
            for (int i = 0; i < sets.length; i++) {
                sets[i] = Uri.decode(sets[i]);
            }
            return sets;
        }
    }
}