Merge "Remove all calls to getComponent(StatusBar.class)"
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 47118a8..2931f33 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2576,10 +2576,12 @@
             PendingIntent.setOnMarshaledListener(
                     (PendingIntent intent, Parcel out, int outFlags) -> {
                 if (parcel == out) {
-                    if (allPendingIntents == null) {
-                        allPendingIntents = new ArraySet<>();
+                    synchronized (this) {
+                        if (allPendingIntents == null) {
+                            allPendingIntents = new ArraySet<>();
+                        }
+                        allPendingIntents.add(intent);
                     }
-                    allPendingIntents.add(intent);
                 }
             });
         }
@@ -2587,8 +2589,10 @@
             // IMPORTANT: Add marshaling code in writeToParcelImpl as we
             // want to intercept all pending events written to the parcel.
             writeToParcelImpl(parcel, flags);
-            // Must be written last!
-            parcel.writeArraySet(allPendingIntents);
+            synchronized (this) {
+                // Must be written last!
+                parcel.writeArraySet(allPendingIntents);
+            }
         } finally {
             if (collectPendingIntents) {
                 PendingIntent.setOnMarshaledListener(null);
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 11d321f..bc1cc09 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -158,6 +158,7 @@
 
 static jclass   gBitmapConfig_class;
 static jfieldID gBitmapConfig_nativeInstanceID;
+static jmethodID gBitmapConfig_nativeToConfigMethodID;
 
 static jclass   gBitmapRegionDecoder_class;
 static jmethodID gBitmapRegionDecoder_constructorMethodID;
@@ -345,6 +346,54 @@
     bitmap::toBitmap(env, bitmap).getSkBitmap(outBitmap);
 }
 
+AndroidBitmapFormat GraphicsJNI::getFormatFromConfig(JNIEnv* env, jobject jconfig) {
+    ALOG_ASSERT(env);
+    if (NULL == jconfig) {
+        return ANDROID_BITMAP_FORMAT_NONE;
+    }
+    ALOG_ASSERT(env->IsInstanceOf(jconfig, gBitmapConfig_class));
+    jint javaConfigId = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID);
+
+    const AndroidBitmapFormat config2BitmapFormat[] = {
+        ANDROID_BITMAP_FORMAT_NONE,
+        ANDROID_BITMAP_FORMAT_A_8,
+        ANDROID_BITMAP_FORMAT_NONE, // Previously Config.Index_8
+        ANDROID_BITMAP_FORMAT_RGB_565,
+        ANDROID_BITMAP_FORMAT_RGBA_4444,
+        ANDROID_BITMAP_FORMAT_RGBA_8888,
+        ANDROID_BITMAP_FORMAT_RGBA_F16,
+        ANDROID_BITMAP_FORMAT_NONE // Congfig.HARDWARE
+    };
+    return config2BitmapFormat[javaConfigId];
+}
+
+jobject GraphicsJNI::getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat format) {
+    ALOG_ASSERT(env);
+    jint configId = kNo_LegacyBitmapConfig;
+    switch (format) {
+      case ANDROID_BITMAP_FORMAT_A_8:
+        configId = kA8_LegacyBitmapConfig;
+        break;
+      case ANDROID_BITMAP_FORMAT_RGB_565:
+        configId = kRGB_565_LegacyBitmapConfig;
+        break;
+      case ANDROID_BITMAP_FORMAT_RGBA_4444:
+        configId = kARGB_4444_LegacyBitmapConfig;
+        break;
+      case ANDROID_BITMAP_FORMAT_RGBA_8888:
+        configId = kARGB_8888_LegacyBitmapConfig;
+        break;
+      case ANDROID_BITMAP_FORMAT_RGBA_F16:
+        configId = kRGBA_16F_LegacyBitmapConfig;
+        break;
+      default:
+        break;
+    }
+
+    return env->CallStaticObjectMethod(gBitmapConfig_class,
+                                       gBitmapConfig_nativeToConfigMethodID, configId);
+}
+
 SkColorType GraphicsJNI::getNativeBitmapColorType(JNIEnv* env, jobject jconfig) {
     ALOG_ASSERT(env);
     if (NULL == jconfig) {
@@ -634,6 +683,9 @@
 
     gBitmapConfig_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Bitmap$Config"));
     gBitmapConfig_nativeInstanceID = GetFieldIDOrDie(env, gBitmapConfig_class, "nativeInt", "I");
+    gBitmapConfig_nativeToConfigMethodID = GetStaticMethodIDOrDie(env, gBitmapConfig_class,
+                                                                  "nativeToConfig",
+                                                                  "(I)Landroid/graphics/Bitmap$Config;");
 
     gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas"));
     gCanvas_nativeInstanceID = GetFieldIDOrDie(env, gCanvas_class, "mNativeCanvasWrapper", "J");
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index f80651c..6e7d9e7 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -76,6 +76,8 @@
         or kUnknown_SkColorType if the java object is null.
     */
     static SkColorType getNativeBitmapColorType(JNIEnv*, jobject jconfig);
+    static AndroidBitmapFormat getFormatFromConfig(JNIEnv* env, jobject jconfig);
+    static jobject getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat format);
 
     static bool isHardwareConfig(JNIEnv* env, jobject jconfig);
     static jint hardwareLegacyBitmapConfig();
diff --git a/core/jni/android/graphics/apex/android_bitmap.cpp b/core/jni/android/graphics/apex/android_bitmap.cpp
index 96cc5db..a328def 100644
--- a/core/jni/android/graphics/apex/android_bitmap.cpp
+++ b/core/jni/android/graphics/apex/android_bitmap.cpp
@@ -17,6 +17,7 @@
 #include "android/graphics/bitmap.h"
 #include "Bitmap.h"
 #include "TypeCast.h"
+#include "GraphicsJNI.h"
 
 #include <hwui/Bitmap.h>
 
@@ -104,3 +105,11 @@
     }
     return bitmap->pixels();
 }
+
+AndroidBitmapFormat ABitmapConfig_getFormatFromConfig(JNIEnv* env, jobject bitmapConfigObj) {
+  return GraphicsJNI::getFormatFromConfig(env, bitmapConfigObj);
+}
+
+jobject ABitmapConfig_getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat format) {
+  return GraphicsJNI::getConfigFromFormat(env, format);
+}
diff --git a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h
index bfa4c8d..dea5517 100644
--- a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h
+++ b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h
@@ -38,6 +38,9 @@
 
 void* ABitmap_getPixels(ABitmap* bitmap);
 
+AndroidBitmapFormat ABitmapConfig_getFormatFromConfig(JNIEnv* env, jobject bitmapConfigObj);
+jobject ABitmapConfig_getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat format);
+
 __END_DECLS
 
 #ifdef	__cplusplus
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 5b9b703..7da2752 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1093,6 +1093,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "200829729": {
+      "message": "ScreenRotationAnimation onAnimationEnd",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
+    },
     "221540118": {
       "message": "mUserActivityTimeout set to %d",
       "level": "DEBUG",
@@ -1159,6 +1165,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "292555239": {
+      "message": "ScreenRotation sill animating: mDisplayAnimator: %s\nmEnterBlackFrameAnimator: %s\nmRotateScreenAnimator: %s\nmScreenshotRotationAnimator: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
+    },
     "292904800": {
       "message": "Deferring rotation, animation in progress.",
       "level": "VERBOSE",
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index bc4bceb..a5c62cb 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -22,7 +22,7 @@
 #include <assert.h>
 #include <utils/Log.h>
 #include <utils/threads.h>
-#include <SkBitmap.h>
+#include <android/graphics/bitmap.h>
 #include <media/IMediaHTTPService.h>
 #include <media/mediametadataretriever.h>
 #include <media/mediascanner.h>
@@ -36,8 +36,6 @@
 #include "android_media_Streams.h"
 #include "android_util_Binder.h"
 
-#include "android/graphics/GraphicsJNI.h"
-
 using namespace android;
 
 struct fields_t {
@@ -45,8 +43,6 @@
     jclass bitmapClazz;  // Must be a global ref
     jmethodID createBitmapMethod;
     jmethodID createScaledBitmapMethod;
-    jclass configClazz;  // Must be a global ref
-    jmethodID createConfigMethod;
     jclass bitmapParamsClazz; // Must be a global ref
     jfieldID inPreferredConfig;
     jfieldID outActualConfig;
@@ -263,7 +259,7 @@
 
 static jobject getBitmapFromVideoFrame(
         JNIEnv *env, VideoFrame *videoFrame, jint dst_width, jint dst_height,
-        SkColorType outColorType) {
+        AndroidBitmapFormat outColorType) {
     ALOGV("getBitmapFromVideoFrame: dimension = %dx%d, displaySize = %dx%d, bytes = %d",
             videoFrame->mWidth,
             videoFrame->mHeight,
@@ -271,11 +267,7 @@
             videoFrame->mDisplayHeight,
             videoFrame->mSize);
 
-    ScopedLocalRef<jobject> config(env,
-            env->CallStaticObjectMethod(
-                    fields.configClazz,
-                    fields.createConfigMethod,
-                    GraphicsJNI::colorTypeToLegacyBitmapConfig(outColorType)));
+    ScopedLocalRef<jobject> config(env, ABitmapConfig_getConfigFromFormat(env, outColorType));
 
     uint32_t width, height, displayWidth, displayHeight;
     bool swapWidthAndHeight = false;
@@ -306,10 +298,9 @@
         return NULL;
     }
 
-    SkBitmap bitmap;
-    GraphicsJNI::getSkBitmap(env, jBitmap, &bitmap);
+    graphics::Bitmap bitmap(env, jBitmap);
 
-    if (outColorType == kRGB_565_SkColorType) {
+    if (outColorType == ANDROID_BITMAP_FORMAT_RGB_565) {
         rotate((uint16_t*)bitmap.getPixels(),
                (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)),
                videoFrame->mWidth,
@@ -350,37 +341,26 @@
     return jBitmap;
 }
 
-static int getColorFormat(JNIEnv *env, jobject options,
-        int defaultPreferred = HAL_PIXEL_FORMAT_RGBA_8888) {
+static AndroidBitmapFormat getColorFormat(JNIEnv *env, jobject options,
+        AndroidBitmapFormat defaultPreferred = ANDROID_BITMAP_FORMAT_RGBA_8888) {
     if (options == NULL) {
         return defaultPreferred;
     }
 
     ScopedLocalRef<jobject> inConfig(env, env->GetObjectField(options, fields.inPreferredConfig));
-    SkColorType prefColorType = GraphicsJNI::getNativeBitmapColorType(env, inConfig.get());
+    AndroidBitmapFormat format = ABitmapConfig_getFormatFromConfig(env, inConfig.get());
 
-    if (prefColorType == kRGB_565_SkColorType) {
-        return HAL_PIXEL_FORMAT_RGB_565;
+    if (format == ANDROID_BITMAP_FORMAT_RGB_565) {
+        return ANDROID_BITMAP_FORMAT_RGB_565;
     }
-    return HAL_PIXEL_FORMAT_RGBA_8888;
+    return ANDROID_BITMAP_FORMAT_RGBA_8888;
 }
 
-static SkColorType setOutColorType(JNIEnv *env, int colorFormat, jobject options) {
-    SkColorType outColorType = kN32_SkColorType;
-    if (colorFormat == HAL_PIXEL_FORMAT_RGB_565) {
-        outColorType = kRGB_565_SkColorType;
-    }
-
+static void setOutConfig(JNIEnv *env, jobject options, AndroidBitmapFormat colorFormat) {
     if (options != NULL) {
-        ScopedLocalRef<jobject> config(env,
-                env->CallStaticObjectMethod(
-                        fields.configClazz,
-                        fields.createConfigMethod,
-                        GraphicsJNI::colorTypeToLegacyBitmapConfig(outColorType)));
-
+        ScopedLocalRef<jobject> config(env, ABitmapConfig_getConfigFromFormat(env, colorFormat));
         env->SetObjectField(options, fields.outActualConfig, config.get());
     }
-    return outColorType;
 }
 
 static jobject android_media_MediaMetadataRetriever_getFrameAtTime(
@@ -394,9 +374,9 @@
         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
         return NULL;
     }
-    // For getFrameAtTime family of calls, default to HAL_PIXEL_FORMAT_RGB_565
+    // For getFrameAtTime family of calls, default to ANDROID_BITMAP_FORMAT_RGB_565
     // to keep the behavior consistent with older releases
-    int colorFormat = getColorFormat(env, params, HAL_PIXEL_FORMAT_RGB_565);
+    AndroidBitmapFormat colorFormat = getColorFormat(env, params, ANDROID_BITMAP_FORMAT_RGB_565);
 
     // Call native method to retrieve a video frame
     VideoFrame *videoFrame = NULL;
@@ -413,9 +393,8 @@
         return NULL;
     }
 
-    SkColorType outColorType = setOutColorType(env, colorFormat, params);
-
-    return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, outColorType);
+    setOutConfig(env, params, colorFormat);
+    return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, colorFormat);
 }
 
 static jobject android_media_MediaMetadataRetriever_getImageAtIndex(
@@ -428,7 +407,7 @@
         return NULL;
     }
 
-    int colorFormat = getColorFormat(env, params);
+    AndroidBitmapFormat colorFormat = getColorFormat(env, params);
 
     // Call native method to retrieve an image
     VideoFrame *videoFrame = NULL;
@@ -445,9 +424,8 @@
         return NULL;
     }
 
-    SkColorType outColorType = setOutColorType(env, colorFormat, params);
-
-    return getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType);
+    setOutConfig(env, params, colorFormat);
+    return getBitmapFromVideoFrame(env, videoFrame, -1, -1, colorFormat);
 }
 
 static jobject android_media_MediaMetadataRetriever_getThumbnailImageAtIndex(
@@ -461,7 +439,7 @@
         return NULL;
     }
 
-    int colorFormat = getColorFormat(env, params);
+    AndroidBitmapFormat colorFormat = getColorFormat(env, params);
     jint dst_width = -1, dst_height = -1;
 
     // Call native method to retrieve an image
@@ -508,9 +486,8 @@
     // thumbnails extracted by BitmapFactory APIs.
     videoFrame->mRotationAngle = 0;
 
-    SkColorType outColorType = setOutColorType(env, colorFormat, params);
-
-    return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, outColorType);
+    setOutConfig(env, params, colorFormat);
+    return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, colorFormat);
 }
 
 static jobject android_media_MediaMetadataRetriever_getFrameAtIndex(
@@ -532,8 +509,8 @@
         return NULL;
     }
 
-    int colorFormat = getColorFormat(env, params);
-    SkColorType outColorType = setOutColorType(env, colorFormat, params);
+    AndroidBitmapFormat colorFormat = getColorFormat(env, params);
+    setOutConfig(env, params, colorFormat);
     size_t i = 0;
     for (; i < numFrames; i++) {
         sp<IMemory> frame = retriever->getFrameAtIndex(frameIndex + i, colorFormat);
@@ -546,7 +523,7 @@
         //       Either document why it is safe in this case or address the
         //       issue (e.g. by copying).
         VideoFrame *videoFrame = static_cast<VideoFrame *>(frame->unsecurePointer());
-        jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType);
+        jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1, colorFormat);
         env->CallBooleanMethod(arrayList, fields.arrayListAdd, bitmapObj);
         env->DeleteLocalRef(bitmapObj);
     }
@@ -671,21 +648,6 @@
         return;
     }
 
-    clazz.reset(env->FindClass("android/graphics/Bitmap$Config"));
-    if (clazz.get() == NULL) {
-        return;
-    }
-    fields.configClazz = (jclass) env->NewGlobalRef(clazz.get());
-    if (fields.configClazz == NULL) {
-        return;
-    }
-    fields.createConfigMethod =
-            env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
-                    "(I)Landroid/graphics/Bitmap$Config;");
-    if (fields.createConfigMethod == NULL) {
-        return;
-    }
-
     clazz.reset(env->FindClass("android/media/MediaMetadataRetriever$BitmapParams"));
     if (clazz.get() == NULL) {
         return;
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 21b52c1..06aba40 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -38,7 +38,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.settingslib.WirelessUtils;
 import com.android.systemui.Dependency;
@@ -106,7 +105,7 @@
             updateCarrierText();
         }
 
