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

Commit 9c165d76 authored by Svet Ganov's avatar Svet Ganov Committed by Svetoslav Ganov
Browse files

Add optional permission review for legacy apps - framework

For some markets we have to allow the user to review permissions
for legacy apps at runtime despite them not supporting the new
permission model. This is achieved by showing a review UI before
launching any app component. If an update is installed the user
should see a permission review UI for the newly requested
permissions.

To allow distinguishing which permissions need a review we set
a special flag in the permission flags that a review is required.
This flag is set if a runtime permission is granted to a legacy
app and the system does not launch any app components until this
flag is cleared. Since install permissions are shared across all
users the dangerous permissions for legacy apps in review mode
are represented as always granted runtime permissions since the
reivew requirement is on a per user basis.

Whether the build supports permission review for legacy apps is
determined by a build constant allowing us to compile away the
unnecessary code for markets that do not require a permissions
review.

If an app launches an activity in another app that has some
permissions needing review, we launch the permissions review
UI and pass it a pending intent to launch the activity after
the review is completed.

If an app sends a broadcast to another app that has some permissions
needing review, we do not deliver the broadcast and if the sending
app is in the foreground plus the broadcast is explicit (has a
component) we launch the review UI giving it a pending intent to
send the broadcast after the review is completed.

If an app starts a service in another app that has some permissions
needing review, we do not start the service and if the calling app
is in the foreground we launch the review UI and pass it a pending
intent to start the service after the review is completed.

If an app binds to a service in another app that has some permissions
needing review, we schedule the binding but do not spin the target
service's process and we launch the review UI and pass it a callback
to invoke after the review is completed which spins the service
process and completes the binding.

If an app requests a content provider in another app that has some
permissions needing review we do not return the provider and if
the calling app is in the foreground we show the review UI.

