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

Commit bf5506b2 authored by Philip P. Moltmann's avatar Philip P. Moltmann Committed by Android (Google) Code Review
Browse files

Merge changes from topic "AlwaysKeepAppOpsSynced" into qt-dev

* changes:
  Avoid overiding bg perm app-op for pre-M apps
  Also trigger PermPolicySvc on app-ops changes
  Combine successive PermPolicySvc syncs
  Always use PermMgrSrv APIs to change permission
parents 408704df 4e369a79
Loading
Loading
Loading
Loading
+89 −37
Original line number Diff line number Diff line
@@ -111,9 +111,6 @@ import static com.android.server.pm.PackageManagerServiceUtils.getCompressedFile
import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime;
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
import android.Manifest;
import android.annotation.IntDef;
@@ -294,6 +291,7 @@ import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.IntPair;
import com.android.internal.util.Preconditions;
import com.android.server.AttributeCache;
import com.android.server.DeviceIdleController;
@@ -19801,6 +19799,8 @@ public class PackageManagerService extends IPackageManager.Stub
            return;
        }
        final String packageName = ps.pkg.packageName;
        // These are flags that can change base on user actions.
        final int userSettableMask = FLAG_PERMISSION_USER_SET
                | FLAG_PERMISSION_USER_FIXED
