Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Marshmallow: Can't execute Settings.System.canWrite(Context) from a service

I have a NotificationListener service running in the background and it throws an exception when I execute Settings.System.canWrite(Settings.java:3742)

12-03 18:25:33.490    2754-2771/? W/System.err﹕ java.lang.SecurityException: uid 10057 does not have android.permission.UPDATE_APP_OPS_STATS.
12-03 18:25:33.490    2754-2771/? W/System.err﹕ at android.os.Parcel.readException(Parcel.java:1599)
12-03 18:25:33.490    2754-2771/? W/System.err﹕ at android.os.Parcel.readException(Parcel.java:1552)
12-03 18:25:33.490    2754-2771/? W/System.err﹕ at com.android.internal.app.IAppOpsService$Stub$Proxy.checkOperation(IAppOpsService.java:327)
12-03 18:25:33.490    2754-2771/? W/System.err﹕ at android.app.AppOpsManager.checkOpNoThrow(AppOpsManager.java:1536)
12-03 18:25:33.490    2754-2771/? W/System.err﹕ at android.provider.Settings.isCallingPackageAllowedToPerformAppOpsProtectedOperation(Settings.java:8425)
12-03 18:25:33.490    2754-2771/? W/System.err﹕ at android.provider.Settings.isCallingPackageAllowedToWriteSettings(Settings.java:8320)
12-03 18:25:33.490    2754-2771/? W/System.err﹕ at android.provider.Settings$System.canWrite(Settings.java:3742)

Is this because the provided Context is a service because this doesn't happen when called from an activity.

like image 594
Olayinka Avatar asked Dec 03 '15 18:12

Olayinka


Video Answer


1 Answers

This is a "bug", although as you said, this method is not expected to called from a NotificationListenerService. You should call this from an ordinary Activty or Service. I will explan it later why.

According to your trace the following is the situation.

android.app.AppOpsManager.checkOpNoThrow(AppOpsManager.java:1536)

  /**
     * Like {@link #checkOp} but instead of throwing a {@link SecurityException} it
     * returns {@link #MODE_ERRORED}.
     * @hide
     */
    public int checkOpNoThrow(int op, int uid, String packageName) {
        try {
            return mService.checkOperation(op, uid, packageName); //1536
        } catch (RemoteException e) {
        }
        return MODE_ERRORED;
    }

Here it is trying to catch RemoteException although in Parcel (android.os.Parcel.readException(Parcel.java:1599)) this is happening:

public final void readException(int code, String msg) {
    switch (code) {
        case EX_SECURITY:
            throw new SecurityException(msg); // 1599
        case EX_BAD_PARCELABLE:
            throw new BadParcelableException(msg);
        case EX_ILLEGAL_ARGUMENT:
            throw new IllegalArgumentException(msg);
        case EX_NULL_POINTER:
            throw new NullPointerException(msg);
        case EX_ILLEGAL_STATE:
            throw new IllegalStateException(msg);
        case EX_NETWORK_MAIN_THREAD:
            throw new NetworkOnMainThreadException();
        case EX_UNSUPPORTED_OPERATION:
            throw new UnsupportedOperationException(msg);
    }
    throw new RuntimeException("Unknown exception code: " + code
            + " msg " + msg);
}

It is throwing a SecurityException.

As SecurityException is NOT a RemoteException try-catch fails to catch the exception in the checkOpNoThrow and you get the error.

What is more, if you try to use the application's context(e.g. this.getApplicationContext()) instead of the listener service's context, it will also lead to an error. This is due to canWrite's checking for the caller's UID. Notification listener has a different UID from the other parts of your application.

public static boolean canWrite(Context context) {
            int uid = Binder.getCallingUid(); 
            return isCallingPackageAllowedToWriteSettings(context, uid, getPackageNameForUid(
                    context, uid), false);
        }

EDIT

This bug is tracked on AOSP issue tracker.

like image 156
csenga Avatar answered Sep 30 '22 10:09

csenga