Change-Id: I550f5ff6cadc46a98a1d1a7b8415eca551203acf
parent 8390b2ac
Loading
Loading
Loading
Loading
+57 −0
Original line number Diff line number Diff line
@@ -1608,6 +1608,53 @@ public class Intent implements Parcelable, Cloneable {
    public static final String ACTION_MANAGE_PERMISSIONS =
            "android.intent.action.MANAGE_PERMISSIONS";

    /**
     * Activity action: Launch UI to review permissions for an app.
     * The system uses this intent if permission review for apps not
     * supporting the new runtime permissions model is enabled. In
     * this mode a permission review is required before any of the
     * app components can run.
     * <p>
     * Input: {@link #EXTRA_PACKAGE_NAME} specifies the package whose
     * permissions will be reviewed (mandatory).
     * </p>
     * <p>
     * Input: {@link #EXTRA_INTENT} specifies a pending intent to
     * be fired after the permission review (optional).
     * </p>
     * <p>
     * Input: {@link #EXTRA_REMOTE_CALLBACK} specifies a callback to
     * be invoked after the permission review (optional).
     * </p>
     * <p>
     * Input: {@link #EXTRA_RESULT_NEEDED} specifies whether the intent
     * passed via {@link #EXTRA_INTENT} needs a result (optional).
     * </p>
     * <p>
     * Output: Nothing.
     * </p>
     *
     * @see #EXTRA_PACKAGE_NAME
     * @see #EXTRA_INTENT
     * @see #EXTRA_REMOTE_CALLBACK
     * @see #EXTRA_RESULT_NEEDED
     *
     * @hide
     */
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_REVIEW_PERMISSIONS =
            "android.intent.action.REVIEW_PERMISSIONS";

    /**
     * Intent extra: A callback for reporting remote result as a bundle.
     * <p>
     * Type: IRemoteCallback
     * </p>
     *
     * @hide
     */
    public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";

    /**
     * Intent extra: An app package name.
     * <p>
@@ -1619,6 +1666,16 @@ public class Intent implements Parcelable, Cloneable {
    @SystemApi
    public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";

    /**
     * Intent extra: An extra for specifying whether a result is needed.
     * <p>
     * Type: boolean
     * </p>
     *
     * @hide
     */
    public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";

    /**
     * Broadcast action that requests current permission granted information.  It will respond
     * to the request by sending a broadcast with action defined by
+9 −1
Original line number Diff line number Diff line
@@ -2018,7 +2018,6 @@ public abstract class PackageManager {
     */
    public static final int FLAG_PERMISSION_SYSTEM_FIXED =  1 << 4;


    /**
     * Permission flag: The permission is granted by default because it
     * enables app functionality that is expected to work out-of-the-box
@@ -2029,6 +2028,14 @@ public abstract class PackageManager {
     */
    public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT =  1 << 5;

    /**
     * Permission flag: The permission has to be reviewed before any of
     * the app components can run.
     *
     * @hide
     */
    public static final int FLAG_PERMISSION_REVIEW_REQUIRED =  1 << 6;

    /**
     * Mask for all permission flags.
     *
@@ -4808,6 +4815,7 @@ public abstract class PackageManager {
            case FLAG_PERMISSION_USER_SET: return "USER_SET";
            case FLAG_PERMISSION_REVOKE_ON_UPGRADE: return "REVOKE_ON_UPGRADE";
            case FLAG_PERMISSION_USER_FIXED: return "USER_FIXED";
            case FLAG_PERMISSION_REVIEW_REQUIRED: return "REVIEW_REQUIRED";
            default: return Integer.toString(flag);
        }
    }
+9 −0
Original line number Diff line number Diff line
@@ -122,4 +122,13 @@ public abstract class PackageManagerInternal {
     * @param packageList List of package names to keep cached.
     */
    public abstract void setKeepUninstalledPackages(List<String> packageList);

    /**
     * Gets whether some of the permissions used by this package require a user
     * review before any of the app components can run.
     * @param packageName The package name for which to check.
     * @param userId The user under which to check.
     * @return True a permissions review is required.
     */
    public abstract boolean isPermissionsReviewRequired(String packageName, int userId);
}
+12 −0
Original line number Diff line number Diff line
@@ -787,6 +787,18 @@ public class Build {
    public static final boolean IS_DEBUGGABLE =
            SystemProperties.getInt("ro.debuggable", 0) == 1;

    /**
     * Specifies whether the permissions needed by a legacy app should be
     * reviewed before any of its components can run. A legacy app is one
     * with targetSdkVersion < 23, i.e apps using the old permission model.
     * If review is not required, permissions are reviewed before the app
     * is installed.
     *
     * @hide
     */
    public static final boolean PERMISSIONS_REVIEW_REQUIRED =
            SystemProperties.getInt("ro.permission_review_required", 0) == 1;

    /**
     * Returns the version string for the radio firmware.  May return
     * null (if, for instance, the radio is not currently on).
+58 −62
Original line number Diff line number Diff line
@@ -16,88 +16,84 @@

package android.os;

import android.annotation.NonNull;
import android.annotation.Nullable;

/**
 * TODO: Make this a public API?  Let's see how it goes with a few use
 * cases first.
 * @hide
 */
public abstract class RemoteCallback implements Parcelable {
    final Handler mHandler;
    final IRemoteCallback mTarget;
    
    class DeliverResult implements Runnable {
        final Bundle mResult;
        
        DeliverResult(Bundle result) {
            mResult = result;
        }
public final class RemoteCallback implements Parcelable {

        public void run() {
            onResult(mResult);
        }
    public interface OnResultListener {
        public void onResult(Bundle result);
    }

    class LocalCallback extends IRemoteCallback.Stub {
        public void sendResult(Bundle bundle) {
            mHandler.post(new DeliverResult(bundle));
        }
    }
    private final OnResultListener mListener;
    private final Handler mHandler;
    private final IRemoteCallback mCallback;

    static class RemoteCallbackProxy extends RemoteCallback {
        RemoteCallbackProxy(IRemoteCallback target) {
            super(target);
    public RemoteCallback(OnResultListener listener) {
        this(listener, null);
    }

        protected void onResult(Bundle bundle) {
    public RemoteCallback(@NonNull OnResultListener listener, @Nullable Handler handler) {
        if (listener == null) {
            throw new NullPointerException("listener cannot be null");
        }
    }
    
    public RemoteCallback(Handler handler) {
        mListener = listener;
        mHandler = handler;
        mTarget = new LocalCallback();
        mCallback = new IRemoteCallback.Stub() {
            @Override
            public void sendResult(Bundle data) {
                RemoteCallback.this.sendResult(data);
            }
        };
    }

     RemoteCallback(IRemoteCallback target) {
    RemoteCallback(Parcel parcel) {
        mListener = null;
        mHandler = null;
        mTarget = target;
        mCallback = IRemoteCallback.Stub.asInterface(
                parcel.readStrongBinder());
    }

    public void sendResult(Bundle bundle) throws RemoteException {
        mTarget.sendResult(bundle);
    public void sendResult(@Nullable final Bundle result) {
        // Do local dispatch
        if (mListener != null) {
            if (mHandler != null) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mListener.onResult(result);
                    }
    
    protected abstract void onResult(Bundle bundle);
    
    public boolean equals(Object otherObj) {
        if (otherObj == null) {
            return false;
                });
            } else {
                mListener.onResult(result);
            }
        // Do remote dispatch
        } else {
            try {
            return mTarget.asBinder().equals(((RemoteCallback)otherObj)
                    .mTarget.asBinder());
        } catch (ClassCastException e) {
                mCallback.sendResult(result);
            } catch (RemoteException e) {
                /* ignore */
            }
        return false;
        }
    
    public int hashCode() {
        return mTarget.asBinder().hashCode();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeStrongBinder(mTarget.asBinder());
    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeStrongBinder(mCallback.asBinder());
    }

    public static final Parcelable.Creator<RemoteCallback> CREATOR
            = new Parcelable.Creator<RemoteCallback>() {
        public RemoteCallback createFromParcel(Parcel in) {
            IBinder target = in.readStrongBinder();
            return target != null ? new RemoteCallbackProxy(
                    IRemoteCallback.Stub.asInterface(target)) : null;
        public RemoteCallback createFromParcel(Parcel parcel) {
            return new RemoteCallback(parcel);
        }

        public RemoteCallback[] newArray(int size) {
Loading