@@ -19810,8 +19810,59 @@ public class PackageManagerService extends IPackageManager.Stub
        final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED
                | FLAG_PERMISSION_POLICY_FIXED;
        boolean writeInstallPermissions = false;
        boolean writeRuntimePermissions = false;
        // Delay and combine non-async permission callbacks
        final boolean[] permissionRemoved = new boolean[1];
        final ArraySet<Long> revokedPermissions = new ArraySet<>();
        final SparseBooleanArray updatedUsers = new SparseBooleanArray();
        PermissionCallback delayingPermCallback = new PermissionCallback() {
            public void onGidsChanged(int appId, int userId) {
                mPermissionCallback.onGidsChanged(appId, userId);
            }
            public void onPermissionChanged() {
                mPermissionCallback.onPermissionChanged();
            }
            public void onPermissionGranted(int uid, int userId) {
                mPermissionCallback.onPermissionGranted(uid, userId);
            }
            public void onInstallPermissionGranted() {
                mPermissionCallback.onInstallPermissionGranted();
            }
            public void onPermissionRevoked(int uid, int userId) {
                revokedPermissions.add(IntPair.of(uid, userId));
                updatedUsers.put(userId, true);
            }
            public void onInstallPermissionRevoked() {
                mPermissionCallback.onInstallPermissionRevoked();
            }
            public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
                for (int userId : updatedUserIds) {
                    if (sync) {
                        updatedUsers.put(userId, true);
                    } else {
                        // Don't override sync=true by sync=false
                        if (!updatedUsers.get(userId)) {
                            updatedUsers.put(userId, false);
                        }
                    }
                }
            }
            public void onPermissionRemoved() {
                permissionRemoved[0] = true;
            }
            public void onInstallPermissionUpdated() {
                mPermissionCallback.onInstallPermissionUpdated();
            }
        };
        final int permissionCount = ps.pkg.requestedPermissions.size();
        for (int i = 0; i < permissionCount; i++) {
@@ -19843,26 +19894,20 @@ public class PackageManagerService extends IPackageManager.Stub
                }
            }
            final PermissionsState permissionsState = ps.getPermissionsState();
            final int oldFlags = permissionsState.getPermissionFlags(permName, userId);
            final int oldFlags = mPermissionManager.getPermissionFlags(permName, packageName,
                    Process.SYSTEM_UID, userId);
            // Always clear the user settable flags.
            final boolean hasInstallState =
                    permissionsState.getInstallPermissionState(permName) != null;
            // If permission review is enabled and this is a legacy app, mark the
            // permission as requiring a review as this is the initial state.
            int flags = 0;
            if (ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M && bp.isRuntime()) {
                flags |= FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
            }
            if (permissionsState.updatePermissionFlags(bp, userId, userSettableMask, flags)) {
                if (hasInstallState) {
                    writeInstallPermissions = true;
                } else {
                    writeRuntimePermissions = true;
                }
            }
            mPermissionManager.updatePermissionFlags(permName, packageName,
                    userSettableMask, flags, Process.SYSTEM_UID, userId, false,
                    delayingPermCallback);
            // Below is only runtime permission handling.
            if (!bp.isRuntime()) {
@@ -19876,35 +19921,42 @@ public class PackageManagerService extends IPackageManager.Stub
            // If this permission was granted by default, make sure it is.
            if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0) {
                if (permissionsState.grantRuntimePermission(bp, userId)
                        != PERMISSION_OPERATION_FAILURE) {
                    writeRuntimePermissions = true;
                }
                mPermissionManager.grantRuntimePermission(permName, packageName, false,
                        Process.SYSTEM_UID, userId, delayingPermCallback);
            // If permission review is enabled the permissions for a legacy apps
            // are represented as constantly granted runtime ones, so don't revoke.
            } else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
                // Otherwise, reset the permission.
                final int revokeResult = permissionsState.revokeRuntimePermission(bp, userId);
                switch (revokeResult) {
                    case PERMISSION_OPERATION_SUCCESS:
                    case PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
                        writeRuntimePermissions = true;
                        final int appId = ps.appId;
                        mHandler.post(
                                () -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
                    } break;
                mPermissionManager.revokeRuntimePermission(permName, packageName, false, userId,
                        delayingPermCallback);
            }
        }
        // Execute delayed callbacks
        if (permissionRemoved[0]) {
            mPermissionCallback.onPermissionRemoved();
        }
        // Synchronously write as we are taking permissions away.
        if (writeRuntimePermissions) {
            mSettings.writeRuntimePermissionsForUserLPr(userId, true);
        // Slight variation on the code in mPermissionCallback.onPermissionRevoked() as we cannot
        // kill uid while holding mPackages-lock
        if (!revokedPermissions.isEmpty()) {
            int numRevokedPermissions = revokedPermissions.size();
            for (int i = 0; i < numRevokedPermissions; i++) {
                int revocationUID = IntPair.first(revokedPermissions.valueAt(i));
                int revocationUserId = IntPair.second(revokedPermissions.valueAt(i));
                mOnPermissionChangeListeners.onPermissionsChanged(revocationUID);
                // Kill app later as we are holding mPackages
                mHandler.post(() -> killUid(UserHandle.getAppId(revocationUID), revocationUserId,
                        KILL_APP_REASON_PERMISSIONS_REVOKED));
            }
        }
        // Synchronously write as we are taking permissions away.
        if (writeInstallPermissions) {
            mSettings.writeLPr();
        int numUpdatedUsers = updatedUsers.size();
        for (int i = 0; i < numUpdatedUsers; i++) {
            mSettings.writeRuntimePermissionsForUserLPr(updatedUsers.keyAt(i),
                    updatedUsers.valueAt(i));
        }
    }
+21 −0
Original line number Diff line number Diff line
@@ -3126,6 +3126,27 @@ public class PermissionManagerService {
            }
        }

        @Override
        public @NonNull ArrayList<PermissionInfo> getAllPermissionWithProtectionLevel(
                @PermissionInfo.Protection int protectionLevel) {
            ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();

            synchronized (PermissionManagerService.this.mLock) {
                int numTotalPermissions = mSettings.mPermissions.size();

                for (int i = 0; i < numTotalPermissions; i++) {
                    BasePermission bp = mSettings.mPermissions.valueAt(i);

                    if (bp.perm != null && bp.perm.info != null
                            && bp.protectionLevel == protectionLevel) {
                        matchingPermissions.add(bp.perm.info);
                    }
                }
            }

            return matchingPermissions;
        }

        @Override
        public @Nullable byte[] backupRuntimePermissions(@NonNull UserHandle user) {
            return PermissionManagerService.this.backupRuntimePermissions(user);
+4 −0
Original line number Diff line number Diff line
@@ -195,4 +195,8 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager

    /** HACK HACK methods to allow for partial migration of data to the PermissionManager class */
    public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName);

    /** Get all permission that have a certain protection level */
    public abstract @NonNull ArrayList<PermissionInfo> getAllPermissionWithProtectionLevel(
            @PermissionInfo.Protection int protectionLevel);
}
+163 −53
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;

import android.annotation.NonNull;
@@ -41,20 +42,27 @@ import android.content.pm.PackageParser;
import android.content.pm.PermissionInfo;
import android.os.Build;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManagerInternal;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManagerInternal;
import android.provider.Telephony;
import android.telecom.TelecomManager;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;