-        public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
+        public void onSimStateChanged(int subId, int slotId, int simState) {
             if (slotId < 0 || slotId >= mSimSlotsNumber) {
                 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
                         + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
@@ -191,7 +190,7 @@
             CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
         final CharSequence carrier = "";
         CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
-                IccCardConstants.State.CARD_IO_ERROR, carrier);
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier);
         // mSimErrorState has the state of each sim indexed by slotID.
         for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) {
             if (!mSimErrorState[index]) {
@@ -288,7 +287,7 @@
             carrierNames[i] = "";
             subsIds[i] = subId;
             subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
-            IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
+            int simState = mKeyguardUpdateMonitor.getSimState(subId);
             CharSequence carrierName = subs.get(i).getCarrierName();
             CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
             if (DEBUG) {
@@ -298,7 +297,7 @@
                 allSimsMissing = false;
                 carrierNames[i] = carrierTextForSimState;
             }
-            if (simState == IccCardConstants.State.READY) {
+            if (simState == TelephonyManager.SIM_STATE_READY) {
                 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
                 if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
                     // hack for WFC (IWLAN) not turning off immediately once
@@ -406,8 +405,7 @@
      *
      * @return Carrier text if not in missing state, null otherwise.
      */
-    private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
-            CharSequence text) {
+    private CharSequence getCarrierTextForSimState(int simState, CharSequence text) {
         CharSequence carrierText = null;
         CarrierTextController.StatusMode status = getStatusForIccState(simState);
         switch (status) {
@@ -498,37 +496,32 @@
     /**
      * Determine the current status of the lock screen given the SIM state and other stuff.
      */
-    private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
-        // Since reading the SIM may take a while, we assume it is present until told otherwise.
-        if (simState == null) {
-            return CarrierTextController.StatusMode.Normal;
-        }
-
+    private CarrierTextController.StatusMode getStatusForIccState(int simState) {
         final boolean missingAndNotProvisioned =
                 !Dependency.get(KeyguardUpdateMonitor.class).isDeviceProvisioned()
-                        && (simState == IccCardConstants.State.ABSENT
-                        || simState == IccCardConstants.State.PERM_DISABLED);
+                        && (simState == TelephonyManager.SIM_STATE_ABSENT
+                        || simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
 
         // Assume we're NETWORK_LOCKED if not provisioned
-        simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
+        simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState;
         switch (simState) {
-            case ABSENT:
+            case TelephonyManager.SIM_STATE_ABSENT:
                 return CarrierTextController.StatusMode.SimMissing;
-            case NETWORK_LOCKED:
+            case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
                 return CarrierTextController.StatusMode.SimMissingLocked;
-            case NOT_READY:
+            case TelephonyManager.SIM_STATE_NOT_READY:
                 return CarrierTextController.StatusMode.SimNotReady;
-            case PIN_REQUIRED:
+            case TelephonyManager.SIM_STATE_PIN_REQUIRED:
                 return CarrierTextController.StatusMode.SimLocked;
-            case PUK_REQUIRED:
+            case TelephonyManager.SIM_STATE_PUK_REQUIRED:
                 return CarrierTextController.StatusMode.SimPukLocked;
-            case READY:
+            case TelephonyManager.SIM_STATE_READY:
                 return CarrierTextController.StatusMode.Normal;
-            case PERM_DISABLED:
+            case TelephonyManager.SIM_STATE_PERM_DISABLED:
                 return CarrierTextController.StatusMode.SimPermDisabled;
-            case UNKNOWN:
+            case TelephonyManager.SIM_STATE_UNKNOWN:
                 return CarrierTextController.StatusMode.SimUnknown;
-            case CARD_IO_ERROR:
+            case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
                 return CarrierTextController.StatusMode.SimIoError;
         }
         return CarrierTextController.StatusMode.SimUnknown;
@@ -575,7 +568,7 @@
         return list;
     }
 
-    private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
+    private CharSequence getCarrierHelpTextForSimState(int simState,
             String plmn, String spn) {
         int carrierHelpTextId = 0;
         CarrierTextController.StatusMode status = getStatusForIccState(simState);
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index ecd8c8d..867014b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -37,7 +37,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
@@ -59,7 +58,7 @@
     KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
 
         @Override
-        public void onSimStateChanged(int subId, int slotId, State simState) {
+        public void onSimStateChanged(int subId, int slotId, int simState) {
             updateEmergencyCallButton();
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index 64b4d32..17abfae 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -20,8 +20,8 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 
-import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
 
@@ -66,12 +66,12 @@
         KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
 
         if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
-                monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) {
+                monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED))) {
             return SecurityMode.SimPuk;
         }
 
         if (SubscriptionManager.isValidSubscriptionId(
-                monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
+                monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED))) {
             return SecurityMode.SimPin;
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index b1502b9..b960de5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -38,8 +38,6 @@
 import android.widget.ImageView;
 
 import com.android.internal.telephony.ITelephony;
-import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -66,10 +64,10 @@
 
     KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
         @Override
-        public void onSimStateChanged(int subId, int slotId, State simState) {
+        public void onSimStateChanged(int subId, int slotId, int simState) {
             if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
             switch(simState) {
-                case READY: {
+                case TelephonyManager.SIM_STATE_READY: {
                     mRemainingAttempts = -1;
                     resetState();
                     break;
@@ -157,7 +155,7 @@
 
     private void handleSubInfoChangeIfNeeded() {
         KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
-        int subId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
+        int subId = monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED);
         if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
             mSubId = subId;
             mShowDefaultMessage = true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 70237a0..7e08ab3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -37,8 +37,6 @@
 import android.widget.ImageView;
 
 import com.android.internal.telephony.ITelephony;
-import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -69,12 +67,12 @@
 
     KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
         @Override
-        public void onSimStateChanged(int subId, int slotId, State simState) {
+        public void onSimStateChanged(int subId, int slotId, int simState) {
             if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
             switch(simState) {
                 // If the SIM is unlocked via a key sequence through the emergency dialer, it will
                 // move into the READY state and the PUK lock keyguard should be removed.
-                case READY: {
+                case TelephonyManager.SIM_STATE_READY: {
                     mRemainingAttempts = -1;
                     mShowDefaultMessage = true;
                     // mCallback can be null if onSimStateChanged callback is called when keyguard
@@ -210,7 +208,7 @@
 
     private void handleSubInfoChangeIfNeeded() {
         KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
-        int subId = monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED);
+        int subId = monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED);
         if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
             mSubId = subId;
             mShowDefaultMessage = true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0b0922a..1d4b9ef 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -93,7 +93,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.widget.LockPatternUtils;
@@ -1055,7 +1054,7 @@
                 // and processed previously.
                 if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
                     // Guarantee mTelephonyCapable state after SysUI crash and restart
-                    if (args.simState == State.ABSENT) {
+                    if (args.simState == TelephonyManager.SIM_STATE_ABSENT) {
                         mHandler.obtainMessage(MSG_TELEPHONY_CAPABLE, true).sendToTarget();
                     }
                     return;
@@ -1226,18 +1225,18 @@
      * the intent and provide a {@link SimCard.State} result.
      */
     private static class SimData {
-        public State simState;
+        public int simState;
         public int slotId;
         public int subId;
 
-        SimData(State state, int slot, int id) {
+        SimData(int state, int slot, int id) {
             simState = state;
             slotId = slot;
             subId = id;
         }
 
         static SimData fromIntent(Intent intent) {
-            State state;
+            int state;
             if (!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
                 throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED");
             }
@@ -1251,33 +1250,33 @@
 
                 if (IccCardConstants.INTENT_VALUE_ABSENT_ON_PERM_DISABLED.equals(
                         absentReason)) {
-                    state = IccCardConstants.State.PERM_DISABLED;
+                    state = TelephonyManager.SIM_STATE_PERM_DISABLED;
                 } else {
-                    state = IccCardConstants.State.ABSENT;
+                    state = TelephonyManager.SIM_STATE_ABSENT;
                 }
             } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
-                state = IccCardConstants.State.READY;
+                state = TelephonyManager.SIM_STATE_READY;
             } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
                 final String lockedReason = intent
                         .getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
                 if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
-                    state = IccCardConstants.State.PIN_REQUIRED;
+                    state = TelephonyManager.SIM_STATE_PIN_REQUIRED;
                 } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
-                    state = IccCardConstants.State.PUK_REQUIRED;
+                    state = TelephonyManager.SIM_STATE_PUK_REQUIRED;
                 } else {
-                    state = IccCardConstants.State.UNKNOWN;
+                    state = TelephonyManager.SIM_STATE_UNKNOWN;
                 }
             } else if (IccCardConstants.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
-                state = IccCardConstants.State.NETWORK_LOCKED;
+                state = TelephonyManager.SIM_STATE_NETWORK_LOCKED;
             } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
-                state = IccCardConstants.State.CARD_IO_ERROR;
+                state = TelephonyManager.SIM_STATE_CARD_IO_ERROR;
             } else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(stateExtra)
                     || IccCardConstants.INTENT_VALUE_ICC_IMSI.equals(stateExtra)) {
                 // This is required because telephony doesn't return to "READY" after
                 // these state transitions. See bug 7197471.
-                state = IccCardConstants.State.READY;
+                state = TelephonyManager.SIM_STATE_READY;
             } else {
-                state = IccCardConstants.State.UNKNOWN;
+                state = TelephonyManager.SIM_STATE_UNKNOWN;
             }
             return new SimData(state, slotId, subId);
         }
@@ -1525,7 +1524,7 @@
                         handleBatteryUpdate((BatteryStatus) msg.obj);
                         break;
                     case MSG_SIM_STATE_CHANGE:
-                        handleSimStateChange(msg.arg1, msg.arg2, (State) msg.obj);
+                        handleSimStateChange(msg.arg1, msg.arg2, (int) msg.obj);
                         break;
                     case MSG_RINGER_MODE_CHANGED:
                         handleRingerModeChange(msg.arg1);
@@ -2260,7 +2259,7 @@
      * Handle {@link #MSG_SIM_STATE_CHANGE}
      */
     @VisibleForTesting
-    void handleSimStateChange(int subId, int slotId, State state) {
+    void handleSimStateChange(int subId, int slotId, int state) {
         checkIsHandlerThread();
         if (DEBUG_SIM_STATES) {
             Log.d(TAG, "handleSimStateChange(subId=" + subId + ", slotId="
@@ -2272,7 +2271,7 @@
             Log.w(TAG, "invalid subId in handleSimStateChange()");
             /* Only handle No SIM(ABSENT) and Card Error(CARD_IO_ERROR) due to
              * handleServiceStateChange() handle other case */
-            if (state == State.ABSENT) {
+            if (state == TelephonyManager.SIM_STATE_ABSENT) {
                 updateTelephonyCapable(true);
                 // Even though the subscription is not valid anymore, we need to notify that the
                 // SIM card was removed so we can update the UI.
@@ -2281,10 +2280,10 @@
                     // Set the SIM state of all SimData associated with that slot to ABSENT se we
                     // do not move back into PIN/PUK locked and not detect the change below.
                     if (data.slotId == slotId) {
-                        data.simState = State.ABSENT;
+                        data.simState = TelephonyManager.SIM_STATE_ABSENT;
                     }
                 }
-            } else if (state == State.CARD_IO_ERROR) {
+            } else if (state == TelephonyManager.SIM_STATE_CARD_IO_ERROR) {
                 updateTelephonyCapable(true);
             } else {
                 return;
@@ -2303,7 +2302,7 @@
             data.subId = subId;
             data.slotId = slotId;
         }
-        if ((changed || becameAbsent) && state != State.UNKNOWN) {
+        if ((changed || becameAbsent) && state != TelephonyManager.SIM_STATE_UNKNOWN) {
             for (int i = 0; i < mCallbacks.size(); i++) {
                 KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
                 if (cb != null) {
@@ -2542,7 +2541,7 @@
     @MainThread
     public void reportSimUnlocked(int subId) {
         if (DEBUG_SIM_STATES) Log.v(TAG, "reportSimUnlocked(subId=" + subId + ")");
-        handleSimStateChange(subId, getSlotId(subId), State.READY);
+        handleSimStateChange(subId, getSlotId(subId), TelephonyManager.SIM_STATE_READY);
     }
 
     /**
@@ -2607,11 +2606,11 @@
         return false;
     }
 
-    public State getSimState(int subId) {
+    public int getSimState(int subId) {
         if (mSimDatas.containsKey(subId)) {
             return mSimDatas.get(subId).simState;
         } else {
-            return State.UNKNOWN;
+            return TelephonyManager.SIM_STATE_UNKNOWN;
         }
     }
 
@@ -2644,22 +2643,10 @@
      * @return true if and only if the state has changed for the specified {@code slotId}
      */
     private boolean refreshSimState(int subId, int slotId) {
-
-        // This is awful. It exists because there are two APIs for getting the SIM status
-        // that don't return the complete set of values and have different types. In Keyguard we
-        // need IccCardConstants, but TelephonyManager would only give us
-        // TelephonyManager.SIM_STATE*, so we retrieve it manually.
         final TelephonyManager tele =
             (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        int simState = (tele != null) ?
+        int state = (tele != null) ?
             tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN;
-        State state;
-        try {
-            state = State.intToState(simState);
-        } catch (IllegalArgumentException ex) {
-            Log.w(TAG, "Unknown sim state: " + simState);
-            state = State.UNKNOWN;
-        }
         SimData data = mSimDatas.get(subId);
         final boolean changed;
         if (data == null) {
@@ -2676,10 +2663,10 @@
     /**
      * If the {@code state} is currently requiring a SIM PIN, PUK, or is disabled.
      */
-    public static boolean isSimPinSecure(IccCardConstants.State state) {
-        return (state == IccCardConstants.State.PIN_REQUIRED
-                || state == IccCardConstants.State.PUK_REQUIRED
-                || state == IccCardConstants.State.PERM_DISABLED);
+    public static boolean isSimPinSecure(int state) {
+        return (state == TelephonyManager.SIM_STATE_PIN_REQUIRED
+                || state == TelephonyManager.SIM_STATE_PUK_REQUIRED
+                || state == TelephonyManager.SIM_STATE_PERM_DISABLED);
     }
 
     public DisplayClientState getCachedDisplayClientState() {
@@ -2741,7 +2728,7 @@
      *
      * @return subid or {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if none found
      */
-    public int getNextSubIdForState(State state) {
+    public int getNextSubIdForState(int state) {
         List<SubscriptionInfo> list = getSubscriptionInfo(false /* forceReload */);
         int resultId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         int bestSlotId = Integer.MAX_VALUE; // Favor lowest slot first
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 0fef755..b4b83d6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -23,7 +23,6 @@
 import android.telephony.TelephonyManager;
 import android.view.WindowManagerPolicyConstants;
 
-import com.android.internal.telephony.IccCardConstants;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 
 import java.util.TimeZone;
@@ -136,7 +135,7 @@
      * @param slotId
      * @param simState
      */
-    public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) { }
+    public void onSimStateChanged(int subId, int slotId, int simState) { }
 
     /**
      * Called when the user's info changed.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c876fa6..f026e68 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -18,9 +18,6 @@
 
 import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
 
-import static com.android.internal.telephony.IccCardConstants.State.ABSENT;
-import static com.android.internal.telephony.IccCardConstants.State.PIN_REQUIRED;
-import static com.android.internal.telephony.IccCardConstants.State.PUK_REQUIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -61,7 +58,7 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManagerPolicyConstants;
@@ -72,7 +69,6 @@
 import com.android.internal.policy.IKeyguardDrawnCallback;
 import com.android.internal.policy.IKeyguardExitCallback;
 import com.android.internal.policy.IKeyguardStateCallback;
-import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardConstants;
@@ -293,7 +289,7 @@
      * Last SIM state reported by the telephony system.
      * Index is the slotId - in case of multiple SIM cards.
      */
-    private final SparseArray<IccCardConstants.State> mLastSimStates = new SparseArray<>();
+    private final SparseIntArray mLastSimStates = new SparseIntArray();
 
     private boolean mDeviceInteractive;
     private boolean mGoingToSleep;
@@ -433,7 +429,7 @@
         }
 
         @Override
-        public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
+        public void onSimStateChanged(int subId, int slotId, int simState) {
 
             if (DEBUG_SIM_STATES) {
                 Log.d(TAG, "onSimStateChanged(subId=" + subId + ", slotId=" + slotId
@@ -455,14 +451,15 @@
 
             boolean simWasLocked;
             synchronized (KeyguardViewMediator.this) {
-                IccCardConstants.State lastState = mLastSimStates.get(slotId);
-                simWasLocked = (lastState == PIN_REQUIRED || lastState == PUK_REQUIRED);
+                int lastState = mLastSimStates.get(slotId);
+                simWasLocked = (lastState == TelephonyManager.SIM_STATE_PIN_REQUIRED
+                        || lastState == TelephonyManager.SIM_STATE_PUK_REQUIRED);
                 mLastSimStates.append(slotId, simState);
             }
 
             switch (simState) {
-                case NOT_READY:
-                case ABSENT:
+                case TelephonyManager.SIM_STATE_NOT_READY:
+                case TelephonyManager.SIM_STATE_ABSENT:
                     // only force lock screen in case of missing sim if user hasn't
                     // gone through setup wizard
                     synchronized (KeyguardViewMediator.this) {
@@ -476,7 +473,7 @@
                                 resetStateLocked();
                             }
                         }
-                        if (simState == ABSENT) {
+                        if (simState == TelephonyManager.SIM_STATE_ABSENT) {
                             // MVNO SIMs can become transiently NOT_READY when switching networks,
                             // so we should only lock when they are ABSENT.
                             if (simWasLocked) {
@@ -487,8 +484,8 @@
                         }
                     }
                     break;
-                case PIN_REQUIRED:
-                case PUK_REQUIRED:
+                case TelephonyManager.SIM_STATE_PIN_REQUIRED:
+                case TelephonyManager.SIM_STATE_PUK_REQUIRED:
                     synchronized (KeyguardViewMediator.this) {
                         if (!mShowing) {
                             if (DEBUG_SIM_STATES) Log.d(TAG,
@@ -500,7 +497,7 @@
                         }
                     }
                     break;
-                case PERM_DISABLED:
+                case TelephonyManager.SIM_STATE_PERM_DISABLED:
                     synchronized (KeyguardViewMediator.this) {
                         if (!mShowing) {
                             if (DEBUG_SIM_STATES) Log.d(TAG, "PERM_DISABLED and "
@@ -513,7 +510,7 @@
                         }
                     }
                     break;
-                case READY:
+                case TelephonyManager.SIM_STATE_READY:
                     synchronized (KeyguardViewMediator.this) {
                         if (DEBUG_SIM_STATES) Log.d(TAG, "READY, reset state? " + mShowing);
                         if (mShowing && simWasLocked) {
@@ -1334,9 +1331,9 @@
             // if the setup wizard hasn't run yet, don't show
             final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
             final boolean absent = SubscriptionManager.isValidSubscriptionId(
-                    mUpdateMonitor.getNextSubIdForState(ABSENT));
+                    mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_ABSENT));
             final boolean disabled = SubscriptionManager.isValidSubscriptionId(
-                    mUpdateMonitor.getNextSubIdForState(IccCardConstants.State.PERM_DISABLED));
+                    mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PERM_DISABLED));
             final boolean lockedOrMissing = mUpdateMonitor.isSimPinSecure()
                     || ((absent || disabled) && requireSim);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 843c37f..0a7ee3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -20,11 +20,11 @@
 import android.os.Bundle;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.widget.TextView;
 
-import com.android.internal.telephony.IccCardConstants.State;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.settingslib.WirelessUtils;
@@ -138,9 +138,9 @@
         final int N = subs.size();
         for (int i = 0; i < N; i++) {
             int subId = subs.get(i).getSubscriptionId();
-            State simState = mKeyguardUpdateMonitor.getSimState(subId);
+            int simState = mKeyguardUpdateMonitor.getSimState(subId);
             CharSequence carrierName = subs.get(i).getCarrierName();
-            if (!TextUtils.isEmpty(carrierName) && simState == State.READY) {
+            if (!TextUtils.isEmpty(carrierName) && simState == TelephonyManager.SIM_STATE_READY) {
                 ServiceState ss = mKeyguardUpdateMonitor.getServiceState(subId);
                 if (ss != null && ss.getState() == ServiceState.STATE_IN_SERVICE) {
                     displayText = carrierName;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 4927ec8..60589843 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -39,7 +39,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.graphics.ColorUtils;
-import com.android.internal.telephony.IccCardConstants;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dependency;
@@ -154,8 +153,7 @@
     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
-                public void onSimStateChanged(int subId, int slotId,
-                        IccCardConstants.State simState) {
+                public void onSimStateChanged(int subId, int slotId, int simState) {
                     mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
                     update();
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 01cd2b4..bfecaaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -33,6 +33,7 @@
 import android.provider.Settings.Global;
 import android.service.notification.ZenModeConfig;
 import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
 import android.text.format.DateFormat;
 import android.util.Log;
 
@@ -127,7 +128,7 @@
 
     // Assume it's all good unless we hear otherwise.  We don't always seem
     // to get broadcasts that it *is* there.
-    IccCardConstants.State mSimState = IccCardConstants.State.READY;
+    int mSimState = TelephonyManager.SIM_STATE_READY;
 
     private boolean mZenVisible;
     private boolean mVolumeVisible;
@@ -307,25 +308,25 @@
     private final void updateSimState(Intent intent) {
         String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
         if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
-            mSimState = IccCardConstants.State.ABSENT;
+            mSimState = TelephonyManager.SIM_STATE_READY;
         } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
-            mSimState = IccCardConstants.State.CARD_IO_ERROR;
+            mSimState = TelephonyManager.SIM_STATE_CARD_IO_ERROR;
         } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED.equals(stateExtra)) {
-            mSimState = IccCardConstants.State.CARD_RESTRICTED;
+            mSimState = TelephonyManager.SIM_STATE_CARD_RESTRICTED;
         } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
-            mSimState = IccCardConstants.State.READY;
+            mSimState = TelephonyManager.SIM_STATE_READY;
         } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
             final String lockedReason =
                     intent.getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
             if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
-                mSimState = IccCardConstants.State.PIN_REQUIRED;
+                mSimState = TelephonyManager.SIM_STATE_PIN_REQUIRED;
             } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
-                mSimState = IccCardConstants.State.PUK_REQUIRED;
+                mSimState = TelephonyManager.SIM_STATE_PUK_REQUIRED;
             } else {
-                mSimState = IccCardConstants.State.NETWORK_LOCKED;
+                mSimState = TelephonyManager.SIM_STATE_NETWORK_LOCKED;
             }
         } else {
-            mSimState = IccCardConstants.State.UNKNOWN;
+            mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index 353d6a4..e675a7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -24,11 +24,11 @@
 import android.net.ConnectivityManager;
 import android.provider.Settings;
 import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.widget.TextView;
 
-import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -83,6 +83,18 @@
         getContext().unregisterReceiver(mReceiver);
     }
 
+    private boolean iccCardExist(int simState) {
+        return ((simState == TelephonyManager.SIM_STATE_PIN_REQUIRED)
+                || (simState == TelephonyManager.SIM_STATE_PUK_REQUIRED)
+                || (simState == TelephonyManager.SIM_STATE_NETWORK_LOCKED)
+                || (simState == TelephonyManager.SIM_STATE_READY)
+                || (simState == TelephonyManager.SIM_STATE_NOT_READY)
+                || (simState == TelephonyManager.SIM_STATE_PERM_DISABLED)
+                || (simState == TelephonyManager.SIM_STATE_CARD_IO_ERROR)
+                || (simState == TelephonyManager.SIM_STATE_CARD_RESTRICTED)
+                || (simState == TelephonyManager.SIM_STATE_LOADED));
+    }
+
     public void update() {
         boolean hasMobile = ConnectivityManager.from(mContext)
                 .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
@@ -102,9 +114,9 @@
         final int N = subs.size();
         for (int i = 0; i < N; i++) {
             int subId = subs.get(i).getSubscriptionId();
-            IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
+            int simState = mKeyguardUpdateMonitor.getSimState(subId);
             CharSequence carrierName = subs.get(i).getCarrierName();
-            if (simState.iccCardExist() && !TextUtils.isEmpty(carrierName)) {
+            if (iccCardExist(simState) && !TextUtils.isEmpty(carrierName)) {
                 allSimsMissing = false;
                 displayText = carrierName;
             }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index d8eaaa1..1a1b679 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -54,7 +54,6 @@
 import android.testing.TestableLooper;
 import android.text.TextUtils;
 
-import com.android.internal.telephony.IccCardConstants;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -179,7 +178,7 @@
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
-        when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(TelephonyManager.SIM_STATE_READY);
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
         mCarrierTextController.updateCarrierText();
@@ -199,13 +198,13 @@
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
-        when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getSimState(1)).thenReturn(
-                IccCardConstants.State.CARD_IO_ERROR);
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR);
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
         mCarrierTextController.mCallback.onSimStateChanged(3, 1,
-                IccCardConstants.State.CARD_IO_ERROR);
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR);
 
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
@@ -234,11 +233,11 @@
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
                 new ArrayList<>());
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
-                IccCardConstants.State.CARD_IO_ERROR);
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR);
         // This should not produce an out of bounds error, even though there are no subscriptions
         mCarrierTextController.mCallback.onSimStateChanged(0, -3,
-                IccCardConstants.State.CARD_IO_ERROR);
-        mCarrierTextController.mCallback.onSimStateChanged(0, 3, IccCardConstants.State.READY);
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+        mCarrierTextController.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY);
         verify(mCarrierTextCallback, never()).updateCarrierInfo(any());
     }
 
@@ -254,10 +253,10 @@
                 new ArrayList<>());
 
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
-                IccCardConstants.State.CARD_IO_ERROR);
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR);
         // This should not produce an out of bounds error, even though there are no subscriptions
         mCarrierTextController.mCallback.onSimStateChanged(0, 1,
-                IccCardConstants.State.CARD_IO_ERROR);
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR);
 
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(
@@ -294,7 +293,8 @@
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION);
-        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -318,7 +318,8 @@
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION_ROAMING);
-        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -342,7 +343,8 @@
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION_NULL);
-        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
+            TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -366,7 +368,8 @@
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION_NULL);
-        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
         mockWifi();
 
@@ -422,7 +425,8 @@
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION);
         list.add(TEST_SUBSCRIPTION);
-        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
+            TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -446,8 +450,8 @@
         list.add(TEST_SUBSCRIPTION);
         list.add(TEST_SUBSCRIPTION);
         when(mKeyguardUpdateMonitor.getSimState(anyInt()))
-                .thenReturn(IccCardConstants.State.READY)
-                .thenReturn(IccCardConstants.State.NOT_READY);
+                .thenReturn(TelephonyManager.SIM_STATE_READY)
+                .thenReturn(TelephonyManager.SIM_STATE_NOT_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -471,8 +475,8 @@
         list.add(TEST_SUBSCRIPTION);
         list.add(TEST_SUBSCRIPTION);
         when(mKeyguardUpdateMonitor.getSimState(anyInt()))
-                .thenReturn(IccCardConstants.State.NOT_READY)
-                .thenReturn(IccCardConstants.State.READY);
+                .thenReturn(TelephonyManager.SIM_STATE_NOT_READY)
+                .thenReturn(TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -497,9 +501,9 @@
         list.add(TEST_SUBSCRIPTION);
         list.add(TEST_SUBSCRIPTION);
         when(mKeyguardUpdateMonitor.getSimState(anyInt()))
-                .thenReturn(IccCardConstants.State.READY)
-                .thenReturn(IccCardConstants.State.NOT_READY)
-                .thenReturn(IccCardConstants.State.READY);
+                .thenReturn(TelephonyManager.SIM_STATE_READY)
+                .thenReturn(TelephonyManager.SIM_STATE_NOT_READY)
+                .thenReturn(TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c1da53b..7be3e2b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -546,8 +546,7 @@
         }
 
         @Override
-        protected void handleSimStateChange(int subId, int slotId,
-                IccCardConstants.State state) {
+        protected void handleSimStateChange(int subId, int slotId, int state) {
             mSimStateChanged.set(true);
             super.handleSimStateChange(subId, slotId, state);
         }
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
index e90612e..f0bb192 100644
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
@@ -16,8 +16,6 @@
 
 package com.android.server.integrity.engine;
 
-import android.util.Slog;
-
 import com.android.server.integrity.model.AppInstallMetadata;
 import com.android.server.integrity.model.IntegrityCheckResult;
 import com.android.server.integrity.model.Rule;
@@ -53,23 +51,11 @@
      *
      * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules
      *                           against.
-     * @return A rule matching the metadata. If there are multiple matching rules, returns any. If
-     * no rules are matching, returns {@link Rule#EMPTY}.
+     * @return result of the integrity check
      */
     public IntegrityCheckResult evaluate(AppInstallMetadata appInstallMetadata) {
         List<Rule> rules = loadRules(appInstallMetadata);
-        Rule matchedRule = RuleEvaluator.evaluateRules(rules, appInstallMetadata);
-        if (matchedRule == Rule.EMPTY) {
-            return IntegrityCheckResult.allow();
-        } else {
-            switch (matchedRule.getEffect()) {
-                case DENY:
-                    return IntegrityCheckResult.deny(matchedRule);
-                default:
-                    Slog.e(TAG, "Matched a non-DENY rule: " + matchedRule);
-                    return IntegrityCheckResult.allow();
-            }
-        }
+        return RuleEvaluator.evaluateRules(rules, appInstallMetadata);
     }
 
     private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) {
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
index 6416505..7deae46 100644
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
@@ -16,14 +16,20 @@
 
 package com.android.server.integrity.engine;
 
+import static com.android.server.integrity.model.Rule.DENY;
+import static com.android.server.integrity.model.Rule.FORCE_ALLOW;
+
+import android.annotation.NonNull;
 import android.util.Slog;
 
 import com.android.server.integrity.model.AppInstallMetadata;
 import com.android.server.integrity.model.AtomicFormula;
 import com.android.server.integrity.model.Formula;
+import com.android.server.integrity.model.IntegrityCheckResult;
 import com.android.server.integrity.model.OpenFormula;
 import com.android.server.integrity.model.Rule;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -40,67 +46,40 @@
      * <p>Rules must be in disjunctive normal form (DNF). A rule should contain AND'ed formulas
      * only. All rules are OR'ed together by default.
      *
-     * @param rules              The list of rules to evaluate.
+     * @param rules The list of rules to evaluate.
      * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules
-     *                           against.
-     * @return A rule matching the metadata. If there are multiple matching rules, returns any. If
-     * no rules are matching, returns {@link Rule#EMPTY}.
+     *     against.
+     * @return result of the integrity check
      */
-    static Rule evaluateRules(List<Rule> rules, AppInstallMetadata appInstallMetadata) {
+    @NonNull
+    static IntegrityCheckResult evaluateRules(
+            List<Rule> rules, AppInstallMetadata appInstallMetadata) {
+        List<Rule> matchedRules = new ArrayList<>();
         for (Rule rule : rules) {
-            if (isConjunctionOfFormulas(rule.getFormula()) && isMatch(rule, appInstallMetadata)) {
-                return rule;
-            }
-        }
-        return Rule.EMPTY;
-    }
-
-    /**
-     * Match a rule against app install metadata.
-     */
-    private static boolean isMatch(Rule rule, AppInstallMetadata appInstallMetadata) {
-        return isMatch(rule.getFormula(), appInstallMetadata);
-    }
-
-    private static boolean isMatch(Formula formula, AppInstallMetadata appInstallMetadata) {
-        if (formula instanceof AtomicFormula) {
-            AtomicFormula atomicFormula = (AtomicFormula) formula;
-            switch (atomicFormula.getKey()) {
-                case PACKAGE_NAME:
-                    return atomicFormula.isMatch(appInstallMetadata.getPackageName());
-                case APP_CERTIFICATE:
-                    return atomicFormula.isMatch(appInstallMetadata.getAppCertificate());
-                case INSTALLER_NAME:
-                    return atomicFormula.isMatch(appInstallMetadata.getInstallerName());
-                case INSTALLER_CERTIFICATE:
-                    return atomicFormula.isMatch(appInstallMetadata.getInstallerCertificate());
-                case VERSION_CODE:
-                    return atomicFormula.isMatch(appInstallMetadata.getVersionCode());
-                case PRE_INSTALLED:
-                    return atomicFormula.isMatch(appInstallMetadata.isPreInstalled());
-                default:
-                    Slog.i(TAG, String.format("Returned no match for unknown key %s",
-                            atomicFormula.getKey()));
-                    return false;
-            }
-        } else if (formula instanceof OpenFormula) {
-            OpenFormula openFormula = (OpenFormula) formula;
-            // A rule is in disjunctive normal form, so there are no OR connectors.
-            switch (openFormula.getConnector()) {
-                case NOT:
-                    // NOT connector has only 1 formula attached.
-                    return !isMatch(openFormula.getFormulas().get(0), appInstallMetadata);
-                case AND:
-                    return openFormula.getFormulas().stream().allMatch(
-                            subFormula -> isMatch(subFormula, appInstallMetadata));
-                default:
-                    Slog.i(TAG, String.format("Returned no match for unknown connector %s",
-                            openFormula.getConnector()));
-                    return false;
+            if (isConjunctionOfFormulas(rule.getFormula())
+                    && rule.getFormula().isSatisfied(appInstallMetadata)) {
+                matchedRules.add(rule);
             }
         }
 
-        return false;
+        boolean denied = false;
+        Rule denyRule = null;
+        for (Rule rule : matchedRules) {
+            switch (rule.getEffect()) {
+                case DENY:
+                    if (!denied) {
+                        denied = true;
+                        denyRule = rule;
+                    }
+                    break;
+                case FORCE_ALLOW:
+                    return IntegrityCheckResult.allow(rule);
+                default:
+                    Slog.e(TAG, "Matched an unknown effect rule: " + rule);
+                    return IntegrityCheckResult.allow();
+            }
+        }
+        return denied ? IntegrityCheckResult.deny(denyRule) : IntegrityCheckResult.allow();
     }
 
     private static boolean isConjunctionOfFormulas(Formula formula) {
@@ -111,7 +90,7 @@
             return true;
         }
         OpenFormula openFormula = (OpenFormula) formula;
-        return openFormula.getConnector() == OpenFormula.Connector.AND
+        return openFormula.getConnector() == OpenFormula.AND
                 && openFormula.getFormulas().stream().allMatch(RuleEvaluator::isAtomicFormula);
     }
 
@@ -120,7 +99,7 @@
             return true;
         }
         OpenFormula openFormula = (OpenFormula) formula;
-        return openFormula.getConnector() == OpenFormula.Connector.NOT
+        return openFormula.getConnector() == OpenFormula.NOT
                 && openFormula.getFormulas().get(0) instanceof AtomicFormula;
     }
 }
diff --git a/services/core/java/com/android/server/integrity/model/AppInstallMetadata.java b/services/core/java/com/android/server/integrity/model/AppInstallMetadata.java
index 660bd2e..dfc373b 100644
--- a/services/core/java/com/android/server/integrity/model/AppInstallMetadata.java
+++ b/services/core/java/com/android/server/integrity/model/AppInstallMetadata.java
@@ -19,7 +19,11 @@
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkNotNull;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * The app install metadata.
@@ -28,7 +32,11 @@
  * to the rule evaluation engine to evaluate the metadata against the rules.
  *
  * <p>Instances of this class are immutable.
+ *
+ * @hide
  */
+@SystemApi
+@VisibleForTesting
 public final class AppInstallMetadata {
     private final String mPackageName;
     // Raw string encoding for the SHA-256 hash of the certificate of the app.
@@ -48,10 +56,12 @@
         this.mIsPreInstalled = builder.mIsPreInstalled;
     }
 
+    @NonNull
     public String getPackageName() {
         return mPackageName;
     }
 
+    @NonNull
     public String getAppCertificate() {
         return mAppCertificate;
     }
@@ -66,23 +76,17 @@
         return mInstallerCertificate;
     }
 
-    /**
-     * @see AppInstallMetadata.Builder#setVersionCode(int)
-     */
+    /** @see AppInstallMetadata.Builder#setVersionCode(int) */
     public int getVersionCode() {
         return mVersionCode;
     }
 
-    /**
-     * @see AppInstallMetadata.Builder#setIsPreInstalled(boolean)
-     */
+    /** @see AppInstallMetadata.Builder#setIsPreInstalled(boolean) */
     public boolean isPreInstalled() {
         return mIsPreInstalled;
     }
 
-    /**
-     * Builder class for constructing {@link AppInstallMetadata} objects.
-     */
+    /** Builder class for constructing {@link AppInstallMetadata} objects. */
     public static final class Builder {
         private String mPackageName;
         private String mAppCertificate;
@@ -96,7 +100,8 @@
          *
          * @see AppInstallMetadata#getPackageName()
          */
-        public Builder setPackageName(String packageName) {
+        @NonNull
+        public Builder setPackageName(@NonNull String packageName) {
             this.mPackageName = checkNotNull(packageName);
             return this;
         }
@@ -109,7 +114,8 @@
          *
          * @see AppInstallMetadata#getAppCertificate()
          */
-        public Builder setAppCertificate(String appCertificate) {
+        @NonNull
+        public Builder setAppCertificate(@NonNull String appCertificate) {
             this.mAppCertificate = checkNotNull(appCertificate);
             return this;
         }
@@ -119,7 +125,8 @@
          *
          * @see AppInstallMetadata#getInstallerName()
          */
-        public Builder setInstallerName(String installerName) {
+        @NonNull
+        public Builder setInstallerName(@NonNull String installerName) {
             this.mInstallerName = checkNotNull(installerName);
             return this;
         }
@@ -132,7 +139,8 @@
          *
          * @see AppInstallMetadata#getInstallerCertificate()
          */
-        public Builder setInstallerCertificate(String installerCertificate) {
+        @NonNull
+        public Builder setInstallerCertificate(@NonNull String installerCertificate) {
             this.mInstallerCertificate = checkNotNull(installerCertificate);
             return this;
         }
@@ -142,6 +150,7 @@
          *
          * @see AppInstallMetadata#getVersionCode()
          */
+        @NonNull
         public Builder setVersionCode(int versionCode) {
             this.mVersionCode = versionCode;
             return this;
@@ -152,6 +161,7 @@
          *
          * @see AppInstallMetadata#isPreInstalled()
          */
+        @NonNull
         public Builder setIsPreInstalled(boolean isPreInstalled) {
             this.mIsPreInstalled = isPreInstalled;
             return this;
@@ -159,7 +169,10 @@
 
         /**
          * Build {@link AppInstallMetadata}.
+         *
+         * @throws IllegalArgumentException if package name or app certificate is null
          */
+        @NonNull
         public AppInstallMetadata build() {
             checkArgument(mPackageName != null);
             checkArgument(mAppCertificate != null);
diff --git a/services/core/java/com/android/server/integrity/model/AtomicFormula.java b/services/core/java/com/android/server/integrity/model/AtomicFormula.java
index b9b46e3..a757528 100644
--- a/services/core/java/com/android/server/integrity/model/AtomicFormula.java
+++ b/services/core/java/com/android/server/integrity/model/AtomicFormula.java
@@ -17,220 +17,404 @@
 package com.android.server.integrity.model;
 
 import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
 
-import android.annotation.Nullable;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
  * Represents a simple formula consisting of an app install metadata field and a value.
  *
  * <p>Instances of this class are immutable.
+ *
+ * @hide
  */
-public final class AtomicFormula extends Formula {
+@SystemApi
+@VisibleForTesting
+public abstract class AtomicFormula implements Formula {
 
     private static final String TAG = "AtomicFormula";
 
-    public enum Key {
-        PACKAGE_NAME,
-        APP_CERTIFICATE,
-        INSTALLER_NAME,
-        INSTALLER_CERTIFICATE,
-        VERSION_CODE,
-        PRE_INSTALLED
+    @IntDef(
+            value = {
+                    PACKAGE_NAME,
+                    APP_CERTIFICATE,
+                    INSTALLER_NAME,
+                    INSTALLER_CERTIFICATE,
+                    VERSION_CODE,
+                    PRE_INSTALLED,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Key {}
+
+    @IntDef(value = {EQ, LT, LE, GT, GE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Operator {}
+
+    public static final int PACKAGE_NAME = 0;
+    public static final int APP_CERTIFICATE = 1;
+    public static final int INSTALLER_NAME = 2;
+    public static final int INSTALLER_CERTIFICATE = 3;
+    public static final int VERSION_CODE = 4;
+    public static final int PRE_INSTALLED = 5;
+
+    public static final int EQ = 0;
+    public static final int LT = 1;
+    public static final int LE = 2;
+    public static final int GT = 3;
+    public static final int GE = 4;
+
+    private final @Key int mKey;
+
+    public AtomicFormula(@Key int key) {
+        mKey = key;
     }
 
-    public enum Operator {
-        EQ,
-        LT,
-        LE,
-        GT,
-        GE
+    /** An {@link AtomicFormula} with an key and int value. */
+    public static final class IntAtomicFormula extends AtomicFormula implements Parcelable {
+        private final int mValue;
+        private final @Operator int mOperator;
+
+        /**
+         * Constructs a new {@link IntAtomicFormula}.
+         *
+         * <p>This formula will hold if and only if the corresponding information of an install
+         * specified by {@code key} is of the correct relationship to {@code value} as specified by
+         * {@code operator}.
+         *
+         * @throws IllegalArgumentException if {@code key} is not {@link #VERSION_CODE}
+         */
+        public IntAtomicFormula(@Key int key, @Operator int operator, int value) {
+            super(key);
+            checkArgument(
+                    key == VERSION_CODE,
+                    String.format("Key %s cannot be used with IntAtomicFormula", keyToString(key)));
+            mOperator = operator;
+            mValue = value;
+        }
+
+        IntAtomicFormula(Parcel in) {
+            super(in.readInt());
+            mValue = in.readInt();
+            mOperator = in.readInt();
+        }
+
+        @NonNull
+        public static final Creator<IntAtomicFormula> CREATOR =
+                new Creator<IntAtomicFormula>() {
+                    @Override
+                    public IntAtomicFormula createFromParcel(Parcel in) {
+                        return new IntAtomicFormula(in);
+                    }
+
+                    @Override
+                    public IntAtomicFormula[] newArray(int size) {
+                        return new IntAtomicFormula[size];
+                    }
+                };
+
+        @Override
+        public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) {
+            int metadataValue = getMetadataValueByKey(appInstallMetadata);
+            switch (mOperator) {
+                case EQ:
+                    return metadataValue == mValue;
+                case LE:
+                    return metadataValue <= mValue;
+                case LT:
+                    return metadataValue < mValue;
+                case GE:
+                    return metadataValue >= mValue;
+                case GT:
+                    return metadataValue > mValue;
+                default:
+                    Slog.i(TAG, String.format("Unexpected operator %d", mOperator));
+                    return false;
+            }
+        }
+
+        @Override
+        public String toString() {
+            return String.format(
+                    "(%s %s %s)", keyToString(getKey()), operatorToString(mOperator), mValue);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            IntAtomicFormula that = (IntAtomicFormula) o;
+            return getKey() == that.getKey() && mValue == that.mValue;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(getKey(), mOperator, mValue);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(getKey());
+            dest.writeInt(mValue);
+            dest.writeInt(mOperator);
+        }
+
+        private int getMetadataValueByKey(AppInstallMetadata appInstallMetadata) {
+            switch (getKey()) {
+                case VERSION_CODE:
+                    return appInstallMetadata.getVersionCode();
+                default:
+                    throw new IllegalStateException(
+                            "Unexpected key in IntAtomicFormula" + getKey());
+            }
+        }
     }
 
-    private final Key mKey;
-    private final Operator mOperator;
+    /** An {@link AtomicFormula} with a key and string value. */
+    public static final class StringAtomicFormula extends AtomicFormula implements Parcelable {
+        private final String mValue;
 
-    // The value of a key can take either 1 of 3 forms: String, Integer, or Boolean.
-    // It cannot have multiple values.
-    @Nullable
-    private final String mStringValue;
-    @Nullable
-    private final Integer mIntValue;
-    @Nullable
-    private final Boolean mBoolValue;
+        /**
+         * Constructs a new {@link StringAtomicFormula}.
+         *
+         * <p>This formula will hold if and only if the corresponding information of an install
+         * specified by {@code key} equals {@code value}.
+         *
+         * @throws IllegalArgumentException if {@code key} is not one of {@link #PACKAGE_NAME},
+         *     {@link #APP_CERTIFICATE}, {@link #INSTALLER_NAME} and {@link #INSTALLER_CERTIFICATE}
+         */
+        public StringAtomicFormula(@Key int key, @NonNull String value) {
+            super(key);
+            checkArgument(
+                    key == PACKAGE_NAME
+                            || key == APP_CERTIFICATE
+                            || key == INSTALLER_CERTIFICATE
+                            || key == INSTALLER_NAME,
+                    String.format(
+                            "Key %s cannot be used with StringAtomicFormula", keyToString(key)));
+            mValue = value;
+        }
 
-    public AtomicFormula(Key key, Operator operator, String stringValue) {
-        validateOperator(key, operator);
-        checkArgument(
-                key == Key.PACKAGE_NAME || key == Key.APP_CERTIFICATE || key == Key.INSTALLER_NAME
-                        || key == Key.INSTALLER_CERTIFICATE,
-                String.format("Key %s cannot have string value", key));
-        this.mKey = checkNotNull(key);
-        this.mOperator = checkNotNull(operator);
-        this.mStringValue = checkNotNull(stringValue);
-        this.mIntValue = null;
-        this.mBoolValue = null;
+        StringAtomicFormula(Parcel in) {
+            super(in.readInt());
+            mValue = in.readStringNoHelper();
+        }
+
+        @NonNull
+        public static final Creator<StringAtomicFormula> CREATOR =
+                new Creator<StringAtomicFormula>() {
+                    @Override
+                    public StringAtomicFormula createFromParcel(Parcel in) {
+                        return new StringAtomicFormula(in);
+                    }
+
+                    @Override
+                    public StringAtomicFormula[] newArray(int size) {
+                        return new StringAtomicFormula[size];
+                    }
+                };
+
+        @Override
+        public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) {
+            String metadataValue = getMetadataValueByKey(appInstallMetadata);
+            return metadataValue.equals(mValue);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("(%s %s %s)", keyToString(getKey()), operatorToString(EQ), mValue);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            StringAtomicFormula that = (StringAtomicFormula) o;
+            return getKey() == that.getKey() && Objects.equals(mValue, that.mValue);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(getKey(), mValue);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(getKey());
+            dest.writeStringNoHelper(mValue);
+        }
+
+        private String getMetadataValueByKey(AppInstallMetadata appInstallMetadata) {
+            switch (getKey()) {
+                case PACKAGE_NAME:
+                    return appInstallMetadata.getPackageName();
+                case APP_CERTIFICATE:
+                    return appInstallMetadata.getAppCertificate();
+                case INSTALLER_CERTIFICATE:
+                    return appInstallMetadata.getInstallerCertificate();
+                case INSTALLER_NAME:
+                    return appInstallMetadata.getInstallerName();
+                default:
+                    throw new IllegalStateException(
+                            "Unexpected key in StringAtomicFormula: " + getKey());
+            }
+        }
     }
 
-    public AtomicFormula(Key key, Operator operator, Integer intValue) {
-        validateOperator(key, operator);
-        checkArgument(key == Key.VERSION_CODE,
-                String.format("Key %s cannot have integer value", key));
-        this.mKey = checkNotNull(key);
-        this.mOperator = checkNotNull(operator);
-        this.mStringValue = null;
-        this.mIntValue = checkNotNull(intValue);
-        this.mBoolValue = null;
+    /** An {@link AtomicFormula} with a key and boolean value. */
+    public static final class BooleanAtomicFormula extends AtomicFormula implements Parcelable {
+        private final boolean mValue;
+
+        /**
+         * Constructs a new {@link BooleanAtomicFormula}.
+         *
+         * <p>This formula will hold if and only if the corresponding information of an install
+         * specified by {@code key} equals {@code value}.
+         *
+         * @throws IllegalArgumentException if {@code key} is not {@link #PRE_INSTALLED}
+         */
+        public BooleanAtomicFormula(@Key int key, boolean value) {
+            super(key);
+            checkArgument(
+                    key == PRE_INSTALLED,
+                    String.format(
+                            "Key %s cannot be used with BooleanAtomicFormula", keyToString(key)));
+            mValue = value;
+        }
+
+        BooleanAtomicFormula(Parcel in) {
+            super(in.readInt());
+            mValue = in.readByte() != 0;
+        }
+
+        @NonNull
+        public static final Creator<BooleanAtomicFormula> CREATOR =
+                new Creator<BooleanAtomicFormula>() {
+                    @Override
+                    public BooleanAtomicFormula createFromParcel(Parcel in) {
+                        return new BooleanAtomicFormula(in);
+                    }
+
+                    @Override
+                    public BooleanAtomicFormula[] newArray(int size) {
+                        return new BooleanAtomicFormula[size];
+                    }
+                };
+
+        @Override
+        public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) {
+            boolean metadataValue = getMetadataValueByKey(appInstallMetadata);
+            return metadataValue == mValue;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("(%s %s %s)", keyToString(getKey()), operatorToString(EQ), mValue);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            BooleanAtomicFormula that = (BooleanAtomicFormula) o;
+            return getKey() == that.getKey() && mValue == that.mValue;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(getKey(), mValue);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(getKey());
+            dest.writeByte((byte) (mValue ? 1 : 0));
+        }
+
+        private boolean getMetadataValueByKey(AppInstallMetadata appInstallMetadata) {
+            switch (getKey()) {
+                case PRE_INSTALLED:
+                    return appInstallMetadata.isPreInstalled();
+                default:
+                    throw new IllegalStateException(
+                            "Unexpected key in BooleanAtomicFormula: " + getKey());
+            }
+        }
     }
 
-    public AtomicFormula(Key key, Operator operator, Boolean boolValue) {
-        validateOperator(key, operator);
-        checkArgument(key == Key.PRE_INSTALLED,
-                String.format("Key %s cannot have boolean value", key));
-        this.mKey = checkNotNull(key);
-        this.mOperator = checkNotNull(operator);
-        this.mStringValue = null;
-        this.mIntValue = null;
-        this.mBoolValue = checkNotNull(boolValue);
-    }
-
-    public Key getKey() {
+    public int getKey() {
         return mKey;
     }
 
-    public Operator getOperator() {
-        return mOperator;
-    }
-
-    public String getStringValue() {
-        return mStringValue;
-    }
-
-    public Integer getIntValue() {
-        return mIntValue;
-    }
-
-    public Boolean getBoolValue() {
-        return mBoolValue;
-    }
-
-    /**
-     * Get string representation of the value of the key in the formula.
-     *
-     * @return string representation of the value of the key.
-     */
-    public String getValue() {
-        if (mStringValue != null) {
-            return mStringValue;
-        }
-        if (mIntValue != null) {
-            return mIntValue.toString();
-        }
-        return mBoolValue.toString();
-    }
-
-    /**
-     * Check if the formula is true when substituting its {@link Key} with the string value.
-     *
-     * @param value String value to substitute the key with.
-     * @return {@code true} if the formula is true, and {@code false} otherwise.
-     */
-    public boolean isMatch(String value) {
-        switch (mOperator) {
-            case EQ:
-                return mStringValue.equals(value);
-        }
-        Slog.i(TAG, String.format("Found operator %s for value %s", mOperator, mStringValue));
-        return false;
-    }
-
-    /**
-     * Check if the formula is true when substituting its {@link Key} with the integer value.
-     *
-     * @param value Integer value to substitute the key with.
-     * @return {@code true} if the formula is true, and {@code false} otherwise.
-     */
-    public boolean isMatch(int value) {
-        switch (mOperator) {
-            case EQ:
-                return mIntValue == value;
-            case LE:
-                return mIntValue <= value;
-            case LT:
-                return mIntValue < value;
-            case GE:
-                return mIntValue >= value;
-            case GT:
-                return mIntValue > value;
-        }
-        Slog.i(TAG, String.format("Found operator %s for value %s", mOperator, mIntValue));
-        return false;
-    }
-
-    /**
-     * Check if the formula is true when substituting its {@link Key} with the boolean value.
-     *
-     * @param value Boolean value to substitute the key with.
-     * @return {@code true} if the formula is true, and {@code false} otherwise.
-     */
-    public boolean isMatch(boolean value) {
-        switch (mOperator) {
-            case EQ:
-                return mBoolValue == value;
-        }
-        Slog.i(TAG, String.format("Found operator %s for value %s", mOperator, mBoolValue));
-        return false;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("%s %s %s", mKey, mOperator, getValue());
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        AtomicFormula that = (AtomicFormula) o;
-        return mKey == that.mKey
-                && mOperator == that.mOperator
-                && Objects.equals(mStringValue, that.mStringValue)
-                && Objects.equals(mIntValue, that.mIntValue)
-                && Objects.equals(mBoolValue, that.mBoolValue);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mKey, mOperator, mStringValue, mIntValue, mBoolValue);
-    }
-
-    private void validateOperator(Key key, Operator operator) {
-        boolean validOperator;
+    String keyToString(int key) {
         switch (key) {
             case PACKAGE_NAME:
+                return "PACKAGE_NAME";
             case APP_CERTIFICATE:
-            case INSTALLER_NAME:
-            case INSTALLER_CERTIFICATE:
-            case PRE_INSTALLED:
-                validOperator = (operator == Operator.EQ);
-                break;
+                return "APP_CERTIFICATE";
             case VERSION_CODE:
-                validOperator = true;
-                break;
+                return "VERSION_CODE";
+            case INSTALLER_NAME:
+                return "INSTALLER_NAME";
+            case INSTALLER_CERTIFICATE:
+                return "INSTALLER_CERTIFICATE";
+            case PRE_INSTALLED:
+                return "PRE_INSTALLED";
             default:
-                Slog.i(TAG, String.format("Found operator %s for key %s", operator, key));
-                validOperator = false;
+                throw new IllegalArgumentException("Unknown key " + key);
         }
-        if (!validOperator) {
-            throw new IllegalArgumentException(
-                    String.format("Invalid operator %s used for key %s", operator, key));
+    }
+
+    String operatorToString(int op) {
+        switch (op) {
+            case EQ:
+                return "EQ";
+            case LT:
+                return "LT";
+            case LE:
+                return "LE";
+            case GT:
+                return "GT";
+            case GE:
+                return "GE";
+            default:
+                throw new IllegalArgumentException("Unknown operator " + op);
         }
     }
 }
diff --git a/services/core/java/com/android/server/integrity/model/Formula.java b/services/core/java/com/android/server/integrity/model/Formula.java
index 9db4453..852ece5 100644
--- a/services/core/java/com/android/server/integrity/model/Formula.java
+++ b/services/core/java/com/android/server/integrity/model/Formula.java
@@ -16,9 +16,84 @@
 
 package com.android.server.integrity.model;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.integrity.model.AtomicFormula.BooleanAtomicFormula;
+import com.android.server.integrity.model.AtomicFormula.IntAtomicFormula;
+import com.android.server.integrity.model.AtomicFormula.StringAtomicFormula;
+
 /**
  * Represents a rule logic/content.
+ *
+ * @hide
  */
-public abstract class Formula {
+@SystemApi
+@VisibleForTesting
+public interface Formula {
 
+    int OPEN_FORMULA_TAG = 0;
+    int STRING_ATOMIC_FORMULA_TAG = 1;
+    int INT_ATOMIC_FORMULA_TAG = 2;
+    int BOOLEAN_ATOMIC_FORMULA_TAG = 3;
+
+    /**
+     * Returns if this formula can be satisfied by substituting the corresponding information of
+     * {@code appInstallMetadata} into the formula.
+     */
+    boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata);
+
+    /**
+     * Write a {@link Formula} to {@link android.os.Parcel}.
+     *
+     * <p>This helper method is needed because non-final class/interface are not allowed to be
+     * {@link Parcelable}.
+     *
+     * @throws IllegalArgumentException if {@link Formula} is not a recognized subclass
+     */
+    static void writeToParcel(@NonNull Formula formula, @NonNull Parcel dest, int flags) {
+        if (formula instanceof OpenFormula) {
+            dest.writeInt(OPEN_FORMULA_TAG);
+            ((OpenFormula) formula).writeToParcel(dest, flags);
+        } else if (formula instanceof StringAtomicFormula) {
+            dest.writeInt(STRING_ATOMIC_FORMULA_TAG);
+            ((StringAtomicFormula) formula).writeToParcel(dest, flags);
+        } else if (formula instanceof IntAtomicFormula) {
+            dest.writeInt(INT_ATOMIC_FORMULA_TAG);
+            ((IntAtomicFormula) formula).writeToParcel(dest, flags);
+        } else if (formula instanceof BooleanAtomicFormula) {
+            dest.writeInt(BOOLEAN_ATOMIC_FORMULA_TAG);
+            ((BooleanAtomicFormula) formula).writeToParcel(dest, flags);
+        } else {
+            throw new IllegalArgumentException("Unrecognized class " + formula.getClass());
+        }
+    }
+
+    /**
+     * Read a {@link Formula} from a {@link android.os.Parcel}.
+     *
+     * <p>We need this (hacky) helper method because non-final class/interface cannot be {@link
+     * Parcelable} (api lint error).
+     *
+     * @throws IllegalArgumentException if the parcel cannot be parsed
+     */
+    @NonNull
+    static Formula readFromParcel(@NonNull Parcel in) {
+        int tag = in.readInt();
+        switch (tag) {
+            case OPEN_FORMULA_TAG:
+                return OpenFormula.CREATOR.createFromParcel(in);
+            case STRING_ATOMIC_FORMULA_TAG:
+                return StringAtomicFormula.CREATOR.createFromParcel(in);
+            case INT_ATOMIC_FORMULA_TAG:
+                return IntAtomicFormula.CREATOR.createFromParcel(in);
+            case BOOLEAN_ATOMIC_FORMULA_TAG:
+                return BooleanAtomicFormula.CREATOR.createFromParcel(in);
+            default:
+                throw new IllegalArgumentException("Unknown formula tag " + tag);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
index 7aeb0c1..ef0751d 100644
--- a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
+++ b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
@@ -16,6 +16,8 @@
 
 package com.android.server.integrity.model;
 
+import android.annotation.Nullable;
+
 /**
  * A class encapsulating the result from the evaluation engine after evaluating rules against app
  * install metadata.
@@ -31,9 +33,9 @@
     }
 
     private final Effect mEffect;
-    private final Rule mRule;
+    @Nullable private final Rule mRule;
 
-    private IntegrityCheckResult(Effect effect, Rule rule) {
+    private IntegrityCheckResult(Effect effect, @Nullable Rule rule) {
         this.mEffect = effect;
         this.mRule = rule;
     }
@@ -49,10 +51,19 @@
     /**
      * Create an ALLOW evaluation outcome.
      *
-     * @return An evaluation outcome with ALLOW effect and empty rule.
+     * @return An evaluation outcome with ALLOW effect and no rule.
      */
     public static IntegrityCheckResult allow() {
-        return new IntegrityCheckResult(Effect.ALLOW, Rule.EMPTY);
+        return new IntegrityCheckResult(Effect.ALLOW, null);
+    }
+
+    /**
+     * Create an ALLOW evaluation outcome.
+     *
+     * @return An evaluation outcome with ALLOW effect and rule causing that effect.
+     */
+    public static IntegrityCheckResult allow(Rule rule) {
+        return new IntegrityCheckResult(Effect.ALLOW, rule);
     }
 
     /**
diff --git a/services/core/java/com/android/server/integrity/model/OpenFormula.java b/services/core/java/com/android/server/integrity/model/OpenFormula.java
index 21da629..f29706a 100644
--- a/services/core/java/com/android/server/integrity/model/OpenFormula.java
+++ b/services/core/java/com/android/server/integrity/model/OpenFormula.java
@@ -17,8 +17,19 @@
 package com.android.server.integrity.model;
 
 import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -27,40 +38,108 @@
  * Represents a complex formula consisting of other simple and complex formulas.
  *
  * <p>Instances of this class are immutable.
+ *
+ * @hide
  */
-public final class OpenFormula extends Formula {
+@SystemApi
+@VisibleForTesting
+public final class OpenFormula implements Formula, Parcelable {
+    private static final String TAG = "OpenFormula";
 
-    public enum Connector {
-        AND,
-        OR,
-        NOT
-    }
+    @IntDef(
+            value = {
+                    AND, OR, NOT,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Connector {}
 
-    private final Connector mConnector;
+    /** Boolean AND operator. */
+    public static final int AND = 0;
+
+    /** Boolean OR operator. */
+    public static final int OR = 1;
+
+    /** Boolean NOT operator. */
+    public static final int NOT = 2;
+
+    private final @Connector int mConnector;
     private final List<Formula> mFormulas;
 
-    public OpenFormula(Connector connector, List<Formula> formulas) {
+    @NonNull
+    public static final Creator<OpenFormula> CREATOR =
+            new Creator<OpenFormula>() {
+                @Override
+                public OpenFormula createFromParcel(Parcel in) {
+                    return new OpenFormula(in);
+                }
+
+                @Override
+                public OpenFormula[] newArray(int size) {
+                    return new OpenFormula[size];
+                }
+            };
+
+    /**
+     * Create a new formula from operator and operands.
+     *
+     * @throws IllegalArgumentException if the number of operands is not matching the requirements
+     *     for that operator (at least 2 for {@link #AND} and {@link #OR}, 1 for {@link #NOT}).
+     */
+    public OpenFormula(@Connector int connector, @NonNull List<Formula> formulas) {
         validateFormulas(connector, formulas);
-        this.mConnector = checkNotNull(connector);
-        this.mFormulas = Collections.unmodifiableList(checkNotNull(formulas));
+        this.mConnector = connector;
+        this.mFormulas = Collections.unmodifiableList(formulas);
     }
 
-    public Connector getConnector() {
+    OpenFormula(Parcel in) {
+        mConnector = in.readInt();
+        int length = in.readInt();
+        checkArgument(length >= 0, "Must have non-negative length. Got " + length);
+        mFormulas = new ArrayList<>(length);
+        for (int i = 0; i < length; i++) {
+            mFormulas.add(Formula.readFromParcel(in));
+        }
+    }
+
+    public @Connector int getConnector() {
         return mConnector;
     }
 
+    @NonNull
     public List<Formula> getFormulas() {
         return mFormulas;
     }
 
     @Override
+    public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) {
+        switch (mConnector) {
+            case NOT:
+                return !mFormulas.get(0).isSatisfied(appInstallMetadata);
+            case AND:
+                return mFormulas.stream()
+                        .allMatch(formula -> formula.isSatisfied(appInstallMetadata));
+            case OR:
+                return mFormulas.stream()
+                        .anyMatch(formula -> formula.isSatisfied(appInstallMetadata));
+            default:
+                Slog.i(TAG, "Unknown connector " + mConnector);
+                return false;
+        }
+    }
+
+    @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < mFormulas.size(); i++) {
-            if (i > 0) {
-                sb.append(String.format(" %s ", mConnector));
+        if (mFormulas.size() == 1) {
+            sb.append(String.format("%s ", connectorToString(mConnector)));
+            sb.append(mFormulas.get(0).toString());
+        } else {
+            for (int i = 0; i < mFormulas.size(); i++) {
+                if (i > 0) {
+                    sb.append(String.format(" %s ", connectorToString(mConnector)));
+                }
+                sb.append(mFormulas.get(i).toString());
             }
-            sb.append(mFormulas.get(i).toString());
         }
         return sb.toString();
     }
@@ -74,8 +153,7 @@
             return false;
         }
         OpenFormula that = (OpenFormula) o;
-        return mConnector == that.mConnector
-                && mFormulas.equals(that.mFormulas);
+        return mConnector == that.mConnector && mFormulas.equals(that.mFormulas);
     }
 
     @Override
@@ -83,17 +161,50 @@
         return Objects.hash(mConnector, mFormulas);
     }
 
-    private void validateFormulas(Connector connector, List<Formula> formulas) {
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mConnector);
+        dest.writeInt(mFormulas.size());
+        for (Formula formula : mFormulas) {
+            Formula.writeToParcel(formula, dest, flags);
+        }
+    }
+
+    private void validateFormulas(@Connector int connector, List<Formula> formulas) {
         switch (connector) {
             case AND:
             case OR:
-                checkArgument(formulas.size() >= 2,
-                        String.format("Connector %s must have at least 2 formulas", connector));
+                checkArgument(
+                        formulas.size() >= 2,
+                        String.format(
+                                "Connector %s must have at least 2 formulas",
+                                connectorToString(connector)));
                 break;
             case NOT:
-                checkArgument(formulas.size() == 1,
-                        String.format("Connector %s must have 1 formula only", connector));
+                checkArgument(
+                        formulas.size() == 1,
+                        String.format(
+                                "Connector %s must have 1 formula only",
+                                connectorToString(connector)));
                 break;
         }
     }
+
+    private String connectorToString(int connector) {
+        switch (connector) {
+            case AND:
+                return "AND";
+            case OR:
+                return "OR";
+            case NOT:
+                return "NOT";
+            default:
+                throw new IllegalArgumentException("Unknown connector " + connector);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/integrity/model/Rule.java b/services/core/java/com/android/server/integrity/model/Rule.java
index 63b9b91..14dcb26 100644
--- a/services/core/java/com/android/server/integrity/model/Rule.java
+++ b/services/core/java/com/android/server/integrity/model/Rule.java
@@ -18,55 +18,96 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
  * Represent rules to be used in the rule evaluation engine to match against app installs.
  *
  * <p>Instances of this class are immutable.
+ *
+ * @hide
  */
-public final class Rule {
+@SystemApi
+@VisibleForTesting
+public final class Rule implements Parcelable {
 
-    public enum Effect {
-        DENY
-    }
+    @IntDef(
+            value = {
+                DENY,
+                FORCE_ALLOW,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Effect {}
 
-    // Holds an empty rule instance.
-    public static final Rule EMPTY = new Rule();
-
-    private final Formula mFormula;
-    private final Effect mEffect;
-
-    private Rule() {
-        this.mFormula = null;
-        this.mEffect = null;
-    }
-
-    public Rule(Formula formula, Effect effect) {
-        this.mFormula = checkNotNull(formula);
-        this.mEffect = checkNotNull(effect);
-    }
+    /** If this rule matches the install, the install should be denied. */
+    public static final int DENY = 0;
 
     /**
-     * Indicates whether the rule is empty or not.
-     *
-     * @return {@code true} if the rule is empty, and {@code false} otherwise.
+     * If this rule matches the install, the install will be allowed regardless of other matched
+     * rules.
      */
-    public boolean isEmpty() {
-        return mFormula == null && mEffect == null;
+    public static final int FORCE_ALLOW = 1;
+
+    private final Formula mFormula;
+    private final @Effect int mEffect;
+
+    public Rule(@NonNull Formula formula, @Effect int effect) {
+        this.mFormula = checkNotNull(formula);
+        this.mEffect = effect;
     }
 
+    Rule(Parcel in) {
+        mFormula = Formula.readFromParcel(in);
+        mEffect = in.readInt();
+    }
+
+    @NonNull
+    public static final Creator<Rule> CREATOR =
+            new Creator<Rule>() {
+                @Override
+                public Rule createFromParcel(Parcel in) {
+                    return new Rule(in);
+                }
+
+                @Override
+                public Rule[] newArray(int size) {
+                    return new Rule[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        Formula.writeToParcel(mFormula, dest, flags);
+        dest.writeInt(mEffect);
+    }
+
+    @NonNull
     public Formula getFormula() {
         return mFormula;
     }
 
-    public Effect getEffect() {
+    public @Effect int getEffect() {
         return mEffect;
     }
 
     @Override
     public String toString() {
-        return String.format("Rule: %s, %s", mFormula, mEffect);
+        return String.format("Rule: %s, %s", mFormula, effectToString(mEffect));
     }
 
     @Override
@@ -78,12 +119,22 @@
             return false;
         }
         Rule that = (Rule) o;
-        return Objects.equals(mFormula, that.mFormula)
-                && Objects.equals(mEffect, that.mEffect);
+        return Objects.equals(mFormula, that.mFormula) && mEffect == that.mEffect;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mFormula, mEffect);
     }
+
+    private String effectToString(int effect) {
+        switch (effect) {
+            case DENY:
+                return "DENY";
+            case FORCE_ALLOW:
+                return "FORCE_ALLOW";
+            default:
+                throw new IllegalArgumentException("Unknown effect " + effect);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
index c1567bc..5ed282c 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
@@ -19,18 +19,19 @@
 import com.android.server.integrity.model.Rule;
 
 import java.io.InputStream;
+import java.util.List;
 
 /** A helper class to parse rules into the {@link Rule} model from Binary representation. */
 public class RuleBinaryParser implements RuleParser {
 
     @Override
-    public Rule parse(String ruleText) {
+    public List<Rule> parse(String ruleText) {
         // TODO: Implement binary text parser.
         return null;
     }
 
     @Override
-    public Rule parse(InputStream inputStream) {
+    public List<Rule> parse(InputStream inputStream) {
         // TODO: Implement stream parser.
         return null;
     }
diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java
index 96ed5993..bfffc70 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleParser.java
@@ -19,13 +19,14 @@
 import com.android.server.integrity.model.Rule;
 
 import java.io.InputStream;
+import java.util.List;
 
 /** A helper class to parse rules into the {@link Rule} model. */
 public interface RuleParser {
 
     /** Parse rules from a string. */
-    Rule parse(String ruleText);
+    List<Rule> parse(String ruleText);
 
     /** Parse rules from an input stream. */
-    Rule parse(InputStream inputStream);
+    List<Rule> parse(InputStream inputStream);
 }
diff --git a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
index 8b1bec9..bf31bb2 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
@@ -16,22 +16,90 @@
 
 package com.android.server.integrity.parser;
 
+import android.util.Slog;
+import android.util.Xml;
+
 import com.android.server.integrity.model.Rule;
 
-import java.io.InputStream;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
-/** A helper class to parse rules into the {@link Rule} model from Xml representation. */
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class to parse rules into the {@link Rule} model from Xml representation.
+ */
 public final class RuleXmlParser implements RuleParser {
 
+    public static final String TAG = "RuleXmlParser";
+
+    private static final String RULE_LIST_TAG = "RuleList";
+    private static final String RULE_TAG = "Rule";
+
     @Override
-    public Rule parse(String ruleText) {
-        // TODO: Implement text parser.
+    public List<Rule> parse(String ruleText) {
+        try {
+            XmlPullParser xmlPullParser = Xml.newPullParser();
+            xmlPullParser.setInput(new StringReader(ruleText));
+            return parseRules(xmlPullParser);
+        } catch (XmlPullParserException | IOException e) {
+            Slog.e(TAG, String.format("Unable to read rules from string: %s", ruleText), e);
+        }
         return null;
     }
 
     @Override
-    public Rule parse(InputStream inputStream) {
-        // TODO: Implement stream parser.
+    public List<Rule> parse(InputStream inputStream) {
+        try {
+            XmlPullParser xmlPullParser = Xml.newPullParser();
+            xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name());
+            return parseRules(xmlPullParser);
+        } catch (XmlPullParserException | IOException e) {
+            Slog.e(TAG, "Unable to read rules from stream", e);
+        }
+        return null;
+    }
+
+    private List<Rule> parseRules(XmlPullParser parser) throws IOException, XmlPullParserException {
+        List<Rule> rules = new ArrayList<>();
+
+        // Skipping the first event type, which is always {@link XmlPullParser.START_DOCUMENT}
+        parser.next();
+
+        // Processing the first tag; which should always be a <RuleList> tag.
+        String nodeName = parser.getName();
+        // Validating that the XML is starting with a <RuleList> tag.
+        // Note: This is the only breaking validation to run against XML files in the platform.
+        // All rules inside are assumed to be validated at the server. If a rule is found to be
+        // corrupt in the XML, it will be skipped to the next rule.
+        if (!nodeName.equals(RULE_LIST_TAG)) {
+            throw new RuntimeException(
+                    String.format("Rules must start with <RuleList> tag. Found: %s at %s", nodeName,
+                            parser.getPositionDescription()));
+        }
+
+        int eventType;
+        while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            nodeName = parser.getName();
+            if (eventType != XmlPullParser.START_TAG || !nodeName.equals(RULE_TAG)) {
+                continue;
+            }
+            Rule parsedRule = parseRule(parser);
+            if (parsedRule != null) {
+                rules.add(parsedRule);
+            }
+        }
+
+        return rules;
+    }
+
+    private Rule parseRule(XmlPullParser parser) {
+        // TODO: Implement rule parser.
         return null;
     }
 }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 887dbb3..0a3c581 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -22,6 +22,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
@@ -125,7 +126,7 @@
 
     // pkg|uid => PackagePreferences
     private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>();
-    // pkg => PackagePreferences
+    // pkg|userId => PackagePreferences
     private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
 
     private final Context mContext;
@@ -172,9 +173,6 @@
         String tag = parser.getName();
         if (!TAG_RANKING.equals(tag)) return;
         synchronized (mPackagePreferences) {
-            // Clobber groups and channels with the xml, but don't delete other data that wasn't
-            // present at the time of serialization.
-            mRestoredWithoutUids.clear();
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                 tag = parser.getName();
                 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
@@ -200,7 +198,8 @@
                             }
                             boolean skipWarningLogged = false;
 
-                            PackagePreferences r = getOrCreatePackagePreferencesLocked(name, uid,
+                            PackagePreferences r = getOrCreatePackagePreferencesLocked(
+                                    name, userId, uid,
                                     XmlUtils.readIntAttribute(
                                             parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
                                     XmlUtils.readIntAttribute(parser, ATT_PRIORITY,
@@ -311,17 +310,27 @@
         return mPackagePreferences.get(key);
     }
 
-    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, int uid) {
-        return getOrCreatePackagePreferencesLocked(pkg, uid,
+    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
+            int uid) {
+        return getOrCreatePackagePreferencesLocked(pkg, UserHandle.getUserId(uid), uid,
                 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
                 DEFAULT_ALLOW_BUBBLE);
     }
 
-    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, int uid,
-            int importance, int priority, int visibility, boolean showBadge, boolean allowBubble) {
+    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
+            @UserIdInt int userId, int uid) {
+        return getOrCreatePackagePreferencesLocked(pkg, userId, uid,
+                DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
+                DEFAULT_ALLOW_BUBBLE);
+    }
+
+    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
+            @UserIdInt int userId, int uid, int importance, int priority, int visibility,
+            boolean showBadge, boolean allowBubble) {
         final String key = packagePreferencesKey(pkg, uid);
         PackagePreferences
-                r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
+                r = (uid == UNKNOWN_UID)
+                ? mRestoredWithoutUids.get(unrestoredPackageKey(pkg, userId))
                 : mPackagePreferences.get(key);
         if (r == null) {
             r = new PackagePreferences();
@@ -340,7 +349,7 @@
             }
 
             if (r.uid == UNKNOWN_UID) {
-                mRestoredWithoutUids.put(pkg, r);
+                mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r);
             } else {
                 mPackagePreferences.put(key, r);
             }
@@ -382,6 +391,10 @@
 
     private boolean createDefaultChannelIfNeededLocked(PackagePreferences r) throws
             PackageManager.NameNotFoundException {
+        if (r.uid == UNKNOWN_UID) {
+            return false;
+        }
+
         if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
             r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
                     com.android.internal.R.string.default_notification_channel_label));
@@ -1769,17 +1782,18 @@
                 synchronized (mPackagePreferences) {
                     mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
                 }
-                mRestoredWithoutUids.remove(pkg);
+                mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
                 updated = true;
             }
         } else {
             for (String pkg : pkgList) {
                 // Package install
-                final PackagePreferences r = mRestoredWithoutUids.get(pkg);
+                final PackagePreferences r =
+                        mRestoredWithoutUids.get(unrestoredPackageKey(pkg, changeUserId));
                 if (r != null) {
                     try {
                         r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
-                        mRestoredWithoutUids.remove(pkg);
+                        mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
                         synchronized (mPackagePreferences) {
                             mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
                         }
@@ -1910,6 +1924,10 @@
         return pkg + "|" + uid;
     }
 
+    private static String unrestoredPackageKey(String pkg, @UserIdInt int userId) {
+        return pkg + "|" + userId;
+    }
+
     private static class PackagePreferences {
         String pkg;
         int uid = UNKNOWN_UID;
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 157bd3b..c19c96f 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING;
 import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
@@ -558,8 +559,6 @@
         private SurfaceAnimator mEnterBlackFrameAnimator;
         private SurfaceAnimator mScreenshotRotationAnimator;
         private SurfaceAnimator mRotateScreenAnimator;
-        private final Runnable mNoopCallback = () -> { // b/141177184
-        };
 
         /**
          * Start the rotation animation of the display and the screenshot on the
@@ -592,7 +591,7 @@
                             .setHeight(mDisplayContent.getSurfaceHeight())
                             .build(),
                     createWindowAnimationSpec(mRotateEnterAnimation),
-                    this::cancel);
+                    this::onAnimationEnd);
         }
 
         private SurfaceAnimator startScreenshotAlphaAnimation() {
@@ -603,7 +602,7 @@
                             .setHeight(mHeight)
                             .build(),
                     createWindowAnimationSpec(mRotateAlphaAnimation),
-                    mNoopCallback);
+                    this::onAnimationEnd);
         }
 
         private SurfaceAnimator startEnterBlackFrameAnimation() {
@@ -612,7 +611,7 @@
                             .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
                             .build(),
                     createWindowAnimationSpec(mRotateEnterAnimation),
-                    mNoopCallback);
+                    this::onAnimationEnd);
         }
 
         private SurfaceAnimator startScreenshotRotationAnimation() {
@@ -654,18 +653,38 @@
         }
 
         private void onAnimationEnd() {
-            mEnterBlackFrameAnimator = null;
-            mScreenshotRotationAnimator = null;
-            mRotateScreenAnimator = null;
-            mService.mAnimator.mBulkUpdateParams |= WindowSurfacePlacer.SET_UPDATE_ROTATION;
-            kill();
-            mService.updateRotation(false, false);
-            AccessibilityController accessibilityController = mService.mAccessibilityController;
+            synchronized (mService.mGlobalLock) {
+                if (isAnimating()) {
+                    ProtoLog.v(WM_DEBUG_ORIENTATION,
+                            "ScreenRotation sill animating: mDisplayAnimator: %s\n"
+                                    + "mEnterBlackFrameAnimator: "
+                                    + "%s\nmRotateScreenAnimator: %s\n"
+                                    + "mScreenshotRotationAnimator: %s",
+                            mDisplayAnimator != null
+                                    ? mDisplayAnimator.isAnimating() : null,
+                            mEnterBlackFrameAnimator != null
+                                    ? mEnterBlackFrameAnimator.isAnimating() : null,
+                            mRotateScreenAnimator != null
+                                    ? mRotateScreenAnimator.isAnimating() : null,
+                            mScreenshotRotationAnimator != null
+                                    ? mScreenshotRotationAnimator.isAnimating() : null
+                    );
+                    return;
+                }
+                ProtoLog.d(WM_DEBUG_ORIENTATION, "ScreenRotationAnimation onAnimationEnd");
+                mEnterBlackFrameAnimator = null;
+                mScreenshotRotationAnimator = null;
+                mRotateScreenAnimator = null;
+                mService.mAnimator.mBulkUpdateParams |= WindowSurfacePlacer.SET_UPDATE_ROTATION;
+                kill();
+                mService.updateRotation(false, false);
+                AccessibilityController accessibilityController = mService.mAccessibilityController;
 
-            if (accessibilityController != null) {
-                // We just finished rotation animation which means we did not
-                // announce the rotation and waited for it to end, announce now.
-                accessibilityController.onRotationChangedLocked(mDisplayContent);
+                if (accessibilityController != null) {
+                    // We just finished rotation animation which means we did not
+                    // announce the rotation and waited for it to end, announce now.
+                    accessibilityController.onRotationChangedLocked(mDisplayContent);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationThread.java b/services/core/java/com/android/server/wm/SurfaceAnimationThread.java
index 0d3afc0..1259ee9 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationThread.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationThread.java
@@ -21,6 +21,7 @@
 import android.os.Handler;
 import android.os.Trace;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.ServiceThread;
 
 /**
@@ -56,4 +57,20 @@
             return sHandler;
         }
     }
+
+    /**
+     * Disposes current surface animation thread if it's initialized. Should only be used in tests
+     * to set up a new environment.
+     */
+    @VisibleForTesting
+    public static void dispose() {
+        synchronized (SurfaceAnimationThread.class) {
+            if (sInstance == null) {
+                return;
+            }
+
+            getHandler().runWithScissors(() -> sInstance.quit(), 0 /* timeout */);
+            sInstance = null;
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
index baf1ed0..e52aca3 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
@@ -16,11 +16,15 @@
 
 package com.android.server.integrity.engine;
 
+import static com.android.server.integrity.model.IntegrityCheckResult.Effect.ALLOW;
+import static com.android.server.integrity.model.IntegrityCheckResult.Effect.DENY;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 
 import com.android.server.integrity.model.AppInstallMetadata;
 import com.android.server.integrity.model.AtomicFormula;
+import com.android.server.integrity.model.AtomicFormula.StringAtomicFormula;
+import com.android.server.integrity.model.IntegrityCheckResult;
 import com.android.server.integrity.model.OpenFormula;
 import com.android.server.integrity.model.Rule;
 
@@ -43,141 +47,176 @@
             new AppInstallMetadata.Builder()
                     .setPackageName(PACKAGE_NAME_1)
                     .setAppCertificate(APP_CERTIFICATE)
+                    .setVersionCode(2)
                     .build();
 
     @Test
-    public void testMatchRules_emptyRules() {
+    public void testEvaluateRules_noRules_allow() {
         List<Rule> rules = new ArrayList<>();
 
-        Rule matchedRule = RuleEvaluator.evaluateRules(rules, APP_INSTALL_METADATA);
+        IntegrityCheckResult result = RuleEvaluator.evaluateRules(rules, APP_INSTALL_METADATA);
 
-        assertEquals(Rule.EMPTY, matchedRule);
+        assertEquals(ALLOW, result.getEffect());
     }
 
     @Test
-    public void testMatchRules_emptyMatch() {
-        Rule rule1 = new Rule(
-                new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
-                        PACKAGE_NAME_2), Rule.Effect.DENY);
+    public void testEvaluateRules_noMatchedRules_allow() {
+        Rule rule1 =
+                new Rule(
+                        new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_2),
+                        Rule.DENY);
 
-        Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule1),
-                APP_INSTALL_METADATA);
+        IntegrityCheckResult result =
+                RuleEvaluator.evaluateRules(Collections.singletonList(rule1), APP_INSTALL_METADATA);
 
-        assertEquals(Rule.EMPTY, matchedRule);
-    }
-
-
-    @Test
-    public void testMatchRules_oneMatch() {
-        Rule rule1 = new Rule(
-                new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
-                        PACKAGE_NAME_1), Rule.Effect.DENY);
-        Rule rule2 = new Rule(
-                new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
-                        PACKAGE_NAME_2), Rule.Effect.DENY);
-
-        Rule matchedRule = RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2),
-                APP_INSTALL_METADATA);
-
-        assertEquals(rule1, matchedRule);
+        assertEquals(ALLOW, result.getEffect());
     }
 
     @Test
-    public void testMatchRules_multipleMatches() {
-        Rule rule1 = new Rule(
-                new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
-                        PACKAGE_NAME_1), Rule.Effect.DENY);
-        OpenFormula openFormula2 = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList(
-                new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
-                        PACKAGE_NAME_1),
-                new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
-                        AtomicFormula.Operator.EQ,
-                        APP_CERTIFICATE)));
-        Rule rule2 = new Rule(
-                openFormula2, Rule.Effect.DENY);
+    public void testEvaluateRules_oneMatch_deny() {
+        Rule rule1 =
+                new Rule(
+                        new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1),
+                        Rule.DENY);
+        Rule rule2 =
+                new Rule(
+                        new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_2),
+                        Rule.DENY);
 
-        Rule matchedRule = RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2),
-                APP_INSTALL_METADATA);
+        IntegrityCheckResult result =
+                RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2), APP_INSTALL_METADATA);
 
-        assertNotEquals(Rule.EMPTY, matchedRule);
+        assertEquals(DENY, result.getEffect());
+        assertEquals(rule1, result.getRule());
     }
 
     @Test
-    public void testMatchRules_ruleWithNot() {
-        OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.NOT,
-                Collections.singletonList(
-                        new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
-                                PACKAGE_NAME_2)));
-        Rule rule = new Rule(openFormula, Rule.Effect.DENY);
+    public void testEvaluateRules_multipleMatches_deny() {
+        Rule rule1 =
+                new Rule(
+                        new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1),
+                        Rule.DENY);
+        OpenFormula openFormula2 =
+                new OpenFormula(
+                        OpenFormula.AND,
+                        Arrays.asList(
+                                new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1),
+                                new StringAtomicFormula(
+                                        AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE)));
+        Rule rule2 = new Rule(openFormula2, Rule.DENY);
 
-        Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
-                APP_INSTALL_METADATA);
+        IntegrityCheckResult result =
+                RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2), APP_INSTALL_METADATA);
 
-        assertEquals(rule, matchedRule);
+        assertEquals(DENY, result.getEffect());
+        assertEquals(rule1, result.getRule());
     }
 
     @Test
-    public void testMatchRules_ruleWithIntegerOperators() {
-        Rule rule1 = new Rule(
-                new AtomicFormula(AtomicFormula.Key.VERSION_CODE, AtomicFormula.Operator.GT,
-                        1), Rule.Effect.DENY);
+    public void testEvaluateRules_ruleWithNot_deny() {
+        OpenFormula openFormula =
+                new OpenFormula(
+                        OpenFormula.NOT,
+                        Collections.singletonList(
+                                new StringAtomicFormula(
+                                        AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_2)));
+        Rule rule = new Rule(openFormula, Rule.DENY);
 
-        Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule1),
-                APP_INSTALL_METADATA);
+        IntegrityCheckResult result =
+                RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
 
-        assertEquals(rule1, matchedRule);
+        assertEquals(DENY, result.getEffect());
+        assertEquals(rule, result.getRule());
     }
 
     @Test
-    public void testMatchRules_validForm() {
-        OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList(
-                new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
-                        PACKAGE_NAME_1),
-                new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
-                        AtomicFormula.Operator.EQ,
-                        APP_CERTIFICATE)));
-        Rule rule = new Rule(
-                openFormula, Rule.Effect.DENY);
+    public void testEvaluateRules_ruleWithIntegerOperators_deny() {
+        Rule rule =
+                new Rule(
+                        new AtomicFormula.IntAtomicFormula(
+                                AtomicFormula.VERSION_CODE, AtomicFormula.GT, 1),
+                        Rule.DENY);
 
-        Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
-                APP_INSTALL_METADATA);
+        IntegrityCheckResult result =
+                RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
 
-        assertEquals(rule, matchedRule);
+        assertEquals(DENY, result.getEffect());
+        assertEquals(rule, result.getRule());
     }
 
     @Test
-    public void testMatchRules_ruleNotInDNF() {
-        OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.OR, Arrays.asList(
-                new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
-                        PACKAGE_NAME_1),
-                new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
-                        AtomicFormula.Operator.EQ,
-                        APP_CERTIFICATE)));
-        Rule rule = new Rule(
-                openFormula, Rule.Effect.DENY);
+    public void testEvaluateRules_validForm_deny() {
+        OpenFormula openFormula =
+                new OpenFormula(
+                        OpenFormula.AND,
+                        Arrays.asList(
+                                new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1),
+                                new StringAtomicFormula(
+                                        AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE)));
+        Rule rule = new Rule(openFormula, Rule.DENY);
 
-        Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
-                APP_INSTALL_METADATA);
+        IntegrityCheckResult result =
+                RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
 
-        assertEquals(Rule.EMPTY, matchedRule);
+        assertEquals(DENY, result.getEffect());
+        assertEquals(rule, result.getRule());
     }
 
     @Test
-    public void testMatchRules_openFormulaWithNot() {
-        OpenFormula openSubFormula = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList(
-                new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
-                        PACKAGE_NAME_2),
-                new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
-                        AtomicFormula.Operator.EQ,
-                        APP_CERTIFICATE)));
-        OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.NOT,
-                Collections.singletonList(openSubFormula));
-        Rule rule = new Rule(
-                openFormula, Rule.Effect.DENY);
+    public void testEvaluateRules_ruleNotInDNF_ignoreAndAllow() {
+        OpenFormula openFormula =
+                new OpenFormula(
+                        OpenFormula.OR,
+                        Arrays.asList(
+                                new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1),
+                                new StringAtomicFormula(
+                                        AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE)));
+        Rule rule = new Rule(openFormula, Rule.DENY);
 
-        Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
-                APP_INSTALL_METADATA);
+        IntegrityCheckResult result =
+                RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
 
-        assertEquals(Rule.EMPTY, matchedRule);
+        assertEquals(ALLOW, result.getEffect());
+    }
+
+    @Test
+    public void testEvaluateRules_openFormulaWithNot_allow() {
+        OpenFormula openSubFormula =
+                new OpenFormula(
+                        OpenFormula.AND,
+                        Arrays.asList(
+                                new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_2),
+                                new StringAtomicFormula(
+                                        AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE)));
+        OpenFormula openFormula =
+                new OpenFormula(OpenFormula.NOT, Collections.singletonList(openSubFormula));
+        Rule rule = new Rule(openFormula, Rule.DENY);
+
+        IntegrityCheckResult result =
+                RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
+
+        assertEquals(ALLOW, result.getEffect());
+    }
+
+    @Test
+    public void testEvaluateRules_forceAllow() {
+        Rule rule1 =
+                new Rule(
+                        new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1),
+                        Rule.FORCE_ALLOW);
+        OpenFormula openFormula2 =
+                new OpenFormula(
+                        OpenFormula.AND,
+                        Arrays.asList(
+                                new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1),
+                                new StringAtomicFormula(
+                                        AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE)));
+        Rule rule2 = new Rule(openFormula2, Rule.DENY);
+
+        IntegrityCheckResult result =
+                RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2), APP_INSTALL_METADATA);
+
+        assertEquals(ALLOW, result.getEffect());
+        assertEquals(rule1, result.getRule());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/AtomicFormulaTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/AtomicFormulaTest.java
index 1cb2fb3..c8c5eca 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/model/AtomicFormulaTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/AtomicFormulaTest.java
@@ -19,6 +19,14 @@
 import static com.android.server.testutils.TestUtils.assertExpectException;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import com.android.server.integrity.model.AtomicFormula.BooleanAtomicFormula;
+import com.android.server.integrity.model.AtomicFormula.IntAtomicFormula;
+import com.android.server.integrity.model.AtomicFormula.StringAtomicFormula;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,32 +37,26 @@
 
     @Test
     public void testValidAtomicFormula_stringValue() {
-        AtomicFormula atomicFormula = new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME,
-                AtomicFormula.Operator.EQ, "com.test.app");
+        StringAtomicFormula stringAtomicFormula =
+                new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "com.test.app");
 
-        assertEquals(AtomicFormula.Key.PACKAGE_NAME, atomicFormula.getKey());
-        assertEquals(AtomicFormula.Operator.EQ, atomicFormula.getOperator());
-        assertEquals("com.test.app", atomicFormula.getStringValue());
+        assertEquals(AtomicFormula.PACKAGE_NAME, stringAtomicFormula.getKey());
     }
 
     @Test
     public void testValidAtomicFormula_intValue() {
-        AtomicFormula atomicFormula = new AtomicFormula(AtomicFormula.Key.VERSION_CODE,
-                AtomicFormula.Operator.LE, 1);
+        IntAtomicFormula intAtomicFormula =
+                new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, 1);
 
-        assertEquals(AtomicFormula.Key.VERSION_CODE, atomicFormula.getKey());
-        assertEquals(AtomicFormula.Operator.LE, atomicFormula.getOperator());
-        assertEquals(1, atomicFormula.getIntValue().intValue());
+        assertEquals(AtomicFormula.VERSION_CODE, intAtomicFormula.getKey());
     }
 
     @Test
     public void testValidAtomicFormula_boolValue() {
-        AtomicFormula atomicFormula = new AtomicFormula(AtomicFormula.Key.PRE_INSTALLED,
-                AtomicFormula.Operator.EQ, true);
+        BooleanAtomicFormula atomicFormula =
+                new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true);
 
-        assertEquals(AtomicFormula.Key.PRE_INSTALLED, atomicFormula.getKey());
-        assertEquals(AtomicFormula.Operator.EQ, atomicFormula.getOperator());
-        assertEquals(true, atomicFormula.getBoolValue());
+        assertEquals(AtomicFormula.PRE_INSTALLED, atomicFormula.getKey());
     }
 
     @Test
@@ -62,9 +64,9 @@
         assertExpectException(
                 IllegalArgumentException.class,
                 /* expectedExceptionMessageRegex */
-                String.format("Key %s cannot have string value", AtomicFormula.Key.VERSION_CODE),
-                () -> new AtomicFormula(AtomicFormula.Key.VERSION_CODE, AtomicFormula.Operator.EQ,
-                        "test-value"));
+                String.format(
+                        "Key VERSION_CODE cannot be used with StringAtomicFormula"),
+                () -> new StringAtomicFormula(AtomicFormula.VERSION_CODE, "test-value"));
     }
 
     @Test
@@ -72,9 +74,9 @@
         assertExpectException(
                 IllegalArgumentException.class,
                 /* expectedExceptionMessageRegex */
-                String.format("Key %s cannot have integer value", AtomicFormula.Key.PACKAGE_NAME),
-                () -> new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
-                        1));
+                String.format(
+                        "Key PACKAGE_NAME cannot be used with IntAtomicFormula"),
+                () -> new IntAtomicFormula(AtomicFormula.PACKAGE_NAME, AtomicFormula.EQ, 1));
     }
 
     @Test
@@ -82,19 +84,193 @@
         assertExpectException(
                 IllegalArgumentException.class,
                 /* expectedExceptionMessageRegex */
-                String.format("Key %s cannot have boolean value", AtomicFormula.Key.PACKAGE_NAME),
-                () -> new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
-                        true));
+                String.format(
+                        "Key PACKAGE_NAME cannot be used with BooleanAtomicFormula"),
+                () -> new BooleanAtomicFormula(AtomicFormula.PACKAGE_NAME, true));
     }
 
     @Test
-    public void testValidateOperator_invalidKeyOperatorPair() {
-        assertExpectException(
-                IllegalArgumentException.class,
-                /* expectedExceptionMessageRegex */
-                String.format("Invalid operator %s used for key %s",
-                        AtomicFormula.Operator.LE, AtomicFormula.Key.PACKAGE_NAME),
-                () -> new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.LE,
-                        "test-value"));
+    public void testIsSatisfiable_string_true() {
+        StringAtomicFormula stringAtomicFormula =
+                new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "com.test.app");
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setPackageName("com.test.app").build();
+
+        assertTrue(stringAtomicFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_string_false() {
+        StringAtomicFormula stringAtomicFormula =
+                new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "com.test.app");
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setPackageName("com.foo.bar").build();
+
+        assertFalse(stringAtomicFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_int_eq_true() {
+        IntAtomicFormula intAtomicFormula =
+                new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 0);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setVersionCode(0).build();
+
+        assertTrue(intAtomicFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_int_eq_false() {
+        IntAtomicFormula intAtomicFormula =
+                new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 0);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setVersionCode(1).build();
+
+        assertFalse(intAtomicFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_int_gt_true() {
+        IntAtomicFormula intAtomicFormula =
+                new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, 0);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setVersionCode(1).build();
+
+        assertTrue(intAtomicFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_int_gt_false() {
+        IntAtomicFormula intAtomicFormula =
+                new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, 1);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setVersionCode(0).build();
+
+        assertFalse(intAtomicFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_int_ge_true() {
+        IntAtomicFormula intAtomicFormula =
+                new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GE, 0);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setVersionCode(1).build();
+
+        assertTrue(intAtomicFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_int_ge_false() {
+        IntAtomicFormula intAtomicFormula =
+                new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GE, 1);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setVersionCode(0).build();
+
+        assertFalse(intAtomicFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_int_lt_true() {
+        IntAtomicFormula intAtomicFormula =
+                new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LT, 1);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setVersionCode(0).build();
+
+        assertTrue(intAtomicFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_int_lt_false() {
+        IntAtomicFormula intAtomicFormula =
+                new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LT, 1);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setVersionCode(2).build();
+
+        assertFalse(intAtomicFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_int_le_true() {
+        IntAtomicFormula intAtomicFormula =
+                new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, 1);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setVersionCode(0).build();
+
+        assertTrue(intAtomicFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_int_le_false() {
+        IntAtomicFormula intAtomicFormula =
+                new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, 1);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setVersionCode(2).build();
+
+        assertFalse(intAtomicFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_bool_true() {
+        BooleanAtomicFormula boolFormula =
+                new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setIsPreInstalled(true).build();
+
+        assertTrue(boolFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_bool_false() {
+        BooleanAtomicFormula boolFormula =
+                new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setIsPreInstalled(false).build();
+
+        assertFalse(boolFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testParcelUnparcel_string() {
+        StringAtomicFormula formula = new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "abc");
+        Parcel p = Parcel.obtain();
+        formula.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        StringAtomicFormula newFormula = StringAtomicFormula.CREATOR.createFromParcel(p);
+
+        assertEquals(formula, newFormula);
+    }
+
+    @Test
+    public void testParcelUnparcel_int() {
+        IntAtomicFormula formula =
+                new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LT, 1);
+        Parcel p = Parcel.obtain();
+        formula.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        IntAtomicFormula newFormula = IntAtomicFormula.CREATOR.createFromParcel(p);
+
+        assertEquals(formula, newFormula);
+    }
+
+    @Test
+    public void testParcelUnparcel_bool() {
+        BooleanAtomicFormula formula = new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true);
+        Parcel p = Parcel.obtain();
+        formula.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        BooleanAtomicFormula newFormula = BooleanAtomicFormula.CREATOR.createFromParcel(p);
+
+        assertEquals(formula, newFormula);
+    }
+
+    /** Returns a builder with all fields filled with some dummy data. */
+    private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
+        return new AppInstallMetadata.Builder()
+                .setPackageName("abc")
+                .setAppCertificate("abc")
+                .setInstallerCertificate("abc")
+                .setInstallerName("abc")
+                .setVersionCode(-1)
+                .setIsPreInstalled(true);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/OpenFormulaTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/OpenFormulaTest.java
index 2133a7d..ecabb52 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/model/OpenFormulaTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/OpenFormulaTest.java
@@ -19,6 +19,10 @@
 import static com.android.server.testutils.TestUtils.assertExpectException;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -30,17 +34,17 @@
 @RunWith(JUnit4.class)
 public class OpenFormulaTest {
 
-    private static final AtomicFormula ATOMIC_FORMULA_1 = new AtomicFormula(
-            AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, "test1");
-    private static final AtomicFormula ATOMIC_FORMULA_2 = new AtomicFormula(
-            AtomicFormula.Key.VERSION_CODE, AtomicFormula.Operator.EQ, 1);
+    private static final AtomicFormula ATOMIC_FORMULA_1 =
+            new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "test1");
+    private static final AtomicFormula ATOMIC_FORMULA_2 =
+            new AtomicFormula.IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 1);
 
     @Test
     public void testValidOpenFormula() {
-        OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.AND,
-                Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
+        OpenFormula openFormula =
+                new OpenFormula(OpenFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
 
-        assertEquals(OpenFormula.Connector.AND, openFormula.getConnector());
+        assertEquals(OpenFormula.AND, openFormula.getConnector());
         assertEquals(Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2), openFormula.getFormulas());
     }
 
@@ -49,10 +53,10 @@
         assertExpectException(
                 IllegalArgumentException.class,
                 /* expectedExceptionMessageRegex */
-                String.format("Connector %s must have at least 2 formulas",
-                        OpenFormula.Connector.AND),
-                () -> new OpenFormula(OpenFormula.Connector.AND,
-                        Collections.singletonList(ATOMIC_FORMULA_1)));
+                String.format("Connector AND must have at least 2 formulas"),
+                () ->
+                        new OpenFormula(
+                                OpenFormula.AND, Collections.singletonList(ATOMIC_FORMULA_1)));
     }
 
     @Test
@@ -60,8 +64,159 @@
         assertExpectException(
                 IllegalArgumentException.class,
                 /* expectedExceptionMessageRegex */
-                String.format("Connector %s must have 1 formula only", OpenFormula.Connector.NOT),
-                () -> new OpenFormula(OpenFormula.Connector.NOT,
-                        Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)));
+                String.format("Connector NOT must have 1 formula only"),
+                () ->
+                        new OpenFormula(
+                                OpenFormula.NOT,
+                                Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)));
+    }
+
+    @Test
+    public void testIsSatisfiable_notFalse_true() {
+        OpenFormula openFormula = new OpenFormula(OpenFormula.NOT, Arrays.asList(ATOMIC_FORMULA_1));
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setPackageName("test2").build();
+        // validate assumptions about the metadata
+        assertFalse(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata));
+
+        assertTrue(openFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_notTrue_false() {
+        OpenFormula openFormula = new OpenFormula(OpenFormula.NOT, Arrays.asList(ATOMIC_FORMULA_1));
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setPackageName("test1").build();
+        // validate assumptions about the metadata
+        assertTrue(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata));
+
+        assertFalse(openFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_trueAndTrue_true() {
+        OpenFormula openFormula =
+                new OpenFormula(OpenFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setPackageName("test1").setVersionCode(1).build();
+        // validate assumptions about the metadata
+        assertTrue(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata));
+        assertTrue(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata));
+
+        assertTrue(openFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_trueAndFalse_false() {
+        OpenFormula openFormula =
+                new OpenFormula(OpenFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setPackageName("test1").setVersionCode(2).build();
+        // validate assumptions about the metadata
+        assertTrue(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata));
+        assertFalse(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata));
+
+        assertFalse(openFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_falseAndTrue_false() {
+        OpenFormula openFormula =
+                new OpenFormula(OpenFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setPackageName("test2").setVersionCode(1).build();
+        // validate assumptions about the metadata
+        assertFalse(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata));
+        assertTrue(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata));
+
+        assertFalse(openFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_falseAndFalse_false() {
+        OpenFormula openFormula =
+                new OpenFormula(OpenFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setPackageName("test2").setVersionCode(2).build();
+        // validate assumptions about the metadata
+        assertFalse(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata));
+        assertFalse(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata));
+
+        assertFalse(openFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_trueOrTrue_true() {
+        OpenFormula openFormula =
+                new OpenFormula(OpenFormula.OR, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setPackageName("test1").setVersionCode(1).build();
+        // validate assumptions about the metadata
+        assertTrue(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata));
+        assertTrue(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata));
+
+        assertTrue(openFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_trueOrFalse_true() {
+        OpenFormula openFormula =
+                new OpenFormula(OpenFormula.OR, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setPackageName("test1").setVersionCode(2).build();
+        // validate assumptions about the metadata
+        assertTrue(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata));
+        assertFalse(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata));
+
+        assertTrue(openFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_falseOrTrue_true() {
+        OpenFormula openFormula =
+                new OpenFormula(OpenFormula.OR, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setPackageName("test2").setVersionCode(1).build();
+        // validate assumptions about the metadata
+        assertFalse(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata));
+        assertTrue(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata));
+
+        assertTrue(openFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testIsSatisfiable_falseOrFalse_false() {
+        OpenFormula openFormula =
+                new OpenFormula(OpenFormula.OR, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder().setPackageName("test2").setVersionCode(2).build();
+        // validate assumptions about the metadata
+        assertFalse(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata));
+        assertFalse(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata));
+
+        assertFalse(openFormula.isSatisfied(appInstallMetadata));
+    }
+
+    @Test
+    public void testParcelUnparcel() {
+        OpenFormula formula =
+                new OpenFormula(OpenFormula.AND, Arrays.asList(ATOMIC_FORMULA_2, ATOMIC_FORMULA_1));
+        Parcel p = Parcel.obtain();
+        formula.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        OpenFormula newFormula = OpenFormula.CREATOR.createFromParcel(p);
+
+        assertEquals(formula, newFormula);
+    }
+
+    /** Returns a builder with all fields filled with some dummy data. */
+    private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
+        return new AppInstallMetadata.Builder()
+                .setPackageName("abc")
+                .setAppCertificate("abc")
+                .setInstallerCertificate("abc")
+                .setInstallerName("abc")
+                .setVersionCode(-1)
+                .setIsPreInstalled(true);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java
index 048ee70..e0c36fd 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java
@@ -20,7 +20,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
+
+import android.os.Parcel;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,23 +32,13 @@
 @RunWith(JUnit4.class)
 public class RuleTest {
 
-    private static final Rule.Effect DENY_EFFECT = Rule.Effect.DENY;
+    private static final @Rule.Effect int DENY_EFFECT = Rule.DENY;
     private static final String PACKAGE_NAME = "com.test.app";
     private static final String APP_CERTIFICATE = "test_cert";
     private static final Formula PACKAGE_NAME_ATOMIC_FORMULA =
-            new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
-                    PACKAGE_NAME);
+            new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME);
     private static final Formula APP_CERTIFICATE_ATOMIC_FORMULA =
-            new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE, AtomicFormula.Operator.EQ,
-                    APP_CERTIFICATE);
-
-    @Test
-    public void testEmptyRule() {
-        Rule emptyRule = Rule.EMPTY;
-
-        assertNull(emptyRule.getFormula());
-        assertNull(emptyRule.getEffect());
-    }
+            new AtomicFormula.StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE);
 
     @Test
     public void testValidRule() {
@@ -58,14 +49,6 @@
     }
 
     @Test
-    public void testInvalidRule_invalidEffect() {
-        assertExpectException(
-                NullPointerException.class,
-                /* expectedExceptionMessageRegex */ null,
-                () -> new Rule(PACKAGE_NAME_ATOMIC_FORMULA, null));
-    }
-
-    @Test
     public void testInvalidRule_invalidFormula() {
         assertExpectException(
                 NullPointerException.class,
@@ -75,14 +58,17 @@
 
     @Test
     public void testToString() {
-        OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.AND,
-                Arrays.asList(PACKAGE_NAME_ATOMIC_FORMULA, APP_CERTIFICATE_ATOMIC_FORMULA));
-        Rule rule = new Rule(openFormula, Rule.Effect.DENY);
+        OpenFormula openFormula =
+                new OpenFormula(
+                        OpenFormula.AND,
+                        Arrays.asList(PACKAGE_NAME_ATOMIC_FORMULA, APP_CERTIFICATE_ATOMIC_FORMULA));
+        Rule rule = new Rule(openFormula, Rule.DENY);
 
-        String toString = rule.toString();
-
-        assertEquals(String.format("Rule: PACKAGE_NAME EQ %s AND APP_CERTIFICATE EQ %s, DENY",
-                PACKAGE_NAME, APP_CERTIFICATE), toString);
+        assertEquals(
+                String.format(
+                        "Rule: (PACKAGE_NAME EQ %s) AND (APP_CERTIFICATE EQ %s), DENY",
+                        PACKAGE_NAME, APP_CERTIFICATE),
+                rule.toString());
     }
 
     @Test
@@ -100,4 +86,24 @@
 
         assertNotEquals(rule1, rule2);
     }
+
+    @Test
+    public void testParcelUnparcel() {
+        Rule rule =
+                new Rule(
+                        new OpenFormula(
+                                OpenFormula.AND,
+                                Arrays.asList(
+                                        APP_CERTIFICATE_ATOMIC_FORMULA,
+                                        new OpenFormula(
+                                                OpenFormula.NOT,
+                                                Arrays.asList(PACKAGE_NAME_ATOMIC_FORMULA)))),
+                        Rule.DENY);
+        Parcel p = Parcel.obtain();
+        rule.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        Rule newRule = Rule.CREATOR.createFromParcel(p);
+
+        assertEquals(newRule, rule);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
new file mode 100644
index 0000000..86e544d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 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.android.server.integrity.parser;
+
+import static com.android.server.testutils.TestUtils.assertExpectException;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.server.integrity.model.Rule;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class RuleXmlParserTest {
+
+    private static final String VALID_RULE_XML = "<RuleList>"
+            + "<Rule>"
+            + "<OpenFormula>"
+            + "<Connector>NOT</Connector>"
+            + "<AtomicFormula>"
+            + "<Key>PACKAGE_NAME</Key>"
+            + "<Operator>EQ</Operator>"
+            + "<Value>com.app.test</Value>"
+            + "</AtomicFormula>"
+            + "</OpenFormula>"
+            + "<Effect>DENY</Effect>"
+            + "</Rule>"
+            + "</RuleList>";
+
+    @Test
+    public void testXmlString_validRule() {
+        RuleParser xmlParser = new RuleXmlParser();
+
+        List<Rule> rules = xmlParser.parse(VALID_RULE_XML);
+
+        assertNotNull(rules);
+        assertTrue(rules.isEmpty());
+    }
+
+    @Test
+    public void testXmlStream_validRule() {
+        RuleParser xmlParser = new RuleXmlParser();
+        InputStream inputStream = new ByteArrayInputStream(VALID_RULE_XML.getBytes());
+
+        List<Rule> rules = xmlParser.parse(inputStream);
+
+        assertNotNull(rules);
+        assertTrue(rules.isEmpty());
+    }
+
+    @Test
+    public void testXmlString_withNoRuleList() {
+        String ruleXmlWithNoRuleList = "<Rule>"
+                + "<OpenFormula>"
+                + "<Connector>NOT</Connector>"
+                + "<AtomicFormula>"
+                + "<Key>PACKAGE_NAME</Key>"
+                + "<Operator>EQ</Operator>"
+                + "<Value>com.app.test</Value>"
+                + "</AtomicFormula>"
+                + "</OpenFormula>"
+                + "<Effect>DENY</Effect>"
+                + "</Rule>";
+        RuleParser xmlParser = new RuleXmlParser();
+
+        assertExpectException(
+                RuntimeException.class,
+                /* expectedExceptionMessageRegex */ "Rules must start with <RuleList> tag.",
+                () -> xmlParser.parse(ruleXmlWithNoRuleList));
+    }
+
+    @Test
+    public void testXmlStream_withNoRuleList() {
+        String ruleXmlWithNoRuleList = "<Rule>"
+                + "<OpenFormula>"
+                + "<Connector>NOT</Connector>"
+                + "<AtomicFormula>"
+                + "<Key>PACKAGE_NAME</Key>"
+                + "<Operator>EQ</Operator>"
+                + "<Value>com.app.test</Value>"
+                + "</AtomicFormula>"
+                + "</OpenFormula>"
+                + "<Effect>DENY</Effect>"
+                + "</Rule>";
+        InputStream inputStream = new ByteArrayInputStream(ruleXmlWithNoRuleList.getBytes());
+        RuleParser xmlParser = new RuleXmlParser();
+
+        assertExpectException(
+                RuntimeException.class,
+                /* expectedExceptionMessageRegex */ "Rules must start with <RuleList> tag.",
+                () -> xmlParser.parse(inputStream));
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index a1322b9..776c00e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2715,4 +2715,57 @@
         assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, extraChannel, true));
         assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, extraChannel1, true));
     }
+
+    @Test
+    public void testRestoreMultiUser() throws Exception {
+        String pkg = "restore_pkg";
+        String channelId = "channelId";
+        int user0Importance = 3;
+        int user10Importance = 4;
+        when(mPm.getPackageUidAsUser(eq(pkg), anyInt())).thenReturn(UserHandle.USER_NULL);
+
+        // both users have the same package, but different notification settings
+        final String xmlUser0 = "<ranking version=\"1\">\n"
+                + "<package name=\"" + pkg + "\" >\n"
+                + "<channel id=\"" + channelId + "\" name=\"hi\""
+                + " importance=\"" + user0Importance + "\"/>"
+                + "</package>"
+                + "</ranking>";
+        final String xmlUser10 = "<ranking version=\"1\">\n"
+                + "<package name=\"" + pkg + "\" >\n"
+                + "<channel id=\"" + channelId + "\" name=\"hi\""
+                + " importance=\"" + user10Importance + "\"/>"
+                + "</package>"
+                + "</ranking>";
+
+        // trigger a restore for both users
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xmlUser0.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, true, 0);
+        parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xmlUser10.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, true, 10);
+
+        // "install" package on both users
+        String[] pkgList = new String[] {pkg};
+        int[] uidList0 = new int[] {UserHandle.PER_USER_RANGE};
+        int[] uidList10 = new int[] {UserHandle.PER_USER_RANGE + 1};
+        when(mPm.getPackageUidAsUser(pkg, 0)).thenReturn(uidList0[0]);
+        when(mPm.getPackageUidAsUser(pkg, 10)).thenReturn(uidList10[0]);
+        ApplicationInfo info = new ApplicationInfo();
+        info.targetSdkVersion = Build.VERSION_CODES.Q;
+        when(mPm.getApplicationInfoAsUser(eq(pkg), anyInt(), anyInt())).thenReturn(info);
+
+        mHelper.onPackagesChanged(false, 0, pkgList, uidList0);
+        mHelper.onPackagesChanged(false, 10, pkgList, uidList10);
+
+        assertEquals(user0Importance,
+                mHelper.getNotificationChannel(pkg, uidList0[0], channelId, false).getImportance());
+        assertEquals(user10Importance, mHelper.getNotificationChannel(
+                pkg, uidList10[0], channelId, false).getImportance());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index df1135f..562775c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -310,6 +310,7 @@
         DisplayThread.dispose();
         AnimationThread.dispose();
         UiThread.dispose();
+        SurfaceAnimationThread.dispose();
         mInputChannel.dispose();
 
         tearDownLocalServices();
diff --git a/tests/FlickerTests/TEST_MAPPING b/tests/FlickerTests/TEST_MAPPING
index f34c432..db251b9 100644
--- a/tests/FlickerTests/TEST_MAPPING
+++ b/tests/FlickerTests/TEST_MAPPING
@@ -1,8 +1,13 @@
 {
   "postsubmit": [
+    // Run tests on real device
     {
       "name": "FlickerTests",
       "keywords": ["primary-device"]
+    },
+    // Also run the tests in the cloud
+    {
+      "name": "FlickerTests"
     }
   ]
 }
\ No newline at end of file