import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
@@ -76,6 +84,13 @@ public final class PermissionPolicyService extends SystemService {
    @GuardedBy("mLock")
    private final SparseBooleanArray mIsStarted = new SparseBooleanArray();

    /**
     * Whether an async {@link #synchronizePackagePermissionsAndAppOpsForUser} is currently
     * scheduled for a package/user.
     */
    @GuardedBy("mLock")
    private final ArraySet<Pair<String, Integer>> mIsPackageSyncsScheduled = new ArraySet<>();

    public PermissionPolicyService(@NonNull Context context) {
        super(context);

@@ -86,8 +101,10 @@ public final class PermissionPolicyService extends SystemService {
    public void onStart() {
        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
                PackageManagerInternal.class);
        final PermissionManagerInternal permManagerInternal = LocalServices.getService(
                PermissionManagerInternal.class);
        final PermissionManagerServiceInternal permManagerInternal = LocalServices.getService(
                PermissionManagerServiceInternal.class);
        final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
                ServiceManager.getService(Context.APP_OPS_SERVICE));

        packageManagerInternal.getPackageList(new PackageListObserver() {
            @Override
@@ -111,11 +128,62 @@ public final class PermissionPolicyService extends SystemService {
        });

        permManagerInternal.addOnRuntimePermissionStateChangedListener(
                (packageName, changedUserId) -> {
                this::synchronizePackagePermissionsAndAppOpsAsyncForUser);

        IAppOpsCallback appOpsListener = new IAppOpsCallback.Stub() {
            public void opChanged(int op, int uid, String packageName) {
                synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName,
                        UserHandle.getUserId(uid));
            }
        };

        final ArrayList<PermissionInfo> dangerousPerms =
                permManagerInternal.getAllPermissionWithProtectionLevel(
                        PermissionInfo.PROTECTION_DANGEROUS);

        try {
            int numDangerousPerms = dangerousPerms.size();
            for (int i = 0; i < numDangerousPerms; i++) {
                PermissionInfo perm = dangerousPerms.get(i);

                if (perm.isHardRestricted() || perm.backgroundPermission != null) {
                    appOpsService.startWatchingMode(AppOpsManager.permissionToOpCode(perm.name),
                            null, appOpsListener);
                } else if (perm.isSoftRestricted()) {
                    appOpsService.startWatchingMode(AppOpsManager.permissionToOpCode(perm.name),
                            null, appOpsListener);

                    SoftRestrictedPermissionPolicy policy =
                            SoftRestrictedPermissionPolicy.forPermission(null, null, null,
                                    perm.name);
                    if (policy.resolveAppOp() != OP_NONE) {
                        appOpsService.startWatchingMode(policy.resolveAppOp(), null,
                                appOpsListener);
                    }
                }
            }
        } catch (RemoteException doesNotHappen) {
            Slog.wtf(LOG_TAG, "Cannot set up app-ops listener");
        }
    }

    private void synchronizePackagePermissionsAndAppOpsAsyncForUser(@NonNull String packageName,
            @UserIdInt int changedUserId) {
        if (isStarted(changedUserId)) {
                        synchronizePackagePermissionsAndAppOpsForUser(packageName, changedUserId);
            synchronized (mLock) {
                if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) {
                    FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                            PermissionPolicyService
                                    ::synchronizePackagePermissionsAndAppOpsForUser,
                            this, packageName, changedUserId));
                } else {
                    if (DEBUG) {
                        Slog.v(LOG_TAG, "sync for " + packageName + "/" + changedUserId
                                + " already scheduled");
                    }
                }
            }
        }
                });
    }

    @Override
@@ -230,10 +298,14 @@ public final class PermissionPolicyService extends SystemService {
     */
    private void synchronizePackagePermissionsAndAppOpsForUser(@NonNull String packageName,
            @UserIdInt int userId) {
        synchronized (mLock) {
            mIsPackageSyncsScheduled.remove(new Pair<>(packageName, userId));
        }

        if (DEBUG) {
            Slog.v(LOG_TAG,
                    "synchronizePackagePermissionsAndAppOpsForUser(" + packageName + ", " + userId
                            + ")");
                    "synchronizePackagePermissionsAndAppOpsForUser(" + packageName + ", "
                            + userId + ")");
        }

        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
@@ -336,6 +408,16 @@ public final class PermissionPolicyService extends SystemService {
         */
        private final @NonNull ArrayList<OpToUnrestrict> mOpsToForeground = new ArrayList<>();

        /**
         * All ops that need to be flipped to foreground if allow.
         *
         * Currently, only used by the foreground/background permissions logic.
         *
         * @see #syncPackages
         */
        private final @NonNull ArrayList<OpToUnrestrict> mOpsToForegroundIfAllow =
                new ArrayList<>();

        PermissionToOpSynchroniser(@NonNull Context context) {
            mContext = context;
            mPackageManager = context.getPackageManager();
@@ -351,22 +433,27 @@ public final class PermissionPolicyService extends SystemService {
            final int allowCount = mOpsToAllow.size();
            for (int i = 0; i < allowCount; i++) {
                final OpToUnrestrict op = mOpsToAllow.get(i);
                setUidModeAllowed(op.code, op.uid);
                setUidModeAllowed(op.code, op.uid, op.packageName);
            }
            final int allowIfDefaultCount = mOpsToAllowIfDefault.size();
            for (int i = 0; i < allowIfDefaultCount; i++) {
                final OpToUnrestrict op = mOpsToAllowIfDefault.get(i);
                setUidModeAllowedIfDefault(op.code, op.uid, op.packageName);
            }
            final int foregroundCount = mOpsToForeground.size();
            final int foregroundCount = mOpsToForegroundIfAllow.size();
            for (int i = 0; i < foregroundCount; i++) {
                final OpToUnrestrict op = mOpsToForegroundIfAllow.get(i);
                setUidModeForegroundIfAllow(op.code, op.uid, op.packageName);
            }
            final int foregroundIfAllowCount = mOpsToForeground.size();
            for (int i = 0; i < foregroundIfAllowCount; i++) {
                final OpToUnrestrict op = mOpsToForeground.get(i);
                setUidModeForeground(op.code, op.uid);
                setUidModeForeground(op.code, op.uid, op.packageName);
            }
            final int ignoreCount = mOpsToIgnore.size();
            for (int i = 0; i < ignoreCount; i++) {
                final OpToUnrestrict op = mOpsToIgnore.get(i);
                setUidModeIgnored(op.code, op.uid);
                setUidModeIgnored(op.code, op.uid, op.packageName);
            }
            final int ignoreIfDefaultCount = mOpsToIgnoreIfDefault.size();
            for (int i = 0; i < ignoreIfDefaultCount; i++) {
@@ -459,6 +546,24 @@ public final class PermissionPolicyService extends SystemService {
            }
        }

        private boolean isBgPermRestricted(@NonNull String pkg, @NonNull String perm, int uid) {
            try {
                final PermissionInfo bgPermInfo = mPackageManager.getPermissionInfo(perm, 0);

                if (bgPermInfo.isSoftRestricted()) {
                    Slog.wtf(LOG_TAG, "Support for soft restricted background permissions not "
                            + "implemented");
                }

                return bgPermInfo.isHardRestricted() && (mPackageManager.getPermissionFlags(
                                perm, pkg, UserHandle.getUserHandleForUid(uid))
                                & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
            } catch (NameNotFoundException e) {
                Slog.w(LOG_TAG, "Cannot read permission state of " + perm, e);
                return false;
            }
        }

        /**
         * Add op that belong to a foreground permission for later processing in
         * {@link #syncPackages()}.
@@ -481,26 +586,27 @@ public final class PermissionPolicyService extends SystemService {
            final String pkgName = pkg.packageName;
            final int uid = pkg.applicationInfo.uid;

            if (mPackageManager.checkPermission(permission, pkgName)
                    == PackageManager.PERMISSION_GRANTED) {
                boolean isBgHardRestricted = false;
                try {
                    final PermissionInfo bgPermInfo = mPackageManager.getPermissionInfo(
                            bgPermissionName, 0);
            // App does not support runtime permissions. Hence the state is encoded in the app-op.
            // To not override unrecoverable state don't change app-op unless bg perm is reviewed.
            if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
                // If the review is required for this permission, the grant state does not
                // really matter. To have a stable state, don't change the app-op if review is still
                // pending.
                int flags = mPackageManager.getPermissionFlags(bgPermissionName,
                        pkg.packageName, UserHandle.getUserHandleForUid(uid));

                    if (bgPermInfo.isSoftRestricted()) {
                        Slog.wtf(LOG_TAG, "Support for soft restricted background permissions not "
                                + "implemented");
                if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0
                        && isBgPermRestricted(pkgName, bgPermissionName, uid)) {
                    mOpsToForegroundIfAllow.add(new OpToUnrestrict(uid, pkgName, opCode));
                }

                    isBgHardRestricted =
                            bgPermInfo.isHardRestricted() && (mPackageManager.getPermissionFlags(
                                    bgPermissionName, pkgName, UserHandle.getUserHandleForUid(uid))
                                    & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
                } catch (NameNotFoundException e) {
                    Slog.w(LOG_TAG, "Cannot read permission state of " + bgPermissionName, e);
                return;
            }

            if (mPackageManager.checkPermission(permission, pkgName)
                    == PackageManager.PERMISSION_GRANTED) {
                final boolean isBgHardRestricted = isBgPermRestricted(pkgName, bgPermissionName,
                        uid);
                final boolean isBgPermGranted = mPackageManager.checkPermission(bgPermissionName,
                        pkgName) == PackageManager.PERMISSION_GRANTED;

@@ -554,45 +660,49 @@ public final class PermissionPolicyService extends SystemService {
        }

        private void setUidModeAllowedIfDefault(int opCode, int uid, @NonNull String packageName) {
            setUidModeIfDefault(opCode, uid, AppOpsManager.MODE_ALLOWED, packageName);
            setUidModeIfMode(opCode, uid, MODE_DEFAULT, MODE_ALLOWED, packageName);
        }

        private void setUidModeAllowed(int opCode, int uid, @NonNull String packageName) {
            setUidMode(opCode, uid, MODE_ALLOWED, packageName);
        }

        private void setUidModeAllowed(int opCode, int uid) {
            mAppOpsManager.setUidMode(opCode, uid, AppOpsManager.MODE_ALLOWED);
        private void setUidModeForegroundIfAllow(int opCode, int uid, @NonNull String packageName) {
            setUidModeIfMode(opCode, uid, MODE_ALLOWED, MODE_FOREGROUND, packageName);
        }

        private void setUidModeForeground(int opCode, int uid) {
            mAppOpsManager.setUidMode(opCode, uid, AppOpsManager.MODE_FOREGROUND);
        private void setUidModeForeground(int opCode, int uid, @NonNull String packageName) {
            setUidMode(opCode, uid, MODE_FOREGROUND, packageName);
        }

        private void setUidModeIgnoredIfDefault(int opCode, int uid, @NonNull String packageName) {
            setUidModeIfDefault(opCode, uid, AppOpsManager.MODE_IGNORED, packageName);
            setUidModeIfMode(opCode, uid, MODE_DEFAULT, MODE_IGNORED, packageName);
        }

        private void setUidModeIgnored(int opCode, int uid) {
            mAppOpsManager.setUidMode(opCode, uid, MODE_IGNORED);
        private void setUidModeIgnored(int opCode, int uid, @NonNull String packageName) {
            setUidMode(opCode, uid, MODE_IGNORED, packageName);
        }

        private void setUidModeIfDefault(int opCode, int uid, int mode,
        private void setUidMode(int opCode, int uid, int mode,
                @NonNull String packageName) {
            final int currentMode;
            try {
                currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager
            final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager
                    .opToPublicName(opCode), uid, packageName);
            } catch (SecurityException e) {
                // This might happen if the app was uninstalled in between the add and sync step.
                // In this case the package name cannot be resolved inside appops service and hence
                // the uid does not match.
                Slog.w(LOG_TAG, "Cannot set mode of uid=" + uid + " op=" + opCode + " to " + mode,
                        e);
                return;
            }

            if (currentMode == MODE_DEFAULT) {
            if (currentMode != mode) {
                mAppOpsManager.setUidMode(opCode, uid, mode);
            }
        }

        private void setUidModeIfMode(int opCode, int uid, int requiredModeBefore, int newMode,
                @NonNull String packageName) {
            final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager
                    .opToPublicName(opCode), uid, packageName);

            if (currentMode == requiredModeBefore) {
                mAppOpsManager.setUidMode(opCode, uid, newMode);
            }
        }

        private void setUidModeDefault(int opCode, int uid) {
            mAppOpsManager.setUidMode(opCode, uid, MODE_DEFAULT);
        }
+26 −10

File changed.

Preview size limit exceeded, changes collapsed.