Merge "Call through to ART Service in notifyDexLoad if it is enabled."
diff --git a/apct-tests/perftests/rubidium/Android.bp b/apct-tests/perftests/rubidium/Android.bp
index ba2b442..ebd228f 100644
--- a/apct-tests/perftests/rubidium/Android.bp
+++ b/apct-tests/perftests/rubidium/Android.bp
@@ -32,6 +32,7 @@
         "collector-device-lib-platform",
         "compatibility-device-util-axt",
         "platform-test-annotations",
+        "framework-adservices-lib",
         "adservices-service-core",
         "androidx.core_core",
     ],
diff --git a/apct-tests/perftests/rubidium/assets/rubidium_bidding_logic_compiled.js b/apct-tests/perftests/rubidium/assets/rubidium_bidding_logic_compiled.js
new file mode 100644
index 0000000..3f853f1
--- /dev/null
+++ b/apct-tests/perftests/rubidium/assets/rubidium_bidding_logic_compiled.js
@@ -0,0 +1,72 @@
+/*
+
+ Copyright The Closure Library Authors.
+ SPDX-License-Identifier: Apache-2.0
+*/
+'use strict';function ba(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}}var ca="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a};
+function da(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");}var fa=da(this);function m(a,b){if(b)a:{var c=fa;a=a.split(".");for(var d=0;d<a.length-1;d++){var f=a[d];if(!(f in c))break a;c=c[f]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&ca(c,a,{configurable:!0,writable:!0,value:b})}}
+m("Symbol",function(a){function b(e){if(this instanceof b)throw new TypeError("Symbol is not a constructor");return new c(d+(e||"")+"_"+f++,e)}function c(e,h){this.h=e;ca(this,"description",{configurable:!0,writable:!0,value:h})}if(a)return a;c.prototype.toString=function(){return this.h};var d="jscomp_symbol_"+(1E9*Math.random()>>>0)+"_",f=0;return b});
+m("Symbol.iterator",function(a){if(a)return a;a=Symbol("Symbol.iterator");for(var b="Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "),c=0;c<b.length;c++){var d=fa[b[c]];"function"===typeof d&&"function"!=typeof d.prototype[a]&&ca(d.prototype,a,{configurable:!0,writable:!0,value:function(){return ia(ba(this))}})}return a});function ia(a){a={next:a};a[Symbol.iterator]=function(){return this};return a}
+function p(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):{next:ba(a)}}function ja(a){if(!(a instanceof Array)){a=p(a);for(var b,c=[];!(b=a.next()).done;)c.push(b.value);a=c}return a}var la="function"==typeof Object.create?Object.create:function(a){function b(){}b.prototype=a;return new b},ma;
+if("function"==typeof Object.setPrototypeOf)ma=Object.setPrototypeOf;else{var na;a:{var oa={a:!0},qa={};try{qa.__proto__=oa;na=qa.a;break a}catch(a){}na=!1}ma=na?function(a,b){a.__proto__=b;if(a.__proto__!==b)throw new TypeError(a+" is not extensible");return a}:null}var ra=ma;
+function q(a,b){a.prototype=la(b.prototype);a.prototype.constructor=a;if(ra)ra(a,b);else for(var c in b)if("prototype"!=c)if(Object.defineProperties){var d=Object.getOwnPropertyDescriptor(b,c);d&&Object.defineProperty(a,c,d)}else a[c]=b[c];a.Ia=b.prototype}function sa(){for(var a=Number(this),b=[],c=a;c<arguments.length;c++)b[c-a]=arguments[c];return b}function ta(a,b){return Object.prototype.hasOwnProperty.call(a,b)}
+m("WeakMap",function(a){function b(k){this.h=(g+=Math.random()+1).toString();if(k){k=p(k);for(var l;!(l=k.next()).done;)l=l.value,this.set(l[0],l[1])}}function c(){}function d(k){var l=typeof k;return"object"===l&&null!==k||"function"===l}function f(k){if(!ta(k,h)){var l=new c;ca(k,h,{value:l})}}function e(k){var l=Object[k];l&&(Object[k]=function(n){if(n instanceof c)return n;Object.isExtensible(n)&&f(n);return l(n)})}if(function(){if(!a||!Object.seal)return!1;try{var k=Object.seal({}),l=Object.seal({}),
+n=new a([[k,2],[l,3]]);if(2!=n.get(k)||3!=n.get(l))return!1;n.delete(k);n.set(l,4);return!n.has(k)&&4==n.get(l)}catch(t){return!1}}())return a;var h="$jscomp_hidden_"+Math.random();e("freeze");e("preventExtensions");e("seal");var g=0;b.prototype.set=function(k,l){if(!d(k))throw Error("Invalid WeakMap key");f(k);if(!ta(k,h))throw Error("WeakMap key fail: "+k);k[h][this.h]=l;return this};b.prototype.get=function(k){return d(k)&&ta(k,h)?k[h][this.h]:void 0};b.prototype.has=function(k){return d(k)&&ta(k,
+h)&&ta(k[h],this.h)};b.prototype.delete=function(k){return d(k)&&ta(k,h)&&ta(k[h],this.h)?delete k[h][this.h]:!1};return b});
+m("Map",function(a){function b(){var g={};return g.C=g.next=g.head=g}function c(g,k){var l=g.h;return ia(function(){if(l){for(;l.head!=g.h;)l=l.C;for(;l.next!=l.head;)return l=l.next,{done:!1,value:k(l)};l=null}return{done:!0,value:void 0}})}function d(g,k){var l=k&&typeof k;"object"==l||"function"==l?e.has(k)?l=e.get(k):(l=""+ ++h,e.set(k,l)):l="p_"+k;var n=g.u[l];if(n&&ta(g.u,l))for(g=0;g<n.length;g++){var t=n[g];if(k!==k&&t.key!==t.key||k===t.key)return{id:l,list:n,index:g,m:t}}return{id:l,list:n,
+index:-1,m:void 0}}function f(g){this.u={};this.h=b();this.size=0;if(g){g=p(g);for(var k;!(k=g.next()).done;)k=k.value,this.set(k[0],k[1])}}if(function(){if(!a||"function"!=typeof a||!a.prototype.entries||"function"!=typeof Object.seal)return!1;try{var g=Object.seal({x:4}),k=new a(p([[g,"s"]]));if("s"!=k.get(g)||1!=k.size||k.get({x:4})||k.set({x:4},"t")!=k||2!=k.size)return!1;var l=k.entries(),n=l.next();if(n.done||n.value[0]!=g||"s"!=n.value[1])return!1;n=l.next();return n.done||4!=n.value[0].x||
+"t"!=n.value[1]||!l.next().done?!1:!0}catch(t){return!1}}())return a;var e=new WeakMap;f.prototype.set=function(g,k){g=0===g?0:g;var l=d(this,g);l.list||(l.list=this.u[l.id]=[]);l.m?l.m.value=k:(l.m={next:this.h,C:this.h.C,head:this.h,key:g,value:k},l.list.push(l.m),this.h.C.next=l.m,this.h.C=l.m,this.size++);return this};f.prototype.delete=function(g){g=d(this,g);return g.m&&g.list?(g.list.splice(g.index,1),g.list.length||delete this.u[g.id],g.m.C.next=g.m.next,g.m.next.C=g.m.C,g.m.head=null,this.size--,
+!0):!1};f.prototype.clear=function(){this.u={};this.h=this.h.C=b();this.size=0};f.prototype.has=function(g){return!!d(this,g).m};f.prototype.get=function(g){return(g=d(this,g).m)&&g.value};f.prototype.entries=function(){return c(this,function(g){return[g.key,g.value]})};f.prototype.keys=function(){return c(this,function(g){return g.key})};f.prototype.values=function(){return c(this,function(g){return g.value})};f.prototype.forEach=function(g,k){for(var l=this.entries(),n;!(n=l.next()).done;)n=n.value,
+g.call(k,n[1],n[0],this)};f.prototype[Symbol.iterator]=f.prototype.entries;var h=0;return f});m("Number.isNaN",function(a){return a?a:function(b){return"number"===typeof b&&isNaN(b)}});function ua(a,b){a instanceof String&&(a+="");var c=0,d=!1,f={next:function(){if(!d&&c<a.length){var e=c++;return{value:b(e,a[e]),done:!1}}d=!0;return{done:!0,value:void 0}}};f[Symbol.iterator]=function(){return f};return f}m("Array.prototype.values",function(a){return a?a:function(){return ua(this,function(b,c){return c})}});
+m("Set",function(a){function b(c){this.h=new Map;if(c){c=p(c);for(var d;!(d=c.next()).done;)this.add(d.value)}this.size=this.h.size}if(function(){if(!a||"function"!=typeof a||!a.prototype.entries||"function"!=typeof Object.seal)return!1;try{var c=Object.seal({x:4}),d=new a(p([c]));if(!d.has(c)||1!=d.size||d.add(c)!=d||1!=d.size||d.add({x:4})!=d||2!=d.size)return!1;var f=d.entries(),e=f.next();if(e.done||e.value[0]!=c||e.value[1]!=c)return!1;e=f.next();return e.done||e.value[0]==c||4!=e.value[0].x||
+e.value[1]!=e.value[0]?!1:f.next().done}catch(h){return!1}}())return a;b.prototype.add=function(c){c=0===c?0:c;this.h.set(c,c);this.size=this.h.size;return this};b.prototype.delete=function(c){c=this.h.delete(c);this.size=this.h.size;return c};b.prototype.clear=function(){this.h.clear();this.size=0};b.prototype.has=function(c){return this.h.has(c)};b.prototype.entries=function(){return this.h.entries()};b.prototype.values=function(){return this.h.values()};b.prototype.keys=b.prototype.values;b.prototype[Symbol.iterator]=
+b.prototype.values;b.prototype.forEach=function(c,d){var f=this;this.h.forEach(function(e){return c.call(d,e,e,f)})};return b});m("globalThis",function(a){return a||fa});m("Object.is",function(a){return a?a:function(b,c){return b===c?0!==b||1/b===1/c:b!==b&&c!==c}});m("Array.prototype.includes",function(a){return a?a:function(b,c){var d=this;d instanceof String&&(d=String(d));var f=d.length;c=c||0;for(0>c&&(c=Math.max(c+f,0));c<f;c++){var e=d[c];if(e===b||Object.is(e,b))return!0}return!1}});
+m("String.prototype.includes",function(a){return a?a:function(b,c){if(null==this)throw new TypeError("The 'this' value for String.prototype.includes must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype.includes must not be a regular expression");return-1!==this.indexOf(b,c||0)}});m("Object.values",function(a){return a?a:function(b){var c=[],d;for(d in b)ta(b,d)&&c.push(b[d]);return c}});
+function va(a){var b=typeof a;return"object"==b&&null!=a||"function"==b};var wa={};var xa=Array.prototype.map?function(a,b){return Array.prototype.map.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=Array(c),f="string"===typeof a?a.split(""):a,e=0;e<c;e++)e in f&&(d[e]=b.call(void 0,f[e],e,a));return d};var ya={},za=null;function Aa(a,b){void 0===b&&(b=0);Ba();b=ya[b];for(var c=Array(Math.floor(a.length/3)),d=b[64]||"",f=0,e=0;f<a.length-2;f+=3){var h=a[f],g=a[f+1],k=a[f+2],l=b[h>>2];h=b[(h&3)<<4|g>>4];g=b[(g&15)<<2|k>>6];k=b[k&63];c[e++]=l+h+g+k}l=0;k=d;switch(a.length-f){case 2:l=a[f+1],k=b[(l&15)<<2]||d;case 1:a=a[f],c[e]=b[a>>2]+b[(a&3)<<4|l>>4]+k+d}return c.join("")}
+function Ca(a){var b=a.length,c=3*b/4;c%3?c=Math.floor(c):-1!="=.".indexOf(a[b-1])&&(c=-1!="=.".indexOf(a[b-2])?c-2:c-1);var d=new Uint8Array(c),f=0;Da(a,function(e){d[f++]=e});return f!==c?d.subarray(0,f):d}
+function Da(a,b){function c(k){for(;d<a.length;){var l=a.charAt(d++),n=za[l];if(null!=n)return n;if(!/^[\s\xa0]*$/.test(l))throw Error("Unknown base64 encoding at char: "+l);}return k}Ba();for(var d=0;;){var f=c(-1),e=c(0),h=c(64),g=c(64);if(64===g&&-1===f)break;b(f<<2|e>>4);64!=h&&(b(e<<4&240|h>>2),64!=g&&b(h<<6&192|g))}}
+function Ba(){if(!za){za={};for(var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),b=["+/=","+/","-_=","-_.","-_"],c=0;5>c;c++){var d=a.concat(b[c].split(""));ya[c]=d;for(var f=0;f<d.length;f++){var e=d[f];void 0===za[e]&&(za[e]=f)}}}};var Ea="undefined"!==typeof Uint8Array;function Fa(a){return null==a||Ga(a)?a:"string"===typeof a?Ca(a):null}function Ga(a){return Ea&&null!=a&&a instanceof Uint8Array}var Ha={};var Ia;function Qa(a){if(a!==Ha)throw Error("illegal external caller");}function Ra(a,b){Qa(b);this.aa=a;if(null!=a&&0===a.length)throw Error("ByteString should be constructed with non-empty values");}function Sa(){return Ia||(Ia=new Ra(null,Ha))}function Ta(a){var b=a.aa;return null==b?"":"string"===typeof b?b:a.aa=Aa(b)};var r="function"===typeof Symbol&&"symbol"===typeof Symbol()?Symbol():void 0;function u(a,b){if(r)return a[r]|=b;if(void 0!==a.A)return a.A|=b;Object.defineProperties(a,{A:{value:b,configurable:!0,writable:!0,enumerable:!1}});return b}function Ua(a,b){r?a[r]&&(a[r]&=~b):void 0!==a.A&&(a.A&=~b)}function v(a){var b;r?b=a[r]:b=a.A;return null==b?0:b}function Va(a,b){r?a[r]=b:void 0!==a.A?a.A=b:Object.defineProperties(a,{A:{value:b,configurable:!0,writable:!0,enumerable:!1}})}
+function $a(a){u(a,1);return a}function w(a){return!!(v(a)&2)}function ab(a){u(a,16);return a}function bb(a,b){Va(b,(a|0)&-51)}function cb(a,b){Va(b,(a|18)&-41)};var db={};function eb(a){return null!==a&&"object"===typeof a&&!Array.isArray(a)&&a.constructor===Object}function fb(a){a instanceof Ra&&(Qa(Ha),a=a.aa||"");return a}var gb,hb=[];Va(hb,23);gb=Object.freeze(hb);function ib(a){if(w(a.i))throw Error("Cannot mutate an immutable Message");}function jb(a){var b=a.length;(b=b?a[b-1]:void 0)&&eb(b)?b.g=1:(b={},a.push((b.g=1,b)))};function kb(a){return a}function lb(a){return a};function mb(a,b){a=a||{};b=b||{};var c={},d;for(d in a)c[d]=0;for(var f in b)c[f]=0;for(var e in c)if(!nb(a[e],b[e]))return!1;return!0}function ob(a){return a&&"object"===typeof a?a.i||a:a}
+function nb(a,b){a=fb(a);b=fb(b);a=ob(a);b=ob(b);if(a==b)return!0;if(Ea){var c=Ga(a),d=Ga(b);if(c||d){if(!c)if("string"===typeof a)a=Fa(a);else return!1;if(d)d=b;else if("string"===typeof b)d=Fa(b);else return!1;if(a.length!==d.length)return!1;for(b=0;b<a.length;b++)if(a[b]!==d[b])return!1;return!0}}if(null==a&&Array.isArray(b)&&v(b)&1&&!b.length||null==b&&Array.isArray(a)&&v(a)&1&&!a.length)return!0;if(!va(a)||!va(b))return"number"===typeof a&&isNaN(a)||"number"===typeof b&&isNaN(b)?String(a)==String(b):
+!1;if(a.constructor!=b.constructor)return!1;if(a.constructor===Array){d=a;c=a=void 0;for(var f=Math.max(d.length,b.length),e=0;e<f;e++){var h=d[e],g=b[e];h&&h.constructor==Object&&(a=h,h=void 0);g&&g.constructor==Object&&(c=g,g=void 0);if(!nb(h,g))return!1}return a||c?(a=a||{},c=c||{},mb(a,c)):!0}if(a.constructor===Object)return mb(a,b);throw Error("Invalid type in JSPB array");};var pb;function qb(a){switch(typeof a){case "number":return isFinite(a)?a:String(a);case "object":if(a)if(Array.isArray(a)){if(0!==(v(a)&128))return a=Array.prototype.slice.call(a),jb(a),a}else{if(Ga(a))return Aa(a);if(a instanceof Ra)return Ta(a)}}return a};function rb(a,b,c,d){if(null!=a){if(Array.isArray(a))a=sb(a,b,c,void 0!==d);else if(eb(a)){var f={},e;for(e in a)f[e]=rb(a[e],b,c,d);a=f}else a=b(a,d);return a}}function sb(a,b,c,d){var f=v(a);d=d?!!(f&16):void 0;a=Array.prototype.slice.call(a);for(var e=0;e<a.length;e++)a[e]=rb(a[e],b,c,d);c(f,a);return a}function tb(a){return a.Y===db?a.toJSON():qb(a)}function ub(a,b){a&128&&jb(b)};function vb(a){return a.h||(a.h=a.i[a.u+a.H]={})}function x(a,b,c){return-1===b?null:b>=a.u?a.h?a.h[b]:void 0:c&&a.h&&(c=a.h[b],null!=c)?c:a.i[b+a.H]}function wb(a,b,c,d){ib(a);return y(a,b,c,d)}function y(a,b,c,d){a.V&&(a.V=void 0);if(b>=a.u||d)return vb(a)[b]=c,a;a.i[b+a.H]=c;(c=a.h)&&b in c&&delete c[b];return a}
+function xb(a,b,c,d,f){var e=x(a,b,d);Array.isArray(e)||(e=gb);var h=v(e);h&1||$a(e);if(f)h&2||u(e,2),c&1||Object.freeze(e);else{f=!(c&2);var g=h&2;c&1||!g?f&&h&16&&!g&&Ua(e,16):(e=$a(Array.prototype.slice.call(e)),y(a,b,e,d))}return e}function yb(a,b){return xb(a,b,0,!1,w(a.i))}
+function B(a,b,c,d){var f=w(a.i),e=xb(a,b,1,d,f),h=v(e);if(!(h&4)){Object.isFrozen(e)&&(e=$a(e.slice()),y(a,b,e,d));for(var g=0,k=0;g<e.length;g++){var l=c(e[g]);null!=l&&(e[k++]=l)}k<g&&(e.length=k);u(e,5);f&&(u(e,2),Object.freeze(e))}!f&&(h&2||Object.isFrozen(e))&&(e=Array.prototype.slice.call(e),u(e,5),zb(a,b,e,d));return e}function Ub(a){return null==a?a:"string"===typeof a?a?new Ra(a,Ha):Sa():a.constructor===Ra?a:Ga(a)?a.length?new Ra(new Uint8Array(a),Ha):Sa():void 0}
+function E(a,b){a=x(a,b);return null==a?0:a}function zb(a,b,c,d){if(null==c)c=gb;else{var f=v(c);1!==(f&1)&&(Object.isFrozen(c)&&(c=Array.prototype.slice.call(c)),Va(c,f|1))}return wb(a,b,c,d)}function G(a,b,c,d){ib(a);c!==d?y(a,b,c):y(a,b,void 0,!1);return a}var Vb=Symbol(void 0);function Wb(a,b,c,d){var f=x(a,c,d);var e=!1;var h=null==f||"object"!==typeof f||(e=Array.isArray(f))||f.Y!==db?e?new b(f):void 0:f;h!==f&&null!=h&&(y(a,c,h,d),u(h.i,v(a.i)&-33));return h}
+function H(a,b,c){if(a=Wb(a,b,c,!1))b=a;else if(a=b[Vb])b=a;else{a=new b;if(wa!==wa)throw Error("requires a valid immutable API token");w(a.i)||((c=a.V)&&nb(c.i,a.i)?a=c:(c=Xb(a,!0),u(c.i,2),a=a.V=c));b=b[Vb]=a}return b}function J(a,b,c){var d=void 0===d?!1:d;b=Wb(a,b,c,d);if(null==b)return b;if(!w(a.i)){var f=Yb(b);f!==b&&(b=f,y(a,c,b,d))}return b}
+function Zb(a,b,c,d,f){a.o||(a.o={});var e=a.o[c],h=xb(a,c,3,void 0,f);if(e)f||(Object.isFrozen(e)?d||(e=Array.prototype.slice.call(e),a.o[c]=e):d&&Object.freeze(e));else{e=[];var g=!!(v(a.i)&16),k=w(h);!f&&k&&(h=$a(Array.prototype.slice.call(h)),y(a,c,h));for(var l=k,n=0;n<h.length;n++){var t=h[n];var I=b;var A=g,F=!1;F=void 0===F?!1:F;A=void 0===A?!1:A;I=Array.isArray(t)?new I(A?ab(t):t):F?new I:void 0;void 0!==I&&(l=l||w(t),e.push(I),k&&u(I.i,2))}a.o[c]=e;a=h;Object.isFrozen(a)||(b=v(a)|33,Va(a,
+l?b&-9:b|8));(f||d&&k)&&u(e,2);(f||d)&&Object.freeze(e)}return e}function K(a,b,c){var d=w(a.i);b=Zb(a,b,c,d,d);a=xb(a,c,3,void 0,d);if(!(d||v(a)&8)){for(d=0;d<b.length;d++){c=b[d];var f=Yb(c);c!==f&&(b[d]=f,a[d]=b[d].i)}u(a,8)}return b}function M(a,b,c){ib(a);null==c&&(c=void 0);return y(a,b,c)}function $b(a,b,c,d){ib(a);if(null!=c){var f=$a([]);for(var e=!1,h=0;h<c.length;h++)f[h]=c[h].i,e=e||w(f[h]);a.o||(a.o={});a.o[b]=c;c=f;e?Ua(c,8):u(c,8)}else a.o&&(a.o[b]=void 0),f=gb;return y(a,b,f,d)}
+function ac(a,b,c,d){ib(a);var f=Zb(a,c,b,!1,!1);c=null!=d?d:new c;a=xb(a,b,2,void 0,!1);f.push(c);a.push(c.i);w(c.i)&&Ua(a,8);return c}function N(a,b){return P(x(a,b),"0")}function P(a,b){return null==a?b:a}function R(a,b){a=x(a,b);return P(null==a?a:!!a,!1)}function S(a,b){a=x(a,b);return P(null==a?a:+a,0)}function T(a,b){return P(x(a,b),0)};function bc(a){var b=v(a);if(b&2)return a;a=xa(a,cc);cb(b,a);Object.freeze(a);return a}function dc(a,b,c){c=void 0===c?cb:c;if(null!=a){if(Ea&&a instanceof Uint8Array)return a.length?new Ra(new Uint8Array(a),Ha):Sa();if(Array.isArray(a)){var d=v(a);if(d&2)return a;if(b&&!(d&32)&&(d&16||0===d))return Va(a,d|2),a;a=sb(a,dc,c,!0);b=v(a);b&4&&b&2&&Object.freeze(a);return a}return a.Y===db?cc(a):a}}function cc(a){if(w(a.i))return a;a=Xb(a,!0);u(a.i,2);return a}
+function Xb(a,b){var c=a.i,d=ab([]),f=a.constructor.h;f&&d.push(f);0!==(v(c)&128)&&jb(d);b=b||w(a.i)?cb:bb;f=a.constructor;pb=d;d=new f(d);pb=void 0;a.ca&&(d.ca=a.ca.slice());f=!!(v(c)&16);for(var e=0;e<c.length;e++){var h=c[e];if(e===c.length-1&&eb(h))for(var g in h){var k=+g;if(Number.isNaN(k))vb(d)[k]=h[k];else{var l=h[g],n=a.o&&a.o[k];n?$b(d,k,bc(n),!0):wb(d,k,dc(l,f,b),!0)}}else k=e-a.H,(l=a.o&&a.o[k])?$b(d,k,bc(l),!1):wb(d,k,dc(h,f,b),!1)}return d}
+function Yb(a){if(!w(a.i))return a;var b=Xb(a,!1);b.V=a;return b};function V(a,b,c){null==a&&(a=pb);pb=void 0;var d=this.constructor.u||0,f=0<d,e=this.constructor.h,h=!1;if(null==a){a=e?[e]:[];var g=!0;Va(a,48)}else{if(!Array.isArray(a))throw Error();if(e&&e!==a[0])throw Error();var k=u(a,0),l=k;if(g=0!==(16&l))(h=0!==(32&l))||(l|=32);if(f)if(128&l)d=0;else{if(0<a.length){var n=a[a.length-1];if(eb(n)&&"g"in n){d=0;l|=128;delete n.g;var t=!0,I;for(I in n){t=!1;break}t&&a.pop()}}}else if(128&l)throw Error();k!==l&&Va(a,l)}this.H=(e?0:-1)-d;this.o=void 0;this.i=a;
+a:{e=this.i.length;d=e-1;if(e&&(e=this.i[d],eb(e))){this.h=e;this.u=d-this.H;break a}void 0!==b&&-1<b?(this.u=Math.max(b,d+1-this.H),this.h=void 0):this.u=Number.MAX_VALUE}if(!f&&this.h&&"g"in this.h)throw Error('Unexpected "g" flag in sparse object of message that is not a group type.');if(c){b=g&&!h&&!0;f=this.u;var A;for(g=0;g<c.length;g++)h=c[g],h<f?(h+=this.H,(d=a[h])?ec(d,b):a[h]=gb):(A||(A=vb(this)),(d=A[h])?ec(d,b):A[h]=gb)}}V.prototype.toJSON=function(){return sb(this.i,tb,ub)};
+function ec(a,b){if(Array.isArray(a)){var c=v(a),d=1;!b||c&2||(d|=16);(c&d)!==d&&Va(a,c|d)}}V.prototype.Y=db;V.prototype.toString=function(){return this.i.toString()};var fc=void 0;function gc(a){var b=fc;fc=void 0;if(!Array.isArray(a))throw b=b?b()+"\n":"",Error(b+String(a));return a};function hc(a){return JSON.stringify([a.map(function(b){var c={};return[(c[b.ta]=b.message.toJSON(),c)]})])};function ic(a){this.h=a}ic.prototype.sa=function(){var a=encodeURIComponent;var b=hc(sa.apply(0,arguments));for(var c=[],d=0,f=0;f<b.length;f++){var e=b.charCodeAt(f);255<e&&(c[d++]=e&255,e>>=8);c[d++]=e}b=Aa(c,3);a=a(b);this.h("https://pagead2.googlesyndication.com/pagead/ping?e=3&d="+a)};function jc(a){V.call(this,a)}q(jc,V);function kc(a){V.call(this,a)}q(kc,V);function lc(a){V.call(this,a,-1,mc)}q(lc,V);var mc=[6];function nc(a){V.call(this,a,-1,oc)}q(nc,V);var oc=[2,3,4];function pc(a){V.call(this,a,-1,qc)}q(pc,V);var qc=[2];function rc(a){V.call(this,a,-1,sc)}q(rc,V);var sc=[1,3,10,7,8];function tc(a){V.call(this,a)}q(tc,V);function uc(a){V.call(this,a,-1,vc)}q(uc,V);var vc=[3];function wc(a){V.call(this,a,-1,xc)}q(wc,V);var xc=[3];function yc(a){V.call(this,a,-1,zc)}q(yc,V);var zc=[2,3,5];function Ac(a){V.call(this,a,-1,Bc)}q(Ac,V);function Cc(a){V.call(this,a,-1,Dc)}q(Cc,V);function Ec(a){V.call(this,a,-1,Fc)}q(Ec,V);var Bc=[1,2],Dc=[3,4,9],Fc=[3,4,5,6,7];function Gc(a){V.call(this,a,-1,Hc)}q(Gc,V);var Hc=[15];function Ic(a){V.call(this,a)}q(Ic,V);function Jc(a){V.call(this,a,-1,Kc)}q(Jc,V);var Kc=[1];function Lc(a){V.call(this,a)}q(Lc,V);function Mc(a){V.call(this,a)}q(Mc,V);function Nc(a){V.call(this,a,-1,Oc)}q(Nc,V);var Oc=[9,10,11,12,13,14,15,17];function Pc(a){V.call(this,a,-1,Qc)}q(Pc,V);var Qc=[1];function Zd(a){V.call(this,a)}q(Zd,V);function $d(a){V.call(this,a,-1,ae)}q($d,V);function W(a){return J(a,be,3)}function ce(a){V.call(this,a,-1,de)}q(ce,V);function ee(a){V.call(this,a)}q(ee,V);function be(a){V.call(this,a)}q(be,V);function fe(a){V.call(this,a)}q(fe,V);function ge(a){V.call(this,a)}q(ge,V);function he(a){var b=new ge;return G(b,1,a,0)}function ie(a){V.call(this,a,-1,je)}q(ie,V);function ke(a){V.call(this,a)}q(ke,V);var ae=[2],de=[1,2,3],je=[1,3];function le(a){V.call(this,a,-1,me)}q(le,V);var me=[10];function ne(a){a.sa.apply(a,ja(sa.apply(1,arguments).map(function(b){return{ta:9,message:b}})))};function oe(a,b){return!b||0>=b?a:Math.min(a,b)}function X(a,b,c){return b?b:c?c:a?1:0}function pe(a,b){return a&&0<a.length?a:b&&0<b.length?b:[]}function qe(a){a=null==a?void 0:E(a,9);return void 0===a?!1:[61,51,52].includes(a)};function re(a,b){if(!b||0>=b)return{j:0,B:2};var c=null==a?void 0:J(a,ie,18),d,f=null==a?void 0:null==(d=W(a))?void 0:J(d,ie,11);d=null==a?void 0:J(a,fe,7);if(!c&&!f){var e;f=null==a?void 0:null==(e=W(a))?void 0:J(e,fe,4);e=X(!0,null==d?void 0:S(d,1),null==f?void 0:S(f,1));a=X(!1,null==d?void 0:S(d,2),null==f?void 0:S(f,2));c=X(!1,null==d?void 0:S(d,3),null==f?void 0:S(f,3));var h,g;d=X(!1,null==d?void 0:null==(h=J(d,ge,5))?void 0:S(h,1),null==f?void 0:null==(g=J(f,ge,5))?void 0:S(g,1));h=new fe;
+g=G(h,1,e,0);g=G(g,2,a,0);g=G(g,3,c,0);f=he(d);M(g,5,f);return{j:b*e*(1-1/(1+Math.exp(-a*(Math.log(b/1E6)-d-c)))),B:1,J:4,ba:h}}if(c||f){e=X(!1,null==c?void 0:S(c,2),null==f?void 0:S(f,2));a=pe(null==c?void 0:B(c,3,Number),null==f?void 0:B(f,3,Number));c=pe(null==c?void 0:yb(c,1),null==f?void 0:yb(f,1));h=[];g=p(c);for(f=g.next();!f.done;f=g.next())switch(f.value){case 1:h.push(1E-6*b);break;case 2:var k=f=void 0,l=null==(f=d)?void 0:null==(k=J(f,ge,5))?void 0:S(k,1);h.push("number"===typeof l?Math.exp(l):
+0)}d=se(a,h);1===d.B?(b=new ie,b=zb(b,3,a),b=zb(b,1,c),b=G(b,2,e,0),d.ba=b,b=d):b=0>=e||1<e?d:{j:b*e,B:d.B,J:9}}else b={j:0,B:3};return b}
+function se(a,b){if(0===a.length||0>a[0])return{j:0,B:5};var c=b.length;if(a.length!==1+2*(1+c))return{j:0,B:6};for(var d=c+2,f=a[1],e=a[d],h=0;h<c;h++){var g=1+h;if(0>=b[h]){if(1E-9>Math.abs(a[1+g])&&1E-9>Math.abs(a[d+g]))continue;return{j:0,B:4}}var k=Math.log(b[h]);f+=a[1+g]*k;e+=a[d+g]*k}return{j:1E9*Math.exp(-.5*(-(f+e)+Math.sqrt((f-e)*(f-e)+4*a[0]))),B:1,J:8}};function te(a,b){var c=null==a?void 0:J(a,fe,6),d,f=null==a?void 0:null==(d=W(a))?void 0:J(d,fe,3);if(!b||0>=b)return{j:0,J:1};if(1===(null==a?void 0:T(a,17)))return{j:b,J:2};var e;if(!(null==a?0:null==(e=W(a))?0:R(e,2)))return{j:.85*b,J:2};d=X(!0,null==c?void 0:S(c,4),null==f?void 0:S(f,4));e=X(!0,null==c?void 0:S(c,1),null==f?void 0:S(f,1));var h=X(!1,null==c?void 0:S(c,2),null==f?void 0:S(f,2)),g=X(!1,null==c?void 0:S(c,3),null==f?void 0:S(f,3)),k,l,n=X(!1,null==c?void 0:null==(k=J(c,ge,5))?void 0:
+S(k,1),null==f?void 0:null==(l=J(f,ge,5))?void 0:S(l,1));l=X(!1,null==c?void 0:S(c,9),null==f?void 0:S(f,9));k=new fe;var t=G(k,1,e,0);t=G(t,2,h,0);t=G(t,3,g,0);t=G(t,4,d,0);t=G(t,9,l,0);var I=he(n);M(t,5,I);t=3;d=d*b*e*(1-1/(1+Math.exp(-h*(Math.log(d*b/1E6)-n-g))));d<l*b&&(d=l*b,t=6);e=1E6*(null==a?NaN:S(a,8));var A;c=null!=(A=null==c?void 0:S(c,8))?A:0;var F;if((null==a?0:null==(F=W(a))?0:R(F,6))&&d<e&&e<b){var O;a=null!=(O=null==f?void 0:S(f,7))?O:0;d=e+a*(0===c?Math.log(b/e):(b-e)/(c-e))*1E6;
+t=7}return{j:d,J:t,ba:k}};function ue(a,b){if(!(0<B(a,2,Number).length&&B(a,2,Number).length===yb(a,3).length&&B(a,2,Number).length===B(a,4,Number).length))return 0;for(var c=0,d=0,f=1,e=p(yb(a,3)),h=e.next();!h.done;h=e.next()){var g=0;switch(h.value){case 1:g=B(a,2,Number)[d]*(b.M?Math.pow(b.M,B(a,4,Number)[d]):0);break;case 2:c=g=B(a,2,Number)[d]*(b.X?Math.pow(b.X,B(a,4,Number)[d]):0);break;case 3:g=B(a,2,Number)[d]}if(0===g)return 0;f*=g;d+=1}0<S(a,7)&&(f=Math.min(f,S(a,7)*c*1E3));return 1E6*f}
+function ve(a,b){var c=0;b&&(0<K(b,nc,7).length?c=ue(K(b,nc,7)[0],a):0<K(b,nc,8).length&&(c=ue(K(b,nc,8)[0],a)));return c};var we={Ga:0,Ca:1,xa:2,za:3,ya:4,Da:5,Ea:6,Ha:7,Ba:8,va:9,Fa:10,Aa:11,wa:12};function xe(a,b,c){if(E(a,2)!==E(b,2))return c;var d=!1;switch(E(a,2)){case 1:a:{var f,e=new Set(null!=(f=yb(a,3))?f:[]);b=p(yb(b,3));for(f=b.next();!f.done;f=b.next())if(e.has(f.value)){d=!0;break a}d=!1}break;case 0:a:{f=new Set(null!=(e=B(a,4,lb,!1))?e:[]);b=p(B(b,4,lb,!1));for(e=b.next();!e.done;e=b.next())if(f.has(e.value)){d=!0;break a}d=!1}break;case 2:b=new ye(b);d=(f=J(a,yc,5))?ze(b,f):!1;break;case 3:a:{f=new Set;e=p(B(a,9,Ub));for(d=e.next();!d.done;d=e.next())f.add(Ta(d.value));if(0===
+f.size)d=!0;else{b=p(B(b,6,Ub));for(e=b.next();!e.done;e=b.next())if(f.has(Ta(e.value))){d=!0;break a}d=!1}}break;case 4:d=Ae(a,b)}return R(a,6)?d?null:c:d?c:null}
+function Ae(a,b){a=J(a,uc,10);if(void 0===a)return!1;var c=K(b,wc,7);if(0===c.length)return!1;b=!0;c=p(c);for(var d=c.next();!d.done;d=c.next())if(d=d.value,T(d,1)===T(a,1)&&T(d,2)===T(a,2)){b&&(b=!1);var f=yb(d,3);if(0===f.length)return!1;d=!0;f=p(f);for(var e=f.next();!e.done;e=f.next()){e=e.value;var h=Math.floor(e/32),g=yb(a,3);if(!(h>=g.length||0!==(g[h]>>>e%32)%2)){d=!1;break}}if(d)return!0}return b}
+function ze(a,b){var c=E(b,1),d=K(b,tc,3),f=K(b,yc,2);switch(c){case 2:c=d.every(function(e){return Be(a,e)})&&f.every(function(e){return ze(a,e)});break;case 1:c=d.some(function(e){return Be(a,e)})||f.some(function(e){return ze(a,e)});break;default:throw Error("unexpected value "+c+"!");}return R(b,4)?!c:c}function ye(a){this.h=new Map;a=p(K(a,tc,5));for(var b=a.next();!b.done;b=a.next()){var c=b.value;b=P(x(c,1),0);c=P(x(c,2),0);var d=this.h.get(b);d||(d=new Set,this.h.set(b,d));d.add(c)}}
+function Be(a,b){var c=P(x(b,2),0);return(a=a.h.get(P(x(b,1),0)))?a.has(c):!1};function Ce(a,b){a=p((null==b?void 0:b.get(a))||[]);for(b=a.next();!b.done;b=a.next())if(b=b.value,b.count+1>b.ra)return!1;return!0};function De(a){a=a.split("!");for(var b="-1",c=0;c<a.length;c++)a[c].match(/^\dm\d+/)?c+=+a[c].substring(2):a[c].match(/^\d{2}m\d+/)?c+=+a[c].substring(3):a[c].match(/^1j\d+/)&&(b=a[c].substring(2));return b}
+function Ee(a,b){if(!a.pa)return 1;var c,d=null==(c=a.da)?void 0:c.some(function(h){var g;return null==(g=b.ga)?void 0:g.includes(h,0)}),f;c=null!=(f=b.interestGroupName)?f:void 0;if(void 0===c)return 10;f=De(c);if("-1"===f)return 11;var e;a=null==(e=a.da)?void 0:e.includes(f,0);return d&&!a?10:1};function Fe(a,b){return null==a.Z?!0:!a.Z.some(function(c){var d;return null==(d=b.ga)?void 0:d.includes(c,0)})};function Ge(a,b){return He(0,(null==a?void 0:K(a,pc,1))||[],(null==b?void 0:K(b,pc,1))||[])}function Ie(a,b){return He(1,(null==a?void 0:K(a,pc,2))||[],(null==b?void 0:K(b,pc,3))||[])}function Je(a,b){return He(1,(null==a?void 0:K(a,pc,3))||[],(null==b?void 0:K(b,pc,3))||[])}
+function He(a,b,c){var d=0,f=new Map;b=p(b);for(var e=b.next();!e.done;e=b.next())d=e.value,f.set(P(x(d,1),""),d),d=S(d,3);b=null;c=p(c);for(e=c.next();!e.done;e=c.next()){var h=e.value;d=S(h,3);if(e=f.get(P(x(h,1),""))){a:{b=a;e=B(e,2,Number);h=B(h,2,Number);if(e.length===h.length){for(var g=0,k=0;k<e.length;k++)g+=e[k]*h[k];e=g}else e=void 0;if(void 0!==e)switch(b){case 0:b=1/(1+Math.exp(-1*e));break a;case 1:b=Math.exp(e);break a}b=void 0}if(void 0!==b)return b;b=d}}var l;return null!=(l=b)?l:
+d};function Ke(a,b,c){"0"===a||c.has(a)||c.set(a,b.filter(function(d){return 0<T(d,3)}).map(function(d){var f=T(d,3);switch(E(d,1)){case 6:d=60*T(d,2);break;case 1:d=3600*T(d,2);break;case 2:d=86400*T(d,2);break;case 3:d=604800*T(d,2);break;case 4:d=2592E3*T(d,2);break;case 5:d=null;break;default:f=d=0}return{fa:d,ra:f,count:0}}))}function Le(a,b,c){if(b=c.get(b))for(b=p(b),c=b.next();!c.done;c=b.next())c=c.value,(null===c.fa||T(a,1)<=c.fa)&&c.count++};function Me(a){V.call(this,a,-1,Ne)}q(Me,V);var Ne=[1];var Oe={ad:{},bid:0,render:"",allowComponentAuction:!0};function Pe(){new Me;return function(a,b,c,d,f){return Qe(a,c,d,f)}}
+function Qe(a,b,c,d){b=b?new $d(gc(b)):void 0;var f,e;if(!b||!(K(b,ee,2).length||(null==(f=W(b))?0:R(f,1))||(null==(e=W(b))?0:R(e,5))))return Oe;f=new Jc(gc(a.userBiddingSignals));e=a.ads.map(function(g){return{renderUrl:g.renderUrl,metadata:new jc(gc(g.metadata))}});c=c[a.name]?new Pc(gc(c[a.name])):void 0;var h=d.prevWins.map(function(g){var k=new kc;k=G(k,1,g[0],0);g=new jc(gc(g[1].metadata));return M(k,2,g)});return Re(a.name,f,e,d,h,c,b)}
+function Re(a,b,c,d,f,e,h){var g=null,k=null,l=null;if(h&&(R(H(h,Zd,12),1)||R(H(h,Zd,12),2))){var n=new le;var t=G(n,2,0,0);var I=G(t,5,a,"");var A=G(I,7,!1,!1);var F=M(A,8,h);g=G(F,9,P(x(h,4),""),"");var O=globalThis.forDebuggingOnly;R(H(h,Zd,12),1)&&(k=new ic(O.reportAdAuctionWin));R(H(h,Zd,12),2)&&(l=new ic(O.reportAdAuctionLoss));if(R(H(h,Zd,12),5)){var Wa=M(g,6,b),ea=new lc;var Xa=G(ea,1,d.topWindowHostname,"");var Y=G(Xa,2,d.seller,"");var Q=G(Y,3,d.topLevelSeller,"");var We=G(Q,4,d.joinCount,
+0);var Xe=G(We,5,d.bidCount,0);var Ye=$b(Xe,6,f);var Ze=G(Ye,7,d.dataVersion,0);M(Wa,11,Ze);e&&M(g,3,e)}}var Ya=g,Rc,D={ea:null!=(Rc=null==h?void 0:W(h))?Rc:void 0,T:new Map,N:new Map,O:new Map,P:new Map,W:new Map,interestGroupName:null!=a?a:void 0,ua:E(b,2),ia:d.joinCount},ka=new Map;if(e){for(var Sc=p(K(e,Nc,1)),Ab=Sc.next();!Ab.done;Ab=Sc.next()){var L=Ab.value,$e=Se(N(L,1),N(L,2),N(L,3));ka.set($e,L);Ke(N(L,2),K(L,Lc,9),D.T);Ke(N(L,1),K(L,Lc,10),D.N);Ke(N(L,6),K(L,Lc,11),D.O);Ke(N(L,7),K(L,Lc,
+12),D.P);Ke(N(L,8),K(L,Lc,13),D.W)}for(var Tc=p(f),Bb=Tc.next();!Bb.done;Bb=Tc.next()){var Z=Bb.value;D.T&&Le(Z,N(H(Z,jc,2),2),D.T);D.N&&Le(Z,N(H(Z,jc,2),1),D.N);D.O&&Le(Z,N(H(Z,jc,2),4),D.O);D.P&&Le(Z,N(H(Z,jc,2),5),D.P);D.W&&Le(Z,N(H(Z,jc,2),6),D.W)}}var Uc=new Map;if(h)for(var Vc=p(K(h,ee,2)),Cb=Vc.next();!Cb.done;Cb=Vc.next()){var Db=Cb.value,af=Se(N(Db,1),N(Db,2),"");Uc.set(af,S(Db,3))}for(var Wc=[],Xc=p(c),Eb=Xc.next();!Eb.done;Eb=Xc.next()){var U=Eb.value,z={renderUrl:U.renderUrl,D:N(U.metadata,
+1),G:N(U.metadata,2),L:N(U.metadata,3),S:N(U.metadata,4),R:N(U.metadata,5),U:N(U.metadata,6),ja:N(U.metadata,7),ka:N(U.metadata,8),la:N(U.metadata,9),ma:N(U.metadata,10),l:0,I:0},pa=Se(z.D,z.G,z.L);z.K=Uc.get(Se(z.D,z.G,""));if(!z.K){var Yc=void 0,Zc=void 0,$c=void 0,ad=void 0;if(!(null==(Yc=h)?0:null==(Zc=W(Yc))?0:R(Zc,1))&&!(null==($c=h)?0:null==(ad=W($c))?0:R(ad,5))){Ya&&Te(Ya,z,5,1);continue}else if(!ka.get(pa)){Ya&&Te(Ya,z,6,1);continue}var bd=void 0,cd=void 0;z.v=null!=(cd=null==(bd=ka.get(pa))?
+void 0:J(bd,rc,4))?cd:void 0}var dd=void 0,ed=void 0;z.F=null!=(ed=null==(dd=ka.get(pa))?void 0:J(dd,Ac,5))?ed:void 0;var fd=void 0,gd=void 0,hd=null!=(gd=null==(fd=ka.get(pa))?void 0:K(fd,Mc,17))?gd:void 0;if(hd)for(var id=p(hd),Fb=id.next();!Fb.done;Fb=id.next()){var Ja=Fb.value;if(z.ja===N(Ja,1)&&z.ka===N(Ja,2)&&z.la===N(Ja,3)&&z.ma===N(Ja,4)){z.F||(z.F=new Ac);var Gb=J(Ja,Ac,5);if(Gb){for(var jd=p(K(Gb,Cc,1)),Hb=jd.next();!Hb.done;Hb=jd.next())ac(z.F,1,Cc,Hb.value);for(var kd=p(K(Gb,Ec,2)),Ib=
+kd.next();!Ib.done;Ib=kd.next())ac(z.F,2,Ec,Ib.value)}}}var ld=void 0,md=void 0;z.Z=null!=(md=null==(ld=ka.get(pa))?void 0:B(ld,14,kb))?md:void 0;var nd=void 0,od=void 0;z.da=null!=(od=null==(nd=ka.get(pa))?void 0:B(nd,15,kb))?od:void 0;var pd=void 0,qd=void 0;z.pa=null!=(qd=null==(pd=ka.get(pa))?void 0:R(pd,16))?qd:!1;Wc.push(z)}var Jb=null==h?void 0:J(h,Ac,5);if(Jb){for(var rd=new Map,sd=new Map,td=p(K(Jb,Cc,1)),Kb=td.next();!Kb.done;Kb=td.next()){var ud=Kb.value;rd.set(E(ud,1),ud)}for(var vd=p(K(Jb,
+Ec,2)),Lb=vd.next();!Lb.done;Lb=vd.next()){var wd=Lb.value;sd.set(E(wd,1),wd)}D.na=rd;D.oa=sd}var xd;D.ga=null!=(xd=B(b,1,kb))?xd:void 0;var ha={ads:Wc,signals:D};var Za=ha.signals;if(1!==Za.ua)var Mb=!0;else{var yd;if(null!=(yd=Za.ea)&&R(yd,8)){var zd;Mb=Za.ia<(null==(zd=Za.ea)?void 0:T(zd,9))?!1:!0}else Mb=!1}var Nb=Mb?1:8;if(1!==Nb&&null!==g)for(var Ad=p(ha.ads),Ob=Ad.next();!Ob.done;Ob=Ad.next())Te(g,Ob.value,Nb,2);var Bd=Oe,Cd=ha;if(1===Nb){for(var aa=g,Dd=null==h?void 0:W(h),Ed=[],Fd=new Map,
+Gd=p(ha.ads),Pb=Gd.next();!Pb.done;Pb=Gd.next()){var C=Pb.value,Hd=void 0;if(void 0!==Dd&&(null==(Hd=Dd)?0:R(Hd,10))&&(C.renderUrl.includes("/td/adfetch/dv3")||qe(C.v)))aa&&Te(aa,C,12,3);else{var Ka=ha.signals;if(!Ce(C.G,Ka.T)||!Ce(C.D,Ka.N)||C.S&&!Ce(C.S,Ka.O)||C.R&&!Ce(C.R,Ka.P)||C.U&&!Ce(C.U,Ka.W))aa&&Te(aa,C,3,3);else{if(C.F){var Id=ha.signals,Jd=Id.na,Kd=Id.oa,Ld=C.F;if(Jd&&Kd&&Ld)a:{for(var bf=Jd,cf=Kd,Md=Ld,Nd=p(K(Md,Cc,1)),Qb=Nd.next();!Qb.done;Qb=Nd.next()){var La=Qb.value,Ma=E(La,1),Rb=
+cf.get(Ma);if(Rb){var Na=null;if(R(La,7)){var Oa=Fd.get(Ma),Od=!0,Pd=P(x(La,8),0);if(void 0===Oa)Oa=new Map,Fd.set(Ma,Oa);else{var Sb=Oa.get(Pd);if(Sb){var Pa=Sb;break a}null===Sb&&(Od=!1)}Od&&(Na=xe(La,Rb,Ma),Oa.set(Pd,Na))}else Na=xe(La,Rb,Ma);if(Na){Pa=Na;break a}}}for(var Qd=p(K(Md,Ec,2)),Tb=Qd.next();!Tb.done;Tb=Qd.next()){var Rd=Tb.value,Sd=E(Rd,1),Td=bf.get(Sd);if(Td){var Ud=xe(Td,Rd,Sd);if(Ud){Pa=Ud;break a}}}Pa=null}else Pa=null;var Vd=Pa;if(Vd){aa&&Te(aa,C,4,3,Vd);continue}}if(Fe(C,ha.signals)){var Wd=
+Ee(C,ha.signals);1!==Wd?aa&&Te(aa,C,Wd,3):Ed.push(C)}else aa&&Te(aa,C,7,3)}}}Cd={ads:Ed,signals:ha.signals};Bd=Ue(Cd,g,h).ha}if(void 0!==h&&null!==g){if(k){var Xd=g,ef=k;G(Xd,2,1,0);ne(ef,Xd)}if(l){var Yd=g,ff=l;G(Yd,2,2,0);ne(ff,Yd)}}return Bd}
+function Te(a,b,c,d,f){a=ac(a,10,Ic);d=G(a,3,d,0);var e;var h=new Gc;h=G(h,1,b.renderUrl,"");h=G(h,2,b.D,"");h=G(h,3,b.G,"");h=G(h,4,b.L,"");h=G(h,12,b.l,0);h=G(h,13,b.I,0);var g=null!=(e=b.qa)?e:!1;e=G(h,16,g,!1);void 0!==b.S&&G(e,5,b.S,"");void 0!==b.R&&G(e,6,b.R,"");void 0!==b.U&&G(e,7,b.U,"");void 0!==b.v&&M(e,8,b.v);void 0!==b.X&&G(e,9,b.X,0);void 0!==b.M&&G(e,10,b.M,0);void 0!==b.K&&G(e,11,b.K,0);void 0!==b.F&&M(e,14,b.F);var k;b=p(null!=(k=b.Z)?k:[]);for(h=b.next();!h.done;h=b.next())k=e,h=
+h.value,ib(k),xb(k,15,2,!1,!1).push(h);M(d,1,e);void 0!==f&&G(a,5,f,0);Object.values(we).includes(c)&&G(a,2,c,0)}
+function Ue(a,b,c){for(var d=[],f=[],e=p(a.ads),h=e.next();!h.done;h=e.next())if(h=h.value,null!=h.K)h.l=h.K,h.qa=!0,d.push(h);else{var g=void 0,k=void 0;if(null==(g=c)?0:null==(k=W(g))?0:R(k,1))qe(h.v)?(g=void 0,h.M=Je(null==(g=c)?void 0:J(g,ce,1),h.v)):(g=void 0,h.X=Ge(null==(g=c)?void 0:J(g,ce,1),h.v),g=void 0,h.M=Ie(null==(g=c)?void 0:J(g,ce,1),h.v)),h.l=ve(h,h.v),h.l||(k=g=void 0,h.l=null!=(k=null==(g=h.v)?void 0:S(g,6))?k:0);else if(k=g=void 0,null==(g=c)?0:null==(k=W(g))?0:R(k,5))k=g=void 0,
+h.l=null!=(k=null==(g=h.v)?void 0:S(g,6))?k:0;k=g=void 0;if(null==(g=c)?0:null==(k=W(g))?0:R(k,7)){var l=k=g=void 0,n=void 0,t=void 0,I=void 0;if(h.D===(null==(g=c)?void 0:null==(k=J(g,ke,16))?void 0:N(k,2))&&h.G===(null==(l=c)?void 0:null==(n=J(l,ke,16))?void 0:N(n,3))&&a.signals.interestGroupName===(null==(t=c)?void 0:null==(I=J(t,ke,16))?void 0:P(x(I,1),""))){if(k=g=void 0,null==(g=c)?0:null==(k=J(g,ke,16))?0:R(k,7))l=k=g=void 0,n=null!=(l=null==(g=c)?void 0:null==(k=J(g,ke,16))?void 0:S(k,5))?
+l:0,h.l=0===n?1:n}else h.l=0}f.push(h)}d={renderUrl:"",D:"",G:"",L:"",l:0,I:0};var A,F;if((null==c?0:null==(A=W(c))?0:R(A,7))&&(null==c?0:null==(F=J(c,ke,16))?0:R(F,7)))c=a.ads.reduce(function(Y,Q){return Y.l<Q.l?Q:Y},d),c.I=c.l;else{A=a.ads.reduce(function(Y,Q){return!qe(Q.v)&&Y.l<Q.l?Q:Y},d);d=a.ads.reduce(function(Y,Q){return qe(Q.v)&&Y.l<Q.l?Q:Y},d);F=te(c,null==A?void 0:A.l);var O,Wa;F.j=oe(F.j,null==c?void 0:null==(O=W(c))?void 0:null==(Wa=J(O,fe,3))?void 0:S(Wa,6));O=re(c,null==d?void 0:d.l);
+var ea,Xa;O.j=oe(O.j,null==c?void 0:null==(ea=W(c))?void 0:null==(Xa=J(ea,fe,4))?void 0:S(Xa,6));F.j>O.j?(c=A,c.I=F.j):(c=d,c.I=O.j)}if(b)for(a=p(a.ads),h=a.next();!h.done;h=a.next())ea=h.value,Te(b,ea,Se(ea.D,ea.G,ea.L)===Se(c.D,c.G,c.L)?1:9,4);return{ha:{ad:{},bid:c.I/1E6,render:c.renderUrl,allowComponentAuction:!0},debugInfo:void 0}}function Se(a,b,c){return a.concat("+",b,"+",c)};function Ve(a,b,c,d,f){a={renderUrl:a.render_uri,metadata:a.metadata.metadata};f={owner:f.owner,name:f.name,biddingLogicUrl:"",userBiddingSignals:f.user_bidding_signals.signals,ads:[a]};a={topWindowHostname:"",seller:"",joinCount:0,bidCount:0,prevWins:[[0,a]]};b=Pe()(f,b,c.signals,d,a);c="";"string"===typeof b.render?c=b.render:(d=b.render,0<d.length&&(c=d[0]));return{status:0,ad:{render_uri:c,metadata:b.ad},bid:b.bid}};var df=globalThis;df.generateBid=function(a,b,c,d,f,e){return Ve(a,b,c,d,e)};df.generateBidIterative=function(a,b,c,d,f,e){f=(new Date).getTime();var h=[];a=p(a);for(var g=a.next();!g.done;g=a.next())g=Ve(g.value,b,c,d,e),h.push(g);return{responses:h,status:0,duration:(new Date).getTime()-f}};
diff --git a/apct-tests/perftests/rubidium/assets/rubidium_scoring_logic_compiled.js b/apct-tests/perftests/rubidium/assets/rubidium_scoring_logic_compiled.js
new file mode 100644
index 0000000..406a45d
--- /dev/null
+++ b/apct-tests/perftests/rubidium/assets/rubidium_scoring_logic_compiled.js
@@ -0,0 +1,59 @@
+/*
+
+ Copyright The Closure Library Authors.
+ SPDX-License-Identifier: Apache-2.0
+*/
+'use strict';function aa(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}}var ba="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a};
+function ca(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");}var ea=ca(this);function n(a,b){if(b)a:{var c=ea;a=a.split(".");for(var d=0;d<a.length-1;d++){var f=a[d];if(!(f in c))break a;c=c[f]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&ba(c,a,{configurable:!0,writable:!0,value:b})}}
+n("Symbol",function(a){function b(e){if(this instanceof b)throw new TypeError("Symbol is not a constructor");return new c(d+(e||"")+"_"+f++,e)}function c(e,g){this.h=e;ba(this,"description",{configurable:!0,writable:!0,value:g})}if(a)return a;c.prototype.toString=function(){return this.h};var d="jscomp_symbol_"+(1E9*Math.random()>>>0)+"_",f=0;return b});
+n("Symbol.iterator",function(a){if(a)return a;a=Symbol("Symbol.iterator");for(var b="Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "),c=0;c<b.length;c++){var d=ea[b[c]];"function"===typeof d&&"function"!=typeof d.prototype[a]&&ba(d.prototype,a,{configurable:!0,writable:!0,value:function(){return fa(aa(this))}})}return a});function fa(a){a={next:a};a[Symbol.iterator]=function(){return this};return a}
+function p(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):{next:aa(a)}}function ha(a){if(!(a instanceof Array)){a=p(a);for(var b,c=[];!(b=a.next()).done;)c.push(b.value);a=c}return a}var ia="function"==typeof Object.create?Object.create:function(a){function b(){}b.prototype=a;return new b},ja;
+if("function"==typeof Object.setPrototypeOf)ja=Object.setPrototypeOf;else{var ka;a:{var la={a:!0},ma={};try{ma.__proto__=la;ka=ma.a;break a}catch(a){}ka=!1}ja=ka?function(a,b){a.__proto__=b;if(a.__proto__!==b)throw new TypeError(a+" is not extensible");return a}:null}var na=ja;
+function r(a,b){a.prototype=ia(b.prototype);a.prototype.constructor=a;if(na)na(a,b);else for(var c in b)if("prototype"!=c)if(Object.defineProperties){var d=Object.getOwnPropertyDescriptor(b,c);d&&Object.defineProperty(a,c,d)}else a[c]=b[c];a.la=b.prototype}function oa(){for(var a=Number(this),b=[],c=a;c<arguments.length;c++)b[c-a]=arguments[c];return b}function t(a,b){return Object.prototype.hasOwnProperty.call(a,b)}
+n("WeakMap",function(a){function b(k){this.h=(h+=Math.random()+1).toString();if(k){k=p(k);for(var l;!(l=k.next()).done;)l=l.value,this.set(l[0],l[1])}}function c(){}function d(k){var l=typeof k;return"object"===l&&null!==k||"function"===l}function f(k){if(!t(k,g)){var l=new c;ba(k,g,{value:l})}}function e(k){var l=Object[k];l&&(Object[k]=function(m){if(m instanceof c)return m;Object.isExtensible(m)&&f(m);return l(m)})}if(function(){if(!a||!Object.seal)return!1;try{var k=Object.seal({}),l=Object.seal({}),
+m=new a([[k,2],[l,3]]);if(2!=m.get(k)||3!=m.get(l))return!1;m.delete(k);m.set(l,4);return!m.has(k)&&4==m.get(l)}catch(q){return!1}}())return a;var g="$jscomp_hidden_"+Math.random();e("freeze");e("preventExtensions");e("seal");var h=0;b.prototype.set=function(k,l){if(!d(k))throw Error("Invalid WeakMap key");f(k);if(!t(k,g))throw Error("WeakMap key fail: "+k);k[g][this.h]=l;return this};b.prototype.get=function(k){return d(k)&&t(k,g)?k[g][this.h]:void 0};b.prototype.has=function(k){return d(k)&&t(k,
+g)&&t(k[g],this.h)};b.prototype.delete=function(k){return d(k)&&t(k,g)&&t(k[g],this.h)?delete k[g][this.h]:!1};return b});
+n("Map",function(a){function b(){var h={};return h.v=h.next=h.head=h}function c(h,k){var l=h.h;return fa(function(){if(l){for(;l.head!=h.h;)l=l.v;for(;l.next!=l.head;)return l=l.next,{done:!1,value:k(l)};l=null}return{done:!0,value:void 0}})}function d(h,k){var l=k&&typeof k;"object"==l||"function"==l?e.has(k)?l=e.get(k):(l=""+ ++g,e.set(k,l)):l="p_"+k;var m=h.i[l];if(m&&t(h.i,l))for(h=0;h<m.length;h++){var q=m[h];if(k!==k&&q.key!==q.key||k===q.key)return{id:l,list:m,index:h,l:q}}return{id:l,list:m,
+index:-1,l:void 0}}function f(h){this.i={};this.h=b();this.size=0;if(h){h=p(h);for(var k;!(k=h.next()).done;)k=k.value,this.set(k[0],k[1])}}if(function(){if(!a||"function"!=typeof a||!a.prototype.entries||"function"!=typeof Object.seal)return!1;try{var h=Object.seal({x:4}),k=new a(p([[h,"s"]]));if("s"!=k.get(h)||1!=k.size||k.get({x:4})||k.set({x:4},"t")!=k||2!=k.size)return!1;var l=k.entries(),m=l.next();if(m.done||m.value[0]!=h||"s"!=m.value[1])return!1;m=l.next();return m.done||4!=m.value[0].x||
+"t"!=m.value[1]||!l.next().done?!1:!0}catch(q){return!1}}())return a;var e=new WeakMap;f.prototype.set=function(h,k){h=0===h?0:h;var l=d(this,h);l.list||(l.list=this.i[l.id]=[]);l.l?l.l.value=k:(l.l={next:this.h,v:this.h.v,head:this.h,key:h,value:k},l.list.push(l.l),this.h.v.next=l.l,this.h.v=l.l,this.size++);return this};f.prototype.delete=function(h){h=d(this,h);return h.l&&h.list?(h.list.splice(h.index,1),h.list.length||delete this.i[h.id],h.l.v.next=h.l.next,h.l.next.v=h.l.v,h.l.head=null,this.size--,
+!0):!1};f.prototype.clear=function(){this.i={};this.h=this.h.v=b();this.size=0};f.prototype.has=function(h){return!!d(this,h).l};f.prototype.get=function(h){return(h=d(this,h).l)&&h.value};f.prototype.entries=function(){return c(this,function(h){return[h.key,h.value]})};f.prototype.keys=function(){return c(this,function(h){return h.key})};f.prototype.values=function(){return c(this,function(h){return h.value})};f.prototype.forEach=function(h,k){for(var l=this.entries(),m;!(m=l.next()).done;)m=m.value,
+h.call(k,m[1],m[0],this)};f.prototype[Symbol.iterator]=f.prototype.entries;var g=0;return f});n("Number.isNaN",function(a){return a?a:function(b){return"number"===typeof b&&isNaN(b)}});function pa(a,b){a instanceof String&&(a+="");var c=0,d=!1,f={next:function(){if(!d&&c<a.length){var e=c++;return{value:b(e,a[e]),done:!1}}d=!0;return{done:!0,value:void 0}}};f[Symbol.iterator]=function(){return f};return f}n("Array.prototype.values",function(a){return a?a:function(){return pa(this,function(b,c){return c})}});
+n("Set",function(a){function b(c){this.h=new Map;if(c){c=p(c);for(var d;!(d=c.next()).done;)this.add(d.value)}this.size=this.h.size}if(function(){if(!a||"function"!=typeof a||!a.prototype.entries||"function"!=typeof Object.seal)return!1;try{var c=Object.seal({x:4}),d=new a(p([c]));if(!d.has(c)||1!=d.size||d.add(c)!=d||1!=d.size||d.add({x:4})!=d||2!=d.size)return!1;var f=d.entries(),e=f.next();if(e.done||e.value[0]!=c||e.value[1]!=c)return!1;e=f.next();return e.done||e.value[0]==c||4!=e.value[0].x||
+e.value[1]!=e.value[0]?!1:f.next().done}catch(g){return!1}}())return a;b.prototype.add=function(c){c=0===c?0:c;this.h.set(c,c);this.size=this.h.size;return this};b.prototype.delete=function(c){c=this.h.delete(c);this.size=this.h.size;return c};b.prototype.clear=function(){this.h.clear();this.size=0};b.prototype.has=function(c){return this.h.has(c)};b.prototype.entries=function(){return this.h.entries()};b.prototype.values=function(){return this.h.values()};b.prototype.keys=b.prototype.values;b.prototype[Symbol.iterator]=
+b.prototype.values;b.prototype.forEach=function(c,d){var f=this;this.h.forEach(function(e){return c.call(d,e,e,f)})};return b});n("globalThis",function(a){return a||ea});n("Object.values",function(a){return a?a:function(b){var c=[],d;for(d in b)t(b,d)&&c.push(b[d]);return c}});n("Object.is",function(a){return a?a:function(b,c){return b===c?0!==b||1/b===1/c:b!==b&&c!==c}});
+n("Array.prototype.includes",function(a){return a?a:function(b,c){var d=this;d instanceof String&&(d=String(d));var f=d.length;c=c||0;for(0>c&&(c=Math.max(c+f,0));c<f;c++){var e=d[c];if(e===b||Object.is(e,b))return!0}return!1}});
+n("String.prototype.includes",function(a){return a?a:function(b,c){if(null==this)throw new TypeError("The 'this' value for String.prototype.includes must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype.includes must not be a regular expression");return-1!==this.indexOf(b,c||0)}});function qa(a){var b=typeof a;return"object"==b&&null!=a||"function"==b};var ra={};var sa=Array.prototype.map?function(a,b){return Array.prototype.map.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=Array(c),f="string"===typeof a?a.split(""):a,e=0;e<c;e++)e in f&&(d[e]=b.call(void 0,f[e],e,a));return d};var ta={},u=null;function ua(a,b){void 0===b&&(b=0);va();b=ta[b];for(var c=Array(Math.floor(a.length/3)),d=b[64]||"",f=0,e=0;f<a.length-2;f+=3){var g=a[f],h=a[f+1],k=a[f+2],l=b[g>>2];g=b[(g&3)<<4|h>>4];h=b[(h&15)<<2|k>>6];k=b[k&63];c[e++]=l+g+h+k}l=0;k=d;switch(a.length-f){case 2:l=a[f+1],k=b[(l&15)<<2]||d;case 1:a=a[f],c[e]=b[a>>2]+b[(a&3)<<4|l>>4]+k+d}return c.join("")}
+function wa(a){var b=a.length,c=3*b/4;c%3?c=Math.floor(c):-1!="=.".indexOf(a[b-1])&&(c=-1!="=.".indexOf(a[b-2])?c-2:c-1);var d=new Uint8Array(c),f=0;xa(a,function(e){d[f++]=e});return f!==c?d.subarray(0,f):d}
+function xa(a,b){function c(k){for(;d<a.length;){var l=a.charAt(d++),m=u[l];if(null!=m)return m;if(!/^[\s\xa0]*$/.test(l))throw Error("Unknown base64 encoding at char: "+l);}return k}va();for(var d=0;;){var f=c(-1),e=c(0),g=c(64),h=c(64);if(64===h&&-1===f)break;b(f<<2|e>>4);64!=g&&(b(e<<4&240|g>>2),64!=h&&b(g<<6&192|h))}}
+function va(){if(!u){u={};for(var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),b=["+/=","+/","-_=","-_.","-_"],c=0;5>c;c++){var d=a.concat(b[c].split(""));ta[c]=d;for(var f=0;f<d.length;f++){var e=d[f];void 0===u[e]&&(u[e]=f)}}}};var ya="undefined"!==typeof Uint8Array;function za(a){return null==a||v(a)?a:"string"===typeof a?wa(a):null}function v(a){return ya&&null!=a&&a instanceof Uint8Array}var w={};var Aa;function Ba(a){if(a!==w)throw Error("illegal external caller");}function x(a,b){Ba(b);this.H=a;if(null!=a&&0===a.length)throw Error("ByteString should be constructed with non-empty values");}function Ca(){return Aa||(Aa=new x(null,w))}function Da(a){var b=a.H;return null==b?"":"string"===typeof b?b:a.H=ua(b)};var y="function"===typeof Symbol&&"symbol"===typeof Symbol()?Symbol():void 0;function z(a,b){if(y)return a[y]|=b;if(void 0!==a.u)return a.u|=b;Object.defineProperties(a,{u:{value:b,configurable:!0,writable:!0,enumerable:!1}});return b}function Ea(a,b){y?a[y]&&(a[y]&=~b):void 0!==a.u&&(a.u&=~b)}function A(a){var b;y?b=a[y]:b=a.u;return null==b?0:b}function B(a,b){y?a[y]=b:void 0!==a.u?a.u=b:Object.defineProperties(a,{u:{value:b,configurable:!0,writable:!0,enumerable:!1}})}
+function C(a){z(a,1);return a}function E(a){return!!(A(a)&2)}function Fa(a){z(a,16);return a}function Ga(a,b){B(b,(a|0)&-51)}function Ha(a,b){B(b,(a|18)&-41)};var F={};function H(a){return null!==a&&"object"===typeof a&&!Array.isArray(a)&&a.constructor===Object}var Ia;function Ja(a){a instanceof x&&(Ba(w),a=a.H||"");return a}var I,Ka=[];B(Ka,23);I=Object.freeze(Ka);function J(a){if(E(a.j))throw Error("Cannot mutate an immutable Message");}function La(a){var b=a.length;(b=b?a[b-1]:void 0)&&H(b)?b.g=1:(b={},a.push((b.g=1,b)))};function Ma(a){return a}function Na(a){return a};function Oa(a,b){a=a||{};b=b||{};var c={},d;for(d in a)c[d]=0;for(var f in b)c[f]=0;for(var e in c)if(!Pa(a[e],b[e]))return!1;return!0}function Qa(a){return a&&"object"===typeof a?a.j||a:a}
+function Pa(a,b){a=Ja(a);b=Ja(b);a=Qa(a);b=Qa(b);if(a==b)return!0;if(ya){var c=v(a),d=v(b);if(c||d){if(!c)if("string"===typeof a)a=za(a);else return!1;if(d)d=b;else if("string"===typeof b)d=za(b);else return!1;if(a.length!==d.length)return!1;for(b=0;b<a.length;b++)if(a[b]!==d[b])return!1;return!0}}if(null==a&&Array.isArray(b)&&A(b)&1&&!b.length||null==b&&Array.isArray(a)&&A(a)&1&&!a.length)return!0;if(!qa(a)||!qa(b))return"number"===typeof a&&isNaN(a)||"number"===typeof b&&isNaN(b)?String(a)==String(b):
+!1;if(a.constructor!=b.constructor)return!1;if(a.constructor===Array){d=a;c=a=void 0;for(var f=Math.max(d.length,b.length),e=0;e<f;e++){var g=d[e],h=b[e];g&&g.constructor==Object&&(a=g,g=void 0);h&&h.constructor==Object&&(c=h,h=void 0);if(!Pa(g,h))return!1}return a||c?(a=a||{},c=c||{},Oa(a,c)):!0}if(a.constructor===Object)return Oa(a,b);throw Error("Invalid type in JSPB array");};var K;function Ra(a){switch(typeof a){case "number":return isFinite(a)?a:String(a);case "object":if(a)if(Array.isArray(a)){if(0!==(A(a)&128))return a=Array.prototype.slice.call(a),La(a),a}else{if(v(a))return ua(a);if(a instanceof x)return Da(a)}}return a};function Sa(a,b,c,d){if(null!=a){if(Array.isArray(a))a=Ta(a,b,c,void 0!==d);else if(H(a)){var f={},e;for(e in a)f[e]=Sa(a[e],b,c,d);a=f}else a=b(a,d);return a}}function Ta(a,b,c,d){var f=A(a);d=d?!!(f&16):void 0;a=Array.prototype.slice.call(a);for(var e=0;e<a.length;e++)a[e]=Sa(a[e],b,c,d);c(f,a);return a}function Ua(a){return a.F===F?a.toJSON():Ra(a)}function Va(a){if(!a)return a;if("object"===typeof a){if(v(a))return new Uint8Array(a);if(a.F===F)return Wa(a)}return a}
+function Xa(a,b){a&128&&La(b)};function Ya(a){return a.h||(a.h=a.j[a.i+a.A]={})}function L(a,b,c){return-1===b?null:b>=a.i?a.h?a.h[b]:void 0:c&&a.h&&(c=a.h[b],null!=c)?c:a.j[b+a.A]}function M(a,b,c,d){a.o&&(a.o=void 0);if(b>=a.i||d)return Ya(a)[b]=c,a;a.j[b+a.A]=c;(c=a.h)&&b in c&&delete c[b];return a}
+function Za(a,b,c,d,f){var e=L(a,b,d);Array.isArray(e)||(e=I);var g=A(e);g&1||C(e);if(f)g&2||z(e,2),c&1||Object.freeze(e);else{f=!(c&2);var h=g&2;c&1||!h?f&&g&16&&!h&&Ea(e,16):(e=C(Array.prototype.slice.call(e)),M(a,b,e,d))}return e}function O(a,b){return Za(a,b,0,!1,E(a.j))}
+function P(a,b,c,d){var f=E(a.j),e=Za(a,b,1,d,f),g=A(e);if(!(g&4)){Object.isFrozen(e)&&(e=C(e.slice()),M(a,b,e,d));for(var h=0,k=0;h<e.length;h++){var l=c(e[h]);null!=l&&(e[k++]=l)}k<h&&(e.length=k);z(e,5);f&&(z(e,2),Object.freeze(e))}!f&&(g&2||Object.isFrozen(e))&&(e=Array.prototype.slice.call(e),z(e,5),c=e,null==c?c=I:(f=A(c),1!==(f&1)&&(Object.isFrozen(c)&&(c=Array.prototype.slice.call(c)),B(c,f|1))),J(a),M(a,b,c,d));return e}
+function $a(a){return null==a?a:"string"===typeof a?a?new x(a,w):Ca():a.constructor===x?a:v(a)?a.length?new x(new Uint8Array(a),w):Ca():void 0}function Q(a,b){a=L(a,b);return null==a?0:a}function R(a,b,c,d){J(a);c!==d?M(a,b,c):M(a,b,void 0,!1);return a}var ab=Symbol(void 0);function bb(a,b,c,d){var f=L(a,c,d);var e=!1;var g=null==f||"object"!==typeof f||(e=Array.isArray(f))||f.F!==F?e?new b(f):void 0:f;g!==f&&null!=g&&(M(a,c,g,d),z(g.j,A(a.j)&-33));return g}
+function S(a,b,c){if(a=bb(a,b,c,!1))b=a;else if(a=b[ab])b=a;else{a=new b;if(ra!==ra)throw Error("requires a valid immutable API token");E(a.j)||((c=a.o)&&Pa(c.j,a.j)?a=c:(c=cb(a,!0),z(c.j,2),a=a.o=c));b=b[ab]=a}return b}function T(a,b,c){var d=void 0===d?!1:d;b=bb(a,b,c,d);if(null==b)return b;if(!E(a.j)){var f=db(b);f!==b&&(b=f,M(a,c,b,d))}return b}
+function eb(a,b,c,d,f,e){a.m||(a.m={});var g=a.m[c],h=Za(a,c,3,d,e);if(g)e||(Object.isFrozen(g)?f||(g=Array.prototype.slice.call(g),a.m[c]=g):f&&Object.freeze(g));else{g=[];var k=!!(A(a.j)&16),l=E(h);!e&&l&&(h=C(Array.prototype.slice.call(h)),M(a,c,h,d));d=l;for(var m=0;m<h.length;m++){var q=h[m];var G=b;var D=k,N=!1;N=void 0===N?!1:N;D=void 0===D?!1:D;G=Array.isArray(q)?new G(D?Fa(q):q):N?new G:void 0;void 0!==G&&(d=d||E(q),g.push(G),l&&z(G.j,2))}a.m[c]=g;a=h;Object.isFrozen(a)||(b=A(a)|33,B(a,d?
+b&-9:b|8));(e||f&&l)&&z(g,2);(e||f)&&Object.freeze(g)}return g}function U(a,b,c,d){var f=E(a.j);b=eb(a,b,c,d,f,f);a=Za(a,c,3,d,f);if(!(f||A(a)&8)){for(f=0;f<b.length;f++)c=b[f],d=db(c),c!==d&&(b[f]=d,a[f]=b[f].j);z(a,8)}return b}function fb(a,b,c){J(a);null==c&&(c=void 0);return M(a,b,c)}function gb(a,b,c,d){J(a);if(null!=c){var f=C([]);for(var e=!1,g=0;g<c.length;g++)f[g]=c[g].j,e=e||E(f[g]);a.m||(a.m={});a.m[b]=c;c=f;e?Ea(c,8):z(c,8)}else a.m&&(a.m[b]=void 0),f=I;M(a,b,f,d)}
+function V(a,b){return null==a?b:a}function W(a,b){a=L(a,b);return V(null==a?a:!!a,!1)};function hb(a){var b=A(a);if(b&2)return a;a=sa(a,ib);Ha(b,a);Object.freeze(a);return a}function jb(a,b,c){c=void 0===c?Ha:c;if(null!=a){if(ya&&a instanceof Uint8Array)return a.length?new x(new Uint8Array(a),w):Ca();if(Array.isArray(a)){var d=A(a);if(d&2)return a;if(b&&!(d&32)&&(d&16||0===d))return B(a,d|2),a;a=Ta(a,jb,c,!0);b=A(a);b&4&&b&2&&Object.freeze(a);return a}return a.F===F?ib(a):a}}function ib(a){if(E(a.j))return a;a=cb(a,!0);z(a.j,2);return a}
+function cb(a,b){var c=a.j,d=Fa([]),f=a.constructor.h;f&&d.push(f);0!==(A(c)&128)&&La(d);b=b||E(a.j)?Ha:Ga;f=a.constructor;K=d;d=new f(d);K=void 0;a.C&&(d.C=a.C.slice());f=!!(A(c)&16);for(var e=0;e<c.length;e++){var g=c[e];if(e===c.length-1&&H(g))for(var h in g){var k=+h;if(Number.isNaN(k))Ya(d)[k]=g[k];else{var l=g[h],m=a.m&&a.m[k];m?gb(d,k,hb(m),!0):(l=jb(l,f,b),J(d),M(d,k,l,!0))}}else k=e-a.A,(l=a.m&&a.m[k])?gb(d,k,hb(l),!1):(g=jb(g,f,b),J(d),M(d,k,g,!1))}return d}
+function db(a){if(!E(a.j))return a;var b=cb(a,!1);b.o=a;return b};function X(a,b,c){null==a&&(a=K);K=void 0;var d=this.constructor.i||0,f=0<d,e=this.constructor.h,g=!1;if(null==a){a=e?[e]:[];var h=!0;B(a,48)}else{if(!Array.isArray(a))throw Error();if(e&&e!==a[0])throw Error();var k=z(a,0),l=k;if(h=0!==(16&l))(g=0!==(32&l))||(l|=32);if(f)if(128&l)d=0;else{if(0<a.length){var m=a[a.length-1];if(H(m)&&"g"in m){d=0;l|=128;delete m.g;var q=!0,G;for(G in m){q=!1;break}q&&a.pop()}}}else if(128&l)throw Error();k!==l&&B(a,l)}this.A=(e?0:-1)-d;this.m=void 0;this.j=a;a:{e=
+this.j.length;d=e-1;if(e&&(e=this.j[d],H(e))){this.h=e;this.i=d-this.A;break a}void 0!==b&&-1<b?(this.i=Math.max(b,d+1-this.A),this.h=void 0):this.i=Number.MAX_VALUE}if(!f&&this.h&&"g"in this.h)throw Error('Unexpected "g" flag in sparse object of message that is not a group type.');if(c){b=h&&!g&&!0;f=this.i;var D;for(h=0;h<c.length;h++)g=c[h],g<f?(g+=this.A,(d=a[g])?kb(d,b):a[g]=I):(D||(D=Ya(this)),(d=D[g])?kb(d,b):D[g]=I)}}X.prototype.toJSON=function(){var a=this.j;return Ia?a:Ta(a,Ua,Xa)};
+function Wa(a){var b=Ta(a.j,Va,Ga);Fa(b);K=b;b=new a.constructor(b);K=void 0;lb(b,a);return b}function kb(a,b){if(Array.isArray(a)){var c=A(a),d=1;!b||c&2||(d|=16);(c&d)!==d&&B(a,c|d)}}X.prototype.F=F;X.prototype.toString=function(){return this.j.toString()};function mb(a,b){return Ra(b)}
+function lb(a,b){b.C&&(a.C=b.C.slice());var c=b.m;if(c){var d=b.h,f;for(f in c)if(b=c[f]){var e=!(!d||!d[f]),g=+f;if(Array.isArray(b)){if(b.length)for(e=U(a,b[0].constructor,g,e),g=0;g<Math.min(e.length,b.length);g++)lb(e[g],b[g])}else throw a=typeof b,Error("unexpected object: type: "+("object"!=a?a:b?Array.isArray(b)?"array":a:"null")+": "+b);}}};var nb=void 0;function ob(a){var b=nb;nb=void 0;if(!Array.isArray(a))throw b=b?b()+"\n":"",Error(b+String(a));return a};function pb(a){return JSON.stringify([a.map(function(b){var c={};return[(c[b.L]=b.message.toJSON(),c)]})])};function qb(a){this.h=a}qb.prototype.K=function(){var a=encodeURIComponent;var b=pb(oa.apply(0,arguments));for(var c=[],d=0,f=0;f<b.length;f++){var e=b.charCodeAt(f);255<e&&(c[d++]=e&255,e>>=8);c[d++]=e}b=ua(c,3);a=a(b);this.h("https://pagead2.googlesyndication.com/pagead/ping?e=3&d="+a)};function rb(a){X.call(this,a)}r(rb,X);function sb(a){X.call(this,a,-1,tb)}r(sb,X);var tb=[3];function ub(a){X.call(this,a,-1,vb)}r(ub,X);var vb=[3];function wb(a){X.call(this,a,-1,xb)}r(wb,X);var xb=[2,3,5];function yb(a){X.call(this,a,-1,zb)}r(yb,X);function Ab(a){X.call(this,a,-1,Bb)}r(Ab,X);function Cb(a){X.call(this,a,-1,Db)}r(Cb,X);var zb=[1,2],Bb=[3,4,9],Db=[3,4,5,6,7];function Y(a){X.call(this,a)}r(Y,X);function Eb(a){X.call(this,a,-1,Fb)}r(Eb,X);var Fb=[1,2,3];function Gb(a){X.call(this,a,-1,Hb)}r(Gb,X);var Hb=[1];function Ib(a){X.call(this,a,-1,Jb)}r(Ib,X);var Jb=[1];function Kb(a){X.call(this,a,-1,Lb)}r(Kb,X);var Lb=[1];function Mb(a){X.call(this,a,-1,Nb)}r(Mb,X);var Nb=[1];function Z(a){X.call(this,a)}r(Z,X);function Ob(a){X.call(this,a,-1,Pb)}r(Ob,X);function Qb(a){X.call(this,a)}r(Qb,X);var Pb=[1];function Rb(a){X.call(this,a)}r(Rb,X);function Sb(a){X.call(this,a)}r(Sb,X);function Tb(a){X.call(this,a)}r(Tb,X);function Ub(a){X.call(this,a)}r(Ub,X);function Vb(a){X.call(this,a,-1,Wb)}r(Vb,X);var Wb=[1];function Xb(a){X.call(this,a,-1,Yb)}r(Xb,X);var Yb=[1,2];function Zb(a){X.call(this,a,-1,$b)}r(Zb,X);var $b=[1];function ac(a){X.call(this,a,-1,bc)}r(ac,X);var bc=[2,3,6,10];function cc(a){X.call(this,a)}r(cc,X);function dc(a,b){return R(a,1,b,"")}function ec(a,b){return fb(a,2,b)};var fc={ja:0,V:1,X:2,ca:3,Z:4,Y:5,aa:6,ba:7,da:8,ea:12,U:9,N:10,W:11};var gc={ka:0,ha:1,S:2,T:3,fa:4,M:5,ia:6,ga:7,P:8,O:9,R:10};function hc(a){X.call(this,a,-1,ic)}r(hc,X);function jc(a,b){fb(a,8,b)}function kc(a){X.call(this,a,-1,lc)}r(kc,X);function mc(a,b){return R(a,2,b,"")}function nc(a,b){return R(a,3,b,"")}var ic=[9],lc=[6];function oc(a){a.K.apply(a,ha(oa.apply(1,arguments).map(function(b){return{L:10,message:b}})))};function pc(a){X.call(this,a,-1,qc)}r(pc,X);var qc=[1];function rc(a,b,c){if(Q(a,2)!==Q(b,2))return c;var d=!1;switch(Q(a,2)){case 1:a:{var f,e=new Set(null!=(f=O(a,3))?f:[]);b=p(O(b,3));for(f=b.next();!f.done;f=b.next())if(e.has(f.value)){d=!0;break a}d=!1}break;case 0:a:{f=new Set(null!=(e=P(a,4,Na,!1))?e:[]);b=p(P(b,4,Na,!1));for(e=b.next();!e.done;e=b.next())if(f.has(e.value)){d=!0;break a}d=!1}break;case 2:b=new sc(b);d=(f=T(a,wb,5))?tc(b,f):!1;break;case 3:a:{f=new Set;e=p(P(a,9,$a));for(d=e.next();!d.done;d=e.next())f.add(Da(d.value));if(0===f.size)d=
+!0;else{b=p(P(b,6,$a));for(e=b.next();!e.done;e=b.next())if(f.has(Da(e.value))){d=!0;break a}d=!1}}break;case 4:d=uc(a,b)}return W(a,6)?d?null:c:d?c:null}
+function uc(a,b){a=T(a,sb,10);if(void 0===a)return!1;var c=U(b,ub,7);if(0===c.length)return!1;b=!0;c=p(c);for(var d=c.next();!d.done;d=c.next())if(d=d.value,V(L(d,1),0)===V(L(a,1),0)&&V(L(d,2),0)===V(L(a,2),0)){b&&(b=!1);var f=O(d,3);if(0===f.length)return!1;d=!0;f=p(f);for(var e=f.next();!e.done;e=f.next()){e=e.value;var g=Math.floor(e/32),h=O(a,3);if(!(g>=h.length||0!==(h[g]>>>e%32)%2)){d=!1;break}}if(d)return!0}return b}
+function tc(a,b){var c=Q(b,1),d=U(b,rb,3),f=U(b,wb,2);switch(c){case 2:c=d.every(function(e){return vc(a,e)})&&f.every(function(e){return tc(a,e)});break;case 1:c=d.some(function(e){return vc(a,e)})||f.some(function(e){return tc(a,e)});break;default:throw Error("unexpected value "+c+"!");}return W(b,4)?!c:c}function sc(a){this.h=new Map;a=p(U(a,rb,5));for(var b=a.next();!b.done;b=a.next()){var c=b.value;b=V(L(c,1),0);c=V(L(c,2),0);var d=this.h.get(b);d||(d=new Set,this.h.set(b,d));d.add(c)}}
+function vc(a,b){var c=V(L(b,2),0);return(a=a.h.get(V(L(b,1),0)))?a.has(c):!1};function wc(a,b){switch(a){case 2:return 187;case 3:return 138;case 4:return xc(b);case 5:return 80;case 6:return 7;case 7:return 2;case 8:return 7;default:return 4}}function xc(a){switch(a){case 2:return 12;case 3:return 14;case 4:return 15;case 5:return 219;case 6:return 82;case 7:return 80;case 8:return 71;case 9:return 16;default:return 4}};function yc(a){return"https://googleads.g.doubleclick.net"===a||"https://td.doubleclick.net"===a};function zc(a,b,c,d,f){this.J=a;this.postRevshareBidCpmUsdMicros=b;this.sellerSignals=c;this.h=d;this.D=f;this.o=new Map;if(W(S(c,Y,12),1)||W(S(c,Y,12),2))a=new hc,this.i=R(a,7,V(L(this.sellerSignals,2),""),"")}zc.prototype.reject=function(a,b){b=void 0===b?0:b;var c,d,f,e,g={ru:null!=(f=null==(c=this.h)?void 0:c.renderUrl)?f:"",igo:null!=(e=null==(d=this.h)?void 0:d.interestGroupOwner)?e:"",s:a,r:b};Ac(this,a,b);return{desirability:0,postRevshareBidCpmUsdMicros:0,debugInfo:this.D?g:void 0,debugEventMessage:this.debugEventMessage}};
+zc.prototype.accept=function(){var a,b,c,d,f={ru:null!=(c=null==(a=this.h)?void 0:a.renderUrl)?c:"",igo:null!=(d=null==(b=this.h)?void 0:b.interestGroupOwner)?d:"",s:1,r:1};Ac(this,1,1);return{desirability:this.postRevshareBidCpmUsdMicros,postRevshareBidCpmUsdMicros:this.postRevshareBidCpmUsdMicros,debugInfo:this.D?f:void 0,debugEventMessage:this.debugEventMessage}};
+function Ac(a,b,c){if(void 0!==a.i){var d=nc(mc(new kc,a.h.interestGroupOwner),a.h.renderUrl);yc(a.h.interestGroupOwner)||R(d,8,a.J,0);fb(a.i,4,d);W(S(a.sellerSignals,Y,12),5)&&(jc(fb(a.i,11,a.sellerSignals),ec(dc(new cc,a.h.renderUrl),a.I)),a.o.forEach(function(h,k){var l;null!=(l=a.i)&&(k=ec(dc(new cc,k),h),J(l),h=eb(l,cc,9,void 0,!1,!1),k=null!=k?k:new cc,l=Za(l,9,2,void 0,!1),h.push(k),l.push(k.j),E(k.j)&&Ea(l,8))}));if(Object.values(gc).includes(b)){var f;null!=(f=a.i)&&R(f,6,b,0)}if(Object.values(fc).includes(c)){var e;
+null!=(e=a.i)&&R(e,5,c,0)}if(1!==b||1!==c){var g;null!=(g=a.i)&&R(g,10,wc(b,c),0)}}}ea.Object.defineProperties(zc.prototype,{debugEventMessage:{configurable:!0,enumerable:!0,get:function(){var a;return null==(a=this.i)?void 0:Wa(a)}}});function Bc(a){var b;this.i=null!=(b=null==a?void 0:V(L(a,4),0))?b:0}Bc.prototype.h=function(a){if(!this.i)return 1;var b;return(null==(b=T(a,Vb,9))?0:O(b,1).includes(this.i))?2:1};function Cc(a,b){return 0===a.size?!1:void 0===b||0===b.length?!0:b.some(function(c){return a.has(c)})}function Dc(a){a=S(S(a,Z,2),Eb,1);this.i=new Set(O(a,1));this.o=W(a,4);this.D=W(a,5)}Dc.prototype.h=function(a){var b;if(Cc(this.i,null==(b=T(a,Xb,1))?void 0:O(b,1)))return 4;a=T(a,Xb,1);return!this.D||a&&!W(a,4)?!this.o||a&&!W(a,3)?1:3:4};function Ec(a){this.i=W(S(S(a,Z,2),Gb,3),2);this.o=new Set(O(S(S(a,Z,2),Gb,3),1))}Ec.prototype.h=function(a){var b=this;return this.i?1:void 0===bb(a,Zb,7,!1)||O(T(a,Zb,7),1).every(function(c){return b.o.has(c)})?1:5};function Fc(a){var b,c,d;this.i=new Set(null!=(d=null==a?void 0:null==(b=T(a,Z,2))?void 0:null==(c=T(b,Ib,4))?void 0:P(c,1,Na,!1))?d:[])}Fc.prototype.h=function(a){var b=this;return 0===this.i.size?1:0===P(a,10,Na,!1).length||P(a,10,Na,!1).some(function(c){return!b.i.has(c)})?6:1};function Gc(a){var b,c;this.i=null!=(c=null==a?void 0:null==(b=T(a,Ob,3))?void 0:U(b,Qb,1))?c:[]}Gc.prototype.h=function(a,b){if(null==b)return 7;if(0===this.i.length)return 1;a=p(this.i);for(var c=a.next();!c.done;c=a.next())if(c=c.value,null!=V(L(c,2),0)&&b<V(L(c,2),0))return 7;return 1};function Hc(a){var b,c,d;this.i=new Set(null!=(d=null==a?void 0:null==(b=T(a,Z,2))?void 0:null==(c=T(b,Kb,2))?void 0:P(c,1,Ma))?d:[])}Hc.prototype.h=function(a){var b=this;return 0!==this.i.size&&P(a,6,Ma).some(function(c){return b.i.has(c)})?8:1};function Ic(a){a=S(S(a,Z,2),Mb,5);this.i=W(a,2);this.o=new Set(O(a,1))}Ic.prototype.h=function(a){var b=this;return this.i?1:O(S(a,Zb,7),1).every(function(c){return b.o.has(c)})?1:9};function Jc(){new pc;return function(a,b,c,d,f){a=new Sb(ob(c.sellerSignals));b=Kc(b,a,d,f,W(a,8)||W(a,22));W(a,8)&&b.debugInfo&&console.log(b.debugInfo);if(W(a,22)&&b.debugInfo){d=b.debugInfo.s;c=b.debugInfo.r;var e,g,h={renderUrl:null!=(e=null==f?void 0:f.renderUrl)?e:"",interestGroupOwner:null!=(g=null==f?void 0:f.interestGroupOwner)?g:"",accepted:!0};if(1!==d||1!==c)h.accepted=!1,h.externalBidStatus=wc(d,c);console.log("Logging debug info of scoreAd().\n",h)}b.debugEventMessage&&(e=b.debugEventMessage,
+g=globalThis.forDebuggingOnly,W(S(a,Y,12),1)&&g.reportAdAuctionWin&&Lc(a,e,g.reportAdAuctionWin,1),W(S(a,Y,12),2)&&g.reportAdAuctionLoss&&Lc(a,e,g.reportAdAuctionLoss,2));return f.topLevelSeller?{desirability:b.desirability,bid:b.postRevshareBidCpmUsdMicros?b.postRevshareBidCpmUsdMicros/1E6:void 0,allowComponentAuction:!0}:{desirability:b.desirability,allowComponentAuction:!0}}}
+function Lc(a,b,c,d){R(b,2,d,0);d=T(b,kc,4);if(W(a,23)&&d&&!yc(V(L(d,2),""))){a=encodeURIComponent;a:{Ia=!0;try{var f=JSON.stringify(b.toJSON(),mb);break a}finally{Ia=!1}f=void 0}c("https://googleads.g.doubleclick.net/td/rdl?tdr="+a(f))}else oc(new qb(c),b)}
+function Mc(a,b){var c,d=null==(c=a.sellerSignals)?void 0:T(c,yb,3),f=null==b?void 0:T(b,yb,11);if(d&&f){c=new Map;for(var e=new Map,g=p(U(d,Ab,1)),h=g.next();!h.done;h=g.next())h=h.value,c.set(Q(h,1),h);d=p(U(d,Cb,2));for(g=d.next();!g.done;g=d.next())g=g.value,e.set(Q(g,1),g);d=new Map;if(g=c&&e&&f)a:{g=p(U(f,Ab,1));for(h=g.next();!h.done;h=g.next()){h=h.value;var k=Q(h,1),l=e.get(k);if(l){var m=null;if(W(h,7)){var q=d.get(k),G=!0,D=V(L(h,8),0);if(void 0===q)q=new Map,d.set(k,q);else{var N=q.get(D);
+if(N){g=N;break a}null===N&&(G=!1)}G&&(m=rc(h,l,k),q.set(D,m))}else m=rc(h,l,k);if(m){g=m;break a}}}f=p(U(f,Cb,2));for(e=f.next();!e.done;e=f.next())if(e=e.value,d=Q(e,1),g=c.get(d))if(e=rc(g,e,d)){g=e;break a}g=null}if(g)return{G:2,B:0}}a:{c=a.postRevshareBidCpmUsdMicros;var da;a=null!=(da=T(a.sellerSignals,Rb,1))?da:new Rb;da=p([new Bc(a),new Dc(a),new Ec(a),new Fc(a),new Gc(a),new Hc(a),new Ic(a)]);for(a=da.next();!a.done;a=da.next())if(a=a.value.h(b,c),1!==a){b=a;break a}b=1}return 1!==b?{G:4,
+B:b}:{G:0,B:1}}
+function Kc(a,b,c,d,f){var e=!!d.componentSeller,g=1E6*a,h=S(S(b,Tb,5),Ub,1);h=L(h,1);h=g*(V(null==h?h:+h,0)||1);a=new zc(a,h,b,d,f);if(d.topLevelSeller&&"https://pubads.g.doubleclick.net"!==d.topLevelSeller)return a.reject(9);var k;f=BigInt((null==(k=T(b,Tb,5))?void 0:V(L(k,2),"0"))||0);if(h<f)return a.reject(5);var l;b=BigInt((null==(l=T(b,Tb,5))?void 0:V(L(l,3),"0"))||0);if(0<b&&g>b)return a.reject(3);if(e)return a.accept();if(!d.renderUrl)return a.reject(7,11);e=null==c?void 0:c.renderUrl;if(null==
+e||!e[d.renderUrl])return a.reject(6,10);var m;e=new ac(ob(null!=(m=null==e?void 0:e[d.renderUrl])?m:[]));a.I=e;m=Mc(a,a.I);if(1!==m.B)return a.reject(m.G,m.B);var q;if(null==(q=d.adComponents)?0:q.length){q=!1;d=p(d.adComponents);for(m=d.next();!m.done;m=d.next())if(m=m.value,l=g=e=void 0,b=null!=(l=null==(e=c)?void 0:null==(g=e.adComponentRenderUrls)?void 0:g[m])?l:[],e=ob(b),e.length&&(q=!0,e=new ac(e),a.o.set(m,e),e=Mc(a,e),1!==e.B))return c=a.reject(e.G,e.B),c.debugInfo&&(c.debugInfo.acru=m),
+c;if(!q)return a.reject(8,12)}return a.accept()};function Nc(a,b,c,d,f){var e=a.metadata.metadata;c={seller:c.seller,decisionLogicUrl:c.decision_logic_uri,trustedScoringSignalsUrl:c.trusted_scoring_signal_uri,interestGroupBuyers:c.custom_audience_buyers,auctionSignals:f,sellerExperimentGroupId:void 0,sellerSignals:d.signals,perBuyerSignals:c.per_buyer_signals};a={topWindowHostname:"",interestGroupOwner:"",renderUrl:a.render_uri,biddingDurationMsec:0};b=Jc()(e,b,c,f,a);return{status:0,score:"number"===typeof b?b:b.desirability}};var Oc=globalThis;Oc.scoreAd=function(a,b,c,d,f){return Nc(a,b,c,d,f)};Oc.scoreAdIterative=function(a,b,c,d){var f=[],e=(new Date).getTime();a=p(a);for(var g=a.next();!g.done;g=a.next())g=g.value,g=Nc(g.ad,g.bid,b,c,d),f.push(g);return{responses:f,status:0,duration:(new Date).getTime()-e}};
diff --git a/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java b/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java
index 33b2bea..0b35101 100644
--- a/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java
+++ b/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java
@@ -17,6 +17,7 @@
 package android.rubidium.js;
 
 import static com.android.adservices.service.js.JSScriptArgument.arrayArg;
+import static com.android.adservices.service.js.JSScriptArgument.jsonArg;
 import static com.android.adservices.service.js.JSScriptArgument.numericArg;
 import static com.android.adservices.service.js.JSScriptArgument.recordArg;
 import static com.android.adservices.service.js.JSScriptArgument.stringArg;
@@ -26,8 +27,14 @@
 
 import static org.junit.Assume.assumeTrue;
 
+import android.adservices.adselection.AdSelectionConfig;
+import android.adservices.adselection.AdWithBid;
+import android.adservices.common.AdData;
+import android.adservices.common.AdSelectionSignals;
+import android.adservices.common.AdTechIdentifier;
 import android.annotation.SuppressLint;
 import android.content.Context;
+import android.net.Uri;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.util.Log;
@@ -37,6 +44,12 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.adservices.data.adselection.CustomAudienceSignals;
+import com.android.adservices.service.adselection.AdDataArgument;
+import com.android.adservices.service.adselection.AdSelectionConfigArgument;
+import com.android.adservices.service.adselection.AdWithBidArgument;
+import com.android.adservices.service.adselection.CustomAudienceBiddingSignalsArgument;
+import com.android.adservices.service.adselection.CustomAudienceScoringSignalsArgument;
 import com.android.adservices.service.js.IsolateSettings;
 import com.android.adservices.service.js.JSScriptArgument;
 import com.android.adservices.service.js.JSScriptArrayArgument;
@@ -46,9 +59,11 @@
 import com.android.adservices.service.profiling.Profiler;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.util.concurrent.ListenableFuture;
 
 import org.json.JSONArray;
+import org.json.JSONObject;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -58,14 +73,23 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Random;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /** To run the unit tests for this class, run "atest RubidiumPerfTests:JSScriptEnginePerfTests" */
 @MediumTest
@@ -78,8 +102,12 @@
     private static final JSScriptEngine sJSScriptEngine =
             JSScriptEngine.getInstanceForTesting(
                     sContext, Profiler.createInstance(JSScriptEngine.TAG));
-
-    @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+    private static final Clock CLOCK = Clock.fixed(Instant.now(), ZoneOffset.UTC);
+    private static final Instant ACTIVATION_TIME = CLOCK.instant();
+    private static final Instant EXPIRATION_TIME = CLOCK.instant().plus(Duration.ofDays(1));
+    private static final AdSelectionSignals CONTEXTUAL_SIGNALS = AdSelectionSignals.EMPTY;
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
     @Before
     public void before() throws Exception {
@@ -162,6 +190,56 @@
         runParametrizedTurtledoveScript(75);
     }
 
+    @Test
+    public void evaluate_rubidiumGenerateBid_parametrized_1Ad() throws Exception {
+        runParameterizedRubidiumGenerateBid(1);
+    }
+
+    @Test
+    public void evaluate_rubidiumGenerateBid_parametrized_10Ads() throws Exception {
+        runParameterizedRubidiumGenerateBid(10);
+    }
+
+    @Test
+    public void evaluate_rubidiumGenerateBid_parametrized_25Ads() throws Exception {
+        runParameterizedRubidiumGenerateBid(25);
+    }
+
+    @Test
+    public void evaluate_rubidiumGenerateBid_parametrized_50Ads() throws Exception {
+        runParameterizedRubidiumGenerateBid(50);
+    }
+
+    @Test
+    public void evaluate_rubidiumGenerateBid_parametrized_75Ads() throws Exception {
+        runParameterizedRubidiumGenerateBid(75);
+    }
+
+    @Test
+    public void evaluate_rubidiumScoreAd_parametrized_1Ad() throws Exception {
+        runParameterizedRubidiumScoreAd(1);
+    }
+
+    @Test
+    public void evaluate_rubidiumScoreAd_parametrized_10Ads() throws Exception {
+        runParameterizedRubidiumScoreAd(10);
+    }
+
+    @Test
+    public void evaluate_rubidiumScoreAd_parametrized_25Ads() throws Exception {
+        runParameterizedRubidiumScoreAd(25);
+    }
+
+    @Test
+    public void evaluate_rubidiumScoreAd_parametrized_50Ads() throws Exception {
+        runParameterizedRubidiumScoreAd(50);
+    }
+
+    @Test
+    public void evaluate_rubidiumScoreAd_parametrized_75Ads() throws Exception {
+        runParameterizedRubidiumScoreAd(75);
+    }
+
     @SuppressLint("DefaultLocale")
     private void runParametrizedTurtledoveScript(int numAds) throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
@@ -190,7 +268,7 @@
                             "(%s: %d)",
                             JSScriptEngineLogConstants.WEBVIEW_EXECUTION_TIME, webviewExecTime);
             // The listener picks up logs from JSScriptEngine, so simulate logging from there.
-            Log.d("JSScriptEngine", webviewExecTimeLog);
+            Log.d(TAG, webviewExecTimeLog);
         }
     }
 
@@ -202,7 +280,9 @@
                         ImmutableList.of(
                                 stringArg(
                                         "renderUrl",
-                                        "https://googleads.g.doubleclick.net/ads/simple-ad.html?adg_id=52836427830&cr_id=310927197297&cv_id=4"),
+                                        "https://googleads.g.doubleclick.net/ads/simple-ad"
+                                                + ".html?adg_id=52836427830&cr_id=310927197297"
+                                                + "&cv_id=4"),
                                 stringArrayArg(
                                         "metadata",
                                         ImmutableList.of(
@@ -350,4 +430,171 @@
     private String readAsset(@NonNull String assetName) throws IOException {
         return new String(readBinaryAsset(assetName), StandardCharsets.UTF_8);
     }
+
+    public void runParameterizedRubidiumGenerateBid(int numOfAds) throws Exception {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        state.pauseTiming();
+        List<AdData> adDataList = getSampleAdDataList(numOfAds, "https://ads.example/");
+        ImmutableList.Builder<JSScriptArgument> adDataListArgument = new ImmutableList.Builder<>();
+        for (AdData adData : adDataList) {
+            adDataListArgument.add(AdDataArgument.asScriptArgument("ignored", adData));
+        }
+        AdSelectionSignals perBuyerSignals = generatePerBuyerSignals(numOfAds);
+        AdSelectionSignals auctionSignals = AdSelectionSignals.fromString("{\"auctionSignal1"
+                + "\":\"auctionValue1\",\"auctionSignal2\":\"auctionValue2\"}");
+
+        AdTechIdentifier buyer = AdTechIdentifier.fromString("https://example-dsp.com");
+        AdSelectionSignals trustedBiddingSignals = AdSelectionSignals.fromString("{\"key1"
+                + "\":\"tbs1\",\"key2\":{}}");
+        CustomAudienceSignals customAudienceSignals = getSampleCustomAudienceSignals(buyer,
+                "shoes-running");
+
+        ImmutableList<JSScriptArgument> args = ImmutableList.<JSScriptArgument>builder()
+                .add(arrayArg("ads", adDataListArgument.build()))
+                .add(jsonArg("auctionSignals", auctionSignals))
+                .add(jsonArg("perBuyerSignals", perBuyerSignals))
+                .add(jsonArg("trustedBiddingSignals", trustedBiddingSignals))
+                .add(jsonArg("contextualSignals", CONTEXTUAL_SIGNALS))
+                .add(CustomAudienceBiddingSignalsArgument.asScriptArgument(
+                        "customAudienceBiddingSignal", customAudienceSignals))
+                .build();
+        InputStream testJsInputStream = sContext.getAssets().open(
+                "rubidium_bidding_logic_compiled.js");
+        String jsTestFile = new String(testJsInputStream.readAllBytes(), StandardCharsets.UTF_8);
+        //logging time taken to call JS
+        state.resumeTiming();
+        while (state.keepRunning()) {
+            String res = callJSEngine(jsTestFile, args, "generateBidIterative");
+            JSONObject jsonObject = new JSONObject(res);
+            long webviewExecTime = jsonObject.getLong("duration");
+            String webviewExecTimeLog =
+                    String.format(Locale.ENGLISH,
+                            "(%s: %d)",
+                            JSScriptEngineLogConstants.WEBVIEW_EXECUTION_TIME,
+                            webviewExecTime);
+            // The listener picks up logs from JSScriptEngine, so simulate logging from there.
+            Log.d(TAG, webviewExecTimeLog);
+        }
+    }
+
+    public void runParameterizedRubidiumScoreAd(int numOfAds) throws Exception {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        state.pauseTiming();
+        String adRenderUrl = "https://rtb.example/creative";
+        List<AdWithBid> adWithBidList = getSampleAdDataWithBidList(numOfAds, adRenderUrl);
+        ImmutableList.Builder<JSScriptArgument> adWithBidArrayArgument =
+                new ImmutableList.Builder<>();
+        for (AdWithBid adWithBid : adWithBidList) {
+            adWithBidArrayArgument.add(AdWithBidArgument.asScriptArgument("adWithBid", adWithBid));
+        }
+        AdTechIdentifier seller = AdTechIdentifier.fromString("www.example-ssp.com");
+        AdSelectionSignals sellerSignals = AdSelectionSignals.fromString("{\"signals\":[]}");
+        String trustedScoringSignalJson = String.format(Locale.ENGLISH,
+                "{\"renderUrl\":{\"%s\":[]}}", adRenderUrl);
+        AdSelectionSignals trustedScoringSignalsJson = AdSelectionSignals.fromString(
+                trustedScoringSignalJson);
+
+        AdTechIdentifier buyer1 = AdTechIdentifier.fromString("https://example-dsp.com");
+        AdSelectionSignals buyer1Signals = AdSelectionSignals.fromString("{\"https://example-dsp"
+                + ".com:1\":\"value1\",\"https://example-dsp.com:2\":\"value2\"}");
+        Map<AdTechIdentifier, AdSelectionSignals> perBuyerSignals = ImmutableMap.of(buyer1,
+                buyer1Signals);
+
+        AdSelectionConfig adSelectionConfig = getSampleAdSelectionConfig(seller, sellerSignals,
+                perBuyerSignals);
+        CustomAudienceSignals customAudienceSignals = getSampleCustomAudienceSignals(buyer1,
+                "shoes-running");
+
+        ImmutableList<JSScriptArgument> args = ImmutableList.<JSScriptArgument>builder()
+                .add(arrayArg("adsWithBids", adWithBidArrayArgument.build()))
+                .add(AdSelectionConfigArgument.asScriptArgument(adSelectionConfig,
+                        "adSelectionConfig"))
+                .add(jsonArg("sellerSignals", sellerSignals))
+                .add(jsonArg("trustedScoringSignals", trustedScoringSignalsJson))
+                .add(jsonArg("contextualSignals", CONTEXTUAL_SIGNALS))
+                .add(CustomAudienceScoringSignalsArgument.asScriptArgument(
+                        "customAudienceScoringSignal", customAudienceSignals))
+                .build();
+        InputStream testJsInputStream = sContext.getAssets().open(
+                "rubidium_scoring_logic_compiled.js");
+        String jsTestFile = new String(testJsInputStream.readAllBytes(), StandardCharsets.UTF_8);
+        //logging time taken to call JS
+        state.resumeTiming();
+        while (state.keepRunning()) {
+            String res = callJSEngine(jsTestFile, args, "scoreAdIterative");
+            JSONObject jsonObject = new JSONObject(res);
+            long webviewExecTime = jsonObject.getLong("duration");
+            String webviewExecTimeLog =
+                    String.format(Locale.ENGLISH,
+                            "(%s: %d)",
+                            JSScriptEngineLogConstants.WEBVIEW_EXECUTION_TIME,
+                            webviewExecTime);
+            // The listener picks up logs from JSScriptEngine, so simulate logging from there.
+            Log.d(TAG, webviewExecTimeLog);
+        }
+    }
+
+    private List<AdWithBid> getSampleAdDataWithBidList(int size, String baseUri) {
+        double initialBid = 1.23;
+        return IntStream.rangeClosed(1, size).mapToObj(iterator -> {
+            Uri renderUri = Uri.parse(String.format(Locale.ENGLISH, "%s%d", baseUri, iterator));
+            String metaDataJson = String.format(Locale.ENGLISH, "{\"metadata\":[\"%d\",\"123\"]}",
+                    iterator);
+            AdData adData = new AdData.Builder().setRenderUri(renderUri).setMetadata(
+                    metaDataJson).build();
+            return new AdWithBid(adData, initialBid + iterator);
+        }).collect(Collectors.toCollection(ArrayList::new));
+    }
+
+    private List<AdData> getSampleAdDataList(int size, String baseUri) {
+        return IntStream.rangeClosed(1, size).mapToObj(iterator -> {
+            Uri renderUri = Uri.parse(String.format(Locale.ENGLISH, "%s%d", baseUri, iterator));
+            String metaDataJson = String.format(Locale.ENGLISH, "{\"metadata\":[\"%d\",\"123\"]}",
+                    iterator);
+            return new AdData.Builder().setRenderUri(renderUri).setMetadata(
+                    metaDataJson).build();
+        }).collect(Collectors.toCollection(ArrayList::new));
+    }
+
+    private CustomAudienceSignals getSampleCustomAudienceSignals(AdTechIdentifier buyer,
+            String name) {
+        String owner = "www.example-dsp.com";
+        AdSelectionSignals userBiddingSignals = AdSelectionSignals.fromString("{\"signals\":[]}");
+        return new CustomAudienceSignals.Builder()
+                .setOwner(owner)
+                .setBuyer(buyer)
+                .setActivationTime(ACTIVATION_TIME)
+                .setExpirationTime(EXPIRATION_TIME)
+                .setUserBiddingSignals(userBiddingSignals)
+                .setName(name)
+                .build();
+    }
+
+    private AdSelectionConfig getSampleAdSelectionConfig(AdTechIdentifier seller,
+            AdSelectionSignals sellerSignals,
+            Map<AdTechIdentifier, AdSelectionSignals> perBuyerSignals) {
+        Uri decisionLogicUri = Uri.parse("https://www.example-ssp.com/decide.js");
+        Uri trustedScoringSignalsUri = Uri.parse("https://www.example-ssp.com/signals");
+        List<AdTechIdentifier> buyers = ImmutableList.copyOf(
+                new ArrayList<>(perBuyerSignals.keySet()));
+        return new AdSelectionConfig.Builder()
+                .setSeller(seller)
+                .setDecisionLogicUri(decisionLogicUri)
+                .setCustomAudienceBuyers(buyers)
+                .setAdSelectionSignals(AdSelectionSignals.EMPTY)
+                .setSellerSignals(sellerSignals)
+                .setPerBuyerSignals(perBuyerSignals)
+                .setTrustedScoringSignalsUri(trustedScoringSignalsUri)
+                .build();
+    }
+
+    private AdSelectionSignals generatePerBuyerSignals(int size) {
+        String signalArrayFormat = "[\"%d\",\"123\",%d]";
+        String signalArray = IntStream.rangeClosed(1, size)
+                .mapToObj(i -> String.format(Locale.ENGLISH, signalArrayFormat, i, i))
+                .collect(Collectors.joining(", ", "[", "]"));
+        return AdSelectionSignals.fromString(
+                String.format(Locale.ENGLISH, "{\"signals\":[null,%s,[null]]}",
+                        signalArray));
+    }
 }
diff --git a/apct-tests/perftests/settingsprovider/Android.bp b/apct-tests/perftests/settingsprovider/Android.bp
new file mode 100644
index 0000000..43ec0e0
--- /dev/null
+++ b/apct-tests/perftests/settingsprovider/Android.bp
@@ -0,0 +1,39 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "SettingsProviderPerfTests",
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "platform-compat-test-rules",
+        "androidx.appcompat_appcompat",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "androidx.annotation_annotation",
+        "apct-perftests-utils",
+        "collector-device-lib-platform",
+        "cts-install-lib-java",
+        "services.core",
+    ],
+
+    libs: ["android.test.base"],
+
+    platform_apis: true,
+
+    test_suites: ["device-tests"],
+
+    data: [":perfetto_artifacts"],
+
+    certificate: "platform",
+}
diff --git a/apct-tests/perftests/settingsprovider/AndroidManifest.xml b/apct-tests/perftests/settingsprovider/AndroidManifest.xml
new file mode 100644
index 0000000..9509c83
--- /dev/null
+++ b/apct-tests/perftests/settingsprovider/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.perftests.settingsprovider">
+
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.perftests.settingsprovider"/>
+
+</manifest>
diff --git a/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java b/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java
new file mode 100644
index 0000000..e31162f
--- /dev/null
+++ b/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 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 android.provider;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public final class SettingsProviderPerfTest {
+    private static final String NAMESPACE = "test@namespace";
+    private static final String SETTING_NAME1 = "test:setting1";
+    private static final String SETTING_NAME2 = "test-setting2";
+
+    private final ContentResolver mContentResolver;
+
+    @Rule
+    public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    public SettingsProviderPerfTest() {
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        mContentResolver = context.getContentResolver();
+    }
+
+    @Before
+    public void setUp() {
+        Settings.Secure.putString(mContentResolver, SETTING_NAME1, "1");
+        Settings.Config.putString(NAMESPACE, SETTING_NAME1, "1", true);
+        Settings.Config.putString(NAMESPACE, SETTING_NAME2, "2", true);
+    }
+
+    @After
+    public void destroy() {
+        Settings.Secure.putString(mContentResolver, SETTING_NAME1, "null");
+        Settings.Config.deleteString(NAMESPACE, SETTING_NAME1);
+        Settings.Config.deleteString(NAMESPACE, SETTING_NAME2);
+    }
+
+    @Test
+    public void testSettingsValueConsecutiveRead() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        int i = 0;
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            // Writing to setting2 should not invalidate setting1's cache
+            Settings.Secure.putString(mContentResolver, SETTING_NAME2, Integer.toString(i));
+            i++;
+            state.resumeTiming();
+            Settings.Secure.getString(mContentResolver, SETTING_NAME1);
+        }
+    }
+
+    @Test
+    public void testSettingsValueConsecutiveReadAfterWrite() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        int i = 0;
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            // Triggering the invalidation of setting1's cache
+            Settings.Secure.putString(mContentResolver, SETTING_NAME1, Integer.toString(i));
+            i++;
+            state.resumeTiming();
+            Settings.Secure.getString(mContentResolver, SETTING_NAME1);
+        }
+    }
+
+    @Test
+    public void testSettingsNamespaceConsecutiveRead() {
+        final List<String> names = new ArrayList<>();
+        names.add(SETTING_NAME1);
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Settings.Config.getStrings(mContentResolver, NAMESPACE, names);
+        }
+    }
+
+    @Test
+    public void testSettingsNamespaceConsecutiveReadAfterWrite() {
+        final List<String> names = new ArrayList<>();
+        names.add(SETTING_NAME1);
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        int i = 0;
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            // Triggering the invalidation of the list's cache
+            Settings.Config.putString(NAMESPACE, SETTING_NAME2, Integer.toString(i), true);
+            i++;
+            state.resumeTiming();
+            Settings.Config.getStrings(mContentResolver, NAMESPACE, names);
+        }
+    }
+}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 9caf99e..19ab5bc 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -30,6 +30,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.app.Notification;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -403,6 +404,13 @@
     public static final int FLAG_DATA_TRANSFER = 1 << 5;
 
     /**
+     * Whether it's a user initiated job or not.
+     *
+     * @hide
+     */
+    public static final int FLAG_USER_INITIATED = 1 << 6;
+
+    /**
      * @hide
      */
     public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
@@ -738,6 +746,14 @@
     }
 
     /**
+     * @see JobInfo.Builder#setUserInitiated(boolean)
+     * @hide
+     */
+    public boolean isUserInitiated() {
+        return (flags & FLAG_USER_INITIATED) != 0;
+    }
+
+    /**
      * @see JobInfo.Builder#setImportantWhileForeground(boolean)
      */
     public boolean isImportantWhileForeground() {
@@ -1849,15 +1865,8 @@
          *
          * <p>
          * For user-initiated transfers that must be started immediately, call
-         * {@link #setExpedited(boolean) setExpedited(true)}. Otherwise, the system may defer the
-         * job to a more opportune time. Using {@link #setExpedited(boolean) setExpedited(true)}
-         * with this API will only be allowed for foreground apps and when the user has clearly
-         * interacted with the app. {@link #setExpedited(boolean) setExpedited(true)} will return
-         * {@link JobScheduler#RESULT_FAILURE} for a data transfer job if the app is in the
-         * background. Apps that successfully schedule data transfer jobs with
-         * {@link #setExpedited(boolean) setExpedited(true)} will not have quotas applied to them,
-         * though they may still be stopped for system health or constraint reasons. The system will
-         * also give a user the ability to stop a data transfer job via the Task Manager.
+         * {@link #setUserInitiated(boolean) setUserInitiated(true)}. Otherwise, the system may
+         * defer the job to a more opportune time.
          *
          * <p>
          * If you want to perform more than one data transfer job, consider enqueuing multiple
@@ -1877,6 +1886,50 @@
         }
 
         /**
+         * Indicates that this job is being scheduled to fulfill an explicit user request.
+         * As such, user-initiated jobs can only be scheduled when the app is in the foreground
+         * or in a state where launching an activity is allowed, as defined
+         * <a href=
+         * "https://developer.android.com/guide/components/activities/background-starts#exceptions">
+         * here</a>. Attempting to schedule one outside of these conditions will throw a
+         * {@link SecurityException}.
+         *
+         * <p>
+         * This should <b>NOT</b> be used for automatic features.
+         *
+         * <p>
+         * All user-initiated jobs must have an associated notification, set via
+         * {@link JobService#setNotification(JobParameters, int, Notification, int)}, and will be
+         * shown in the Task Manager when running.
+         *
+         * <p>
+         * These jobs will not be subject to quotas and will be started immediately once scheduled
+         * if all constraints are met and the device system health allows for additional tasks.
+         *
+         * @see JobInfo#isUserInitiated()
+         * @hide
+         */
+        @NonNull
+        public Builder setUserInitiated(boolean userInitiated) {
+            if (userInitiated) {
+                mFlags |= FLAG_USER_INITIATED;
+                if (mPriority == PRIORITY_DEFAULT) {
+                    // The default priority for UIJs is MAX, but only change this if .setPriority()
+                    // hasn't been called yet.
+                    mPriority = PRIORITY_MAX;
+                }
+            } else {
+                if (mPriority == PRIORITY_MAX && (mFlags & FLAG_USER_INITIATED) != 0) {
+                    // Reset the priority for the job, but only change this if .setPriority()
+                    // hasn't been called yet.
+                    mPriority = PRIORITY_DEFAULT;
+                }
+                mFlags &= (~FLAG_USER_INITIATED);
+            }
+            return this;
+        }
+
+        /**
          * Setting this to true indicates that this job is important while the scheduling app
          * is in the foreground or on the temporary whitelist for background restrictions.
          * This means that the system will relax doze restrictions on this job during this time.
@@ -2086,10 +2139,12 @@
         }
 
         final boolean isExpedited = (flags & FLAG_EXPEDITED) != 0;
+        final boolean isUserInitiated = (flags & FLAG_USER_INITIATED) != 0;
         switch (mPriority) {
             case PRIORITY_MAX:
-                if (!isExpedited) {
-                    throw new IllegalArgumentException("Only expedited jobs can have max priority");
+                if (!(isExpedited || isUserInitiated)) {
+                    throw new IllegalArgumentException(
+                            "Only expedited or user-initiated jobs can have max priority");
                 }
                 break;
             case PRIORITY_HIGH:
@@ -2118,14 +2173,20 @@
             if (isPeriodic) {
                 throw new IllegalArgumentException("An expedited job cannot be periodic");
             }
+            if ((flags & FLAG_DATA_TRANSFER) != 0) {
+                throw new IllegalArgumentException(
+                        "An expedited job cannot also be a data transfer job");
+            }
+            if (isUserInitiated) {
+                throw new IllegalArgumentException("An expedited job cannot be user-initiated");
+            }
             if (mPriority != PRIORITY_MAX && mPriority != PRIORITY_HIGH) {
                 throw new IllegalArgumentException(
                         "An expedited job must be high or max priority. Don't use expedited jobs"
                                 + " for unimportant tasks.");
             }
-            if (((constraintFlags & ~CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0
-                    || (flags & ~(FLAG_EXPEDITED | FLAG_EXEMPT_FROM_APP_STANDBY
-                                    | FLAG_DATA_TRANSFER)) != 0)) {
+            if ((constraintFlags & ~CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0
+                    || (flags & ~(FLAG_EXPEDITED | FLAG_EXEMPT_FROM_APP_STANDBY)) != 0) {
                 throw new IllegalArgumentException(
                         "An expedited job can only have network and storage-not-low constraints");
             }
@@ -2152,6 +2213,33 @@
                         "A data transfer job must specify a valid network type");
             }
         }
+
+        if (isUserInitiated) {
+            if (hasEarlyConstraint) {
+                throw new IllegalArgumentException("A user-initiated job cannot have a time delay");
+            }
+            if (hasLateConstraint) {
+                throw new IllegalArgumentException("A user-initiated job cannot have a deadline");
+            }
+            if (isPeriodic) {
+                throw new IllegalArgumentException("A user-initiated job cannot be periodic");
+            }
+            if ((flags & FLAG_PREFETCH) != 0) {
+                throw new IllegalArgumentException(
+                        "A user-initiated job cannot also be a prefetch job");
+            }
+            if (mPriority != PRIORITY_MAX) {
+                throw new IllegalArgumentException("A user-initiated job must be max priority.");
+            }
+            if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
+                throw new IllegalArgumentException(
+                        "A user-initiated job cannot have a device-idle constraint");
+            }
+            if (triggerContentUris != null && triggerContentUris.length > 0) {
+                throw new IllegalArgumentException(
+                        "Can't call addTriggerContentUri() on a user-initiated job");
+            }
+        }
     }
 
     /**
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 30986dd..651853b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -761,8 +761,8 @@
             if (js != null) {
                 mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
                 assignment.workType = jsc.getRunningJobWorkType();
-                if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
-                    info.numRunningTopEj++;
+                if (js.startedWithImmediacyPrivilege) {
+                    info.numRunningImmediacyPrivileged++;
                 }
             }
 
@@ -829,11 +829,9 @@
                 continue;
             }
 
-            final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob()
-                    && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP;
+            final boolean hasImmediacyPrivilege = hasImmediacyPrivilegeLocked(nextPending);
             if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
-                Slog.w(TAG, "Already running similar " + (isTopEj ? "TOP-EJ" : "job")
-                        + " to: " + nextPending);
+                Slog.w(TAG, "Already running similar job to: " + nextPending);
             }
 
             // Factoring minChangedWaitingTimeMs into the min waiting time effectively limits
@@ -876,23 +874,25 @@
                     final JobStatus runningJob = assignment.context.getRunningJobLocked();
                     // Maybe stop the job if it has had its day in the sun. Only allow replacing
                     // for one of the following conditions:
-                    // 1. We're putting in the current TOP app's EJ
+                    // 1. We're putting in a job that has the privilege of running immediately
                     // 2. There aren't too many jobs running AND the current job started when the
                     //    app was in the background
                     // 3. There aren't too many jobs running AND the current job started when the
                     //    app was on TOP, but the app has since left TOP
                     // 4. There aren't too many jobs running AND the current job started when the
-                    //    app was on TOP, the app is still TOP, but there are too many TOP+EJs
+                    //    app was on TOP, the app is still TOP, but there are too many
+                    //    immediacy-privileged jobs
                     //    running (because we don't want them to starve out other apps and the
                     //    current job has already run for the minimum guaranteed time).
                     // 5. This new job could be waiting for too long for a slot to open up
-                    boolean canReplace = isTopEj; // Case 1
+                    boolean canReplace = hasImmediacyPrivilege; // Case 1
                     if (!canReplace && !isInOverage) {
                         final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
                         canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2
                                 || currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
                                 // Case 4
-                                || info.numRunningTopEj > .5 * mWorkTypeConfig.getMaxTotal();
+                                || info.numRunningImmediacyPrivileged
+                                        > (mWorkTypeConfig.getMaxTotal() / 2);
                     }
                     if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5
                         if (nextPending.shouldTreatAsExpeditedJob()) {
@@ -919,7 +919,7 @@
                     }
                 }
             }
-            if (selectedContext == null && (!isInOverage || isTopEj)) {
+            if (selectedContext == null && (!isInOverage || hasImmediacyPrivilege)) {
                 int lowestBiasSeen = Integer.MAX_VALUE;
                 long newMinPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
                 for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
@@ -962,12 +962,13 @@
                     info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
                 }
             }
-            // Make sure to run EJs for the TOP app immediately.
-            if (isTopEj) {
+            // Make sure to run jobs with special privilege immediately.
+            if (hasImmediacyPrivilege) {
                 if (selectedContext != null
                         && selectedContext.context.getRunningJobLocked() != null) {
-                    // We're "replacing" a currently running job, but we want TOP EJs to start
-                    // immediately, so we'll start the EJ on a fresh available context and
+                    // We're "replacing" a currently running job, but we want immediacy-privileged
+                    // jobs to start immediately, so we'll start the privileged jobs on a fresh
+                    // available context and
                     // stop this currently running job to replace in two steps.
                     changed.add(selectedContext);
                     projectedRunningCount--;
@@ -1029,6 +1030,7 @@
                     projectedRunningCount--;
                 }
                 if (selectedContext.newJob != null) {
+                    selectedContext.newJob.startedWithImmediacyPrivilege = hasImmediacyPrivilege;
                     projectedRunningCount++;
                     minChangedWaitingTimeMs = Math.min(minChangedWaitingTimeMs,
                             mService.getMinJobExecutionGuaranteeMs(selectedContext.newJob));
@@ -1103,6 +1105,18 @@
         mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
     }
 
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job) {
+        // EJs & user-initiated jobs for the TOP app should run immediately.
+        // However, even for user-initiated jobs, if the app has not recently been in TOP or BAL
+        // state, we don't give the immediacy privilege so that we can try and maintain
+        // reasonably concurrency behavior.
+        return job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP
+                // TODO(): include BAL state for user-initiated jobs
+                && (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiated());
+    }
+
     @GuardedBy("mLock")
     void onUidBiasChangedLocked(int prevBias, int newBias) {
         if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) {
@@ -1361,7 +1375,7 @@
         mActiveServices.remove(worker);
         if (mIdleContexts.size() < MAX_RETAINED_OBJECTS) {
             // Don't need to save all new contexts, but keep some extra around in case we need
-            // extras for another TOP+EJ overage.
+            // extras for another immediacy privileged overage.
             mIdleContexts.add(worker);
         } else {
             mNumDroppedContexts++;
@@ -1403,7 +1417,8 @@
             }
             if (respectConcurrencyLimit) {
                 worker.clearPreferredUid();
-                // We're over the limit (because the TOP app scheduled a lot of EJs), but we should
+                // We're over the limit (because there were a lot of immediacy-privileged jobs
+                // scheduled), but we should
                 // be able to stop the other jobs soon so don't start running anything new until we
                 // get back below the limit.
                 noteConcurrency();
@@ -1627,17 +1642,17 @@
                 }
             } else if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) {
                 return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue";
-            } else if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
-                // Try not to let TOP + EJ starve out other apps.
-                int topEjCount = 0;
+            } else if (js.startedWithImmediacyPrivilege) {
+                // Try not to let jobs with immediacy privilege starve out other apps.
+                int immediacyPrivilegeCount = 0;
                 for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
                     JobStatus j = mRunningJobs.valueAt(r);
-                    if (j.startedAsExpeditedJob && j.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
-                        topEjCount++;
+                    if (j.startedWithImmediacyPrivilege) {
+                        immediacyPrivilegeCount++;
                     }
                 }
-                if (topEjCount > .5 * mWorkTypeConfig.getMaxTotal()) {
-                    return "prevent top EJ dominance";
+                if (immediacyPrivilegeCount > mWorkTypeConfig.getMaxTotal() / 2) {
+                    return "prevent immediacy privilege dominance";
                 }
             }
             // No other pending EJs. Return null so we don't let regular jobs preempt an EJ.
@@ -1688,6 +1703,40 @@
         return foundSome;
     }
 
+    /**
+     * Returns the estimated network bytes if the job is running. Returns {@code null} if the job
+     * isn't running.
+     */
+    @Nullable
+    @GuardedBy("mLock")
+    Pair<Long, Long> getEstimatedNetworkBytesLocked(String pkgName, int uid, int jobId) {
+        for (int i = 0; i < mActiveServices.size(); i++) {
+            final JobServiceContext jc = mActiveServices.get(i);
+            final JobStatus js = jc.getRunningJobLocked();
+            if (js != null && js.matches(uid, jobId) && js.getSourcePackageName().equals(pkgName)) {
+                return jc.getEstimatedNetworkBytes();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the transferred network bytes if the job is running. Returns {@code null} if the job
+     * isn't running.
+     */
+    @Nullable
+    @GuardedBy("mLock")
+    Pair<Long, Long> getTransferredNetworkBytesLocked(String pkgName, int uid, int jobId) {
+        for (int i = 0; i < mActiveServices.size(); i++) {
+            final JobServiceContext jc = mActiveServices.get(i);
+            final JobStatus js = jc.getRunningJobLocked();
+            if (js != null && js.matches(uid, jobId) && js.getSourcePackageName().equals(pkgName)) {
+                return jc.getTransferredNetworkBytes();
+            }
+        }
+        return null;
+    }
+
     @NonNull
     private JobServiceContext createNewJobServiceContext() {
         return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator,
@@ -2566,11 +2615,11 @@
     @VisibleForTesting
     static final class AssignmentInfo {
         public long minPreferredUidOnlyWaitingTimeMs;
-        public int numRunningTopEj;
+        public int numRunningImmediacyPrivileged;
 
         void clear() {
             minPreferredUidOnlyWaitingTimeMs = 0;
-            numRunningTopEj = 0;
+            numRunningImmediacyPrivileged = 0;
         }
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 6f58c7ce..e9b9660 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -86,6 +86,7 @@
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -2850,8 +2851,8 @@
                 }
 
                 final boolean shouldForceBatchJob;
-                if (job.shouldTreatAsExpeditedJob()) {
-                    // Never batch expedited jobs, even for RESTRICTED apps.
+                if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiated()) {
+                    // Never batch expedited or user-initiated jobs, even for RESTRICTED apps.
                     shouldForceBatchJob = false;
                 } else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
                     // Restricted jobs must always be batched
@@ -3212,14 +3213,23 @@
     /** Returns the maximum amount of time this job could run for. */
     public long getMaxJobExecutionTimeMs(JobStatus job) {
         synchronized (mLock) {
-            final boolean shouldTreatAsDataTransfer = job.getJob().isDataTransfer()
-                    && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
+            final boolean allowLongerJob;
+            final boolean isDataTransfer = job.getJob().isDataTransfer();
+            if (isDataTransfer || job.shouldTreatAsUserInitiated()) {
+                allowLongerJob =
+                        checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
+            } else {
+                allowLongerJob = false;
+            }
             if (job.shouldTreatAsUserInitiated()) {
-                if (shouldTreatAsDataTransfer) {
+                if (isDataTransfer && allowLongerJob) {
                     return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
                 }
-                return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
-            } else if (shouldTreatAsDataTransfer) {
+                if (allowLongerJob) {
+                    return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
+                }
+                return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
+            } else if (isDataTransfer && allowLongerJob) {
                 return mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS;
             }
             return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
@@ -4159,6 +4169,100 @@
         }
     }
 
+    int getEstimatedNetworkBytes(PrintWriter pw, String pkgName, int userId, int jobId,
+            int byteOption) {
+        try {
+            final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
+                    userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
+            if (uid < 0) {
+                pw.print("unknown(");
+                pw.print(pkgName);
+                pw.println(")");
+                return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
+            }
+
+            synchronized (mLock) {
+                final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
+                if (DEBUG) {
+                    Slog.d(TAG, "get-estimated-network-bytes " + uid + "/" + jobId + ": " + js);
+                }
+                if (js == null) {
+                    pw.print("unknown("); UserHandle.formatUid(pw, uid);
+                    pw.print("/jid"); pw.print(jobId); pw.println(")");
+                    return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
+                }
+
+                final long downloadBytes;
+                final long uploadBytes;
+                final Pair<Long, Long> bytes =
+                        mConcurrencyManager.getEstimatedNetworkBytesLocked(pkgName, uid, jobId);
+                if (bytes == null) {
+                    downloadBytes = js.getEstimatedNetworkDownloadBytes();
+                    uploadBytes = js.getEstimatedNetworkUploadBytes();
+                } else {
+                    downloadBytes = bytes.first;
+                    uploadBytes = bytes.second;
+                }
+                if (byteOption == JobSchedulerShellCommand.BYTE_OPTION_DOWNLOAD) {
+                    pw.println(downloadBytes);
+                } else {
+                    pw.println(uploadBytes);
+                }
+                pw.println();
+            }
+        } catch (RemoteException e) {
+            // can't happen
+        }
+        return 0;
+    }
+
+    int getTransferredNetworkBytes(PrintWriter pw, String pkgName, int userId, int jobId,
+            int byteOption) {
+        try {
+            final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
+                    userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
+            if (uid < 0) {
+                pw.print("unknown(");
+                pw.print(pkgName);
+                pw.println(")");
+                return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
+            }
+
+            synchronized (mLock) {
+                final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
+                if (DEBUG) {
+                    Slog.d(TAG, "get-transferred-network-bytes " + uid + "/" + jobId + ": " + js);
+                }
+                if (js == null) {
+                    pw.print("unknown("); UserHandle.formatUid(pw, uid);
+                    pw.print("/jid"); pw.print(jobId); pw.println(")");
+                    return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
+                }
+
+                final long downloadBytes;
+                final long uploadBytes;
+                final Pair<Long, Long> bytes =
+                        mConcurrencyManager.getTransferredNetworkBytesLocked(pkgName, uid, jobId);
+                if (bytes == null) {
+                    downloadBytes = 0;
+                    uploadBytes = 0;
+                } else {
+                    downloadBytes = bytes.first;
+                    uploadBytes = bytes.second;
+                }
+                if (byteOption == JobSchedulerShellCommand.BYTE_OPTION_DOWNLOAD) {
+                    pw.println(downloadBytes);
+                } else {
+                    pw.println(uploadBytes);
+                }
+                pw.println();
+            }
+        } catch (RemoteException e) {
+            // can't happen
+        }
+        return 0;
+    }
+
     private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
         // Returns true if both the appop and permission are granted.
         return PermissionChecker.checkPermissionForPreflight(getTestableContext(),
@@ -4171,6 +4275,16 @@
         return mConnectivityController;
     }
 
+    @VisibleForTesting
+    protected QuotaController getQuotaController() {
+        return mQuotaController;
+    }
+
+    @VisibleForTesting
+    protected TareController getTareController() {
+        return mTareController;
+    }
+
     // Shell command infrastructure
     int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
         try {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 27268d2..36ba8dd 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -32,6 +32,9 @@
     public static final int CMD_ERR_NO_JOB = -1001;
     public static final int CMD_ERR_CONSTRAINTS = -1002;
 
+    static final int BYTE_OPTION_DOWNLOAD = 0;
+    static final int BYTE_OPTION_UPLOAD = 1;
+
     JobSchedulerService mInternal;
     IPackageManager mPM;
 
@@ -59,10 +62,18 @@
                     return getBatteryCharging(pw);
                 case "get-battery-not-low":
                     return getBatteryNotLow(pw);
+                case "get-estimated-download-bytes":
+                    return getEstimatedNetworkBytes(pw, BYTE_OPTION_DOWNLOAD);
+                case "get-estimated-upload-bytes":
+                    return getEstimatedNetworkBytes(pw, BYTE_OPTION_UPLOAD);
                 case "get-storage-seq":
                     return getStorageSeq(pw);
                 case "get-storage-not-low":
                     return getStorageNotLow(pw);
+                case "get-transferred-download-bytes":
+                    return getTransferredNetworkBytes(pw, BYTE_OPTION_DOWNLOAD);
+                case "get-transferred-upload-bytes":
+                    return getTransferredNetworkBytes(pw, BYTE_OPTION_UPLOAD);
                 case "get-job-state":
                     return getJobState(pw);
                 case "heartbeat":
@@ -304,6 +315,43 @@
         return 0;
     }
 
+    private int getEstimatedNetworkBytes(PrintWriter pw, int byteOption) throws Exception {
+        checkPermission("get estimated bytes");
+
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-u":
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+
+                default:
+                    pw.println("Error: unknown option '" + opt + "'");
+                    return -1;
+            }
+        }
+
+        if (userId == UserHandle.USER_CURRENT) {
+            userId = ActivityManager.getCurrentUser();
+        }
+
+        final String pkgName = getNextArgRequired();
+        final String jobIdStr = getNextArgRequired();
+        final int jobId = Integer.parseInt(jobIdStr);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            int ret = mInternal.getEstimatedNetworkBytes(pw, pkgName, userId, jobId, byteOption);
+            printError(ret, pkgName, userId, jobId);
+            return ret;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private int getStorageSeq(PrintWriter pw) {
         int seq = mInternal.getStorageSeq();
         pw.println(seq);
@@ -316,8 +364,45 @@
         return 0;
     }
 
+    private int getTransferredNetworkBytes(PrintWriter pw, int byteOption) throws Exception {
+        checkPermission("get transferred bytes");
+
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-u":
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+
+                default:
+                    pw.println("Error: unknown option '" + opt + "'");
+                    return -1;
+            }
+        }
+
+        if (userId == UserHandle.USER_CURRENT) {
+            userId = ActivityManager.getCurrentUser();
+        }
+
+        final String pkgName = getNextArgRequired();
+        final String jobIdStr = getNextArgRequired();
+        final int jobId = Integer.parseInt(jobIdStr);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            int ret = mInternal.getTransferredNetworkBytes(pw, pkgName, userId, jobId, byteOption);
+            printError(ret, pkgName, userId, jobId);
+            return ret;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private int getJobState(PrintWriter pw) throws Exception {
-        checkPermission("force timeout jobs");
+        checkPermission("get job state");
 
         int userId = UserHandle.USER_SYSTEM;
 
@@ -473,10 +558,30 @@
         pw.println("    Return whether the battery is currently considered to be charging.");
         pw.println("  get-battery-not-low");
         pw.println("    Return whether the battery is currently considered to not be low.");
+        pw.println("  get-estimated-download-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+        pw.println("    Return the most recent estimated download bytes for the job.");
+        pw.println("    Options:");
+        pw.println("      -u or --user: specify which user's job is to be run; the default is");
+        pw.println("         the primary or system user");
+        pw.println("  get-estimated-upload-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+        pw.println("    Return the most recent estimated upload bytes for the job.");
+        pw.println("    Options:");
+        pw.println("      -u or --user: specify which user's job is to be run; the default is");
+        pw.println("         the primary or system user");
         pw.println("  get-storage-seq");
         pw.println("    Return the last storage update sequence number that was received.");
         pw.println("  get-storage-not-low");
         pw.println("    Return whether storage is currently considered to not be low.");
+        pw.println("  get-transferred-download-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+        pw.println("    Return the most recent transferred download bytes for the job.");
+        pw.println("    Options:");
+        pw.println("      -u or --user: specify which user's job is to be run; the default is");
+        pw.println("         the primary or system user");
+        pw.println("  get-transferred-upload-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+        pw.println("    Return the most recent transferred upload bytes for the job.");
+        pw.println("    Options:");
+        pw.println("      -u or --user: specify which user's job is to be run; the default is");
+        pw.println("         the primary or system user");
         pw.println("  get-job-state [-u | --user USER_ID] PACKAGE JOB_ID");
         pw.println("    Return the current state of a job, may be any combination of:");
         pw.println("      pending: currently on the pending list, waiting to be active");
@@ -493,5 +598,4 @@
         pw.println("    Trigger wireless charging dock state.  Active by default.");
         pw.println();
     }
-
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index df47f17..0dcfd24 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -49,6 +49,7 @@
 import android.os.UserHandle;
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.TimeUtils;
 
@@ -171,6 +172,11 @@
     /** The absolute maximum amount of time the job can run */
     private long mMaxExecutionTimeMillis;
 
+    private long mEstimatedDownloadBytes;
+    private long mEstimatedUploadBytes;
+    private long mTransferredDownloadBytes;
+    private long mTransferredUploadBytes;
+
     /**
      * The stop reason for a pending cancel. If there's not pending cancel, then the value should be
      * {@link JobParameters#STOP_REASON_UNDEFINED}.
@@ -306,6 +312,9 @@
             mMinExecutionGuaranteeMillis = mService.getMinJobExecutionGuaranteeMs(job);
             mMaxExecutionTimeMillis =
                     Math.max(mService.getMaxJobExecutionTimeMs(job), mMinExecutionGuaranteeMillis);
+            mEstimatedDownloadBytes = job.getEstimatedNetworkDownloadBytes();
+            mEstimatedUploadBytes = job.getEstimatedNetworkUploadBytes();
+            mTransferredDownloadBytes = mTransferredUploadBytes = 0;
 
             final long whenDeferred = job.getWhenStandbyDeferred();
             if (whenDeferred > 0) {
@@ -524,6 +533,16 @@
         return false;
     }
 
+    @GuardedBy("mLock")
+    Pair<Long, Long> getEstimatedNetworkBytes() {
+        return Pair.create(mEstimatedDownloadBytes, mEstimatedUploadBytes);
+    }
+
+    @GuardedBy("mLock")
+    Pair<Long, Long> getTransferredNetworkBytes() {
+        return Pair.create(mTransferredDownloadBytes, mTransferredUploadBytes);
+    }
+
     void doJobFinished(JobCallback cb, int jobId, boolean reschedule) {
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -541,14 +560,26 @@
         }
     }
 
-    private void doAcknowledgeGetTransferredDownloadBytesMessage(JobCallback jobCallback, int jobId,
+    private void doAcknowledgeGetTransferredDownloadBytesMessage(JobCallback cb, int jobId,
             int workId, @BytesLong long transferredBytes) {
         // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+        synchronized (mLock) {
+            if (!verifyCallerLocked(cb)) {
+                return;
+            }
+            mTransferredDownloadBytes = transferredBytes;
+        }
     }
 
-    private void doAcknowledgeGetTransferredUploadBytesMessage(JobCallback jobCallback, int jobId,
+    private void doAcknowledgeGetTransferredUploadBytesMessage(JobCallback cb, int jobId,
             int workId, @BytesLong long transferredBytes) {
         // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+        synchronized (mLock) {
+            if (!verifyCallerLocked(cb)) {
+                return;
+            }
+            mTransferredUploadBytes = transferredBytes;
+        }
     }
 
     void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
@@ -603,6 +634,30 @@
         }
     }
 
+    private void doUpdateEstimatedNetworkBytes(JobCallback cb, int jobId,
+            @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+        // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+        synchronized (mLock) {
+            if (!verifyCallerLocked(cb)) {
+                return;
+            }
+            mEstimatedDownloadBytes = downloadBytes;
+            mEstimatedUploadBytes = uploadBytes;
+        }
+    }
+
+    private void doUpdateTransferredNetworkBytes(JobCallback cb, int jobId,
+            @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+        // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+        synchronized (mLock) {
+            if (!verifyCallerLocked(cb)) {
+                return;
+            }
+            mTransferredDownloadBytes = downloadBytes;
+            mTransferredUploadBytes = uploadBytes;
+        }
+    }
+
     private void doSetNotification(JobCallback cb, int jodId, int notificationId,
             Notification notification, int jobEndNotificationPolicy) {
         final int callingPid = Binder.getCallingPid();
@@ -627,16 +682,6 @@
         }
     }
 
-    private void doUpdateTransferredNetworkBytes(JobCallback jobCallback, int jobId,
-            @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
-        // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
-    }
-
-    private void doUpdateEstimatedNetworkBytes(JobCallback jobCallback, int jobId,
-            @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
-        // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
-    }
-
     /**
      * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
      * we intend to send to the client - we stop sending work when the service is unbound so until
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 6166921..3610b0a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -149,11 +149,13 @@
             //   2. Waiting connectivity jobs would be ready with connectivity
             //   3. An existing network satisfies a waiting connectivity job's requirements
             //   4. TOP proc state
-            //   5. Existence of treat-as-EJ EJs (not just requested EJs)
-            //   6. FGS proc state
-            //   7. EJ enqueue time
-            //   8. Any other important job priorities/proc states
-            //   9. Enqueue time
+            //   5. Existence of treat-as-UI UIJs (not just requested UIJs)
+            //   6. Existence of treat-as-EJ EJs (not just requested EJs)
+            //   7. FGS proc state
+            //   8. UIJ enqueue time
+            //   9. EJ enqueue time
+            //   10. Any other important job priorities/proc states
+            //   11. Enqueue time
             // TODO: maybe consider number of jobs
             // TODO: consider IMPORTANT_WHILE_FOREGROUND bit
             final int runningPriority = prioritizeExistenceOver(0,
@@ -181,8 +183,13 @@
             if (topPriority != 0) {
                 return topPriority;
             }
-            // They're either both TOP or both not TOP. Prioritize the app that has runnable EJs
+            // They're either both TOP or both not TOP. Prioritize the app that has runnable UIJs
             // pending.
+            final int uijPriority = prioritizeExistenceOver(0, us1.numUIJs, us2.numUIJs);
+            if (uijPriority != 0) {
+                return uijPriority;
+            }
+            // Still equivalent. Prioritize the app that has runnable EJs pending.
             final int ejPriority = prioritizeExistenceOver(0, us1.numEJs, us2.numEJs);
             if (ejPriority != 0) {
                 return ejPriority;
@@ -195,6 +202,12 @@
             if (fgsPriority != 0) {
                 return fgsPriority;
             }
+            // Order them by UIJ enqueue time to help provide low UIJ latency.
+            if (us1.earliestUIJEnqueueTime < us2.earliestUIJEnqueueTime) {
+                return -1;
+            } else if (us1.earliestUIJEnqueueTime > us2.earliestUIJEnqueueTime) {
+                return 1;
+            }
             // Order them by EJ enqueue time to help provide low EJ latency.
             if (us1.earliestEJEnqueueTime < us2.earliestEJEnqueueTime) {
                 return -1;
@@ -414,7 +427,7 @@
         final UidStats uidStats =
                 getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
 
-        if (jobStatus.shouldTreatAsExpeditedJob()) {
+        if (jobStatus.shouldTreatAsExpeditedJob() && jobStatus.shouldTreatAsUserInitiated()) {
             if (!jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)) {
                 // Don't request a direct hole through any of the firewalls. Instead, mark the
                 // constraint as satisfied if the network is available, and the job will get
@@ -936,10 +949,12 @@
             if (us.lastUpdatedElapsed + MIN_STATS_UPDATE_INTERVAL_MS < nowElapsed) {
                 us.earliestEnqueueTime = Long.MAX_VALUE;
                 us.earliestEJEnqueueTime = Long.MAX_VALUE;
+                us.earliestUIJEnqueueTime = Long.MAX_VALUE;
                 us.numReadyWithConnectivity = 0;
                 us.numRequestedNetworkAvailable = 0;
                 us.numRegular = 0;
                 us.numEJs = 0;
+                us.numUIJs = 0;
 
                 for (int j = 0; j < jobs.size(); ++j) {
                     JobStatus job = jobs.valueAt(j);
@@ -956,10 +971,15 @@
                         if (job.shouldTreatAsExpeditedJob() || job.startedAsExpeditedJob) {
                             us.earliestEJEnqueueTime =
                                     Math.min(us.earliestEJEnqueueTime, job.enqueueTime);
+                        } else if (job.shouldTreatAsUserInitiated()) {
+                            us.earliestUIJEnqueueTime =
+                                    Math.min(us.earliestUIJEnqueueTime, job.enqueueTime);
                         }
                     }
                     if (job.shouldTreatAsExpeditedJob() || job.startedAsExpeditedJob) {
                         us.numEJs++;
+                    } else if (job.shouldTreatAsUserInitiated()) {
+                        us.numUIJs++;
                     } else {
                         us.numRegular++;
                     }
@@ -1466,8 +1486,10 @@
         public int numRequestedNetworkAvailable;
         public int numEJs;
         public int numRegular;
+        public int numUIJs;
         public long earliestEnqueueTime;
         public long earliestEJEnqueueTime;
+        public long earliestUIJEnqueueTime;
         public long lastUpdatedElapsed;
 
         private UidStats(int uid) {
@@ -1485,6 +1507,7 @@
             pw.print("#reg", numRegular);
             pw.print("earliestEnqueue", earliestEnqueueTime);
             pw.print("earliestEJEnqueue", earliestEJEnqueueTime);
+            pw.print("earliestUIJEnqueue", earliestUIJEnqueueTime);
             pw.print("updated=");
             TimeUtils.formatDuration(lastUpdatedElapsed - nowElapsed, pw);
             pw.println("}");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 419127e..251a4da 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -388,6 +388,8 @@
      */
     public boolean startedAsExpeditedJob = false;
 
+    public boolean startedWithImmediacyPrivilege = false;
+
     // If non-null, this is work that has been enqueued for the job.
     public ArrayList<JobWorkItem> pendingWork;
 
@@ -1369,9 +1371,7 @@
      * @return true if this is a job whose execution should be made visible to the user.
      */
     public boolean isUserVisibleJob() {
-        // TODO(255767350): limit to user-initiated jobs
-        // Placeholder implementation until we have the code in
-        return shouldTreatAsExpeditedJob();
+        return shouldTreatAsUserInitiated();
     }
 
     /**
@@ -1382,12 +1382,14 @@
         return appHasDozeExemption
                 || (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
                 || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
+                || shouldTreatAsUserInitiated()
                 && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
     }
 
     boolean canRunInBatterySaver() {
         return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0
                 || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
+                || shouldTreatAsUserInitiated()
                 && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0);
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index d8206ad..404186d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -641,6 +641,9 @@
             mTopStartedJobs.add(jobStatus);
             // Top jobs won't count towards quota so there's no need to involve the Timer.
             return;
+        } else if (jobStatus.shouldTreatAsUserInitiated()) {
+            // User-initiated jobs won't count towards quota.
+            return;
         }
 
         final int userId = jobStatus.getSourceUserId();
@@ -892,7 +895,8 @@
         //   1. it was started while the app was in the TOP state
         //   2. the app is currently in the foreground
         //   3. the app overall is within its quota
-        return isTopStartedJobLocked(jobStatus)
+        return jobStatus.shouldTreatAsUserInitiated()
+                || isTopStartedJobLocked(jobStatus)
                 || isUidInForeground(jobStatus.getSourceUid())
                 || isWithinQuotaLocked(
                 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
@@ -2116,6 +2120,13 @@
         }
 
         void startTrackingJobLocked(@NonNull JobStatus jobStatus) {
+            if (jobStatus.shouldTreatAsUserInitiated()) {
+                if (DEBUG) {
+                    Slog.v(TAG, "Timer ignoring " + jobStatus.toShortString()
+                            + " because it's user-initiated");
+                }
+                return;
+            }
             if (isTopStartedJobLocked(jobStatus)) {
                 // We intentionally don't pay attention to fg state changes after a TOP job has
                 // started.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
index cafb02d..de065b2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
@@ -313,6 +313,11 @@
     @GuardedBy("mLock")
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
         final long nowElapsed = sElapsedRealtimeClock.millis();
+        if (jobStatus.shouldTreatAsUserInitiated()) {
+            // User-initiated jobs should always be allowed to run.
+            jobStatus.setTareWealthConstraintSatisfied(nowElapsed, true);
+            return;
+        }
         jobStatus.setTareWealthConstraintSatisfied(nowElapsed, hasEnoughWealthLocked(jobStatus));
         setExpeditedTareApproved(jobStatus, nowElapsed,
                 jobStatus.isRequestedExpeditedJob() && canAffordExpeditedBillLocked(jobStatus));
@@ -326,6 +331,11 @@
     @Override
     @GuardedBy("mLock")
     public void prepareForExecutionLocked(JobStatus jobStatus) {
+        if (jobStatus.shouldTreatAsUserInitiated()) {
+            // TODO(202954395): consider noting execution with the EconomyManager even though it
+            //                  won't affect this job
+            return;
+        }
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
         ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap =
@@ -355,6 +365,9 @@
     @Override
     @GuardedBy("mLock")
     public void unprepareFromExecutionLocked(JobStatus jobStatus) {
+        if (jobStatus.shouldTreatAsUserInitiated()) {
+            return;
+        }
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
         // If this method is called, then jobStatus.madeActive was never updated, so don't use it
@@ -384,6 +397,9 @@
     @Override
     @GuardedBy("mLock")
     public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) {
+        if (jobStatus.shouldTreatAsUserInitiated()) {
+            return;
+        }
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
         if (!mTopStartedJobs.remove(jobStatus) && jobStatus.madeActive > 0) {
@@ -637,6 +653,10 @@
         if (!mIsEnabled) {
             return true;
         }
+        if (jobStatus.shouldTreatAsUserInitiated()) {
+            // Always allow user-initiated jobs.
+            return true;
+        }
         if (mService.getUidBias(jobStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP
                 || isTopStartedJobLocked(jobStatus)) {
             // Jobs for the top app should always be allowed to run, and any jobs started while
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 1a775b4..6869936 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -2187,8 +2187,10 @@
                 }
                 // component-level enable/disable can affect bucketing, so we always
                 // reevaluate that for any PACKAGE_CHANGED
-                mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkgName)
-                    .sendToTarget();
+                if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+                    mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkgName)
+                            .sendToTarget();
+                }
             }
             if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
                     Intent.ACTION_PACKAGE_ADDED.equals(action))) {
diff --git a/core/api/current.txt b/core/api/current.txt
index b31fdc7..877e2d6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -176,6 +176,8 @@
     field public static final String REQUEST_COMPANION_PROFILE_APP_STREAMING = "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING";
     field public static final String REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION = "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION";
     field public static final String REQUEST_COMPANION_PROFILE_COMPUTER = "android.permission.REQUEST_COMPANION_PROFILE_COMPUTER";
+    field public static final String REQUEST_COMPANION_PROFILE_GLASSES = "android.permission.REQUEST_COMPANION_PROFILE_GLASSES";
+    field public static final String REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING = "android.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING";
     field public static final String REQUEST_COMPANION_PROFILE_WATCH = "android.permission.REQUEST_COMPANION_PROFILE_WATCH";
     field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND";
     field public static final String REQUEST_COMPANION_SELF_MANAGED = "android.permission.REQUEST_COMPANION_SELF_MANAGED";
@@ -4148,6 +4150,8 @@
     method @Deprecated public android.app.FragmentManager getFragmentManager();
     method public android.content.Intent getIntent();
     method @Nullable public Object getLastNonConfigurationInstance();
+    method @Nullable public String getLaunchedFromPackage();
+    method public int getLaunchedFromUid();
     method @NonNull public android.view.LayoutInflater getLayoutInflater();
     method @Deprecated public android.app.LoaderManager getLoaderManager();
     method @NonNull public String getLocalClassName();
@@ -4290,6 +4294,7 @@
     method @Deprecated public final void removeDialog(int);
     method public void reportFullyDrawn();
     method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent);
+    method public void requestFullscreenMode(@NonNull int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
     method public final void requestPermissions(@NonNull String[], int);
     method public final void requestShowKeyboardShortcuts();
     method @Deprecated public boolean requestVisibleBehind(boolean);
@@ -4376,6 +4381,8 @@
     field public static final int DEFAULT_KEYS_SEARCH_LOCAL = 3; // 0x3
     field public static final int DEFAULT_KEYS_SHORTCUT = 2; // 0x2
     field protected static final int[] FOCUSED_STATE_SET;
+    field public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1; // 0x1
+    field public static final int FULLSCREEN_MODE_REQUEST_EXIT = 0; // 0x0
     field public static final int RESULT_CANCELED = 0; // 0x0
     field public static final int RESULT_FIRST_USER = 1; // 0x1
     field public static final int RESULT_OK = -1; // 0xffffffff
@@ -4617,6 +4624,7 @@
     method public android.app.ActivityOptions setLaunchDisplayId(int);
     method public android.app.ActivityOptions setLockTaskEnabled(boolean);
     method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
+    method @NonNull public android.app.ActivityOptions setShareIdentityEnabled(boolean);
     method @NonNull public android.app.ActivityOptions setSplashScreenStyle(int);
     method public android.os.Bundle toBundle();
     method public void update(android.app.ActivityOptions);
@@ -9058,6 +9066,8 @@
     field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING) public static final String DEVICE_PROFILE_APP_STREAMING = "android.app.role.COMPANION_DEVICE_APP_STREAMING";
     field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) public static final String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION = "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION";
     field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER) public static final String DEVICE_PROFILE_COMPUTER = "android.app.role.COMPANION_DEVICE_COMPUTER";
+    field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_GLASSES) public static final String DEVICE_PROFILE_GLASSES = "android.app.role.COMPANION_DEVICE_GLASSES";
+    field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING) public static final String DEVICE_PROFILE_NEARBY_DEVICE_STREAMING = "android.app.role.COMPANION_DEVICE_NEARBY_DEVICE_STREAMING";
     field public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH";
   }
 
@@ -9177,8 +9187,8 @@
 
   public final class VirtualDeviceManager {
     method @NonNull public java.util.List<android.companion.virtual.VirtualDevice> getVirtualDevices();
-    field public static final int DEFAULT_DEVICE_ID = 0; // 0x0
-    field public static final int INVALID_DEVICE_ID = -1; // 0xffffffff
+    field public static final int DEVICE_ID_DEFAULT = 0; // 0x0
+    field public static final int DEVICE_ID_INVALID = -1; // 0xffffffff
   }
 
 }
@@ -10585,6 +10595,7 @@
     field public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
     field public static final String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE";
     field public static final String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI";
+    field public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
     field public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
     field public static final String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME";
     field public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
@@ -11714,9 +11725,12 @@
     method public void unregisterSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
     method public void updateSessionAppIcon(int, @Nullable android.graphics.Bitmap);
     method public void updateSessionAppLabel(int, @Nullable CharSequence);
+    method public void waitForInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull android.content.IntentSender, long);
     field public static final String ACTION_SESSION_COMMITTED = "android.content.pm.action.SESSION_COMMITTED";
     field public static final String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS";
     field public static final String ACTION_SESSION_UPDATED = "android.content.pm.action.SESSION_UPDATED";
+    field public static final String EXTRA_INSTALL_CONSTRAINTS = "android.content.pm.extra.INSTALL_CONSTRAINTS";
+    field public static final String EXTRA_INSTALL_CONSTRAINTS_RESULT = "android.content.pm.extra.INSTALL_CONSTRAINTS_RESULT";
     field public static final String EXTRA_OTHER_PACKAGE_NAME = "android.content.pm.extra.OTHER_PACKAGE_NAME";
     field public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
     field public static final String EXTRA_PRE_APPROVAL = "android.content.pm.extra.PRE_APPROVAL";
@@ -12499,7 +12513,7 @@
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
     field @Deprecated @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
     field @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT) public static final int FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT = 4096; // 0x1000
-    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
     field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
     field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
@@ -17932,6 +17946,7 @@
     method public int capture(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.ExtensionCaptureCallback) throws android.hardware.camera2.CameraAccessException;
     method public void close() throws android.hardware.camera2.CameraAccessException;
     method @NonNull public android.hardware.camera2.CameraDevice getDevice();
+    method @Nullable public android.util.Pair<java.lang.Long,java.lang.Long> getRealtimeStillCaptureLatency() throws android.hardware.camera2.CameraAccessException;
     method public int setRepeatingRequest(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.ExtensionCaptureCallback) throws android.hardware.camera2.CameraAccessException;
     method public void stopRepeating() throws android.hardware.camera2.CameraAccessException;
   }
@@ -20514,6 +20529,7 @@
     field public static final int ENCODING_DOLBY_MAT = 19; // 0x13
     field public static final int ENCODING_DOLBY_TRUEHD = 14; // 0xe
     field public static final int ENCODING_DRA = 28; // 0x1c
+    field public static final int ENCODING_DSD = 31; // 0x1f
     field public static final int ENCODING_DTS = 7; // 0x7
     field public static final int ENCODING_DTS_HD = 8; // 0x8
     field public static final int ENCODING_DTS_HD_MA = 29; // 0x1d
@@ -20825,6 +20841,7 @@
     method public int getMixerBehavior();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioMixerAttributes> CREATOR;
+    field public static final int MIXER_BEHAVIOR_BIT_PERFECT = 1; // 0x1
     field public static final int MIXER_BEHAVIOR_DEFAULT = 0; // 0x0
   }
 
@@ -24053,6 +24070,8 @@
     method @NonNull public String getRouteId();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR;
+    field public static final int DISABLE_REASON_AD = 3; // 0x3
+    field public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2; // 0x2
     field public static final int DISABLE_REASON_NONE = 0; // 0x0
     field public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1; // 0x1
     field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
@@ -25445,11 +25464,21 @@
 
   public abstract static class MediaProjection.Callback {
     ctor public MediaProjection.Callback();
+    method public void onCapturedContentResize(int, int);
     method public void onStop();
   }
 
+  public final class MediaProjectionConfig implements android.os.Parcelable {
+    method @NonNull public static android.media.projection.MediaProjectionConfig createConfigForDisplay(@IntRange(from=android.view.Display.DEFAULT_DISPLAY, to=android.view.Display.DEFAULT_DISPLAY) int);
+    method @NonNull public static android.media.projection.MediaProjectionConfig createConfigForUserChoice();
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.projection.MediaProjectionConfig> CREATOR;
+  }
+
   public final class MediaProjectionManager {
-    method public android.content.Intent createScreenCaptureIntent();
+    method @NonNull public android.content.Intent createScreenCaptureIntent();
+    method @NonNull public android.content.Intent createScreenCaptureIntent(@NonNull android.media.projection.MediaProjectionConfig);
     method public android.media.projection.MediaProjection getMediaProjection(int, @NonNull android.content.Intent);
   }
 
@@ -26778,6 +26807,7 @@
     method public boolean onKeyUp(int, @NonNull android.view.KeyEvent);
     method public void onMediaViewSizeChanged(@Px int, @Px int);
     method public void onRecordingStarted(@NonNull String);
+    method public void onRecordingStopped(@NonNull String);
     method public abstract void onRelease();
     method public void onResetInteractiveApp();
     method public abstract boolean onSetSurface(@Nullable android.view.Surface);
@@ -26804,6 +26834,7 @@
     method @CallSuper public void requestCurrentTvInputId();
     method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
     method @CallSuper public void requestStartRecording(@Nullable android.net.Uri);
+    method @CallSuper public void requestStopRecording(@NonNull String);
     method @CallSuper public void requestStreamVolume();
     method @CallSuper public void requestTrackInfoList();
     method @CallSuper public void sendPlaybackCommandRequest(@NonNull String, @Nullable android.os.Bundle);
@@ -26836,6 +26867,7 @@
     method @Nullable public android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
     method public void notifyError(@NonNull String, @NonNull android.os.Bundle);
     method public void notifyRecordingStarted(@NonNull String);
+    method public void notifyRecordingStopped(@NonNull String);
     method public void onAttachedToWindow();
     method public void onDetachedFromWindow();
     method public void onLayout(boolean, int, int, int, int);
@@ -26878,6 +26910,7 @@
     method public void onRequestCurrentTvInputId(@NonNull String);
     method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
     method public void onRequestStartRecording(@NonNull String, @Nullable android.net.Uri);
+    method public void onRequestStopRecording(@NonNull String, @NonNull String);
     method public void onRequestStreamVolume(@NonNull String);
     method public void onRequestTrackInfoList(@NonNull String);
     method public void onSetVideoBounds(@NonNull String, @NonNull android.graphics.Rect);
@@ -27844,6 +27877,7 @@
   public final class VcnConfig implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
+    method @NonNull public java.util.Set<java.lang.Integer> getRestrictedUnderlyingNetworkTransports();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
   }
@@ -27852,6 +27886,7 @@
     ctor public VcnConfig.Builder(@NonNull android.content.Context);
     method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
     method @NonNull public android.net.vcn.VcnConfig build();
+    method @NonNull public android.net.vcn.VcnConfig.Builder setRestrictedUnderlyingNetworkTransports(@NonNull java.util.Set<java.lang.Integer>);
   }
 
   public final class VcnGatewayConnectionConfig {
@@ -27860,13 +27895,17 @@
     method @IntRange(from=0x500) public int getMaxMtu();
     method @NonNull public long[] getRetryIntervalsMillis();
     method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
+    method public boolean hasGatewayOption(int);
+    field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0
   }
 
   public static final class VcnGatewayConnectionConfig.Builder {
     ctor public VcnGatewayConnectionConfig.Builder(@NonNull String, @NonNull android.net.ipsec.ike.IkeTunnelConnectionParams);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(int);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
@@ -36260,7 +36299,7 @@
     field public static final String PARENTAL_CONTROL_LAST_UPDATE = "parental_control_last_update";
     field public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
     field public static final String RTT_CALLING_MODE = "rtt_calling_mode";
-    field public static final String SECURE_FRP_MODE = "secure_frp_mode";
+    field @Deprecated public static final String SECURE_FRP_MODE = "secure_frp_mode";
     field public static final String SELECTED_INPUT_METHOD_SUBTYPE = "selected_input_method_subtype";
     field public static final String SETTINGS_CLASSNAME = "settings_classname";
     field public static final String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
@@ -38256,6 +38295,11 @@
     ctor public AlreadyPersonalizedException(@NonNull String, @NonNull Throwable);
   }
 
+  public final class AuthenticationKeyMetadata {
+    method @NonNull public java.time.Instant getExpirationDate();
+    method @IntRange(from=0) public int getUsageCount();
+  }
+
   public class CipherSuiteNotSupportedException extends android.security.identity.IdentityCredentialException {
     ctor public CipherSuiteNotSupportedException(@NonNull String);
     ctor public CipherSuiteNotSupportedException(@NonNull String, @NonNull Throwable);
@@ -38286,6 +38330,7 @@
   public abstract class CredentialDataResult {
     method @Nullable public abstract byte[] getDeviceMac();
     method @NonNull public abstract byte[] getDeviceNameSpaces();
+    method @Nullable public byte[] getDeviceSignature();
     method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getDeviceSignedEntries();
     method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getIssuerSignedEntries();
     method @NonNull public abstract byte[] getStaticAuthenticationData();
@@ -38322,13 +38367,15 @@
     method @NonNull public byte[] delete(@NonNull byte[]);
     method @Deprecated @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]);
     method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getAuthKeysNeedingCertification();
-    method @NonNull public abstract int[] getAuthenticationDataUsageCount();
+    method @Deprecated @NonNull public abstract int[] getAuthenticationDataUsageCount();
+    method @NonNull public java.util.List<android.security.identity.AuthenticationKeyMetadata> getAuthenticationKeyMetadata();
     method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain();
     method @Deprecated @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException;
     method @NonNull public byte[] proveOwnership(@NonNull byte[]);
     method @Deprecated public abstract void setAllowUsingExhaustedKeys(boolean);
     method @Deprecated public void setAllowUsingExpiredKeys(boolean);
-    method public abstract void setAvailableAuthenticationKeys(int, int);
+    method @Deprecated public abstract void setAvailableAuthenticationKeys(int, int);
+    method public void setAvailableAuthenticationKeys(@IntRange(from=0) int, @IntRange(from=1) int, @IntRange(from=0) long);
     method @Deprecated public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException;
     method @Deprecated public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
     method public void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull java.time.Instant, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
@@ -42002,11 +42049,15 @@
   }
 
   public class CarrierConfigManager {
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfig();
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfig();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public android.os.PersistableBundle getConfig(@NonNull java.lang.String...);
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfigForSubId(int);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfigForSubId(int);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public android.os.PersistableBundle getConfigForSubId(int, @NonNull java.lang.String...);
     method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyConfigChangedForSubId(int);
+    method public void registerCarrierConfigChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.CarrierConfigManager.CarrierConfigChangeListener);
+    method public void unregisterCarrierConfigChangeListener(@NonNull android.telephony.CarrierConfigManager.CarrierConfigChangeListener);
     field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
     field public static final int CARRIER_NR_AVAILABILITY_NSA = 1; // 0x1
     field public static final int CARRIER_NR_AVAILABILITY_SA = 2; // 0x2
@@ -42336,6 +42387,10 @@
     field public static final String KEY_PREFIX = "bsf.";
   }
 
+  public static interface CarrierConfigManager.CarrierConfigChangeListener {
+    method public void onCarrierConfigChanged(int, int, int, int);
+  }
+
   public static final class CarrierConfigManager.Gps {
     field public static final String KEY_PERSIST_LPP_MODE_BOOL = "gps.persist_lpp_mode_bool";
     field public static final String KEY_PREFIX = "gps.";
@@ -45428,6 +45483,7 @@
 package android.telephony.ims.stub {
 
   public class ImsRegistrationImplBase {
+    field public static final int REGISTRATION_TECH_3G = 4; // 0x4
     field public static final int REGISTRATION_TECH_CROSS_SIM = 2; // 0x2
     field public static final int REGISTRATION_TECH_IWLAN = 1; // 0x1
     field public static final int REGISTRATION_TECH_LTE = 0; // 0x0
@@ -49548,6 +49604,7 @@
     field public static final int KEYCODE_PROG_YELLOW = 185; // 0xb9
     field public static final int KEYCODE_Q = 45; // 0x2d
     field public static final int KEYCODE_R = 46; // 0x2e
+    field public static final int KEYCODE_RECENT_APPS = 312; // 0x138
     field public static final int KEYCODE_REFRESH = 285; // 0x11d
     field public static final int KEYCODE_RIGHT_BRACKET = 72; // 0x48
     field public static final int KEYCODE_RO = 217; // 0xd9
@@ -52629,8 +52686,10 @@
   }
 
   public final class WindowMetrics {
-    ctor public WindowMetrics(@NonNull android.graphics.Rect, @NonNull android.view.WindowInsets);
+    ctor @Deprecated public WindowMetrics(@NonNull android.graphics.Rect, @NonNull android.view.WindowInsets);
+    ctor public WindowMetrics(@NonNull android.graphics.Rect, @NonNull android.view.WindowInsets, float);
     method @NonNull public android.graphics.Rect getBounds();
+    method public float getDensity();
     method @NonNull public android.view.WindowInsets getWindowInsets();
   }
 
@@ -52744,12 +52803,14 @@
     method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
     method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
     method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
+    method public void addUiContrastChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener);
     method @ColorInt public int getAccessibilityFocusColor();
     method public int getAccessibilityFocusStrokeWidth();
     method @Deprecated public java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int);
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
     method public int getRecommendedTimeoutMillis(int, int);
+    method @FloatRange(from=-1.0F, to=1.0f) public float getUiContrast();
     method public void interrupt();
     method public static boolean isAccessibilityButtonSupported();
     method public boolean isAudioDescriptionRequested();
@@ -52761,6 +52822,7 @@
     method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
     method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
     method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
+    method public void removeUiContrastChangeListener(@NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener);
     method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
     field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
     field public static final int FLAG_CONTENT_ICONS = 1; // 0x1
@@ -52783,6 +52845,10 @@
     method public void onTouchExplorationStateChanged(boolean);
   }
 
+  public static interface AccessibilityManager.UiContrastChangeListener {
+    method public void onUiContrastChanged(@FloatRange(from=-1.0F, to=1.0f) float);
+  }
+
   public class AccessibilityNodeInfo implements android.os.Parcelable {
     ctor public AccessibilityNodeInfo();
     ctor public AccessibilityNodeInfo(@NonNull android.view.View);
@@ -54523,6 +54589,8 @@
     method @NonNull public android.text.SegmentFinder getGraphemeSegmentFinder();
     method @NonNull public android.text.SegmentFinder getLineSegmentFinder();
     method @NonNull public android.graphics.Matrix getMatrix();
+    method public int getOffsetForPosition(float, float);
+    method @Nullable public int[] getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy);
     method public int getStart();
     method @NonNull public android.text.SegmentFinder getWordSegmentFinder();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 98c78fe..314fd03 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -99,6 +99,10 @@
 
   public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
     method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraryInfos();
+    field public static final int HIDDEN_API_ENFORCEMENT_DEFAULT = -1; // 0xffffffff
+    field public static final int HIDDEN_API_ENFORCEMENT_DISABLED = 0; // 0x0
+    field public static final int HIDDEN_API_ENFORCEMENT_ENABLED = 2; // 0x2
+    field public static final int HIDDEN_API_ENFORCEMENT_JUST_WARN = 1; // 0x1
   }
 
   public abstract class PackageManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 992e524..566ac45 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -352,6 +352,7 @@
     field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
     field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
+    field public static final String WAKEUP_SURFACE_FLINGER = "android.permission.WAKEUP_SURFACE_FLINGER";
     field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
     field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
     field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
@@ -548,6 +549,8 @@
     method @Nullable public static String opToPermission(@NonNull String);
     method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(@NonNull String, int, @Nullable String, int);
     method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(@NonNull String, int, int);
+    method @RequiresPermission(value="android.permission.WATCH_APPOPS", conditional=true) public void startWatchingNoted(@NonNull String[], @NonNull android.app.AppOpsManager.OnOpNotedListener);
+    method public void stopWatchingNoted(@NonNull android.app.AppOpsManager.OnOpNotedListener);
     field public static final int HISTORY_FLAGS_ALL = 3; // 0x3
     field public static final int HISTORY_FLAG_AGGREGATE = 1; // 0x1
     field public static final int HISTORY_FLAG_DISCRETE = 2; // 0x2
@@ -735,6 +738,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalUidOps> CREATOR;
   }
 
+  public static interface AppOpsManager.OnOpNotedListener {
+    method public void onOpNoted(@NonNull String, int, @NonNull String, @Nullable String, int, int);
+  }
+
   public static final class AppOpsManager.OpEntry implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.Map<java.lang.String,android.app.AppOpsManager.AttributedOpEntry> getAttributedOpEntries();
@@ -2993,6 +3000,7 @@
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.input.VirtualMouseConfig);
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualNavigationTouchpad createVirtualNavigationTouchpad(@NonNull android.hardware.input.VirtualNavigationTouchpadConfig);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig);
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method public int getDeviceId();
@@ -3285,6 +3293,7 @@
     field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
     field public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
     field @RequiresPermission(android.Manifest.permission.REVIEW_ACCESSIBILITY_SERVICES) public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES = "android.intent.action.REVIEW_ACCESSIBILITY_SERVICES";
+    field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_APP_DATA_SHARING_UPDATES = "android.intent.action.REVIEW_APP_DATA_SHARING_UPDATES";
     field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_ONGOING_PERMISSION_USAGE = "android.intent.action.REVIEW_ONGOING_PERMISSION_USAGE";
     field public static final String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
     field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_PERMISSION_HISTORY = "android.intent.action.REVIEW_PERMISSION_HISTORY";
@@ -3312,7 +3321,6 @@
     field public static final String EXTRA_INSTANT_APP_TOKEN = "android.intent.extra.INSTANT_APP_TOKEN";
     field public static final String EXTRA_LONG_VERSION_CODE = "android.intent.extra.LONG_VERSION_CODE";
     field public static final String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID";
-    field public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
     field public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME";
     field public static final String EXTRA_REASON = "android.intent.extra.REASON";
     field public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
@@ -4777,6 +4785,24 @@
     method @NonNull public android.hardware.input.VirtualMouseScrollEvent.Builder setYAxisMovement(@FloatRange(from=-1.0F, to=1.0f) float);
   }
 
+  public class VirtualNavigationTouchpad implements java.io.Closeable {
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendTouchEvent(@NonNull android.hardware.input.VirtualTouchEvent);
+  }
+
+  public final class VirtualNavigationTouchpadConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getHeight();
+    method public int getWidth();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualNavigationTouchpadConfig> CREATOR;
+  }
+
+  public static final class VirtualNavigationTouchpadConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualNavigationTouchpadConfig.Builder> {
+    ctor public VirtualNavigationTouchpadConfig.Builder(@IntRange(from=1) int, @IntRange(from=1) int);
+    method @NonNull public android.hardware.input.VirtualNavigationTouchpadConfig build();
+  }
+
   public final class VirtualTouchEvent implements android.os.Parcelable {
     method public int describeContents();
     method public int getAction();
@@ -5268,11 +5294,12 @@
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector> CREATOR;
     field public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; // 0x1
+    field public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14; // 0xe
     field public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6; // 0x6
     field public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8; // 0x8
     field public static final int IDENTIFIER_TYPE_DAB_SCID = 7; // 0x7
-    field public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
-    field public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
+    field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
+    field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
     field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
     field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
     field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
@@ -5309,7 +5336,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector.Identifier> CREATOR;
   }
 
-  @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType {
+  @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType {
   }
 
   @Deprecated @IntDef(prefix={"PROGRAM_TYPE_"}, value={android.hardware.radio.ProgramSelector.PROGRAM_TYPE_INVALID, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DAB, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DRMO, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_SXM}) @IntRange(from=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.ProgramType {
@@ -5524,30 +5551,31 @@
 
   public abstract class RadioTuner {
     ctor public RadioTuner();
-    method public abstract int cancel();
-    method public abstract void cancelAnnouncement();
-    method public abstract void close();
-    method @Deprecated public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]);
-    method @Nullable public android.hardware.radio.ProgramList getDynamicProgramList(@Nullable android.hardware.radio.ProgramList.Filter);
-    method public abstract boolean getMute();
-    method @NonNull public java.util.Map<java.lang.String,java.lang.String> getParameters(@NonNull java.util.List<java.lang.String>);
-    method @Deprecated public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]);
-    method @Deprecated @NonNull public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(@Nullable java.util.Map<java.lang.String,java.lang.String>);
-    method public abstract boolean hasControl();
-    method @Deprecated public abstract boolean isAnalogForced();
-    method @Deprecated public abstract boolean isAntennaConnected();
-    method public boolean isConfigFlagSet(int);
-    method public boolean isConfigFlagSupported(int);
-    method public abstract int scan(int, boolean);
-    method @Deprecated public abstract void setAnalogForced(boolean);
-    method public void setConfigFlag(int, boolean);
-    method @Deprecated public abstract int setConfiguration(android.hardware.radio.RadioManager.BandConfig);
-    method public abstract int setMute(boolean);
-    method @NonNull public java.util.Map<java.lang.String,java.lang.String> setParameters(@NonNull java.util.Map<java.lang.String,java.lang.String>);
-    method public abstract boolean startBackgroundScan();
-    method public abstract int step(int, boolean);
-    method @Deprecated public abstract int tune(int, int);
-    method public abstract void tune(@NonNull android.hardware.radio.ProgramSelector);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int cancel();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void cancelAnnouncement();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void close();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public android.hardware.radio.ProgramList getDynamicProgramList(@Nullable android.hardware.radio.ProgramList.Filter);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean getMute();
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public java.util.Map<java.lang.String,java.lang.String> getParameters(@NonNull java.util.List<java.lang.String>);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(@Nullable java.util.Map<java.lang.String,java.lang.String>);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean hasControl();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean isAnalogForced();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean isAntennaConnected();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public boolean isConfigFlagSet(int);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public boolean isConfigFlagSupported(int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int scan(int, boolean);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public int seek(int, boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void setAnalogForced(boolean);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public void setConfigFlag(int, boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int setConfiguration(android.hardware.radio.RadioManager.BandConfig);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int setMute(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public java.util.Map<java.lang.String,java.lang.String> setParameters(@NonNull java.util.Map<java.lang.String,java.lang.String>);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean startBackgroundScan();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int step(int, boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int tune(int, int);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void tune(@NonNull android.hardware.radio.ProgramSelector);
     field public static final int DIRECTION_DOWN = 1; // 0x1
     field public static final int DIRECTION_UP = 0; // 0x0
     field @Deprecated public static final int ERROR_BACKGROUND_SCAN_FAILED = 6; // 0x6
@@ -5557,6 +5585,14 @@
     field @Deprecated public static final int ERROR_HARDWARE_FAILURE = 0; // 0x0
     field @Deprecated public static final int ERROR_SCAN_TIMEOUT = 3; // 0x3
     field @Deprecated public static final int ERROR_SERVER_DIED = 1; // 0x1
+    field public static final int TUNER_RESULT_CANCELED = 6; // 0x6
+    field public static final int TUNER_RESULT_INTERNAL_ERROR = 1; // 0x1
+    field public static final int TUNER_RESULT_INVALID_ARGUMENTS = 2; // 0x2
+    field public static final int TUNER_RESULT_INVALID_STATE = 3; // 0x3
+    field public static final int TUNER_RESULT_NOT_SUPPORTED = 4; // 0x4
+    field public static final int TUNER_RESULT_OK = 0; // 0x0
+    field public static final int TUNER_RESULT_TIMEOUT = 5; // 0x5
+    field public static final int TUNER_RESULT_UNKNOWN_ERROR = 7; // 0x7
   }
 
   public abstract static class RadioTuner.Callback {
@@ -5564,6 +5600,7 @@
     method public void onAntennaState(boolean);
     method public void onBackgroundScanAvailabilityChange(boolean);
     method public void onBackgroundScanComplete();
+    method public void onConfigFlagUpdated(int, boolean);
     method @Deprecated public void onConfigurationChanged(android.hardware.radio.RadioManager.BandConfig);
     method public void onControlChanged(boolean);
     method public void onEmergencyAnnouncement(boolean);
@@ -6246,6 +6283,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean unregisterGnssBatchedLocationCallback(@NonNull android.location.BatchedLocationCallback);
     field public static final String ACTION_ADAS_GNSS_ENABLED_CHANGED = "android.location.action.ADAS_GNSS_ENABLED_CHANGED";
     field public static final String EXTRA_ADAS_GNSS_ENABLED = "android.location.extra.ADAS_GNSS_ENABLED";
+    field @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public static final String GPS_HARDWARE_PROVIDER = "gps_hardware";
   }
 
   public final class LocationRequest implements android.os.Parcelable {
@@ -6385,6 +6423,7 @@
     method public void setAllowed(boolean);
     method public void setProperties(@NonNull android.location.provider.ProviderProperties);
     field public static final String ACTION_FUSED_PROVIDER = "com.android.location.service.FusedLocationProvider";
+    field public static final String ACTION_GNSS_PROVIDER = "android.location.provider.action.GNSS_PROVIDER";
     field public static final String ACTION_NETWORK_PROVIDER = "com.android.location.service.v3.NetworkLocationProvider";
   }
 
@@ -9695,6 +9734,8 @@
   }
 
   public class Environment {
+    method @NonNull public static java.io.File getDataCePackageDirectoryForUser(@NonNull java.util.UUID, @NonNull android.os.UserHandle, @NonNull String);
+    method @NonNull public static java.io.File getDataDePackageDirectoryForUser(@NonNull java.util.UUID, @NonNull android.os.UserHandle, @NonNull String);
     method @NonNull public static java.util.Collection<java.io.File> getInternalMediaDirectories();
     method @NonNull public static java.io.File getOdmDirectory();
     method @NonNull public static java.io.File getOemDirectory();
@@ -10872,6 +10913,7 @@
     field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis";
     field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
     field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
+    field public static final String SECURE_FRP_MODE = "secure_frp_mode";
     field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
     field public static final String TETHER_SUPPORTED = "tether_supported";
     field public static final String THEATER_MODE_ON = "theater_mode_on";
@@ -15498,6 +15540,16 @@
     method public void onPublishStateChange(int);
   }
 
+  public interface RegistrationManager {
+    field public static final int SUGGESTED_ACTION_NONE = 0; // 0x0
+    field public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK = 1; // 0x1
+    field public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT = 2; // 0x2
+  }
+
+  public static class RegistrationManager.RegistrationCallback {
+    method public void onUnregistered(@NonNull android.telephony.ims.ImsReasonInfo, int);
+  }
+
   public final class RtpHeaderExtension implements android.os.Parcelable {
     ctor public RtpHeaderExtension(@IntRange(from=1, to=14) int, @NonNull byte[]);
     method public int describeContents();
@@ -15716,9 +15768,12 @@
     method public void onFeatureRemoved();
     method public boolean queryCapabilityConfiguration(int, int);
     method @NonNull public final android.telephony.ims.feature.MmTelFeature.MmTelCapabilities queryCapabilityStatus();
+    method public final void setCallAudioHandler(int);
     method public void setTerminalBasedCallWaitingStatus(boolean);
     method public void setUiTtyMode(int, @Nullable android.os.Message);
     method public int shouldProcessCall(@NonNull String[]);
+    field public static final int AUDIO_HANDLER_ANDROID = 0; // 0x0
+    field public static final int AUDIO_HANDLER_BASEBAND = 1; // 0x1
     field public static final String EXTRA_IS_UNKNOWN_CALL = "android.telephony.ims.feature.extra.IS_UNKNOWN_CALL";
     field public static final String EXTRA_IS_USSD = "android.telephony.ims.feature.extra.IS_USSD";
     field public static final int PROCESS_CALL_CSFB = 1; // 0x1
@@ -15894,6 +15949,7 @@
     ctor public ImsRegistrationImplBase();
     ctor public ImsRegistrationImplBase(@NonNull java.util.concurrent.Executor);
     method public final void onDeregistered(android.telephony.ims.ImsReasonInfo);
+    method public final void onDeregistered(@Nullable android.telephony.ims.ImsReasonInfo, int);
     method public final void onRegistered(int);
     method public final void onRegistered(@NonNull android.telephony.ims.ImsRegistrationAttributes);
     method public final void onRegistering(int);
@@ -15911,6 +15967,7 @@
     method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int, @NonNull byte[]);
     method public void acknowledgeSmsReport(int, @IntRange(from=0, to=65535) int, int);
     method public String getSmsFormat();
+    method public void onMemoryAvailable(int);
     method public void onReady();
     method @Deprecated public final void onSendSmsResult(int, @IntRange(from=0, to=65535) int, int, int) throws java.lang.RuntimeException;
     method public final void onSendSmsResultError(int, @IntRange(from=0, to=65535) int, int, int, int) throws java.lang.RuntimeException;
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e14902f..5e02e72 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -513,7 +513,7 @@
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs();
     method public void forceUpdateUserSetupComplete(int);
     method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages();
-    method public int getDeviceOwnerType(@NonNull android.content.ComponentName);
+    method @Deprecated public int getDeviceOwnerType(@NonNull android.content.ComponentName);
     method @Nullable public String getDevicePolicyManagementRoleHolderUpdaterPackage();
     method @NonNull public java.util.Set<java.lang.String> getDisallowedSystemApps(@NonNull android.content.ComponentName, int, @NonNull String);
     method public long getLastBugReportRequestTime();
@@ -1158,6 +1158,7 @@
   public final class CameraManager {
     method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
+    field public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L; // 0xef10e60L
   }
 
   public abstract static class CameraManager.AvailabilityCallback {
@@ -2070,7 +2071,7 @@
 
   public class StorageManager {
     method public long computeStorageCacheBytes(@NonNull java.io.File);
-    method @NonNull public static java.util.UUID convert(@NonNull String);
+    method @NonNull public static java.util.UUID convert(@Nullable String);
     method @NonNull public static String convert(@NonNull java.util.UUID);
     method @Nullable public String getCloudMediaProvider();
     method public boolean isAppIoBlocked(@NonNull java.util.UUID, int, int, int);
@@ -2637,6 +2638,8 @@
 
   public final class SmsManager {
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int checkSmsShortCodeDestination(String, String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void clearStorageMonitorMemoryStatusOverride();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setStorageMonitorMemoryStatusOverride(boolean);
     field public static final int SMS_CATEGORY_FREE_SHORT_CODE = 1; // 0x1
     field public static final int SMS_CATEGORY_NOT_SHORT_CODE = 0; // 0x0
     field public static final int SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3; // 0x3
@@ -2927,7 +2930,7 @@
     method public static String actionToString(int);
     method public final void setDisplayId(int);
     field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
-    field public static final int LAST_KEYCODE = 311; // 0x137
+    field public static final int LAST_KEYCODE = 312; // 0x138
   }
 
   public final class KeyboardShortcutGroup implements android.os.Parcelable {
@@ -2941,6 +2944,7 @@
   }
 
   public final class MotionEvent extends android.view.InputEvent implements android.os.Parcelable {
+    method public int getDisplayId();
     method public void setActionButton(int);
     method public void setButtonState(int);
     method public void setDisplayId(int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 30ff052..b9eb443 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -83,6 +83,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.OutcomeReceiver;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -987,6 +988,17 @@
     /** @hide */
     boolean mIsInPictureInPictureMode;
 
+    /** @hide */
+    @IntDef(prefix = { "FULLSCREEN_REQUEST_" }, value = {
+            FULLSCREEN_MODE_REQUEST_EXIT,
+            FULLSCREEN_MODE_REQUEST_ENTER
+    })
+    public @interface FullscreenModeRequest {}
+
+    public static final int FULLSCREEN_MODE_REQUEST_EXIT = 0;
+
+    public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1;
+
     private boolean mShouldDockBigOverlays;
 
     private UiTranslationController mUiTranslationController;
@@ -3001,6 +3013,36 @@
     }
 
     /**
+     * Request to put the a freeform activity into fullscreen. This will only be allowed if the
+     * activity is on a freeform display, such as a desktop device. The requester has to be the
+     * top-most activity and the request should be a response to a user input. When getting
+     * fullscreen and receiving corresponding {@link #onConfigurationChanged(Configuration)} and
+     * {@link #onMultiWindowModeChanged(boolean, Configuration)}, the activity should relayout
+     * itself and the system bars' visibilities can be controlled as usual fullscreen apps.
+     *
+     * Calling it again with the exit request can restore the activity to the previous status.
+     * This will only happen when it got into fullscreen through this API.
+     *
+     * If an app wants to be in fullscreen always, it should claim as not being resizable
+     * by setting
+     * <a href="https://developer.android.com/guide/topics/large-screens/multi-window-support#resizeableActivity">
+     * {@code android:resizableActivity="false"}</a> instead of calling this API.
+     *
+     * @param request Can be {@link #FULLSCREEN_MODE_REQUEST_ENTER} or
+     *                {@link #FULLSCREEN_MODE_REQUEST_EXIT} to indicate this request is to get
+     *                fullscreen or get restored.
+     * @param approvalCallback Optional callback, use {@code null} when not necessary. When the
+     *                         request is approved or rejected, the callback will be triggered. This
+     *                         will happen before any configuration change. The callback will be
+     *                         dispatched on the main thread.
+     */
+    public void requestFullscreenMode(@NonNull @FullscreenModeRequest int request,
+            @Nullable OutcomeReceiver<Void, Throwable> approvalCallback) {
+        FullscreenRequestHandler.requestFullscreenMode(
+                request, approvalCallback, mCurrentConfig, getActivityToken());
+    }
+
+    /**
      * Specifies a preference to dock big overlays like the expanded picture-in-picture on TV
      * (see {@link PictureInPictureParams.Builder#setExpandedAspectRatio}). Docking puts the
      * big overlay side-by-side next to this activity, so that both windows are fully visible to
@@ -6612,16 +6654,64 @@
     }
 
     /**
-     * Returns the uid who started this activity.
-     * @hide
+     * Returns the uid of the app that initially launched this activity.
+     *
+     * <p>In order to receive the launching app's uid, at least one of the following has to
+     * be met:
+     * <ul>
+     *     <li>The app must call {@link ActivityOptions#setShareIdentityEnabled(boolean)} with a
+     *     value of {@code true} and launch this activity with the resulting {@code
+     *     ActivityOptions}.
+     *     <li>The launched activity has the same uid as the launching app.
+     *     <li>The launched activity is running in a package that is signed with the same key
+     *     used to sign the platform (typically only system packages such as Settings will
+     *     meet this requirement).
+     * </ul>.
+     * These are the same requirements for {@link #getLaunchedFromPackage()}; if any of these are
+     * met, then these methods can be used to obtain the uid and package name of the launching
+     * app. If none are met, then {@link Process#INVALID_UID} is returned.
+     *
+     * <p>Note, even if the above conditions are not met, the launching app's identity may
+     * still be available from {@link #getCallingPackage()} if this activity was started with
+     * {@code Activity#startActivityForResult} to allow validation of the result's recipient.
+     *
+     * @return the uid of the launching app or {@link Process#INVALID_UID} if the current
+     * activity cannot access the identity of the launching app
+     *
+     * @see ActivityOptions#setShareIdentityEnabled(boolean)
+     * @see #getLaunchedFromPackage()
      */
     public int getLaunchedFromUid() {
         return ActivityClient.getInstance().getLaunchedFromUid(getActivityToken());
     }
 
     /**
-     * Returns the package who started this activity.
-     * @hide
+     * Returns the package name of the app that initially launched this activity.
+     *
+     * <p>In order to receive the launching app's package name, at least one of the following has
+     * to be met:
+     * <ul>
+     *     <li>The app must call {@link ActivityOptions#setShareIdentityEnabled(boolean)} with a
+     *     value of {@code true} and launch this activity with the resulting
+     *     {@code ActivityOptions}.
+     *     <li>The launched activity has the same uid as the launching app.
+     *     <li>The launched activity is running in a package that is signed with the same key
+     *     used to sign the platform (typically only system packages such as Settings will
+     *     meet this requirement).
+     * </ul>.
+     * These are the same requirements for {@link #getLaunchedFromUid()}; if any of these are
+     * met, then these methods can be used to obtain the uid and package name of the launching
+     * app. If none are met, then {@code null} is returned.
+     *
+     * <p>Note, even if the above conditions are not met, the launching app's identity may
+     * still be available from {@link #getCallingPackage()} if this activity was started with
+     * {@code Activity#startActivityForResult} to allow validation of the result's recipient.
+     *
+     * @return the package name of the launching app or null if the current activity
+     * cannot access the identity of the launching app
+     *
+     * @see ActivityOptions#setShareIdentityEnabled(boolean)
+     * @see #getLaunchedFromUid()
      */
     @Nullable
     public String getLaunchedFromPackage() {
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 324b8e7..ce99119 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -24,6 +24,7 @@
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.util.Singleton;
@@ -372,6 +373,14 @@
         }
     }
 
+    void requestMultiwindowFullscreen(IBinder token, int request, IRemoteCallback callback) {
+        try {
+            getActivityClientController().requestMultiwindowFullscreen(token, request, callback);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     void startLockTaskModeByToken(IBinder token) {
         try {
             getActivityClientController().startLockTaskModeByToken(token);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 83963fd..2d6eaf1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -5305,6 +5305,38 @@
     }
 
     /**
+     * Delays delivering broadcasts to the specified package.
+     *
+     * <p> When {@code delayedDurationMs} is {@code 0}, it will clears any previously
+     * set forced delays.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void forceDelayBroadcastDelivery(@NonNull String targetPackage,
+            @IntRange(from = 0) long delayedDurationMs) {
+        try {
+            getService().forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Checks if the "modern" broadcast queue is enabled.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public boolean isModernBroadcastQueueEnabled() {
+        try {
+            return getService().isModernBroadcastQueueEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * @return The reason code of whether or not the given UID should be exempted from background
      * restrictions here.
      *
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 52ef7fb..1b92312 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -202,6 +202,12 @@
     private static final String KEY_LOCK_TASK_MODE = "android:activity.lockTaskMode";
 
     /**
+     * Whether the launching app's identity should be available to the launched activity.
+     * @see #setShareIdentityEnabled(boolean)
+     */
+    private static final String KEY_SHARE_IDENTITY = "android:activity.shareIdentity";
+
+    /**
      * The display id the activity should be launched into.
      * @see #setLaunchDisplayId(int)
      * @hide
@@ -457,6 +463,7 @@
     private int mLaunchTaskId = -1;
     private int mPendingIntentLaunchFlags;
     private boolean mLockTaskMode = false;
+    private boolean mShareIdentity = false;
     private boolean mDisallowEnterPictureInPictureWhileLaunching;
     private boolean mApplyActivityFlagsForBubbles;
     private boolean mTaskAlwaysOnTop;
@@ -1238,6 +1245,7 @@
                 break;
         }
         mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false);
+        mShareIdentity = opts.getBoolean(KEY_SHARE_IDENTITY, false);
         mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
         mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY);
         mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN, android.window.WindowContainerToken.class);
@@ -1488,6 +1496,20 @@
     }
 
     /**
+     * Returns whether the launching app has opted-in to sharing its identity with the launched
+     * activity.
+     *
+     * @see #setShareIdentityEnabled(boolean)
+     * @see Activity#getLaunchedFromUid()
+     * @see Activity#getLaunchedFromPackage()
+     *
+     * @hide
+     */
+    public boolean getShareIdentity() {
+        return mShareIdentity;
+    }
+
+    /**
      * Gets whether the activity want to be launched as other theme for the splash screen.
      * @hide
      */
@@ -1560,6 +1582,33 @@
     }
 
     /**
+     * Sets whether the identity of the launching app should be shared with the activity.
+     *
+     * <p>Use this option when starting an activity that needs to know the identity of the
+     * launching app; with this set to {@code true}, the activity will have access to the launching
+     * app's package name and uid.
+     *
+     * <p>Defaults to {@code false} if not set.
+     *
+     * <p>Note, even if the launching app does not explicitly enable sharing of its identity, if
+     * the activity is started with {@code Activity#startActivityForResult}, then {@link
+     * Activity#getCallingPackage()} will still return the launching app's package name to
+     * allow validation of the result's recipient. Also, an activity running within a package
+     * signed by the same key used to sign the platform (some system apps such as Settings will
+     * be signed with the platform's key) will have access to the launching app's identity.
+     *
+     * @param shareIdentity whether the launching app's identity should be shared with the activity
+     * @return {@code this} {@link ActivityOptions} instance.
+     * @see Activity#getLaunchedFromPackage()
+     * @see Activity#getLaunchedFromUid()
+     */
+    @NonNull
+    public ActivityOptions setShareIdentityEnabled(boolean shareIdentity) {
+        mShareIdentity = shareIdentity;
+        return this;
+    }
+
+    /**
      * Gets the id of the display where activity should be launched.
      * @return The id of the display where activity should be launched,
      *         {@link android.view.Display#INVALID_DISPLAY} if not set.
@@ -2039,6 +2088,7 @@
                 break;
         }
         mLockTaskMode = otherOptions.mLockTaskMode;
+        mShareIdentity = otherOptions.mShareIdentity;
         mAnimSpecs = otherOptions.mAnimSpecs;
         mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
         mSpecsFuture = otherOptions.mSpecsFuture;
@@ -2123,6 +2173,9 @@
         if (mLockTaskMode) {
             b.putBoolean(KEY_LOCK_TASK_MODE, mLockTaskMode);
         }
+        if (mShareIdentity) {
+            b.putBoolean(KEY_SHARE_IDENTITY, mShareIdentity);
+        }
         if (mLaunchDisplayId != INVALID_DISPLAY) {
             b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId);
         }
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 7cfca97..be8f48d 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -62,6 +62,12 @@
     public static final int INVALID_TASK_ID = -1;
 
     /**
+     * Invalid windowing mode.
+     * @hide
+     */
+    public static final int INVALID_WINDOWING_MODE = -1;
+
+    /**
      * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
      * that the resize doesn't need to preserve the window, and can be skipped if bounds
      * is unchanged. This mode is used by window manager in most cases.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d5879fb..563f6d4 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -6844,22 +6844,52 @@
     }
 
     /**
-     * Callback for notification of an op being noted.
+     * Callback for notification of an app-op being noted.
      *
      * @hide
      */
+    @SystemApi
     public interface OnOpNotedListener {
         /**
-         * Called when an op was noted.
-         * @param code The op code.
+         * Called when an app-op is noted.
+         *
+         * @param op The operation that was noted.
          * @param uid The UID performing the operation.
          * @param packageName The package performing the operation.
          * @param attributionTag The attribution tag performing the operation.
          * @param flags The flags of this op
          * @param result The result of the note.
          */
-        void onOpNoted(int code, int uid, String packageName, String attributionTag,
-                @OpFlags int flags, @Mode int result);
+        void onOpNoted(@NonNull String op, int uid, @NonNull String packageName,
+                @Nullable String attributionTag, @OpFlags int flags, @Mode int result);
+    }
+
+    /**
+     * Callback for notification of an app-op being noted to be used within platform code.
+     *
+     * This allows being notified using raw op codes instead of string op names.
+     *
+     * @hide
+     */
+    public interface OnOpNotedInternalListener extends OnOpNotedListener {
+        /**
+         * Called when an app-op is noted.
+         *
+         * @param code The code of the operation that was noted.
+         * @param uid The UID performing the operation.
+         * @param packageName The package performing the operation.
+         * @param attributionTag The attribution tag performing the operation.
+         * @param flags The flags of this op
+         * @param result The result of the note.
+         */
+        void onOpNoted(int code, int uid, @NonNull String packageName,
+                @Nullable String attributionTag, @OpFlags int flags, @Mode int result);
+
+        @Override
+        default void onOpNoted(@NonNull String op, int uid, @NonNull String packageName,
+                @Nullable String attributionTag, @OpFlags int flags, @Mode int result) {
+            onOpNoted(strOpToOp(op), uid, packageName, attributionTag, flags, result);
+        }
     }
 
     /**
@@ -7654,13 +7684,42 @@
      * @param ops The ops to watch.
      * @param callback Where to report changes.
      *
-     * @see #startWatchingActive(int[], OnOpActiveChangedListener)
-     * @see #startWatchingStarted(int[], OnOpStartedListener)
      * @see #stopWatchingNoted(OnOpNotedListener)
      * @see #noteOp(String, int, String, String, String)
      *
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
+    public void startWatchingNoted(@NonNull String[] ops, @NonNull OnOpNotedListener callback) {
+        final int[] intOps = new int[ops.length];
+        for (int i = 0; i < ops.length; i++) {
+            intOps[i] = strOpToOp(ops[i]);
+        }
+        startWatchingNoted(intOps, callback);
+    }
+
+    /**
+     * Start watching for noted app ops. An app op may be immediate or long running.
+     * Immediate ops are noted while long running ones are started and stopped. This
+     * method allows registering a listener to be notified when an app op is noted. If
+     * an op is being noted by any package you will get a callback. To change the
+     * watched ops for a registered callback you need to unregister and register it again.
+     *
+     * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
+     * you can watch changes only for your UID.
+     *
+     * This allows observing noted ops by their raw op codes instead of string op names.
+     *
+     * @param ops The ops to watch.
+     * @param callback Where to report changes.
+     *
+     * @see #startWatchingActive(int[], OnOpActiveChangedListener)
+     * @see #startWatchingStarted(int[], OnOpStartedListener)
+     * @see #startWatchingNoted(String[], OnOpNotedListener)
+     *
+     * @hide
+     */
     @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
     public void startWatchingNoted(@NonNull int[] ops, @NonNull OnOpNotedListener callback) {
         IAppOpsNotedCallback cb;
@@ -7673,7 +7732,10 @@
                 @Override
                 public void opNoted(int op, int uid, String packageName, String attributionTag,
                         int flags, int mode) {
-                    callback.onOpNoted(op, uid, packageName, attributionTag, flags, mode);
+                    if (sAppOpInfos[op].name != null) {
+                        callback.onOpNoted(sAppOpInfos[op].name, uid, packageName, attributionTag,
+                                flags, mode);
+                    }
                 }
             };
             mNotedWatchers.put(callback, cb);
@@ -7689,11 +7751,12 @@
      * Stop watching for noted app ops. An app op may be immediate or long running.
      * Unregistering a non-registered callback has no effect.
      *
-     * @see #startWatchingNoted(int[], OnOpNotedListener)
+     * @see #startWatchingNoted(String[], OnOpNotedListener)
      * @see #noteOp(String, int, String, String, String)
      *
      * @hide
      */
+    @SystemApi
     public void stopWatchingNoted(@NonNull OnOpNotedListener callback) {
         synchronized (mNotedWatchers) {
             final IAppOpsNotedCallback cb = mNotedWatchers.remove(callback);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 042bdd7..39f7153 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -243,7 +243,7 @@
     @UnsupportedAppUsage
     private @NonNull Resources mResources;
     private @Nullable Display mDisplay; // may be null if invalid display or not initialized yet.
-    private int mDeviceId = VirtualDeviceManager.DEFAULT_DEVICE_ID;
+    private int mDeviceId = VirtualDeviceManager.DEVICE_ID_DEFAULT;
 
     /**
      * If set to {@code true} the resources for this context will be configured for mDisplay which
@@ -2699,8 +2699,8 @@
 
     @Override
     public @NonNull Context createDeviceContext(int deviceId) {
-        boolean validDeviceId = deviceId == VirtualDeviceManager.DEFAULT_DEVICE_ID;
-        if (deviceId > VirtualDeviceManager.DEFAULT_DEVICE_ID) {
+        boolean validDeviceId = deviceId == VirtualDeviceManager.DEVICE_ID_DEFAULT;
+        if (deviceId > VirtualDeviceManager.DEVICE_ID_DEFAULT) {
             VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
             if (vdm != null) {
                 List<VirtualDevice> virtualDevices = vdm.getVirtualDevices();
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index e99e360..877177c 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -56,7 +56,6 @@
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbManager;
-import android.healthconnect.HealthConnectManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.permission.PermissionCheckerManager;
@@ -73,7 +72,6 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Optional;
-import java.util.Set;
 
 /**
  * This class enforces the policies around the foreground service types.
@@ -994,45 +992,6 @@
         }
     }
 
-    static class HealthConnectPermission extends RegularPermission {
-        private @Nullable String[] mPermissionNames;
-
-        HealthConnectPermission() {
-            super("Health Connect");
-        }
-
-        @Override
-        @SuppressLint("AndroidFrameworkRequiresPermission")
-        @PackageManager.PermissionResult
-        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
-                String packageName, boolean allowWhileInUse) {
-            final String[] perms = getPermissions(context);
-            for (String perm : perms) {
-                if (checkPermission(context, perm, callerUid, callerPid,
-                        packageName, allowWhileInUse) == PERMISSION_GRANTED) {
-                    return PERMISSION_GRANTED;
-                }
-            }
-            return PERMISSION_DENIED;
-        }
-
-        @Override
-        void addToList(@NonNull Context context, @NonNull ArrayList<String> list) {
-            final String[] perms = getPermissions(context);
-            for (String perm : perms) {
-                list.add(perm);
-            }
-        }
-
-        private @NonNull String[] getPermissions(@NonNull Context context) {
-            if (mPermissionNames != null) {
-                return mPermissionNames;
-            }
-            final Set<String> healthPerms = HealthConnectManager.getHealthPermissions(context);
-            return mPermissionNames = healthPerms.toArray(new String[healthPerms.size()]);
-        }
-    }
-
     /**
      * The default policy for the foreground service types.
      *
diff --git a/core/java/android/app/FullscreenRequestHandler.java b/core/java/android/app/FullscreenRequestHandler.java
new file mode 100644
index 0000000..52f461d
--- /dev/null
+++ b/core/java/android/app/FullscreenRequestHandler.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 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 android.app;
+
+import static android.app.Activity.FULLSCREEN_MODE_REQUEST_ENTER;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.OutcomeReceiver;
+
+/**
+ * @hide
+ */
+public class FullscreenRequestHandler {
+    @IntDef(prefix = { "RESULT_" }, value = {
+            RESULT_APPROVED,
+            RESULT_FAILED_NOT_IN_FREEFORM,
+            RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY,
+            RESULT_FAILED_NOT_DEFAULT_FREEFORM,
+            RESULT_FAILED_NOT_TOP_FOCUSED
+    })
+    public @interface RequestResult {}
+
+    public static final int RESULT_APPROVED = 0;
+    public static final int RESULT_FAILED_NOT_IN_FREEFORM = 1;
+    public static final int RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY = 2;
+    public static final int RESULT_FAILED_NOT_DEFAULT_FREEFORM = 3;
+    public static final int RESULT_FAILED_NOT_TOP_FOCUSED = 4;
+
+    public static final String REMOTE_CALLBACK_RESULT_KEY = "result";
+
+    static void requestFullscreenMode(@NonNull @Activity.FullscreenModeRequest int request,
+            @Nullable OutcomeReceiver<Void, Throwable> approvalCallback, Configuration config,
+            IBinder token) {
+        int earlyCheck = earlyCheckRequestMatchesWindowingMode(
+                request, config.windowConfiguration.getWindowingMode());
+        if (earlyCheck != RESULT_APPROVED) {
+            if (approvalCallback != null) {
+                notifyFullscreenRequestResult(approvalCallback, earlyCheck);
+            }
+            return;
+        }
+        try {
+            if (approvalCallback != null) {
+                ActivityClient.getInstance().requestMultiwindowFullscreen(token, request,
+                        new IRemoteCallback.Stub() {
+                            @Override
+                            public void sendResult(Bundle res) {
+                                notifyFullscreenRequestResult(
+                                        approvalCallback, res.getInt(REMOTE_CALLBACK_RESULT_KEY));
+                            }
+                        });
+            } else {
+                ActivityClient.getInstance().requestMultiwindowFullscreen(token, request, null);
+            }
+        } catch (Throwable e) {
+            if (approvalCallback != null) {
+                approvalCallback.onError(e);
+            }
+        }
+    }
+
+    private static void notifyFullscreenRequestResult(
+            OutcomeReceiver<Void, Throwable> callback, int result) {
+        Throwable e = null;
+        switch (result) {
+            case RESULT_FAILED_NOT_IN_FREEFORM:
+                e = new IllegalStateException("The window is not a freeform window, the request "
+                        + "to get into fullscreen cannot be approved.");
+                break;
+            case RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY:
+                e = new IllegalStateException("The window is not in fullscreen by calling the "
+                        + "requestFullscreenMode API before, such that cannot be restored.");
+                break;
+            case RESULT_FAILED_NOT_DEFAULT_FREEFORM:
+                e = new IllegalStateException("The window is not launched in freeform by default.");
+                break;
+            case RESULT_FAILED_NOT_TOP_FOCUSED:
+                e = new IllegalStateException("The window is not the top focused window.");
+                break;
+            default:
+                callback.onResult(null);
+                break;
+        }
+        if (e != null) {
+            callback.onError(e);
+        }
+    }
+
+    private static int earlyCheckRequestMatchesWindowingMode(int request, int windowingMode) {
+        if (request == FULLSCREEN_MODE_REQUEST_ENTER) {
+            if (windowingMode != WINDOWING_MODE_FREEFORM) {
+                return RESULT_FAILED_NOT_IN_FREEFORM;
+            }
+        } else {
+            if (windowingMode != WINDOWING_MODE_FULLSCREEN) {
+                return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
+            }
+        }
+        return RESULT_APPROVED;
+    }
+}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 8b655b9..286b84c 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Bundle;
+import android.os.IRemoteCallback;
 import android.os.PersistableBundle;
 import android.view.RemoteAnimationDefinition;
 import android.window.SizeConfigurationBuckets;
@@ -102,6 +103,8 @@
     void setPictureInPictureParams(in IBinder token, in PictureInPictureParams params);
     oneway void setShouldDockBigOverlays(in IBinder token, in boolean shouldDockBigOverlays);
     void toggleFreeformWindowingMode(in IBinder token);
+    oneway void requestMultiwindowFullscreen(in IBinder token, in int request,
+            in IRemoteCallback callback);
 
     oneway void startLockTaskModeByToken(in IBinder token);
     oneway void stopLockTaskModeByToken(in IBinder token);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 040111c..938f1f6 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -771,6 +771,14 @@
     /** Blocks until all broadcast queues become idle. */
     void waitForBroadcastIdle();
 
+    /** Delays delivering broadcasts to the specified package. */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DUMP)")
+    void forceDelayBroadcastDelivery(in String targetPackage, long delayedDurationMs);
+
+    /** Checks if the modern broadcast queue is enabled. */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DUMP)")
+    boolean isModernBroadcastQueueEnabled();
+
     /**
      * @return The reason code of whether or not the given UID should be exempted from background
      * restrictions here.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index dd82bc1..128a872 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -169,7 +169,12 @@
     public static final String DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG =
             "deprecate_usermanagerinternal_devicepolicy";
     /** @hide */
-    public static final boolean DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT = false;
+    public static final boolean DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT = true;
+    /** @hide */
+    public static final String ADD_ISFINANCED_DEVICE_FLAG =
+            "add-isfinanced-device";
+    /** @hide */
+    public static final boolean ADD_ISFINANCED_FEVICE_DEFAULT = true;
 
     private static String TAG = "DevicePolicyManager";
 
@@ -3913,34 +3918,46 @@
     public static @interface MtePolicy {}
 
     /**
-     * Set MTE policy for device. MTE_ENABLED does not necessarily enable MTE if set on a device
-     * that does not support MTE.
-     *
-     * The default policy is MTE_NOT_CONTROLLED_BY_POLICY.
-     *
-     * Memory Tagging Extension (MTE) is a CPU extension that allows to protect against certain
+     * Called by a device owner or profile owner of an organization-owned device to set the Memory
+     * Tagging Extension (MTE) policy. MTE is a CPU extension that allows to protect against certain
      * classes of security problems at a small runtime performance cost overhead.
      *
-     * @param policy the policy to be set
+     * <p>The MTE policy can only be set to {@link #MTE_DISABLED} if called by a device owner.
+     * Otherwise a {@link SecurityException} will be thrown.
+     *
+     * @throws SecurityException if caller is not device owner or profile owner of org-owned device
+     *     or if called on a parent instance
+     * @param policy the MTE policy to be set
      */
     public void setMtePolicy(@MtePolicy int policy) {
-        // TODO(b/244290023): implement
-        // This is SecurityException to temporarily make ParentProfileTest happy.
-        // This is not used.
-        throw new SecurityException("not implemented");
+        throwIfParentInstance("setMtePolicy");
+        if (mService != null) {
+            try {
+                mService.setMtePolicy(policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
-     * Get currently set MTE policy. This is not necessarily the same as the state of MTE on the
-     * device, as the device might not support MTE.
+     * Called by a device owner, a profile owner of an organization-owned device or the system to
+     * get the Memory Tagging Extension (MTE) policy
      *
-     * @return the currently set policy
+     * @throws SecurityException if caller is not device owner or profile owner of org-owned device
+     *                           or system uid, or if called on a parent instance
+     * @return the currently set MTE policy
      */
     public @MtePolicy int getMtePolicy() {
-        // TODO(b/244290023): implement
-        // This is SecurityException to temporarily make ParentProfileTest happy.
-        // This is not used.
-        throw new SecurityException("not implemented");
+        throwIfParentInstance("setMtePolicy");
+        if (mService != null) {
+            try {
+                return mService.getMtePolicy();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return MTE_NOT_CONTROLLED_BY_POLICY;
     }
 
     // TODO: Expose this as SystemAPI once we add the query API
@@ -15425,10 +15442,13 @@
      *
      * @throws IllegalStateException When admin is not the device owner or there is no device owner.
      *
+     * @deprecated Use type-specific APIs (e.g. {@link #isFinancedDevice}).
      * @hide
      */
     @TestApi
     @DeviceOwnerType
+    @Deprecated
+    // TODO(b/259908270): remove
     public int getDeviceOwnerType(@NonNull ComponentName admin) {
         throwIfParentInstance("getDeviceOwnerType");
         if (mService != null) {
@@ -15442,6 +15462,20 @@
     }
 
     /**
+     * {@code true} if this device is financed.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS
+    })
+    public boolean isFinancedDevice() {
+        return isDeviceManaged()
+                && getDeviceOwnerType(getDeviceOwnerComponentOnAnyUser())
+                == DEVICE_OWNER_TYPE_FINANCED;
+    }
+
+    /**
      * Called by device owner or profile owner of an organization-owned managed profile to
      * enable or disable USB data signaling for the device. When disabled, USB data connections
      * (except from charging functions) are prohibited.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8a40265..5383dca 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -571,4 +571,7 @@
 
     void setApplicationExemptions(String packageName, in int[]exemptions);
     int[] getApplicationExemptions(String packageName);
+
+    void setMtePolicy(int flag);
+    int getMtePolicy();
 }
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 3325d1e..2e969f8 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -73,6 +73,21 @@
     public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH";
 
     /**
+     * Device profile: glasses.
+     *
+     * If specified, the current request may have a modified UI to highlight that the device being
+     * set up is a glasses device, and some extra permissions may be granted to the app
+     * as a result.
+     *
+     * Using it requires declaring uses-permission
+     * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_GLASSES} in the manifest.
+     *
+     * @see AssociationRequest.Builder#setDeviceProfile
+     */
+    @RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_GLASSES)
+    public static final String DEVICE_PROFILE_GLASSES = "android.app.role.COMPANION_DEVICE_GLASSES";
+
+    /**
      * Device profile: a virtual display capable of rendering Android applications, and sending back
      * input events.
      *
@@ -87,6 +102,20 @@
             "android.app.role.COMPANION_DEVICE_APP_STREAMING";
 
     /**
+     * Device profile: a virtual device capable of rendering content from an Android host to a
+     * nearby device.
+     *
+     * Only applications that have been granted
+     * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING}
+     * are allowed to request to be associated with such devices.
+     *
+     * @see AssociationRequest.Builder#setDeviceProfile
+     */
+    @RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING)
+    public static final String DEVICE_PROFILE_NEARBY_DEVICE_STREAMING =
+            "android.app.role.COMPANION_DEVICE_NEARBY_DEVICE_STREAMING";
+
+    /**
      * Device profile: Android Automotive Projection
      *
      * Only applications that have been granted
@@ -116,7 +145,8 @@
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @StringDef(value = { DEVICE_PROFILE_WATCH, DEVICE_PROFILE_COMPUTER,
-            DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, DEVICE_PROFILE_APP_STREAMING })
+            DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, DEVICE_PROFILE_APP_STREAMING,
+            DEVICE_PROFILE_GLASSES, DEVICE_PROFILE_NEARBY_DEVICE_STREAMING })
     public @interface DeviceProfile {}
 
     /**
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 5c47ea2..f17d186 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -33,6 +33,7 @@
 import android.hardware.input.VirtualMouseScrollEvent;
 import android.hardware.input.VirtualTouchEvent;
 import android.hardware.input.VirtualTouchscreenConfig;
+import android.hardware.input.VirtualNavigationTouchpadConfig;
 import android.os.ResultReceiver;
 
 /**
@@ -84,6 +85,10 @@
     void createVirtualTouchscreen(
             in VirtualTouchscreenConfig config,
             IBinder token);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    void createVirtualNavigationTouchpad(
+            in VirtualNavigationTouchpadConfig config,
+            IBinder token);
     void unregisterInputDevice(IBinder token);
     int getInputDeviceId(IBinder token);
     boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 9e95d47..f3f43354 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -38,9 +38,9 @@
      * @hide
      */
     public VirtualDevice(int id, @Nullable String name) {
-        if (id <= VirtualDeviceManager.DEFAULT_DEVICE_ID) {
+        if (id <= VirtualDeviceManager.DEVICE_ID_DEFAULT) {
             throw new IllegalArgumentException("VirtualDevice ID mist be greater than "
-                    + VirtualDeviceManager.DEFAULT_DEVICE_ID);
+                    + VirtualDeviceManager.DEVICE_ID_DEFAULT);
         }
         mId = id;
         mName = name;
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 109f4b1..dba7c8e 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -29,11 +29,14 @@
 import android.companion.AssociationInfo;
 import android.companion.virtual.audio.VirtualAudioDevice;
 import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
+import android.companion.virtual.camera.VirtualCameraDevice;
+import android.companion.virtual.camera.VirtualCameraInput;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Point;
+import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.VirtualDisplayFlag;
 import android.hardware.display.DisplayManagerGlobal;
@@ -46,6 +49,8 @@
 import android.hardware.input.VirtualKeyboardConfig;
 import android.hardware.input.VirtualMouse;
 import android.hardware.input.VirtualMouseConfig;
+import android.hardware.input.VirtualNavigationTouchpad;
+import android.hardware.input.VirtualNavigationTouchpadConfig;
 import android.hardware.input.VirtualTouchscreen;
 import android.hardware.input.VirtualTouchscreenConfig;
 import android.os.Binder;
@@ -88,12 +93,12 @@
     /**
      * The default device ID, which is the ID of the primary (non-virtual) device.
      */
-    public static final int DEFAULT_DEVICE_ID = 0;
+    public static final int DEVICE_ID_DEFAULT = 0;
 
     /**
      * Invalid device ID.
      */
-    public static final int INVALID_DEVICE_ID = -1;
+    public static final int DEVICE_ID_INVALID = -1;
 
     /**
      * Broadcast Action: A Virtual Device was removed.
@@ -238,7 +243,7 @@
     public int getDeviceIdForDisplayId(int displayId) {
         if (mService == null) {
             Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
-            return DEFAULT_DEVICE_ID;
+            return DEVICE_ID_DEFAULT;
         }
         try {
             return mService.getDeviceIdForDisplayId(displayId);
@@ -293,9 +298,11 @@
                     }
                 };
         @Nullable
-        private VirtualAudioDevice mVirtualAudioDevice;
+        private VirtualCameraDevice mVirtualCameraDevice;
         @NonNull
-        private List<VirtualSensor> mVirtualSensors = new ArrayList<>();
+        private final List<VirtualSensor> mVirtualSensors = new ArrayList<>();
+        @Nullable
+        private VirtualAudioDevice mVirtualAudioDevice;
 
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         private VirtualDevice(
@@ -655,6 +662,30 @@
         }
 
         /**
+         * Creates a virtual touchpad in navigation mode.
+         *
+         * A touchpad in navigation mode means that its events are interpreted as navigation events
+         * (up, down, etc) instead of using them to update a cursor's absolute position. If the
+         * events are not consumed they are converted to DPAD events.
+         *
+         * @param config the configurations of the virtual navigation touchpad.
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        public VirtualNavigationTouchpad createVirtualNavigationTouchpad(
+                 @NonNull VirtualNavigationTouchpadConfig config) {
+            try {
+                final IBinder token = new Binder(
+                        "android.hardware.input.VirtualNavigationTouchpad:"
+                            + config.getInputDeviceName());
+                mVirtualDevice.createVirtualNavigationTouchpad(config, token);
+                return new VirtualNavigationTouchpad(mVirtualDevice, token);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Creates a virtual touchscreen.
          *
          * @param display         the display that the events inputted through this device should
@@ -716,6 +747,34 @@
         }
 
         /**
+         * Creates a new virtual camera. If a virtual camera was already created, it will be closed.
+         *
+         * @param cameraName name of the virtual camera.
+         * @param characteristics camera characteristics.
+         * @param virtualCameraInput callback that provides input to camera.
+         * @param executor Executor on which camera input will be sent into system. Don't
+         *         use the Main Thread for this executor.
+         * @return newly created camera;
+         *
+         * @hide
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        public VirtualCameraDevice createVirtualCameraDevice(
+                @NonNull String cameraName,
+                @NonNull CameraCharacteristics characteristics,
+                @NonNull VirtualCameraInput virtualCameraInput,
+                @NonNull Executor executor) {
+            if (mVirtualCameraDevice != null) {
+                mVirtualCameraDevice.close();
+            }
+            int deviceId = getDeviceId();
+            mVirtualCameraDevice = new VirtualCameraDevice(
+                    deviceId, cameraName, characteristics, virtualCameraInput, executor);
+            return mVirtualCameraDevice;
+        }
+
+        /**
          * Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
          *
          * @param showPointerIcon True if the pointer should be shown; false otherwise. The default
@@ -743,11 +802,11 @@
 
         private String getVirtualDisplayName() {
             try {
-                // Currently this just use the association ID, which means all of the virtual
-                // displays created using the same virtual device will have the same name. The name
-                // should only be used for informational purposes, and not for identifying the
-                // display in code.
-                return "VirtualDevice_" + mVirtualDevice.getAssociationId();
+                // Currently this just use the device ID, which means all of the virtual displays
+                // created using the same virtual device will have the same name. The name should
+                // only be used for informational purposes, and not for identifying the display in
+                // code.
+                return "VirtualDevice_" + mVirtualDevice.getDeviceId();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraDevice.java b/core/java/android/companion/virtual/camera/VirtualCameraDevice.java
new file mode 100644
index 0000000..a7eba87
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraDevice.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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 android.companion.virtual.camera;
+
+import android.hardware.camera2.CameraCharacteristics;
+
+import androidx.annotation.NonNull;
+
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Virtual camera that is used to send image data into system.
+ *
+ * @hide
+ */
+public final class VirtualCameraDevice implements AutoCloseable {
+
+    @NonNull
+    private final String mCameraDeviceName;
+    @NonNull
+    private final CameraCharacteristics mCameraCharacteristics;
+    @NonNull
+    private final VirtualCameraOutput mCameraOutput;
+    private boolean mCameraRegistered = false;
+
+    /**
+     * VirtualCamera device constructor.
+     *
+     * @param virtualDeviceId ID of virtual device to which camera will be added.
+     * @param cameraName must be unique for each camera per virtual device.
+     * @param characteristics of camera that will be passed into system in order to describe
+     *         camera.
+     * @param virtualCameraInput component that provides image data.
+     * @param executor on which to collect image data and pass it into system.
+     */
+    public VirtualCameraDevice(int virtualDeviceId, @NonNull String cameraName,
+            @NonNull CameraCharacteristics characteristics,
+            @NonNull VirtualCameraInput virtualCameraInput, @NonNull Executor executor) {
+        Objects.requireNonNull(cameraName);
+        mCameraCharacteristics = Objects.requireNonNull(characteristics);
+        mCameraDeviceName = generateCameraDeviceName(virtualDeviceId, cameraName);
+        mCameraOutput = new VirtualCameraOutput(virtualCameraInput, executor);
+        registerCamera();
+    }
+
+    private static String generateCameraDeviceName(int deviceId, @NonNull String cameraName) {
+        return String.format(Locale.ENGLISH, "%d_%s", deviceId, Objects.requireNonNull(cameraName));
+    }
+
+    @Override
+    public void close() {
+        if (!mCameraRegistered) {
+            return;
+        }
+
+        mCameraOutput.closeStream();
+    }
+
+    private void registerCamera() {
+        if (mCameraRegistered) {
+            return;
+        }
+
+        mCameraRegistered = true;
+    }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraInput.java b/core/java/android/companion/virtual/camera/VirtualCameraInput.java
new file mode 100644
index 0000000..690a64b
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraInput.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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 android.companion.virtual.camera;
+
+import android.annotation.NonNull;
+import android.hardware.camera2.params.InputConfiguration;
+
+import java.io.InputStream;
+
+/***
+ *  Used for sending image data into virtual camera.
+ *  <p>
+ *  The system will call {@link  #openStream(InputConfiguration)} to signal when you
+ *  should start sending Camera image data.
+ *  When Camera is no longer needed, or there is change in configuration
+ *  {@link #closeStream()} will be called. At that time finish sending current
+ *  image data and then close the stream.
+ *  <p>
+ *  If Camera image data is needed again, {@link #openStream(InputConfiguration)} will be
+ *  called by the system.
+ *
+ * @hide
+ */
+public interface VirtualCameraInput {
+
+    /**
+     * Opens a new image stream for the provided {@link InputConfiguration}.
+     *
+     * @param inputConfiguration image data configuration.
+     * @return image data stream.
+     */
+    @NonNull
+    InputStream openStream(@NonNull InputConfiguration inputConfiguration);
+
+    /**
+     * Stop sending image data and close {@link InputStream} provided in {@link
+     * #openStream(InputConfiguration)}. Do nothing if there is currently no active stream.
+     */
+    void closeStream();
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraOutput.java b/core/java/android/companion/virtual/camera/VirtualCameraOutput.java
new file mode 100644
index 0000000..fa1c3ad
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraOutput.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2022 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 android.companion.virtual.camera;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.camera2.params.InputConfiguration;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Component for providing Camera data to the system.
+ * <p>
+ * {@link #getStreamDescriptor(InputConfiguration)} will be called by the system when Camera should
+ * start sending image data. Camera data will continue to be sent into {@link ParcelFileDescriptor}
+ * until {@link #closeStream()} is called by the system, at which point {@link ParcelFileDescriptor}
+ * will be closed.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public class VirtualCameraOutput {
+
+    private static final String TAG = "VirtualCameraDeviceImpl";
+
+    @NonNull
+    private final VirtualCameraInput mVirtualCameraInput;
+    @NonNull
+    private final Executor mExecutor;
+    @Nullable
+    private VirtualCameraStream mCameraStream;
+
+    @VisibleForTesting
+    public VirtualCameraOutput(@NonNull VirtualCameraInput cameraInput,
+            @NonNull Executor executor) {
+        mVirtualCameraInput = Objects.requireNonNull(cameraInput);
+        mExecutor = Objects.requireNonNull(executor);
+    }
+
+    /**
+     * Get a read Descriptor on which Camera HAL will receive data. At any point in time there can
+     * exist a maximum of one active {@link ParcelFileDescriptor}.
+     * Calling this method with a different {@link InputConfiguration} is going to close the
+     * previously created file descriptor.
+     *
+     * @param imageConfiguration for which to create the {@link ParcelFileDescriptor}.
+     * @return Newly created ParcelFileDescriptor if stream param is different from previous or if
+     *         this is first time call. Will return null if there was an error during Descriptor
+     *         creation process.
+     */
+    @Nullable
+    @VisibleForTesting
+    public ParcelFileDescriptor getStreamDescriptor(
+            @NonNull InputConfiguration imageConfiguration) {
+        Objects.requireNonNull(imageConfiguration);
+
+        // Reuse same descriptor if stream is the same, otherwise create a new one.
+        try {
+            if (mCameraStream == null) {
+                mCameraStream = new VirtualCameraStream(imageConfiguration, mExecutor);
+            } else if (!mCameraStream.isSameConfiguration(imageConfiguration)) {
+                mCameraStream.close();
+                mCameraStream = new VirtualCameraStream(imageConfiguration, mExecutor);
+            }
+        } catch (IOException exception) {
+            Log.e(TAG, "Unable to open file descriptor.", exception);
+            return null;
+        }
+
+        InputStream imageStream = mVirtualCameraInput.openStream(imageConfiguration);
+        mCameraStream.startSending(imageStream);
+        return mCameraStream.getDescriptor();
+    }
+
+    /**
+     * Closes currently opened stream. If there is no stream, do nothing.
+     */
+    @VisibleForTesting
+    public void closeStream() {
+        mVirtualCameraInput.closeStream();
+        if (mCameraStream != null) {
+            mCameraStream.close();
+            mCameraStream = null;
+        }
+
+        try {
+            mVirtualCameraInput.closeStream();
+        } catch (Exception e) {
+            Log.e(TAG, "Error during closing stream.", e);
+        }
+    }
+
+    private static class VirtualCameraStream implements AutoCloseable {
+
+        private static final String TAG = "VirtualCameraStream";
+        private static final int BUFFER_SIZE = 1024;
+
+        private static final int SENDING_STATE_INITIAL = 0;
+        private static final int SENDING_STATE_IN_PROGRESS = 1;
+        private static final int SENDING_STATE_CLOSED = 2;
+
+        @NonNull
+        private final InputConfiguration mImageConfiguration;
+        @NonNull
+        private final Executor mExecutor;
+        @Nullable
+        private final ParcelFileDescriptor mReadDescriptor;
+        @Nullable
+        private final ParcelFileDescriptor mWriteDescriptor;
+        private int mSendingState;
+
+        VirtualCameraStream(@NonNull InputConfiguration imageConfiguration,
+                @NonNull Executor executor) throws IOException {
+            mSendingState = SENDING_STATE_INITIAL;
+            mImageConfiguration = Objects.requireNonNull(imageConfiguration);
+            mExecutor = Objects.requireNonNull(executor);
+            ParcelFileDescriptor[] parcels = ParcelFileDescriptor.createPipe();
+            mReadDescriptor = parcels[0];
+            mWriteDescriptor = parcels[1];
+        }
+
+        boolean isSameConfiguration(@NonNull InputConfiguration imageConfiguration) {
+            return mImageConfiguration == Objects.requireNonNull(imageConfiguration);
+        }
+
+        @Nullable
+        ParcelFileDescriptor getDescriptor() {
+            return mReadDescriptor;
+        }
+
+        public void startSending(@NonNull InputStream inputStream) {
+            Objects.requireNonNull(inputStream);
+
+            if (mSendingState != SENDING_STATE_INITIAL) {
+                return;
+            }
+
+            mSendingState = SENDING_STATE_IN_PROGRESS;
+            mExecutor.execute(() -> sendData(inputStream));
+        }
+
+        @Override
+        public void close() {
+            mSendingState = SENDING_STATE_CLOSED;
+            try {
+                mReadDescriptor.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Unable to close read descriptor.", e);
+            }
+            try {
+                mWriteDescriptor.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Unable to close write descriptor.", e);
+            }
+        }
+
+        private void sendData(@NonNull InputStream inputStream) {
+            Objects.requireNonNull(inputStream);
+
+            byte[] buffer = new byte[BUFFER_SIZE];
+            FileDescriptor fd = mWriteDescriptor.getFileDescriptor();
+            try (FileOutputStream outputStream = new FileOutputStream(fd)) {
+                while (mSendingState == SENDING_STATE_IN_PROGRESS) {
+                    int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
+                    if (bytesRead < 1) continue;
+
+                    outputStream.write(buffer, 0, bytesRead);
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Error while sending camera data.", e);
+            }
+        }
+    }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index df5a1ed..382e2bb 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6903,7 +6903,7 @@
      * <p>
      * Applications that run on virtual devices may use this method to access the default device
      * capabilities and functionality (by passing
-     * {@link android.companion.virtual.VirtualDeviceManager#DEFAULT_DEVICE_ID}. Similarly,
+     * {@link android.companion.virtual.VirtualDeviceManager#DEVICE_ID_DEFAULT}. Similarly,
      * applications running on the default device may access the functionality of virtual devices.
      * </p>
      * @param deviceId The ID of the device to associate with this context.
@@ -7245,7 +7245,7 @@
      * determine whether they are running on a virtual device and identify that device.
      *
      * The device ID of the host device is
-     * {@link android.companion.virtual.VirtualDeviceManager#DEFAULT_DEVICE_ID}
+     * {@link android.companion.virtual.VirtualDeviceManager#DEVICE_ID_DEFAULT}
      *
      * @return the ID of the device this context is associated with.
      * @see #createDeviceContext(int)
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 86a672f..8aa0454 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2422,6 +2422,30 @@
     public static final String ACTION_SAFETY_CENTER =
             "android.intent.action.SAFETY_CENTER";
 
+    /**
+     * Activity action: Launch the UI to view recent updates that installed apps have made to their
+     * data sharing policy in their safety labels.
+     *
+     * <p>
+     * Input: Nothing.
+     * </p>
+     * <p>
+     * Output: Nothing.
+     * </p>
+     *
+     * <p class="note">
+     * This intent action requires the {@link android.Manifest.permission#GRANT_RUNTIME_PERMISSIONS}
+     * permission.
+     * </p>
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_REVIEW_APP_DATA_SHARING_UPDATES =
+            "android.intent.action.REVIEW_APP_DATA_SHARING_UPDATES";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Standard intent broadcast actions (see action variable).
@@ -6093,9 +6117,8 @@
     public static final String EXTRA_UID = "android.intent.extra.UID";
 
     /**
-     * @hide String array of package names.
+     * String array of package names.
      */
-    @SystemApi
     public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
 
     /**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2c28268..84811ea 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1528,12 +1528,14 @@
      * the application, e.g. the target SDK version.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int HIDDEN_API_ENFORCEMENT_DEFAULT = -1;
     /**
      * No API enforcement; the app can access the entire internal private API. Only for use by
      * system apps.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int HIDDEN_API_ENFORCEMENT_DISABLED = 0;
     /**
      * No API enforcement, but enable the detection logic and warnings. Observed behaviour is the
@@ -1541,11 +1543,13 @@
      * APIs are accessed.
      * @hide
      * */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int HIDDEN_API_ENFORCEMENT_JUST_WARN = 1;
     /**
      * Dark grey list enforcement. Enforces the dark grey and black lists
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int HIDDEN_API_ENFORCEMENT_ENABLED = 2;
 
     private static final int HIDDEN_API_ENFORCEMENT_MIN = HIDDEN_API_ENFORCEMENT_DEFAULT;
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 1e928bd..115d4b0 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -69,4 +69,7 @@
     void setSilentUpdatesThrottleTime(long throttleTimeInSeconds);
     void checkInstallConstraints(String installerPackageName, in List<String> packageNames,
             in PackageInstaller.InstallConstraints constraints, in RemoteCallback callback);
+    void waitForInstallConstraints(String installerPackageName, in List<String> packageNames,
+            in PackageInstaller.InstallConstraints constraints, in IntentSender callback,
+            long timeout);
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 1f01ae9..f17d8fa 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -30,6 +30,7 @@
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -248,6 +249,24 @@
      */
     public static final String EXTRA_STORAGE_PATH = "android.content.pm.extra.STORAGE_PATH";
 
+    /**
+     * The {@link InstallConstraints} object.
+     *
+     * @see Intent#getParcelableExtra(String, Class)
+     * @see #waitForInstallConstraints(List, InstallConstraints, IntentSender, long)
+     */
+    public static final String EXTRA_INSTALL_CONSTRAINTS =
+            "android.content.pm.extra.INSTALL_CONSTRAINTS";
+
+    /**
+     * The {@link InstallConstraintsResult} object.
+     *
+     * @see Intent#getParcelableExtra(String, Class)
+     * @see #waitForInstallConstraints(List, InstallConstraints, IntentSender, long)
+     */
+    public static final String EXTRA_INSTALL_CONSTRAINTS_RESULT =
+            "android.content.pm.extra.INSTALL_CONSTRAINTS_RESULT";
+
     /** {@hide} */
     @Deprecated
     public static final String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES";
@@ -885,6 +904,32 @@
     }
 
     /**
+     * Similar to {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)},
+     * but the callback is invoked only when the constraints are satisfied or after timeout.
+     *
+     * @param callback Called when the constraints are satisfied or after timeout.
+     *                 Intents sent to this callback contain:
+     *                 {@link Intent#EXTRA_PACKAGES} for the input package names,
+     *                 {@link #EXTRA_INSTALL_CONSTRAINTS} for the input constraints,
+     *                 {@link #EXTRA_INSTALL_CONSTRAINTS_RESULT} for the result.
+     * @param timeoutMillis The maximum time to wait, in milliseconds until the constraints are
+     *                      satisfied. Valid range is from 0 to one week. {@code 0} means the
+     *                      callback will be invoked immediately no matter constraints are
+     *                      satisfied or not.
+     */
+    public void waitForInstallConstraints(@NonNull List<String> packageNames,
+            @NonNull InstallConstraints constraints,
+            @NonNull IntentSender callback,
+            @DurationMillisLong long timeoutMillis) {
+        try {
+            mInstaller.waitForInstallConstraints(
+                    mInstallerPackageName, packageNames, constraints, callback, timeoutMillis);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Events for observing session lifecycle.
      * <p>
      * A typical session lifecycle looks like this:
@@ -3807,7 +3852,7 @@
      * Note the constraints are applied transitively. If app Foo is used by app Bar (via shared
      * library or bounded service), the constraints will also be applied to Bar.
      */
-    @DataClass(genParcelable = true, genHiddenConstructor = true)
+    @DataClass(genParcelable = true, genHiddenConstructor = true, genEqualsHashCode=true)
     public static final class InstallConstraints implements Parcelable {
         /**
          * Preset constraints suitable for gentle update.
@@ -3968,6 +4013,41 @@
 
         @Override
         @DataClass.Generated.Member
+        public boolean equals(@Nullable Object o) {
+            // You can override field equality logic by defining either of the methods like:
+            // boolean fieldNameEquals(InstallConstraints other) { ... }
+            // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            @SuppressWarnings("unchecked")
+            InstallConstraints that = (InstallConstraints) o;
+            //noinspection PointlessBooleanExpression
+            return true
+                    && mRequireDeviceIdle == that.mRequireDeviceIdle
+                    && mRequireAppNotForeground == that.mRequireAppNotForeground
+                    && mRequireAppNotInteracting == that.mRequireAppNotInteracting
+                    && mRequireAppNotTopVisible == that.mRequireAppNotTopVisible
+                    && mRequireNotInCall == that.mRequireNotInCall;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int hashCode() {
+            // You can override field hashCode logic by defining methods like:
+            // int fieldNameHashCode() { ... }
+
+            int _hash = 1;
+            _hash = 31 * _hash + Boolean.hashCode(mRequireDeviceIdle);
+            _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotForeground);
+            _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotInteracting);
+            _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotTopVisible);
+            _hash = 31 * _hash + Boolean.hashCode(mRequireNotInCall);
+            return _hash;
+        }
+
+        @Override
+        @DataClass.Generated.Member
         public void writeToParcel(@NonNull Parcel dest, int flags) {
             // You can override field parcelling by defining methods like:
             // void parcelFieldName(Parcel dest, int flags) { ... }
@@ -4023,10 +4103,10 @@
         };
 
         @DataClass.Generated(
-                time = 1668650523752L,
+                time = 1670207178734L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
-                inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final  boolean mRequireDeviceIdle\nprivate final  boolean mRequireAppNotForeground\nprivate final  boolean mRequireAppNotInteracting\nprivate final  boolean mRequireAppNotTopVisible\nprivate final  boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mRequireDeviceIdle\nprivate  boolean mRequireAppNotForeground\nprivate  boolean mRequireAppNotInteracting\nprivate  boolean mRequireAppNotTopVisible\nprivate  boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+                inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final  boolean mRequireDeviceIdle\nprivate final  boolean mRequireAppNotForeground\nprivate final  boolean mRequireAppNotInteracting\nprivate final  boolean mRequireAppNotTopVisible\nprivate final  boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mRequireDeviceIdle\nprivate  boolean mRequireAppNotForeground\nprivate  boolean mRequireAppNotInteracting\nprivate  boolean mRequireAppNotTopVisible\nprivate  boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genEqualsHashCode=true)")
         @Deprecated
         private void __metadata() {}
 
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index fc2c532..6453ab0 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -308,9 +308,7 @@
      * permissions:
      * {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
      * {@link android.Manifest.permission#BODY_SENSORS},
-     * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS},
-     * or one of the {@code "android.permission.health.*"} permissions defined in the
-     * {@link android.healthconnect.HealthPermissions}.
+     * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
      */
     @RequiresPermission(
             allOf = {
@@ -320,8 +318,7 @@
                 Manifest.permission.ACTIVITY_RECOGNITION,
                 Manifest.permission.BODY_SENSORS,
                 Manifest.permission.HIGH_SAMPLING_RATE_SENSORS,
-            },
-            conditional = true
+            }
     )
     public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 1 << 8;
 
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index fd35378..fb61b37 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -46,6 +46,8 @@
     private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings";
     private static final String ATTR_INHERIT_DEVICE_POLICY = "inheritDevicePolicy";
     private static final String ATTR_USE_PARENTS_CONTACTS = "useParentsContacts";
+    private static final String ATTR_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA =
+            "updateCrossProfileIntentFiltersOnOTA";
 
     /** Index values of each property (to indicate whether they are present in this object). */
     @IntDef(prefix = "INDEX_", value = {
@@ -54,6 +56,7 @@
             INDEX_SHOW_IN_SETTINGS,
             INDEX_INHERIT_DEVICE_POLICY,
             INDEX_USE_PARENTS_CONTACTS,
+            INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
@@ -63,6 +66,7 @@
     private static final int INDEX_SHOW_IN_SETTINGS = 2;
     private static final int INDEX_INHERIT_DEVICE_POLICY = 3;
     private static final int INDEX_USE_PARENTS_CONTACTS = 4;
+    private static final int INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA = 5;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -199,6 +203,7 @@
             // Add items that require exposeAllFields to be true (strictest permission level).
             setStartWithParent(orig.getStartWithParent());
             setInheritDevicePolicy(orig.getInheritDevicePolicy());
+            setUpdateCrossProfileIntentFiltersOnOTA(orig.getUpdateCrossProfileIntentFiltersOnOTA());
         }
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
@@ -354,6 +359,34 @@
      */
     private boolean mUseParentsContacts;
 
+    /**
+     * Returns true if user needs to update default
+     * {@link com.android.server.pm.CrossProfileIntentFilter} with its parents during an OTA update
+     * @hide
+     */
+    public boolean getUpdateCrossProfileIntentFiltersOnOTA() {
+        if (isPresent(INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA)) {
+            return mUpdateCrossProfileIntentFiltersOnOTA;
+        }
+        if (mDefaultProperties != null) {
+            return mDefaultProperties.mUpdateCrossProfileIntentFiltersOnOTA;
+        }
+        throw new SecurityException("You don't have permission to query "
+                + "updateCrossProfileIntentFiltersOnOTA");
+    }
+
+    /** @hide */
+    public void setUpdateCrossProfileIntentFiltersOnOTA(boolean val) {
+        this.mUpdateCrossProfileIntentFiltersOnOTA = val;
+        setPresent(INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA);
+    }
+
+    /*
+     Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during
+     OTA update between user-parent
+     */
+    private boolean mUpdateCrossProfileIntentFiltersOnOTA;
+
     @Override
     public String toString() {
         // Please print in increasing order of PropertyIndex.
@@ -364,6 +397,8 @@
                 + ", mShowInSettings=" + getShowInSettings()
                 + ", mInheritDevicePolicy=" + getInheritDevicePolicy()
                 + ", mUseParentsContacts=" + getUseParentsContacts()
+                + ", mUpdateCrossProfileIntentFiltersOnOTA="
+                + getUpdateCrossProfileIntentFiltersOnOTA()
                 + "}";
     }
 
@@ -380,6 +415,8 @@
         pw.println(prefix + "    mShowInSettings=" + getShowInSettings());
         pw.println(prefix + "    mInheritDevicePolicy=" + getInheritDevicePolicy());
         pw.println(prefix + "    mUseParentsContacts=" + getUseParentsContacts());
+        pw.println(prefix + "    mUpdateCrossProfileIntentFiltersOnOTA="
+                + getUpdateCrossProfileIntentFiltersOnOTA());
     }
 
     /**
@@ -428,6 +465,9 @@
                 case ATTR_USE_PARENTS_CONTACTS:
                     setUseParentsContacts(parser.getAttributeBoolean(i));
                     break;
+                case ATTR_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA:
+                    setUpdateCrossProfileIntentFiltersOnOTA(parser.getAttributeBoolean(i));
+                    break;
                 default:
                     Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
             }
@@ -462,6 +502,11 @@
             serializer.attributeBoolean(null, ATTR_USE_PARENTS_CONTACTS,
                     mUseParentsContacts);
         }
+        if (isPresent(INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA)) {
+            serializer.attributeBoolean(null,
+                    ATTR_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA,
+                    mUpdateCrossProfileIntentFiltersOnOTA);
+        }
     }
 
     // For use only with an object that has already had any permission-lacking fields stripped out.
@@ -473,6 +518,7 @@
         dest.writeInt(mShowInSettings);
         dest.writeInt(mInheritDevicePolicy);
         dest.writeBoolean(mUseParentsContacts);
+        dest.writeBoolean(mUpdateCrossProfileIntentFiltersOnOTA);
     }
 
     /**
@@ -488,6 +534,7 @@
         mShowInSettings = source.readInt();
         mInheritDevicePolicy = source.readInt();
         mUseParentsContacts = source.readBoolean();
+        mUpdateCrossProfileIntentFiltersOnOTA = source.readBoolean();
     }
 
     @Override
@@ -517,6 +564,7 @@
         private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT;
         private @InheritDevicePolicy int mInheritDevicePolicy = INHERIT_DEVICE_POLICY_NO;
         private boolean mUseParentsContacts = false;
+        private boolean mUpdateCrossProfileIntentFiltersOnOTA = false;
 
         public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
             mShowInLauncher = showInLauncher;
@@ -546,6 +594,13 @@
             return this;
         }
 
+        /** Sets the value for {@link #mUpdateCrossProfileIntentFiltersOnOTA} */
+        public Builder setUpdateCrossProfileIntentFiltersOnOTA(boolean
+                updateCrossProfileIntentFiltersOnOTA) {
+            mUpdateCrossProfileIntentFiltersOnOTA = updateCrossProfileIntentFiltersOnOTA;
+            return this;
+        }
+
         /** Builds a UserProperties object with *all* values populated. */
         public UserProperties build() {
             return new UserProperties(
@@ -553,7 +608,8 @@
                     mStartWithParent,
                     mShowInSettings,
                     mInheritDevicePolicy,
-                    mUseParentsContacts);
+                    mUseParentsContacts,
+                    mUpdateCrossProfileIntentFiltersOnOTA);
         }
     } // end Builder
 
@@ -563,7 +619,7 @@
             boolean startWithParent,
             @ShowInSettings int showInSettings,
             @InheritDevicePolicy int inheritDevicePolicy,
-            boolean useParentsContacts) {
+            boolean useParentsContacts, boolean updateCrossProfileIntentFiltersOnOTA) {
 
         mDefaultProperties = null;
         setShowInLauncher(showInLauncher);
@@ -571,5 +627,6 @@
         setShowInSettings(showInSettings);
         setInheritDevicePolicy(inheritDevicePolicy);
         setUseParentsContacts(useParentsContacts);
+        setUpdateCrossProfileIntentFiltersOnOTA(updateCrossProfileIntentFiltersOnOTA);
     }
 }
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index fb1fcf8..d6934bc 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -43,6 +43,7 @@
 import android.annotation.XmlRes;
 import android.app.Application;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
 import android.content.res.loader.ResourcesLoader;
@@ -2181,17 +2182,19 @@
     }
 
     /**
-     * Return the current display metrics that are in effect for this resource
+     * Returns the current display metrics that are in effect for this resource
      * object. The returned object should be treated as read-only.
      *
      * <p>Note that the reported value may be different than the window this application is
      * interested in.</p>
      *
-     * <p>Best practices are to obtain metrics from {@link WindowManager#getCurrentWindowMetrics()}
-     * for window bounds, {@link Display#getRealMetrics(DisplayMetrics)} for display bounds and
-     * obtain density from {@link Configuration#densityDpi}. The value obtained from this API may be
-     * wrong if the {@link Resources} is from the context which is different than the window is
-     * attached such as {@link Application#getResources()}.
+     * <p>The best practices is to obtain metrics from
+     * {@link WindowManager#getCurrentWindowMetrics()} for window bounds. The value obtained from
+     * this API may be wrong if {@link Context#getResources()} is from
+     * non-{@link android.annotation.UiContext}.
+     * For example, use the {@link DisplayMetrics} obtained from {@link Application#getResources()}
+     * to build {@link android.app.Activity} UI elements especially when the
+     * {@link android.app.Activity} is in the multi-window mode or on the secondary {@link Display}.
      * <p/>
      *
      * @return The resource's current display metrics.
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index c011949..49e8495 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -21,6 +21,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.app.Activity;
 import android.app.PendingIntent;
@@ -32,6 +33,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -40,9 +42,9 @@
  * <p>Note that an application should call the Jetpack CredentialManager apis instead of directly
  * calling these framework apis.
  *
- * <p>The CredentialManager apis launch framework UI flows for a user to
- * register a new credential or to consent to a saved credential from supported credential
- * providers, which can then be used to authenticate to the app.
+ * <p>The CredentialManager apis launch framework UI flows for a user to register a new credential
+ * or to consent to a saved credential from supported credential providers, which can then be used
+ * to authenticate to the app.
  */
 @SystemService(Context.CREDENTIAL_SERVICE)
 public final class CredentialManager {
@@ -76,8 +78,7 @@
             @NonNull Activity activity,
             @Nullable CancellationSignal cancellationSignal,
             @CallbackExecutor @NonNull Executor executor,
-            @NonNull OutcomeReceiver<
-                    GetCredentialResponse, GetCredentialException> callback) {
+            @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
         requireNonNull(request, "request must not be null");
         requireNonNull(activity, "activity must not be null");
         requireNonNull(executor, "executor must not be null");
@@ -90,10 +91,11 @@
 
         ICancellationSignal cancelRemote = null;
         try {
-            cancelRemote = mService.executeGetCredential(
-                    request,
-                    new GetCredentialTransport(activity, executor, callback),
-                    mContext.getOpPackageName());
+            cancelRemote =
+                    mService.executeGetCredential(
+                            request,
+                            new GetCredentialTransport(activity, executor, callback),
+                            mContext.getOpPackageName());
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -106,8 +108,8 @@
     /**
      * Launches the necessary flows to register an app credential for the user.
      *
-     * <p>The execution can potentially launch UI flows to collect user consent to creating
-     * or storing the new credential, etc.
+     * <p>The execution can potentially launch UI flows to collect user consent to creating or
+     * storing the new credential, etc.
      *
      * @param request the request specifying type(s) of credentials to get from the user
      * @param activity the activity used to launch any UI needed
@@ -120,8 +122,8 @@
             @NonNull Activity activity,
             @Nullable CancellationSignal cancellationSignal,
             @CallbackExecutor @NonNull Executor executor,
-            @NonNull OutcomeReceiver<
-                    CreateCredentialResponse, CreateCredentialException> callback) {
+            @NonNull
+                    OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
         requireNonNull(request, "request must not be null");
         requireNonNull(activity, "activity must not be null");
         requireNonNull(executor, "executor must not be null");
@@ -134,9 +136,11 @@
 
         ICancellationSignal cancelRemote = null;
         try {
-            cancelRemote = mService.executeCreateCredential(request,
-                    new CreateCredentialTransport(activity, executor, callback),
-                    mContext.getOpPackageName());
+            cancelRemote =
+                    mService.executeCreateCredential(
+                            request,
+                            new CreateCredentialTransport(activity, executor, callback),
+                            mContext.getOpPackageName());
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -149,10 +153,10 @@
     /**
      * Clears the current user credential state from all credential providers.
      *
-     * You should invoked this api after your user signs out of your app to notify all credential
+     * <p>You should invoked this api after your user signs out of your app to notify all credential
      * providers that any stored credential session for the given app should be cleared.
      *
-     * A credential provider may have stored an active credential session and use it to limit
+     * <p>A credential provider may have stored an active credential session and use it to limit
      * sign-in options for future get-credential calls. For example, it may prioritize the active
      * credential over any other available credential. When your user explicitly signs out of your
      * app and in order to get the holistic sign-in options the next time, you should call this API
@@ -178,9 +182,11 @@
 
         ICancellationSignal cancelRemote = null;
         try {
-            cancelRemote = mService.clearCredentialState(request,
-                    new ClearCredentialStateTransport(executor, callback),
-                    mContext.getOpPackageName());
+            cancelRemote =
+                    mService.clearCredentialState(
+                            request,
+                            new ClearCredentialStateTransport(executor, callback),
+                            mContext.getOpPackageName());
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -190,15 +196,86 @@
         }
     }
 
+    /**
+     * Gets a list of all user configurable credential providers registered on the system. This API
+     * is intended for browsers and settings apps.
+     *
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
+     * @hide
+     */
+    @RequiresPermission(
+            allOf = {
+                android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS,
+                android.Manifest.permission.QUERY_ALL_PACKAGES
+            })
+    public void listEnabledProviders(
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull
+                    OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>
+                            callback) {
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "listEnabledProviders already canceled");
+            return;
+        }
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            cancelRemote =
+                    mService.listEnabledProviders(
+                            new ListEnabledProvidersTransport(executor, callback));
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
+    /**
+     * Sets a list of all user configurable credential providers registered on the system. This API
+     * is intended for settings apps.
+     *
+     * @param providers the list of enabled providers
+     * @param userId the user ID to configure credential manager for
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public void setEnabledProviders(
+            @NonNull List<String> providers,
+            int userId,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, SetEnabledProvidersException> callback) {
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+        requireNonNull(providers, "providers must not be null");
+
+        try {
+            mService.setEnabledProviders(
+                    providers, userId, new SetEnabledProvidersTransport(executor, callback));
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
         private final Activity mActivity;
         private final Executor mExecutor;
-        private final OutcomeReceiver<
-                GetCredentialResponse, GetCredentialException> mCallback;
+        private final OutcomeReceiver<GetCredentialResponse, GetCredentialException> mCallback;
 
-        private GetCredentialTransport(Activity activity, Executor executor,
+        private GetCredentialTransport(
+                Activity activity,
+                Executor executor,
                 OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
             mActivity = activity;
             mExecutor = executor;
@@ -210,8 +287,10 @@
             try {
                 mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
             } catch (IntentSender.SendIntentException e) {
-                Log.e(TAG, "startIntentSender() failed for intent:"
-                        + pendingIntent.getIntentSender(), e);
+                Log.e(
+                        TAG,
+                        "startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
+                        e);
                 // TODO: propagate the error.
             }
         }
@@ -233,10 +312,12 @@
 
         private final Activity mActivity;
         private final Executor mExecutor;
-        private final OutcomeReceiver<
-                CreateCredentialResponse, CreateCredentialException> mCallback;
+        private final OutcomeReceiver<CreateCredentialResponse, CreateCredentialException>
+                mCallback;
 
-        private CreateCredentialTransport(Activity activity, Executor executor,
+        private CreateCredentialTransport(
+                Activity activity,
+                Executor executor,
                 OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
             mActivity = activity;
             mExecutor = executor;
@@ -248,8 +329,10 @@
             try {
                 mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
             } catch (IntentSender.SendIntentException e) {
-                Log.e(TAG, "startIntentSender() failed for intent:"
-                        + pendingIntent.getIntentSender(), e);
+                Log.e(
+                        TAG,
+                        "startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
+                        e);
                 // TODO: propagate the error.
             }
         }
@@ -266,15 +349,14 @@
         }
     }
 
-    private static class ClearCredentialStateTransport
-            extends IClearCredentialStateCallback.Stub {
+    private static class ClearCredentialStateTransport extends IClearCredentialStateCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
         private final Executor mExecutor;
         private final OutcomeReceiver<Void, ClearCredentialStateException> mCallback;
 
-        private ClearCredentialStateTransport(Executor executor,
-                OutcomeReceiver<Void, ClearCredentialStateException> callback) {
+        private ClearCredentialStateTransport(
+                Executor executor, OutcomeReceiver<Void, ClearCredentialStateException> callback) {
             mExecutor = executor;
             mCallback = callback;
         }
@@ -290,4 +372,59 @@
                     () -> mCallback.onError(new ClearCredentialStateException(errorType, message)));
         }
     }
+
+    private static class ListEnabledProvidersTransport extends IListEnabledProvidersCallback.Stub {
+        // TODO: listen for cancellation to release callback.
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>
+                mCallback;
+
+        private ListEnabledProvidersTransport(
+                Executor executor,
+                OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>
+                        callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResponse(ListEnabledProvidersResponse response) {
+            mExecutor.execute(() -> mCallback.onResult(response));
+        }
+
+        @Override
+        public void onError(String errorType, String message) {
+            mExecutor.execute(
+                    () -> mCallback.onError(new ListEnabledProvidersException(errorType, message)));
+          }
+    }
+
+    private static class SetEnabledProvidersTransport extends ISetEnabledProvidersCallback.Stub {
+        // TODO: listen for cancellation to release callback.
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<Void, SetEnabledProvidersException> mCallback;
+
+        private SetEnabledProvidersTransport(
+                Executor executor, OutcomeReceiver<Void, SetEnabledProvidersException> callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        public void onResponse(Void result) {
+            mExecutor.execute(() -> mCallback.onResult(result));
+        }
+
+        @Override
+        public void onResponse() {
+            mExecutor.execute(() -> mCallback.onResult(null));
+        }
+
+        @Override
+        public void onError(String errorType, String message) {
+            mExecutor.execute(
+                    () -> mCallback.onError(new SetEnabledProvidersException(errorType, message)));
+        }
+    }
 }
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index c5497bd..c3ca03d 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -16,12 +16,16 @@
 
 package android.credentials;
 
+import java.util.List;
+
 import android.credentials.ClearCredentialStateRequest;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialRequest;
 import android.credentials.IClearCredentialStateCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.IGetCredentialCallback;
+import android.credentials.IListEnabledProvidersCallback;
+import android.credentials.ISetEnabledProvidersCallback;
 import android.os.ICancellationSignal;
 
 /**
@@ -36,4 +40,8 @@
     @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
 
     @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
+
+    @nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
+
+    void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
 }
diff --git a/core/java/android/credentials/IListEnabledProvidersCallback.aidl b/core/java/android/credentials/IListEnabledProvidersCallback.aidl
new file mode 100644
index 0000000..3a8e25ed
--- /dev/null
+++ b/core/java/android/credentials/IListEnabledProvidersCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 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 android.credentials;
+
+import android.credentials.ListEnabledProvidersResponse;
+
+/**
+ * Listener for an listEnabledProviders request.
+ *
+ * @hide
+ */
+interface IListEnabledProvidersCallback {
+    oneway void onResponse(in ListEnabledProvidersResponse response);
+    oneway void onError(String errorType, String message);
+}
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt b/core/java/android/credentials/ISetEnabledProvidersCallback.aidl
similarity index 65%
copy from services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
copy to core/java/android/credentials/ISetEnabledProvidersCallback.aidl
index 528680e7..3044278 100644
--- a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
+++ b/core/java/android/credentials/ISetEnabledProvidersCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright 2022 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.
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.server.permission.access.external
+package android.credentials;
 
-class RoSystemProperties {
-    companion object {
-        const val CONTROL_PRIVAPP_PERMISSIONS_DISABLE = false
-        const val CONTROL_PRIVAPP_PERMISSIONS_ENFORCE = false
-    }
-}
+/**
+ * Listener for an setEnabledProviders request.
+ *
+ * @hide
+ */
+interface ISetEnabledProvidersCallback {
+    oneway void onResponse();
+    oneway void onError(String errorType, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/ListEnabledProvidersException.java b/core/java/android/credentials/ListEnabledProvidersException.java
new file mode 100644
index 0000000..c12c656
--- /dev/null
+++ b/core/java/android/credentials/ListEnabledProvidersException.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2022 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 android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Represents an error encountered during the {@link
+ * CredentialManager#listEnabledProviders(CancellationSignal Executor, OutcomeReceiver)} operation.
+ *
+ * @hide
+ */
+public class ListEnabledProvidersException extends Exception {
+
+    @NonNull public final String errorType;
+
+    /**
+     * Constructs a {@link ListEnabledProvidersException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public ListEnabledProvidersException(@NonNull String errorType, @Nullable String message) {
+        this(errorType, message, null);
+    }
+
+    /**
+     * Constructs a {@link ListEnabledProvidersException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public ListEnabledProvidersException(
+            @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+        super(message, cause);
+        this.errorType =
+                Preconditions.checkStringNotEmpty(errorType, "errorType must not be empty");
+    }
+
+    /**
+     * Constructs a {@link ListEnabledProvidersException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public ListEnabledProvidersException(@NonNull String errorType, @Nullable Throwable cause) {
+        this(errorType, null, cause);
+    }
+
+    /**
+     * Constructs a {@link ListEnabledProvidersException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public ListEnabledProvidersException(@NonNull String errorType) {
+        this(errorType, null, null);
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt b/core/java/android/credentials/ListEnabledProvidersResponse.aidl
similarity index 65%
copy from services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
copy to core/java/android/credentials/ListEnabledProvidersResponse.aidl
index 528680e7..759bf48 100644
--- a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
+++ b/core/java/android/credentials/ListEnabledProvidersResponse.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright 2022 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.
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.server.permission.access.external
+package android.credentials;
 
-class RoSystemProperties {
-    companion object {
-        const val CONTROL_PRIVAPP_PERMISSIONS_DISABLE = false
-        const val CONTROL_PRIVAPP_PERMISSIONS_ENFORCE = false
-    }
-}
+parcelable ListEnabledProvidersResponse;
\ No newline at end of file
diff --git a/core/java/android/credentials/ListEnabledProvidersResponse.java b/core/java/android/credentials/ListEnabledProvidersResponse.java
new file mode 100644
index 0000000..532adf7
--- /dev/null
+++ b/core/java/android/credentials/ListEnabledProvidersResponse.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2022 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 android.credentials;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Response from Credential Manager listing the providers that are enabled and available to the
+ * user.
+ *
+ * @hide
+ */
+public final class ListEnabledProvidersResponse implements Parcelable {
+
+    /** List of providers. */
+    @NonNull private final List<String> mProviders;
+
+    /**
+     * Creates a {@link ListEnabledProvidersResponse} with a list of providers.
+     *
+     * @throws NullPointerException If args are null.
+     */
+    public static @NonNull ListEnabledProvidersResponse create(@NonNull List<String> providers) {
+        Objects.requireNonNull(providers, "providers must not be null");
+        Preconditions.checkCollectionElementsNotNull(providers, /* valueName= */ "providers");
+        return new ListEnabledProvidersResponse(providers);
+    }
+
+    private ListEnabledProvidersResponse(@NonNull List<String> providers) {
+        mProviders = providers;
+    }
+
+    private ListEnabledProvidersResponse(@NonNull Parcel in) {
+        mProviders = in.createStringArrayList();
+    }
+
+    public static final @NonNull Creator<ListEnabledProvidersResponse> CREATOR =
+            new Creator<ListEnabledProvidersResponse>() {
+                @Override
+                public ListEnabledProvidersResponse createFromParcel(Parcel in) {
+                    return new ListEnabledProvidersResponse(in);
+                }
+
+                @Override
+                public ListEnabledProvidersResponse[] newArray(int size) {
+                    return new ListEnabledProvidersResponse[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStringList(mProviders);
+    }
+
+    /** Returns the list of flattened Credential Manager provider component names as strings. */
+    public @NonNull List<String> getProviderComponentNames() {
+        return mProviders;
+    }
+}
diff --git a/core/java/android/credentials/SetEnabledProvidersException.java b/core/java/android/credentials/SetEnabledProvidersException.java
new file mode 100644
index 0000000..6178f349
--- /dev/null
+++ b/core/java/android/credentials/SetEnabledProvidersException.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2022 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 android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Represents an error encountered during the {@link
+ * CredentialManager#setEnabledProviders(CancellationSignal Executor, OutcomeReceiver)} operation.
+ *
+ * @hide
+ */
+public class SetEnabledProvidersException extends Exception {
+
+    @NonNull public final String errorType;
+
+    /**
+     * Constructs a {@link SetEnabledProvidersException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public SetEnabledProvidersException(@NonNull String errorType, @Nullable String message) {
+        this(errorType, message, null);
+    }
+
+    /**
+     * Constructs a {@link SetEnabledProvidersException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public SetEnabledProvidersException(
+            @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+        super(message, cause);
+        this.errorType =
+                Preconditions.checkStringNotEmpty(errorType, "errorType must not be empty");
+    }
+
+    /**
+     * Constructs a {@link SetEnabledProvidersException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public SetEnabledProvidersException(@NonNull String errorType, @Nullable Throwable cause) {
+        this(errorType, null, cause);
+    }
+
+    /**
+     * Constructs a {@link SetEnabledProvidersException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public SetEnabledProvidersException(@NonNull String errorType) {
+        this(errorType, null, null);
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt b/core/java/android/credentials/SetEnabledProvidersRequest.aidl
similarity index 65%
copy from services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
copy to core/java/android/credentials/SetEnabledProvidersRequest.aidl
index 528680e7..271f58f 100644
--- a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
+++ b/core/java/android/credentials/SetEnabledProvidersRequest.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright 2022 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.
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.server.permission.access.external
+package android.credentials;
 
-class RoSystemProperties {
-    companion object {
-        const val CONTROL_PRIVAPP_PERMISSIONS_DISABLE = false
-        const val CONTROL_PRIVAPP_PERMISSIONS_ENFORCE = false
-    }
-}
+parcelable SetEnabledProvidersRequest;
\ No newline at end of file
diff --git a/core/java/android/credentials/SetEnabledProvidersRequest.java b/core/java/android/credentials/SetEnabledProvidersRequest.java
new file mode 100644
index 0000000..d1136ba0
--- /dev/null
+++ b/core/java/android/credentials/SetEnabledProvidersRequest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2022 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 android.credentials;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Sets the enabled list of credential manager providers.
+ *
+ * @hide
+ */
+public final class SetEnabledProvidersRequest implements Parcelable {
+
+    /** List of providers. */
+    @NonNull private final List<String> mProviders;
+
+    /**
+     * Creates a {@link SetEnabledProvidersRequest} with a list of providers. The list is made up of
+     * strings that are flattened component names of the service that is the credman provider.
+     *
+     * @throws NullPointerException If args are null.
+     */
+    public SetEnabledProvidersRequest(@NonNull List<String> providers) {
+        Objects.requireNonNull(providers, "providers must not be null");
+        Preconditions.checkCollectionElementsNotNull(providers, /* valueName= */ "providers");
+        mProviders = providers;
+    }
+
+    private SetEnabledProvidersRequest(@NonNull Parcel in) {
+        mProviders = in.createStringArrayList();
+    }
+
+    public static final @NonNull Creator<SetEnabledProvidersRequest> CREATOR =
+            new Creator<SetEnabledProvidersRequest>() {
+                @Override
+                public SetEnabledProvidersRequest createFromParcel(Parcel in) {
+                    return new SetEnabledProvidersRequest(in);
+                }
+
+                @Override
+                public SetEnabledProvidersRequest[] newArray(int size) {
+                    return new SetEnabledProvidersRequest[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStringList(mProviders);
+    }
+
+    /** Returns the list of flattened Credential Manager provider component names as strings. */
+    public @NonNull List<String> getProviderComponentNames() {
+        return mProviders;
+    }
+}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 3bdd39f..5291d2b 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -29,12 +29,14 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.app.ActivityThread;
 import android.app.AppOpsManager;
+import android.app.compat.CompatChanges;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraManager;
 import android.media.AudioAttributes;
 import android.media.IAudioService;
 import android.os.Build;
@@ -45,6 +47,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.renderscript.Allocation;
 import android.renderscript.Element;
 import android.renderscript.RSIllegalArgumentException;
@@ -281,6 +284,14 @@
      */
     public native static int getNumberOfCameras();
 
+    private static final boolean sLandscapeToPortrait =
+            SystemProperties.getBoolean(CameraManager.LANDSCAPE_TO_PORTRAIT_PROP, false);
+
+    private static boolean shouldOverrideToPortrait() {
+        return CompatChanges.isChangeEnabled(CameraManager.OVERRIDE_FRONT_CAMERA_APP_COMPAT)
+                && sLandscapeToPortrait;
+    }
+
     /**
      * Returns the information about a particular camera.
      * If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1.
@@ -290,7 +301,9 @@
      *    low-level failure).
      */
     public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) {
-        _getCameraInfo(cameraId, cameraInfo);
+        boolean overrideToPortrait = shouldOverrideToPortrait();
+
+        _getCameraInfo(cameraId, overrideToPortrait, cameraInfo);
         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
         IAudioService audioService = IAudioService.Stub.asInterface(b);
         try {
@@ -303,7 +316,8 @@
             Log.e(TAG, "Audio service is unavailable for queries");
         }
     }
-    private native static void _getCameraInfo(int cameraId, CameraInfo cameraInfo);
+    private native static void _getCameraInfo(int cameraId, boolean overrideToPortrait,
+            CameraInfo cameraInfo);
 
     /**
      * Information about a camera
@@ -484,8 +498,9 @@
             mEventHandler = null;
         }
 
+        boolean overrideToPortrait = shouldOverrideToPortrait();
         return native_setup(new WeakReference<Camera>(this), cameraId,
-                ActivityThread.currentOpPackageName());
+                ActivityThread.currentOpPackageName(), overrideToPortrait);
     }
 
     /** used by Camera#open, Camera#open(int) */
@@ -555,7 +570,8 @@
     }
 
     @UnsupportedAppUsage
-    private native int native_setup(Object cameraThis, int cameraId, String packageName);
+    private native int native_setup(Object cameraThis, int cameraId, String packageName,
+            boolean overrideToPortrait);
 
     private native final void native_release();
 
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 161b1b7..9388ae3 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -17,7 +17,7 @@
 package android.hardware;
 
 import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
-import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
@@ -530,7 +530,7 @@
                     if (intent.getAction().equals(ACTION_VIRTUAL_DEVICE_REMOVED)) {
                         synchronized (mFullRuntimeSensorListByDevice) {
                             final int deviceId = intent.getIntExtra(
-                                    EXTRA_VIRTUAL_DEVICE_ID, DEFAULT_DEVICE_ID);
+                                    EXTRA_VIRTUAL_DEVICE_ID, DEVICE_ID_DEFAULT);
                             List<Sensor> removedSensors =
                                     mFullRuntimeSensorListByDevice.removeReturnOld(deviceId);
                             if (removedSensors != null) {
@@ -1134,7 +1134,7 @@
     }
 
     private boolean isDeviceSensorPolicyDefault(int deviceId) {
-        if (deviceId == DEFAULT_DEVICE_ID) {
+        if (deviceId == DEVICE_ID_DEFAULT) {
             return true;
         }
         if (mVdm == null) {
diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java
index 6f895d5..b0fafea 100644
--- a/core/java/android/hardware/camera2/CameraExtensionSession.java
+++ b/core/java/android/hardware/camera2/CameraExtensionSession.java
@@ -18,6 +18,11 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.camera2.impl.PublicKey;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.Pair;
+import android.util.Range;
 
 import java.util.concurrent.Executor;
 
@@ -424,6 +429,28 @@
     }
 
     /**
+     * Return the realtime still {@link #capture} latency.
+     *
+     * <p>The pair will be in milliseconds with the first value indicating the capture latency from
+     * the {@link ExtensionCaptureCallback#onCaptureStarted} until
+     * {@link ExtensionCaptureCallback#onCaptureProcessStarted}
+     * and the second value containing the estimated post-processing latency from
+     * {@link ExtensionCaptureCallback#onCaptureProcessStarted} until the processed frame returns
+     * to the client.</p>
+     *
+     * <p>The estimations will take into account the current environment conditions, the camera
+     * state and will include the time spent processing the multi-frame capture request along with
+     * any additional time for encoding of the processed buffer if necessary.</p>
+     *
+     * @return The realtime still capture latency,
+     * or {@code null} if the estimation is not supported.
+     */
+    @Nullable
+    public Pair<Long, Long> getRealtimeStillCaptureLatency() throws CameraAccessException {
+        throw new UnsupportedOperationException("Subclasses must override this method");
+    }
+
+    /**
      * Close this capture session asynchronously.
      *
      * <p>Closing a session frees up the target output Surfaces of the session
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index f858227..ce191e8 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -23,6 +23,10 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Overridable;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Point;
@@ -104,6 +108,24 @@
     private final boolean mHasOpenCloseListenerPermission;
 
     /**
+     * Force camera output to be rotated to portrait orientation on landscape cameras.
+     * Many apps do not handle this situation and display stretched images otherwise.
+     * @hide
+     */
+    @ChangeId
+    @Overridable
+    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+    @TestApi
+    public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L;
+
+    /**
+     * System property for allowing the above
+     * @hide
+     */
+    public static final String LANDSCAPE_TO_PORTRAIT_PROP =
+            "camera.enable_landscape_to_portrait";
+
+    /**
      * @hide
      */
     public CameraManager(Context context) {
@@ -526,7 +548,8 @@
             for (String physicalCameraId : physicalCameraIds) {
                 CameraMetadataNative physicalCameraInfo =
                         cameraService.getCameraCharacteristics(physicalCameraId,
-                                mContext.getApplicationInfo().targetSdkVersion);
+                                mContext.getApplicationInfo().targetSdkVersion,
+                                /*overrideToPortrait*/false);
                 StreamConfiguration[] configs = physicalCameraInfo.get(
                         CameraCharacteristics.
                                 SCALER_PHYSICAL_CAMERA_MULTI_RESOLUTION_STREAM_CONFIGURATIONS);
@@ -585,8 +608,9 @@
             try {
                 Size displaySize = getDisplaySize();
 
+                boolean overrideToPortrait = shouldOverrideToPortrait();
                 CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
-                        mContext.getApplicationInfo().targetSdkVersion);
+                        mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait);
                 try {
                     info.setCameraId(Integer.parseInt(cameraId));
                 } catch (NumberFormatException e) {
@@ -703,9 +727,12 @@
                         ICameraService.ERROR_DISCONNECTED,
                         "Camera service is currently unavailable");
                 }
+
+                boolean overrideToPortrait = shouldOverrideToPortrait();
                 cameraUser = cameraService.connectDevice(callbacks, cameraId,
-                    mContext.getOpPackageName(),  mContext.getAttributionTag(), uid,
-                    oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion);
+                    mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
+                    oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion,
+                    overrideToPortrait);
             } catch (ServiceSpecificException e) {
                 if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
                     throw new AssertionError("Should've gone down the shim path");
@@ -1133,6 +1160,11 @@
         return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId);
     }
 
+    private static boolean shouldOverrideToPortrait() {
+        return CompatChanges.isChangeEnabled(OVERRIDE_FRONT_CAMERA_APP_COMPAT)
+                && CameraManagerGlobal.sLandscapeToPortrait;
+    }
+
     /**
      * A callback for camera devices becoming available or unavailable to open.
      *
@@ -1579,6 +1611,9 @@
         public static final boolean sCameraServiceDisabled =
                 SystemProperties.getBoolean("config.disable_cameraservice", false);
 
+        public static final boolean sLandscapeToPortrait =
+                SystemProperties.getBoolean(LANDSCAPE_TO_PORTRAIT_PROP, false);
+
         public static CameraManagerGlobal get() {
             return gCameraManager;
         }
diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
index 615536b..360f809 100644
--- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
@@ -19,6 +19,7 @@
 
 import android.hardware.camera2.extension.CaptureStageImpl;
 import android.hardware.camera2.extension.ICaptureProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
 import android.hardware.camera2.extension.LatencyRange;
 import android.hardware.camera2.extension.Size;
 import android.hardware.camera2.extension.SizeList;
@@ -43,4 +44,5 @@
     CameraMetadataNative getAvailableCaptureRequestKeys();
     CameraMetadataNative getAvailableCaptureResultKeys();
     boolean isCaptureProcessProgressAvailable();
+    @nullable LatencyPair getRealtimeCaptureLatency();
 }
diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
index 0eca5a7..e0f1b64 100644
--- a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
@@ -19,6 +19,8 @@
 import android.hardware.camera2.extension.CameraSessionConfig;
 import android.hardware.camera2.extension.ICaptureCallback;
 import android.hardware.camera2.extension.IRequestProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
+import android.hardware.camera2.extension.LatencyRange;
 import android.hardware.camera2.extension.OutputSurface;
 
 /** @hide */
@@ -34,4 +36,5 @@
     int startCapture(in ICaptureCallback callback);
     void setParameters(in CaptureRequest captureRequest);
     int startTrigger(in CaptureRequest captureRequest, in ICaptureCallback callback);
+    @nullable LatencyPair getRealtimeCaptureLatency();
 }
diff --git a/core/java/android/hardware/camera2/extension/LatencyPair.aidl b/core/java/android/hardware/camera2/extension/LatencyPair.aidl
new file mode 100644
index 0000000..5174f1d
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/LatencyPair.aidl
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2022, 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 android.hardware.camera2.extension;
+
+/** @hide */
+parcelable LatencyPair
+{
+    long first;
+    long second;
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 3a8dc03..42c4411 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -43,6 +43,7 @@
 import android.hardware.camera2.extension.IRequestCallback;
 import android.hardware.camera2.extension.IRequestProcessorImpl;
 import android.hardware.camera2.extension.ISessionProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
 import android.hardware.camera2.extension.OutputConfigId;
 import android.hardware.camera2.extension.OutputSurface;
 import android.hardware.camera2.extension.ParcelCaptureResult;
@@ -61,6 +62,8 @@
 import android.os.HandlerThread;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
 
@@ -329,6 +332,28 @@
     }
 
     @Override
+    public Pair<Long, Long> getRealtimeStillCaptureLatency() throws CameraAccessException {
+        synchronized (mInterfaceLock) {
+            if (!mInitialized) {
+                throw new IllegalStateException("Uninitialized component");
+            }
+
+            try {
+                LatencyPair latency = mSessionProcessor.getRealtimeCaptureLatency();
+                if (latency != null) {
+                    return new Pair<>(latency.first, latency.second);
+                }
+
+                return null;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to query realtime latency! Extension service does not "
+                        + "respond");
+                throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
+            }
+        }
+    }
+
+    @Override
     public int setRepeatingRequest(@NonNull CaptureRequest request, @NonNull Executor executor,
             @NonNull ExtensionCaptureCallback listener) throws CameraAccessException {
         int seqId = -1;
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 389c214..259bd7b 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -43,6 +43,7 @@
 import android.hardware.camera2.extension.IPreviewExtenderImpl;
 import android.hardware.camera2.extension.IProcessResultImpl;
 import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
 import android.hardware.camera2.extension.ParcelImage;
 import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.params.ExtensionSessionConfiguration;
@@ -59,6 +60,7 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
 
@@ -466,6 +468,28 @@
     }
 
     @Override
+    public Pair<Long, Long> getRealtimeStillCaptureLatency() throws CameraAccessException {
+        synchronized (mInterfaceLock) {
+            if (!mInitialized) {
+                throw new IllegalStateException("Uninitialized component");
+            }
+
+            try {
+                LatencyPair latency = mImageExtender.getRealtimeCaptureLatency();
+                if (latency != null) {
+                    return new Pair<>(latency.first, latency.second);
+                }
+
+                return null;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to query realtime latency! Extension service does not "
+                        + "respond");
+                throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
+            }
+        }
+    }
+
+    @Override
     public int setRepeatingRequest(@NonNull CaptureRequest request,
                                    @NonNull Executor executor,
                                    @NonNull ExtensionCaptureCallback listener)
diff --git a/core/java/android/hardware/display/AmbientBrightnessDayStats.java b/core/java/android/hardware/display/AmbientBrightnessDayStats.java
index 8aff911..2816706 100644
--- a/core/java/android/hardware/display/AmbientBrightnessDayStats.java
+++ b/core/java/android/hardware/display/AmbientBrightnessDayStats.java
@@ -208,7 +208,7 @@
     }
 
     private int getBucketIndex(float ambientBrightness) {
-        if (ambientBrightness < mBucketBoundaries[0]) {
+        if (ambientBrightness < mBucketBoundaries[0] || Float.isNaN(ambientBrightness)) {
             return -1;
         }
         int low = 0;
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 829908f..7409187 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -439,9 +439,6 @@
         // 1 (brighter). Set to Float.NaN if there's no override.
         public float screenAutoBrightnessAdjustmentOverride;
 
-        // If true, enables automatic brightness control.
-        public boolean useAutoBrightness;
-
         // If true, scales the brightness to a fraction of desired (as defined by
         // screenLowPowerBrightnessFactor).
         public boolean lowPowerMode;
@@ -471,7 +468,6 @@
             policy = POLICY_BRIGHT;
             useProximitySensor = false;
             screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-            useAutoBrightness = false;
             screenAutoBrightnessAdjustmentOverride = Float.NaN;
             screenLowPowerBrightnessFactor = 0.5f;
             blockScreenOn = false;
@@ -491,7 +487,6 @@
             policy = other.policy;
             useProximitySensor = other.useProximitySensor;
             screenBrightnessOverride = other.screenBrightnessOverride;
-            useAutoBrightness = other.useAutoBrightness;
             screenAutoBrightnessAdjustmentOverride = other.screenAutoBrightnessAdjustmentOverride;
             screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor;
             blockScreenOn = other.blockScreenOn;
@@ -513,7 +508,6 @@
                     && useProximitySensor == other.useProximitySensor
                     && floatEquals(screenBrightnessOverride,
                             other.screenBrightnessOverride)
-                    && useAutoBrightness == other.useAutoBrightness
                     && floatEquals(screenAutoBrightnessAdjustmentOverride,
                             other.screenAutoBrightnessAdjustmentOverride)
                     && screenLowPowerBrightnessFactor
@@ -539,7 +533,6 @@
             return "policy=" + policyToString(policy)
                     + ", useProximitySensor=" + useProximitySensor
                     + ", screenBrightnessOverride=" + screenBrightnessOverride
-                    + ", useAutoBrightness=" + useAutoBrightness
                     + ", screenAutoBrightnessAdjustmentOverride="
                     + screenAutoBrightnessAdjustmentOverride
                     + ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index eef0f42..6314cab 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -120,8 +120,8 @@
             in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype,
             String keyboardLayoutDescriptor);
 
-    String[] getKeyboardLayoutListForInputDevice(in InputDeviceIdentifier identifier, int userId,
-            in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+    KeyboardLayout[] getKeyboardLayoutListForInputDevice(in InputDeviceIdentifier identifier,
+            int userId, in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
 
     // Modifier key remapping APIs.
     @EnforcePermission("REMAP_MODIFIER_KEYS")
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index a616014..2eeae46 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -986,7 +986,7 @@
     @Nullable
     public String getKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @NonNull InputMethodSubtype imeSubtype) {
+            @Nullable InputMethodSubtype imeSubtype) {
         try {
             return mIm.getKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype);
         } catch (RemoteException ex) {
@@ -1014,7 +1014,7 @@
     @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
     public void setKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @NonNull InputMethodSubtype imeSubtype, @NonNull String keyboardLayoutDescriptor) {
+            @Nullable InputMethodSubtype imeSubtype, @NonNull String keyboardLayoutDescriptor) {
         if (identifier == null) {
             throw new IllegalArgumentException("identifier must not be null");
         }
@@ -1031,8 +1031,8 @@
     }
 
     /**
-     * Gets all keyboard layout descriptors that are enabled for the specified input device, userId,
-     * imeInfo and imeSubtype.
+     * Gets all keyboard layouts that are enabled for the specified input device, userId, imeInfo
+     * and imeSubtype.
      *
      * @param identifier The identifier for the input device.
      * @param userId user profile ID
@@ -1042,9 +1042,9 @@
      *
      * @hide
      */
-    public String[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+    public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @NonNull InputMethodSubtype imeSubtype) {
+            @Nullable InputMethodSubtype imeSubtype) {
         if (identifier == null) {
             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
         }
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpad.java b/core/java/android/hardware/input/VirtualNavigationTouchpad.java
new file mode 100644
index 0000000..2854034
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpad.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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 android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * A virtual navigation touchpad representing a touch-based input mechanism on a remote device.
+ *
+ * <p>This registers an InputDevice that is interpreted like a physically-connected device and
+ * dispatches received events to it.
+ *
+ * <p>The virtual touchpad will be in navigation mode. Motion results in focus traversal in the same
+ * manner as D-Pad navigation if the events are not consumed.
+ *
+ * @see android.view.InputDevice#SOURCE_TOUCH_NAVIGATION
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualNavigationTouchpad extends VirtualInputDevice {
+
+    /** @hide */
+    public VirtualNavigationTouchpad(IVirtualDevice virtualDevice, IBinder token) {
+        super(virtualDevice, token);
+    }
+
+    /**
+     * Sends a touch event to the system.
+     *
+     * @param event the event to send
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void sendTouchEvent(@NonNull VirtualTouchEvent event) {
+        try {
+            mVirtualDevice.sendTouchEvent(mToken, event);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.aidl
similarity index 71%
rename from services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
rename to core/java/android/hardware/input/VirtualNavigationTouchpadConfig.aidl
index 528680e7..d912491 100644
--- a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.aidl
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.server.permission.access.external
+package android.hardware.input;
 
-class RoSystemProperties {
-    companion object {
-        const val CONTROL_PRIVAPP_PERMISSIONS_DISABLE = false
-        const val CONTROL_PRIVAPP_PERMISSIONS_ENFORCE = false
-    }
-}
+parcelable VirtualNavigationTouchpadConfig;
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
new file mode 100644
index 0000000..f2805bb
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 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 android.hardware.input;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create virtual navigation touchpad.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualNavigationTouchpadConfig extends VirtualInputDeviceConfig
+        implements Parcelable {
+
+    /** The touchpad height. */
+    private final int mHeight;
+    /** The touchpad width. */
+    private final int mWidth;
+
+    private VirtualNavigationTouchpadConfig(@NonNull Builder builder) {
+        super(builder);
+        mHeight = builder.mHeight;
+        mWidth = builder.mWidth;
+    }
+
+    private VirtualNavigationTouchpadConfig(@NonNull Parcel in) {
+        super(in);
+        mHeight = in.readInt();
+        mWidth = in.readInt();
+    }
+
+    /** Returns the touchpad height. */
+    public int getHeight() {
+        return mHeight;
+    }
+
+    /** Returns the touchpad width. */
+    public int getWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mHeight);
+        dest.writeInt(mWidth);
+    }
+
+    @NonNull
+    public static final Creator<VirtualNavigationTouchpadConfig> CREATOR =
+            new Creator<VirtualNavigationTouchpadConfig>() {
+                @Override
+                public VirtualNavigationTouchpadConfig createFromParcel(Parcel in) {
+                    return new VirtualNavigationTouchpadConfig(in);
+                }
+
+                @Override
+                public VirtualNavigationTouchpadConfig[] newArray(int size) {
+                    return new VirtualNavigationTouchpadConfig[size];
+                }
+            };
+
+    /**
+     * Builder for creating a {@link VirtualNavigationTouchpadConfig}.
+     */
+    public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+
+        private final int mHeight;
+        private final int mWidth;
+
+        public Builder(@IntRange(from = 1) int touchpadHeight,
+                @IntRange(from = 1) int touchpadWidth) {
+            if (touchpadHeight <= 0 || touchpadWidth <= 0) {
+                throw new IllegalArgumentException(
+                        "Cannot create a virtual navigation touchpad, touchpad dimensions must be "
+                                + "positive. Got: (" + touchpadHeight + ", "
+                                + touchpadWidth + ")");
+            }
+            mHeight = touchpadHeight;
+            mWidth = touchpadWidth;
+        }
+
+        /**
+         * Builds the {@link VirtualNavigationTouchpadConfig} instance.
+         */
+        @NonNull
+        public VirtualNavigationTouchpadConfig build() {
+            return new VirtualNavigationTouchpadConfig(this);
+        }
+    }
+}
diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl
index 9349cf7..c7131a7 100644
--- a/core/java/android/hardware/radio/IRadioService.aidl
+++ b/core/java/android/hardware/radio/IRadioService.aidl
@@ -31,7 +31,7 @@
     List<RadioManager.ModuleProperties> listModules();
 
     ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio,
-            in ITunerCallback callback);
+            in ITunerCallback callback, int targetSdkVersion);
 
     ICloseHandle addAnnouncementListener(in int[] enabledTypes,
             in IAnnouncementListener listener);
diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl
index 7bf234b..e68c3cc 100644
--- a/core/java/android/hardware/radio/ITuner.aidl
+++ b/core/java/android/hardware/radio/ITuner.aidl
@@ -49,7 +49,7 @@
     /**
      * @throws IllegalStateException if called out of sequence
      */
-    void scan(boolean directionDown, boolean skipSubChannel);
+    void seek(boolean directionDown, boolean skipSubChannel);
 
     /**
      * @throws IllegalArgumentException if invalid arguments are passed
diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl
index f98947b..13092cc 100644
--- a/core/java/android/hardware/radio/ITunerCallback.aidl
+++ b/core/java/android/hardware/radio/ITunerCallback.aidl
@@ -24,6 +24,13 @@
 /** {@hide} */
 oneway interface ITunerCallback {
     void onError(int status);
+
+    /**
+     * Callback called when tuning operations, such as tune, step, seek, failed.
+     *
+     * @param result Tuning result of {@link RadioTuner#TunerResultType} type.
+     * @param selector Program selector used for the tuning operation.
+     */
     void onTuneFailed(int result, in ProgramSelector selector);
     void onConfigurationChanged(in RadioManager.BandConfig config);
     void onCurrentProgramInfoChanged(in RadioManager.ProgramInfo info);
@@ -36,6 +43,18 @@
     void onProgramListUpdated(in ProgramList.Chunk chunk);
 
     /**
+     * Callback for passing updates to config flags from {@link IRadioService} to
+     * {@link RadioTuner}.
+     *
+     * @param flag Config flag (defined in {@link RadioManager.ConfigFlag}) updated
+     * @param value Updated value for the config flag
+     */
+    void onConfigFlagUpdated(int flag, boolean value);
+
+    /**
+     * Callback for passing updates to vendor-specific parameter values from
+     * {@link IRadioService} to {@link RadioTuner}.
+     *
      * @param parameters Vendor-specific key-value pairs
      */
     void onParametersUpdated(in Map<String, String> parameters);
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 8a92135..7faa285 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -167,7 +167,10 @@
     public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
     /**
      * @see {@link IDENTIFIER_TYPE_DAB_SID_EXT}
+     *
+     * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5;
     /**
      * 28bit compound primary identifier for Digital Audio Broadcasting.
@@ -183,7 +186,10 @@
      *
      * The remaining bits should be set to zeros when writing on the chip side
      * and ignored when read.
+     *
+     * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_DAB_SID_EXT = IDENTIFIER_TYPE_DAB_SIDECC;
     /** 16bit */
     public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6;
@@ -197,7 +203,7 @@
     public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
     /**
      * 1: AM, 2:FM
-     * @deprecated use {@link IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
+     * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
      */
     @Deprecated
     public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
@@ -206,6 +212,23 @@
     /** 0-999 range */
     public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
     /**
+     * 44bit compound primary identifier for Digital Audio Broadcasting and
+     * Digital Multimedia Broadcasting.
+     *
+     * <p>Consists of (from the LSB):
+     * - 32bit: SId;
+     * - 8bit: ECC code;
+     * - 4bit: SCIdS.
+     *
+     * <p>SCIdS (Service Component Identifier within the Service) value
+     * of 0 represents the main service, while 1 and above represents
+     * secondary services.
+     *
+     * The remaining bits should be set to zeros when writing on the chip side
+     * and ignored when read.
+     */
+    public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14;
+    /**
      * Primary identifier for vendor-specific radio technology.
      * The value format is determined by a vendor.
      *
@@ -219,12 +242,12 @@
      */
     public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END;
     /**
-     * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_START} instead
+     * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_START} instead
      */
     @Deprecated
     public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = IDENTIFIER_TYPE_VENDOR_START;
     /**
-     * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_END} instead
+     * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_END} instead
      */
     @Deprecated
     public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END;
@@ -245,6 +268,7 @@
         IDENTIFIER_TYPE_DRMO_MODULATION,
         IDENTIFIER_TYPE_SXM_SERVICE_ID,
         IDENTIFIER_TYPE_SXM_CHANNEL,
+        IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
     })
     @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END)
     @Retention(RetentionPolicy.SOURCE)
@@ -285,7 +309,7 @@
      * Type of a radio technology.
      *
      * @return program type.
-     * @deprecated use {@link getPrimaryId} instead
+     * @deprecated use {@link #getPrimaryId} instead
      */
     @Deprecated
     public @ProgramType int getProgramType() {
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 9a217f9..f072e3b 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -82,6 +82,24 @@
     /** Method return status: time out before operation completion */
     public static final int STATUS_TIMED_OUT = -110;
 
+    /**
+     *  Radio operation status types
+     *
+     * @hide
+     */
+    @IntDef(prefix = { "STATUS_" }, value = {
+            STATUS_OK,
+            STATUS_ERROR,
+            STATUS_PERMISSION_DENIED,
+            STATUS_NO_INIT,
+            STATUS_BAD_VALUE,
+            STATUS_DEAD_OBJECT,
+            STATUS_INVALID_OPERATION,
+            STATUS_TIMED_OUT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RadioStatusType{}
+
 
     // keep in sync with radio_class_t in /system/core/incluse/system/radio.h
     /** Radio module class supporting FM (including HD radio) and AM */
@@ -330,6 +348,7 @@
          * program list.
          * @return the number of audio sources available.
          */
+        @RadioStatusType
         public int getNumAudioSources() {
             return mNumAudioSources;
         }
@@ -1724,6 +1743,7 @@
      * </ul>
      */
     @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioStatusType
     public int listModules(List<ModuleProperties> modules) {
         if (modules == null) {
             Log.e(TAG, "the output list must not be empty");
@@ -1776,7 +1796,7 @@
         ITuner tuner;
         TunerCallbackAdapter halCallback = new TunerCallbackAdapter(callback, handler);
         try {
-            tuner = mService.openTuner(moduleId, config, withAudio, halCallback);
+            tuner = mService.openTuner(moduleId, config, withAudio, halCallback, mTargetSdkVersion);
         } catch (RemoteException | IllegalArgumentException | IllegalStateException ex) {
             Log.e(TAG, "Failed to open tuner", ex);
             return null;
@@ -1853,6 +1873,7 @@
 
     @NonNull private final Context mContext;
     @NonNull private final IRadioService mService;
+    private final int mTargetSdkVersion;
 
     /**
      * @hide
@@ -1869,5 +1890,6 @@
     public RadioManager(Context context, IRadioService service) {
         mContext = context;
         mService = service;
+        mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
     }
 }
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
index 969db96..9b2bcde 100644
--- a/core/java/android/hardware/radio/RadioTuner.java
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -16,14 +16,20 @@
 
 package android.hardware.radio;
 
+import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.graphics.Bitmap;
 import android.os.Handler;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 /**
  * RadioTuner interface provides methods to control a radio tuner on the device: selecting and
@@ -43,16 +49,21 @@
     public static final int DIRECTION_DOWN    = 1;
 
     /**
-     * Close the tuner interface. The {@link Callback} callback will not be called
-     * anymore and associated resources will be released.
-     * Must be called when the tuner is not needed to make hardware resources available to others.
+     * Close the tuner interface.
+     *
+     * <p>The {@link Callback} callback will not be called anymore and associated resources will be
+     * released. Must be called when the tuner is not needed to make hardware resources available
+     * to others.
      * */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract void close();
 
     /**
      * Set the active band configuration for this module.
-     * Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor listed
-     * in the ModuleProperties of the module with the specified ID.
+     *
+     * <p>Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor
+     * listed in the ModuleProperties of the module with the specified ID.
+     *
      * @param config The desired band configuration (FmBandConfig or AmBandConfig).
      * @return
      * <ul>
@@ -67,10 +78,13 @@
      * @deprecated Only applicable for HAL 1.x.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int setConfiguration(RadioManager.BandConfig config);
 
     /**
      * Get current configuration.
+     *
      * @param config a BandConfig array of lengh 1 where the configuration is returned.
      * @return
      * <ul>
@@ -86,11 +100,15 @@
      * @deprecated Only applicable for HAL 1.x.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int getConfiguration(RadioManager.BandConfig[] config);
 
 
     /**
-     * Set mute state. When muted, the radio tuner audio source is not available for playback on
+     * Set mute state.
+     *
+     * <p>When muted, the radio tuner audio source is not available for playback on
      * any audio device. when unmuted, the radio tuner audio source is output as a media source
      * and renderd over the audio device selected for media use case.
      * The radio tuner audio source is muted by default when the tuner is first attached.
@@ -107,6 +125,8 @@
      *  service fails, </li>
      * </ul>
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int setMute(boolean mute);
 
     /**
@@ -115,13 +135,19 @@
      * @return {@code true} if the radio tuner audio source is muted or a problem occured
      * retrieving the mute state, {@code false} otherwise.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract boolean getMute();
 
     /**
      * Step up or down by one channel spacing.
-     * The operation is asynchronous and {@link Callback}
-     * onProgramInfoChanged() will be called when step completes or
-     * onError() when cancelled or timeout.
+     *
+     * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+     * will be called when step completes or {@link Callback#onTuneFailed}
+     * when timeout or canceled.
+     *
+     * <p>When this operation is called by users other than current user or system user, it is
+     * ignored silently.
+     *
      * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
      * @param skipSubChannel indicates to skip sub channels when the configuration currently
      * selected supports sub channel (e.g HD Radio). N/A otherwise.
@@ -136,13 +162,50 @@
      *  service fails, </li>
      * </ul>
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int step(int direction, boolean skipSubChannel);
 
     /**
      * Scan up or down to next valid station.
-     * The operation is asynchronous and {@link Callback}
-     * onProgramInfoChanged() will be called when scan completes or
-     * onError() when cancelled or timeout.
+     *
+     * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+     * will be called when scan completes or {@link Callback#onTuneFailed}
+     * when timeout or canceled.
+     *
+     * <p>When this operation is called by users other than current user or system user, it is
+     * ignored silently.
+     *
+     * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
+     * @param skipSubChannel indicates to skip sub channels when the configuration currently
+     * selected supports sub channel (e.g HD Radio). N/A otherwise.
+     * @return
+     * <ul>
+     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *  service fails, </li>
+     * </ul>
+     * @deprecated Use {@link #seek(int, boolean)} instead.
+     */
+    @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
+    public abstract int scan(int direction, boolean skipSubChannel);
+
+    /**
+     * Seek up or down to next valid station.
+     *
+     * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+     * will be called when seek completes or {@link Callback#onTuneFailed}
+     * when timeout or canceled.
+     *
+     * <p>When this operation is called by users other than current user or system user, it is
+     * ignore silently.
+     *
      * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
      * @param skipSubChannel indicates to skip sub channels when the configuration currently
      * selected supports sub channel (e.g HD Radio). N/A otherwise.
@@ -157,13 +220,22 @@
      *  service fails, </li>
      * </ul>
      */
-    public abstract int scan(int direction, boolean skipSubChannel);
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
+    public int seek(int direction, boolean skipSubChannel) {
+        throw new UnsupportedOperationException("Seeking is not supported");
+    }
 
     /**
      * Tune to a specific frequency.
-     * The operation is asynchronous and {@link Callback}
-     * onProgramInfoChanged() will be called when tune completes or
-     * onError() when cancelled or timeout.
+     *
+     * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+     * will be called when tune completes or {@link Callback#onTuneFailed}
+     * when timeout or canceled.
+     *
+     * <p>When this operation is called by users other than current user or system user, it is
+     * ignored silently.
+     *
      * @param channel the specific channel or frequency to tune to.
      * @param subChannel the specific sub-channel to tune to. N/A if the selected configuration
      * does not support cub channels.
@@ -177,25 +249,37 @@
      *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
      *  service fails, </li>
      * </ul>
-     * @deprecated Use {@link tune(ProgramSelector)} instead.
+     * @deprecated Use {@link #tune(ProgramSelector)} instead.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int tune(int channel, int subChannel);
 
     /**
      * Tune to a program.
      *
-     * The operation is asynchronous and {@link Callback} onProgramInfoChanged() will be called
-     * when tune completes or onError() when cancelled or on timeout.
+     * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+     * will be called when tune completes or {@link Callback#onTuneFailed}
+     * when timeout or canceled.
+     *
+     * <p>When this operation is called by users other than current user or system user, it is
+     * ignored silently.
      *
      * @throws IllegalArgumentException if the provided selector is invalid
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract void tune(@NonNull ProgramSelector selector);
 
     /**
      * Cancel a pending scan or tune operation.
-     * If an operation is pending, {@link Callback} onError() will be called with
+     *
+     * <p>If an operation is pending, {@link Callback#onTuneFailed} will be called with
      * {@link #ERROR_CANCELLED}.
+     *
+     * <p>When this operation is called by users other than current user or system
+     * user, it is ignored silently.
+     *
      * @return
      * <ul>
      *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
@@ -207,21 +291,27 @@
      *  service fails, </li>
      * </ul>
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int cancel();
 
     /**
      * Cancels traffic or emergency announcement.
      *
-     * If there was no announcement to cancel, no action is taken.
+     * <p>If there was no announcement to cancel, no action is taken.
      *
-     * There is a race condition between calling cancelAnnouncement and the actual announcement
+     * <p>There is a race condition between calling cancelAnnouncement and the actual announcement
      * being finished, so onTrafficAnnouncement / onEmergencyAnnouncement callback should be
      * tracked with proper locking.
+     * @deprecated Only applicable for HAL 1.x.
      */
+    @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract void cancelAnnouncement();
 
     /**
      * Get current station information.
+     *
      * @param info a ProgramInfo array of lengh 1 where the information is returned.
      * @return
      * <ul>
@@ -233,23 +323,27 @@
      *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
      *  service fails, </li>
      * </ul>
-     * @deprecated Use {@link onProgramInfoChanged} callback instead.
+     * @deprecated Use {@link Callback#onProgramInfoChanged(RadioManager.ProgramInfo)} callback
+     * instead.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int getProgramInformation(RadioManager.ProgramInfo[] info);
 
     /**
      * Retrieves a {@link Bitmap} for the given image ID or null,
      * if the image was missing from the tuner.
      *
-     * This involves doing a call to the tuner, so the bitmap should be cached
+     * <p>This involves doing a call to the tuner, so the bitmap should be cached
      * on the application side.
      *
-     * If the method returns null for non-zero ID, it means the image was
+     * <p>If the method returns null for non-zero ID, it means the image was
      * updated on the tuner side. There is a race conditon between fetching
      * image for an old ID and tuner updating the image (and cleaning up the
      * old image). In such case, a new ProgramInfo with updated image id will
-     * be sent with a {@link onProgramInfoChanged} callback.
+     * be sent with a {@link Callback#onProgramInfoChanged(RadioManager.ProgramInfo)}
+     * callback.
      *
      * @param id The image identifier, retrieved with
      *           {@link RadioMetadata#getBitmapId(String)}.
@@ -258,14 +352,16 @@
      * @hide This API is not thoroughly elaborated yet
      */
     @SuppressWarnings("HiddenAbstractMethod")
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract @Nullable Bitmap getMetadataImage(int id);
 
     /**
      * Initiates a background scan to update internally cached program list.
      *
-     * It may not be necessary to initiate the scan explicitly - the scan MAY be performed on boot.
+     * <p>It may not be necessary to initiate the scan explicitly - the scan MAY be performed on
+     * boot.
      *
-     * The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will
+     * <p>The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will
      * be called if the return value of this call was {@code true}. As result of this call
      * programListChanged may be triggered (if the scanned list differs).
      *
@@ -273,13 +369,16 @@
      * is unavailable; ie. temporarily due to ongoing foreground playback in single-tuner device
      * or permanently if the feature is not supported
      * (see ModuleProperties#isBackgroundScanningSupported()).
+     * @deprecated Only applicable for HAL 1.x.
      */
+    @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract boolean startBackgroundScan();
 
     /**
      * Get the list of discovered radio stations.
      *
-     * To get the full list, set filter to null or empty map.
+     * <p>To get the full list, set filter to null or empty map.
      * Keys must be prefixed with unique vendor Java-style namespace,
      * eg. 'com.somecompany.parameter1'.
      *
@@ -288,24 +387,27 @@
      * @throws IllegalStateException if the scan is in progress or has not been started,
      *         startBackgroundScan() call may fix it.
      * @throws IllegalArgumentException if the vendorFilter argument is not valid.
-     * @deprecated Use {@link getDynamicProgramList} instead.
+     * @deprecated Use {@link #getDynamicProgramList(ProgramList.Filter)} instead.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract @NonNull List<RadioManager.ProgramInfo>
             getProgramList(@Nullable Map<String, String> vendorFilter);
 
     /**
      * Get the dynamic list of discovered radio stations.
      *
-     * The list object is updated asynchronously; to get the updates register
-     * with {@link ProgramList#addListCallback}.
+     * <p>The list object is updated asynchronously; to get the updates register
+     * with {@link ProgramList#registerListCallback(ProgramList.ListCallback)}
+     * or {@link ProgramList#registerListCallback(Executor, ProgramList.ListCallback)}.
      *
-     * When the returned object is no longer used, it must be closed.
+     * <p>When the returned object is no longer used, it must be closed.
      *
      * @param filter filter for the list, or null to get the full list.
      * @return the dynamic program list object, close it after use
      *         or {@code null} if program list is not supported by the tuner
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
         return null;
     }
@@ -316,26 +418,28 @@
      * @throws IllegalStateException if the switch is not supported at current
      *         configuration.
      * @return {@code true} if analog is forced, {@code false} otherwise.
-     * @deprecated Use {@link isConfigFlagSet(int)} instead.
+     * @deprecated Use {@link #isConfigFlagSet(int)} instead.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract boolean isAnalogForced();
 
     /**
      * Forces the analog playback for the supporting radio technology.
      *
-     * User may disable digital playback for FM HD Radio or hybrid FM/DAB with
+     * <p>User may disable digital playback for FM HD Radio or hybrid FM/DAB with
      * this option. This is purely user choice, ie. does not reflect digital-
      * analog handover managed from the HAL implementation side.
      *
-     * Some radio technologies may not support this, ie. DAB.
+     * <p>Some radio technologies may not support this, ie. DAB.
      *
      * @param isForced {@code true} to force analog, {@code false} for a default behaviour.
      * @throws IllegalStateException if the switch is not supported at current
      *         configuration.
-     * @deprecated Use {@link setConfigFlag(int, boolean)} instead.
+     * @deprecated Use {@link #setConfigFlag(int, boolean)}  instead.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract void setAnalogForced(boolean isForced);
 
     /**
@@ -344,6 +448,7 @@
      * @param flag Flag to check.
      * @return True, if the flag is supported.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public boolean isConfigFlagSupported(@RadioManager.ConfigFlag int flag) {
         return false;
     }
@@ -351,29 +456,34 @@
     /**
      * Fetches the current setting of a given config flag.
      *
-     * The success/failure result is consistent with isConfigFlagSupported.
+     * <p>The success/failure result is consistent with isConfigFlagSupported.
      *
      * @param flag Flag to fetch.
      * @return The current value of the flag.
      * @throws IllegalStateException if the flag is not applicable right now.
      * @throws UnsupportedOperationException if the flag is not supported at all.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) {
-        throw new UnsupportedOperationException();
+        throw new UnsupportedOperationException("isConfigFlagSet is not supported");
     }
 
     /**
      * Sets the config flag.
      *
-     * The success/failure result is consistent with isConfigFlagSupported.
+     * <p>The success/failure result is consistent with isConfigFlagSupported.
+     *
+     * <p>When this operation is called by users other than current user or system user,
+     * it is ignored silently.
      *
      * @param flag Flag to set.
      * @param value The new value of a given flag.
      * @throws IllegalStateException if the flag is not applicable right now.
      * @throws UnsupportedOperationException if the flag is not supported at all.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) {
-        throw new UnsupportedOperationException();
+        throw new UnsupportedOperationException("Setting config flag is not supported");
     }
 
     /**
@@ -381,30 +491,37 @@
      * The framework does not interpret the parameters, they are passed
      * in an opaque manner between a vendor application and HAL.
      *
-     * Framework does not make any assumptions on the keys or values, other than
+     * <p>Framework does not make any assumptions on the keys or values, other than
      * ones stated in VendorKeyValue documentation (a requirement of key
      * prefixes).
-     * See VendorKeyValue at hardware/interfaces/broadcastradio/2.0/types.hal.
+     * See VendorKeyValue at hardware/interfaces/broadcastradio/2.0/types.hal for
+     * HIDL 2.0 HAL or
+     * hardware/interfaces/broadcastradio/aidl/android/hardware/broadcastradio/VendorKeyValue.aidl
+     * for AIDL HAL.
      *
-     * For each pair in the result map, the key will be one of the keys
+     * <p>For each pair in the result map, the key will be one of the keys
      * contained in the input (possibly with wildcards expanded), and the value
      * will be a vendor-specific result status (such as "OK" or an error code).
      * The implementation may choose to return an empty map, or only return
      * a status for a subset of the provided inputs, at its discretion.
      *
-     * Application and HAL must not use keys with unknown prefix. In particular,
+     * <p>Application and HAL must not use keys with unknown prefix. In particular,
      * it must not place a key-value pair in results vector for unknown key from
      * parameters vector - instead, an unknown key should simply be ignored.
      * In other words, results vector may contain a subset of parameter keys
      * (however, the framework doesn't enforce a strict subset - the only
      * formal requirement is vendor domain prefix for keys).
      *
+     * <p>When this operation is called by users other than current user or system user,
+     * it is ignored silently.
+     *
      * @param parameters Vendor-specific key-value pairs.
      * @return Operation completion status for parameters being set.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public @NonNull Map<String, String>
             setParameters(@NonNull Map<String, String> parameters) {
-        throw new UnsupportedOperationException();
+        throw new UnsupportedOperationException("Setting parameters is not supported");
     }
 
     /**
@@ -412,23 +529,24 @@
      * The framework does not interpret the parameters, they are passed
      * in an opaque manner between a vendor application and HAL.
      *
-     * Framework does not cache set/get requests, so it's possible for
+     * <p>Framework does not cache set/get requests, so it's possible for
      * getParameter to return a different value than previous setParameter call.
      *
-     * The syntax and semantics of keys are up to the vendor (as long as prefix
+     * <p>The syntax and semantics of keys are up to the vendor (as long as prefix
      * rules are obeyed). For instance, vendors may include some form of
      * wildcard support. In such case, result vector may be of different size
      * than requested keys vector. However, wildcards are not recognized by
      * framework and they are passed as-is to the HAL implementation.
      *
-     * Unknown keys must be ignored and not placed into results vector.
+     * <p>Unknown keys must be ignored and not placed into results vector.
      *
      * @param keys Parameter keys to fetch.
      * @return Vendor-specific key-value pairs.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public @NonNull Map<String, String>
             getParameters(@NonNull List<String> keys) {
-        throw new UnsupportedOperationException();
+        throw new UnsupportedOperationException("Getting parameters is not supported");
     }
 
     /**
@@ -436,14 +554,16 @@
      * Only valid if a configuration has been applied.
      * @return {@code true} if the antenna is connected, {@code false} otherwise.
      *
-     * @deprecated Use {@link onAntennaState} callback instead
+     * @deprecated Use {@link Callback#onAntennaState(boolean)} callback instead
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract boolean isAntennaConnected();
 
     /**
      * Indicates if this client actually controls the tuner.
-     * Control is always granted after
+     *
+     * <p>Control is always granted after
      * {@link RadioManager#openTuner(int,
      * RadioManager.BandConfig, boolean, Callback, Handler)}
      * returns a non null tuner interface.
@@ -451,48 +571,102 @@
      * When this happens, {@link Callback#onControlChanged(boolean)} is received.
      * The client can either wait for control to be returned (which is indicated by the same
      * callback) or close and reopen the tuner interface.
+     *
      * @return {@code true} if this interface controls the tuner,
      * {@code false} otherwise or if a problem occured retrieving the state.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract boolean hasControl();
 
     /** Indicates a failure of radio IC or driver.
-     * The application must close and re open the tuner
-     * @deprecated See {@link onError} callback.
+     *
+     * <p>The application must close and re open the tuner
+     *
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final int ERROR_HARDWARE_FAILURE = 0;
     /** Indicates a failure of the radio service.
-     * The application must close and re open the tuner
-     * @deprecated See {@link onError} callback.
+     *
+     * <p>The application must close and re open the tuner
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final  int ERROR_SERVER_DIED = 1;
     /** A pending seek or tune operation was cancelled
-     * @deprecated See {@link onError} callback.
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final  int ERROR_CANCELLED = 2;
     /** A pending seek or tune operation timed out
-     * @deprecated See {@link onError} callback.
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final  int ERROR_SCAN_TIMEOUT = 3;
     /** The requested configuration could not be applied
-     * @deprecated See {@link onError} callback.
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final  int ERROR_CONFIG = 4;
     /** Background scan was interrupted due to hardware becoming temporarily unavailable.
-     * @deprecated See {@link onError} callback.
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5;
     /** Background scan failed due to other error, ie. HW failure.
-     * @deprecated See {@link onError} callback.
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final int ERROR_BACKGROUND_SCAN_FAILED = 6;
+    /** Result when a tune, seek, or step operation runs without error.
+     */
+    public static final int TUNER_RESULT_OK = 0;
+    /** Result when internal error occurs in HAL.
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_INTERNAL_ERROR = 1;
+    /** Result used when the input argument for the method is invalid.
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_INVALID_ARGUMENTS = 2;
+    /** Result when HAL is of invalid state.
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_INVALID_STATE = 3;
+    /** Result when the operation is not supported.
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_NOT_SUPPORTED = 4;
+    /** Result when a tune, seek, or step operation is timeout
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_TIMEOUT = 5;
+    /** Result when a tune, seek, or step operation is canceled before processed.
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_CANCELED = 6;
+    /** Result when a tune, seek, or step operation fails due to unknown error.
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_UNKNOWN_ERROR = 7;
+
+    /**
+     *  Tuning operation result types
+     *
+     * @hide
+     */
+    @IntDef(prefix = { "TUNER_RESULT_" }, value = {
+            TUNER_RESULT_OK,
+            TUNER_RESULT_INTERNAL_ERROR,
+            TUNER_RESULT_INVALID_ARGUMENTS,
+            TUNER_RESULT_INVALID_STATE,
+            TUNER_RESULT_NOT_SUPPORTED,
+            TUNER_RESULT_TIMEOUT,
+            TUNER_RESULT_CANCELED,
+            TUNER_RESULT_UNKNOWN_ERROR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TunerResultType{}
 
     /**
      * Callback provided by the client application when opening a {@link RadioTuner}
@@ -506,8 +680,9 @@
          * {@link #ERROR_CANCELLED}, {@link #ERROR_SCAN_TIMEOUT},
          * {@link #ERROR_CONFIG}
          *
-         * @deprecated Use {@link onTuneFailed} for tune, scan and step;
-         *             other use cases (configuration, background scan) are already deprecated.
+         * @deprecated Use {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} for
+         *             tune, scan and step; other use cases (configuration, background scan)
+         *             are already deprecated.
          */
         public void onError(int status) {}
 
@@ -518,7 +693,7 @@
          * @param selector ProgramSelector argument of tune that failed;
          *                 null for scan and step.
          */
-        public void onTuneFailed(int result, @Nullable ProgramSelector selector) {}
+        public void onTuneFailed(@TunerResultType int result, @Nullable ProgramSelector selector) {}
 
         /**
          * onConfigurationChanged() is called upon successful completion of
@@ -533,7 +708,7 @@
         /**
          * Called when program info (including metadata) for the current program has changed.
          *
-         * It happens either upon successful completion of {@link RadioTuner#step(int, boolean)},
+         * <p>It happens either upon successful completion of {@link RadioTuner#step(int, boolean)},
          * {@link RadioTuner#scan(int, boolean)}, {@link RadioTuner#tune(int, int)}; when
          * a switching to alternate frequency occurs; or when metadata is updated.
          */
@@ -589,16 +764,29 @@
         /**
          * Called when available program list changed.
          *
-         * Use {@link RadioTuner#getProgramList(String)} to get an actual list.
+         * Use {@link RadioTuner#getProgramList(Map)} to get an actual list.
          */
         public void onProgramListChanged() {}
 
         /**
+         * Called when config flags are updated asynchronously due to internal events
+         * in broadcast radio HAL.
+         *
+         * {@link RadioTuner#setConfigFlag(int, boolean)} must not trigger this
+         * callback.
+         *
+         * @param flag Config flag updated
+         * @param value Value of the updated config flag
+         */
+        public void onConfigFlagUpdated(@RadioManager.ConfigFlag int flag, boolean value) {}
+
+        /**
          * Generic callback for passing updates to vendor-specific parameter values.
-         * The framework does not interpret the parameters, they are passed
+         *
+         * <p>The framework does not interpret the parameters, they are passed
          * in an opaque manner between a vendor application and HAL.
          *
-         * It's up to the HAL implementation if and how to implement this callback,
+         * <p>It's up to the HAL implementation if and how to implement this callback,
          * as long as it obeys the prefix rule. In particular, only selected keys
          * may be notified this way. However, setParameters must not trigger
          * this callback, while an internal event can change parameters
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index 4a18333..bdbca91 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -154,7 +154,7 @@
     @Override
     public int scan(int direction, boolean skipSubChannel) {
         try {
-            mTuner.scan(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
+            mTuner.seek(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
                     skipSubChannel);
         } catch (IllegalStateException e) {
             Log.e(TAG, "Can't scan", e);
@@ -167,6 +167,21 @@
     }
 
     @Override
+    public int seek(int direction, boolean skipSubChannel) {
+        try {
+            mTuner.seek(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
+                    skipSubChannel);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "Can't seek", e);
+            return RadioManager.STATUS_INVALID_OPERATION;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Service died", e);
+            return RadioManager.STATUS_DEAD_OBJECT;
+        }
+        return RadioManager.STATUS_OK;
+    }
+
+    @Override
     public int tune(int channel, int subChannel) {
         try {
             int band;
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index b9782a8..22f5902 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -246,6 +246,11 @@
     }
 
     @Override
+    public void onConfigFlagUpdated(@RadioManager.ConfigFlag int flag, boolean value) {
+        mHandler.post(() -> mCallback.onConfigFlagUpdated(flag, value));
+    }
+
+    @Override
     public void onParametersUpdated(Map<String, String> parameters) {
         mHandler.post(() -> mCallback.onParametersUpdated(parameters));
     }
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index fd3fe37..dcf0026 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -15,16 +15,23 @@
  */
 package android.net.vcn;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
+import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
+import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
@@ -32,6 +39,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.Objects;
 import java.util.Set;
 
@@ -46,22 +54,36 @@
 public final class VcnConfig implements Parcelable {
     @NonNull private static final String TAG = VcnConfig.class.getSimpleName();
 
+    private static final Set<Integer> ALLOWED_TRANSPORTS = new ArraySet<>();
+
+    static {
+        ALLOWED_TRANSPORTS.add(TRANSPORT_WIFI);
+        ALLOWED_TRANSPORTS.add(TRANSPORT_CELLULAR);
+    }
+
     private static final String PACKAGE_NAME_KEY = "mPackageName";
     @NonNull private final String mPackageName;
 
     private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
     @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
 
+    private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT =
+            Collections.singleton(TRANSPORT_WIFI);
+    private static final String RESTRICTED_TRANSPORTS_KEY = "mRestrictedTransports";
+    @NonNull private final Set<Integer> mRestrictedTransports;
+
     private static final String IS_TEST_MODE_PROFILE_KEY = "mIsTestModeProfile";
     private final boolean mIsTestModeProfile;
 
     private VcnConfig(
             @NonNull String packageName,
             @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs,
+            @NonNull Set<Integer> restrictedTransports,
             boolean isTestModeProfile) {
         mPackageName = packageName;
         mGatewayConnectionConfigs =
                 Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
+        mRestrictedTransports = Collections.unmodifiableSet(new ArraySet<>(restrictedTransports));
         mIsTestModeProfile = isTestModeProfile;
 
         validate();
@@ -82,6 +104,20 @@
                 new ArraySet<>(
                         PersistableBundleUtils.toList(
                                 gatewayConnectionConfigsBundle, VcnGatewayConnectionConfig::new));
+
+        final PersistableBundle restrictedTransportsBundle =
+                in.getPersistableBundle(RESTRICTED_TRANSPORTS_KEY);
+        if (restrictedTransportsBundle == null) {
+            // RESTRICTED_TRANSPORTS_KEY was added in U and does not exist in VcnConfigs created in
+            // older platforms
+            mRestrictedTransports = RESTRICTED_TRANSPORTS_DEFAULT;
+        } else {
+            mRestrictedTransports =
+                    new ArraySet<Integer>(
+                            PersistableBundleUtils.toList(
+                                    restrictedTransportsBundle, INTEGER_DESERIALIZER));
+        }
+
         mIsTestModeProfile = in.getBoolean(IS_TEST_MODE_PROFILE_KEY);
 
         validate();
@@ -91,6 +127,19 @@
         Objects.requireNonNull(mPackageName, "packageName was null");
         Preconditions.checkCollectionNotEmpty(
                 mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty");
+
+        final Iterator<Integer> iterator = mRestrictedTransports.iterator();
+        while (iterator.hasNext()) {
+            final int transport = iterator.next();
+            if (!ALLOWED_TRANSPORTS.contains(transport)) {
+                iterator.remove();
+                Log.w(
+                        TAG,
+                        "Found invalid transport "
+                                + transport
+                                + " which might be from a new version of VcnConfig");
+            }
+        }
     }
 
     /**
@@ -110,6 +159,16 @@
     }
 
     /**
+     * Retrieve the transports that will be restricted by the VCN.
+     *
+     * @see Builder#setRestrictedUnderlyingNetworkTransports(Set)
+     */
+    @NonNull
+    public Set<Integer> getRestrictedUnderlyingNetworkTransports() {
+        return Collections.unmodifiableSet(mRestrictedTransports);
+    }
+
+    /**
      * Returns whether or not this VcnConfig is restricted to test networks.
      *
      * @hide
@@ -134,6 +193,12 @@
                         new ArrayList<>(mGatewayConnectionConfigs),
                         VcnGatewayConnectionConfig::toPersistableBundle);
         result.putPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY, gatewayConnectionConfigsBundle);
+
+        final PersistableBundle restrictedTransportsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mRestrictedTransports), INTEGER_SERIALIZER);
+        result.putPersistableBundle(RESTRICTED_TRANSPORTS_KEY, restrictedTransportsBundle);
+
         result.putBoolean(IS_TEST_MODE_PROFILE_KEY, mIsTestModeProfile);
 
         return result;
@@ -141,7 +206,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPackageName, mGatewayConnectionConfigs, mIsTestModeProfile);
+        return Objects.hash(
+                mPackageName, mGatewayConnectionConfigs, mRestrictedTransports, mIsTestModeProfile);
     }
 
     @Override
@@ -153,6 +219,7 @@
         final VcnConfig rhs = (VcnConfig) other;
         return mPackageName.equals(rhs.mPackageName)
                 && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs)
+                && mRestrictedTransports.equals(rhs.mRestrictedTransports)
                 && mIsTestModeProfile == rhs.mIsTestModeProfile;
     }
 
@@ -189,12 +256,15 @@
         @NonNull
         private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();
 
+        @NonNull private final Set<Integer> mRestrictedTransports = new ArraySet<>();
+
         private boolean mIsTestModeProfile = false;
 
         public Builder(@NonNull Context context) {
             Objects.requireNonNull(context, "context was null");
 
             mPackageName = context.getOpPackageName();
+            mRestrictedTransports.addAll(RESTRICTED_TRANSPORTS_DEFAULT);
         }
 
         /**
@@ -225,6 +295,36 @@
             return this;
         }
 
+        private void validateRestrictedTransportsOrThrow(Set<Integer> restrictedTransports) {
+            Objects.requireNonNull(restrictedTransports, "transports was null");
+
+            for (int transport : restrictedTransports) {
+                if (!ALLOWED_TRANSPORTS.contains(transport)) {
+                    throw new IllegalArgumentException("Invalid transport " + transport);
+                }
+            }
+        }
+
+        /**
+         * Sets transports that will be restricted by the VCN.
+         *
+         * @param transports transports that will be restricted by VCN. Networks that include any
+         *     of the transports will be marked as restricted. Only {@link
+         *     NetworkCapabilities#TRANSPORT_WIFI} and {@link
+         *     NetworkCapabilities#TRANSPORT_CELLULAR} are allowed. {@link
+         *     NetworkCapabilities#TRANSPORT_WIFI} is marked restricted by default.
+         * @return this {@link Builder} instance, for chaining
+         * @throws IllegalArgumentException if the input contains unsupported transport types.
+         */
+        @NonNull
+        public Builder setRestrictedUnderlyingNetworkTransports(@NonNull Set<Integer> transports) {
+            validateRestrictedTransportsOrThrow(transports);
+
+            mRestrictedTransports.clear();
+            mRestrictedTransports.addAll(transports);
+            return this;
+        }
+
         /**
          * Restricts this VcnConfig to matching with test networks (only).
          *
@@ -248,7 +348,11 @@
          */
         @NonNull
         public VcnConfig build() {
-            return new VcnConfig(mPackageName, mGatewayConnectionConfigs, mIsTestModeProfile);
+            return new VcnConfig(
+                    mPackageName,
+                    mGatewayConnectionConfigs,
+                    mRestrictedTransports,
+                    mIsTestModeProfile);
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 2339656..b8850f4 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -42,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -130,6 +131,30 @@
             })
     public @interface VcnSupportedCapability {}
 
+    /**
+     * Perform mobility update to attempt recovery from suspected data stalls.
+     *
+     * <p>If set, the gatway connection will monitor the data stall detection of the VCN network.
+     * When there is a suspected data stall, the gateway connection will attempt recovery by
+     * performing a mobility update on the underlying IKE session.
+     */
+    public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"VCN_GATEWAY_OPTION_"},
+            value = {
+                VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY,
+            })
+    public @interface VcnGatewayOption {}
+
+    private static final Set<Integer> ALLOWED_GATEWAY_OPTIONS = new ArraySet<>();
+
+    static {
+        ALLOWED_GATEWAY_OPTIONS.add(VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY);
+    }
+
     private static final int DEFAULT_MAX_MTU = 1500;
 
     /**
@@ -201,6 +226,9 @@
     private static final String RETRY_INTERVAL_MS_KEY = "mRetryIntervalsMs";
     @NonNull private final long[] mRetryIntervalsMs;
 
+    private static final String GATEWAY_OPTIONS_KEY = "mGatewayOptions";
+    @NonNull private final Set<Integer> mGatewayOptions;
+
     /** Builds a VcnGatewayConnectionConfig with the specified parameters. */
     private VcnGatewayConnectionConfig(
             @NonNull String gatewayConnectionName,
@@ -208,12 +236,14 @@
             @NonNull Set<Integer> exposedCapabilities,
             @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             @NonNull long[] retryIntervalsMs,
-            @IntRange(from = MIN_MTU_V6) int maxMtu) {
+            @IntRange(from = MIN_MTU_V6) int maxMtu,
+            @NonNull Set<Integer> gatewayOptions) {
         mGatewayConnectionName = gatewayConnectionName;
         mTunnelConnectionParams = tunnelConnectionParams;
         mExposedCapabilities = new TreeSet(exposedCapabilities);
         mRetryIntervalsMs = retryIntervalsMs;
         mMaxMtu = maxMtu;
+        mGatewayOptions = Collections.unmodifiableSet(new HashSet(gatewayOptions));
 
         mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates);
         if (mUnderlyingNetworkTemplates.isEmpty()) {
@@ -256,6 +286,20 @@
                             VcnUnderlyingNetworkTemplate::fromPersistableBundle);
         }
 
+        final PersistableBundle gatewayOptionsBundle = in.getPersistableBundle(GATEWAY_OPTIONS_KEY);
+
+        if (gatewayOptionsBundle == null) {
+            // GATEWAY_OPTIONS_KEY was added in Android U. Thus VcnGatewayConnectionConfig created
+            // on old platforms will not have this data and will be assigned with the default value
+            mGatewayOptions = Collections.emptySet();
+        } else {
+            mGatewayOptions =
+                    new HashSet<>(
+                            PersistableBundleUtils.toList(
+                                    gatewayOptionsBundle,
+                                    PersistableBundleUtils.INTEGER_DESERIALIZER));
+        }
+
         mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
         mMaxMtu = in.getInt(MAX_MTU_KEY);
 
@@ -279,6 +323,10 @@
 
         Preconditions.checkArgument(
                 mMaxMtu >= MIN_MTU_V6, "maxMtu must be at least IPv6 min MTU (1280)");
+
+        for (int option : mGatewayOptions) {
+            validateGatewayOption(option);
+        }
     }
 
     private static void checkValidCapability(int capability) {
@@ -315,6 +363,12 @@
         }
     }
 
+    private static void validateGatewayOption(int option) {
+        if (!ALLOWED_GATEWAY_OPTIONS.contains(option)) {
+            throw new IllegalArgumentException("Invalid vcn gateway option: " + option);
+        }
+    }
+
     /**
      * Returns the configured Gateway Connection name.
      *
@@ -399,6 +453,19 @@
     }
 
     /**
+     * Checks if the given VCN gateway option is enabled.
+     *
+     * @param option the option to check.
+     * @throws IllegalArgumentException if the provided option is invalid.
+     * @see Builder#addGatewayOption(int)
+     * @see Builder#removeGatewayOption(int)
+     */
+    public boolean hasGatewayOption(@VcnGatewayOption int option) {
+        validateGatewayOption(option);
+        return mGatewayOptions.contains(option);
+    }
+
+    /**
      * Converts this config to a PersistableBundle.
      *
      * @hide
@@ -418,11 +485,16 @@
                 PersistableBundleUtils.fromList(
                         mUnderlyingNetworkTemplates,
                         VcnUnderlyingNetworkTemplate::toPersistableBundle);
+        final PersistableBundle gatewayOptionsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mGatewayOptions),
+                        PersistableBundleUtils.INTEGER_SERIALIZER);
 
         result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName);
         result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle);
         result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle);
         result.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, networkTemplatesBundle);
+        result.putPersistableBundle(GATEWAY_OPTIONS_KEY, gatewayOptionsBundle);
         result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
         result.putInt(MAX_MTU_KEY, mMaxMtu);
 
@@ -437,7 +509,8 @@
                 mExposedCapabilities,
                 mUnderlyingNetworkTemplates,
                 Arrays.hashCode(mRetryIntervalsMs),
-                mMaxMtu);
+                mMaxMtu,
+                mGatewayOptions);
     }
 
     @Override
@@ -452,7 +525,8 @@
                 && mExposedCapabilities.equals(rhs.mExposedCapabilities)
                 && mUnderlyingNetworkTemplates.equals(rhs.mUnderlyingNetworkTemplates)
                 && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs)
-                && mMaxMtu == rhs.mMaxMtu;
+                && mMaxMtu == rhs.mMaxMtu
+                && mGatewayOptions.equals(rhs.mGatewayOptions);
     }
 
     /**
@@ -470,6 +544,8 @@
         @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
         private int mMaxMtu = DEFAULT_MAX_MTU;
 
+        @NonNull private final Set<Integer> mGatewayOptions = new ArraySet<>();
+
         // TODO: (b/175829816) Consider VCN-exposed capabilities that may be transport dependent.
         //       Consider the case where the VCN might only expose MMS on WiFi, but defer to MMS
         //       when on Cell.
@@ -628,6 +704,34 @@
         }
 
         /**
+         * Enables the specified VCN gateway option.
+         *
+         * @param option the option to be enabled
+         * @return this {@link Builder} instance, for chaining
+         * @throws IllegalArgumentException if the provided option is invalid
+         */
+        @NonNull
+        public Builder addGatewayOption(@VcnGatewayOption int option) {
+            validateGatewayOption(option);
+            mGatewayOptions.add(option);
+            return this;
+        }
+
+        /**
+         * Resets (disables) the specified VCN gateway option.
+         *
+         * @param option the option to be disabled
+         * @return this {@link Builder} instance, for chaining
+         * @throws IllegalArgumentException if the provided option is invalid
+         */
+        @NonNull
+        public Builder removeGatewayOption(@VcnGatewayOption int option) {
+            validateGatewayOption(option);
+            mGatewayOptions.remove(option);
+            return this;
+        }
+
+        /**
          * Builds and validates the VcnGatewayConnectionConfig.
          *
          * @return an immutable VcnGatewayConnectionConfig instance
@@ -640,7 +744,8 @@
                     mExposedCapabilities,
                     mUnderlyingNetworkTemplates,
                     mRetryIntervalsMs,
-                    mMaxMtu);
+                    mMaxMtu,
+                    mGatewayOptions);
         }
     }
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index f545c30..0c7f529 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -30,6 +30,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.nfc.tech.MifareClassic;
@@ -525,6 +526,66 @@
     }
 
     /**
+     * Helper to check if this device has FEATURE_NFC_BEAM, but without using
+     * a context.
+     * Equivalent to
+     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_BEAM)
+     */
+    private static boolean hasBeamFeature() {
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no Android Beam feature");
+            return false;
+        }
+        try {
+            return pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no Android Beam feature", e);
+            return false;
+        }
+    }
+
+    /**
+     * Helper to check if this device has FEATURE_NFC, but without using
+     * a context.
+     * Equivalent to
+     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)
+     */
+    private static boolean hasNfcFeature() {
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
+            return false;
+        }
+        try {
+            return pm.hasSystemFeature(PackageManager.FEATURE_NFC, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
+            return false;
+        }
+    }
+
+    /**
+     * Helper to check if this device is NFC HCE capable, by checking for
+     * FEATURE_NFC_HOST_CARD_EMULATION and/or FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+     * but without using a context.
+     */
+    private static boolean hasNfcHceFeature() {
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
+            return false;
+        }
+        try {
+            return pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)
+                || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
+            return false;
+        }
+    }
+
+    /**
      * Return list of Secure Elements which support off host card emulation.
      *
      * @return List<String> containing secure elements on the device which supports
@@ -533,21 +594,23 @@
      * @hide
      */
     public @NonNull List<String> getSupportedOffHostSecureElements() {
-        if (mContext == null) {
-            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
-                    + " getSupportedOffHostSecureElements APIs");
-        }
         List<String> offHostSE = new ArrayList<String>();
-        PackageManager pm = mContext.getPackageManager();
+        IPackageManager pm = ActivityThread.getPackageManager();
         if (pm == null) {
             Log.e(TAG, "Cannot get package manager, assuming no off-host CE feature");
             return offHostSE;
         }
-        if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)) {
-            offHostSE.add("SIM");
-        }
-        if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE)) {
-            offHostSE.add("eSE");
+        try {
+            if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC, 0)) {
+                offHostSE.add("SIM");
+            }
+            if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE, 0)) {
+                offHostSE.add("eSE");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no off-host CE feature", e);
+            offHostSE.clear();
+            return offHostSE;
         }
         return offHostSE;
     }
@@ -559,19 +622,10 @@
      */
     @UnsupportedAppUsage
     public static synchronized NfcAdapter getNfcAdapter(Context context) {
-        if (context == null) {
-            if (sNullContextNfcAdapter == null) {
-                sNullContextNfcAdapter = new NfcAdapter(null);
-            }
-            return sNullContextNfcAdapter;
-        }
         if (!sIsInitialized) {
-            PackageManager pm = context.getPackageManager();
-            sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
-            sHasBeamFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM);
-            boolean hasHceFeature =
-                    pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
-                    || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF);
+            sHasNfcFeature = hasNfcFeature();
+            sHasBeamFeature = hasBeamFeature();
+            boolean hasHceFeature = hasNfcHceFeature();
             /* is this device meant to have NFC */
             if (!sHasNfcFeature && !hasHceFeature) {
                 Log.v(TAG, "this device does not have NFC support");
@@ -607,6 +661,12 @@
 
             sIsInitialized = true;
         }
+        if (context == null) {
+            if (sNullContextNfcAdapter == null) {
+                sNullContextNfcAdapter = new NfcAdapter(null);
+            }
+            return sNullContextNfcAdapter;
+        }
         NfcAdapter adapter = sNfcAdapters.get(context);
         if (adapter == null) {
             adapter = new NfcAdapter(context);
@@ -617,12 +677,8 @@
 
     /** get handle to NFC service interface */
     private static INfcAdapter getServiceInterface() {
-        if (!sHasNfcFeature) {
-            /* NFC is not supported */
-            return null;
-        }
         /* get a handle to NFC service */
-        IBinder b = ServiceManager.waitForService("nfc");
+        IBinder b = ServiceManager.getService("nfc");
         if (b == null) {
             return null;
         }
@@ -652,15 +708,6 @@
                     "context not associated with any application (using a mock context?)");
         }
 
-        synchronized (NfcAdapter.class) {
-            if (!sIsInitialized) {
-                PackageManager pm = context.getPackageManager();
-                sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
-            }
-            if (!sHasNfcFeature) {
-                return null;
-            }
-        }
         if (getServiceInterface() == null) {
             // NFC is not available
             return null;
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 6a42091..0b56d19 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -22,9 +22,11 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.app.Activity;
+import android.app.ActivityThread;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.nfc.INfcCardEmulation;
 import android.nfc.NfcAdapter;
@@ -156,13 +158,18 @@
             throw new UnsupportedOperationException();
         }
         if (!sIsInitialized) {
-            PackageManager pm = context.getPackageManager();
+            IPackageManager pm = ActivityThread.getPackageManager();
             if (pm == null) {
                 Log.e(TAG, "Cannot get PackageManager");
                 throw new UnsupportedOperationException();
             }
-            if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
-                Log.e(TAG, "This device does not support card emulation");
+            try {
+                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)) {
+                    Log.e(TAG, "This device does not support card emulation");
+                    throw new UnsupportedOperationException();
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "PackageManager query failed.");
                 throw new UnsupportedOperationException();
             }
             sIsInitialized = true;
diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
index 48bbf5b6..3c92455 100644
--- a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
+++ b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
@@ -17,8 +17,10 @@
 package android.nfc.cardemulation;
 
 import android.app.Activity;
+import android.app.ActivityThread;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.nfc.INfcFCardEmulation;
 import android.nfc.NfcAdapter;
@@ -68,13 +70,18 @@
             throw new UnsupportedOperationException();
         }
         if (!sIsInitialized) {
-            PackageManager pm = context.getPackageManager();
+            IPackageManager pm = ActivityThread.getPackageManager();
             if (pm == null) {
                 Log.e(TAG, "Cannot get PackageManager");
                 throw new UnsupportedOperationException();
             }
-            if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)) {
-                Log.e(TAG, "This device does not support NFC-F card emulation");
+            try {
+                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0)) {
+                    Log.e(TAG, "This device does not support NFC-F card emulation");
+                    throw new UnsupportedOperationException();
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "PackageManager query failed.");
                 throw new UnsupportedOperationException();
             }
             sIsInitialized = true;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index e915d98..2a4c861 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -44,6 +44,7 @@
 import android.util.MutableBoolean;
 import android.util.Pair;
 import android.util.Printer;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseDoubleArray;
 import android.util.SparseIntArray;
@@ -52,6 +53,7 @@
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BatteryStatsHistoryIterator;
 
 import com.google.android.collect.Lists;
 
@@ -2398,9 +2400,6 @@
 
     public abstract int getHistoryUsedSize();
 
-    @UnsupportedAppUsage
-    public abstract boolean startIteratingHistoryLocked();
-
     public abstract int getHistoryStringPoolSize();
 
     public abstract int getHistoryStringPoolBytes();
@@ -2409,10 +2408,11 @@
 
     public abstract int getHistoryTagPoolUid(int index);
 
-    @UnsupportedAppUsage
-    public abstract boolean getNextHistoryLocked(HistoryItem out);
-
-    public abstract void finishIteratingHistoryLocked();
+    /**
+     * Returns a BatteryStatsHistoryIterator. Battery history will remain immutable until the
+     * {@link BatteryStatsHistoryIterator#close()} method is invoked.
+     */
+    public abstract BatteryStatsHistoryIterator iterateBatteryStatsHistory();
 
     /**
      * Returns the number of times the device has been started.
@@ -7451,80 +7451,88 @@
 
     private void dumpHistoryLocked(PrintWriter pw, int flags, long histStart, boolean checkin) {
         final HistoryPrinter hprinter = new HistoryPrinter();
-        final HistoryItem rec = new HistoryItem();
         long lastTime = -1;
         long baseTime = -1;
         boolean printed = false;
         HistoryEventTracker tracker = null;
-        while (getNextHistoryLocked(rec)) {
-            lastTime = rec.time;
-            if (baseTime < 0) {
-                baseTime = lastTime;
-            }
-            if (rec.time >= histStart) {
-                if (histStart >= 0 && !printed) {
-                    if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
-                            || rec.cmd == HistoryItem.CMD_RESET
-                            || rec.cmd == HistoryItem.CMD_START
-                            || rec.cmd == HistoryItem.CMD_SHUTDOWN) {
-                        printed = true;
-                        hprinter.printNextItem(pw, rec, baseTime, checkin,
-                                (flags&DUMP_VERBOSE) != 0);
-                        rec.cmd = HistoryItem.CMD_UPDATE;
-                    } else if (rec.currentTime != 0) {
-                        printed = true;
-                        byte cmd = rec.cmd;
-                        rec.cmd = HistoryItem.CMD_CURRENT_TIME;
-                        hprinter.printNextItem(pw, rec, baseTime, checkin,
-                                (flags&DUMP_VERBOSE) != 0);
-                        rec.cmd = cmd;
+        try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
+            HistoryItem rec;
+            while ((rec = iterator.next()) != null) {
+                try {
+                    lastTime = rec.time;
+                    if (baseTime < 0) {
+                        baseTime = lastTime;
                     }
-                    if (tracker != null) {
-                        if (rec.cmd != HistoryItem.CMD_UPDATE) {
-                            hprinter.printNextItem(pw, rec, baseTime, checkin,
-                                    (flags&DUMP_VERBOSE) != 0);
-                            rec.cmd = HistoryItem.CMD_UPDATE;
-                        }
-                        int oldEventCode = rec.eventCode;
-                        HistoryTag oldEventTag = rec.eventTag;
-                        rec.eventTag = new HistoryTag();
-                        for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
-                            HashMap<String, SparseIntArray> active
-                                    = tracker.getStateForEvent(i);
-                            if (active == null) {
-                                continue;
+                    if (rec.time >= histStart) {
+                        if (histStart >= 0 && !printed) {
+                            if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
+                                    || rec.cmd == HistoryItem.CMD_RESET
+                                    || rec.cmd == HistoryItem.CMD_START
+                                    || rec.cmd == HistoryItem.CMD_SHUTDOWN) {
+                                printed = true;
+                                hprinter.printNextItem(pw, rec, baseTime, checkin,
+                                        (flags & DUMP_VERBOSE) != 0);
+                                rec.cmd = HistoryItem.CMD_UPDATE;
+                            } else if (rec.currentTime != 0) {
+                                printed = true;
+                                byte cmd = rec.cmd;
+                                rec.cmd = HistoryItem.CMD_CURRENT_TIME;
+                                hprinter.printNextItem(pw, rec, baseTime, checkin,
+                                        (flags & DUMP_VERBOSE) != 0);
+                                rec.cmd = cmd;
                             }
-                            for (HashMap.Entry<String, SparseIntArray> ent
-                                    : active.entrySet()) {
-                                SparseIntArray uids = ent.getValue();
-                                for (int j=0; j<uids.size(); j++) {
-                                    rec.eventCode = i;
-                                    rec.eventTag.string = ent.getKey();
-                                    rec.eventTag.uid = uids.keyAt(j);
-                                    rec.eventTag.poolIdx = uids.valueAt(j);
+                            if (tracker != null) {
+                                if (rec.cmd != HistoryItem.CMD_UPDATE) {
                                     hprinter.printNextItem(pw, rec, baseTime, checkin,
-                                            (flags&DUMP_VERBOSE) != 0);
-                                    rec.wakeReasonTag = null;
-                                    rec.wakelockTag = null;
+                                            (flags & DUMP_VERBOSE) != 0);
+                                    rec.cmd = HistoryItem.CMD_UPDATE;
                                 }
+                                int oldEventCode = rec.eventCode;
+                                HistoryTag oldEventTag = rec.eventTag;
+                                rec.eventTag = new HistoryTag();
+                                for (int i = 0; i < HistoryItem.EVENT_COUNT; i++) {
+                                    Map<String, SparseIntArray> active =
+                                            tracker.getStateForEvent(i);
+                                    if (active == null) {
+                                        continue;
+                                    }
+                                    for (Map.Entry<String, SparseIntArray> ent :
+                                            active.entrySet()) {
+                                        SparseIntArray uids = ent.getValue();
+                                        for (int j = 0; j < uids.size(); j++) {
+                                            rec.eventCode = i;
+                                            rec.eventTag.string = ent.getKey();
+                                            rec.eventTag.uid = uids.keyAt(j);
+                                            rec.eventTag.poolIdx = uids.valueAt(j);
+                                            hprinter.printNextItem(pw, rec, baseTime, checkin,
+                                                    (flags & DUMP_VERBOSE) != 0);
+                                            rec.wakeReasonTag = null;
+                                            rec.wakelockTag = null;
+                                        }
+                                    }
+                                }
+                                rec.eventCode = oldEventCode;
+                                rec.eventTag = oldEventTag;
+                                tracker = null;
                             }
                         }
-                        rec.eventCode = oldEventCode;
-                        rec.eventTag = oldEventTag;
-                        tracker = null;
+                        hprinter.printNextItem(pw, rec, baseTime, checkin,
+                                (flags & DUMP_VERBOSE) != 0);
+                    } else if (false/* && rec.eventCode != HistoryItem.EVENT_NONE */) {
+                        // This is an attempt to aggregate the previous state and generate
+                        // fake events to reflect that state at the point where we start
+                        // printing real events.  It doesn't really work right, so is turned off.
+                        if (tracker == null) {
+                            tracker = new HistoryEventTracker();
+                        }
+                        tracker.updateState(rec.eventCode, rec.eventTag.string,
+                                rec.eventTag.uid, rec.eventTag.poolIdx);
                     }
+                } catch (Throwable t) {
+                    t.printStackTrace(pw);
+                    Slog.wtf(TAG, "Corrupted battery history", t);
+                    break;
                 }
-                hprinter.printNextItem(pw, rec, baseTime, checkin,
-                        (flags&DUMP_VERBOSE) != 0);
-            } else if (false && rec.eventCode != HistoryItem.EVENT_NONE) {
-                // This is an attempt to aggregate the previous state and generate
-                // fake events to reflect that state at the point where we start
-                // printing real events.  It doesn't really work right, so is turned off.
-                if (tracker == null) {
-                    tracker = new HistoryEventTracker();
-                }
-                tracker.updateState(rec.eventCode, rec.eventTag.string,
-                        rec.eventTag.uid, rec.eventTag.poolIdx);
             }
         }
         if (histStart >= 0) {
@@ -7595,25 +7603,19 @@
         if ((flags&DUMP_HISTORY_ONLY) != 0 || !filtering) {
             final long historyTotalSize = getHistoryTotalSize();
             final long historyUsedSize = getHistoryUsedSize();
-            if (startIteratingHistoryLocked()) {
-                try {
-                    pw.print("Battery History (");
-                    pw.print((100*historyUsedSize)/historyTotalSize);
-                    pw.print("% used, ");
-                    printSizeValue(pw, historyUsedSize);
-                    pw.print(" used of ");
-                    printSizeValue(pw, historyTotalSize);
-                    pw.print(", ");
-                    pw.print(getHistoryStringPoolSize());
-                    pw.print(" strings using ");
-                    printSizeValue(pw, getHistoryStringPoolBytes());
-                    pw.println("):");
-                    dumpHistoryLocked(pw, flags, histStart, false);
-                    pw.println();
-                } finally {
-                    finishIteratingHistoryLocked();
-                }
-            }
+            pw.print("Battery History (");
+            pw.print((100 * historyUsedSize) / historyTotalSize);
+            pw.print("% used, ");
+            printSizeValue(pw, historyUsedSize);
+            pw.print(" used of ");
+            printSizeValue(pw, historyTotalSize);
+            pw.print(", ");
+            pw.print(getHistoryStringPoolSize());
+            pw.print(" strings using ");
+            printSizeValue(pw, getHistoryStringPoolBytes());
+            pw.println("):");
+            dumpHistoryLocked(pw, flags, histStart, false);
+            pw.println();
         }
 
         if (filtering && (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) {
@@ -7770,28 +7772,24 @@
                 getEndPlatformVersion());
 
         if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
-            if (startIteratingHistoryLocked()) {
-                try {
-                    for (int i=0; i<getHistoryStringPoolSize(); i++) {
-                        pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
-                        pw.print(HISTORY_STRING_POOL); pw.print(',');
-                        pw.print(i);
-                        pw.print(",");
-                        pw.print(getHistoryTagPoolUid(i));
-                        pw.print(",\"");
-                        String str = getHistoryTagPoolString(i);
-                        if (str != null) {
-                            str = str.replace("\\", "\\\\");
-                            str = str.replace("\"", "\\\"");
-                            pw.print(str);
-                        }
-                        pw.print("\"");
-                        pw.println();
-                    }
-                    dumpHistoryLocked(pw, flags, histStart, true);
-                } finally {
-                    finishIteratingHistoryLocked();
+            for (int i = 0; i < getHistoryStringPoolSize(); i++) {
+                pw.print(BATTERY_STATS_CHECKIN_VERSION);
+                pw.print(',');
+                pw.print(HISTORY_STRING_POOL);
+                pw.print(',');
+                pw.print(i);
+                pw.print(",");
+                pw.print(getHistoryTagPoolUid(i));
+                pw.print(",\"");
+                String str = getHistoryTagPoolString(i);
+                if (str != null) {
+                    str = str.replace("\\", "\\\\");
+                    str = str.replace("\"", "\\\"");
+                    pw.print(str);
                 }
+                pw.print("\"");
+                pw.println();
+                dumpHistoryLocked(pw, flags, histStart, true);
             }
         }
 
@@ -8331,17 +8329,12 @@
     }
 
     private void dumpProtoHistoryLocked(ProtoOutputStream proto, int flags, long histStart) {
-        if (!startIteratingHistoryLocked()) {
-            return;
-        }
-
         proto.write(BatteryStatsServiceDumpHistoryProto.REPORT_VERSION, CHECKIN_VERSION);
         proto.write(BatteryStatsServiceDumpHistoryProto.PARCEL_VERSION, getParcelVersion());
         proto.write(BatteryStatsServiceDumpHistoryProto.START_PLATFORM_VERSION,
                 getStartPlatformVersion());
         proto.write(BatteryStatsServiceDumpHistoryProto.END_PLATFORM_VERSION,
                 getEndPlatformVersion());
-        try {
             long token;
             // History string pool (HISTORY_STRING_POOL)
             for (int i = 0; i < getHistoryStringPoolSize(); ++i) {
@@ -8353,14 +8346,15 @@
                 proto.end(token);
             }
 
-            // History data (HISTORY_DATA)
-            final HistoryPrinter hprinter = new HistoryPrinter();
-            final HistoryItem rec = new HistoryItem();
-            long lastTime = -1;
-            long baseTime = -1;
-            boolean printed = false;
-            HistoryEventTracker tracker = null;
-            while (getNextHistoryLocked(rec)) {
+        // History data (HISTORY_DATA)
+        final HistoryPrinter hprinter = new HistoryPrinter();
+        long lastTime = -1;
+        long baseTime = -1;
+        boolean printed = false;
+        HistoryEventTracker tracker = null;
+        try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
+            HistoryItem rec;
+            while ((rec = iterator.next()) != null) {
                 lastTime = rec.time;
                 if (baseTime < 0) {
                     baseTime = lastTime;
@@ -8427,8 +8421,6 @@
                 proto.write(BatteryStatsServiceDumpHistoryProto.CSV_LINES,
                         "NEXT: " + (lastTime + 1));
             }
-        } finally {
-            finishIteratingHistoryLocked();
         }
     }
 
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 399f11b..a31c2e6 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1956,7 +1956,21 @@
     /** @hide */
     public static final int MEMINFO_UNEVICTABLE = 18;
     /** @hide */
-    public static final int MEMINFO_COUNT = 19;
+    public static final int MEMINFO_AVAILABLE = 19;
+    /** @hide */
+    public static final int MEMINFO_ACTIVE_ANON = 20;
+    /** @hide */
+    public static final int MEMINFO_INACTIVE_ANON = 21;
+    /** @hide */
+    public static final int MEMINFO_ACTIVE_FILE = 22;
+    /** @hide */
+    public static final int MEMINFO_INACTIVE_FILE = 23;
+    /** @hide */
+    public static final int MEMINFO_CMA_TOTAL = 24;
+    /** @hide */
+    public static final int MEMINFO_CMA_FREE = 25;
+    /** @hide */
+    public static final int MEMINFO_COUNT = 26;
 
     /**
      * Retrieves /proc/meminfo.  outSizes is filled with fields
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index ffa9507..536ef31 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -44,6 +45,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.UUID;
 
 /**
  * Provides access to environment variables.
@@ -551,12 +553,37 @@
     }
 
     /** {@hide} */
-    public static File getDataUserCePackageDirectory(String volumeUuid, int userId,
-            String packageName) {
+    @NonNull
+    public static File getDataUserCePackageDirectory(@Nullable String volumeUuid, int userId,
+            @NonNull String packageName) {
         // TODO: keep consistent with installd
         return new File(getDataUserCeDirectory(volumeUuid, userId), packageName);
     }
 
+    /**
+     * Retrieve the credential encrypted data directory for a specific package of a specific user.
+     * This is equivalent to {@link ApplicationInfo#credentialProtectedDataDir}, exposed because
+     * fetching a full {@link ApplicationInfo} instance may be expensive if all the caller needs
+     * is this directory.
+     *
+     * @param storageUuid The storage volume for this directory, usually retrieved from a
+     * {@link StorageManager} API or {@link ApplicationInfo#storageUuid}.
+     * @param user The user this directory is for.
+     * @param packageName The app this directory is for.
+     *
+     * @see ApplicationInfo#credentialProtectedDataDir
+     * @return A file to the directory.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public static File getDataCePackageDirectoryForUser(@NonNull UUID storageUuid,
+            @NonNull UserHandle user, @NonNull String packageName) {
+        var volumeUuid = StorageManager.convert(storageUuid);
+        return getDataUserCePackageDirectory(volumeUuid, user.getIdentifier(), packageName);
+    }
+
     /** {@hide} */
     public static File getDataUserDeDirectory(String volumeUuid) {
         return new File(getDataDirectory(volumeUuid), DIR_USER_DE);
@@ -568,13 +595,38 @@
     }
 
     /** {@hide} */
-    public static File getDataUserDePackageDirectory(String volumeUuid, int userId,
-            String packageName) {
+    @NonNull
+    public static File getDataUserDePackageDirectory(@Nullable String volumeUuid, int userId,
+            @NonNull String packageName) {
         // TODO: keep consistent with installd
         return new File(getDataUserDeDirectory(volumeUuid, userId), packageName);
     }
 
     /**
+     * Retrieve the device encrypted data directory for a specific package of a specific user. This
+     * is equivalent to {@link ApplicationInfo#deviceProtectedDataDir}, exposed because fetching a
+     * full {@link ApplicationInfo} instance may be expensive if all the caller needs is this
+     * directory.
+     *
+     * @param storageUuid The storage volume for this directory, usually retrieved from a
+     * {@link StorageManager} API or {@link ApplicationInfo#storageUuid}.
+     * @param user The user this directory is for.
+     * @param packageName The app this directory is for.
+     *
+     * @see ApplicationInfo#deviceProtectedDataDir
+     * @return A file to the directory.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public static File getDataDePackageDirectoryForUser(@NonNull UUID storageUuid,
+            @NonNull UserHandle user, @NonNull String packageName) {
+        var volumeUuid = StorageManager.convert(storageUuid);
+        return getDataUserDePackageDirectory(volumeUuid, user.getIdentifier(), packageName);
+    }
+
+    /**
      * Return preloads directory.
      * <p>This directory may contain pre-loaded content such as
      * {@link #getDataPreloadsDemoDirectory() demo videos} and
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index a529ac6..712d328 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -177,12 +177,15 @@
         final long traceTag = me.mTraceTag;
         long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
         long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
-        if (thresholdOverride > 0) {
+
+        final boolean hasOverride = thresholdOverride >= 0;
+        if (hasOverride) {
             slowDispatchThresholdMs = thresholdOverride;
             slowDeliveryThresholdMs = thresholdOverride;
         }
-        final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
-        final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
+        final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0 || hasOverride)
+                && (msg.when > 0);
+        final boolean logSlowDispatch = (slowDispatchThresholdMs > 0 || hasOverride);
 
         final boolean needStartTime = logSlowDelivery || logSlowDispatch;
         final boolean needEndTime = logSlowDispatch;
@@ -283,7 +286,7 @@
                 SystemProperties.getInt("log.looper."
                         + Process.myUid() + "."
                         + Thread.currentThread().getName()
-                        + ".slow", 0);
+                        + ".slow", -1);
 
         me.mSlowDeliveryDetected = false;
 
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index 3a56662..cc54266 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -84,6 +84,15 @@
           "include-filter": "android.os.cts.SharedMemoryTest"
         }
       ]
+    },
+    {
+      "file_patterns": ["Environment[^/]*\\.java"],
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.os.EnvironmentTest"
+        }
+      ]
     }
   ],
   "postsubmit": [
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 9c8ee56..8004143 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -25,6 +25,9 @@
 import android.content.pm.IPackageLoadingProgressCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -146,15 +149,29 @@
     @Nullable
     public IncrementalStorage createStorage(@NonNull String path,
             @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) {
+        int id = -1;
         try {
-            final int id = mService.createLinkedStorage(
+            // Incremental service mounts its newly created storage on top of the supplied path,
+            // ensure that the original mode remains the same after mounting.
+            StructStat st = Os.stat(path);
+            id = mService.createLinkedStorage(
                     path, linkedStorage.getId(), createMode);
             if (id < 0) {
                 return null;
             }
+            Os.chmod(path, st.st_mode & 07777);
             return new IncrementalStorage(mService, id);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
+        } catch (ErrnoException e) {
+            if (id >= 0) {
+                try {
+                    mService.deleteStorage(id);
+                } catch (RemoteException re) {
+                    throw re.rethrowFromSystemServer();
+                }
+            }
+            throw new RuntimeException(e);
         }
     }
 
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 7899420..a72ccad 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2742,7 +2742,8 @@
 
     /** {@hide} */
     @TestApi
-    public static @NonNull UUID convert(@NonNull String uuid) {
+    public static @NonNull UUID convert(@Nullable String uuid) {
+        // UUID_PRIVATE_INTERNAL is null, so this accepts nullable input
         if (Objects.equals(uuid, UUID_PRIVATE_INTERNAL)) {
             return UUID_DEFAULT;
         } else if (Objects.equals(uuid, UUID_PRIMARY_PHYSICAL)) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e90ba6e..90aca14 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6210,6 +6210,7 @@
             MOVED_TO_GLOBAL.add(Settings.Global.DEFAULT_DNS_SERVER);
             MOVED_TO_GLOBAL.add(Settings.Global.PREFERRED_NETWORK_MODE);
             MOVED_TO_GLOBAL.add(Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY);
+            MOVED_TO_GLOBAL.add(Settings.Global.SECURE_FRP_MODE);
         }
 
         /** @hide */
@@ -7072,7 +7073,10 @@
          * device is removed from this mode.
          * <p>
          * Type: int (0 for false, 1 for true)
+         *
+         * @deprecated Use Global.SECURE_FRP_MODE
          */
+        @Deprecated
         @Readable
         public static final String SECURE_FRP_MODE = "secure_frp_mode";
 
@@ -11897,7 +11901,21 @@
         public static final String DEVICE_PROVISIONED = "device_provisioned";
 
         /**
-         * Whether bypassing the device policy management role holder qualifcation is allowed,
+         * Indicates whether the device is under restricted secure FRP mode.
+         * Secure FRP mode is enabled when the device is under FRP. On solving of FRP challenge,
+         * device is removed from this mode.
+         * <p>
+         * Type: int (0 for false, 1 for true)
+         *
+         * @hide
+         */
+        @SystemApi
+        @Readable
+        @SuppressLint("NoSettingsProvider")
+        public static final String SECURE_FRP_MODE = "secure_frp_mode";
+
+        /**
+         * Whether bypassing the device policy management role holder qualification is allowed,
          * (0 = false, 1 = true).
          *
          * @hide
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractHotwordDetector.java
index 28357ef..c90ab67 100644
--- a/core/java/android/service/voice/AbstractHotwordDetector.java
+++ b/core/java/android/service/voice/AbstractHotwordDetector.java
@@ -25,7 +25,9 @@
 import android.app.compat.CompatChanges;
 import android.media.AudioFormat;
 import android.media.permission.Identity;
+import android.os.Binder;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
@@ -49,19 +51,20 @@
     private final IVoiceInteractionManagerService mManagerService;
     private final Handler mHandler;
     private final HotwordDetector.Callback mCallback;
-    private final int mDetectorType;
     private Consumer<AbstractHotwordDetector> mOnDestroyListener;
     private final AtomicBoolean mIsDetectorActive;
+    /**
+     * A token which is used by voice interaction system service to identify different detectors.
+     */
+    private final IBinder mToken = new Binder();
 
     AbstractHotwordDetector(
             IVoiceInteractionManagerService managerService,
-            HotwordDetector.Callback callback,
-            int detectorType) {
+            HotwordDetector.Callback callback) {
         mManagerService = managerService;
         // TODO: this needs to be supplied from above
         mHandler = new Handler(Looper.getMainLooper());
         mCallback = callback;
-        mDetectorType = detectorType;
         mIsDetectorActive = new AtomicBoolean(true);
     }
 
@@ -94,6 +97,7 @@
                     audioStream,
                     audioFormat,
                     options,
+                    mToken,
                     new BinderCallback(mHandler, mCallback));
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
@@ -111,7 +115,7 @@
         }
         throwIfDetectorIsNoLongerActive();
         try {
-            mManagerService.updateState(options, sharedMemory);
+            mManagerService.updateState(options, sharedMemory, mToken);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -128,7 +132,7 @@
         Identity identity = new Identity();
         identity.packageName = ActivityThread.currentOpPackageName();
         try {
-            mManagerService.initAndVerifyDetector(identity, options, sharedMemory, callback,
+            mManagerService.initAndVerifyDetector(identity, options, sharedMemory, mToken, callback,
                     detectorType);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -151,6 +155,11 @@
             return;
         }
         mIsDetectorActive.set(false);
+        try {
+            mManagerService.destroyDetector(mToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
         synchronized (mLock) {
             mOnDestroyListener.accept(this);
         }
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 320280b..9008bf7 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -293,7 +293,7 @@
     private final Handler mHandler;
     private final IBinder mBinder = new Binder();
     private final int mTargetSdkVersion;
-    private final boolean mSupportHotwordDetectionService;
+    private final boolean mSupportSandboxedDetectionService;
 
     @GuardedBy("mLock")
     private boolean mIsAvailabilityOverriddenByTestApi = false;
@@ -756,7 +756,7 @@
      * @param callback A non-null Callback for receiving the recognition events.
      * @param modelManagementService A service that allows management of sound models.
      * @param targetSdkVersion The target SDK version.
-     * @param supportHotwordDetectionService {@code true} if HotwordDetectionService should be
+     * @param SupportSandboxedDetectionService {@code true} if HotwordDetectionService should be
      * triggered, otherwise {@code false}.
      *
      * @hide
@@ -764,10 +764,8 @@
     public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback,
             KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
             IVoiceInteractionManagerService modelManagementService, int targetSdkVersion,
-            boolean supportHotwordDetectionService) {
-        super(modelManagementService, callback,
-                supportHotwordDetectionService ? DETECTOR_TYPE_TRUSTED_HOTWORD_DSP
-                        : DETECTOR_TYPE_NORMAL);
+            boolean supportSandboxedDetectionService) {
+        super(modelManagementService, callback);
 
         mHandler = new MyHandler();
         mText = text;
@@ -777,12 +775,12 @@
         mInternalCallback = new SoundTriggerListener(mHandler);
         mModelManagementService = modelManagementService;
         mTargetSdkVersion = targetSdkVersion;
-        mSupportHotwordDetectionService = supportHotwordDetectionService;
+        mSupportSandboxedDetectionService = supportSandboxedDetectionService;
     }
 
     @Override
     void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
-        if (mSupportHotwordDetectionService) {
+        if (mSupportSandboxedDetectionService) {
             initAndVerifyDetector(options, sharedMemory, mInternalCallback,
                     DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
         }
@@ -814,7 +812,7 @@
     public final void updateState(@Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory) throws IllegalDetectorStateException {
         synchronized (mLock) {
-            if (!mSupportHotwordDetectionService) {
+            if (!mSupportSandboxedDetectionService) {
                 throw new IllegalStateException(
                         "updateState called, but it doesn't support hotword detection service");
             }
@@ -1410,8 +1408,8 @@
      * @hide
      */
     @Override
-    public boolean isUsingHotwordDetectionService() {
-        return mSupportHotwordDetectionService;
+    public boolean isUsingSandboxedDetectionService() {
+        return mSupportSandboxedDetectionService;
     }
 
     /**
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index 552a793..a47c096 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -140,7 +140,7 @@
     @Nullable
     private IRecognitionServiceManager mIRecognitionServiceManager;
 
-    private final IHotwordDetectionService mInterface = new IHotwordDetectionService.Stub() {
+    private final ISandboxedDetectionService mInterface = new ISandboxedDetectionService.Stub() {
         @Override
         public void detectFromDspSource(
                 SoundTrigger.KeyphraseRecognitionEvent event,
diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java
index 7112dc6..b7f7d54 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -178,7 +178,7 @@
     /**
      * @hide
      */
-    default boolean isUsingHotwordDetectionService() {
+    default boolean isUsingSandboxedDetectionService() {
         throw new UnsupportedOperationException("Not implemented. Must override in a subclass.");
     }
 
diff --git a/core/java/android/service/voice/IHotwordDetectionService.aidl b/core/java/android/service/voice/ISandboxedDetectionService.aidl
similarity index 94%
rename from core/java/android/service/voice/IHotwordDetectionService.aidl
rename to core/java/android/service/voice/ISandboxedDetectionService.aidl
index 9ef9307..5537fd1 100644
--- a/core/java/android/service/voice/IHotwordDetectionService.aidl
+++ b/core/java/android/service/voice/ISandboxedDetectionService.aidl
@@ -29,11 +29,11 @@
 import android.speech.IRecognitionServiceManager;
 
 /**
- * Provide the interface to communicate with hotword detection service.
+ * Provide the interface to communicate with sandboxed detection service.
  *
  * @hide
  */
-oneway interface IHotwordDetectionService {
+oneway interface ISandboxedDetectionService {
     void detectFromDspSource(
         in SoundTrigger.KeyphraseRecognitionEvent event,
         in AudioFormat audioFormat,
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 11688df..f1b7745 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -59,7 +59,7 @@
             IVoiceInteractionManagerService managerService,
             AudioFormat audioFormat,
             HotwordDetector.Callback callback) {
-        super(managerService, callback, DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
+        super(managerService, callback);
 
         mManagerService = managerService;
         mAudioFormat = audioFormat;
@@ -129,7 +129,7 @@
      * @hide
      */
     @Override
-    public boolean isUsingHotwordDetectionService() {
+    public boolean isUsingSandboxedDetectionService() {
         return true;
     }
 
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 7c125c7..a59578e 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -430,7 +430,7 @@
                 safelyShutdownAllHotwordDetectors();
             } else {
                 for (HotwordDetector detector : mActiveHotwordDetectors) {
-                    if (detector.isUsingHotwordDetectionService()
+                    if (detector.isUsingSandboxedDetectionService()
                             != supportHotwordDetectionService) {
                         throw new IllegalStateException(
                                 "It disallows to create trusted and non-trusted detectors "
@@ -513,7 +513,7 @@
                 safelyShutdownAllHotwordDetectors();
             } else {
                 for (HotwordDetector detector : mActiveHotwordDetectors) {
-                    if (!detector.isUsingHotwordDetectionService()) {
+                    if (!detector.isUsingSandboxedDetectionService()) {
                         throw new IllegalStateException(
                                 "It disallows to create trusted and non-trusted detectors "
                                         + "at the same time.");
@@ -605,7 +605,7 @@
 
     private void shutdownHotwordDetectionServiceIfRequiredLocked() {
         for (HotwordDetector detector : mActiveHotwordDetectors) {
-            if (detector.isUsingHotwordDetectionService()) {
+            if (detector.isUsingSandboxedDetectionService()) {
                 return;
             }
         }
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 0a1538de..519647d 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -45,6 +45,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.listeners.ListenerExecutor;
+import com.android.internal.telephony.ICarrierConfigChangeListener;
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.ITelephonyRegistry;
@@ -54,8 +55,10 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
@@ -89,6 +92,14 @@
             IOnSubscriptionsChangedListener> mOpportunisticSubscriptionChangedListenerMap
             = new HashMap<>();
 
+    /**
+     * A mapping between {@link CarrierConfigManager.CarrierConfigChangeListener} and its callback
+     * ICarrierConfigChangeListener.
+     */
+    private final ConcurrentHashMap<CarrierConfigManager.CarrierConfigChangeListener,
+                ICarrierConfigChangeListener>
+            mCarrierConfigChangeListenerMap = new ConcurrentHashMap<>();
+
 
     /** @hide **/
     public TelephonyRegistryManager(@NonNull Context context) {
@@ -1412,4 +1423,94 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Register a {@link android.telephony.CarrierConfigManager.CarrierConfigChangeListener} to get
+     * notification when carrier configurations have changed.
+     *
+     * @param executor The executor on which the callback will be executed.
+     * @param listener The CarrierConfigChangeListener to be registered with.
+     */
+    public void addCarrierConfigChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull CarrierConfigManager.CarrierConfigChangeListener listener) {
+        Objects.requireNonNull(executor, "Executor should be non-null.");
+        Objects.requireNonNull(listener, "Listener should be non-null.");
+        if (mCarrierConfigChangeListenerMap.get(listener) != null) {
+            Log.e(TAG, "registerCarrierConfigChangeListener: listener already present");
+            return;
+        }
+
+        ICarrierConfigChangeListener callback = new ICarrierConfigChangeListener.Stub() {
+            @Override
+            public void onCarrierConfigChanged(int slotIndex, int subId, int carrierId,
+                    int specificCarrierId) {
+                Log.d(TAG, "onCarrierConfigChanged call in ICarrierConfigChangeListener callback");
+                final long identify = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> listener.onCarrierConfigChanged(slotIndex, subId,
+                            carrierId, specificCarrierId));
+                } finally {
+                    Binder.restoreCallingIdentity(identify);
+                }
+            }
+        };
+
+        try {
+            sRegistry.addCarrierConfigChangeListener(callback,
+                    mContext.getOpPackageName(), mContext.getAttributionTag());
+            mCarrierConfigChangeListenerMap.put(listener, callback);
+        } catch (RemoteException re) {
+            // system server crashes
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregister to stop the notification when carrier configurations changed.
+     *
+     * @param listener The CarrierConfigChangeListener to be unregistered with.
+     */
+    public void removeCarrierConfigChangedListener(
+            @NonNull CarrierConfigManager.CarrierConfigChangeListener listener) {
+        Objects.requireNonNull(listener, "Listener should be non-null.");
+        if (mCarrierConfigChangeListenerMap.get(listener) == null) {
+            Log.e(TAG, "removeCarrierConfigChangedListener: listener was not present");
+            return;
+        }
+
+        try {
+            sRegistry.removeCarrierConfigChangeListener(
+                    mCarrierConfigChangeListenerMap.get(listener), mContext.getOpPackageName());
+            mCarrierConfigChangeListenerMap.remove(listener);
+        } catch (RemoteException re) {
+            // System sever crashes
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Notify the registrants the carrier configurations have changed.
+     *
+     * @param slotIndex         The SIM slot index on which to monitor and get notification.
+     * @param subId             The subscription on the SIM slot. May be
+     *                          {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+     * @param carrierId         The optional carrier Id, may be
+     *                          {@link TelephonyManager#UNKNOWN_CARRIER_ID}.
+     * @param specificCarrierId The optional specific carrier Id, may be {@link
+     *                          TelephonyManager#UNKNOWN_CARRIER_ID}.
+     */
+    public void notifyCarrierConfigChanged(int slotIndex, int subId, int carrierId,
+            int specificCarrierId) {
+        // Only validate slotIndex, all others are optional and allowed to be invalid
+        if (!SubscriptionManager.isValidPhoneId(slotIndex)) {
+            Log.e(TAG, "notifyCarrierConfigChanged, ignored: invalid slotIndex " + slotIndex);
+            return;
+        }
+        try {
+            sRegistry.notifyCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 959295b..101a071 100755
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -20,12 +20,23 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.FontScaleConverter;
 import android.os.SystemProperties;
+import android.view.WindowManager;
 
 /**
  * A structure describing general information about a display, such as its
  * size, density, and font scaling.
  * <p>To access the DisplayMetrics members, retrieve display metrics like this:</p>
  * <pre>context.getResources().getDisplayMetrics();</pre>
+ *
+ * <p>
+ * For UI layout, obtain {@link android.view.WindowMetrics} from
+ * {@link WindowManager#getCurrentWindowMetrics()}. {@code DisplayMetrics} should only be used for
+ * obtaining display related properties, such as {@link #xdpi} and {@link #ydpi}
+ * </p><p>
+ * See {@link #density} for more information about the differences between {@link #xdpi},
+ * {@link #ydpi} and {@link #density}.
+ * </p>
+ *
  */
 public class DisplayMetrics {
     /**
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 4277d01..2c4f38d 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -52,16 +52,6 @@
     public static final String SETTINGS_SUPPORT_LARGE_SCREEN = "settings_support_large_screen";
 
     /**
-     * Feature flag to allow/restrict intent redirection from/to clone profile.
-     * Default value is false,this is to ensure that framework is not impacted by intent redirection
-     * till we are ready to launch.
-     * From Android U onwards, this would be set to true and eventually removed.
-     * @hide
-     */
-    public static final String SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE =
-            "settings_allow_intent_redirection_for_clone_profile";
-
-    /**
      * Support locale opt-out and opt-in switch for per app's language.
      * @hide
      */
@@ -174,7 +164,6 @@
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true");
         DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
         DEFAULT_FLAGS.put("settings_search_always_expand", "true");
-        DEFAULT_FLAGS.put(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE, "false");
         DEFAULT_FLAGS.put(SETTINGS_APP_LOCALE_OPT_IN_ENABLED, "true");
         DEFAULT_FLAGS.put(SETTINGS_VOLUME_PANEL_IN_SYSTEMUI, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
@@ -196,7 +185,6 @@
 
     static {
         PERSISTENT_FLAGS = new HashSet<>();
-        PERSISTENT_FLAGS.add(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE);
         PERSISTENT_FLAGS.add(SETTINGS_APP_LOCALE_OPT_IN_ENABLED);
         PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index c692981..3b082bc 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -140,13 +140,13 @@
     }
 
     /**
-     * Returns a SyncTarget that can be used to sync {@link AttachedSurfaceControl} in a
+     * Returns a SurfaceSyncGroup that can be used to sync {@link AttachedSurfaceControl} in a
      * {@link SurfaceSyncGroup}
      *
      * @hide
      */
     @Nullable
-    default SurfaceSyncGroup.SyncTarget getSyncTarget() {
+    default SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
         return null;
     }
 }
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index a42d3eb..71030bcc 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1469,7 +1469,8 @@
      * @param outMetrics A {@link DisplayMetrics} object which receives the display metrics.
      *
      * @deprecated Use {@link WindowMetrics#getBounds()} to get the dimensions of the application
-     *     window. Use {@link Configuration#densityDpi} to get the display density.
+     *     window. Use {@link WindowMetrics#getDensity()} to get the density of the application
+     *     window.
      */
     @Deprecated
     public void getMetrics(DisplayMetrics outMetrics) {
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 06c1b25..61582cd 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -892,13 +892,18 @@
      * The use of this button does not usually correspond to the function of an eraser.
      */
     public static final int KEYCODE_STYLUS_BUTTON_TAIL = 311;
+    /**
+     * Key code constant: To open recent apps view (a.k.a. Overview).
+     * This key is handled by the framework and is never delivered to applications.
+     */
+    public static final int KEYCODE_RECENT_APPS = 312;
 
    /**
      * Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent.
      * @hide
      */
     @TestApi
-    public static final int LAST_KEYCODE = KEYCODE_STYLUS_BUTTON_TAIL;
+    public static final int LAST_KEYCODE = KEYCODE_RECENT_APPS;
 
     // NOTE: If you add a new keycode here you must also add it to:
     //  isSystem()
@@ -2021,6 +2026,7 @@
             case KeyEvent.KEYCODE_MENU:
             case KeyEvent.KEYCODE_SOFT_RIGHT:
             case KeyEvent.KEYCODE_HOME:
+            case KeyEvent.KEYCODE_RECENT_APPS:
             case KeyEvent.KEYCODE_BACK:
             case KeyEvent.KEYCODE_CALL:
             case KeyEvent.KEYCODE_ENDCALL:
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index c8a5d8d..4fbb249 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2199,6 +2199,7 @@
     }
 
     /** @hide */
+    @TestApi
     @Override
     public int getDisplayId() {
         return nativeGetDisplayId(mNativePtr);
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index c50f70a..f0c7909 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -129,7 +129,7 @@
             close();
         }
         mCancellation = null;
-        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, START_CAPTURE, mTraceId);
+        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
     }
 
     @BinderThread
@@ -164,7 +164,7 @@
         } finally {
             mCancellation = null;
         }
-        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, REQUEST_IMAGE, mTraceId);
+        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
     }
 
     @BinderThread
@@ -200,8 +200,8 @@
             mCancellation = null;
             close();
         }
-        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, END_CAPTURE, mTraceId);
-        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, SESSION, mTraceId);
+        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
+        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
     }
 
     @Override
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 277b90c..5e8e191 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -275,6 +275,7 @@
             int l, int t, int r, int b);
     private static native void nativeSetDefaultApplyToken(IBinder token);
     private static native IBinder nativeGetDefaultApplyToken();
+    private static native boolean nativeBootFinished();
 
 
     /**
@@ -2376,6 +2377,14 @@
     }
 
     /**
+     * Lets surfaceFlinger know the boot procedure is completed.
+     * @hide
+     */
+    public static boolean bootFinished() {
+        return nativeBootFinished();
+    }
+
+    /**
      * Interface to handle request to
      * {@link SurfaceControl.Transaction#addTransactionCommittedListener(Executor, TransactionCommittedListener)}
      */
@@ -3870,4 +3879,5 @@
         SyncFence fence = new SyncFence(nativeFencePtr);
         callback.accept(fence);
     }
+
 }
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 1889772..0a134be 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -404,14 +404,6 @@
     }
 
     /**
-     * @hide
-     */
-    @TestApi
-    public void relayout(WindowManager.LayoutParams attrs) {
-        relayout(attrs, SurfaceControl.Transaction::apply);
-    }
-
-    /**
      * Forces relayout and draw and allows to set a custom callback when it is finished
      * @hide
      */
@@ -423,6 +415,14 @@
     }
 
     /**
+     * @hide
+     */
+    @TestApi
+    public void relayout(WindowManager.LayoutParams attrs) {
+        mViewRoot.setLayoutParams(attrs, false);
+    }
+
+    /**
      * Modify the size of the root view.
      *
      * @param width Width in pixels
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 33ea92d..9db084e 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -982,8 +982,8 @@
 
                 final boolean redrawNeeded = sizeChanged || creating || hintChanged
                         || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
-                boolean shouldSyncBuffer =
-                        redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
+                boolean shouldSyncBuffer = redrawNeeded && viewRoot.wasRelayoutRequested()
+                        && viewRoot.isInWMSRequestedSync();
                 SyncBufferTransactionCallback syncBufferTransactionCallback = null;
                 if (shouldSyncBuffer) {
                     syncBufferTransactionCallback = new SyncBufferTransactionCallback();
@@ -1073,35 +1073,34 @@
     private void handleSyncBufferCallback(SurfaceHolder.Callback[] callbacks,
             SyncBufferTransactionCallback syncBufferTransactionCallback) {
 
-        getViewRootImpl().addToSync((parentSyncGroup, syncBufferCallback) ->
-                redrawNeededAsync(callbacks, () -> {
-                    Transaction t = null;
-                    if (mBlastBufferQueue != null) {
-                        mBlastBufferQueue.stopContinuousSyncTransaction();
-                        t = syncBufferTransactionCallback.waitForTransaction();
-                    }
+        final SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup();
+        getViewRootImpl().addToSync(surfaceSyncGroup);
+        redrawNeededAsync(callbacks, () -> {
+            Transaction t = null;
+            if (mBlastBufferQueue != null) {
+                mBlastBufferQueue.stopContinuousSyncTransaction();
+                t = syncBufferTransactionCallback.waitForTransaction();
+            }
 
-                    syncBufferCallback.onTransactionReady(t);
-                    onDrawFinished();
-                }));
+            surfaceSyncGroup.onTransactionReady(t);
+            onDrawFinished();
+        });
     }
 
     private void handleSyncNoBuffer(SurfaceHolder.Callback[] callbacks) {
-        final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+        final SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup();
         synchronized (mSyncGroups) {
-            mSyncGroups.add(syncGroup);
+            mSyncGroups.add(surfaceSyncGroup);
         }
 
-        syncGroup.addToSync((parentSyncGroup, syncBufferCallback) ->
-                redrawNeededAsync(callbacks, () -> {
-                    syncBufferCallback.onTransactionReady(null);
-                    onDrawFinished();
-                    synchronized (mSyncGroups) {
-                        mSyncGroups.remove(syncGroup);
-                    }
-                }));
+        redrawNeededAsync(callbacks, () -> {
+            synchronized (mSyncGroups) {
+                mSyncGroups.remove(surfaceSyncGroup);
+            }
+            surfaceSyncGroup.onTransactionReady(null);
+            onDrawFinished();
+        });
 
-        syncGroup.markSyncReady();
     }
 
     private void redrawNeededAsync(SurfaceHolder.Callback[] callbacks,
@@ -1119,7 +1118,7 @@
         if (viewRoot != null) {
             synchronized (mSyncGroups) {
                 for (SurfaceSyncGroup syncGroup : mSyncGroups) {
-                    viewRoot.mergeSync(syncGroup);
+                    viewRoot.addToSync(syncGroup);
                 }
             }
         }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 727011c..b2973ef 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -595,19 +595,21 @@
     String mLastPerformDrawSkippedReason;
     /** The reason the last call to performTraversals() returned without drawing */
     String mLastPerformTraversalsSkipDrawReason;
-    /** The state of the local sync, if one is in progress. Can be one of the states below. */
-    int mLocalSyncState;
+    /** The state of the WMS requested sync, if one is in progress. Can be one of the states
+     * below. */
+    int mWmsRequestSyncGroupState;
 
-    // The possible states of the local sync, see createSyncIfNeeded()
-    private final int LOCAL_SYNC_NONE = 0;
-    private final int LOCAL_SYNC_PENDING = 1;
-    private final int LOCAL_SYNC_RETURNED = 2;
-    private final int LOCAL_SYNC_MERGED = 3;
+    // The possible states of the WMS requested sync, see createSyncIfNeeded()
+    private static final int WMS_SYNC_NONE = 0;
+    private static final int WMS_SYNC_PENDING = 1;
+    private static final int WMS_SYNC_RETURNED = 2;
+    private static final int WMS_SYNC_MERGED = 3;
 
     /**
-     * Set whether the draw should send the buffer to system server. When set to true, VRI will
-     * create a sync transaction with BBQ and send the resulting buffer to system server. If false,
-     * VRI will not try to sync a buffer in BBQ, but still report when a draw occurred.
+     * Set whether the requested SurfaceSyncGroup should sync the buffer. When set to true, VRI will
+     * create a sync transaction with BBQ and send the resulting buffer back to the
+     * SurfaceSyncGroup. If false, VRI will not try to sync a buffer in BBQ, but still report when a
+     * draw occurred.
      */
     private boolean mSyncBuffer = false;
 
@@ -849,8 +851,19 @@
         return mHandwritingInitiator;
     }
 
-    private SurfaceSyncGroup mSyncGroup;
-    private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
+    /**
+     * A SurfaceSyncGroup that is created when WMS requested to sync the buffer
+     */
+    private SurfaceSyncGroup mWmsRequestSyncGroup;
+
+    /**
+     * The SurfaceSyncGroup that represents the active VRI SurfaceSyncGroup. This is non null if
+     * anyone requested the SurfaceSyncGroup for this VRI to ensure that anyone trying to sync with
+     * this VRI are collected together. The SurfaceSyncGroup is cleared when the VRI draws since
+     * that is the stop point where all changes are have been applied. A new SurfaceSyncGroup is
+     * created after that point when something wants to sync VRI again.
+     */
+    private SurfaceSyncGroup mActiveSurfaceSyncGroup;
 
     private static final Object sSyncProgressLock = new Object();
     // The count needs to be static since it's used to enable or disable RT animations which is
@@ -3643,6 +3656,12 @@
         boolean cancelAndRedraw = cancelDueToPreDrawListener
                  || (cancelDraw && mDrewOnceForSync);
         if (!cancelAndRedraw) {
+            // A sync was already requested before the WMS requested sync. This means we need to
+            // sync the buffer, regardless if WMS wants to sync the buffer.
+            if (mActiveSurfaceSyncGroup != null) {
+                mSyncBuffer = true;
+            }
+
             createSyncIfNeeded();
             mDrewOnceForSync = true;
         }
@@ -3656,8 +3675,8 @@
                 mPendingTransitions.clear();
             }
 
-            if (mTransactionReadyCallback != null) {
-                mTransactionReadyCallback.onTransactionReady(null);
+            if (mActiveSurfaceSyncGroup != null) {
+                mActiveSurfaceSyncGroup.onTransactionReady(null);
             }
         } else if (cancelAndRedraw) {
             mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
@@ -3672,8 +3691,8 @@
                 }
                 mPendingTransitions.clear();
             }
-            if (!performDraw() && mTransactionReadyCallback != null) {
-                mTransactionReadyCallback.onTransactionReady(null);
+            if (!performDraw() && mActiveSurfaceSyncGroup != null) {
+                mActiveSurfaceSyncGroup.onTransactionReady(null);
             }
         }
 
@@ -3687,39 +3706,40 @@
         if (!cancelAndRedraw) {
             mReportNextDraw = false;
             mLastReportNextDrawReason = null;
-            mTransactionReadyCallback = null;
+            mActiveSurfaceSyncGroup = null;
             mSyncBuffer = false;
-            if (isInLocalSync()) {
-                mSyncGroup.markSyncReady();
-                mSyncGroup = null;
-                mLocalSyncState = LOCAL_SYNC_NONE;
+            if (isInWMSRequestedSync()) {
+                mWmsRequestSyncGroup.onTransactionReady(null);
+                mWmsRequestSyncGroup = null;
+                mWmsRequestSyncGroupState = WMS_SYNC_NONE;
             }
         }
     }
 
     private void createSyncIfNeeded() {
-        // Started a sync already or there's nothing needing to sync
-        if (isInLocalSync() || !mReportNextDraw) {
+        // WMS requested sync already started or there's nothing needing to sync
+        if (isInWMSRequestedSync() || !mReportNextDraw) {
             return;
         }
 
         final int seqId = mSyncSeqId;
-        mLocalSyncState = LOCAL_SYNC_PENDING;
-        mSyncGroup = new SurfaceSyncGroup(transaction -> {
-            mLocalSyncState = LOCAL_SYNC_RETURNED;
+        mWmsRequestSyncGroupState = WMS_SYNC_PENDING;
+        mWmsRequestSyncGroup = new SurfaceSyncGroup(t -> {
+            mWmsRequestSyncGroupState = WMS_SYNC_RETURNED;
             // Callback will be invoked on executor thread so post to main thread.
             mHandler.postAtFrontOfQueue(() -> {
-                if (transaction != null) {
-                    mSurfaceChangedTransaction.merge(transaction);
+                if (t != null) {
+                    mSurfaceChangedTransaction.merge(t);
                 }
-                mLocalSyncState = LOCAL_SYNC_MERGED;
+                mWmsRequestSyncGroupState = WMS_SYNC_MERGED;
                 reportDrawFinished(seqId);
             });
         });
         if (DEBUG_BLAST) {
-            Log.d(mTag, "Setup new sync id=" + mSyncGroup);
+            Log.d(mTag, "Setup new sync id=" + mWmsRequestSyncGroup);
         }
-        mSyncGroup.addToSync(mSyncTarget);
+
+        mWmsRequestSyncGroup.addToSync(this);
         notifySurfaceSyncStarted();
     }
 
@@ -3820,43 +3840,7 @@
         }
 
         if (mAdded) {
-            profileRendering(hasWindowFocus);
-            if (hasWindowFocus) {
-                if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) {
-                    mFullRedrawNeeded = true;
-                    try {
-                        final Rect surfaceInsets = mWindowAttributes.surfaceInsets;
-                        mAttachInfo.mThreadedRenderer.initializeIfNeeded(
-                                mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
-                    } catch (OutOfResourcesException e) {
-                        Log.e(mTag, "OutOfResourcesException locking surface", e);
-                        try {
-                            if (!mWindowSession.outOfMemory(mWindow)) {
-                                Slog.w(mTag, "No processes killed for memory;"
-                                        + " killing self");
-                                Process.killProcess(Process.myPid());
-                            }
-                        } catch (RemoteException ex) {
-                        }
-                        // Retry in a bit.
-                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                                MSG_WINDOW_FOCUS_CHANGED), 500);
-                        return;
-                    }
-                }
-            }
-
-            mAttachInfo.mHasWindowFocus = hasWindowFocus;
-            mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes);
-
-            if (mView != null) {
-                mAttachInfo.mKeyDispatchState.reset();
-                mView.dispatchWindowFocusChanged(hasWindowFocus);
-                mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
-                if (mAttachInfo.mTooltipHost != null) {
-                    mAttachInfo.mTooltipHost.hideTooltip();
-                }
-            }
+            dispatchFocusEvent(hasWindowFocus);
 
             // Note: must be done after the focus change callbacks,
             // so all of the view state is set up correctly.
@@ -3894,6 +3878,44 @@
         }
     }
 
+    private void dispatchFocusEvent(boolean hasWindowFocus) {
+        profileRendering(hasWindowFocus);
+        if (hasWindowFocus && mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) {
+            mFullRedrawNeeded = true;
+            try {
+                final Rect surfaceInsets = mWindowAttributes.surfaceInsets;
+                mAttachInfo.mThreadedRenderer.initializeIfNeeded(
+                        mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
+            } catch (OutOfResourcesException e) {
+                Log.e(mTag, "OutOfResourcesException locking surface", e);
+                try {
+                    if (!mWindowSession.outOfMemory(mWindow)) {
+                        Slog.w(mTag, "No processes killed for memory;"
+                                + " killing self");
+                        Process.killProcess(Process.myPid());
+                    }
+                } catch (RemoteException ex) {
+                }
+                // Retry in a bit.
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                        MSG_WINDOW_FOCUS_CHANGED), 500);
+                return;
+            }
+        }
+
+        mAttachInfo.mHasWindowFocus = hasWindowFocus;
+        mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes);
+
+        if (mView != null) {
+            mAttachInfo.mKeyDispatchState.reset();
+            mView.dispatchWindowFocusChanged(hasWindowFocus);
+            mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
+            if (mAttachInfo.mTooltipHost != null) {
+                mAttachInfo.mTooltipHost.hideTooltip();
+            }
+        }
+    }
+
     private void handleWindowTouchModeChanged() {
         final boolean inTouchMode;
         synchronized (this) {
@@ -4368,19 +4390,11 @@
         return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
     }
 
-    void addToSync(SurfaceSyncGroup.SyncTarget syncable) {
-        if (!isInLocalSync()) {
-            return;
-        }
-        mSyncGroup.addToSync(syncable);
-    }
-
     /**
-     * This VRI is currently in the middle of a sync request, but specifically one initiated from
-     * within VRI.
+     * This VRI is currently in the middle of a sync request that was initiated by WMS.
      */
-    public boolean isInLocalSync() {
-        return mSyncGroup != null;
+    public boolean isInWMSRequestedSync() {
+        return mWmsRequestSyncGroup != null;
     }
 
     private void addFrameCommitCallbackIfNeeded() {
@@ -4447,7 +4461,7 @@
             return false;
         }
 
-        final boolean fullRedrawNeeded = mFullRedrawNeeded || mTransactionReadyCallback != null;
+        final boolean fullRedrawNeeded = mFullRedrawNeeded || mActiveSurfaceSyncGroup != null;
         mFullRedrawNeeded = false;
 
         mIsDrawing = true;
@@ -4455,9 +4469,9 @@
 
         addFrameCommitCallbackIfNeeded();
 
-        boolean usingAsyncReport = isHardwareEnabled() && mTransactionReadyCallback != null;
+        boolean usingAsyncReport = isHardwareEnabled() && mActiveSurfaceSyncGroup != null;
         if (usingAsyncReport) {
-            registerCallbacksForSync(mSyncBuffer, mTransactionReadyCallback);
+            registerCallbacksForSync(mSyncBuffer, mActiveSurfaceSyncGroup);
         } else if (mHasPendingTransactions) {
             // These callbacks are only needed if there's no sync involved and there were calls to
             // applyTransactionOnDraw. These callbacks check if the draw failed for any reason and
@@ -4508,11 +4522,10 @@
             }
 
             if (mSurfaceHolder != null && mSurface.isValid()) {
-                final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback =
-                        mTransactionReadyCallback;
+                final SurfaceSyncGroup surfaceSyncGroup = mActiveSurfaceSyncGroup;
                 SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
-                        mHandler.post(() -> transactionReadyCallback.onTransactionReady(null)));
-                mTransactionReadyCallback = null;
+                        mHandler.post(() -> surfaceSyncGroup.onTransactionReady(null)));
+                mActiveSurfaceSyncGroup = null;
 
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
 
@@ -4523,8 +4536,8 @@
                 }
             }
         }
-        if (mTransactionReadyCallback != null && !usingAsyncReport) {
-            mTransactionReadyCallback.onTransactionReady(null);
+        if (mActiveSurfaceSyncGroup != null && !usingAsyncReport) {
+            mActiveSurfaceSyncGroup.onTransactionReady(null);
         }
         if (mPerformContentCapture) {
             performContentCaptureInitialReport();
@@ -8614,8 +8627,8 @@
             writer.println(innerPrefix + "mLastPerformDrawFailedReason="
                 + mLastPerformDrawSkippedReason);
         }
-        if (mLocalSyncState != LOCAL_SYNC_NONE) {
-            writer.println(innerPrefix + "mLocalSyncState=" + mLocalSyncState);
+        if (mWmsRequestSyncGroupState != WMS_SYNC_NONE) {
+            writer.println(innerPrefix + "mWmsRequestSyncGroupState=" + mWmsRequestSyncGroupState);
         }
         writer.println(innerPrefix + "mLastReportedMergedConfiguration="
                 + mLastReportedMergedConfiguration);
@@ -11203,7 +11216,7 @@
     }
 
     private void registerCallbacksForSync(boolean syncBuffer,
-            final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
+            final SurfaceSyncGroup surfaceSyncGroup) {
         if (!isHardwareEnabled()) {
             return;
         }
@@ -11230,7 +11243,7 @@
                 // pendingDrawFinished.
                 if ((syncResult
                         & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
-                    transactionReadyCallback.onTransactionReady(
+                    surfaceSyncGroup.onTransactionReady(
                             mBlastBufferQueue.gatherPendingTransactions(frame));
                     return null;
                 }
@@ -11241,7 +11254,7 @@
 
                 if (syncBuffer) {
                     mBlastBufferQueue.syncNextTransaction(
-                            transactionReadyCallback::onTransactionReady);
+                            surfaceSyncGroup::onTransactionReady);
                 }
 
                 return didProduceBuffer -> {
@@ -11261,7 +11274,7 @@
                         // since the frame didn't draw on this vsync. It's possible the frame will
                         // draw later, but it's better to not be sync than to block on a frame that
                         // may never come.
-                        transactionReadyCallback.onTransactionReady(
+                        surfaceSyncGroup.onTransactionReady(
                                 mBlastBufferQueue.gatherPendingTransactions(frame));
                         return;
                     }
@@ -11270,35 +11283,23 @@
                     // syncNextTransaction callback. Instead, just report back to the Syncer so it
                     // knows that this sync request is complete.
                     if (!syncBuffer) {
-                        transactionReadyCallback.onTransactionReady(null);
+                        surfaceSyncGroup.onTransactionReady(null);
                     }
                 };
             }
         });
     }
 
-    public final SurfaceSyncGroup.SyncTarget mSyncTarget = new SurfaceSyncGroup.SyncTarget() {
-        @Override
-        public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
-                SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
-            updateSyncInProgressCount(parentSyncGroup);
-            if (!isInLocalSync()) {
-                // Always sync the buffer if the sync request did not come from VRI.
-                mSyncBuffer = true;
-            }
-
-            if (mTransactionReadyCallback != null) {
-                Log.d(mTag, "Already set sync for the next draw.");
-                mTransactionReadyCallback.onTransactionReady(null);
-            }
-            if (DEBUG_BLAST) {
-                Log.d(mTag, "Setting syncFrameCallback");
-            }
-            mTransactionReadyCallback = transactionReadyCallback;
+    @Override
+    public SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
+        if (mActiveSurfaceSyncGroup == null) {
+            mActiveSurfaceSyncGroup = new SurfaceSyncGroup();
+            updateSyncInProgressCount(mActiveSurfaceSyncGroup);
             if (!mIsInTraversal && !mTraversalScheduled) {
                 scheduleTraversals();
             }
         }
+        return mActiveSurfaceSyncGroup;
     };
 
     private final Executor mSimpleExecutor = Runnable::run;
@@ -11323,15 +11324,10 @@
         });
     }
 
-    @Override
-    public SurfaceSyncGroup.SyncTarget getSyncTarget() {
-        return mSyncTarget;
-    }
-
-    void mergeSync(SurfaceSyncGroup otherSyncGroup) {
-        if (!isInLocalSync()) {
+    void addToSync(SurfaceSyncGroup syncable) {
+        if (mActiveSurfaceSyncGroup == null) {
             return;
         }
-        mSyncGroup.merge(otherSyncGroup);
+        mActiveSurfaceSyncGroup.addToSync(syncable, false /* parentSyncGroupMerge */);
     }
 }
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index 52e4e15..141849f 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -25,22 +25,64 @@
  * <p>
  * This is usually obtained from {@link WindowManager#getCurrentWindowMetrics()} and
  * {@link WindowManager#getMaximumWindowMetrics()}.
+ * </p>
+ * After {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, it also provides density.
+ * <h3>Obtains Window Dimensions in Density-independent Pixel(DP)</h3>
+ * <p>
+ * While {@link #getDensity()} is provided, the dimension in density-independent pixel could also be
+ * calculated with {@code WindowMetrics} properties, which is similar to
+ * {@link android.content.res.Configuration#screenWidthDp}
+ * <pre class="prettyprint">
+ * float widthInDp = windowMetrics.getBounds().width() / windowMetrics.getDensity();
+ * float heightInDp = windowMetrics.getBounds().height() / windowMetrics.getDensity();
+ * </pre>
+ * Also, the density in DPI can be obtained by:
+ * <pre class="prettyprint">
+ * float densityDp = DisplayMetrics.DENSITY_DEFAULT * windowMetrics.getDensity();
+ * </pre>
+ * </p>
  *
  * @see WindowInsets#getInsets(int)
  * @see WindowManager#getCurrentWindowMetrics()
  * @see WindowManager#getMaximumWindowMetrics()
+ * @see android.annotation.UiContext
  */
 public final class WindowMetrics {
-    private final @NonNull Rect mBounds;
-    private final @NonNull WindowInsets mWindowInsets;
+    @NonNull
+    private final Rect mBounds;
+    @NonNull
+    private final WindowInsets mWindowInsets;
 
+    /** @see android.util.DisplayMetrics#density */
+    private final float mDensity;
+
+    /** @deprecated use {@link #WindowMetrics(Rect, WindowInsets, float)} instead. */
+    @Deprecated
     public WindowMetrics(@NonNull Rect bounds, @NonNull WindowInsets windowInsets) {
-        mBounds = bounds;
-        mWindowInsets = windowInsets;
+        this(bounds, windowInsets, 1.0f);
     }
 
     /**
-     * Returns the bounds of the area associated with this window or visual context.
+     * The constructor to create a {@link WindowMetrics} instance.
+     * <p>
+     * Note that in most cases {@link WindowMetrics} is obtained from
+     * {@link WindowManager#getCurrentWindowMetrics()} or
+     * {@link WindowManager#getMaximumWindowMetrics()}.
+     * </p>
+     *
+     * @param bounds The window bounds
+     * @param windowInsets The {@link WindowInsets} of the window
+     * @param density The window density
+     */
+    public WindowMetrics(@NonNull Rect bounds, @NonNull WindowInsets windowInsets, float density) {
+        mBounds = bounds;
+        mWindowInsets = windowInsets;
+        mDensity = density;
+    }
+
+    /**
+     * Returns the bounds of the area associated with this window or
+     * {@link android.annotation.UiContext}.
      * <p>
      * <b>Note that the size of the reported bounds can have different size than
      * {@link Display#getSize(Point)}.</b> This method reports the window size including all system
@@ -66,16 +108,40 @@
      *
      * @return window bounds in pixels.
      */
-    public @NonNull Rect getBounds() {
+    @NonNull
+    public Rect getBounds() {
         return mBounds;
     }
 
     /**
-     * Returns the {@link WindowInsets} of the area associated with this window or visual context.
+     * Returns the {@link WindowInsets} of the area associated with this window or
+     * {@link android.annotation.UiContext}.
      *
      * @return the {@link WindowInsets} of the visual area.
      */
-    public @NonNull WindowInsets getWindowInsets() {
+    @NonNull
+    public WindowInsets getWindowInsets() {
         return mWindowInsets;
     }
+
+    /**
+     * Returns the density of the area associated with this window or
+     * {@link android.annotation.UiContext}, which uses the same units as
+     * {@link android.util.DisplayMetrics#density}.
+     *
+     * @see android.util.DisplayMetrics#DENSITY_DEFAULT
+     * @see android.util.DisplayMetrics#density
+     */
+    public float getDensity() {
+        return mDensity;
+    }
+
+    @Override
+    public String toString() {
+        return WindowMetrics.class.getSimpleName() + ":{"
+                + "bounds=" + mBounds
+                + ", windowInsets=" + mWindowInsets
+                + ", density" + mDensity
+                + "}";
+    }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
index 44b6deb..a757236 100644
--- a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
+++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
@@ -242,11 +242,7 @@
             super(context, executor, new AccessibilityService.Callbacks() {
                 @Override
                 public void onAccessibilityEvent(AccessibilityEvent event) {
-                    // TODO(254545943): Remove check when event processing is done more upstream in
-                    // AccessibilityManagerService.
-                    if (event.getDisplayId() == mDisplayId) {
-                        AccessibilityDisplayProxy.this.onAccessibilityEvent(event);
-                    }
+                    AccessibilityDisplayProxy.this.onAccessibilityEvent(event);
                 }
 
                 @Override
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 423c560..9abbba9 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -25,6 +25,7 @@
 import android.accessibilityservice.AccessibilityShortcutInfo;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -75,6 +76,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -138,6 +140,21 @@
     public static final int AUTOCLICK_DELAY_DEFAULT = 600;
 
     /**
+     * The contrast is defined as a float in [-1, 1], with a default value of 0.
+     * @hide
+     */
+    public static final float CONTRAST_MIN_VALUE = -1f;
+
+    /** @hide */
+    public static final float CONTRAST_MAX_VALUE = 1f;
+
+    /** @hide */
+    public static final float CONTRAST_DEFAULT_VALUE = 0f;
+
+    /** @hide */
+    public static final float CONTRAST_NOT_SET = Float.MIN_VALUE;
+
+    /**
      * Activity action: Launch UI to manage which accessibility service or feature is assigned
      * to the navigation bar Accessibility button.
      * <p>
@@ -246,6 +263,8 @@
     @UnsupportedAppUsage(trackingBug = 123768939L)
     boolean mIsHighTextContrastEnabled;
 
+    private float mUiContrast;
+
     boolean mIsAudioDescriptionByDefaultRequested;
 
     // accessibility tracing state
@@ -270,6 +289,9 @@
     private final ArrayMap<HighTextContrastChangeListener, Handler>
             mHighTextContrastStateChangeListeners = new ArrayMap<>();
 
+    private final ArrayMap<UiContrastChangeListener, Executor>
+            mUiContrastChangeListeners = new ArrayMap<>();
+
     private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
             mServicesStateChangeListeners = new ArrayMap<>();
 
@@ -336,7 +358,7 @@
          *
          * @param manager The manager that is calling back
          */
-        void onAccessibilityServicesStateChanged(@NonNull  AccessibilityManager manager);
+        void onAccessibilityServicesStateChanged(@NonNull AccessibilityManager manager);
     }
 
     /**
@@ -358,6 +380,21 @@
     }
 
     /**
+     * Listener for the UI contrast. To listen for changes to
+     * the UI contrast on the device, implement this interface and
+     * register it with the system by calling {@link #addUiContrastChangeListener}.
+     */
+    public interface UiContrastChangeListener {
+
+        /**
+         * Called when the color contrast enabled state changes.
+         *
+         * @param uiContrast The color contrast as in {@link #getUiContrast}
+         */
+        void onUiContrastChanged(@FloatRange(from = -1.0f, to = 1.0f) float uiContrast);
+    }
+
+    /**
      * Listener for the audio description by default state. To listen for
      * changes to the audio description by default state on the device,
      * implement this interface and register it with the system by calling
@@ -471,6 +508,16 @@
                 updateFocusAppearanceLocked(strokeWidth, color);
             }
         }
+
+        @Override
+        public void setUiContrast(float contrast) {
+            synchronized (mLock) {
+                // if value changed in the settings, update the cached value and notify listeners
+                if (Math.abs(mUiContrast - contrast) < 1e-10) return;
+                mUiContrast = contrast;
+            }
+            mHandler.obtainMessage(MyCallback.MSG_NOTIFY_CONTRAST_CHANGED).sendToTarget();
+        }
     };
 
     /**
@@ -641,7 +688,7 @@
     /**
      * Returns if the high text contrast in the system is enabled.
      * <p>
-     * <strong>Note:</strong> You need to query this only if you application is
+     * <strong>Note:</strong> You need to query this only if your application is
      * doing its own rendering and does not rely on the platform rendering pipeline.
      * </p>
      *
@@ -661,6 +708,24 @@
     }
 
     /**
+     * Returns the color contrast for the user.
+     * <p>
+     * <strong>Note:</strong> You need to query this only if your application is
+     * doing its own rendering and does not rely on the platform rendering pipeline.
+     * </p>
+     * @return The color contrast, float in [-1, 1] where
+     *          0 corresponds to the default contrast
+     *         -1 corresponds to the minimum contrast that the user can set
+     *          1 corresponds to the maximum contrast that the user can set
+     */
+    @FloatRange(from = -1.0f, to = 1.0f)
+    public float getUiContrast() {
+        synchronized (mLock) {
+            return mUiContrast;
+        }
+    }
+
+    /**
      * Sends an {@link AccessibilityEvent}.
      *
      * @param event The event to send.
@@ -1240,6 +1305,35 @@
     }
 
     /**
+     * Registers a {@link UiContrastChangeListener} for the current user.
+     *
+     * @param executor The executor on which the listener should be called back.
+     * @param listener The listener.
+     */
+    public void addUiContrastChangeListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull UiContrastChangeListener listener) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(listener);
+        synchronized (mLock) {
+            mUiContrastChangeListeners.put(listener, executor);
+        }
+    }
+
+    /**
+     * Unregisters a {@link UiContrastChangeListener} for the current user.
+     * If the listener was not registered, does nothing and returns.
+     *
+     * @param listener The listener to unregister.
+     */
+    public void removeUiContrastChangeListener(@NonNull UiContrastChangeListener listener) {
+        Objects.requireNonNull(listener);
+        synchronized (mLock) {
+            mUiContrastChangeListeners.remove(listener);
+        }
+    }
+
+    /**
      * Registers a {@link AudioDescriptionRequestedChangeListener}
      * for changes in the audio description by default state of the system.
      * The value could be read via {@link #isAudioDescriptionRequested}.
@@ -2004,6 +2098,7 @@
             mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
             updateUiTimeout(service.getRecommendedTimeoutMillis());
             updateFocusAppearanceLocked(service.getFocusStrokeWidth(), service.getFocusColor());
+            mUiContrast = service.getUiContrast();
             mService = service;
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
@@ -2082,6 +2177,22 @@
     }
 
     /**
+     * Notifies the registered {@link UiContrastChangeListener}s if the value changed.
+     */
+    private void notifyUiContrastChanged() {
+        final ArrayMap<UiContrastChangeListener, Executor> listeners;
+        synchronized (mLock) {
+            listeners = new ArrayMap<>(mUiContrastChangeListeners);
+        }
+
+        listeners.entrySet().forEach(entry -> {
+            UiContrastChangeListener listener = entry.getKey();
+            Executor executor = entry.getValue();
+            executor.execute(() -> listener.onUiContrastChanged(mUiContrast));
+        });
+    }
+
+    /**
      * Notifies the registered {@link AudioDescriptionStateChangeListener}s.
      */
     private void notifyAudioDescriptionbyDefaultStateChanged() {
@@ -2171,6 +2282,7 @@
 
     private final class MyCallback implements Handler.Callback {
         public static final int MSG_SET_STATE = 1;
+        public static final int MSG_NOTIFY_CONTRAST_CHANGED = 2;
 
         @Override
         public boolean handleMessage(Message message) {
@@ -2182,6 +2294,9 @@
                         setStateLocked(state);
                     }
                 } break;
+                case MSG_NOTIFY_CONTRAST_CHANGED: {
+                    notifyUiContrastChanged();
+                }
             }
             return true;
         }
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 364c7c8..c2d899a 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -118,4 +118,6 @@
 
     // Used by UiAutomation for tests on the InputFilter
     void injectInputEventToInputFilter(in InputEvent event);
+
+    float getUiContrast();
 }
diff --git a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
index 041399c..931f862 100644
--- a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
@@ -31,4 +31,6 @@
     void setRelevantEventTypes(int eventTypes);
 
     void setFocusAppearance(int strokeWidth, int color);
+
+    void setUiContrast(float contrast);
 }
diff --git a/core/java/android/view/inputmethod/TextBoundsInfo.java b/core/java/android/view/inputmethod/TextBoundsInfo.java
index 4e87405..dd05543 100644
--- a/core/java/android/view/inputmethod/TextBoundsInfo.java
+++ b/core/java/android/view/inputmethod/TextBoundsInfo.java
@@ -25,6 +25,7 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.Layout;
 import android.text.SegmentFinder;
 
 import com.android.internal.util.ArrayUtils;
@@ -314,6 +315,554 @@
     }
 
     /**
+     * Return the index of the closest character to the given position.
+     * It's similar to the text layout API {@link Layout#getOffsetForHorizontal(int, float)}.
+     * And it's mainly used to find the cursor index (the index of the character before which the
+     * cursor should be placed) for the given position. It's guaranteed that the returned index is
+     * a grapheme break. Check {@link #getGraphemeSegmentFinder()} for more information.
+     *
+     * <p>It's assumed that the editor lays out text in horizontal lines from top to bottom and each
+     * line is laid out according to the display algorithm specified in
+     * <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm"> unicode bidirectional
+     * algorithm</a>.
+     * </p>
+     *
+     * <p> This method won't check the text ranges whose line information is missing. For example,
+     * the {@link TextBoundsInfo}'s range is from index 5 to 15. If the associated
+     * {@link SegmentFinder} only identifies one line range from 7 to 12. Then this method
+     * won't check the text in the ranges of [5, 7) and [12, 15).
+     * </p>
+     *
+     * @param x the x coordinates of the interested location, in the editor's coordinates.
+     * @param y the y coordinates of the interested location, in the editor's coordinates.
+     * @return the index of the character whose position is closest to the given location. It will
+     * return -1 if it can't find a character.
+     *
+     * @see Layout#getOffsetForHorizontal(int, float)
+     */
+    public int getOffsetForPosition(float x, float y) {
+        final int[] lineRange = new int[2];
+        final RectF lineBounds = new RectF();
+        getLineInfo(y, lineRange, lineBounds);
+        // No line is found, return -1;
+        if (lineRange[0] == -1 || lineRange[1] == -1) return -1;
+        final int lineStart = lineRange[0];
+        final int lineEnd = lineRange[1];
+
+        final boolean lineEndsWithLinefeed =
+                (getCharacterFlags(lineEnd - 1) & FLAG_CHARACTER_LINEFEED) != 0;
+
+        // Consider the following 2 cases:
+        // Case 1:
+        //   Text: "AB\nCD"
+        //   Layout: AB
+        //           CD
+        // Case 2:
+        //   Text: "ABCD"
+        //   Layout: AB
+        //           CD
+        // If user wants to insert a 'X' character at the end of the first line:
+        //   In case 1, 'X' is inserted before the last character '\n'.
+        //   In case 2, 'X' is inserted after the last character 'B'.
+        // So if a line ends with linefeed, it shouldn't check the cursor position after the last
+        // character.
+        final int lineLimit;
+        if (lineEndsWithLinefeed) {
+            lineLimit = lineEnd;
+        } else {
+            lineLimit = lineEnd + 1;
+        }
+        // Point graphemeStart to the start of the first grapheme segment intersects with the line.
+        int graphemeStart = mGraphemeSegmentFinder.nextEndBoundary(lineStart);
+        // The grapheme information is missing.
+        if (graphemeStart == SegmentFinder.DONE) return -1;
+        graphemeStart = mGraphemeSegmentFinder.previousStartBoundary(graphemeStart);
+
+        int target = -1;
+        float minDistance = Float.MAX_VALUE;
+        while (graphemeStart != SegmentFinder.DONE && graphemeStart < lineLimit) {
+            if (graphemeStart >= lineStart) {
+                float cursorPosition = getCursorHorizontalPosition(graphemeStart, lineStart,
+                        lineEnd, lineBounds.left, lineBounds.right);
+                final float distance = Math.abs(cursorPosition - x);
+                if (distance < minDistance) {
+                    minDistance = distance;
+                    target = graphemeStart;
+                }
+            }
+            graphemeStart = mGraphemeSegmentFinder.nextStartBoundary(graphemeStart);
+        }
+
+        return target;
+    }
+
+    /**
+     * Whether the primary position at the given index is the previous character's trailing
+     * position. <br/>
+     *
+     * For LTR character, trailing position is its right edge. For RTL character, trailing position
+     * is its left edge.
+     *
+     * The primary position is defined as the position of a newly inserted character with the
+     * context direction at the given offset. In contrast, the secondary position is the position
+     * of a newly inserted character with the context's opposite direction at the given offset.
+     *
+     * In Android, the trailing position is used for primary position when the direction run after
+     * the given index has a higher level than the current direction run.
+     *
+     * <p>
+     * For example:
+     * (L represents LTR character, and R represents RTL character. The number is the index)
+     * <pre>
+     * input text:          L0 L1 L2 R3 R4 R5 L6 L7 L8
+     * render result:       L0 L1 L2 R5 R4 R3 L6 L7 L8
+     * BiDi Run:            [ Run 0 ][ Run 1 ][ Run 2 ]
+     * BiDi Level:          0  0  0  1  1  1  0  0  0
+     * </pre>
+     *
+     * The index 3 is a BiDi transition point, the cursor can be placed either after L2 or before
+     * R3. Because the bidi level of run 1 is higher than the run 0, this method returns true. And
+     * the cursor should be placed after L2.
+     * <pre>
+     * render result:       L0 L1 L2 R5 R4 R3 L6 L7 L8
+     * position after L2:           |
+     * position before R3:                   |
+     * result position:             |
+     * </pre>
+     *
+     * The index 6 is also a Bidi transition point, the 2 possible cursor positions are exactly the
+     * same as index 3. However, since the bidi level of run 2 is higher than the run 1, this
+     * method returns false. And the cursor should be placed before L6.
+     * <pre>
+     * render result:       L0 L1 L2 R5 R4 R3 L6 L7 L8
+     * position after R5:           |
+     * position before L6:                   |
+     * result position:                      |
+     * </pre>
+     *
+     * This method helps guarantee that the cursor index and the cursor position forms a one to
+     * one relation.
+     * </p>
+     *
+     * @param offset the offset of the character in front of which the cursor is placed. It must be
+     *              the start index of a grapheme. And it must be in the range from lineStart to
+     *              lineEnd. An offset equal to lineEnd is allowed. It indicates that the cursor is
+     *              placed at the end of current line instead of the start of the following line.
+     * @param lineStart the start index of the line that index belongs to, inclusive.
+     * @param lineEnd the end index of the line that index belongs to, exclusive.
+     * @return true if primary position is the trailing position of the previous character.
+     *
+     * @see #getCursorHorizontalPosition(int, int, int, float, float)
+     */
+    private boolean primaryIsTrailingPrevious(int offset, int lineStart, int lineEnd) {
+        final int bidiLevel;
+        if (offset < lineEnd) {
+            bidiLevel = getCharacterBidiLevel(offset);
+        } else {
+            // index equals to lineEnd, use line's BiDi level for the BiDi run.
+            boolean lineIsRtl =
+                    (getCharacterFlags(offset - 1) & FLAG_LINE_IS_RTL) == FLAG_LINE_IS_RTL;
+            bidiLevel = lineIsRtl ? 1 : 0;
+        }
+        final int bidiLevelBefore;
+        if (offset > lineStart) {
+            // Here it assumes index is always the start of a grapheme. And (index - 1) belongs to
+            // the previous grapheme.
+            bidiLevelBefore = getCharacterBidiLevel(offset - 1);
+        } else {
+            // index equals to lineStart, use line's BiDi level for previous BiDi run.
+            boolean lineIsRtl =
+                    (getCharacterFlags(offset) & FLAG_LINE_IS_RTL) == FLAG_LINE_IS_RTL;
+            bidiLevelBefore = lineIsRtl ? 1 : 0;
+        }
+        return bidiLevelBefore < bidiLevel;
+    }
+
+    /**
+     * Returns the x coordinates of the cursor at the given index. (The index of the character
+     * before which the cursor should be placed.)
+     *
+     * @param index the character index before which the cursor is placed. It must be the start
+     *              index of a grapheme. It must be in the range from lineStart to lineEnd.
+     *              An index equal to lineEnd is allowed. It indicates that the cursor is
+     *              placed at the end of current line instead of the start of the following line.
+     * @param lineStart start index of the line that index belongs to, inclusive.
+     * @param lineEnd end index of the line that index belongs, exclusive.
+     * @return the x coordinates of the cursor at the given index,
+     *
+     * @see #primaryIsTrailingPrevious(int, int, int)
+     */
+    private float getCursorHorizontalPosition(int index, int lineStart, int lineEnd,
+            float lineLeft, float lineRight) {
+        Preconditions.checkArgumentInRange(index, lineStart, lineEnd, "index");
+        final boolean lineIsRtl = (getCharacterFlags(lineStart) & FLAG_LINE_IS_RTL) != 0;
+        final boolean isPrimaryIsTrailingPrevious =
+                primaryIsTrailingPrevious(index, lineStart, lineEnd);
+
+        // The index of the character used to compute the cursor position.
+        final int targetIndex;
+        // Whether to use the start position of the character.
+        // For LTR character start is the left edge. For RTL character, start is the right edge.
+        final boolean isStart;
+        if (isPrimaryIsTrailingPrevious) {
+            // (index - 1) belongs to the previous line(if any), return the line start position.
+            if (index <= lineStart) {
+                return lineIsRtl ? lineRight : lineLeft;
+            }
+            targetIndex = index - 1;
+            isStart = false;
+        } else {
+            // index belongs to the next line(if any), return the line end position.
+            if (index >= lineEnd) {
+                return lineIsRtl ? lineLeft : lineRight;
+            }
+            targetIndex = index;
+            isStart = true;
+        }
+
+        // The BiDi level is odd when the character is RTL.
+        final boolean isRtl = (getCharacterBidiLevel(targetIndex) & 1) != 0;
+        final int offset = targetIndex - mStart;
+        // If the character is RTL, the start is the right edge. Otherwise, the start is the
+        // left edge:
+        //  +-----------------------+
+        //  |       | start | end   |
+        //  |-------+-------+-------|
+        //  | RTL   | right | left  |
+        //  |-------+-------+-------|
+        //  | LTR   | left  | right |
+        //  +-------+-------+-------+
+        return (isRtl != isStart) ? mCharacterBounds[4 * offset] : mCharacterBounds[4 * offset + 2];
+    }
+
+    /**
+     * Return the minimal rectangle that contains all the characters in the given range.
+     *
+     * @param start the start index of the given range, inclusive.
+     * @param end the end index of the given range, exclusive.
+     * @param rectF the {@link RectF} to receive the bounds.
+     */
+    private void getBoundsForRange(int start, int end, @NonNull RectF rectF) {
+        Preconditions.checkArgumentInRange(start, mStart, mEnd - 1, "start");
+        Preconditions.checkArgumentInRange(end, start, mEnd, "end");
+        if (end <= start) {
+            rectF.setEmpty();
+            return;
+        }
+
+        rectF.left = Float.MAX_VALUE;
+        rectF.top = Float.MAX_VALUE;
+        rectF.right = Float.MIN_VALUE;
+        rectF.bottom = Float.MIN_VALUE;
+        for (int index = start; index < end; ++index) {
+            final int offset = index - mStart;
+            rectF.left = Math.min(rectF.left, mCharacterBounds[4 * offset]);
+            rectF.top = Math.min(rectF.top, mCharacterBounds[4 * offset + 1]);
+            rectF.right = Math.max(rectF.right, mCharacterBounds[4 * offset + 2]);
+            rectF.bottom = Math.max(rectF.bottom, mCharacterBounds[4 * offset + 3]);
+        }
+    }
+
+    /**
+     * Return the character range and bounds of the closest line to the given {@code y} coordinate,
+     * in the editor's local coordinates.
+     *
+     * If the given y is above the first line or below the last line -1 will be returned for line
+     * start and end.
+     *
+     * This method assumes that the lines are laid out from the top to bottom.
+     *
+     * @param y the y coordinates used to search for the line.
+     * @param characterRange a two element array used to receive the character range of the line.
+     *                       If no valid line is found -1 will be returned for both start and end.
+     * @param bounds {@link RectF} to receive the line bounds result, nullable. If given, it can
+     *                            still be modified even if no valid line is found.
+     */
+    private void getLineInfo(float y, @NonNull int[] characterRange, @Nullable RectF bounds) {
+        characterRange[0] = -1;
+        characterRange[1] = -1;
+
+        // Starting from the first line.
+        int currentLineEnd = mLineSegmentFinder.nextEndBoundary(mStart);
+        if (currentLineEnd == SegmentFinder.DONE) return;
+        int currentLineStart = mLineSegmentFinder.previousStartBoundary(currentLineEnd);
+
+        float top = Float.MAX_VALUE;
+        float bottom = Float.MIN_VALUE;
+        float minDistance = Float.MAX_VALUE;
+        final RectF currentLineBounds = new RectF();
+        while (currentLineStart != SegmentFinder.DONE && currentLineStart < mEnd) {
+            final int lineStartInRange = Math.max(mStart, currentLineStart);
+            final int lineEndInRange = Math.min(mEnd, currentLineEnd);
+            getBoundsForRange(lineStartInRange, lineEndInRange, currentLineBounds);
+
+            top = Math.min(currentLineBounds.top, top);
+            bottom = Math.max(currentLineBounds.bottom, bottom);
+
+            final float distance = verticalDistance(currentLineBounds, y);
+
+            if (distance == 0f) {
+                characterRange[0] = currentLineStart;
+                characterRange[1] = currentLineEnd;
+                if (bounds != null) {
+                    bounds.set(currentLineBounds);
+                }
+                return;
+            }
+
+            if (distance < minDistance) {
+                minDistance = distance;
+                characterRange[0] = currentLineStart;
+                characterRange[1] = currentLineEnd;
+                if (bounds != null) {
+                    bounds.set(currentLineBounds);
+                }
+            }
+            if (y < bounds.top) break;
+            currentLineStart = mLineSegmentFinder.nextStartBoundary(currentLineStart);
+            currentLineEnd = mLineSegmentFinder.nextEndBoundary(currentLineEnd);
+        }
+
+        // y is above the first line or below the last line. The founded line is still invalid,
+        // clear the result.
+        if (y < top || y > bottom) {
+            characterRange[0] = -1;
+            characterRange[1] = -1;
+            if (bounds != null) {
+                bounds.setEmpty();
+            }
+        }
+    }
+
+    /**
+     * Finds the range of text which is inside the specified rectangle area. This method is a
+     * counterpart of the
+     * {@link Layout#getRangeForRect(RectF, SegmentFinder, Layout.TextInclusionStrategy)}.
+     *
+     * <p>It's assumed that the editor lays out text in horizontal lines from top to bottom
+     * and each line is laid out according to the display algorithm specified in
+     * <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm"> unicode bidirectional
+     * algorithm</a>.
+     * </p>
+     *
+     * <p> This method won't check the text ranges whose line information is missing. For example,
+     * the {@link TextBoundsInfo}'s range is from index 5 to 15. If the associated line
+     * {@link SegmentFinder} only identifies one line range from 7 to 12. Then this method
+     * won't check the text in the ranges of [5, 7) and [12, 15).
+     * </p>
+     *
+     * @param area area for which the text range will be found
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered as a
+     *     text segment
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *          specified area
+     * @return the text range stored in a two element int array. The first element is the
+     * start (inclusive) of the text range, and the second element is the end (exclusive) character
+     * offsets of the text range, or null if there are no text segments inside the area.
+     *
+     * @see Layout#getRangeForRect(RectF, SegmentFinder, Layout.TextInclusionStrategy)
+     */
+    @Nullable
+    public int[] getRangeForRect(@NonNull RectF area, @NonNull SegmentFinder segmentFinder,
+            @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+        int lineEnd = mLineSegmentFinder.nextEndBoundary(mStart);
+        // Line information is missing.
+        if (lineEnd == SegmentFinder.DONE) return null;
+        int lineStart = mLineSegmentFinder.previousStartBoundary(lineEnd);
+
+        int start = -1;
+        while (lineStart != SegmentFinder.DONE && start == -1) {
+            start = getStartForRectWithinLine(lineStart, lineEnd, area, segmentFinder,
+                    inclusionStrategy);
+            lineStart = mLineSegmentFinder.nextStartBoundary(lineStart);
+            lineEnd = mLineSegmentFinder.nextEndBoundary(lineEnd);
+        }
+
+        // Can't find the start index; the specified contains no valid segment.
+        if (start == -1) return null;
+
+        lineStart = mLineSegmentFinder.previousStartBoundary(mEnd);
+        // Line information is missing.
+        if (lineStart == SegmentFinder.DONE) return null;
+        lineEnd = mLineSegmentFinder.nextEndBoundary(lineStart);
+        int end = -1;
+        while (lineEnd > start && end == -1) {
+            end = getEndForRectWithinLine(lineStart, lineEnd, area, segmentFinder,
+                    inclusionStrategy);
+            lineStart = mLineSegmentFinder.previousStartBoundary(lineStart);
+            lineEnd = mLineSegmentFinder.previousEndBoundary(lineEnd);
+        }
+
+        // We've already found start, end is guaranteed to be found at this point.
+        start = segmentFinder.previousStartBoundary(start + 1);
+        end = segmentFinder.nextEndBoundary(end - 1);
+        return new int[] { start, end };
+    }
+
+    /**
+     * Find the start character index of the first text segments within a line inside the specified
+     * {@code area}.
+     *
+     * @param lineStart the start of this line, inclusive .
+     * @param lineEnd the end of this line, exclusive.
+     * @param area the area inside which the text segments will be found.
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered a
+     *                      text segment.
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *                          specified area.
+     * @return the start index of the first segment in the area.
+     */
+    private int getStartForRectWithinLine(int lineStart, int lineEnd, @NonNull RectF area,
+            @NonNull SegmentFinder segmentFinder,
+            @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+        if (lineStart >= lineEnd) return -1;
+
+        int runStart = lineStart;
+        int runLevel = -1;
+        // Check the BiDi runs and search for the start index.
+        for (int index = lineStart; index < lineEnd; ++index) {
+            final int level = getCharacterBidiLevel(index);
+            if (level != runLevel) {
+                final int start = getStartForRectWithinRun(runStart, index, area, segmentFinder,
+                        inclusionStrategy);
+                if (start != -1) {
+                    return start;
+                }
+
+                runStart = index;
+                runLevel = level;
+            }
+        }
+        return getStartForRectWithinRun(runStart, lineEnd, area, segmentFinder, inclusionStrategy);
+    }
+
+    /**
+     * Find the start character index of the first text segments within the directional run inside
+     * the specified {@code area}.
+     *
+     * @param runStart the start of this directional run, inclusive.
+     * @param runEnd the end of this directional run, exclusive.
+     * @param area the area inside which the text segments will be found.
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered a
+     *                      text segment.
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *                          specified area.
+     * @return the start index of the first segment in the area.
+     */
+    private int getStartForRectWithinRun(int runStart, int runEnd, @NonNull RectF area,
+            @NonNull SegmentFinder segmentFinder,
+            @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+        if (runStart >= runEnd) return -1;
+
+        int segmentEndOffset = segmentFinder.nextEndBoundary(runStart);
+        // No segment is found in run.
+        if (segmentEndOffset == SegmentFinder.DONE) return -1;
+        int segmentStartOffset = segmentFinder.previousStartBoundary(segmentEndOffset);
+
+        final RectF segmentBounds = new RectF();
+        while (segmentStartOffset != SegmentFinder.DONE && segmentStartOffset < runEnd) {
+            final int start = Math.max(runStart, segmentStartOffset);
+            final int end = Math.min(runEnd, segmentEndOffset);
+            getBoundsForRange(start, end, segmentBounds);
+            // Find the first segment inside the area, return the start.
+            if (inclusionStrategy.isSegmentInside(segmentBounds, area)) return start;
+
+            segmentStartOffset = segmentFinder.nextStartBoundary(segmentStartOffset);
+            segmentEndOffset = segmentFinder.nextEndBoundary(segmentEndOffset);
+        }
+        return -1;
+    }
+
+    /**
+     * Find the end character index of the last text segments within a line inside the specified
+     * {@code area}.
+     *
+     * @param lineStart the start of this line, inclusive .
+     * @param lineEnd the end of this line, exclusive.
+     * @param area the area inside which the text segments will be found.
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered a
+     *                      text segment.
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *                          specified area.
+     * @return the end index of the last segment in the area.
+     */
+    private int getEndForRectWithinLine(int lineStart, int lineEnd, @NonNull RectF area,
+            @NonNull SegmentFinder segmentFinder,
+            @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+        if (lineStart >= lineEnd) return -1;
+        lineStart = Math.max(lineStart, mStart);
+        lineEnd = Math.min(lineEnd, mEnd);
+
+        // The exclusive run end index.
+        int runEnd = lineEnd;
+        int runLevel = -1;
+        // Check the BiDi runs backwards and search for the end index.
+        for (int index = lineEnd - 1; index >= lineStart; --index) {
+            final int level = getCharacterBidiLevel(index);
+            if (level != runLevel) {
+                final int end = getEndForRectWithinRun(index + 1, runEnd, area, segmentFinder,
+                        inclusionStrategy);
+                if (end != -1) return end;
+
+                runEnd = index + 1;
+                runLevel = level;
+            }
+        }
+        return getEndForRectWithinRun(lineStart, runEnd, area, segmentFinder, inclusionStrategy);
+    }
+
+    /**
+     * Find the end character index of the last text segments within the directional run inside the
+     * specified {@code area}.
+     *
+     * @param runStart the start of this directional run, inclusive.
+     * @param runEnd the end of this directional run, exclusive.
+     * @param area the area inside which the text segments will be found.
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered a
+     *                      text segment.
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *                          specified area.
+     * @return the end index of the last segment in the area.
+     */
+    private int getEndForRectWithinRun(int runStart, int runEnd, @NonNull RectF area,
+            @NonNull SegmentFinder segmentFinder,
+            @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+        if (runStart >= runEnd) return -1;
+
+        int segmentStart = segmentFinder.previousStartBoundary(runEnd);
+        // No segment is found before the runEnd.
+        if (segmentStart == SegmentFinder.DONE) return -1;
+        int segmentEnd = segmentFinder.nextEndBoundary(segmentStart);
+
+        final RectF segmentBounds = new RectF();
+        while (segmentEnd != SegmentFinder.DONE && segmentEnd > runStart) {
+            final int start = Math.max(runStart, segmentStart);
+            final int end = Math.min(runEnd, segmentEnd);
+            getBoundsForRange(start, end, segmentBounds);
+            // Find the last segment inside the area, return the end.
+            if (inclusionStrategy.isSegmentInside(segmentBounds, area)) return end;
+
+            segmentStart = segmentFinder.previousStartBoundary(segmentStart);
+            segmentEnd = segmentFinder.previousEndBoundary(segmentEnd);
+        }
+        return -1;
+    }
+
+    /**
+     * Get the vertical distance from the {@code pointF} to the {@code rectF}. It's useful to find
+     * the corresponding line for a given point.
+     */
+    private static float verticalDistance(@NonNull RectF rectF, float y) {
+        if (rectF.top <= y && y < rectF.bottom) {
+            return 0f;
+        }
+        if (y < rectF.top) {
+            return rectF.top - y;
+        }
+        return y - rectF.bottom;
+    }
+
+    /**
      * Describe the kinds of special objects contained in this Parcelable
      * instance's marshaled representation. For example, if the object will
      * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 3950739..250652a 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -40,64 +40,65 @@
  * mechanism so each sync implementation doesn't need to handle it themselves. The SurfaceSyncGroup
  * class is used the following way.
  *
- * 1. {@link #SurfaceSyncGroup()} constructor is called
- * 2. {@link #addToSync(SyncTarget)} is called for every SyncTarget object that wants to be
- * included in the sync. If the addSync is called for an {@link AttachedSurfaceControl} or
- * {@link SurfaceView} it needs to be called on the UI thread. When addToSync is called, it's
+ * 1. {@link #addToSync(SurfaceSyncGroup, boolean)} is called for every SurfaceSyncGroup object that
+ * wants to be included in the sync. If the addSync is called for an {@link AttachedSurfaceControl}
+ * or {@link SurfaceView} it needs to be called on the UI thread. When addToSync is called, it's
  * guaranteed that any UI updates that were requested before addToSync but after the last frame
  * drew, will be included in the sync.
- * 3. {@link #markSyncReady()} should be called when all the {@link SyncTarget}s have been added
- * to the SurfaceSyncGroup. At this point, the SurfaceSyncGroup is closed and no more SyncTargets
- * can be added to it.
- * 4. The SurfaceSyncGroup will gather the data for each SyncTarget using the steps described below.
- * When all the SyncTargets have finished, the syncRequestComplete will be invoked and the
- * transaction will either be applied or sent to the caller. In most cases, only the
- * SurfaceSyncGroup  should be handling the Transaction object directly. However, there are some
+ * 2. {@link #markSyncReady()} should be called when all the {@link SurfaceSyncGroup}s have been
+ * added to the SurfaceSyncGroup. At this point, the SurfaceSyncGroup is closed and no more
+ * SurfaceSyncGroups can be added to it.
+ * 3. The SurfaceSyncGroup will gather the data for each SurfaceSyncGroup using the steps described
+ * below. When all the SurfaceSyncGroups have finished, the syncRequestComplete will be invoked and
+ * the transaction will either be applied or sent to the caller. In most cases, only the
+ * SurfaceSyncGroup should be handling the Transaction object directly. However, there are some
  * cases where the framework needs to send the Transaction elsewhere, like in ViewRootImpl, so that
  * option is provided.
  *
- * The following is what happens within the {@link SurfaceSyncGroup}
- * 1. Each SyncTarget will get a {@link SyncTarget#onAddedToSyncGroup} callback that contains a
- * {@link TransactionReadyCallback}.
- * 2. Each {@link SyncTarget} needs to invoke
- * {@link TransactionReadyCallback#onTransactionReady(Transaction)}. This makes sure the
- * SurfaceSyncGroup knows when the SyncTarget is complete, allowing the SurfaceSyncGroup to get the
- * Transaction that contains the buffer.
- * 3. When the final TransactionReadyCallback finishes for the SurfaceSyncGroup, in most cases the
- * transaction is applied and then the sync complete callbacks are invoked, letting the callers know
- * the sync is now complete.
+ * The following is what happens within the {@link android.window.SurfaceSyncGroup}
+ * 1. Each SurfaceSyncGroup will get a
+ * {@link SurfaceSyncGroup#onAddedToSyncGroup(SurfaceSyncGroup, TransactionReadyCallback)} callback
+ * that contains a  {@link TransactionReadyCallback}.
+ * 2. Each {@link SurfaceSyncGroup} needs to invoke
+ * {@link SurfaceSyncGroup#onTransactionReady(Transaction)}.
+ * This makes sure the parent SurfaceSyncGroup knows when the SurfaceSyncGroup is complete, allowing
+ * the parent SurfaceSyncGroup to get the Transaction that contains the changes for the child
+ * SurfaceSyncGroup
+ * 3. When the final TransactionReadyCallback finishes for the child SurfaceSyncGroups, the
+ * transaction is either applied if it's the top most parent or the final merged transaction is sent
+ * up to its parent SurfaceSyncGroup.
  *
  * @hide
  */
-public final class SurfaceSyncGroup {
+public class SurfaceSyncGroup {
     private static final String TAG = "SurfaceSyncGroup";
     private static final boolean DEBUG = false;
 
     private static Supplier<Transaction> sTransactionFactory = Transaction::new;
 
     /**
-     * Class that collects the {@link SyncTarget}s and notifies when all the surfaces have
+     * Class that collects the {@link SurfaceSyncGroup}s and notifies when all the surfaces have
      * a frame ready.
      */
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
-    private final Set<Integer> mPendingSyncs = new ArraySet<>();
+    private final Set<TransactionReadyCallback> mPendingSyncs = new ArraySet<>();
     @GuardedBy("mLock")
     private final Transaction mTransaction = sTransactionFactory.get();
     @GuardedBy("mLock")
     private boolean mSyncReady;
 
     @GuardedBy("mLock")
-    private Consumer<Transaction> mSyncRequestCompleteCallback;
-
-    @GuardedBy("mLock")
-    private final Set<SurfaceSyncGroup> mMergedSyncGroups = new ArraySet<>();
-
-    @GuardedBy("mLock")
     private boolean mFinished;
 
     @GuardedBy("mLock")
+    private TransactionReadyCallback mTransactionReadyCallback;
+
+    @GuardedBy("mLock")
+    private SurfaceSyncGroup mParentSyncGroup;
+
+    @GuardedBy("mLock")
     private final ArraySet<Pair<Executor, Runnable>> mSyncCompleteCallbacks = new ArraySet<>();
 
     /**
@@ -122,16 +123,16 @@
     /**
      * Creates a sync.
      *
-     * @param syncRequestComplete The complete callback that contains the syncId and transaction
-     *                            with all the sync data merged. The Transaction passed back can be
-     *                            null.
+     * @param transactionReadyCallback The complete callback that contains the syncId and
+     *                                 transaction with all the sync data merged. The Transaction
+     *                                 passed back can be null.
      *
      * NOTE: Only should be used by ViewRootImpl
      * @hide
      */
-    public SurfaceSyncGroup(Consumer<Transaction> syncRequestComplete) {
-        mSyncRequestCompleteCallback = transaction -> {
-            syncRequestComplete.accept(transaction);
+    public SurfaceSyncGroup(Consumer<Transaction> transactionReadyCallback) {
+        mTransactionReadyCallback = transaction -> {
+            transactionReadyCallback.accept(transaction);
             synchronized (mLock) {
                 for (Pair<Executor, Runnable> callback : mSyncCompleteCallbacks) {
                     callback.first.execute(callback.second);
@@ -157,6 +158,31 @@
     }
 
     /**
+     * Mark the sync set as ready to complete. No more data can be added to the specified
+     * syncId.
+     * Once the sync set is marked as ready, it will be able to complete once all Syncables in the
+     * set have completed their sync
+     */
+    public void markSyncReady() {
+        onTransactionReady(null);
+    }
+
+    /**
+     * Similar to {@link #markSyncReady()}, but a transaction is passed in to merge with the
+     * SurfaceSyncGroup.
+     * @param t The transaction that merges into the main Transaction for the SurfaceSyncGroup.
+     */
+    public void onTransactionReady(@Nullable Transaction t) {
+        synchronized (mLock) {
+            mSyncReady = true;
+            if (t != null) {
+                mTransaction.merge(t);
+            }
+            checkIfSyncIsComplete();
+        }
+    }
+
+    /**
      * Add a SurfaceView to a sync set. This is different than
      * {@link #addToSync(AttachedSurfaceControl)} because it requires the caller to notify the start
      * and finish drawing in order to sync.
@@ -171,7 +197,13 @@
     @UiThread
     public boolean addToSync(SurfaceView surfaceView,
             Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
-        return addToSync(new SurfaceViewSyncTarget(surfaceView, frameCallbackConsumer));
+        SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup();
+        if (addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */)) {
+            frameCallbackConsumer.accept(
+                    () -> surfaceView.syncNextFrame(surfaceSyncGroup::onTransactionReady));
+            return true;
+        }
+        return false;
     }
 
     /**
@@ -185,29 +217,38 @@
         if (viewRoot == null) {
             return false;
         }
-        SyncTarget syncTarget = viewRoot.getSyncTarget();
-        if (syncTarget == null) {
+        SurfaceSyncGroup surfaceSyncGroup = viewRoot.getOrCreateSurfaceSyncGroup();
+        if (surfaceSyncGroup == null) {
             return false;
         }
-        return addToSync(syncTarget);
+        return addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */);
     }
 
     /**
-     * Add a {@link SyncTarget} to a sync set. The sync set will wait for all
+     * Add a {@link SurfaceSyncGroup} to a sync set. The sync set will wait for all
      * SyncableSurfaces to complete before notifying.
      *
-     * @param syncTarget A SyncTarget that implements how to handle syncing transactions.
-     * @return true if the SyncTarget was successfully added to the SyncGroup, false otherwise.
+     * @param surfaceSyncGroup A SyncableSurface that implements how to handle syncing
+     *                         buffers.
+     * @return true if the SyncGroup was successfully added to the current SyncGroup, false
+     * otherwise.
      */
-    public boolean addToSync(SyncTarget syncTarget) {
+    public boolean addToSync(SurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge) {
         TransactionReadyCallback transactionReadyCallback = new TransactionReadyCallback() {
             @Override
             public void onTransactionReady(Transaction t) {
                 synchronized (mLock) {
                     if (t != null) {
+                        // When an older parent sync group is added due to a child syncGroup getting
+                        // added to multiple groups, we need to maintain merge order so the older
+                        // parentSyncGroup transactions are overwritten by anything in the newer
+                        // parentSyncGroup.
+                        if (parentSyncGroupMerge) {
+                            t.merge(mTransaction);
+                        }
                         mTransaction.merge(t);
                     }
-                    mPendingSyncs.remove(hashCode());
+                    mPendingSyncs.remove(this);
                     checkIfSyncIsComplete();
                 }
             }
@@ -216,48 +257,16 @@
         synchronized (mLock) {
             if (mSyncReady) {
                 Log.e(TAG, "Sync " + this + " was already marked as ready. No more "
-                        + "SyncTargets can be added.");
+                        + "SurfaceSyncGroups can be added.");
                 return false;
             }
-            mPendingSyncs.add(transactionReadyCallback.hashCode());
+            mPendingSyncs.add(transactionReadyCallback);
         }
-        syncTarget.onAddedToSyncGroup(this, transactionReadyCallback);
+        surfaceSyncGroup.onAddedToSyncGroup(this, transactionReadyCallback);
         return true;
     }
 
     /**
-     * Mark the sync set as ready to complete. No more data can be added to the specified
-     * syncId.
-     * Once the sync set is marked as ready, it will be able to complete once all Syncables in the
-     * set have completed their sync
-     */
-    public void markSyncReady() {
-        synchronized (mLock) {
-            mSyncReady = true;
-            checkIfSyncIsComplete();
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void checkIfSyncIsComplete() {
-        if (!mSyncReady || !mPendingSyncs.isEmpty() || !mMergedSyncGroups.isEmpty()) {
-            if (DEBUG) {
-                Log.d(TAG, "Syncable is not complete. mSyncReady=" + mSyncReady
-                        + " mPendingSyncs=" + mPendingSyncs.size() + " mergedSyncs="
-                        + mMergedSyncGroups.size());
-            }
-            return;
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "Successfully finished sync id=" + this);
-        }
-
-        mSyncRequestCompleteCallback.accept(mTransaction);
-        mFinished = true;
-    }
-
-    /**
      * Add a Transaction to this sync set. This allows the caller to provide other info that
      * should be synced with the transactions.
      */
@@ -267,99 +276,68 @@
         }
     }
 
-    private void updateCallback(Consumer<Transaction> transactionConsumer) {
+    @GuardedBy("mLock")
+    private void checkIfSyncIsComplete() {
+        if (mFinished) {
+            if (DEBUG) {
+                Log.d(TAG, "SurfaceSyncGroup=" + this + " is already complete");
+            }
+            return;
+        }
+
+        if (!mSyncReady || !mPendingSyncs.isEmpty()) {
+            if (DEBUG) {
+                Log.d(TAG, "SurfaceSyncGroup=" + this + " is not complete. mSyncReady="
+                        + mSyncReady + " mPendingSyncs=" + mPendingSyncs.size());
+            }
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "Successfully finished sync id=" + this);
+        }
+        mTransactionReadyCallback.onTransactionReady(mTransaction);
+        mFinished = true;
+    }
+
+    private void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+            TransactionReadyCallback transactionReadyCallback) {
+        boolean finished = false;
         synchronized (mLock) {
             if (mFinished) {
-                Log.e(TAG, "Attempting to merge SyncGroup " + this + " when sync is"
-                        + " already complete");
-                transactionConsumer.accept(null);
-            }
-
-            final Consumer<Transaction> oldCallback = mSyncRequestCompleteCallback;
-            mSyncRequestCompleteCallback = transaction -> {
-                oldCallback.accept(null);
-                transactionConsumer.accept(transaction);
-            };
-        }
-    }
-
-    /**
-     * Merge a SyncGroup into this SyncGroup. Since SyncGroups could still have pending SyncTargets,
-     * we need to make sure those can still complete before the mergeTo SyncGroup is considered
-     * complete.
-     *
-     * We keep track of all the merged SyncGroups until they are marked as done, and then they
-     * are removed from the set. This SyncGroup is not considered done until all the merged
-     * SyncGroups are done.
-     *
-     * When the merged SyncGroup is complete, it will invoke the original syncRequestComplete
-     * callback but send an empty transaction to ensure the changes are applied early. This
-     * is needed in case the original sync is relying on the callback to continue processing.
-     *
-     * @param otherSyncGroup The other SyncGroup to merge into this one.
-     */
-    public void merge(SurfaceSyncGroup otherSyncGroup) {
-        synchronized (mLock) {
-            mMergedSyncGroups.add(otherSyncGroup);
-        }
-        otherSyncGroup.updateCallback(transaction -> {
-            synchronized (mLock) {
-                mMergedSyncGroups.remove(otherSyncGroup);
-                if (transaction != null) {
-                    mTransaction.merge(transaction);
+                finished = true;
+            } else {
+                // If this SurfaceSyncGroup was already added to a different SurfaceSyncGroup, we
+                // need to combine everything. We can add the old SurfaceSyncGroup parent to the new
+                // parent so the new parent doesn't complete until the old parent does.
+                // Additionally, the old parent will not get the final transaction object and
+                // instead will send it to the new parent, ensuring that any other SurfaceSyncGroups
+                // from the original parent are also combined with the new parent SurfaceSyncGroup.
+                if (mParentSyncGroup != null) {
+                    Log.d(TAG, "Already part of sync group " + mParentSyncGroup + " " + this);
+                    parentSyncGroup.addToSync(mParentSyncGroup, true /* parentSyncGroupMerge */);
                 }
-                checkIfSyncIsComplete();
+                mParentSyncGroup = parentSyncGroup;
+                final TransactionReadyCallback lastCallback = mTransactionReadyCallback;
+                mTransactionReadyCallback = t -> {
+                    lastCallback.onTransactionReady(null);
+                    transactionReadyCallback.onTransactionReady(t);
+                };
             }
-        });
-    }
-
-    /**
-     * Wrapper class to help synchronize SurfaceViews
-     */
-    private static class SurfaceViewSyncTarget implements SyncTarget {
-        private final SurfaceView mSurfaceView;
-        private final Consumer<SurfaceViewFrameCallback> mFrameCallbackConsumer;
-
-        SurfaceViewSyncTarget(SurfaceView surfaceView,
-                Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
-            mSurfaceView = surfaceView;
-            mFrameCallbackConsumer = frameCallbackConsumer;
         }
 
-        @Override
-        public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
-                TransactionReadyCallback transactionReadyCallback) {
-            mFrameCallbackConsumer.accept(
-                    () -> mSurfaceView.syncNextFrame(transactionReadyCallback::onTransactionReady));
+        // Invoke the callback outside of the lock when the SurfaceSyncGroup being added was already
+        // complete.
+        if (finished) {
+            transactionReadyCallback.onTransactionReady(null);
         }
     }
-
-    /**
-     * A SyncTarget that can be added to a sync set.
-     */
-    public interface SyncTarget {
-        /**
-         * Called when the SyncTarget has been added to a SyncGroup as is ready to begin handing a
-         * sync request. When invoked, the implementor is required to call
-         * {@link TransactionReadyCallback#onTransactionReady(Transaction)} in order for this
-         * SurfaceSyncGroup to fully complete.
-         *
-         * Always invoked on the thread that initiated the call to {@link #addToSync(SyncTarget)}
-         *
-         * @param parentSyncGroup The sync group this target has been added to.
-         * @param transactionReadyCallback A TransactionReadyCallback that the caller must invoke
-         *                                 onTransactionReady
-         */
-        void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
-                TransactionReadyCallback transactionReadyCallback);
-    }
-
     /**
      * Interface so the SurfaceSyncer can know when it's safe to start and when everything has been
      * completed. The caller should invoke the calls when the rendering has started and finished a
      * frame.
      */
-    public interface TransactionReadyCallback {
+    private interface TransactionReadyCallback {
         /**
          * Invoked when the transaction is ready to sync.
          *
diff --git a/core/java/android/window/TaskConstants.java b/core/java/android/window/TaskConstants.java
new file mode 100644
index 0000000..c403840
--- /dev/null
+++ b/core/java/android/window/TaskConstants.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 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 android.window;
+
+import android.annotation.IntDef;
+
+/**
+ * Holds constants related to task managements but not suitable in {@code TaskOrganizer}.
+ * @hide
+ */
+public class TaskConstants {
+
+    /**
+     * Sizes of a z-order region assigned to child layers of task layers. Components are allowed to
+     * use all values in [assigned value, assigned value + region size).
+     * @hide
+     */
+    public static final int TASK_CHILD_LAYER_REGION_SIZE = 10000;
+
+    /**
+     * Indicates system responding to task drag resizing while app content isn't updated.
+     * @hide
+     */
+    public static final int TASK_CHILD_LAYER_TASK_BACKGROUND = -3 * TASK_CHILD_LAYER_REGION_SIZE;
+
+    /**
+     * Provides solid color letterbox background or blur effect and dimming for the wallpaper
+     * letterbox background. It also listens to touches for double tap gesture for repositioning
+     * letterbox.
+     * @hide
+     */
+    public static final int TASK_CHILD_LAYER_LETTERBOX_BACKGROUND =
+            -2 * TASK_CHILD_LAYER_REGION_SIZE;
+
+    /**
+     * When a unresizable app is moved in the different configuration, a restart button appears
+     * allowing to adapt (~resize) app to the new configuration mocks.
+     * @hide
+     */
+    public static final int TASK_CHILD_LAYER_SIZE_COMPAT_RESTART_BUTTON =
+            TASK_CHILD_LAYER_REGION_SIZE;
+
+    /**
+     * Shown the first time an app is opened in size compat mode in landscape.
+     * @hide
+     */
+    public static final int TASK_CHILD_LAYER_LETTERBOX_EDUCATION = 2 * TASK_CHILD_LAYER_REGION_SIZE;
+
+    /**
+     * Captions, window frames and resize handlers around task windows.
+     * @hide
+     */
+    public static final int TASK_CHILD_LAYER_WINDOW_DECORATIONS = 3 * TASK_CHILD_LAYER_REGION_SIZE;
+
+    /**
+     * Overlays the task when going into PIP w/ gesture navigation.
+     * @hide
+     */
+    public static final int TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY =
+            4 * TASK_CHILD_LAYER_REGION_SIZE;
+
+    /**
+     * Allows other apps to add overlays on the task (i.e. game dashboard)
+     * @hide
+     */
+    public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 5 * TASK_CHILD_LAYER_REGION_SIZE;
+
+    /**
+     * Legacy machanism to force an activity to the top of the task (i.e. for work profile
+     * comfirmation).
+     * @hide
+     */
+    public static final int TASK_CHILD_LAYER_TASK_OVERLAY_ACTIVITIES =
+            6 * TASK_CHILD_LAYER_REGION_SIZE;
+
+    /**
+     * Z-orders of task child layers other than activities, task fragments and layers interleaved
+     * with them, e.g. IME windows. [-10000, 10000) is reserved for these layers.
+     * @hide
+     */
+    @IntDef({
+            TASK_CHILD_LAYER_TASK_BACKGROUND,
+            TASK_CHILD_LAYER_LETTERBOX_BACKGROUND,
+            TASK_CHILD_LAYER_SIZE_COMPAT_RESTART_BUTTON,
+            TASK_CHILD_LAYER_LETTERBOX_EDUCATION,
+            TASK_CHILD_LAYER_WINDOW_DECORATIONS,
+            TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY,
+            TASK_CHILD_LAYER_TASK_OVERLAY,
+            TASK_CHILD_LAYER_TASK_OVERLAY_ACTIVITIES
+    })
+    public @interface TaskChildLayer {}
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 9f23f24..b9ca557 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -252,11 +252,13 @@
      * @param sharedMemory The unrestricted data blob to provide to the
      * {@link HotwordDetectionService}. Use this to provide the hotword models data or other
      * such data to the trusted process.
+     * @param token Use this to identify which detector calls this method.
      */
     @EnforcePermission("MANAGE_HOTWORD_DETECTION")
     void updateState(
             in PersistableBundle options,
-            in SharedMemory sharedMemory);
+            in SharedMemory sharedMemory,
+            in IBinder token);
 
     /**
      * Set configuration and pass read-only data to hotword detection service when creating
@@ -272,6 +274,7 @@
      * @param sharedMemory The unrestricted data blob to provide to the
      * {@link HotwordDetectionService}. Use this to provide the hotword models data or other
      * such data to the trusted process.
+     * @param token Use this to identify which detector calls this method.
      * @param callback Use this to report {@link HotwordDetectionService} status.
      * @param detectorType Indicate which detector is used.
      */
@@ -280,10 +283,18 @@
             in Identity originatorIdentity,
             in PersistableBundle options,
             in SharedMemory sharedMemory,
+            in IBinder token,
             in IHotwordRecognitionStatusCallback callback,
             int detectorType);
 
     /**
+     * Destroy the detector callback.
+     *
+     * @param token Indicate which callback will be destroyed.
+     */
+    void destroyDetector(in IBinder token);
+
+    /**
      * Requests to shutdown hotword detection service.
      */
     void shutdownHotwordDetectionService();
@@ -298,6 +309,7 @@
         in ParcelFileDescriptor audioStream,
         in AudioFormat audioFormat,
         in PersistableBundle options,
+        in IBinder token,
         in IMicrophoneHotwordDetectionVoiceInteractionCallback callback);
 
     /**
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 75f0bf5..7bd6ec8 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -780,17 +780,17 @@
         // 2. The returned string should be the same with the name defined in atoms.proto.
         switch (cujType) {
             case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
-                return "SHADE_EXPAND_COLLAPSE";
+                return "NOTIFICATION_SHADE_EXPAND_COLLAPSE";
             case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
-                return "SHADE_SCROLL_FLING";
+                return "NOTIFICATION_SHADE_SCROLL_FLING";
             case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
-                return "SHADE_ROW_EXPAND";
+                return "NOTIFICATION_SHADE_ROW_EXPAND";
             case CUJ_NOTIFICATION_SHADE_ROW_SWIPE:
-                return "SHADE_ROW_SWIPE";
+                return "NOTIFICATION_SHADE_ROW_SWIPE";
             case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE:
-                return "SHADE_QS_EXPAND_COLLAPSE";
+                return "NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE";
             case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE:
-                return "SHADE_QS_SCROLL_SWIPE";
+                return "NOTIFICATION_SHADE_QS_SCROLL_SWIPE";
             case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS:
                 return "LAUNCHER_APP_LAUNCH_FROM_RECENTS";
             case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON:
@@ -880,9 +880,9 @@
             case CUJ_SPLIT_SCREEN_EXIT:
                 return "SPLIT_SCREEN_EXIT";
             case CUJ_LOCKSCREEN_LAUNCH_CAMERA:
-                return "CUJ_LOCKSCREEN_LAUNCH_CAMERA";
+                return "LOCKSCREEN_LAUNCH_CAMERA";
             case CUJ_SPLIT_SCREEN_RESIZE:
-                return "CUJ_SPLIT_SCREEN_RESIZE";
+                return "SPLIT_SCREEN_RESIZE";
             case CUJ_SETTINGS_SLIDER:
                 return "SETTINGS_SLIDER";
             case CUJ_TAKE_SCREENSHOT:
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index c3361a2..66d64c4 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -49,6 +49,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.ConcurrentModificationException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -163,10 +164,6 @@
      */
     private int mCurrentParcelEnd;
     /**
-     * When iterating history files, the current record count.
-     */
-    private int mRecordCount = 0;
-    /**
      * Used when BatteryStatsImpl object is created from deserialization of a parcel,
      * such as Settings app or checkin file, to iterate over history parcels.
      */
@@ -199,10 +196,8 @@
     private boolean mMeasuredEnergyHeaderWritten = false;
     private boolean mCpuUsageHeaderWritten = false;
     private final VarintParceler mVarintParceler = new VarintParceler();
-
     private byte mLastHistoryStepLevel = 0;
-
-    private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
+    private boolean mMutable = true;
 
     /**
      * A delegate responsible for computing additional details for a step in battery history.
@@ -493,25 +488,21 @@
      * @return always return true.
      */
     public BatteryStatsHistoryIterator iterate() {
-        mRecordCount = 0;
         mCurrentFileIndex = 0;
         mCurrentParcel = null;
         mCurrentParcelEnd = 0;
         mParcelIndex = 0;
-        mBatteryStatsHistoryIterator = new BatteryStatsHistoryIterator(this);
-        return mBatteryStatsHistoryIterator;
+        mMutable = false;
+        return new BatteryStatsHistoryIterator(this);
     }
 
     /**
      * Finish iterating history files and history buffer.
      */
-    void finishIteratingHistory() {
+    void iteratorFinished() {
         // setDataPosition so mHistoryBuffer Parcel can be written.
         mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
-        mBatteryStatsHistoryIterator = null;
-        if (DEBUG) {
-            Slog.d(TAG, "Battery history records iterated: " + mRecordCount);
-        }
+        mMutable = true;
     }
 
     /**
@@ -519,17 +510,11 @@
      * history file, when reached the mActiveFile (highest numbered history file), do not read from
      * mActiveFile, read from history buffer instead because the buffer has more updated data.
      *
-     * @param out a history item.
      * @return The parcel that has next record. null if finished all history files and history
      * buffer
      */
-    public Parcel getNextParcel(HistoryItem out) {
-        if (mRecordCount == 0) {
-            // reset out if it is the first record.
-            out.clear();
-        }
-        ++mRecordCount;
-
+    @Nullable
+    public Parcel getNextParcel() {
         // First iterate through all records in current parcel.
         if (mCurrentParcel != null) {
             if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
@@ -1270,6 +1255,10 @@
             return;
         }
 
+        if (!mMutable) {
+            throw new ConcurrentModificationException("Battery history is not writable");
+        }
+
         final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
         final int diffStates = mHistoryLastWritten.states ^ (cur.states & mActiveHistoryStates);
         final int diffStates2 = mHistoryLastWritten.states2 ^ (cur.states2 & mActiveHistoryStates2);
@@ -1390,8 +1379,8 @@
 
     private void writeHistoryItem(long elapsedRealtimeMs,
             @SuppressWarnings("UnusedVariable") long uptimeMs, HistoryItem cur, byte cmd) {
-        if (mBatteryStatsHistoryIterator != null) {
-            throw new IllegalStateException("Can't do this while iterating history!");
+        if (!mMutable) {
+            throw new ConcurrentModificationException("Battery history is not writable");
         }
         mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
         mHistoryLastLastWritten.setTo(mHistoryLastWritten);
@@ -1687,7 +1676,10 @@
                     Slog.i(TAG, "WRITE DELTA: cpuUsageDetails=" + cur.cpuUsageDetails);
                 }
                 if (!mCpuUsageHeaderWritten) {
-                    dest.writeStringArray(cur.cpuUsageDetails.cpuBracketDescriptions);
+                    dest.writeInt(cur.cpuUsageDetails.cpuBracketDescriptions.length);
+                    for (String desc: cur.cpuUsageDetails.cpuBracketDescriptions) {
+                        dest.writeString(desc);
+                    }
                     mCpuUsageHeaderWritten = true;
                 }
                 dest.writeInt(cur.cpuUsageDetails.uid);
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 09fe100..b88116d 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -23,10 +23,13 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import java.util.Iterator;
+
 /**
  * An iterator for {@link BatteryStats.HistoryItem}'s.
  */
-public class BatteryStatsHistoryIterator {
+public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.HistoryItem>,
+        AutoCloseable {
     private static final boolean DEBUG = false;
     private static final String TAG = "BatteryStatsHistoryItr";
     private final BatteryStatsHistory mBatteryStatsHistory;
@@ -38,29 +41,51 @@
     private final BatteryStatsHistory.VarintParceler mVarintParceler =
             new BatteryStatsHistory.VarintParceler();
 
+    private final BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
+
+    private static final int MAX_ENERGY_CONSUMER_COUNT = 100;
+    private static final int MAX_CPU_BRACKET_COUNT = 100;
+
     public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
         mBatteryStatsHistory = history;
+        mHistoryItem.clear();
+    }
+
+    @Override
+    public boolean hasNext() {
+        Parcel p = mBatteryStatsHistory.getNextParcel();
+        if (p == null) {
+            close();
+            return false;
+        }
+        return true;
     }
 
     /**
-     * Retrieves the next HistoryItem from battery history, if available. Returns false if there
+     * Retrieves the next HistoryItem from battery history, if available. Returns null if there
      * are no more items.
      */
-    public boolean next(BatteryStats.HistoryItem out) {
-        Parcel p = mBatteryStatsHistory.getNextParcel(out);
+    @Override
+    public BatteryStats.HistoryItem next() {
+        Parcel p = mBatteryStatsHistory.getNextParcel();
         if (p == null) {
-            mBatteryStatsHistory.finishIteratingHistory();
-            return false;
+            close();
+            return null;
         }
 
-        final long lastRealtimeMs = out.time;
-        final long lastWalltimeMs = out.currentTime;
-        readHistoryDelta(p, out);
-        if (out.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
-                && out.cmd != BatteryStats.HistoryItem.CMD_RESET && lastWalltimeMs != 0) {
-            out.currentTime = lastWalltimeMs + (out.time - lastRealtimeMs);
+        final long lastRealtimeMs = mHistoryItem.time;
+        final long lastWalltimeMs = mHistoryItem.currentTime;
+        try {
+            readHistoryDelta(p, mHistoryItem);
+        } catch (Throwable t) {
+            Slog.wtf(TAG, "Corrupted battery history", t);
+            return null;
         }
-        return true;
+        if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
+                && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET && lastWalltimeMs != 0) {
+            mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
+        }
+        return mHistoryItem;
     }
 
     private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
@@ -210,6 +235,12 @@
                 }
 
                 final int consumerCount = src.readInt();
+                if (consumerCount > MAX_ENERGY_CONSUMER_COUNT) {
+                    // Check to avoid a heap explosion in case the parcel is corrupted
+                    throw new IllegalStateException(
+                            "EnergyConsumer count too high: " + consumerCount
+                                    + ". Max = " + MAX_ENERGY_CONSUMER_COUNT);
+                }
                 mMeasuredEnergyDetails.consumers =
                         new BatteryStats.MeasuredEnergyDetails.EnergyConsumer[consumerCount];
                 mMeasuredEnergyDetails.chargeUC = new long[consumerCount];
@@ -236,7 +267,16 @@
 
             if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG) != 0) {
                 mCpuUsageDetails = new BatteryStats.CpuUsageDetails();
-                mCpuUsageDetails.cpuBracketDescriptions = src.readStringArray();
+                final int cpuBracketCount = src.readInt();
+                if (cpuBracketCount > MAX_CPU_BRACKET_COUNT) {
+                    // Check to avoid a heap explosion in case the parcel is corrupted
+                    throw new IllegalStateException("Too many CPU brackets: " + cpuBracketCount
+                            + ". Max = " + MAX_CPU_BRACKET_COUNT);
+                }
+                mCpuUsageDetails.cpuBracketDescriptions = new String[cpuBracketCount];
+                for (int i = 0; i < cpuBracketCount; i++) {
+                    mCpuUsageDetails.cpuBracketDescriptions[i] = src.readString();
+                }
                 mCpuUsageDetails.cpuUsageMs =
                         new long[mCpuUsageDetails.cpuBracketDescriptions.length];
             } else if (mCpuUsageDetails != null) {
@@ -294,7 +334,8 @@
     /**
      * Should be called when iteration is complete.
      */
+    @Override
     public void close() {
-        mBatteryStatsHistory.finishIteratingHistory();
+        mBatteryStatsHistory.iteratorFinished();
     }
 }
diff --git a/core/java/com/android/internal/security/TEST_MAPPING b/core/java/com/android/internal/security/TEST_MAPPING
index 803760c..b47ecec 100644
--- a/core/java/com/android/internal/security/TEST_MAPPING
+++ b/core/java/com/android/internal/security/TEST_MAPPING
@@ -14,6 +14,19 @@
     {
       "name": "ApkVerityTest",
       "file_patterns": ["VerityUtils\\.java"]
+    },
+    {
+      "name": "UpdatableSystemFontTest",
+      "file_patterns": ["VerityUtils\\.java"]
+    },
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.ApkVerityInstallTest"
+        }
+      ],
+      "file_patterns": ["VerityUtils\\.java"]
     }
   ]
 }
diff --git a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt b/core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl
similarity index 71%
copy from services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
copy to core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl
index 528680e7..0f7ab0a 100644
--- a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
+++ b/core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl
@@ -14,11 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.server.permission.access.external
+package com.android.internal.telephony;
 
-class RoSystemProperties {
-    companion object {
-        const val CONTROL_PRIVAPP_PERMISSIONS_DISABLE = false
-        const val CONTROL_PRIVAPP_PERMISSIONS_ENFORCE = false
-    }
-}
+oneway interface ICarrierConfigChangeListener {
+    void onCarrierConfigChanged(int slotIndex, int subId, int carrierId, int specificCarrierId);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 7ba2686..54936c6 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -32,6 +32,7 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.emergency.EmergencyNumber;
+import com.android.internal.telephony.ICarrierConfigChangeListener;
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
@@ -109,4 +110,8 @@
             int phoneId, in List<String> privilegedPackageNames, in int[] privilegedUids);
     void notifyCarrierServiceChanged(int phoneId, in String packageName, int uid);
 
+    void addCarrierConfigChangeListener(ICarrierConfigChangeListener listener,
+            String pkg, String featureId);
+    void removeCarrierConfigChangeListener(ICarrierConfigChangeListener listener, String pkg);
+    void notifyCarrierConfigChanged(int phoneId, int subId, int carrierId, int specificCarrierId);
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 953b36b..cc5de3e 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1742,7 +1742,7 @@
     }
 
     public static boolean userOwnsFrpCredential(Context context, UserInfo info) {
-        return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context);
+        return info != null && info.isMain() && info.isAdmin() && frpCredentialEnabled(context);
     }
 
     public static boolean frpCredentialEnabled(Context context) {
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 365a18d..a8abe50 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -529,9 +529,8 @@
     return Camera::getNumberOfCameras();
 }
 
-static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz,
-    jint cameraId, jobject info_obj)
-{
+static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, jint cameraId,
+                                                  jboolean overrideToPortrait, jobject info_obj) {
     CameraInfo cameraInfo;
     if (cameraId >= Camera::getNumberOfCameras() || cameraId < 0) {
         ALOGE("%s: Unknown camera ID %d", __FUNCTION__, cameraId);
@@ -539,7 +538,7 @@
         return;
     }
 
-    status_t rc = Camera::getCameraInfo(cameraId, &cameraInfo);
+    status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, &cameraInfo);
     if (rc != NO_ERROR) {
         jniThrowRuntimeException(env, "Fail to get camera info");
         return;
@@ -555,9 +554,9 @@
 }
 
 // connect to camera service
-static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
-    jobject weak_this, jint cameraId, jstring clientPackageName)
-{
+static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
+                                                 jint cameraId, jstring clientPackageName,
+                                                 jboolean overrideToPortrait) {
     // Convert jstring to String16
     const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
         env->GetStringChars(clientPackageName, NULL));
@@ -567,8 +566,9 @@
                             reinterpret_cast<const jchar*>(rawClientName));
 
     int targetSdkVersion = android_get_application_target_sdk_version();
-    sp<Camera> camera = Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID,
-                                        Camera::USE_CALLING_PID, targetSdkVersion);
+    sp<Camera> camera =
+            Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID, Camera::USE_CALLING_PID,
+                            targetSdkVersion, overrideToPortrait);
     if (camera == NULL) {
         return -EACCES;
     }
@@ -596,7 +596,7 @@
 
     // Update default display orientation in case the sensor is reverse-landscape
     CameraInfo cameraInfo;
-    status_t rc = Camera::getCameraInfo(cameraId, &cameraInfo);
+    status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, &cameraInfo);
     if (rc != NO_ERROR) {
         ALOGE("%s: getCameraInfo error: %d", __FUNCTION__, rc);
         return rc;
@@ -1051,93 +1051,43 @@
 //-------------------------------------------------
 
 static const JNINativeMethod camMethods[] = {
-  { "getNumberOfCameras",
-    "()I",
-    (void *)android_hardware_Camera_getNumberOfCameras },
-  { "_getCameraInfo",
-    "(ILandroid/hardware/Camera$CameraInfo;)V",
-    (void*)android_hardware_Camera_getCameraInfo },
-  { "native_setup",
-    "(Ljava/lang/Object;ILjava/lang/String;)I",
-    (void*)android_hardware_Camera_native_setup },
-  { "native_release",
-    "()V",
-    (void*)android_hardware_Camera_release },
-  { "setPreviewSurface",
-    "(Landroid/view/Surface;)V",
-    (void *)android_hardware_Camera_setPreviewSurface },
-  { "setPreviewTexture",
-    "(Landroid/graphics/SurfaceTexture;)V",
-    (void *)android_hardware_Camera_setPreviewTexture },
-  { "setPreviewCallbackSurface",
-    "(Landroid/view/Surface;)V",
-    (void *)android_hardware_Camera_setPreviewCallbackSurface },
-  { "startPreview",
-    "()V",
-    (void *)android_hardware_Camera_startPreview },
-  { "_stopPreview",
-    "()V",
-    (void *)android_hardware_Camera_stopPreview },
-  { "previewEnabled",
-    "()Z",
-    (void *)android_hardware_Camera_previewEnabled },
-  { "setHasPreviewCallback",
-    "(ZZ)V",
-    (void *)android_hardware_Camera_setHasPreviewCallback },
-  { "_addCallbackBuffer",
-    "([BI)V",
-    (void *)android_hardware_Camera_addCallbackBuffer },
-  { "native_autoFocus",
-    "()V",
-    (void *)android_hardware_Camera_autoFocus },
-  { "native_cancelAutoFocus",
-    "()V",
-    (void *)android_hardware_Camera_cancelAutoFocus },
-  { "native_takePicture",
-    "(I)V",
-    (void *)android_hardware_Camera_takePicture },
-  { "native_setParameters",
-    "(Ljava/lang/String;)V",
-    (void *)android_hardware_Camera_setParameters },
-  { "native_getParameters",
-    "()Ljava/lang/String;",
-    (void *)android_hardware_Camera_getParameters },
-  { "reconnect",
-    "()V",
-    (void*)android_hardware_Camera_reconnect },
-  { "lock",
-    "()V",
-    (void*)android_hardware_Camera_lock },
-  { "unlock",
-    "()V",
-    (void*)android_hardware_Camera_unlock },
-  { "startSmoothZoom",
-    "(I)V",
-    (void *)android_hardware_Camera_startSmoothZoom },
-  { "stopSmoothZoom",
-    "()V",
-    (void *)android_hardware_Camera_stopSmoothZoom },
-  { "setDisplayOrientation",
-    "(I)V",
-    (void *)android_hardware_Camera_setDisplayOrientation },
-  { "_enableShutterSound",
-    "(Z)Z",
-    (void *)android_hardware_Camera_enableShutterSound },
-  { "_startFaceDetection",
-    "(I)V",
-    (void *)android_hardware_Camera_startFaceDetection },
-  { "_stopFaceDetection",
-    "()V",
-    (void *)android_hardware_Camera_stopFaceDetection},
-  { "enableFocusMoveCallback",
-    "(I)V",
-    (void *)android_hardware_Camera_enableFocusMoveCallback},
-  { "setAudioRestriction",
-    "(I)V",
-    (void *)android_hardware_Camera_setAudioRestriction},
-  { "getAudioRestriction",
-    "()I",
-    (void *)android_hardware_Camera_getAudioRestriction},
+        {"getNumberOfCameras", "()I", (void *)android_hardware_Camera_getNumberOfCameras},
+        {"_getCameraInfo", "(IZLandroid/hardware/Camera$CameraInfo;)V",
+         (void *)android_hardware_Camera_getCameraInfo},
+        {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;Z)I",
+         (void *)android_hardware_Camera_native_setup},
+        {"native_release", "()V", (void *)android_hardware_Camera_release},
+        {"setPreviewSurface", "(Landroid/view/Surface;)V",
+         (void *)android_hardware_Camera_setPreviewSurface},
+        {"setPreviewTexture", "(Landroid/graphics/SurfaceTexture;)V",
+         (void *)android_hardware_Camera_setPreviewTexture},
+        {"setPreviewCallbackSurface", "(Landroid/view/Surface;)V",
+         (void *)android_hardware_Camera_setPreviewCallbackSurface},
+        {"startPreview", "()V", (void *)android_hardware_Camera_startPreview},
+        {"_stopPreview", "()V", (void *)android_hardware_Camera_stopPreview},
+        {"previewEnabled", "()Z", (void *)android_hardware_Camera_previewEnabled},
+        {"setHasPreviewCallback", "(ZZ)V", (void *)android_hardware_Camera_setHasPreviewCallback},
+        {"_addCallbackBuffer", "([BI)V", (void *)android_hardware_Camera_addCallbackBuffer},
+        {"native_autoFocus", "()V", (void *)android_hardware_Camera_autoFocus},
+        {"native_cancelAutoFocus", "()V", (void *)android_hardware_Camera_cancelAutoFocus},
+        {"native_takePicture", "(I)V", (void *)android_hardware_Camera_takePicture},
+        {"native_setParameters", "(Ljava/lang/String;)V",
+         (void *)android_hardware_Camera_setParameters},
+        {"native_getParameters", "()Ljava/lang/String;",
+         (void *)android_hardware_Camera_getParameters},
+        {"reconnect", "()V", (void *)android_hardware_Camera_reconnect},
+        {"lock", "()V", (void *)android_hardware_Camera_lock},
+        {"unlock", "()V", (void *)android_hardware_Camera_unlock},
+        {"startSmoothZoom", "(I)V", (void *)android_hardware_Camera_startSmoothZoom},
+        {"stopSmoothZoom", "()V", (void *)android_hardware_Camera_stopSmoothZoom},
+        {"setDisplayOrientation", "(I)V", (void *)android_hardware_Camera_setDisplayOrientation},
+        {"_enableShutterSound", "(Z)Z", (void *)android_hardware_Camera_enableShutterSound},
+        {"_startFaceDetection", "(I)V", (void *)android_hardware_Camera_startFaceDetection},
+        {"_stopFaceDetection", "()V", (void *)android_hardware_Camera_stopFaceDetection},
+        {"enableFocusMoveCallback", "(I)V",
+         (void *)android_hardware_Camera_enableFocusMoveCallback},
+        {"setAudioRestriction", "(I)V", (void *)android_hardware_Camera_setAudioRestriction},
+        {"getAudioRestriction", "()I", (void *)android_hardware_Camera_getAudioRestriction},
 };
 
 struct field {
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index 962b501..a9b1906 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -49,6 +49,7 @@
 #define ENCODING_DRA 28
 #define ENCODING_DTS_HD_MA 29
 #define ENCODING_DTS_UHD_P2 30
+#define ENCODING_DSD 31
 
 #define ENCODING_INVALID    0
 #define ENCODING_DEFAULT    1
@@ -122,6 +123,8 @@
         return AUDIO_FORMAT_DTS_HD_MA;
     case ENCODING_DTS_UHD_P2:
         return AUDIO_FORMAT_DTS_UHD_P2;
+    case ENCODING_DSD:
+        return AUDIO_FORMAT_DSD;
     default:
         return AUDIO_FORMAT_INVALID;
     }
@@ -201,6 +204,8 @@
         return ENCODING_DTS_UHD_P2;
     case AUDIO_FORMAT_DEFAULT:
         return ENCODING_DEFAULT;
+    case AUDIO_FORMAT_DSD:
+        return ENCODING_DSD;
     default:
         return ENCODING_INVALID;
     }
diff --git a/core/jni/android_media_AudioMixerAttributes.h b/core/jni/android_media_AudioMixerAttributes.h
index 61adb27..9e4637f 100644
--- a/core/jni/android_media_AudioMixerAttributes.h
+++ b/core/jni/android_media_AudioMixerAttributes.h
@@ -21,6 +21,7 @@
 
 // Keep sync with AudioMixerAttributes.java
 #define MIXER_BEHAVIOR_DEFAULT 0
+#define MIXER_BEHAVIOR_BIT_PERFECT 1
 // Invalid value is not added in JAVA API, but keep sync with native value
 #define MIXER_BEHAVIOR_INVALID -1
 
@@ -28,6 +29,8 @@
     switch (mixerBehavior) {
         case MIXER_BEHAVIOR_DEFAULT:
             return AUDIO_MIXER_BEHAVIOR_DEFAULT;
+        case MIXER_BEHAVIOR_BIT_PERFECT:
+            return AUDIO_MIXER_BEHAVIOR_BIT_PERFECT;
         default:
             return AUDIO_MIXER_BEHAVIOR_INVALID;
     }
@@ -37,6 +40,8 @@
     switch (mixerBehavior) {
         case AUDIO_MIXER_BEHAVIOR_DEFAULT:
             return MIXER_BEHAVIOR_DEFAULT;
+        case AUDIO_MIXER_BEHAVIOR_BIT_PERFECT:
+            return MIXER_BEHAVIOR_BIT_PERFECT;
         case AUDIO_MIXER_BEHAVIOR_INVALID:
         default:
             return MIXER_BEHAVIOR_INVALID;
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 5e0d9b3..fe95762 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -587,6 +587,13 @@
     MEMINFO_ACTIVE,
     MEMINFO_INACTIVE,
     MEMINFO_UNEVICTABLE,
+    MEMINFO_AVAILABLE,
+    MEMINFO_ACTIVE_ANON,
+    MEMINFO_INACTIVE_ANON,
+    MEMINFO_ACTIVE_FILE,
+    MEMINFO_INACTIVE_FILE,
+    MEMINFO_CMA_TOTAL,
+    MEMINFO_CMA_FREE,
     MEMINFO_COUNT
 };
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 593482c..34589b7 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1906,6 +1906,11 @@
     return javaObjectForIBinder(env, token);
 }
 
+static jboolean nativeBootFinished(JNIEnv* env, jclass clazz) {
+    status_t error = SurfaceComposerClient::bootFinished();
+    return error == OK ? JNI_TRUE : JNI_FALSE;
+}
+
 // ----------------------------------------------------------------------------
 
 SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env,
@@ -2138,6 +2143,8 @@
                 (void*)nativeSetDefaultApplyToken },
     {"nativeGetDefaultApplyToken", "()Landroid/os/IBinder;",
                 (void*)nativeGetDefaultApplyToken },
+    {"nativeBootFinished", "()Z",
+            (void*)nativeBootFinished },
         // clang-format on
 };
 
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 9070933..db391f7 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -16,7 +16,8 @@
 per-file package_item_info.proto = toddke@google.com,patb@google.com
 per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS
 per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
-per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com,ewol@google.com
+per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
+per-file background_install_control.proto = wenhaowang@google.com,georgechan@google.com,billylau@google.com
 
 # Biometrics
 jaggies@google.com
diff --git a/core/proto/android/server/background_install_control.proto b/core/proto/android/server/background_install_control.proto
new file mode 100644
index 0000000..38e6b4d
--- /dev/null
+++ b/core/proto/android/server/background_install_control.proto
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+syntax = "proto2";
+package com.android.server.pm;
+
+option java_multiple_files = true;
+
+// Proto for the background installed packages.
+// It's used for serializing the background installed package info to disk.
+message BackgroundInstalledPackagesProto {
+  repeated BackgroundInstalledPackageProto bg_installed_pkg = 1;
+}
+
+// Proto for the background installed package entry
+message BackgroundInstalledPackageProto {
+  optional string package_name = 1;
+  optional int32 user_id = 2;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 09672c2..fd4d4f8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3349,6 +3349,14 @@
                 android:description="@string/permdesc_companionProfileWatch"
                 android:protectionLevel="normal" />
 
+    <!-- Allows app to request to be associated with a device via
+         {@link android.companion.CompanionDeviceManager}
+         as "glasses"
+         <p>Protection level: normal
+     -->
+    <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_GLASSES"
+        android:protectionLevel="normal" />
+
     <!-- Allows application to request to be associated with a virtual display capable of streaming
          Android applications
          ({@link android.companion.AssociationRequest#DEVICE_PROFILE_APP_STREAMING})
@@ -3358,6 +3366,14 @@
     <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- Allows application to request to stream content from an Android host to a nearby device
+         ({@link android.companion.AssociationRequest#DEVICE_PROFILE_NEARBY_DEVICE_STREAMING})
+         by {@link android.companion.CompanionDeviceManager}.
+        <p>Not for use by third-party applications.
+    -->
+    <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows application to request to be associated with a vehicle head unit capable of
          automotive projection
          ({@link android.companion.AssociationRequest#DEVICE_PROFILE_AUTOMOTIVE_PROJECTION})
@@ -3847,6 +3863,11 @@
     <permission android:name="android.permission.REQUEST_UNIQUE_ID_ATTESTATION"
          android:protectionLevel="signature" />
 
+    <!-- Allows an application to get enabled credential manager providers.
+         @hide -->
+    <permission android:name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS"
+        android:protectionLevel="signature|privileged" />
+
     <!-- ========================================= -->
     <!-- Permissions for special development tools -->
     <!-- ========================================= -->
@@ -4916,7 +4937,7 @@
     <!-- Allows an application to manage the companion devices.
          @hide -->
     <permission android:name="android.permission.MANAGE_COMPANION_DEVICES"
-                android:protectionLevel="signature|role" />
+                android:protectionLevel="module|signature|role" />
 
     <!-- Allows an application to subscribe to notifications about the presence status change
          of their associated companion device
@@ -4954,6 +4975,15 @@
     <permission android:name="android.permission.ROTATE_SURFACE_FLINGER"
         android:protectionLevel="signature|recents" />
 
+    <!-- @SystemApi Allows an application to provide hints to SurfaceFlinger that can influence
+         its wakes up time to compose the next frame. This is a subset of the capabilities granted
+         by {@link #ACCESS_SURFACE_FLINGER}.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.WAKEUP_SURFACE_FLINGER"
+        android:protectionLevel="signature|recents" />
+
     <!-- Allows an application to take screen shots and more generally
          get access to the frame buffer data.
          <p>Not for use by third-party applications.
diff --git a/core/res/OWNERS b/core/res/OWNERS
index a2ef400..b878189 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -21,6 +21,11 @@
 tsuji@google.com
 yamasani@google.com
 
+# WindowManager team
+# TODO(262451702): Move WindowManager configs out of config.xml in a separate file
+per-file core/res/res/values/config.xml = file:/services/core/java/com/android/server/wm/OWNERS
+per-file core/res/res/values/symbols.xml = file:/services/core/java/com/android/server/wm/OWNERS
+
 # Resources finalization
 per-file res/xml/public-staging.xml = file:/tools/aapt2/OWNERS
 per-file res/xml/public-final.xml = file:/tools/aapt2/OWNERS
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index cead34b..f995a6e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3422,6 +3422,11 @@
          phone object irrespective of this config -->
     <bool name="config_switch_phone_on_voice_reg_state_change">true</bool>
 
+    <!-- Config determines whether Memory Availability Notification is supported over Ims so that
+         the RP-SMMA Notification is sent over Ims to SMS Service center indicating that UE can now
+          start receiving SMS after failures due to Memory Full event -->
+    <bool name="config_smma_notification_supported_over_ims">false</bool>
+
     <bool name="config_sms_force_7bit_encoding">false</bool>
 
     <!-- Number of physical SIM slots on the device. This includes both eSIM and pSIM slots, and
@@ -4765,11 +4770,11 @@
     <integer name="config_defaultPeakRefreshRate">0</integer>
 
     <!-- The display uses different gamma curves for different refresh rates. It's hard for panel
-         vendor to tune the curves to have exact same brightness for different refresh rate. So
+         vendors to tune the curves to have exact same brightness for different refresh rate. So
          flicker could be observed at switch time. The issue is worse at the gamma lower end.
          In addition, human eyes are more sensitive to the flicker at darker environment.
          To prevent flicker, we only support higher refresh rates if the display brightness is above
-         a threshold. And the darker environment could have higher threshold.
+         a threshold.
          For example, no higher refresh rate if
              display brightness <= disp0 && ambient brightness <= amb0
              || display brightness <= disp1 && ambient brightness <= amb1 -->
@@ -4791,13 +4796,12 @@
     <integer name="config_defaultRefreshRateInZone">0</integer>
 
     <!-- The display uses different gamma curves for different refresh rates. It's hard for panel
-         vendor to tune the curves to have exact same brightness for different refresh rate. So
+         vendors to tune the curves to have exact same brightness for different refresh rate. So
          flicker could be observed at switch time. The issue can be observed on the screen with
          even full white content at the high brightness. To prevent flickering, we support fixed
          refresh rates if the display and ambient brightness are equal to or above the provided
          thresholds. You can define multiple threshold levels as higher brightness environments
-         may have lower display brightness requirements for the flickering is visible. And the
-         high brightness environment could have higher threshold.
+         may have lower display brightness requirements for the flickering is visible.
          For example, fixed refresh rate if
              display brightness >= disp0 && ambient brightness >= amb0
              || display brightness >= disp1 && ambient brightness >= amb1 -->
@@ -5277,6 +5281,10 @@
     <!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. -->
     <bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool>
 
+    <!-- Whether the specific behaviour for translucent activities letterboxing is enabled.
+         TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
+    <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
+
     <!-- Whether a camera compat controller is enabled to allow the user to apply or revert
          treatment for stretched issues in camera viewfinder. -->
     <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml
index 78ec145..bd93aa9 100644
--- a/core/res/res/values/locale_config.xml
+++ b/core/res/res/values/locale_config.xml
@@ -23,7 +23,7 @@
         <item>ak-GH</item> <!-- Akan (Ghana) -->
         <item>am-ET</item> <!-- Amharic (Ethiopia) -->
         <item>ar-AE</item> <!-- Arabic (United Arab Emirates) -->
-        <item>ar-AE-u-nu-arab</item> <!-- Arabic (United Arab Emirates, Arabic Digits) -->
+        <item>ar-AE-u-nu-arab</item> <!-- Arabic (United Arab Emirates, Arabic-Indic Digits) -->
         <item>ar-BH</item> <!-- Arabic (Bahrain) -->
         <item>ar-BH-u-nu-latn</item> <!-- Arabic (Bahrain, Western Digits) -->
         <item>ar-DJ</item> <!-- Arabic (Djibouti) -->
@@ -108,6 +108,7 @@
         <item>cgg-UG</item> <!-- Chiga (Uganda) -->
         <item>chr-US</item> <!-- Cherokee (United States) -->
         <item>cs-CZ</item> <!-- Czech (Czechia) -->
+        <item>cv-RU</item> <!-- Chuvash (Russia) -->
         <item>cy-GB</item> <!-- Welsh (United Kingdom) -->
         <item>da-DK</item> <!-- Danish (Denmark) -->
         <item>da-GL</item> <!-- Danish (Greenland) -->
@@ -270,42 +271,42 @@
         <item>fa-AF-u-nu-latn</item> <!-- Persian (Afghanistan, Western Digits) -->
         <item>fa-IR</item> <!-- Persian (Iran) -->
         <item>fa-IR-u-nu-latn</item> <!-- Persian (Iran, Western Digits) -->
-        <item>ff-Adlm-BF</item> <!-- Fulah (Adlam, Burkina Faso) -->
-        <item>ff-Adlm-BF-u-nu-latn</item> <!-- Fulah (Adlam, Burkina Faso, Western Digits) -->
-        <item>ff-Adlm-CM</item> <!-- Fulah (Adlam, Cameroon) -->
-        <item>ff-Adlm-CM-u-nu-latn</item> <!-- Fulah (Adlam, Cameroon, Western Digits) -->
-        <item>ff-Adlm-GH</item> <!-- Fulah (Adlam, Ghana) -->
-        <item>ff-Adlm-GH-u-nu-latn</item> <!-- Fulah (Adlam, Ghana, Western Digits) -->
-        <item>ff-Adlm-GM</item> <!-- Fulah (Adlam, Gambia) -->
-        <item>ff-Adlm-GM-u-nu-latn</item> <!-- Fulah (Adlam, Gambia, Western Digits) -->
-        <item>ff-Adlm-GN</item> <!-- Fulah (Adlam, Guinea) -->
-        <item>ff-Adlm-GN-u-nu-latn</item> <!-- Fulah (Adlam, Guinea, Western Digits) -->
-        <item>ff-Adlm-GW</item> <!-- Fulah (Adlam, Guinea-Bissau) -->
-        <item>ff-Adlm-GW-u-nu-latn</item> <!-- Fulah (Adlam, Guinea-Bissau, Western Digits) -->
-        <item>ff-Adlm-LR</item> <!-- Fulah (Adlam, Liberia) -->
-        <item>ff-Adlm-LR-u-nu-latn</item> <!-- Fulah (Adlam, Liberia, Western Digits) -->
-        <item>ff-Adlm-MR</item> <!-- Fulah (Adlam, Mauritania) -->
-        <item>ff-Adlm-MR-u-nu-latn</item> <!-- Fulah (Adlam, Mauritania, Western Digits) -->
-        <item>ff-Adlm-NE</item> <!-- Fulah (Adlam, Niger) -->
-        <item>ff-Adlm-NE-u-nu-latn</item> <!-- Fulah (Adlam, Niger, Western Digits) -->
-        <item>ff-Adlm-NG</item> <!-- Fulah (Adlam, Nigeria) -->
-        <item>ff-Adlm-NG-u-nu-latn</item> <!-- Fulah (Adlam, Nigeria, Western Digits) -->
-        <item>ff-Adlm-SL</item> <!-- Fulah (Adlam, Sierra Leone) -->
-        <item>ff-Adlm-SL-u-nu-latn</item> <!-- Fulah (Adlam, Sierra Leone, Western Digits) -->
-        <item>ff-Adlm-SN</item> <!-- Fulah (Adlam, Senegal) -->
-        <item>ff-Adlm-SN-u-nu-latn</item> <!-- Fulah (Adlam, Senegal, Western Digits) -->
-        <item>ff-Latn-BF</item> <!-- Fulah (Latin, Burkina Faso) -->
-        <item>ff-Latn-CM</item> <!-- Fulah (Latin, Cameroon) -->
-        <item>ff-Latn-GH</item> <!-- Fulah (Latin, Ghana) -->
-        <item>ff-Latn-GM</item> <!-- Fulah (Latin, Gambia) -->
-        <item>ff-Latn-GN</item> <!-- Fulah (Latin, Guinea) -->
-        <item>ff-Latn-GW</item> <!-- Fulah (Latin, Guinea-Bissau) -->
-        <item>ff-Latn-LR</item> <!-- Fulah (Latin, Liberia) -->
-        <item>ff-Latn-MR</item> <!-- Fulah (Latin, Mauritania) -->
-        <item>ff-Latn-NE</item> <!-- Fulah (Latin, Niger) -->
-        <item>ff-Latn-NG</item> <!-- Fulah (Latin, Nigeria) -->
-        <item>ff-Latn-SL</item> <!-- Fulah (Latin, Sierra Leone) -->
-        <item>ff-Latn-SN</item> <!-- Fulah (Latin, Senegal) -->
+        <item>ff-Adlm-BF</item> <!-- Fula (Adlam, Burkina Faso) -->
+        <item>ff-Adlm-BF-u-nu-latn</item> <!-- Fula (Adlam, Burkina Faso, Western Digits) -->
+        <item>ff-Adlm-CM</item> <!-- Fula (Adlam, Cameroon) -->
+        <item>ff-Adlm-CM-u-nu-latn</item> <!-- Fula (Adlam, Cameroon, Western Digits) -->
+        <item>ff-Adlm-GH</item> <!-- Fula (Adlam, Ghana) -->
+        <item>ff-Adlm-GH-u-nu-latn</item> <!-- Fula (Adlam, Ghana, Western Digits) -->
+        <item>ff-Adlm-GM</item> <!-- Fula (Adlam, Gambia) -->
+        <item>ff-Adlm-GM-u-nu-latn</item> <!-- Fula (Adlam, Gambia, Western Digits) -->
+        <item>ff-Adlm-GN</item> <!-- Fula (Adlam, Guinea) -->
+        <item>ff-Adlm-GN-u-nu-latn</item> <!-- Fula (Adlam, Guinea, Western Digits) -->
+        <item>ff-Adlm-GW</item> <!-- Fula (Adlam, Guinea-Bissau) -->
+        <item>ff-Adlm-GW-u-nu-latn</item> <!-- Fula (Adlam, Guinea-Bissau, Western Digits) -->
+        <item>ff-Adlm-LR</item> <!-- Fula (Adlam, Liberia) -->
+        <item>ff-Adlm-LR-u-nu-latn</item> <!-- Fula (Adlam, Liberia, Western Digits) -->
+        <item>ff-Adlm-MR</item> <!-- Fula (Adlam, Mauritania) -->
+        <item>ff-Adlm-MR-u-nu-latn</item> <!-- Fula (Adlam, Mauritania, Western Digits) -->
+        <item>ff-Adlm-NE</item> <!-- Fula (Adlam, Niger) -->
+        <item>ff-Adlm-NE-u-nu-latn</item> <!-- Fula (Adlam, Niger, Western Digits) -->
+        <item>ff-Adlm-NG</item> <!-- Fula (Adlam, Nigeria) -->
+        <item>ff-Adlm-NG-u-nu-latn</item> <!-- Fula (Adlam, Nigeria, Western Digits) -->
+        <item>ff-Adlm-SL</item> <!-- Fula (Adlam, Sierra Leone) -->
+        <item>ff-Adlm-SL-u-nu-latn</item> <!-- Fula (Adlam, Sierra Leone, Western Digits) -->
+        <item>ff-Adlm-SN</item> <!-- Fula (Adlam, Senegal) -->
+        <item>ff-Adlm-SN-u-nu-latn</item> <!-- Fula (Adlam, Senegal, Western Digits) -->
+        <item>ff-Latn-BF</item> <!-- Fula (Latin, Burkina Faso) -->
+        <item>ff-Latn-CM</item> <!-- Fula (Latin, Cameroon) -->
+        <item>ff-Latn-GH</item> <!-- Fula (Latin, Ghana) -->
+        <item>ff-Latn-GM</item> <!-- Fula (Latin, Gambia) -->
+        <item>ff-Latn-GN</item> <!-- Fula (Latin, Guinea) -->
+        <item>ff-Latn-GW</item> <!-- Fula (Latin, Guinea-Bissau) -->
+        <item>ff-Latn-LR</item> <!-- Fula (Latin, Liberia) -->
+        <item>ff-Latn-MR</item> <!-- Fula (Latin, Mauritania) -->
+        <item>ff-Latn-NE</item> <!-- Fula (Latin, Niger) -->
+        <item>ff-Latn-NG</item> <!-- Fula (Latin, Nigeria) -->
+        <item>ff-Latn-SL</item> <!-- Fula (Latin, Sierra Leone) -->
+        <item>ff-Latn-SN</item> <!-- Fula (Latin, Senegal) -->
         <item>fi-FI</item> <!-- Finnish (Finland) -->
         <item>fil-PH</item> <!-- Filipino (Philippines) -->
         <item>fo-DK</item> <!-- Faroese (Denmark) -->
@@ -373,12 +374,13 @@
         <item>ha-NG</item> <!-- Hausa (Nigeria) -->
         <item>haw-US</item> <!-- Hawaiian (United States) -->
         <item>hi-IN</item> <!-- Hindi (India) -->
+        <item>hi-Latn-IN</item> <!-- Hindi (Latin, India) -->
         <item>hr-BA</item> <!-- Croatian (Bosnia & Herzegovina) -->
         <item>hr-HR</item> <!-- Croatian (Croatia) -->
         <item>hsb-DE</item> <!-- Upper Sorbian (Germany) -->
         <item>hu-HU</item> <!-- Hungarian (Hungary) -->
         <item>hy-AM</item> <!-- Armenian (Armenia) -->
-        <item>ia-001</item> <!-- Interlingua (World) -->
+        <item>ia-001</item> <!-- Interlingua (world) -->
         <item>ig-NG</item> <!-- Igbo (Nigeria) -->
         <item>ii-CN</item> <!-- Sichuan Yi (China) -->
         <item>in-ID</item> <!-- Indonesian (Indonesia) -->
@@ -397,6 +399,7 @@
         <item>kam-KE</item> <!-- Kamba (Kenya) -->
         <item>kde-TZ</item> <!-- Makonde (Tanzania) -->
         <item>kea-CV</item> <!-- Kabuverdianu (Cape Verde) -->
+        <item>kgp-BR</item> <!-- Kaingang (Brazil) -->
         <item>khq-ML</item> <!-- Koyra Chiini (Mali) -->
         <item>ki-KE</item> <!-- Kikuyu (Kenya) -->
         <item>kk-KZ</item> <!-- Kazakh (Kazakhstan) -->
@@ -408,6 +411,9 @@
         <item>ko-KP</item> <!-- Korean (North Korea) -->
         <item>ko-KR</item> <!-- Korean (South Korea) -->
         <item>kok-IN</item> <!-- Konkani (India) -->
+        <item>ks-Arab-IN</item> <!-- Kashmiri (Arabic, India) -->
+        <item>ks-Arab-IN-u-nu-latn</item> <!-- Kashmiri (Arabic, India, Western Digits) -->
+        <item>ks-Deva-IN</item> <!-- Kashmiri (Devanagari, India) -->
         <item>ksb-TZ</item> <!-- Shambala (Tanzania) -->
         <item>ksf-CM</item> <!-- Bafia (Cameroon) -->
         <item>ksh-DE</item> <!-- Colognian (Germany) -->
@@ -435,7 +441,7 @@
         <item>mg-MG</item> <!-- Malagasy (Madagascar) -->
         <item>mgh-MZ</item> <!-- Makhuwa-Meetto (Mozambique) -->
         <item>mgo-CM</item> <!-- Metaʼ (Cameroon) -->
-        <item>mi-NZ</item> <!-- Maori (New Zealand) -->
+        <item>mi-NZ</item> <!-- Māori (New Zealand) -->
         <item>mk-MK</item> <!-- Macedonian (North Macedonia) -->
         <item>ml-IN</item> <!-- Malayalam (India) -->
         <item>mn-MN</item> <!-- Mongolian (Mongolia) -->
@@ -500,6 +506,8 @@
         <item>qu-BO</item> <!-- Quechua (Bolivia) -->
         <item>qu-EC</item> <!-- Quechua (Ecuador) -->
         <item>qu-PE</item> <!-- Quechua (Peru) -->
+        <item>raj-IN</item> <!-- Rajasthani (India) -->
+        <item>raj-IN-u-nu-latn</item> <!-- Rajasthani (India, Western Digits) -->
         <item>rm-CH</item> <!-- Romansh (Switzerland) -->
         <item>rn-BI</item> <!-- Rundi (Burundi) -->
         <item>ro-MD</item> <!-- Romanian (Moldova) -->
@@ -514,11 +522,13 @@
         <item>rw-RW</item> <!-- Kinyarwanda (Rwanda) -->
         <item>rwk-TZ</item> <!-- Rwa (Tanzania) -->
         <item>sa-IN</item> <!-- Sanskrit (India) -->
-        <item>sah-RU</item> <!-- Sakha (Russia) -->
+        <item>sa-IN-u-nu-latn</item> <!-- Sanskrit (India, Western Digits) -->
+        <item>sah-RU</item> <!-- Yakut (Russia) -->
         <item>saq-KE</item> <!-- Samburu (Kenya) -->
         <item>sat-IN</item> <!-- Santali (India) -->
         <item>sat-IN-u-nu-latn</item> <!-- Santali (India, Western Digits) -->
         <item>sbp-TZ</item> <!-- Sangu (Tanzania) -->
+        <item>sc-IT</item> <!-- Sardinian (Italy) -->
         <item>sd-Arab-PK</item> <!-- Sindhi (Arabic, Pakistan) -->
         <item>sd-Arab-PK-u-nu-latn</item> <!-- Sindhi (Arabic, Pakistan, Western Digits) -->
         <item>sd-Deva-IN</item> <!-- Sindhi (Devanagari, India) -->
@@ -565,6 +575,8 @@
         <item>teo-UG</item> <!-- Teso (Uganda) -->
         <item>tg-TJ</item> <!-- Tajik (Tajikistan) -->
         <item>th-TH</item> <!-- Thai (Thailand) -->
+        <item>ti-ER</item> <!-- Tigrinya (Eritrea) -->
+        <item>ti-ET</item> <!-- Tigrinya (Ethiopia) -->
         <item>tk-TM</item> <!-- Turkmen (Turkmenistan) -->
         <item>to-TO</item> <!-- Tongan (Tonga) -->
         <item>tr-CY</item> <!-- Turkish (Cyprus) -->
@@ -586,10 +598,14 @@
         <item>vun-TZ</item> <!-- Vunjo (Tanzania) -->
         <item>wae-CH</item> <!-- Walser (Switzerland) -->
         <item>wo-SN</item> <!-- Wolof (Senegal) -->
+        <item>xh-ZA</item> <!-- Xhosa (South Africa) -->
         <item>xog-UG</item> <!-- Soga (Uganda) -->
         <item>yav-CM</item> <!-- Yangben (Cameroon) -->
         <item>yo-BJ</item> <!-- Yoruba (Benin) -->
         <item>yo-NG</item> <!-- Yoruba (Nigeria) -->
+        <item>yrl-BR</item> <!-- Nheengatu (Brazil) -->
+        <item>yrl-CO</item> <!-- Nheengatu (Colombia) -->
+        <item>yrl-VE</item> <!-- Nheengatu (Venezuela) -->
         <item>yue-Hans-CN</item> <!-- Cantonese (Simplified, China) -->
         <item>yue-Hant-HK</item> <!-- Cantonese (Traditional, Hong Kong) -->
         <item>zgh-MA</item> <!-- Standard Moroccan Tamazight (Morocco) -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 18a5c72..8106e24 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -139,9 +139,9 @@
     <!-- Displayed when a SIM PUK password is too short. -->
     <string name="invalidPuk">Type a PUK that is 8 numbers or longer.</string>
     <!-- Displayed to prompt the user to type the PUK password to unlock
-         the SIM card. -->
-    <string name="needPuk">Your SIM card is PUK-locked. Type the PUK code to unlock it.</string>
-    <string name="needPuk2">Type PUK2 to unblock SIM card.</string>
+         the SIM. -->
+    <string name="needPuk">Your SIM is PUK-locked. Type the PUK code to unlock it.</string>
+    <string name="needPuk2">Type PUK2 to unblock SIM.</string>
     <!-- Displayed when user attempts to change SIM PIN1 without enabling PIN1. -->
     <string name="enablePin">Unsuccessful, enable SIM/RUIM Lock.</string>
     <!-- Displayed when a SIM PIN/PUK is entered incorrectly. -->
@@ -2529,23 +2529,23 @@
     <!-- Shown when face unlock failed multiple times so we're just using the backup -->
     <string name="faceunlock_multiple_failures">Maximum Face Unlock attempts exceeded</string>
 
-    <!-- Shown in the lock screen when there is no SIM card. -->
-    <string name="lockscreen_missing_sim_message_short">No SIM card</string>
-    <!-- Shown in the lock screen when there is no SIM card. -->
-    <string name="lockscreen_missing_sim_message" product="tablet">No SIM card in tablet.</string>
-    <!-- Shown in the lock screen when there is no SIM card. -->
-    <string name="lockscreen_missing_sim_message" product="tv">No SIM card in your Android TV device.</string>
-    <!-- Shown in the lock screen when there is no SIM card. -->
-    <string name="lockscreen_missing_sim_message" product="default">No SIM card in phone.</string>
-    <!-- Shown in the lock screen to ask the user to insert a SIM card. -->
-    <string name="lockscreen_missing_sim_instructions">Insert a SIM card.</string>
-    <!-- Shown in the lock screen to ask the user to insert a SIM card when sim is missing or not readable. -->
-    <string name="lockscreen_missing_sim_instructions_long">The SIM card is missing or not readable. Insert a SIM card.</string>
-    <!-- Shown in the lock screen when SIM card is permanently disabled. -->
-    <string name="lockscreen_permanent_disabled_sim_message_short">Unusable SIM card.</string>
-    <!-- Shown in the lock screen to inform the user to SIM card is permanently disabled. -->
-    <string name="lockscreen_permanent_disabled_sim_instructions">Your SIM card has been permanently disabled.\n
-    Contact your wireless service provider for another SIM card.</string>
+    <!-- Shown in the lock screen when there is no SIM. -->
+    <string name="lockscreen_missing_sim_message_short">No SIM</string>
+    <!-- Shown in the lock screen when there is no SIM. -->
+    <string name="lockscreen_missing_sim_message" product="tablet">No SIM in tablet.</string>
+    <!-- Shown in the lock screen when there is no SIM. -->
+    <string name="lockscreen_missing_sim_message" product="tv">No SIM in your Android TV device.</string>
+    <!-- Shown in the lock screen when there is no SIM. -->
+    <string name="lockscreen_missing_sim_message" product="default">No SIM in phone.</string>
+    <!-- Shown in the lock screen to ask the user to add a SIM. -->
+    <string name="lockscreen_missing_sim_instructions">Add a SIM.</string>
+    <!-- Shown in the lock screen to ask the user to add a SIM when sim is missing or not readable. -->
+    <string name="lockscreen_missing_sim_instructions_long">The SIM is missing or not readable. Add a SIM.</string>
+    <!-- Shown in the lock screen when SIM is permanently disabled. -->
+    <string name="lockscreen_permanent_disabled_sim_message_short">Unusable SIM.</string>
+    <!-- Shown in the lock screen to inform the user to SIM is permanently deactivated. -->
+    <string name="lockscreen_permanent_disabled_sim_instructions">Your SIM has been permanently deactivated.\n
+    Contact your wireless service provider for another SIM.</string>
 
     <!-- Shown on transport control of lockscreen. Pressing button goes to previous track. -->
     <string name="lockscreen_transport_prev_description">Previous track</string>
@@ -2572,17 +2572,17 @@
 
     <!-- When the user enters a wrong sim pin too many times, it becomes
          PUK locked (Pin Unlock Kode) -->
-    <string name="lockscreen_sim_puk_locked_message">SIM card is PUK-locked.</string>
+    <string name="lockscreen_sim_puk_locked_message">SIM is PUK-locked.</string>
     <!-- Shown in the lock screen when the SIM has become PUK locked and the user must call customer care to unlock it. -->
     <string name="lockscreen_sim_puk_locked_instructions">See the User Guide or contact Customer Care.</string>
 
     <!-- Shown in the lock screen to tell the user that their SIM is locked and they must unlock it. -->
-    <string name="lockscreen_sim_locked_message">SIM card is locked.</string>
+    <string name="lockscreen_sim_locked_message">SIM is locked.</string>
 
     <!-- For the unlock screen, When the user enters a sim unlock code, it takes a little while to check
          whether it is valid, and to unlock the sim if it is valid.  we display a
          progress dialog in the meantime.  this is the emssage. -->
-    <string name="lockscreen_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message">Unlocking SIM\u2026</string>
 
     <!-- For the unlock screen, Information message shown in dialog when user has too many failed attempts at
          drawing the unlock pattern -->
@@ -3883,13 +3883,13 @@
 
     <!-- SIM swap and device reboot Dialog --> <skip />
     <!-- See SIM_REMOVED_DIALOG.  This is the title of that dialog. -->
-    <string name="sim_removed_title">SIM card removed</string>
+    <string name="sim_removed_title">SIM removed</string>
     <!-- See SIM_REMOVED_DIALOG.  This is the message of that dialog. -->
-    <string name="sim_removed_message">The mobile network will be unavailable until you restart with a valid SIM card inserted.</string>
+    <string name="sim_removed_message">The mobile network will be unavailable until you restart with a valid SIM.</string>
     <!-- See SIM_REMOVED_DIALOG.  This is the button of that dialog. -->
     <string name="sim_done_button">Done</string>
     <!-- See SIM_ADDED_DIALOG.  This is the title of that dialog. -->
-    <string name="sim_added_title">SIM card added</string>
+    <string name="sim_added_title">SIM added</string>
     <!-- See SIM_ADDED_DIALOG.  This is the message of that dialog. -->
     <string name="sim_added_message">Restart your device to access the mobile network.</string>
     <!-- See SIM_ADDED_DIALOG.  This is the button of that dialog. -->
@@ -4713,8 +4713,8 @@
     <string name="kg_puk_enter_pin_hint">Enter desired PIN code</string>
     <!-- Message shown when the user needs to confirm the PIN they just entered in the PUK screen -->
     <string name="kg_enter_confirm_pin_hint">Confirm desired PIN code</string>
-    <!-- Message shown in dialog while the device is unlocking the SIM card -->
-    <string name="kg_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string>
+    <!-- Message shown in dialog while the device is unlocking the SIM -->
+    <string name="kg_sim_unlock_progress_dialog_message">Unlocking SIM\u2026</string>
     <!-- Message shown when the user enters the wrong PIN code -->
     <string name="kg_password_wrong_pin_code">Incorrect PIN code.</string>
     <!-- Message shown when the user enters an invalid SIM pin password in PUK screen -->
@@ -4937,7 +4937,7 @@
 
     <!-- Title of Color Correction feature, which is mostly used to help users who are colorblind,
      shown in the warning dialog about the accessibility shortcut. -->
-    <string name="color_correction_feature_name">Color Correction</string>
+    <string name="color_correction_feature_name">Color correction</string>
 
     <!-- Title of One Handed Mode feature, shown in the system gestures of the accessibility shortcut. [CHAR LIMIT=none] -->
     <string name="one_handed_mode_feature_name">One-Handed mode</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cd0024f..151530b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2810,6 +2810,7 @@
   <java-symbol type="bool" name="config_switch_phone_on_voice_reg_state_change" />
   <java-symbol type="string" name="whichHomeApplicationNamed" />
   <java-symbol type="string" name="whichHomeApplicationLabel" />
+  <java-symbol type="bool" name="config_smma_notification_supported_over_ims" />
   <java-symbol type="bool" name="config_sms_force_7bit_encoding" />
   <java-symbol type="bool" name="config_defaultWindowFeatureOptionsPanel" />
   <java-symbol type="bool" name="config_defaultWindowFeatureContextMenu" />
@@ -4396,6 +4397,9 @@
   <!-- Set to true to make assistant show in front of the dream/screensaver. -->
   <java-symbol type="bool" name="config_assistantOnTopOfDream"/>
 
+  <!-- Set to true to enable letterboxing on translucent activities. -->
+  <java-symbol type="bool" name="config_letterboxIsEnabledForTranslucentActivities" />
+
   <java-symbol type="string" name="config_overrideComponentUiPackage" />
 
   <java-symbol type="string" name="notification_channel_network_status" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
index 3f35e99..cabeb13 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
@@ -17,6 +17,7 @@
 
 import static org.junit.Assert.*;
 import static org.junit.Assume.*;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
@@ -335,8 +336,10 @@
         assertEquals(RadioManager.STATUS_OK, scanRet);
         assertEquals(RadioManager.STATUS_OK, cancelRet);
 
-        verify(mCallback, after(kCancelTimeoutMs).atMost(1)).onError(RadioTuner.ERROR_CANCELLED);
+        verify(mCallback, after(kCancelTimeoutMs).atMost(1))
+                .onTuneFailed(eq(RadioTuner.TUNER_RESULT_CANCELED), any());
         verify(mCallback, atMost(1)).onProgramInfoChanged(any());
+        Mockito.reset(mCallback);
     }
 
     @Test
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
index 2fa3f876..65e55a2 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
@@ -130,6 +130,18 @@
     };
 
     @Test
+    public void seek_forRadioTuner_throwsException() {
+        UnsupportedOperationException thrown = assertThrows(
+                UnsupportedOperationException.class, () -> {
+                    DEFAULT_RADIO_TUNER.seek(RadioTuner.DIRECTION_DOWN,
+                            /* skipSubChannel= */ false);
+                });
+
+        assertWithMessage("Exception for seeking on default radio tuner")
+                .that(thrown).hasMessageThat().contains("Seeking is not supported");
+    }
+
+    @Test
     public void getDynamicProgramList_forRadioTuner_returnsNull() {
         assertWithMessage("Dynamic program list obtained from default radio tuner")
                 .that(DEFAULT_RADIO_TUNER.getDynamicProgramList(new ProgramList.Filter())).isNull();
@@ -143,29 +155,45 @@
 
     @Test
     public void isConfigFlagSet_forRadioTuner_throwsException() {
-        assertThrows(UnsupportedOperationException.class, () -> {
-            DEFAULT_RADIO_TUNER.isConfigFlagSet(/* flag= */ 1);
-        });
+        UnsupportedOperationException thrown = assertThrows(
+                UnsupportedOperationException.class, () -> {
+                    DEFAULT_RADIO_TUNER.isConfigFlagSet(/* flag= */ 1);
+                });
+
+        assertWithMessage("Exception for isConfigFlagSet on default radio tuner")
+                .that(thrown).hasMessageThat().contains("isConfigFlagSet is not supported");
     }
 
     @Test
     public void setConfigFlag_forRadioTuner_throwsException() {
-        assertThrows(UnsupportedOperationException.class, () -> {
+        UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+                () -> {
             DEFAULT_RADIO_TUNER.setConfigFlag(/* flag= */ 1, /* value= */ false);
         });
+
+        assertWithMessage("Exception for setting config flag on default radio tuner")
+                .that(thrown).hasMessageThat().contains("Setting config flag is not supported");
     }
 
     @Test
     public void setParameters_forRadioTuner_throwsException() {
-        assertThrows(UnsupportedOperationException.class, () -> {
+        UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+                () -> {
             DEFAULT_RADIO_TUNER.setParameters(Map.of("testKey", "testValue"));
         });
+
+        assertWithMessage("Exception for setting parameters from default radio tuner")
+                .that(thrown).hasMessageThat().contains("Setting parameters is not supported");
     }
 
     @Test
     public void getParameters_forRadioTuner_throwsException() {
-        assertThrows(UnsupportedOperationException.class, () -> {
+        UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+                () -> {
             DEFAULT_RADIO_TUNER.getParameters(List.of("testKey"));
         });
+
+        assertWithMessage("Exception for getting parameters from default radio tuner")
+                .that(thrown).hasMessageThat().contains("Getting parameters is not supported");
     }
 }
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
index 2b9de18..87f91fa 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
@@ -41,6 +41,8 @@
 import android.os.RemoteException;
 import android.util.ArraySet;
 
+import androidx.test.InstrumentationRegistry;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -54,6 +56,8 @@
 @RunWith(MockitoJUnitRunner.class)
 public final class ProgramListTest {
 
+    public final Context mContext = InstrumentationRegistry.getContext();
+
     private static final int CREATOR_ARRAY_SIZE = 3;
     private static final VerificationWithTimeout CALLBACK_TIMEOUT = timeout(/* millis= */ 500);
 
@@ -109,8 +113,6 @@
     @Mock
     private IRadioService mRadioServiceMock;
     @Mock
-    private Context mContextMock;
-    @Mock
     private ITuner mTunerMock;
     @Mock
     private RadioTuner.Callback mTunerCallbackMock;
@@ -477,7 +479,7 @@
     }
 
     private void createRadioTuner() throws Exception {
-        RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
+        RadioManager radioManager = new RadioManager(mContext, mRadioServiceMock);
         RadioManager.BandConfig band = new RadioManager.FmBandConfig(
                 new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
                         /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 200,
@@ -487,7 +489,7 @@
         doAnswer(invocation -> {
             mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
             return mTunerMock;
-        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
+        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt());
 
         mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, band,
                 /* withAudio= */ true, mTunerCallbackMock, /* handler= */ null);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
index 44aa6d1..03742eb 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
@@ -18,14 +18,16 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.hardware.radio.Announcement;
 import android.hardware.radio.IAnnouncementListener;
 import android.hardware.radio.ICloseHandle;
@@ -34,6 +36,7 @@
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioMetadata;
 import android.hardware.radio.RadioTuner;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.ArrayMap;
@@ -53,6 +56,8 @@
 @RunWith(MockitoJUnitRunner.class)
 public final class RadioManagerTest {
 
+    private static final int TEST_TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
     private static final int REGION = RadioManager.REGION_ITU_2;
     private static final int FM_LOWER_LIMIT = 87500;
     private static final int FM_UPPER_LIMIT = 108000;
@@ -126,6 +131,7 @@
                     /* vendorInfo= */ new ArrayMap<>()));
 
     private RadioManager mRadioManager;
+    private final ApplicationInfo mApplicationInfo = new ApplicationInfo();
 
     @Mock
     private IRadioService mRadioServiceMock;
@@ -1008,7 +1014,8 @@
         mRadioManager.openTuner(moduleId, FM_BAND_CONFIG, withAudio, mCallbackMock,
                 /* handler= */ null);
 
-        verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any());
+        verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any(),
+                anyInt());
     }
 
     @Test
@@ -1103,6 +1110,8 @@
     }
 
     private void createRadioManager() throws RemoteException {
+        mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION;
+        when(mContextMock.getApplicationInfo()).thenReturn(mApplicationInfo);
         when(mRadioServiceMock.listModules()).thenReturn(Arrays.asList(AMFM_PROPERTIES));
         when(mRadioServiceMock.addAnnouncementListener(any(), any())).thenReturn(mCloseHandleMock);
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
index bdba6a1..d851a7724 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.graphics.Bitmap;
 import android.hardware.radio.IRadioService;
 import android.hardware.radio.ITuner;
@@ -35,6 +36,7 @@
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioMetadata;
 import android.hardware.radio.RadioTuner;
+import android.os.Build;
 
 import org.junit.After;
 import org.junit.Before;
@@ -51,11 +53,12 @@
 @RunWith(MockitoJUnitRunner.class)
 public final class TunerAdapterTest {
 
+    private static final int TEST_TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
     private static final int CALLBACK_TIMEOUT_MS = 30_000;
     private static final int AM_LOWER_LIMIT_KHZ = 150;
 
     private static final RadioManager.BandConfig TEST_BAND_CONFIG = createBandConfig();
-
     private static final ProgramSelector.Identifier FM_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
                     /* value= */ 94300);
@@ -66,6 +69,7 @@
 
     private RadioTuner mRadioTuner;
     private ITunerCallback mTunerCallback;
+    private final ApplicationInfo mApplicationInfo = new ApplicationInfo();
 
     @Mock
     private IRadioService mRadioServiceMock;
@@ -78,12 +82,14 @@
 
     @Before
     public void setUp() throws Exception {
+        mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION;
+        when(mContextMock.getApplicationInfo()).thenReturn(mApplicationInfo);
         RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
 
         doAnswer(invocation -> {
             mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
             return mTunerMock;
-        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
+        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt());
 
         doAnswer(invocation -> {
             ProgramSelector program = (ProgramSelector) invocation.getArguments()[0];
@@ -92,7 +98,7 @@
                 throw new IllegalArgumentException();
             }
             if (program.getPrimaryId().getValue() < AM_LOWER_LIMIT_KHZ) {
-                mTunerCallback.onTuneFailed(RadioManager.STATUS_BAD_VALUE, program);
+                mTunerCallback.onTuneFailed(RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS, program);
             } else {
                 mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
             }
@@ -170,15 +176,30 @@
     }
 
     @Test
+    public void scan_forTunerAdapter_succeeds() throws Exception {
+        doAnswer(invocation -> {
+            mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
+            return RadioManager.STATUS_OK;
+        }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+        int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
+
+        verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false);
+        assertWithMessage("Status for scaning")
+                .that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
+        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+    }
+
+    @Test
     public void seek_forTunerAdapter_succeeds() throws Exception {
         doAnswer(invocation -> {
             mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
             return RadioManager.STATUS_OK;
-        }).when(mTunerMock).scan(anyBoolean(), anyBoolean());
+        }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
 
         int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
 
-        verify(mTunerMock).scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+        verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false);
         assertWithMessage("Status for seeking")
                 .that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
         verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
@@ -187,13 +208,14 @@
     @Test
     public void seek_forTunerAdapter_invokesOnErrorWhenTimeout() throws Exception {
         doAnswer(invocation -> {
-            mTunerCallback.onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+            mTunerCallback.onTuneFailed(RadioTuner.TUNER_RESULT_TIMEOUT, FM_SELECTOR);
             return RadioManager.STATUS_OK;
-        }).when(mTunerMock).scan(anyBoolean(), anyBoolean());
+        }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
 
         mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel*/ true);
 
-        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onTuneFailed(
+                RadioTuner.TUNER_RESULT_TIMEOUT, FM_SELECTOR);
     }
 
     @Test
@@ -224,7 +246,7 @@
         mRadioTuner.tune(invalidSelector);
 
         verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
-                .onTuneFailed(RadioManager.STATUS_BAD_VALUE, invalidSelector);
+                .onTuneFailed(RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS, invalidSelector);
     }
 
     @Test
@@ -415,6 +437,17 @@
     }
 
     @Test
+    public void onConfigFlagUpdated_forTunerCallbackAdapter() throws Exception {
+        int configFlag = RadioManager.CONFIG_RDS_AF;
+        boolean configFlagValue = true;
+
+        mTunerCallback.onConfigFlagUpdated(configFlag, configFlagValue);
+
+        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+                .onConfigFlagUpdated(configFlag, configFlagValue);
+    }
+
+    @Test
     public void onParametersUpdated_forTunerCallbackAdapter() throws Exception {
         Map<String, String> parametersExpected = Map.of("ParameterKeyMock", "ParameterValueMock");
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
index a2df426..9803474 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -33,6 +34,7 @@
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.ServiceManager;
 
@@ -55,6 +57,7 @@
             "android.hardware.broadcastradio.IBroadcastRadio/amfm";
     private static final String DAB_SERVICE_NAME =
             "android.hardware.broadcastradio.IBroadcastRadio/dab";
+    private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
 
     private IRadioServiceAidlImpl mAidlImpl;
 
@@ -82,7 +85,7 @@
         doNothing().when(mServiceMock).enforcePolicyAccess();
 
         when(mHalMock.listModules()).thenReturn(List.of(mModuleMock));
-        when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any()))
+        when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any(), eq(TARGET_SDK_VERSION)))
                 .thenReturn(mTunerMock);
         when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle);
 
@@ -114,7 +117,7 @@
     @Test
     public void openTuner_forAidlImpl() throws Exception {
         ITuner tuner = mAidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock,
-                /* withAudio= */ true, mTunerCallbackMock);
+                /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
 
         assertWithMessage("Tuner opened in AIDL HAL")
                 .that(tuner).isEqualTo(mTunerMock);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
index 5ab9435..cfff477 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
@@ -32,6 +32,7 @@
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
+import android.os.Build;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,6 +50,7 @@
 
     private static final int HAL1_MODULE_ID = 0;
     private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+    private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
 
     private IRadioServiceHidlImpl mHidlImpl;
 
@@ -103,7 +105,7 @@
     @Test
     public void openTuner_withHal1ModuleId_forHidlImpl() throws Exception {
         ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID, mBandConfigMock,
-                /* withAudio= */ true, mTunerCallbackMock);
+                /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
 
         assertWithMessage("Tuner opened in HAL 1")
                 .that(tuner).isEqualTo(mHal1TunerMock);
@@ -112,7 +114,7 @@
     @Test
     public void openTuner_withHal2ModuleId_forHidlImpl() throws Exception {
         ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID + 1, mBandConfigMock,
-                /* withAudio= */ true, mTunerCallbackMock);
+                /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
 
         assertWithMessage("Tuner opened in HAL 2")
                 .that(tuner).isEqualTo(mHal2TunerMock);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
index 1cc0a985..f404082 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
@@ -36,6 +36,7 @@
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioTuner;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.IServiceCallback;
 import android.os.RemoteException;
@@ -54,6 +55,8 @@
 
 public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTestCase {
 
+    private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
     private static final int FM_RADIO_MODULE_ID = 0;
     private static final int DAB_RADIO_MODULE_ID = 1;
     private static final ArrayList<String> SERVICE_LIST =
@@ -137,7 +140,8 @@
         createBroadcastRadioService();
 
         ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
-                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
+                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
+                TARGET_SDK_VERSION);
 
         assertWithMessage("Session opened in FM radio module")
                 .that(session).isEqualTo(mFmTunerSessionMock);
@@ -148,7 +152,8 @@
         createBroadcastRadioService();
 
         ITuner session = mBroadcastRadioService.openSession(DAB_RADIO_MODULE_ID + 1,
-                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
+                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
+                TARGET_SDK_VERSION);
 
         assertWithMessage("Session opened with id not found").that(session).isNull();
     }
@@ -160,7 +165,8 @@
 
         IllegalStateException thrown = assertThrows(IllegalStateException.class,
                 () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
-                        /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock));
+                        /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
+                        TARGET_SDK_VERSION));
 
         assertWithMessage("Exception for opening session by non-current user")
                 .that(thrown).hasMessageThat().contains("Cannot open session for non-current user");
@@ -228,7 +234,7 @@
             return null;
         }).when(mFmBinderMock).linkToDeath(any(), anyInt());
 
-        when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock)))
+        when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock), eq(TARGET_SDK_VERSION)))
                 .thenReturn(mFmTunerSessionMock);
     }
 }
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsResultTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsResultTest.java
new file mode 100644
index 0000000..df3ddfd
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsResultTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 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.broadcastradio.aidl;
+
+import android.hardware.broadcastradio.Result;
+import android.hardware.radio.RadioTuner;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public final class ConversionUtilsResultTest {
+
+    private final int mHalResult;
+    private final int mTunerResult;
+
+    @Rule
+    public final Expect expect = Expect.create();
+
+    public ConversionUtilsResultTest(int halResult, int tunerResult) {
+        this.mHalResult = halResult;
+        this.mTunerResult = tunerResult;
+    }
+
+    @Parameterized.Parameters
+    public static List<Object[]> inputParameters() {
+        return Arrays.asList(new Object[][]{
+                {Result.OK, RadioTuner.TUNER_RESULT_OK},
+                {Result.INTERNAL_ERROR, RadioTuner.TUNER_RESULT_INTERNAL_ERROR},
+                {Result.INVALID_ARGUMENTS, RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS},
+                {Result.INVALID_STATE, RadioTuner.TUNER_RESULT_INVALID_STATE},
+                {Result.NOT_SUPPORTED, RadioTuner.TUNER_RESULT_NOT_SUPPORTED},
+                {Result.TIMEOUT, RadioTuner.TUNER_RESULT_TIMEOUT},
+                {Result.UNKNOWN_ERROR, RadioTuner.TUNER_RESULT_UNKNOWN_ERROR}
+        });
+    }
+
+    @Test
+    public void halResultToTunerResult() {
+        expect.withMessage("Tuner result converted from AIDL HAL result %s", mHalResult)
+                .that(ConversionUtils.halResultToTunerResult(mHalResult))
+                .isEqualTo(mTunerResult);
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 3119554..a1cebb6 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -25,6 +25,7 @@
 import android.hardware.radio.Announcement;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
+import android.os.Build;
 
 import com.google.common.truth.Expect;
 
@@ -69,6 +70,18 @@
     public final Expect expect = Expect.create();
 
     @Test
+    public void isAtLeastU_withTSdkVersion_returnsFalse() {
+        expect.withMessage("Target SDK version of T")
+                .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.TIRAMISU)).isFalse();
+    }
+
+    @Test
+    public void isAtLeastU_withCurrentSdkVersion_returnsTrue() {
+        expect.withMessage("Target SDK version of U")
+                .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.CUR_DEVELOPMENT)).isTrue();
+    }
+
+    @Test
     public void propertiesFromHalProperties_idsMatch() {
         expect.withMessage("Properties id")
                 .that(MODULE_PROPERTIES.getId()).isEqualTo(TEST_ID);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 993ca77..c5c6349 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -42,6 +42,7 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioTuner;
+import android.os.Build;
 import android.os.ServiceSpecificException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -65,6 +66,7 @@
  */
 public final class TunerSessionTest extends ExtendedRadioMockitoTestCase {
 
+    private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
     private static final VerificationWithTimeout CALLBACK_TIMEOUT =
             timeout(/* millis= */ 200);
     private static final int SIGNAL_QUALITY = 1;
@@ -299,6 +301,18 @@
     }
 
     @Test
+    public void tune_withLowerSdkVersion() throws Exception {
+        openAidlClients(/* numClients= */ 1, Build.VERSION_CODES.TIRAMISU);
+        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+        RadioManager.ProgramInfo tuneInfo =
+                AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+        mTunerSessions[0].tune(initialSel);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo);
+    }
+
+    @Test
     public void tune_withMultipleSessions() throws Exception {
         int numSessions = 3;
         openAidlClients(numSessions);
@@ -377,49 +391,49 @@
     }
 
     @Test
-    public void scan_withDirectionUp() throws Exception {
+    public void seek_withDirectionUp() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[2];
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
-        RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
+        RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo(
                 AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
                 SIGNAL_QUALITY);
         openAidlClients(/* numClients= */ 1);
         mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
 
-        mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+        mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
 
         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
-                .onCurrentProgramInfoChanged(scanUpInfo);
+                .onCurrentProgramInfoChanged(seekUpInfo);
     }
 
     @Test
-    public void scan_callsOnTuneFailedWhenTimeout() throws Exception {
+    public void seek_callsOnTuneFailedWhenTimeout() throws Exception {
         int numSessions = 2;
         openAidlClients(numSessions);
 
-        mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+        mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
 
         for (int index = 0; index < numSessions; index++) {
             verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
-                    .onTuneFailed(eq(Result.TIMEOUT), any());
+                    .onTuneFailed(eq(RadioTuner.TUNER_RESULT_TIMEOUT), any());
         }
     }
 
     @Test
-    public void scan_withDirectionDown() throws Exception {
+    public void seek_withDirectionDown() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[2];
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
-        RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
+        RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo(
                 AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
                 SIGNAL_QUALITY);
         openAidlClients(/* numClients= */ 1);
         mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
 
-        mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+        mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
-                .onCurrentProgramInfoChanged(scanUpInfo);
+                .onCurrentProgramInfoChanged(seekUpInfo);
     }
 
     @Test
@@ -585,7 +599,7 @@
     }
 
     @Test
-    public void onConfigFlagUpdated_forTunerCallback() throws Exception {
+    public void onAntennaStateChange_forTunerCallback() throws Exception {
         int numSessions = 3;
         openAidlClients(numSessions);
 
@@ -598,6 +612,21 @@
     }
 
     @Test
+    public void onConfigFlagUpdated_forTunerCallback() throws Exception {
+        int numSessions = 3;
+        openAidlClients(numSessions);
+        int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+        boolean configFlagValue = true;
+
+        mHalTunerCallback.onConfigFlagUpdated(flag, configFlagValue);
+
+        for (int index = 0; index < numSessions; index++) {
+            verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+                    .onConfigFlagUpdated(flag, configFlagValue);
+        }
+    }
+
+    @Test
     public void onParametersUpdated_forTunerCallback() throws Exception {
         int numSessions = 3;
         openAidlClients(numSessions);
@@ -612,13 +641,17 @@
                     .onParametersUpdated(parametersExpected);
         }
     }
-
     private void openAidlClients(int numClients) throws Exception {
+        openAidlClients(numClients, TARGET_SDK_VERSION);
+    }
+
+    private void openAidlClients(int numClients, int targetSdkVersion) throws Exception {
         mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
         mTunerSessions = new TunerSession[numClients];
         for (int index = 0; index < numClients; index++) {
             mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
-            mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
+            mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index],
+                    targetSdkVersion);
         }
     }
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertResultTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertResultTest.java
new file mode 100644
index 0000000..d106de9
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertResultTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.broadcastradio.hal2;
+
+import android.hardware.broadcastradio.V2_0.Result;
+import android.hardware.radio.RadioTuner;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public final class ConvertResultTest {
+
+    private final int mHalResult;
+    private final int mTunerResult;
+
+    @Rule
+    public final Expect expect = Expect.create();
+
+    public ConvertResultTest(int halResult, int tunerResult) {
+        this.mHalResult = halResult;
+        this.mTunerResult = tunerResult;
+    }
+
+    @Parameterized.Parameters
+    public static List<Object[]> inputParameters() {
+        return Arrays.asList(new Object[][]{
+                {Result.OK, RadioTuner.TUNER_RESULT_OK},
+                {Result.INTERNAL_ERROR, RadioTuner.TUNER_RESULT_INTERNAL_ERROR},
+                {Result.INVALID_ARGUMENTS, RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS},
+                {Result.INVALID_STATE, RadioTuner.TUNER_RESULT_INVALID_STATE},
+                {Result.NOT_SUPPORTED, RadioTuner.TUNER_RESULT_NOT_SUPPORTED},
+                {Result.TIMEOUT, RadioTuner.TUNER_RESULT_TIMEOUT},
+                {Result.UNKNOWN_ERROR, RadioTuner.TUNER_RESULT_UNKNOWN_ERROR}
+        });
+    }
+
+    @Test
+    public void halResultToTunerResult() {
+        expect.withMessage("Tuner result converted from HAL 2.0 result %s",
+                Result.toString(mHalResult))
+                .that(Convert.halResultToTunerResult(mHalResult))
+                .isEqualTo(mTunerResult);
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
index ff988a2..db16c03 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -384,49 +384,49 @@
     }
 
     @Test
-    public void scan_withDirectionUp() throws Exception {
+    public void seek_withDirectionUp() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[2];
         ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
-        RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
+        RadioManager.ProgramInfo seekUpInfo = TestUtils.makeProgramInfo(
                 TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
                 SIGNAL_QUALITY);
         openAidlClients(/* numClients= */ 1);
         mHalCurrentInfo = TestUtils.makeHalProgramInfo(
                 Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
 
-        mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+        mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
 
         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
-                .onCurrentProgramInfoChanged(scanUpInfo);
+                .onCurrentProgramInfoChanged(seekUpInfo);
     }
 
     @Test
-    public void scan_callsOnTuneFailedWhenTimeout() throws Exception {
+    public void seek_callsOnTuneFailedWhenTimeout() throws Exception {
         int numSessions = 2;
         openAidlClients(numSessions);
 
-        mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+        mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
 
         for (int index = 0; index < numSessions; index++) {
             verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
-                    .onTuneFailed(eq(Result.TIMEOUT), any());
+                    .onTuneFailed(eq(RadioTuner.TUNER_RESULT_TIMEOUT), any());
         }
     }
 
     @Test
-    public void scan_withDirectionDown() throws Exception {
+    public void seek_withDirectionDown() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[2];
         ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
-        RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
+        RadioManager.ProgramInfo seekUpInfo = TestUtils.makeProgramInfo(
                 TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
                 SIGNAL_QUALITY);
         openAidlClients(/* numClients= */ 1);
         mHalCurrentInfo = TestUtils.makeHalProgramInfo(
                 Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
 
-        mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+        mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
-                .onCurrentProgramInfoChanged(scanUpInfo);
+                .onCurrentProgramInfoChanged(seekUpInfo);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/companion/virtual/OWNERS b/core/tests/coretests/src/android/companion/virtual/OWNERS
new file mode 100644
index 0000000..1a3e927
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include /services/companion/java/com/android/server/companion/virtual/OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java b/core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java
new file mode 100644
index 0000000..694b312
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 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 android.companion.virtual.camera;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.graphics.PixelFormat;
+import android.hardware.camera2.params.InputConfiguration;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class VirtualCameraOutputTest {
+
+    private static final String TAG = "VirtualCameraOutputTest";
+
+    private ExecutorService mExecutor;
+
+    private InputConfiguration mConfiguration;
+
+    @Before
+    public void setUp() {
+        mExecutor = Executors.newSingleThreadExecutor();
+        mConfiguration = new InputConfiguration(64, 64, PixelFormat.RGB_888);
+    }
+
+    @After
+    public void cleanUp() {
+        mExecutor.shutdownNow();
+    }
+
+    @Test
+    public void createStreamDescriptor_successfulDataStream() {
+        byte[] cameraData = new byte[]{1, 2, 3, 4, 5};
+        VirtualCameraInput input = createCameraInput(cameraData);
+        VirtualCameraOutput output = new VirtualCameraOutput(input, mExecutor);
+        ParcelFileDescriptor descriptor = output.getStreamDescriptor(mConfiguration);
+
+        try (FileInputStream fis = new FileInputStream(descriptor.getFileDescriptor())) {
+            byte[] receivedData = fis.readNBytes(cameraData.length);
+
+            output.closeStream();
+            assertThat(receivedData).isEqualTo(cameraData);
+        } catch (IOException exception) {
+            fail("Unable to read bytes from FileInputStream. Message: " + exception.getMessage());
+        }
+    }
+
+    @Test
+    public void createStreamDescriptor_multipleCallsSameStream() {
+        VirtualCameraInput input = createCameraInput(new byte[]{0});
+        VirtualCameraOutput output = new VirtualCameraOutput(input, mExecutor);
+
+        ParcelFileDescriptor firstDescriptor = output.getStreamDescriptor(mConfiguration);
+        ParcelFileDescriptor secondDescriptor = output.getStreamDescriptor(mConfiguration);
+
+        assertThat(firstDescriptor).isSameInstanceAs(secondDescriptor);
+    }
+
+    @Test
+    public void createStreamDescriptor_differentStreams() {
+        VirtualCameraInput input = createCameraInput(new byte[]{0});
+        VirtualCameraOutput callback = new VirtualCameraOutput(input, mExecutor);
+
+        InputConfiguration differentConfig = new InputConfiguration(mConfiguration.getWidth() + 1,
+                mConfiguration.getHeight() + 1, mConfiguration.getFormat());
+
+        ParcelFileDescriptor firstDescriptor = callback.getStreamDescriptor(mConfiguration);
+        ParcelFileDescriptor secondDescriptor = callback.getStreamDescriptor(differentConfig);
+
+        assertThat(firstDescriptor).isNotSameInstanceAs(secondDescriptor);
+    }
+
+    private VirtualCameraInput createCameraInput(byte[] data) {
+        return new VirtualCameraInput() {
+            private ByteArrayInputStream mInputStream = null;
+
+            @Override
+            @NonNull
+            public InputStream openStream(@NonNull InputConfiguration inputConfiguration) {
+                closeStream();
+                mInputStream = new ByteArrayInputStream(data);
+                return mInputStream;
+            }
+
+            @Override
+            public void closeStream() {
+                if (mInputStream == null) {
+                    return;
+                }
+                try {
+                    mInputStream.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "Unable to close image stream.", e);
+                }
+                mInputStream = null;
+            }
+        };
+    }
+}
diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index bc356f8..324f810 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -16,7 +16,7 @@
 
 package android.content;
 
-import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -216,7 +216,7 @@
         final Context systemContext =
                 ActivityThread.currentActivityThread().getSystemContext();
 
-        assertEquals(systemContext.getDeviceId(), DEFAULT_DEVICE_ID);
+        assertEquals(systemContext.getDeviceId(), DEVICE_ID_DEFAULT);
     }
 
     @Test
@@ -224,7 +224,7 @@
         final Context systemUiContext =
                 ActivityThread.currentActivityThread().getSystemUiContext();
 
-        assertEquals(systemUiContext.getDeviceId(), DEFAULT_DEVICE_ID);
+        assertEquals(systemUiContext.getDeviceId(), DEVICE_ID_DEFAULT);
     }
 
     @Test
@@ -232,7 +232,7 @@
         final Context testContext =
                 InstrumentationRegistry.getInstrumentation().getTargetContext();
 
-        assertEquals(testContext.getDeviceId(), DEFAULT_DEVICE_ID);
+        assertEquals(testContext.getDeviceId(), DEVICE_ID_DEFAULT);
     }
 
     private Context createUiContext() {
diff --git a/core/tests/coretests/src/android/os/EnvironmentTest.java b/core/tests/coretests/src/android/os/EnvironmentTest.java
index 8e63a0f..ef38cde 100644
--- a/core/tests/coretests/src/android/os/EnvironmentTest.java
+++ b/core/tests/coretests/src/android/os/EnvironmentTest.java
@@ -22,12 +22,12 @@
 import static android.os.Environment.HAS_OTHER;
 import static android.os.Environment.classifyExternalStorageDirectory;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.app.AppOpsManager;
+import static org.junit.Assert.assertEquals;
+
 import android.content.Context;
+import android.os.storage.StorageManager;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -38,6 +38,9 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.UUID;
+import java.util.function.BiFunction;
 
 @RunWith(AndroidJUnit4.class)
 public class EnvironmentTest {
@@ -104,4 +107,42 @@
         Environment.buildPath(dir, "Taxes.pdf").createNewFile();
         assertEquals(HAS_OTHER, classifyExternalStorageDirectory(dir));
     }
+
+    @Test
+    public void testDataCePackageDirectoryForUser() {
+        testDataPackageDirectoryForUser(
+                (uuid, userHandle) -> Environment.getDataCePackageDirectoryForUser(
+                        uuid, userHandle, getContext().getPackageName()),
+                (uuid, user) -> Environment.getDataUserCePackageDirectory(
+                        uuid, user, getContext().getPackageName())
+        );
+    }
+
+    @Test
+    public void testDataDePackageDirectoryForUser() {
+        testDataPackageDirectoryForUser(
+                (uuid, userHandle) -> Environment.getDataDePackageDirectoryForUser(
+                        uuid, userHandle, getContext().getPackageName()),
+                (uuid, user) -> Environment.getDataUserDePackageDirectory(
+                        uuid, user, getContext().getPackageName())
+        );
+    }
+
+    private void testDataPackageDirectoryForUser(
+            BiFunction<UUID, UserHandle, File> publicApi,
+            BiFunction<String, Integer, File> hideApi) {
+        var uuids = new ArrayList<String>();
+        uuids.add(null); // Private internal
+        uuids.add("primary_physical");
+        uuids.add("system");
+        uuids.add("3939-3939"); // FAT Volume
+        uuids.add("57554103-df3e-4475-ae7a-8feba49353ac"); // Random valid UUID
+        var userHandle = UserHandle.of(0);
+
+        // Check that the @hide method is consistent with the public API
+        for (String uuid : uuids) {
+            assertThat(publicApi.apply(StorageManager.convert(uuid), userHandle))
+                    .isEqualTo(hideApi.apply(uuid, 0));
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 7674135..8f83461 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -20,7 +20,6 @@
 
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_LAUNCH_CAMERA;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_ADD;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_APP_START;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_APPEAR;
@@ -32,7 +31,6 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_EXPAND;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE;
 import static com.android.internal.jank.InteractionJankMonitor.MAX_LENGTH_OF_CUJ_NAME;
 import static com.android.internal.jank.InteractionJankMonitor.getNameOfCuj;
@@ -92,7 +90,6 @@
 public class InteractionJankMonitorTest {
     private static final String CUJ_POSTFIX = "";
     private static final SparseArray<String> ENUM_NAME_EXCEPTION_MAP = new SparseArray<>();
-    private static final SparseArray<String> CUJ_NAME_EXCEPTION_MAP = new SparseArray<>();
     private static final String ENUM_NAME_PREFIX =
             "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__";
 
@@ -129,22 +126,6 @@
                 CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE"));
         ENUM_NAME_EXCEPTION_MAP.put(
                 CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"));
-
-        CUJ_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, "CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE");
-        CUJ_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
-                "CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE");
-        CUJ_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, "CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE");
-        CUJ_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_ROW_EXPAND, "CUJ_NOTIFICATION_SHADE_ROW_EXPAND");
-        CUJ_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_ROW_SWIPE, "CUJ_NOTIFICATION_SHADE_ROW_SWIPE");
-        CUJ_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_SCROLL_FLING, "CUJ_NOTIFICATION_SHADE_SCROLL_FLING");
-        CUJ_NAME_EXCEPTION_MAP.put(CUJ_LOCKSCREEN_LAUNCH_CAMERA, "CUJ_LOCKSCREEN_LAUNCH_CAMERA");
-        CUJ_NAME_EXCEPTION_MAP.put(CUJ_SPLIT_SCREEN_RESIZE, "CUJ_SPLIT_SCREEN_RESIZE");
     }
 
     private static String getEnumName(String name) {
@@ -272,9 +253,8 @@
                     : formatSimple("%s%s", ENUM_NAME_PREFIX, cujName.substring(4));
             final int enumKey = CUJ_TO_STATSD_INTERACTION_TYPE[cuj];
             final String enumName = enumsMap.get(enumKey);
-            final String expectedNameOfCuj = CUJ_NAME_EXCEPTION_MAP.contains(cuj)
-                    ? CUJ_NAME_EXCEPTION_MAP.get(cuj)
-                    : formatSimple("CUJ_%s", getNameOfCuj(cuj));
+            final String expectedNameOfCuj = formatSimple("CUJ_%s", getNameOfCuj(cuj));
+
             mExpect
                     .withMessage(formatSimple(
                             "%s (%d) not matches %s (%d)", cujName, cuj, enumName, enumKey))
@@ -323,7 +303,7 @@
         // Since the length of the cuj name is tested in another test, no need to test it here.
         // Too long postfix case, should trim the postfix and keep the cuj name completed.
         final String expectedTrimmedName = formatSimple("J<%s::%s>", cujName,
-                "ThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagT...");
+                "ThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThi...");
         Session longPostfix = new Session(cujType, tooLongTag);
         assertThat(longPostfix.getName()).isEqualTo(expectedTrimmedName);
     }
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index 3958bb7..e278c52 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -63,5 +63,6 @@
         <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
         <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
         <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
+        <permission name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS" />
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index e0b2e8a..4109425 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -387,6 +387,7 @@
         <permission name="android.permission.MANAGE_COMPANION_DEVICES" />
         <permission name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" />
         <permission name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
+        <permission name="android.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING" />
         <permission name="android.permission.REQUEST_COMPANION_PROFILE_COMPUTER" />
         <permission name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
         <!-- Permission required for testing registering pull atom callbacks. -->
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 3346740..1ab5e4b 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -169,6 +169,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1944652783": {
+      "message": "Unable to tell MediaProjectionManagerService to stop the active projection: %s",
+      "level": "ERROR",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-1941440781": {
       "message": "Creating Pending Move-to-back: %s",
       "level": "VERBOSE",
@@ -619,6 +625,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityStarter.java"
     },
+    "-1484988952": {
+      "message": "Creating Pending Multiwindow Fullscreen Request: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/ActivityClientController.java"
+    },
     "-1483435730": {
       "message": "InsetsSource setWin %s for type %s",
       "level": "DEBUG",
@@ -709,6 +721,12 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1423223548": {
+      "message": "Unable to tell MediaProjectionManagerService about resizing the active projection: %s",
+      "level": "ERROR",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-1421296808": {
       "message": "Moving to RESUMED: %s (in existing)",
       "level": "VERBOSE",
@@ -805,6 +823,12 @@
       "group": "WM_DEBUG_CONTENT_RECORDING",
       "at": "com\/android\/server\/wm\/ContentRecorder.java"
     },
+    "-1323783276": {
+      "message": "performEnableScreen: bootFinished() failed.",
+      "level": "WARN",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-1318478129": {
       "message": "applyAnimation: win=%s anim=%d attr=0x%x a=%s transit=%d type=%d isEntrance=%b Callers %s",
       "level": "VERBOSE",
@@ -1603,6 +1627,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-576580969": {
+      "message": "viewServerWindowCommand: bootFinished() failed.",
+      "level": "WARN",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-576070986": {
       "message": "Performing post-rotate rotation after seamless rotation",
       "level": "INFO",
@@ -2113,12 +2143,6 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
-    "-87703044": {
-      "message": "Boot completed: SurfaceFlinger is dead!",
-      "level": "ERROR",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-86763148": {
       "message": "  KILL SURFACE SESSION %s",
       "level": "INFO",
@@ -2911,12 +2935,6 @@
       "group": "WM_DEBUG_CONTENT_RECORDING",
       "at": "com\/android\/server\/wm\/ContentRecorder.java"
     },
-    "620368427": {
-      "message": "******* TELLING SURFACE FLINGER WE ARE BOOTED!",
-      "level": "INFO",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "620519522": {
       "message": "findFocusedWindow: No focusable windows, display=%d",
       "level": "VERBOSE",
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index a43e225..0f8616d 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -139,7 +139,7 @@
 key 117   NUMPAD_EQUALS
 # key 118 "KEY_KPPLUSMINUS"
 key 119   BREAK
-# key 120 (undefined)
+key 120   RECENT_APPS
 key 121   NUMPAD_COMMA
 key 122   KANA
 key 123   EISU
diff --git a/identity/java/android/security/identity/AuthenticationKeyMetadata.java b/identity/java/android/security/identity/AuthenticationKeyMetadata.java
new file mode 100644
index 0000000..c6abc22
--- /dev/null
+++ b/identity/java/android/security/identity/AuthenticationKeyMetadata.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2022 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 android.security.identity;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+
+import java.time.Instant;
+
+/**
+ * Data about authentication keys.
+ */
+public final class AuthenticationKeyMetadata {
+    private int mUsageCount;
+    private Instant mExpirationDate;
+
+    AuthenticationKeyMetadata(int usageCount, @NonNull Instant expirationDate) {
+        mUsageCount = usageCount;
+        mExpirationDate = expirationDate;
+    }
+
+    /**
+     * Gets usage count for the authentication key.
+     *
+     * @return the usage count
+     */
+    public @IntRange(from = 0) int getUsageCount() {
+        return mUsageCount;
+    }
+
+    /**
+     * Gets expiration date for the authentication key.
+     *
+     * @return the expiration date of the authentication key.
+     */
+    public @NonNull Instant getExpirationDate() {
+        return mExpirationDate;
+    }
+}
diff --git a/identity/java/android/security/identity/CredentialDataResult.java b/identity/java/android/security/identity/CredentialDataResult.java
index beb03af..dca039a 100644
--- a/identity/java/android/security/identity/CredentialDataResult.java
+++ b/identity/java/android/security/identity/CredentialDataResult.java
@@ -106,6 +106,30 @@
     public abstract @Nullable byte[] getDeviceMac();
 
     /**
+     * Returns a signature over the {@code DeviceAuthenticationBytes} CBOR
+     * specified in {@link #getDeviceNameSpaces()}, to prove to the reader that the data
+     * is from a trusted credential.
+     *
+     * <p>The signature is made using the authentication private key. See section 9.1.3.4 of
+     * ISO/IEC 18013-5:2021 for details of this operation.
+     *
+     * <p>If the session transcript or reader ephemeral key wasn't set on the {@link
+     * PresentationSession} used to obtain this data no signature will be produced and this method
+     * will return {@code null}.
+     *
+     * <p>This is only implemented in feature version 202301 or later. If not implemented, the call
+     * fails with {@link UnsupportedOperationException}. See
+     * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+     * feature versions.
+     *
+     * @return A COSE_Sign1 structure as described above or {@code null} if the conditions
+     *     specified above are not met.
+     */
+    public @Nullable byte[] getDeviceSignature() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Returns the static authentication data associated with the dynamic authentication
      * key used to MAC the data returned by {@link #getDeviceNameSpaces()}.
      *
diff --git a/identity/java/android/security/identity/CredstoreCredentialDataResult.java b/identity/java/android/security/identity/CredstoreCredentialDataResult.java
index 7afe3d4..b4fd5d3 100644
--- a/identity/java/android/security/identity/CredstoreCredentialDataResult.java
+++ b/identity/java/android/security/identity/CredstoreCredentialDataResult.java
@@ -47,6 +47,11 @@
     }
 
     @Override
+    public @Nullable byte[] getDeviceSignature() {
+        return mDeviceSignedResult.getSignature();
+    }
+
+    @Override
     public @NonNull byte[] getStaticAuthenticationData() {
         return mDeviceSignedResult.getStaticAuthenticationData();
     }
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java
index c591c87..449c7a7 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java
@@ -60,16 +60,19 @@
     private Context mContext;
     private ICredential mBinder;
     private CredstorePresentationSession mSession;
+    private int mFeatureVersion;
 
     CredstoreIdentityCredential(Context context, String credentialName,
             @IdentityCredentialStore.Ciphersuite int cipherSuite,
             ICredential binder,
-            @Nullable CredstorePresentationSession session) {
+            @Nullable CredstorePresentationSession session,
+            int featureVersion) {
         mContext = context;
         mCredentialName = credentialName;
         mCipherSuite = cipherSuite;
         mBinder = binder;
         mSession = session;
+        mFeatureVersion = featureVersion;
     }
 
     private KeyPair mEphemeralKeyPair = null;
@@ -347,12 +350,18 @@
             }
         }
 
+        byte[] signature = resultParcel.signature;
+        if (signature != null && signature.length == 0) {
+            signature = null;
+        }
+
         byte[] mac = resultParcel.mac;
         if (mac != null && mac.length == 0) {
             mac = null;
         }
         CredstoreResultData.Builder resultDataBuilder = new CredstoreResultData.Builder(
-                resultParcel.staticAuthenticationData, resultParcel.deviceNameSpaces, mac);
+                mFeatureVersion, resultParcel.staticAuthenticationData,
+                resultParcel.deviceNameSpaces, mac, signature);
 
         for (ResultNamespaceParcel resultNamespaceParcel : resultParcel.resultNamespaces) {
             for (ResultEntryParcel resultEntryParcel : resultNamespaceParcel.entries) {
@@ -371,8 +380,14 @@
 
     @Override
     public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) {
+        setAvailableAuthenticationKeys(keyCount, maxUsesPerKey, 0);
+    }
+
+    @Override
+    public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey,
+                                               long minValidTimeMillis) {
         try {
-            mBinder.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey);
+            mBinder.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey, minValidTimeMillis);
         } catch (android.os.RemoteException e) {
             throw new RuntimeException("Unexpected RemoteException ", e);
         } catch (android.os.ServiceSpecificException e) {
@@ -472,6 +487,34 @@
     }
 
     @Override
+    public @NonNull List<AuthenticationKeyMetadata> getAuthenticationKeyMetadata() {
+        try {
+            int[] usageCount = mBinder.getAuthenticationDataUsageCount();
+            long[] expirationsMillis = mBinder.getAuthenticationDataExpirations();
+            if (usageCount.length != expirationsMillis.length) {
+                throw new IllegalStateException("Size og usageCount and expirationMillis differ");
+            }
+            List<AuthenticationKeyMetadata> mds = new ArrayList<>();
+            for (int n = 0; n < expirationsMillis.length; n++) {
+                AuthenticationKeyMetadata md = null;
+                long expirationMillis = expirationsMillis[n];
+                if (expirationMillis != Long.MAX_VALUE) {
+                    md = new AuthenticationKeyMetadata(
+                        usageCount[n],
+                        Instant.ofEpochMilli(expirationMillis));
+                }
+                mds.add(md);
+            }
+            return mds;
+        } catch (android.os.RemoteException e) {
+            throw new IllegalStateException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new IllegalStateException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @Override
     public @NonNull byte[] proveOwnership(@NonNull byte[] challenge) {
         try {
             byte[] proofOfOwnership = mBinder.proveOwnership(challenge);
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
index bbaf086..d785c3c 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.security.GenerateRkpKey;
@@ -30,10 +32,28 @@
 
     private Context mContext = null;
     private ICredentialStore mStore = null;
+    private int mFeatureVersion;
+
+    static int getFeatureVersion(@NonNull Context context) {
+        PackageManager pm = context.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
+            FeatureInfo[] infos = pm.getSystemAvailableFeatures();
+            for (int n = 0; n < infos.length; n++) {
+                FeatureInfo info = infos[n];
+                if (info.name.equals(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
+                    return info.version;
+                }
+            }
+        }
+        // Use of the system feature is not required since Android 12. So for Android 11
+        // return 202009 which is the feature version shipped with Android 11.
+        return 202009;
+    }
 
     private CredstoreIdentityCredentialStore(@NonNull Context context, ICredentialStore store) {
         mContext = context;
         mStore = store;
+        mFeatureVersion = getFeatureVersion(mContext);
     }
 
     static CredstoreIdentityCredentialStore getInstanceForType(@NonNull Context context,
@@ -139,8 +159,7 @@
             ICredential credstoreCredential;
             credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite);
             return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite,
-                    credstoreCredential,
-                    null);
+                    credstoreCredential, null, mFeatureVersion);
         } catch (android.os.RemoteException e) {
             throw new RuntimeException("Unexpected RemoteException ", e);
         } catch (android.os.ServiceSpecificException e) {
@@ -182,7 +201,8 @@
             throws CipherSuiteNotSupportedException {
         try {
             ISession credstoreSession = mStore.createPresentationSession(cipherSuite);
-            return new CredstorePresentationSession(mContext, cipherSuite, this, credstoreSession);
+            return new CredstorePresentationSession(mContext, cipherSuite, this, credstoreSession,
+                                                    mFeatureVersion);
         } catch (android.os.RemoteException e) {
             throw new RuntimeException("Unexpected RemoteException ", e);
         } catch (android.os.ServiceSpecificException e) {
diff --git a/identity/java/android/security/identity/CredstorePresentationSession.java b/identity/java/android/security/identity/CredstorePresentationSession.java
index e3c6689..96bda52 100644
--- a/identity/java/android/security/identity/CredstorePresentationSession.java
+++ b/identity/java/android/security/identity/CredstorePresentationSession.java
@@ -48,15 +48,18 @@
     private byte[] mSessionTranscript = null;
     private boolean mOperationHandleSet = false;
     private long mOperationHandle = 0;
+    private int mFeatureVersion = 0;
 
     CredstorePresentationSession(Context context,
             @IdentityCredentialStore.Ciphersuite int cipherSuite,
             CredstoreIdentityCredentialStore store,
-            ISession binder) {
+            ISession binder,
+            int featureVersion) {
         mContext = context;
         mCipherSuite = cipherSuite;
         mStore = store;
         mBinder = binder;
+        mFeatureVersion = featureVersion;
     }
 
     private void ensureEphemeralKeyPair() {
@@ -147,7 +150,7 @@
                     mBinder.getCredentialForPresentation(credentialName);
                 credential = new CredstoreIdentityCredential(mContext, credentialName,
                                                              mCipherSuite, credstoreCredential,
-                                                             this);
+                                                             this, mFeatureVersion);
                 mCredentialCache.put(credentialName, credential);
 
                 credential.setAllowUsingExhaustedKeys(request.isAllowUsingExhaustedKeys());
diff --git a/identity/java/android/security/identity/CredstoreResultData.java b/identity/java/android/security/identity/CredstoreResultData.java
index 2ef735e..57c0436 100644
--- a/identity/java/android/security/identity/CredstoreResultData.java
+++ b/identity/java/android/security/identity/CredstoreResultData.java
@@ -30,10 +30,11 @@
  * data requested from a {@link IdentityCredential}.
  */
 class CredstoreResultData extends ResultData {
-
+    int mFeatureVersion = 0;
     byte[] mStaticAuthenticationData = null;
     byte[] mAuthenticatedData = null;
     byte[] mMessageAuthenticationCode = null;
+    byte[] mSignature = null;
 
     private Map<String, Map<String, EntryData>> mData = new LinkedHashMap<>();
 
@@ -61,6 +62,14 @@
     }
 
     @Override
+    @Nullable byte[] getSignature() {
+        if (mFeatureVersion < 202301) {
+            throw new UnsupportedOperationException();
+        }
+        return mSignature;
+    }
+
+    @Override
     public @NonNull byte[] getStaticAuthenticationData() {
         return mStaticAuthenticationData;
     }
@@ -124,13 +133,17 @@
     static class Builder {
         private CredstoreResultData mResultData;
 
-        Builder(byte[] staticAuthenticationData,
+        Builder(int featureVersion,
+                byte[] staticAuthenticationData,
                 byte[] authenticatedData,
-                byte[] messageAuthenticationCode) {
+                byte[] messageAuthenticationCode,
+                byte[] signature) {
             this.mResultData = new CredstoreResultData();
+            this.mResultData.mFeatureVersion = featureVersion;
             this.mResultData.mStaticAuthenticationData = staticAuthenticationData;
             this.mResultData.mAuthenticatedData = authenticatedData;
             this.mResultData.mMessageAuthenticationCode = messageAuthenticationCode;
+            this.mResultData.mSignature = signature;
         }
 
         private Map<String, EntryData> getOrCreateInnerMap(String namespaceName) {
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index f440b69..2dd9778 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -16,6 +16,7 @@
 
 package android.security.identity;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
@@ -25,6 +26,7 @@
 import java.security.cert.X509Certificate;
 import java.time.Instant;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -236,14 +238,15 @@
      *   IntentToRetain = bool
      * </pre>
      *
-     * <p>If the {@code sessionTranscript} parameter is not {@code null}, the X and Y coordinates
-     * of the public part of the key-pair previously generated by {@link #createEphemeralKeyPair()}
-     * must appear somewhere in the bytes of the CBOR. Each of these coordinates must appear
-     * encoded with the most significant bits first and use the exact amount of bits indicated by
-     * the key size of the ephemeral keys. For example, if the ephemeral key is using the P-256
-     * curve then the 32 bytes for the X coordinate encoded with the most significant bits first
-     * must appear somewhere in {@code sessionTranscript} and ditto for the 32 bytes for the Y
-     * coordinate.
+     * <p>If mdoc session encryption is used (e.g. if {@link #createEphemeralKeyPair()} has been
+     * called) and if the {@code sessionTranscript} parameter is not {@code null}, the X and Y
+     * coordinates of the public part of the key-pair previously generated by
+     * {@link #createEphemeralKeyPair()} must appear somewhere in the bytes of the CBOR. Each of
+     * these coordinates must appear encoded with the most significant bits first and use the
+     * exact amount of bits indicated by the key size of the ephemeral keys. For example, if the
+     * ephemeral key is using the P-256 curve then the 32 bytes for the X coordinate encoded with
+     * the most significant bits first must appear somewhere in {@code sessionTranscript} and
+     * ditto for the 32 bytes for the Y coordinate.
      *
      * <p>If {@code readerSignature} is not {@code null} it must be the bytes of a
      * {@code COSE_Sign1} structure as defined in RFC 8152. For the payload nil shall be used and
@@ -296,7 +299,7 @@
      *                                                session transcripts.
      * @throws NoAuthenticationKeyAvailableException  if authentication keys were never
      *                                                provisioned, the method
-     *                                             {@link #setAvailableAuthenticationKeys(int, int)}
+     *                                      {@link #setAvailableAuthenticationKeys(int, int, long)}
      *                                                was called with {@code keyCount} set to 0,
      *                                                the method
      *                                                {@link #setAllowUsingExhaustedKeys(boolean)}
@@ -330,19 +333,25 @@
      * for which this method has not been called behave as though it had been called wit
      * {@code keyCount} 0 and {@code maxUsesPerKey} 1.
      *
+     * <p>The effect of this method is like calling
+     * {@link #setAvailableAuthenticationKeys(int, int, long)} with the last parameter is set to 0.
+     *
      * @param keyCount      The number of active, certified dynamic authentication keys the
      *                      {@code IdentityCredential} will try to keep available. This value
      *                      must be non-negative.
      * @param maxUsesPerKey The maximum number of times each of the keys will be used before it's
      *                      eligible for replacement. This value must be greater than zero.
+     * @deprecated Use {@link #setAvailableAuthenticationKeys(int, int, long)} instead.
      */
+    @Deprecated
     public abstract void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey);
 
     /**
      * Gets a collection of dynamic authentication keys that need certification.
      *
      * <p>When there aren't enough certified dynamic authentication keys, either because the key
-     * count has been increased or because one or more keys have reached their usage count, this
+     * count has been increased or because one or more keys have reached their usage count or
+     * it if a key is too close to its expiration date, this
      * method will generate replacement keys and certificates and return them for issuer
      * certification.  The issuer certificates and associated static authentication data must then
      * be provided back to the Identity Credential using
@@ -400,11 +409,6 @@
      * This should only be called for an authenticated key returned by
      * {@link #getAuthKeysNeedingCertification()}.
      *
-     * <p>This is only implemented in feature version 202101 or later. If not implemented, the call
-     * fails with {@link UnsupportedOperationException}. See
-     * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
-     * feature versions.
-     *
      * @param authenticationKey The dynamic authentication key for which certification and
      *                          associated static
      *                          authentication data is being provided.
@@ -426,7 +430,9 @@
      * Get the number of times the dynamic authentication keys have been used.
      *
      * @return int array of dynamic authentication key usage counts.
+     * @deprecated Use {@link #getAuthenticationKeyMetadata()} instead.
      */
+    @Deprecated
     public @NonNull abstract int[] getAuthenticationDataUsageCount();
 
     /**
@@ -519,4 +525,47 @@
     public @NonNull byte[] update(@NonNull PersonalizationData personalizationData) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * Sets the number of dynamic authentication keys the {@code IdentityCredential} will maintain,
+     * the number of times each should be used, and the minimum amount of time it's valid for.
+     *
+     * <p>The Identity Credential system will select the least-used dynamic authentication key each
+     * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. Identity Credentials
+     * for which this method has not been called behave as though it had been called wit
+     * {@code keyCount} 0, {@code maxUsesPerKey} 1, and {@code minValidTimeMillis} 0.
+     *
+     * <p>Applications can use {@link #getAuthenticationKeyMetadata()} to get a picture of the
+     * usage andtime left of each configured authentication key. This can be used to determine
+     * how urgent it is recertify new authentication keys via the
+     * {@link #getAuthKeysNeedingCertification()} method.
+     *
+     * @param keyCount      The number of active, certified dynamic authentication keys the
+     *                      {@code IdentityCredential} will try to keep available. This value
+     *                      must be non-negative.
+     * @param maxUsesPerKey The maximum number of times each of the keys will be used before it's
+     *                      eligible for replacement. This value must be greater than zero.
+     * @param minValidTimeMillis If a key has less time left than this value it will be eliglible
+     *                           for replacement. This value must be non-negative.
+     */
+    public void setAvailableAuthenticationKeys(
+            @IntRange(from = 0) int keyCount,
+            @IntRange(from = 1) int maxUsesPerKey,
+            @IntRange(from = 0) long minValidTimeMillis) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Get information about dynamic authentication keys.
+     *
+     * <p>The returned list may have <code>null</code> values if certification for the dynamic
+     * authentication key is pending.
+     *
+     * <p>The list is always <code>keyCount</code> elements long.
+     *
+     * @return list of authentication key metadata objects.
+     */
+    public @NonNull List<AuthenticationKeyMetadata> getAuthenticationKeyMetadata() {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/identity/java/android/security/identity/PresentationSession.java b/identity/java/android/security/identity/PresentationSession.java
index 6cde611..f392e12 100644
--- a/identity/java/android/security/identity/PresentationSession.java
+++ b/identity/java/android/security/identity/PresentationSession.java
@@ -73,7 +73,8 @@
      * <p>If called, this must be called before any calls to
      * {@link #getCredentialData(String, CredentialDataRequest)}.
      *
-     * <p>The X and Y coordinates of the public part of the key-pair returned by {@link
+     * <p>If mdoc session encryption is used (e.g. if {@link #getEphemeralKeyPair()} has been
+     * called) then the X and Y coordinates of the public part of the key-pair returned by {@link
      * #getEphemeralKeyPair()} must appear somewhere in the bytes of the passed in CBOR.  Each of
      * these coordinates must appear encoded with the most significant bits first and use the exact
      * amount of bits indicated by the key size of the ephemeral keys. For example, if the
diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java
index d46f985..8a0e56e 100644
--- a/identity/java/android/security/identity/ResultData.java
+++ b/identity/java/android/security/identity/ResultData.java
@@ -134,6 +134,10 @@
      */
     public abstract @Nullable byte[] getMessageAuthenticationCode();
 
+    @Nullable byte[] getSignature() {
+        throw new UnsupportedOperationException();
+    }
+
     /**
      * Returns the static authentication data associated with the dynamic authentication
      * key used to sign or MAC the data returned by {@link #getAuthenticatedData()}.
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 8881be7..36d3313 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -21,5 +21,6 @@
     <uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
+    <uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" />
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
 </manifest>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 70755e6..dcce4698 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -44,67 +44,15 @@
             android:background="@color/tv_pip_menu_dim_layer"
             android:alpha="0"/>
 
-        <ScrollView
-            android:id="@+id/tv_pip_menu_scroll"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:scrollbars="none"
-            android:visibility="gone"/>
-
-        <HorizontalScrollView
-            android:id="@+id/tv_pip_menu_horizontal_scroll"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:scrollbars="none">
-
-            <LinearLayout
-                android:id="@+id/tv_pip_menu_action_buttons"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:alpha="0">
-
-                <Space
-                    android:layout_width="@dimen/pip_menu_button_wrapper_margin"
-                    android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
-
-                <com.android.wm.shell.common.TvWindowMenuActionButton
-                    android:id="@+id/tv_pip_menu_fullscreen_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:src="@drawable/pip_ic_fullscreen_white"
-                    android:text="@string/pip_fullscreen" />
-
-                <com.android.wm.shell.common.TvWindowMenuActionButton
-                    android:id="@+id/tv_pip_menu_close_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:src="@drawable/pip_ic_close_white"
-                    android:text="@string/pip_close" />
-
-                <!-- More TvWindowMenuActionButtons may be added here at runtime. -->
-
-                <com.android.wm.shell.common.TvWindowMenuActionButton
-                    android:id="@+id/tv_pip_menu_move_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:src="@drawable/pip_ic_move_white"
-                    android:text="@string/pip_move" />
-
-                <com.android.wm.shell.common.TvWindowMenuActionButton
-                    android:id="@+id/tv_pip_menu_expand_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:src="@drawable/pip_ic_collapse"
-                    android:visibility="gone"
-                    android:text="@string/pip_collapse" />
-
-                <Space
-                    android:layout_width="@dimen/pip_menu_button_wrapper_margin"
-                    android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
-
-            </LinearLayout>
-        </HorizontalScrollView>
+        <com.android.internal.widget.RecyclerView
+            android:id="@+id/tv_pip_menu_action_buttons"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:padding="@dimen/pip_menu_button_start_end_offset"
+            android:clipToPadding="false"
+            android:alpha="0"
+            android:contentDescription="@string/a11y_pip_menu_entered"/>
     </FrameLayout>
 
     <!-- Frame around the content, just overlapping the corners to make them round -->
diff --git a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
index c4dbd39..b2ac85b 100644
--- a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
@@ -21,6 +21,7 @@
     android:layout_width="@dimen/tv_window_menu_button_size"
     android:layout_height="@dimen/tv_window_menu_button_size"
     android:padding="@dimen/tv_window_menu_button_margin"
+    android:duplicateParentState="true"
     android:stateListAnimator="@animator/tv_window_menu_action_button_animator"
     android:focusable="true">
 
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 1a7cf3e..9a926d8 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"दिखाएं"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"ऐप्लिकेशन शायद स्प्लिट स्क्रीन मोड में काम न करे."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ऐप विभाजित स्‍क्रीन का समर्थन नहीं करता है."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"इस ऐप्लिकेशन को सिर्फ़ एक विंडो में खोला जा सकता है."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"हो सकता है कि ऐप प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर काम न करे."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर ऐप लॉन्च नहीं किया जा सकता."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"विभाजित स्क्रीन विभाजक"</string>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 9833a88..0b61d7a 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -28,7 +28,7 @@
     <dimen name="pip_menu_background_corner_radius">6dp</dimen>
     <dimen name="pip_menu_border_width">4dp</dimen>
     <dimen name="pip_menu_outer_space">24dp</dimen>
-    <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
+    <dimen name="pip_menu_button_start_end_offset">30dp</dimen>
 
     <!-- outer space minus border width -->
     <dimen name="pip_menu_outer_space_frame">20dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index c6197c8..23db233 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -113,6 +113,6 @@
     <bool name="config_dimNonImeAttachedSide">true</bool>
 
     <!-- Components support to launch multiple instances into split-screen -->
-    <string-array name="config_componentsSupportMultiInstancesSplit">
+    <string-array name="config_appsSupportMultiInstancesSplit">
     </string-array>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index 39b0b55..8ba785a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -30,11 +32,11 @@
 /**
  * A common action button for TV window menu layouts.
  */
-public class TvWindowMenuActionButton extends RelativeLayout implements View.OnClickListener {
+public class TvWindowMenuActionButton extends RelativeLayout {
     private final ImageView mIconImageView;
     private final View mButtonBackgroundView;
-    private final View mButtonView;
-    private OnClickListener mOnClickListener;
+
+    private Icon mCurrentIcon;
 
     public TvWindowMenuActionButton(Context context) {
         this(context, null, 0, 0);
@@ -56,7 +58,6 @@
         inflater.inflate(R.layout.tv_window_menu_action_button, this);
 
         mIconImageView = findViewById(R.id.icon);
-        mButtonView = findViewById(R.id.button);
         mButtonBackgroundView = findViewById(R.id.background);
 
         final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
@@ -71,23 +72,6 @@
         typedArray.recycle();
     }
 
-    @Override
-    public void setOnClickListener(OnClickListener listener) {
-        // We do not want to set an OnClickListener to the TvWindowMenuActionButton itself, but only
-        // to the ImageView. So let's "cash" the listener we've been passed here and set a "proxy"
-        // listener to the ImageView.
-        mOnClickListener = listener;
-        mButtonView.setOnClickListener(listener != null ? this : null);
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (mOnClickListener != null) {
-            // Pass the correct view - this.
-            mOnClickListener.onClick(this);
-        }
-    }
-
     /**
      * Sets the drawable for the button with the given drawable.
      */
@@ -104,11 +88,24 @@
         }
     }
 
+    public void setImageIconAsync(Icon icon, Handler handler) {
+        mCurrentIcon = icon;
+        // Remove old image while waiting for the new one to load.
+        mIconImageView.setImageDrawable(null);
+        icon.loadDrawableAsync(mContext, d -> {
+            // The image hasn't been set any other way and the drawable belongs to the most
+            // recently set Icon.
+            if (mIconImageView.getDrawable() == null && mCurrentIcon == icon) {
+                mIconImageView.setImageDrawable(d);
+            }
+        }, handler);
+    }
+
     /**
      * Sets the text for description the with the given string.
      */
     public void setTextAndDescription(CharSequence text) {
-        mButtonView.setContentDescription(text);
+        setContentDescription(text);
     }
 
     /**
@@ -118,16 +115,6 @@
         setTextAndDescription(getContext().getString(resId));
     }
 
-    @Override
-    public void setEnabled(boolean enabled) {
-        mButtonView.setEnabled(enabled);
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return mButtonView.isEnabled();
-    }
-
     /**
      * Marks this button as a custom close action button.
      * This changes the style of the action button to highlight that this action finishes the
@@ -147,10 +134,10 @@
 
     @Override
     public String toString() {
-        if (mButtonView.getContentDescription() == null) {
+        if (getContentDescription() == null) {
             return TvWindowMenuActionButton.class.getSimpleName();
         }
-        return mButtonView.getContentDescription().toString();
+        return getContentDescription().toString();
     }
 
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 214b304..c634198 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -403,5 +403,10 @@
             }
             return true;
         }
+
+        @Override
+        public boolean onDoubleTapEvent(@NonNull MotionEvent e) {
+            return true;
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index c836b95..a9d3c9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -74,7 +74,8 @@
 
     private boolean mShown;
     private boolean mIsResizing;
-    private final Rect mBounds = new Rect();
+    private final Rect mOldBounds = new Rect();
+    private final Rect mResizingBounds = new Rect();
     private final Rect mTempRect = new Rect();
     private ValueAnimator mFadeAnimator;
 
@@ -158,6 +159,8 @@
         mResizingIconView = null;
         mIsResizing = false;
         mShown = false;
+        mOldBounds.setEmpty();
+        mResizingBounds.setEmpty();
     }
 
     /** Showing resizing hint. */
@@ -170,13 +173,14 @@
 
         if (!mIsResizing) {
             mIsResizing = true;
-            mBounds.set(newBounds);
+            mOldBounds.set(newBounds);
         }
+        mResizingBounds.set(newBounds);
         mOffsetX = offsetX;
         mOffsetY = offsetY;
 
         final boolean show =
-                newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height();
+                newBounds.width() > mOldBounds.width() || newBounds.height() > mOldBounds.height();
         final boolean update = show != mShown;
         if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) {
             // If we need to animate and animator still running, cancel it before we ensure both
@@ -193,8 +197,8 @@
 
         if (mGapBackgroundLeash == null && !immediately) {
             final boolean isLandscape = newBounds.height() == sideBounds.height();
-            final int left = isLandscape ? mBounds.width() : 0;
-            final int top = isLandscape ? 0 : mBounds.height();
+            final int left = isLandscape ? mOldBounds.width() : 0;
+            final int top = isLandscape ? 0 : mOldBounds.height();
             mGapBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
                     GAP_BACKGROUND_SURFACE_NAME, mSurfaceSession);
             // Fill up another side bounds area.
@@ -272,6 +276,8 @@
         mIsResizing = false;
         mOffsetX = 0;
         mOffsetY = 0;
+        mOldBounds.setEmpty();
+        mResizingBounds.setEmpty();
         if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
             if (!mShown) {
                 // If fade-out animation is running, just add release callback to it.
@@ -303,8 +309,8 @@
 
     /** Screenshot host leash and attach on it if meet some conditions */
     public void screenshotIfNeeded(SurfaceControl.Transaction t) {
-        if (!mShown && mIsResizing) {
-            mTempRect.set(mBounds);
+        if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+            mTempRect.set(mOldBounds);
             mTempRect.offsetTo(0, 0);
             mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
                     Integer.MAX_VALUE - 1);
@@ -315,7 +321,7 @@
     public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
         if (screenshot == null || !screenshot.isValid()) return;
 
-        if (!mShown && mIsResizing) {
+        if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
             mScreenshot = screenshot;
             t.reparent(screenshot, mHostLeash);
             t.setLayer(screenshot, Integer.MAX_VALUE - 1);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 8022e9b..b144d22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -81,6 +81,7 @@
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayController displayController,
             WindowManagerShellWrapper windowManagerShellWrapper,
+            @ShellMainThread Handler mainHandler, // needed for registerReceiverForAllUsers()
             @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.of(
                 TvPipController.create(
@@ -100,6 +101,7 @@
                         pipParamsChangedForwarder,
                         displayController,
                         windowManagerShellWrapper,
+                        mainHandler,
                         mainExecutor));
     }
 
@@ -157,22 +159,17 @@
             Context context,
             TvPipBoundsState tvPipBoundsState,
             SystemWindows systemWindows,
-            PipMediaController pipMediaController,
             @ShellMainThread Handler mainHandler) {
-        return new TvPipMenuController(context, tvPipBoundsState, systemWindows, pipMediaController,
-                mainHandler);
+        return new TvPipMenuController(context, tvPipBoundsState, systemWindows, mainHandler);
     }
 
-    // Handler needed for registerReceiverForAllUsers()
     @WMSingleton
     @Provides
     static TvPipNotificationController provideTvPipNotificationController(Context context,
             PipMediaController pipMediaController,
-            PipParamsChangedForwarder pipParamsChangedForwarder,
-            TvPipBoundsState tvPipBoundsState,
-            @ShellMainThread Handler mainHandler) {
+            PipParamsChangedForwarder pipParamsChangedForwarder) {
         return new TvPipNotificationController(context, pipMediaController,
-                pipParamsChangedForwarder, tvPipBoundsState, mainHandler);
+                pipParamsChangedForwarder);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 7eb01a7..f5f3573 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -40,6 +40,7 @@
 import android.provider.Settings;
 import android.util.ArraySet;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
 import android.window.DisplayAreaInfo;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
@@ -329,15 +330,17 @@
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
-        // Only do anything if we are in desktop mode and opening a task/app in freeform
+        // Only do anything if we are in desktop mode and opening/moving-to-front a task/app in
+        // freeform
         if (!DesktopModeStatus.isActive(mContext)) {
             ProtoLog.d(WM_SHELL_DESKTOP_MODE,
                     "skip shell transition request: desktop mode not active");
             return null;
         }
-        if (request.getType() != TRANSIT_OPEN) {
+        if (request.getType() != TRANSIT_OPEN && request.getType() != TRANSIT_TO_FRONT) {
             ProtoLog.d(WM_SHELL_DESKTOP_MODE,
-                    "skip shell transition request: only supports TRANSIT_OPEN");
+                    "skip shell transition request: unsupported type %s",
+                    WindowManager.transitTypeToString(request.getType()));
             return null;
         }
         if (request.getTriggerTask() == null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 62bf517..d93a901 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -240,7 +240,7 @@
             // Update launch options for the split side we are targeting.
             position = leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
             // Add some data for logging splitscreen once it is invoked
-            mSplitScreen.logOnDroppedToSplit(position, mLoggerSessionId);
+            mSplitScreen.onDroppedToSplit(position, mLoggerSessionId);
         }
 
         final ClipDescription description = data.getDescription();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
new file mode 100644
index 0000000..222307f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.pip.tv;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+abstract class TvPipAction {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"ACTION_"}, value = {
+            ACTION_FULLSCREEN,
+            ACTION_CLOSE,
+            ACTION_MOVE,
+            ACTION_EXPAND_COLLAPSE,
+            ACTION_CUSTOM,
+            ACTION_CUSTOM_CLOSE
+    })
+    public @interface ActionType {
+    }
+
+    public static final int ACTION_FULLSCREEN = 0;
+    public static final int ACTION_CLOSE = 1;
+    public static final int ACTION_MOVE = 2;
+    public static final int ACTION_EXPAND_COLLAPSE = 3;
+    public static final int ACTION_CUSTOM = 4;
+    public static final int ACTION_CUSTOM_CLOSE = 5;
+
+    @ActionType
+    private final int mActionType;
+
+    @NonNull
+    private final SystemActionsHandler mSystemActionsHandler;
+
+    TvPipAction(@ActionType int actionType, @NonNull SystemActionsHandler systemActionsHandler) {
+        Objects.requireNonNull(systemActionsHandler);
+        mActionType = actionType;
+        mSystemActionsHandler = systemActionsHandler;
+    }
+
+    boolean isCloseAction() {
+        return mActionType == ACTION_CLOSE || mActionType == ACTION_CUSTOM_CLOSE;
+    }
+
+    @ActionType
+    int getActionType() {
+        return mActionType;
+    }
+
+    abstract void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler);
+
+    abstract PendingIntent getPendingIntent();
+
+    void executeAction() {
+        mSystemActionsHandler.executeAction(mActionType);
+    }
+
+    abstract Notification.Action toNotificationAction(Context context);
+
+    interface SystemActionsHandler {
+        void executeAction(@TvPipAction.ActionType int actionType);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
new file mode 100644
index 0000000..fa62a73
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.pip.tv;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_CLOSE_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_MOVE_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_TOGGLE_EXPANDED_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_TO_FULLSCREEN;
+
+import android.annotation.NonNull;
+import android.app.RemoteAction;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Creates the system TvPipActions (fullscreen, close, move, expand/collapse),  and handles all the
+ * changes to the actions, including the custom app actions and media actions. Other components can
+ * listen to those changes.
+ */
+public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler {
+    private static final String TAG = TvPipActionsProvider.class.getSimpleName();
+
+    private static final int CLOSE_ACTION_INDEX = 1;
+    private static final int FIRST_CUSTOM_ACTION_INDEX = 2;
+
+    private final List<Listener> mListeners = new ArrayList<>();
+    private final TvPipAction.SystemActionsHandler mSystemActionsHandler;
+
+    private final List<TvPipAction> mActionsList;
+    private final TvPipSystemAction mDefaultCloseAction;
+    private final TvPipSystemAction mExpandCollapseAction;
+
+    private final List<RemoteAction> mMediaActions = new ArrayList<>();
+    private final List<RemoteAction> mAppActions = new ArrayList<>();
+
+    public TvPipActionsProvider(Context context, PipMediaController pipMediaController,
+            TvPipAction.SystemActionsHandler systemActionsHandler) {
+        mSystemActionsHandler = systemActionsHandler;
+
+        mActionsList = new ArrayList<>();
+        mActionsList.add(new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
+                R.drawable.pip_ic_fullscreen_white, ACTION_TO_FULLSCREEN, context,
+                mSystemActionsHandler));
+
+        mDefaultCloseAction = new TvPipSystemAction(ACTION_CLOSE, R.string.pip_close,
+                R.drawable.pip_ic_close_white, ACTION_CLOSE_PIP, context, mSystemActionsHandler);
+        mActionsList.add(mDefaultCloseAction);
+
+        mActionsList.add(new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
+                R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context, mSystemActionsHandler));
+
+        mExpandCollapseAction = new TvPipSystemAction(ACTION_EXPAND_COLLAPSE, R.string.pip_collapse,
+                R.drawable.pip_ic_collapse, ACTION_TOGGLE_EXPANDED_PIP, context,
+                mSystemActionsHandler);
+        mActionsList.add(mExpandCollapseAction);
+
+        pipMediaController.addActionListener(this::onMediaActionsChanged);
+    }
+
+    @Override
+    public void executeAction(@TvPipAction.ActionType int actionType) {
+        if (mSystemActionsHandler != null) {
+            mSystemActionsHandler.executeAction(actionType);
+        }
+    }
+
+    private void notifyActionsChanged(int added, int changed, int startIndex) {
+        for (Listener listener : mListeners) {
+            listener.onActionsChanged(added, changed, startIndex);
+        }
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    public void setAppActions(@NonNull List<RemoteAction> appActions, RemoteAction closeAction) {
+        // Update close action.
+        mActionsList.set(CLOSE_ACTION_INDEX,
+                closeAction == null ? mDefaultCloseAction
+                        : new TvPipCustomAction(ACTION_CUSTOM_CLOSE, closeAction,
+                                mSystemActionsHandler));
+        notifyActionsChanged(/* added= */ 0, /* updated= */ 1, CLOSE_ACTION_INDEX);
+
+        // Replace custom actions with new ones.
+        mAppActions.clear();
+        for (RemoteAction action : appActions) {
+            if (action != null && !PipUtils.remoteActionsMatch(action, closeAction)) {
+                // Only show actions that aren't duplicates of the custom close action.
+                mAppActions.add(action);
+            }
+        }
+
+        updateCustomActions(mAppActions);
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public void onMediaActionsChanged(List<RemoteAction> actions) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: onMediaActionsChanged()", TAG);
+
+        mMediaActions.clear();
+        // Don't show disabled actions.
+        for (RemoteAction remoteAction : actions) {
+            if (remoteAction.isEnabled()) {
+                mMediaActions.add(remoteAction);
+            }
+        }
+
+        updateCustomActions(mMediaActions);
+    }
+
+    private void updateCustomActions(@NonNull List<RemoteAction> customActions) {
+        List<RemoteAction> newCustomActions = customActions;
+        if (newCustomActions == mMediaActions && !mAppActions.isEmpty()) {
+            // Don't show the media actions while there are app actions.
+            return;
+        } else if (newCustomActions == mAppActions && mAppActions.isEmpty()) {
+            // If all the app actions were removed, show the media actions.
+            newCustomActions = mMediaActions;
+        }
+
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: replaceCustomActions, count: %d", TAG, newCustomActions.size());
+        int oldCustomActionsCount = 0;
+        for (TvPipAction action : mActionsList) {
+            if (action.getActionType() == ACTION_CUSTOM) {
+                oldCustomActionsCount++;
+            }
+        }
+        mActionsList.removeIf(tvPipAction -> tvPipAction.getActionType() == ACTION_CUSTOM);
+
+        List<TvPipAction> actions = new ArrayList<>();
+        for (RemoteAction action : newCustomActions) {
+            actions.add(new TvPipCustomAction(ACTION_CUSTOM, action, mSystemActionsHandler));
+        }
+        mActionsList.addAll(FIRST_CUSTOM_ACTION_INDEX, actions);
+
+        int added = newCustomActions.size() - oldCustomActionsCount;
+        int changed = Math.min(newCustomActions.size(), oldCustomActionsCount);
+        notifyActionsChanged(added, changed, FIRST_CUSTOM_ACTION_INDEX);
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    public void updateExpansionEnabled(boolean enabled) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: updateExpansionState, enabled: %b", TAG, enabled);
+        int actionIndex = mActionsList.indexOf(mExpandCollapseAction);
+        boolean actionInList = actionIndex != -1;
+        if (enabled && !actionInList) {
+            mActionsList.add(mExpandCollapseAction);
+            actionIndex = mActionsList.size() - 1;
+        } else if (!enabled && actionInList) {
+            mActionsList.remove(actionIndex);
+        } else {
+            return;
+        }
+        notifyActionsChanged(/* added= */ enabled ? 1 : -1, /* updated= */ 0, actionIndex);
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    public void onPipExpansionToggled(boolean expanded) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: onPipExpansionToggled, expanded: %b", TAG, expanded);
+
+        mExpandCollapseAction.update(
+                expanded ? R.string.pip_collapse : R.string.pip_expand,
+                expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
+
+        notifyActionsChanged(/* added= */ 0, /* updated= */ 1,
+                mActionsList.indexOf(mExpandCollapseAction));
+    }
+
+    List<TvPipAction> getActionsList() {
+        return mActionsList;
+    }
+
+    @NonNull
+    TvPipAction getCloseAction() {
+        return mActionsList.get(CLOSE_ACTION_INDEX);
+    }
+
+    void addListener(Listener listener) {
+        if (!mListeners.contains(listener)) {
+            mListeners.add(listener);
+        }
+    }
+
+    /**
+     * Returns the index of the first action of the given action type or -1 if none can be found.
+     */
+    int getFirstIndexOfAction(@TvPipAction.ActionType int actionType) {
+        for (int i = 0; i < mActionsList.size(); i++) {
+            if (mActionsList.get(i).getActionType() == actionType) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Allow components to listen to updates to the actions list, including where they happen so
+     * that changes can be animated.
+     */
+    interface Listener {
+        /**
+         * Notifies the listener how many actions were added/removed or updated.
+         *
+         * @param added      can be positive (number of actions added), negative (number of actions
+         *                   removed) or zero (the number of actions stayed the same).
+         * @param updated    the number of actions that might have been updated and need to be
+         *                   refreshed.
+         * @param startIndex The index of the first updated action. The added/removed actions start
+         *                   at (startIndex + updated).
+         */
+        void onActionsChanged(int added, int updated, int startIndex);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 3e8de45..7671081 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -22,13 +22,16 @@
 import android.annotation.IntDef;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.app.TaskInfo;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.view.Gravity;
 
@@ -67,8 +70,8 @@
  */
 public class TvPipController implements PipTransitionController.PipTransitionCallback,
         TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate,
-        TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener,
-        ConfigurationChangeListener, UserChangeListener {
+        DisplayController.OnDisplaysChangedListener, ConfigurationChangeListener,
+        UserChangeListener {
     private static final String TAG = "TvPipController";
 
     private static final int NONEXISTENT_TASK_ID = -1;
@@ -98,6 +101,17 @@
      */
     private static final int STATE_PIP_MENU = 2;
 
+    static final String ACTION_SHOW_PIP_MENU =
+            "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
+    static final String ACTION_CLOSE_PIP =
+            "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
+    static final String ACTION_MOVE_PIP =
+            "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
+    static final String ACTION_TOGGLE_EXPANDED_PIP =
+            "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
+    static final String ACTION_TO_FULLSCREEN =
+            "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
+
     private final Context mContext;
 
     private final ShellController mShellController;
@@ -107,6 +121,7 @@
     private final PipAppOpsListener mAppOpsListener;
     private final PipTaskOrganizer mPipTaskOrganizer;
     private final PipMediaController mPipMediaController;
+    private final TvPipActionsProvider mTvPipActionsProvider;
     private final TvPipNotificationController mPipNotificationController;
     private final TvPipMenuController mTvPipMenuController;
     private final PipTransitionController mPipTransitionController;
@@ -115,14 +130,16 @@
     private final DisplayController mDisplayController;
     private final WindowManagerShellWrapper mWmShellWrapper;
     private final ShellExecutor mMainExecutor;
+    private final Handler mMainHandler; // For registering the broadcast receiver
     private final TvPipImpl mImpl = new TvPipImpl();
 
+    private final ActionBroadcastReceiver mActionBroadcastReceiver;
+
     @State
     private int mState = STATE_NO_PIP;
     private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
     private int mPinnedTaskId = NONEXISTENT_TASK_ID;
 
-    private RemoteAction mCloseAction;
     // How long the shell will wait for the app to close the PiP if a custom action is set.
     private int mPipForceCloseDelay;
 
@@ -146,6 +163,7 @@
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayController displayController,
             WindowManagerShellWrapper wmShell,
+            Handler mainHandler,
             ShellExecutor mainExecutor) {
         return new TvPipController(
                 context,
@@ -164,6 +182,7 @@
                 pipParamsChangedForwarder,
                 displayController,
                 wmShell,
+                mainHandler,
                 mainExecutor).mImpl;
     }
 
@@ -184,8 +203,10 @@
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayController displayController,
             WindowManagerShellWrapper wmShellWrapper,
+            Handler mainHandler,
             ShellExecutor mainExecutor) {
         mContext = context;
+        mMainHandler = mainHandler;
         mMainExecutor = mainExecutor;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -198,12 +219,17 @@
         mTvPipBoundsController.setListener(this);
 
         mPipMediaController = pipMediaController;
+        mTvPipActionsProvider = new TvPipActionsProvider(context, pipMediaController,
+                this::executeAction);
 
         mPipNotificationController = pipNotificationController;
-        mPipNotificationController.setDelegate(this);
+        mPipNotificationController.setTvPipActionsProvider(mTvPipActionsProvider);
 
         mTvPipMenuController = tvPipMenuController;
         mTvPipMenuController.setDelegate(this);
+        mTvPipMenuController.setTvPipActionsProvider(mTvPipActionsProvider);
+
+        mActionBroadcastReceiver = new ActionBroadcastReceiver();
 
         mAppOpsListener = pipAppOpsListener;
         mPipTaskOrganizer = pipTaskOrganizer;
@@ -241,7 +267,7 @@
                 "%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));
 
         loadConfigurations();
-        mPipNotificationController.onConfigurationChanged(mContext);
+        mPipNotificationController.onConfigurationChanged();
         mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
     }
 
@@ -256,9 +282,10 @@
      * Starts the process if bringing up the Pip menu if by issuing a command to move Pip
      * task/window to the "Menu" position. We'll show the actual Menu UI (eg. actions) once the Pip
      * task/window is properly positioned in {@link #onPipTransitionFinished(int)}.
+     *
+     * @param moveMenu If true, show the moveMenu, otherwise show the regular menu.
      */
-    @Override
-    public void showPictureInPictureMenu() {
+    private void showPictureInPictureMenu(boolean moveMenu) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: showPictureInPictureMenu(), state=%s", TAG, stateToName(mState));
 
@@ -269,7 +296,11 @@
         }
 
         setState(STATE_PIP_MENU);
-        mTvPipMenuController.showMenu();
+        if (moveMenu) {
+            mTvPipMenuController.showMovementMenu();
+        } else {
+            mTvPipMenuController.showMenu();
+        }
         updatePinnedStackBounds();
     }
 
@@ -289,8 +320,7 @@
     /**
      * Opens the "Pip-ed" Activity fullscreen.
      */
-    @Override
-    public void movePipToFullscreen() {
+    private void movePipToFullscreen() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState));
 
@@ -298,8 +328,7 @@
         onPipDisappeared();
     }
 
-    @Override
-    public void togglePipExpansion() {
+    private void togglePipExpansion() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: togglePipExpansion()", TAG);
         boolean expanding = !mTvPipBoundsState.isTvPipExpanded();
@@ -310,18 +339,11 @@
         }
         mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
         mTvPipBoundsState.setTvPipExpanded(expanding);
-        mPipNotificationController.updateExpansionState();
 
         updatePinnedStackBounds();
     }
 
     @Override
-    public void enterPipMovementMenu() {
-        setState(STATE_PIP_MENU);
-        mTvPipMenuController.showMovementMenuOnly();
-    }
-
-    @Override
     public void movePip(int keycode) {
         if (mTvPipBoundsAlgorithm.updateGravity(keycode)) {
             mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity());
@@ -373,30 +395,23 @@
     @Override
     public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) {
         mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds,
-                animationDuration, rect -> mTvPipMenuController.updateExpansionState());
+                animationDuration, null);
         mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds);
     }
 
     /**
      * Closes Pip window.
      */
-    @Override
     public void closePip() {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: closePip(), state=%s, loseAction=%s", TAG, stateToName(mState),
-                mCloseAction);
+        closeCurrentPiP(mPinnedTaskId);
+    }
 
-        if (mCloseAction != null) {
-            try {
-                mCloseAction.getActionIntent().send();
-            } catch (PendingIntent.CanceledException e) {
-                ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                        "%s: Failed to send close action, %s", TAG, e);
-            }
-            mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay);
-        } else {
-            closeCurrentPiP(mPinnedTaskId);
-        }
+    /**
+     * Force close the current PiP after some time in case the custom action hasn't done it by
+     * itself.
+     */
+    public void customClosePip() {
+        mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay);
     }
 
     private void closeCurrentPiP(int pinnedTaskId) {
@@ -426,6 +441,7 @@
         mPinnedTaskId = pinnedTask.taskId;
 
         mPipMediaController.onActivityPinned();
+        mActionBroadcastReceiver.register();
         mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
     }
 
@@ -445,6 +461,8 @@
                 "%s: onPipDisappeared() state=%s", TAG, stateToName(mState));
 
         mPipNotificationController.dismiss();
+        mActionBroadcastReceiver.unregister();
+
         mTvPipMenuController.closeMenu();
         mTvPipBoundsState.resetTvPipState();
         mTvPipBoundsController.onPipDismissed();
@@ -454,6 +472,11 @@
 
     @Override
     public void onPipTransitionStarted(int direction, Rect currentPipBounds) {
+        final boolean enterPipTransition = PipAnimationController.isInPipDirection(direction);
+        if (enterPipTransition && mState == STATE_NO_PIP) {
+            // Set the initial ability to expand the PiP when entering PiP.
+            updateExpansionState();
+        }
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: onPipTransition_Started(), state=%s, direction=%d",
                 TAG, stateToName(mState), direction);
@@ -465,6 +488,7 @@
                 "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
         mTvPipMenuController.onPipTransitionFinished(
                 PipAnimationController.isInPipDirection(direction));
+        mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
     }
 
     @Override
@@ -477,6 +501,12 @@
                 "%s: onPipTransition_Finished(), state=%s, direction=%d",
                 TAG, stateToName(mState), direction);
         mTvPipMenuController.onPipTransitionFinished(enterPipTransition);
+        mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
+    }
+
+    private void updateExpansionState() {
+        mTvPipActionsProvider.updateExpansionEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
+                && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
     }
 
     private void setState(@State int state) {
@@ -534,8 +564,7 @@
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                         "%s: onActionsChanged()", TAG);
 
-                mTvPipMenuController.setAppActions(actions, closeAction);
-                mCloseAction = closeAction;
+                mTvPipActionsProvider.setAppActions(actions, closeAction);
             }
 
             @Override
@@ -555,7 +584,7 @@
                         "%s: onExpandedAspectRatioChanged: %f", TAG, ratio);
 
                 mTvPipBoundsState.setDesiredTvExpandedAspectRatio(ratio, false);
-                mTvPipMenuController.updateExpansionState();
+                updateExpansionState();
 
                 // 1) PiP is expanded and only aspect ratio changed, but wasn't disabled
                 // --> update bounds, but don't toggle
@@ -662,6 +691,90 @@
         }
     }
 
+    private void executeAction(@TvPipAction.ActionType int actionType) {
+        switch (actionType) {
+            case TvPipAction.ACTION_FULLSCREEN:
+                movePipToFullscreen();
+                break;
+            case TvPipAction.ACTION_CLOSE:
+                closePip();
+                break;
+            case TvPipAction.ACTION_MOVE:
+                showPictureInPictureMenu(/* moveMenu= */ true);
+                break;
+            case TvPipAction.ACTION_CUSTOM_CLOSE:
+                customClosePip();
+                break;
+            case TvPipAction.ACTION_EXPAND_COLLAPSE:
+                togglePipExpansion();
+                break;
+            default:
+                // NOOP
+                break;
+        }
+    }
+
+    private class ActionBroadcastReceiver extends BroadcastReceiver {
+        private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
+
+        final IntentFilter mIntentFilter;
+
+        {
+            mIntentFilter = new IntentFilter();
+            mIntentFilter.addAction(ACTION_CLOSE_PIP);
+            mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
+            mIntentFilter.addAction(ACTION_MOVE_PIP);
+            mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
+            mIntentFilter.addAction(ACTION_TO_FULLSCREEN);
+        }
+
+        boolean mRegistered = false;
+
+        void register() {
+            if (mRegistered) return;
+
+            mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
+                    mMainHandler);
+            mRegistered = true;
+        }
+
+        void unregister() {
+            if (!mRegistered) return;
+
+            mContext.unregisterReceiver(this);
+            mRegistered = false;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: on(Broadcast)Receive(), action=%s", TAG, action);
+
+            if (ACTION_SHOW_PIP_MENU.equals(action)) {
+                showPictureInPictureMenu(/* moveMenu= */ false);
+            } else {
+                executeAction(getCorrespondingActionType(action));
+            }
+        }
+
+        @TvPipAction.ActionType
+        private int getCorrespondingActionType(String broadcast) {
+            if (ACTION_CLOSE_PIP.equals(broadcast)) {
+                return TvPipAction.ACTION_CLOSE;
+            } else if (ACTION_MOVE_PIP.equals(broadcast)) {
+                return TvPipAction.ACTION_MOVE;
+            } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(broadcast)) {
+                return TvPipAction.ACTION_EXPAND_COLLAPSE;
+            } else if (ACTION_TO_FULLSCREEN.equals(broadcast)) {
+                return TvPipAction.ACTION_FULLSCREEN;
+            }
+
+            // Default: handle it like an action we don't know the content of.
+            return TvPipAction.ACTION_CUSTOM;
+        }
+    }
+
     private class TvPipImpl implements Pip {
         // Not used
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
new file mode 100644
index 0000000..449a2bf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.pip.tv;
+
+import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
+import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A TvPipAction for actions that the app provides via {@link
+ * android.app.PictureInPictureParams.Builder#setCloseAction(RemoteAction)} or {@link
+ * android.app.PictureInPictureParams.Builder#setActions(List)}.
+ */
+public class TvPipCustomAction extends TvPipAction {
+    private static final String TAG = TvPipCustomAction.class.getSimpleName();
+
+    private final RemoteAction mRemoteAction;
+
+    TvPipCustomAction(@ActionType int actionType, @NonNull RemoteAction remoteAction,
+            SystemActionsHandler systemActionsHandler) {
+        super(actionType, systemActionsHandler);
+        Objects.requireNonNull(remoteAction);
+        mRemoteAction = remoteAction;
+    }
+
+    void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
+        if (button == null || mainHandler == null) return;
+        if (mRemoteAction.getContentDescription().length() > 0) {
+            button.setTextAndDescription(mRemoteAction.getContentDescription());
+        } else {
+            button.setTextAndDescription(mRemoteAction.getTitle());
+        }
+        button.setImageIconAsync(mRemoteAction.getIcon(), mainHandler);
+        button.setEnabled(isCloseAction() || mRemoteAction.isEnabled());
+    }
+
+    PendingIntent getPendingIntent() {
+        return mRemoteAction.getActionIntent();
+    }
+
+    void executeAction() {
+        super.executeAction();
+        try {
+            mRemoteAction.getActionIntent().send();
+        } catch (PendingIntent.CanceledException e) {
+            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Failed to send action, %s", TAG, e);
+        }
+    }
+
+    @Override
+    Notification.Action toNotificationAction(Context context) {
+        Notification.Action.Builder builder = new Notification.Action.Builder(
+                mRemoteAction.getIcon(),
+                mRemoteAction.getTitle(),
+                mRemoteAction.getActionIntent());
+        Bundle extras = new Bundle();
+        extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
+                mRemoteAction.getContentDescription());
+        builder.addExtras(extras);
+
+        builder.setSemanticAction(isCloseAction()
+                ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE);
+        builder.setContextual(true);
+        return builder.build();
+    }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index ab7edbf..00e4f47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -41,13 +41,10 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipMenuController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Manages the visibility of the PiP Menu as user interacts with PiP.
@@ -60,22 +57,20 @@
     private final SystemWindows mSystemWindows;
     private final TvPipBoundsState mTvPipBoundsState;
     private final Handler mMainHandler;
+    private TvPipActionsProvider mTvPipActionsProvider;
 
     private Delegate mDelegate;
     private SurfaceControl mLeash;
     private TvPipMenuView mPipMenuView;
     private View mPipBackgroundView;
 
+    private boolean mMenuIsOpen;
     // User can actively move the PiP via the DPAD.
     private boolean mInMoveMode;
     // Used when only showing the move menu since we want to close the menu completely when
     // exiting the move menu instead of showing the regular button menu.
     private boolean mCloseAfterExitMoveMenu;
 
-    private final List<RemoteAction> mMediaActions = new ArrayList<>();
-    private final List<RemoteAction> mAppActions = new ArrayList<>();
-    private RemoteAction mCloseAction;
-
     private SyncRtSurfaceTransactionApplier mApplier;
     private SyncRtSurfaceTransactionApplier mBackgroundApplier;
     RectF mTmpSourceRectF = new RectF();
@@ -83,8 +78,7 @@
     Matrix mMoveTransform = new Matrix();
 
     public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
-            SystemWindows systemWindows, PipMediaController pipMediaController,
-            Handler mainHandler) {
+            SystemWindows systemWindows, Handler mainHandler) {
         mContext = context;
         mTvPipBoundsState = tvPipBoundsState;
         mSystemWindows = systemWindows;
@@ -101,9 +95,6 @@
         context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver,
                 new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */,
                 mainHandler, Context.RECEIVER_EXPORTED);
-
-        pipMediaController.addActionListener(this::onMediaActionsChanged);
-
     }
 
     void setDelegate(Delegate delegate) {
@@ -120,6 +111,10 @@
         mDelegate = delegate;
     }
 
+    void setTvPipActionsProvider(TvPipActionsProvider tvPipActionsProvider) {
+        mTvPipActionsProvider = tvPipActionsProvider;
+    }
+
     @Override
     public void attach(SurfaceControl leash) {
         if (mDelegate == null) {
@@ -146,15 +141,19 @@
         int pipMenuBorderWidth = mContext.getResources()
                 .getDimensionPixelSize(R.dimen.pip_menu_border_width);
         mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-pipMenuBorderWidth,
-                    -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
+                -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
         mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -pipEduTextHeight));
     }
 
     private void attachPipMenuView() {
-        mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this);
+        if (mTvPipActionsProvider == null) {
+            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Actions provider is not set", TAG);
+            return;
+        }
+        mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
         setUpViewSurfaceZOrder(mPipMenuView, 1);
         addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
-        maybeUpdateMenuViewActions();
     }
 
     private void attachPipBackgroundView() {
@@ -189,17 +188,22 @@
         // and the menu view has been fully remeasured and relaid out, we add a small delay here by
         // posting on the handler.
         mMainHandler.post(() -> {
-            mPipMenuView.onPipTransitionFinished(
-                    enterTransition, mTvPipBoundsState.isTvPipExpanded());
+            if (mPipMenuView != null) {
+                mPipMenuView.onPipTransitionFinished(enterTransition);
+            }
         });
     }
 
-    void showMovementMenuOnly() {
+    void showMovementMenu() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: showMovementMenuOnly()", TAG);
         setInMoveMode(true);
-        mCloseAfterExitMoveMenu = true;
-        showMenuInternal();
+        if (mMenuIsOpen) {
+            mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
+        } else {
+            mCloseAfterExitMoveMenu = true;
+            showMenuInternal();
+        }
     }
 
     @Override
@@ -214,14 +218,13 @@
         if (mPipMenuView == null) {
             return;
         }
-        maybeUpdateMenuViewActions();
-        updateExpansionState();
 
+        mMenuIsOpen = true;
         grantPipMenuFocus(true);
         if (mInMoveMode) {
             mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
         } else {
-            mPipMenuView.showButtonsMenu();
+            mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false);
         }
         mPipMenuView.updateBounds(mTvPipBoundsState.getBounds());
     }
@@ -236,11 +239,6 @@
         mPipMenuView.showMovementHints(gravity);
     }
 
-    void updateExpansionState() {
-        mPipMenuView.setExpandedModeEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
-                && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
-    }
-
     private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
         return mPipMenuView.getPipMenuContainerBounds(pipBounds);
     }
@@ -252,6 +250,8 @@
         if (mPipMenuView == null) {
             return;
         }
+
+        mMenuIsOpen = false;
         mPipMenuView.hideAllUserControls();
         grantPipMenuFocus(false);
         mDelegate.onMenuClosed();
@@ -272,29 +272,19 @@
     }
 
     @Override
-    public void onEnterMoveMode() {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
-                mCloseAfterExitMoveMenu);
-        setInMoveMode(true);
-        mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
-    }
-
-    @Override
     public boolean onExitMoveMode() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onExitMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
-                mCloseAfterExitMoveMenu);
+                "%s: onExitMoveMode - %b, close when exiting move menu: %b",
+                TAG, mInMoveMode, mCloseAfterExitMoveMenu);
 
-        if (mCloseAfterExitMoveMenu) {
-            setInMoveMode(false);
-            mCloseAfterExitMoveMenu = false;
-            closeMenu();
-            return true;
-        }
         if (mInMoveMode) {
             setInMoveMode(false);
-            mPipMenuView.showButtonsMenu();
+            if (mCloseAfterExitMoveMenu) {
+                mCloseAfterExitMoveMenu = false;
+                closeMenu();
+            } else {
+                mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ true);
+            }
             return true;
         }
         return false;
@@ -319,51 +309,7 @@
 
     @Override
     public void setAppActions(List<RemoteAction> actions, RemoteAction closeAction) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: setAppActions()", TAG);
-        updateAdditionalActionsList(mAppActions, actions, closeAction);
-    }
-
-    private void onMediaActionsChanged(List<RemoteAction> actions) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onMediaActionsChanged()", TAG);
-
-        // Hide disabled actions.
-        List<RemoteAction> enabledActions = new ArrayList<>();
-        for (RemoteAction remoteAction : actions) {
-            if (remoteAction.isEnabled()) {
-                enabledActions.add(remoteAction);
-            }
-        }
-        updateAdditionalActionsList(mMediaActions, enabledActions, mCloseAction);
-    }
-
-    private void updateAdditionalActionsList(List<RemoteAction> destination,
-            @Nullable List<RemoteAction> source, RemoteAction closeAction) {
-        final int number = source != null ? source.size() : 0;
-        if (number == 0 && destination.isEmpty() && Objects.equals(closeAction, mCloseAction)) {
-            // Nothing changed.
-            return;
-        }
-
-        mCloseAction = closeAction;
-
-        destination.clear();
-        if (number > 0) {
-            destination.addAll(source);
-        }
-        maybeUpdateMenuViewActions();
-    }
-
-    private void maybeUpdateMenuViewActions() {
-        if (mPipMenuView == null) {
-            return;
-        }
-        if (!mAppActions.isEmpty()) {
-            mPipMenuView.setAdditionalActions(mAppActions, mCloseAction, mMainHandler);
-        } else {
-            mPipMenuView.setAdditionalActions(mMediaActions, mCloseAction, mMainHandler);
-        }
+        // NOOP - handled via the TvPipActionsProvider
     }
 
     @Override
@@ -544,42 +490,21 @@
     }
 
     @Override
-    public void onCloseButtonClick() {
-        mDelegate.closePip();
-    }
-
-    @Override
-    public void onFullscreenButtonClick() {
-        mDelegate.movePipToFullscreen();
-    }
-
-    @Override
-    public void onToggleExpandedMode() {
-        mDelegate.togglePipExpansion();
-    }
-
-    @Override
     public void onCloseEduText() {
         mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
         mDelegate.closeEduText();
     }
 
     interface Delegate {
-        void movePipToFullscreen();
-
         void movePip(int keycode);
 
         void onInMoveModeChanged();
 
         int getPipGravity();
 
-        void togglePipExpansion();
-
         void onMenuClosed();
 
         void closeEduText();
-
-        void closePip();
     }
 
     private void grantPipMenuFocus(boolean grantFocus) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 57e95c4..56c602a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -25,119 +25,102 @@
 import static android.view.KeyEvent.KEYCODE_DPAD_UP;
 import static android.view.KeyEvent.KEYCODE_ENTER;
 
-import android.app.PendingIntent;
-import android.app.RemoteAction;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.view.Gravity;
 import android.view.KeyEvent;
-import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewRootImpl;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.TvWindowMenuActionButton;
 import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
- * A View that represents Pip Menu on TV. It's responsible for displaying 3 ever-present Pip Menu
- * actions: Fullscreen, Move and Close, but could also display "additional" actions, that may be set
- * via a {@link #setAdditionalActions(List, RemoteAction, Handler)} call.
+ * A View that represents Pip Menu on TV. It's responsible for displaying the Pip menu actions from
+ * the TvPipActionsProvider as well as the buttons for manually moving the PiP.
  */
-public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
+public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.Listener {
     private static final String TAG = "TvPipMenuView";
 
-    private static final int FIRST_CUSTOM_ACTION_POSITION = 3;
+    private final TvPipMenuView.Listener mListener;
 
-    private final Listener mListener;
+    private final TvPipActionsProvider mTvPipActionsProvider;
 
-    private final LinearLayout mActionButtonsContainer;
-    private final View mMenuFrameView;
-    private final List<TvWindowMenuActionButton> mAdditionalButtons = new ArrayList<>();
+    private final RecyclerView mActionButtonsRecyclerView;
+    private final LinearLayoutManager mButtonLayoutManager;
+    private final RecyclerViewAdapter mRecyclerViewAdapter;
+
     private final View mPipFrameView;
+    private final View mMenuFrameView;
     private final View mPipView;
+
+    private final View mPipBackground;
+    private final View mDimLayer;
+
     private final TvPipMenuEduTextDrawer mEduTextDrawer;
+
     private final int mPipMenuOuterSpace;
     private final int mPipMenuBorderWidth;
 
+    private final int mPipMenuFadeAnimationDuration;
+    private final int mResizeAnimationDuration;
+
     private final ImageView mArrowUp;
     private final ImageView mArrowRight;
     private final ImageView mArrowDown;
     private final ImageView mArrowLeft;
     private final TvWindowMenuActionButton mA11yDoneButton;
 
-    private final View mPipBackground;
-    private final View mDimLayer;
-
-    private final ScrollView mScrollView;
-    private final HorizontalScrollView mHorizontalScrollView;
-    private View mFocusedButton;
-
     private Rect mCurrentPipBounds;
     private boolean mMoveMenuIsVisible;
     private boolean mButtonMenuIsVisible;
-
-    private final TvWindowMenuActionButton mExpandButton;
-    private final TvWindowMenuActionButton mCloseButton;
-
     private boolean mSwitchingOrientation;
 
-    private final int mPipMenuFadeAnimationDuration;
-    private final int mResizeAnimationDuration;
-
     private final AccessibilityManager mA11yManager;
     private final Handler mMainHandler;
 
     public TvPipMenuView(@NonNull Context context, @NonNull Handler mainHandler,
-            @NonNull Listener listener) {
+            @NonNull Listener listener, TvPipActionsProvider tvPipActionsProvider) {
         super(context, null, 0, 0);
-
         inflate(context, R.layout.tv_pip_menu, this);
 
         mMainHandler = mainHandler;
         mListener = listener;
-
         mA11yManager = context.getSystemService(AccessibilityManager.class);
 
-        mActionButtonsContainer = findViewById(R.id.tv_pip_menu_action_buttons);
-        mActionButtonsContainer.findViewById(R.id.tv_pip_menu_fullscreen_button)
-                .setOnClickListener(this);
+        mActionButtonsRecyclerView = findViewById(R.id.tv_pip_menu_action_buttons);
+        mButtonLayoutManager = new LinearLayoutManager(mContext);
+        mActionButtonsRecyclerView.setLayoutManager(mButtonLayoutManager);
+        mActionButtonsRecyclerView.setPreserveFocusAfterLayout(true);
 
-        mCloseButton = mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button);
-        mCloseButton.setOnClickListener(this);
-        mCloseButton.setIsCustomCloseAction(true);
+        mTvPipActionsProvider = tvPipActionsProvider;
+        mRecyclerViewAdapter = new RecyclerViewAdapter(tvPipActionsProvider.getActionsList());
+        mActionButtonsRecyclerView.setAdapter(mRecyclerViewAdapter);
 
-        mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button)
-                .setOnClickListener(this);
-        mExpandButton = findViewById(R.id.tv_pip_menu_expand_button);
-        mExpandButton.setOnClickListener(this);
-
-        mPipBackground = findViewById(R.id.tv_pip_menu_background);
-        mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
-
-        mScrollView = findViewById(R.id.tv_pip_menu_scroll);
-        mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
+        tvPipActionsProvider.addListener(this);
 
         mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
         mPipFrameView = findViewById(R.id.tv_pip_border);
         mPipView = findViewById(R.id.tv_pip);
 
+        mPipBackground = findViewById(R.id.tv_pip_menu_background);
+        mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
+
         mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
         mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
         mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
@@ -160,8 +143,12 @@
     }
 
     void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
+        if (targetBounds == null) {
+            return;
+        }
+
         // Fade out content by fading in view on top.
-        if (mCurrentPipBounds != null && targetBounds != null) {
+        if (mCurrentPipBounds != null) {
             boolean ratioChanged = PipUtils.aspectRatioChanged(
                     mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(),
                     targetBounds.width() / (float) targetBounds.height());
@@ -177,7 +164,7 @@
         // Update buttons.
         final boolean vertical = targetBounds.height() > targetBounds.width();
         final boolean orientationChanged =
-                vertical != (mActionButtonsContainer.getOrientation() == LinearLayout.VERTICAL);
+                vertical != (mButtonLayoutManager.getOrientation() == LinearLayoutManager.VERTICAL);
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: onPipTransitionToTargetBoundsStarted(), orientation changed %b",
                 TAG, orientationChanged);
@@ -187,27 +174,27 @@
 
         if (mButtonMenuIsVisible) {
             mSwitchingOrientation = true;
-            mActionButtonsContainer.animate()
+            mActionButtonsRecyclerView.animate()
                     .alpha(0)
                     .setInterpolator(TvPipInterpolators.EXIT)
                     .setDuration(mResizeAnimationDuration / 2)
                     .withEndAction(() -> {
-                        changeButtonScrollOrientation(targetBounds);
-                        updateButtonGravity(targetBounds);
+                        mButtonLayoutManager.setOrientation(vertical
+                                ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
                         // Only make buttons visible again in onPipTransitionFinished to keep in
                         // sync with PiP content alpha animation.
                     });
         } else {
-            changeButtonScrollOrientation(targetBounds);
-            updateButtonGravity(targetBounds);
+            mButtonLayoutManager.setOrientation(vertical
+                    ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
         }
     }
 
-    void onPipTransitionFinished(boolean enterTransition, boolean isTvPipExpanded) {
+    void onPipTransitionFinished(boolean enterTransition) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: onPipTransitionFinished()", TAG);
 
-        // Fade in content by fading out view on top.
+        // Fade in content by fading out view on top (faded out at every aspect ratio change).
         mPipBackground.animate()
                 .alpha(0f)
                 .setDuration(mResizeAnimationDuration / 2)
@@ -218,16 +205,11 @@
             mEduTextDrawer.init();
         }
 
-        setIsExpanded(isTvPipExpanded);
-
-        // Update buttons.
         if (mSwitchingOrientation) {
-            mActionButtonsContainer.animate()
+            mActionButtonsRecyclerView.animate()
                     .alpha(1)
                     .setInterpolator(TvPipInterpolators.ENTER)
                     .setDuration(mResizeAnimationDuration / 2);
-        } else {
-            refocusPreviousButton();
         }
         mSwitchingOrientation = false;
     }
@@ -240,107 +222,9 @@
                 "%s: updateLayout, width: %s, height: %s", TAG, updatedBounds.width(),
                 updatedBounds.height());
         mCurrentPipBounds = updatedBounds;
-        if (!mSwitchingOrientation) {
-            updateButtonGravity(mCurrentPipBounds);
-        }
-
         updatePipFrameBounds();
     }
 
-    private void changeButtonScrollOrientation(Rect bounds) {
-        final boolean vertical = bounds.height() > bounds.width();
-
-        final ViewGroup oldScrollView = vertical ? mHorizontalScrollView : mScrollView;
-        final ViewGroup newScrollView = vertical ? mScrollView : mHorizontalScrollView;
-
-        if (oldScrollView.getChildCount() == 1) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: orientation changed", TAG);
-            oldScrollView.removeView(mActionButtonsContainer);
-            oldScrollView.setVisibility(GONE);
-            mActionButtonsContainer.setOrientation(vertical ? LinearLayout.VERTICAL
-                    : LinearLayout.HORIZONTAL);
-            newScrollView.addView(mActionButtonsContainer);
-            newScrollView.setVisibility(VISIBLE);
-            if (mFocusedButton != null) {
-                mFocusedButton.requestFocus();
-            }
-        }
-    }
-
-    /**
-     * Change button gravity based on new dimensions
-     */
-    private void updateButtonGravity(Rect bounds) {
-        final boolean vertical = bounds.height() > bounds.width();
-        // Use Math.max since the possible orientation change might not have been applied yet.
-        final int buttonsSize = Math.max(mActionButtonsContainer.getHeight(),
-                mActionButtonsContainer.getWidth());
-
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: buttons container width: %s, height: %s", TAG,
-                mActionButtonsContainer.getWidth(), mActionButtonsContainer.getHeight());
-
-        final boolean buttonsFit =
-                vertical ? buttonsSize < bounds.height()
-                        : buttonsSize < bounds.width();
-        final int buttonGravity = buttonsFit ? Gravity.CENTER
-                : (vertical ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL);
-
-        final LayoutParams params = (LayoutParams) mActionButtonsContainer.getLayoutParams();
-        params.gravity = buttonGravity;
-        mActionButtonsContainer.setLayoutParams(params);
-
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: vertical: %b, buttonsFit: %b, gravity: %s", TAG, vertical, buttonsFit,
-                Gravity.toString(buttonGravity));
-    }
-
-    private void refocusPreviousButton() {
-        if (mMoveMenuIsVisible || mCurrentPipBounds == null || mFocusedButton == null) {
-            return;
-        }
-        final boolean vertical = mCurrentPipBounds.height() > mCurrentPipBounds.width();
-
-        if (!mFocusedButton.hasFocus()) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: request focus from: %s", TAG, mFocusedButton);
-            mFocusedButton.requestFocus();
-        } else {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: already focused: %s", TAG, mFocusedButton);
-        }
-
-        // Do we need to scroll?
-        final Rect buttonBounds = new Rect();
-        final Rect scrollBounds = new Rect();
-        if (vertical) {
-            mScrollView.getDrawingRect(scrollBounds);
-        } else {
-            mHorizontalScrollView.getDrawingRect(scrollBounds);
-        }
-        mFocusedButton.getHitRect(buttonBounds);
-
-        if (scrollBounds.contains(buttonBounds)) {
-            // Button is already completely visible, don't scroll
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: not scrolling", TAG);
-            return;
-        }
-
-        // Scrolling so the button is visible to the user.
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: scrolling to focused button", TAG);
-
-        if (vertical) {
-            mScrollView.smoothScrollTo((int) mFocusedButton.getX(),
-                    (int) mFocusedButton.getY());
-        } else {
-            mHorizontalScrollView.smoothScrollTo((int) mFocusedButton.getX(),
-                    (int) mFocusedButton.getY());
-        }
-    }
-
     Rect getPipMenuContainerBounds(Rect pipBounds) {
         final Rect menuUiBounds = new Rect(pipBounds);
         menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
@@ -370,20 +254,14 @@
             mPipView.setLayoutParams(pipViewParams);
         }
 
-
-    }
-
-    void setExpandedModeEnabled(boolean enabled) {
-        mExpandButton.setVisibility(enabled ? VISIBLE : GONE);
-    }
-
-    void setIsExpanded(boolean expanded) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: setIsExpanded, expanded: %b", TAG, expanded);
-        mExpandButton.setImageResource(
-                expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
-        mExpandButton.setTextAndDescription(
-                expanded ? R.string.pip_collapse : R.string.pip_expand);
+        // Keep focused button within the visible area while the PiP is changing size. Otherwise,
+        // the button would lose focus which would cause a need for scrolling and re-focusing after
+        // the animation finishes, which does not look good.
+        View focusedChild = mActionButtonsRecyclerView.getFocusedChild();
+        if (focusedChild != null) {
+            mActionButtonsRecyclerView.scrollToPosition(
+                    mActionButtonsRecyclerView.getChildLayoutPosition(focusedChild));
+        }
     }
 
     /**
@@ -391,48 +269,63 @@
      */
     void showMoveMenu(int gravity) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
-        showButtonsMenu(false);
         showMovementHints(gravity);
+        setMenuButtonsVisible(false);
         setFrameHighlighted(true);
 
-        mHorizontalScrollView.setFocusable(false);
-        mScrollView.setFocusable(false);
+        animateAlphaTo(mA11yManager.isEnabled() ? 1f : 0f, mDimLayer);
 
         mEduTextDrawer.closeIfNeeded();
     }
 
-    void showButtonsMenu() {
+
+    void showButtonsMenu(boolean exitingMoveMode) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: showButtonsMenu()", TAG);
-        showButtonsMenu(true);
+                "%s: showButtonsMenu(), exitingMoveMode %b", TAG, exitingMoveMode);
+        setMenuButtonsVisible(true);
         hideMovementHints();
         setFrameHighlighted(true);
+        animateAlphaTo(1f, mDimLayer);
+        mEduTextDrawer.closeIfNeeded();
 
-        mHorizontalScrollView.setFocusable(true);
-        mScrollView.setFocusable(true);
-
-        // Always focus on the first button when opening the menu, except directly after moving.
-        if (mFocusedButton == null) {
-            // Focus on first button (there is a Space at position 0)
-            mFocusedButton = mActionButtonsContainer.getChildAt(1);
-            // Reset scroll position.
-            mScrollView.scrollTo(0, 0);
-            mHorizontalScrollView.scrollTo(
-                    isLayoutRtl() ? mActionButtonsContainer.getWidth() : 0, 0);
+        if (exitingMoveMode) {
+            scrollAndRefocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE),
+                    /* alwaysScroll= */ false);
+        } else {
+            scrollAndRefocusButton(0, /* alwaysScroll= */ true);
         }
-        refocusPreviousButton();
+    }
+
+    private void scrollAndRefocusButton(int position, boolean alwaysScroll) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: scrollAndRefocusButton, target: %d", TAG, position);
+
+        if (alwaysScroll || !refocusButton(position)) {
+            mButtonLayoutManager.scrollToPositionWithOffset(position, 0);
+            mActionButtonsRecyclerView.post(() -> refocusButton(position));
+        }
     }
 
     /**
-     * Hides all menu views, including the menu frame.
+     * @return true if focus was requested, false if focus request could not be carried out due to
+     * the view for the position not being available (scrolling beforehand will be necessary).
      */
+    private boolean refocusButton(int position) {
+        View itemToFocus = mButtonLayoutManager.findViewByPosition(position);
+        if (itemToFocus != null) {
+            itemToFocus.requestFocus();
+            itemToFocus.requestAccessibilityFocus();
+        }
+        return itemToFocus != null;
+    }
+
     void hideAllUserControls() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: hideAllUserControls()", TAG);
-        mFocusedButton = null;
-        showButtonsMenu(false);
+        setMenuButtonsVisible(false);
         hideMovementHints();
         setFrameHighlighted(false);
+        animateAlphaTo(0f, mDimLayer);
     }
 
     @Override
@@ -463,134 +356,19 @@
                 });
     }
 
-    /**
-     * Button order:
-     * - Fullscreen
-     * - Close
-     * - Custom actions (app or media actions)
-     * - System actions
-     */
-    void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
-            Handler mainHandler) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: setAdditionalActions()", TAG);
-
-        // Replace system close action with custom close action if available
-        if (closeAction != null) {
-            setActionForButton(closeAction, mCloseButton, mainHandler);
-        } else {
-            mCloseButton.setTextAndDescription(R.string.pip_close);
-            mCloseButton.setImageResource(R.drawable.pip_ic_close_white);
-        }
-        mCloseButton.setIsCustomCloseAction(closeAction != null);
-        // Make sure the close action is always enabled
-        mCloseButton.setEnabled(true);
-
-        // Make sure we exactly as many additional buttons as we have actions to display.
-        final int actionsNumber = actions.size();
-        int buttonsNumber = mAdditionalButtons.size();
-        if (actionsNumber > buttonsNumber) {
-            // Add buttons until we have enough to display all the actions.
-            while (actionsNumber > buttonsNumber) {
-                TvWindowMenuActionButton button = new TvWindowMenuActionButton(mContext);
-                button.setOnClickListener(this);
-
-                mActionButtonsContainer.addView(button,
-                        FIRST_CUSTOM_ACTION_POSITION + buttonsNumber);
-                mAdditionalButtons.add(button);
-
-                buttonsNumber++;
-            }
-        } else if (actionsNumber < buttonsNumber) {
-            // Hide buttons until we as many as the actions.
-            while (actionsNumber < buttonsNumber) {
-                final View button = mAdditionalButtons.get(buttonsNumber - 1);
-                button.setVisibility(View.GONE);
-                button.setTag(null);
-
-                buttonsNumber--;
-            }
-        }
-
-        // "Assign" actions to the buttons.
-        for (int index = 0; index < actionsNumber; index++) {
-            final RemoteAction action = actions.get(index);
-            final TvWindowMenuActionButton button = mAdditionalButtons.get(index);
-
-            // Remove action if it matches the custom close action.
-            if (PipUtils.remoteActionsMatch(action, closeAction)) {
-                button.setVisibility(GONE);
-                continue;
-            }
-            setActionForButton(action, button, mainHandler);
-        }
-
-        if (mCurrentPipBounds != null) {
-            updateButtonGravity(mCurrentPipBounds);
-            refocusPreviousButton();
-        }
-    }
-
-    private void setActionForButton(RemoteAction action, TvWindowMenuActionButton button,
-            Handler mainHandler) {
-        button.setVisibility(View.VISIBLE); // Ensure the button is visible.
-        if (action.getContentDescription().length() > 0) {
-            button.setTextAndDescription(action.getContentDescription());
-        } else {
-            button.setTextAndDescription(action.getTitle());
-        }
-        button.setEnabled(action.isEnabled());
-        button.setTag(action);
-        action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler);
-    }
-
-    @Nullable
-    SurfaceControl getWindowSurfaceControl() {
-        final ViewRootImpl root = getViewRootImpl();
-        if (root == null) {
-            return null;
-        }
-        final SurfaceControl out = root.getSurfaceControl();
-        if (out != null && out.isValid()) {
-            return out;
-        }
-        return null;
-    }
-
     @Override
-    public void onClick(View v) {
-        final int id = v.getId();
-        if (id == R.id.tv_pip_menu_fullscreen_button) {
-            mListener.onFullscreenButtonClick();
-        } else if (id == R.id.tv_pip_menu_move_button) {
-            mListener.onEnterMoveMode();
-        } else if (id == R.id.tv_pip_menu_close_button) {
-            mListener.onCloseButtonClick();
-        } else if (id == R.id.tv_pip_menu_expand_button) {
-            mListener.onToggleExpandedMode();
-        } else {
-            // This should be an "additional action"
-            final RemoteAction action = (RemoteAction) v.getTag();
-            if (action != null) {
-                try {
-                    action.getActionIntent().send();
-                } catch (PendingIntent.CanceledException e) {
-                    ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                            "%s: Failed to send action, %s", TAG, e);
-                }
-            } else {
-                ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                        "%s: RemoteAction is null", TAG);
-            }
+    public void onActionsChanged(int added, int updated, int startIndex) {
+        mRecyclerViewAdapter.notifyItemRangeChanged(startIndex, updated);
+        if (added > 0) {
+            mRecyclerViewAdapter.notifyItemRangeInserted(startIndex + updated, added);
+        } else if (added < 0) {
+            mRecyclerViewAdapter.notifyItemRangeRemoved(startIndex + updated, -added);
         }
     }
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         if (event.getAction() == ACTION_UP) {
-            if (!mMoveMenuIsVisible) {
-                mFocusedButton = mActionButtonsContainer.getFocusedChild();
-            }
 
             if (event.getKeyCode() == KEYCODE_BACK) {
                 mListener.onBackPress();
@@ -624,10 +402,6 @@
     public void showMovementHints(int gravity) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
-
-        if (mMoveMenuIsVisible) {
-            return;
-        }
         mMoveMenuIsVisible = true;
 
         animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
@@ -643,9 +417,12 @@
 
         animateAlphaTo(a11yEnabled ? 1f : 0f, mA11yDoneButton);
         if (a11yEnabled) {
+            mA11yDoneButton.setVisibility(VISIBLE);
             mA11yDoneButton.setOnClickListener(v -> {
                 mListener.onExitMoveMode();
             });
+            mA11yDoneButton.requestFocus();
+            mA11yDoneButton.requestAccessibilityFocus();
         }
     }
 
@@ -684,33 +461,67 @@
     /**
      * Show or hide the pip buttons menu.
      */
-    public void showButtonsMenu(boolean show) {
+    private void setMenuButtonsVisible(boolean visible) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: showUserActions: %b", TAG, show);
-        if (mButtonMenuIsVisible == show) {
-            return;
-        }
-        mButtonMenuIsVisible = show;
-
-        if (show) {
-            mActionButtonsContainer.setVisibility(VISIBLE);
-            refocusPreviousButton();
-        }
-        animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
-        animateAlphaTo(show ? 1 : 0, mDimLayer);
-        mEduTextDrawer.closeIfNeeded();
+                "%s: showUserActions: %b", TAG, visible);
+        mButtonMenuIsVisible = visible;
+        animateAlphaTo(visible ? 1 : 0, mActionButtonsRecyclerView);
     }
 
     private void setFrameHighlighted(boolean highlighted) {
         mMenuFrameView.setActivated(highlighted);
     }
 
+    private class RecyclerViewAdapter extends
+            RecyclerView.Adapter<RecyclerViewAdapter.ButtonViewHolder> {
+
+        private final List<TvPipAction> mActionList;
+
+        RecyclerViewAdapter(List<TvPipAction> actionList) {
+            this.mActionList = actionList;
+        }
+
+        @NonNull
+        @Override
+        public ButtonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+            return new ButtonViewHolder(new TvWindowMenuActionButton(mContext));
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull ButtonViewHolder holder, int position) {
+            TvPipAction action = mActionList.get(position);
+            action.populateButton(holder.mButton, mMainHandler);
+        }
+
+        @Override
+        public int getItemCount() {
+            return mActionList.size();
+        }
+
+        private class ButtonViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
+            TvWindowMenuActionButton mButton;
+
+            ButtonViewHolder(@NonNull View itemView) {
+                super(itemView);
+                mButton = (TvWindowMenuActionButton) itemView;
+                mButton.setOnClickListener(this);
+            }
+
+            @Override
+            public void onClick(View v) {
+                TvPipAction action = mActionList.get(
+                        mActionButtonsRecyclerView.getChildLayoutPosition(v));
+                if (action != null) {
+                    action.executeAction();
+                }
+            }
+        }
+    }
+
     interface Listener extends TvPipMenuEduTextDrawer.Listener {
 
         void onBackPress();
 
-        void onEnterMoveMode();
-
         /**
          * Called when a button for exiting move mode was pressed.
          *
@@ -723,11 +534,5 @@
          * @return whether pip movement was handled.
          */
         boolean onPipMovement(int keycode);
-
-        void onCloseButtonClick();
-
-        void onFullscreenButtonClick();
-
-        void onToggleExpandedMode();
     }
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index e3308f0..f22ee59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -16,18 +16,13 @@
 
 package com.android.wm.shell.pip.tv;
 
-import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
-import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
-
+import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
@@ -35,7 +30,6 @@
 import android.graphics.drawable.Icon;
 import android.media.session.MediaSession;
 import android.os.Bundle;
-import android.os.Handler;
 import android.text.TextUtils;
 
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -47,7 +41,6 @@
 import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -55,39 +48,18 @@
  * <p>Once it's created, it will manage the PiP notification UI by itself except for handling
  * configuration changes and user initiated expanded PiP toggling.
  */
-public class TvPipNotificationController {
-    private static final String TAG = "TvPipNotification";
+public class TvPipNotificationController implements TvPipActionsProvider.Listener {
+    private static final String TAG = TvPipNotificationController.class.getSimpleName();
 
     // Referenced in com.android.systemui.util.NotificationChannels.
     public static final String NOTIFICATION_CHANNEL = "TVPIP";
     private static final String NOTIFICATION_TAG = "TvPip";
-    private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
-
-    private static final String ACTION_SHOW_PIP_MENU =
-            "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
-    private static final String ACTION_CLOSE_PIP =
-            "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
-    private static final String ACTION_MOVE_PIP =
-            "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
-    private static final String ACTION_TOGGLE_EXPANDED_PIP =
-            "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
-    private static final String ACTION_FULLSCREEN =
-            "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
 
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final NotificationManager mNotificationManager;
     private final Notification.Builder mNotificationBuilder;
-    private final ActionBroadcastReceiver mActionBroadcastReceiver;
-    private final Handler mMainHandler;
-    private Delegate mDelegate;
-    private final TvPipBoundsState mTvPipBoundsState;
-
-    private String mDefaultTitle;
-
-    private final List<RemoteAction> mCustomActions = new ArrayList<>();
-    private final List<RemoteAction> mMediaActions = new ArrayList<>();
-    private RemoteAction mCustomCloseAction;
+    private TvPipActionsProvider mTvPipActionsProvider;
 
     private MediaSession.Token mMediaSessionToken;
 
@@ -95,19 +67,23 @@
     private String mPackageName;
 
     private boolean mIsNotificationShown;
+    private String mDefaultTitle;
     private String mPipTitle;
     private String mPipSubtitle;
 
+    // Saving the actions, so they don't have to be regenerated when e.g. the PiP title changes.
+    @NonNull
+    private Notification.Action[] mPipActions;
+
     private Bitmap mActivityIcon;
 
     public TvPipNotificationController(Context context, PipMediaController pipMediaController,
-            PipParamsChangedForwarder pipParamsChangedForwarder, TvPipBoundsState tvPipBoundsState,
-            Handler mainHandler) {
+            PipParamsChangedForwarder pipParamsChangedForwarder) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mNotificationManager = context.getSystemService(NotificationManager.class);
-        mMainHandler = mainHandler;
-        mTvPipBoundsState = tvPipBoundsState;
+
+        mPipActions = new Notification.Action[0];
 
         mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
                 .setLocalOnly(true)
@@ -117,34 +93,15 @@
                 .setOnlyAlertOnce(true)
                 .setSmallIcon(R.drawable.pip_icon)
                 .setAllowSystemGeneratedContextualActions(false)
-                .setContentIntent(createPendingIntent(context, ACTION_FULLSCREEN))
-                .setDeleteIntent(getCloseAction().actionIntent)
-                .extend(new Notification.TvExtender()
-                        .setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU))
-                        .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP)));
+                .setContentIntent(
+                        createPendingIntent(context, TvPipController.ACTION_TO_FULLSCREEN));
+        // TvExtender and DeleteIntent set later since they might change.
 
-        mActionBroadcastReceiver = new ActionBroadcastReceiver();
-
-        pipMediaController.addActionListener(this::onMediaActionsChanged);
         pipMediaController.addTokenListener(this::onMediaSessionTokenChanged);
 
         pipParamsChangedForwarder.addListener(
                 new PipParamsChangedForwarder.PipParamsChangedCallback() {
                     @Override
-                    public void onExpandedAspectRatioChanged(float ratio) {
-                        updateExpansionState();
-                    }
-
-                    @Override
-                    public void onActionsChanged(List<RemoteAction> actions,
-                            RemoteAction closeAction) {
-                        mCustomActions.clear();
-                        mCustomActions.addAll(actions);
-                        mCustomCloseAction = closeAction;
-                        updateNotificationContent();
-                    }
-
-                    @Override
                     public void onTitleChanged(String title) {
                         mPipTitle = title;
                         updateNotificationContent();
@@ -157,34 +114,33 @@
                     }
                 });
 
-        onConfigurationChanged(context);
+        onConfigurationChanged();
     }
 
-    void setDelegate(Delegate delegate) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: setDelegate(), delegate=%s",
-                TAG, delegate);
+    /**
+     * Call before showing any notification.
+     */
+    void setTvPipActionsProvider(@NonNull TvPipActionsProvider tvPipActionsProvider) {
+        mTvPipActionsProvider = tvPipActionsProvider;
+        mTvPipActionsProvider.addListener(this);
+    }
 
-        if (mDelegate != null) {
-            throw new IllegalStateException(
-                    "The delegate has already been set and should not change.");
-        }
-        if (delegate == null) {
-            throw new IllegalArgumentException("The delegate must not be null.");
-        }
-
-        mDelegate = delegate;
+    void onConfigurationChanged() {
+        mDefaultTitle = mContext.getResources().getString(R.string.pip_notification_unknown_title);
+        updateNotificationContent();
     }
 
     void show(String packageName) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: show %s", TAG, packageName);
-        if (mDelegate == null) {
-            throw new IllegalStateException("Delegate is not set.");
+        if (mTvPipActionsProvider == null) {
+            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Missing TvPipActionsProvider", TAG);
+            return;
         }
 
         mIsNotificationShown = true;
         mPackageName = packageName;
         mActivityIcon = getActivityIcon();
-        mActionBroadcastReceiver.register();
 
         updateNotificationContent();
     }
@@ -194,151 +150,42 @@
 
         mIsNotificationShown = false;
         mPackageName = null;
-        mActionBroadcastReceiver.unregister();
-
         mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
     }
 
-    private Notification.Action getToggleAction(boolean expanded) {
-        if (expanded) {
-            return createSystemAction(R.drawable.pip_ic_collapse,
-                    R.string.pip_collapse, ACTION_TOGGLE_EXPANDED_PIP);
-        } else {
-            return createSystemAction(R.drawable.pip_ic_expand, R.string.pip_expand,
-                    ACTION_TOGGLE_EXPANDED_PIP);
-        }
-    }
-
-    private Notification.Action createSystemAction(int iconRes, int titleRes, String action) {
-        Notification.Action.Builder builder = new Notification.Action.Builder(
-                Icon.createWithResource(mContext, iconRes),
-                mContext.getString(titleRes),
-                createPendingIntent(mContext, action));
-        builder.setContextual(true);
-        return builder.build();
-    }
-
-    private void onMediaActionsChanged(List<RemoteAction> actions) {
-        mMediaActions.clear();
-        mMediaActions.addAll(actions);
-        if (mCustomActions.isEmpty()) {
-            updateNotificationContent();
-        }
-    }
-
     private void onMediaSessionTokenChanged(MediaSession.Token token) {
         mMediaSessionToken = token;
         updateNotificationContent();
     }
 
-    private Notification.Action remoteToNotificationAction(RemoteAction action) {
-        return remoteToNotificationAction(action, SEMANTIC_ACTION_NONE);
-    }
-
-    private Notification.Action remoteToNotificationAction(RemoteAction action,
-            int semanticAction) {
-        Notification.Action.Builder builder = new Notification.Action.Builder(action.getIcon(),
-                action.getTitle(),
-                action.getActionIntent());
-        if (action.getContentDescription() != null) {
-            Bundle extras = new Bundle();
-            extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
-                    action.getContentDescription());
-            builder.addExtras(extras);
-        }
-        builder.setSemanticAction(semanticAction);
-        builder.setContextual(true);
-        return builder.build();
-    }
-
-    private Notification.Action[] getNotificationActions() {
-        final List<Notification.Action> actions = new ArrayList<>();
-
-        // 1. Fullscreen
-        actions.add(getFullscreenAction());
-        // 2. Close
-        actions.add(getCloseAction());
-        // 3. App actions
-        final List<RemoteAction> appActions =
-                mCustomActions.isEmpty() ? mMediaActions : mCustomActions;
-        for (RemoteAction appAction : appActions) {
-            if (PipUtils.remoteActionsMatch(mCustomCloseAction, appAction)
-                    || !appAction.isEnabled()) {
-                continue;
-            }
-            actions.add(remoteToNotificationAction(appAction));
-        }
-        // 4. Move
-        actions.add(getMoveAction());
-        // 5. Toggle expansion (if expanded PiP enabled)
-        if (mTvPipBoundsState.getDesiredTvExpandedAspectRatio() > 0
-                && mTvPipBoundsState.isTvExpandedPipSupported()) {
-            actions.add(getToggleAction(mTvPipBoundsState.isTvPipExpanded()));
-        }
-        return actions.toArray(new Notification.Action[0]);
-    }
-
-    private Notification.Action getCloseAction() {
-        if (mCustomCloseAction == null) {
-            return createSystemAction(R.drawable.pip_ic_close_white, R.string.pip_close,
-                    ACTION_CLOSE_PIP);
-        } else {
-            return remoteToNotificationAction(mCustomCloseAction, SEMANTIC_ACTION_DELETE);
-        }
-    }
-
-    private Notification.Action getFullscreenAction() {
-        return createSystemAction(R.drawable.pip_ic_fullscreen_white,
-                R.string.pip_fullscreen, ACTION_FULLSCREEN);
-    }
-
-    private Notification.Action getMoveAction() {
-        return createSystemAction(R.drawable.pip_ic_move_white, R.string.pip_move,
-                ACTION_MOVE_PIP);
-    }
-
-    /**
-     * Called by {@link TvPipController} when the configuration is changed.
-     */
-    void onConfigurationChanged(Context context) {
-        mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title);
-        updateNotificationContent();
-    }
-
-    void updateExpansionState() {
-        updateNotificationContent();
-    }
-
     private void updateNotificationContent() {
         if (mPackageManager == null || !mIsNotificationShown) {
             return;
         }
 
-        Notification.Action[] actions = getNotificationActions();
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: update(), title: %s, subtitle: %s, mediaSessionToken: %s, #actions: %s", TAG,
-                getNotificationTitle(), mPipSubtitle, mMediaSessionToken, actions.length);
-        for (Notification.Action action : actions) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: action: %s", TAG,
-                    action.toString());
-        }
-
+                getNotificationTitle(), mPipSubtitle, mMediaSessionToken, mPipActions.length);
         mNotificationBuilder
                 .setWhen(System.currentTimeMillis())
                 .setContentTitle(getNotificationTitle())
                 .setContentText(mPipSubtitle)
                 .setSubText(getApplicationLabel(mPackageName))
-                .setActions(actions);
+                .setActions(mPipActions);
         setPipIcon();
 
         Bundle extras = new Bundle();
         extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
         mNotificationBuilder.setExtras(extras);
 
+        PendingIntent closeIntent = mTvPipActionsProvider.getCloseAction().getPendingIntent();
+        mNotificationBuilder.setDeleteIntent(closeIntent);
         // TvExtender not recognized if not set last.
         mNotificationBuilder.extend(new Notification.TvExtender()
-                .setContentIntent(createPendingIntent(mContext, ACTION_SHOW_PIP_MENU))
-                .setDeleteIntent(createPendingIntent(mContext, ACTION_CLOSE_PIP)));
+                .setContentIntent(
+                        createPendingIntent(mContext, TvPipController.ACTION_SHOW_PIP_MENU))
+                .setDeleteIntent(closeIntent));
+
         mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP,
                 mNotificationBuilder.build());
     }
@@ -390,68 +237,20 @@
         return ImageUtils.buildScaledBitmap(drawable, width, height, /* allowUpscaling */ true);
     }
 
-    private static PendingIntent createPendingIntent(Context context, String action) {
+    static PendingIntent createPendingIntent(Context context, String action) {
         return PendingIntent.getBroadcast(context, 0,
                 new Intent(action).setPackage(context.getPackageName()),
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 
-    private class ActionBroadcastReceiver extends BroadcastReceiver {
-        final IntentFilter mIntentFilter;
-        {
-            mIntentFilter = new IntentFilter();
-            mIntentFilter.addAction(ACTION_CLOSE_PIP);
-            mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
-            mIntentFilter.addAction(ACTION_MOVE_PIP);
-            mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
-            mIntentFilter.addAction(ACTION_FULLSCREEN);
+    @Override
+    public void onActionsChanged(int added, int updated, int startIndex) {
+        List<TvPipAction> actions = mTvPipActionsProvider.getActionsList();
+        mPipActions = new Notification.Action[actions.size()];
+        for (int i = 0; i < mPipActions.length; i++) {
+            mPipActions[i] = actions.get(i).toNotificationAction(mContext);
         }
-        boolean mRegistered = false;
-
-        void register() {
-            if (mRegistered) return;
-
-            mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
-                    mMainHandler);
-            mRegistered = true;
-        }
-
-        void unregister() {
-            if (!mRegistered) return;
-
-            mContext.unregisterReceiver(this);
-            mRegistered = false;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: on(Broadcast)Receive(), action=%s", TAG, action);
-
-            if (ACTION_SHOW_PIP_MENU.equals(action)) {
-                mDelegate.showPictureInPictureMenu();
-            } else if (ACTION_CLOSE_PIP.equals(action)) {
-                mDelegate.closePip();
-            } else if (ACTION_MOVE_PIP.equals(action)) {
-                mDelegate.enterPipMovementMenu();
-            } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) {
-                mDelegate.togglePipExpansion();
-            } else if (ACTION_FULLSCREEN.equals(action)) {
-                mDelegate.movePipToFullscreen();
-            }
-        }
+        updateNotificationContent();
     }
 
-    interface Delegate {
-        void showPictureInPictureMenu();
-
-        void closePip();
-
-        void enterPipMovementMenu();
-
-        void togglePipExpansion();
-
-        void movePipToFullscreen();
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
new file mode 100644
index 0000000..93b6a90
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.pip.tv;
+
+import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
+import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
+
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.StringRes;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
+
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+
+/**
+ * A TvPipAction for actions that the system provides, i.e. fullscreen, default close, move,
+ * expand/collapse.
+ */
+public class TvPipSystemAction extends TvPipAction {
+
+    @StringRes
+    private int mTitleResource;
+    @DrawableRes
+    private int mIconResource;
+
+    private final PendingIntent mBroadcastIntent;
+
+    TvPipSystemAction(@ActionType int actionType, @StringRes int title, @DrawableRes int icon,
+            String broadcastAction, @NonNull Context context,
+            SystemActionsHandler systemActionsHandler) {
+        super(actionType, systemActionsHandler);
+        update(title, icon);
+        mBroadcastIntent = TvPipNotificationController.createPendingIntent(context,
+                broadcastAction);
+    }
+
+    void update(@StringRes int title, @DrawableRes int icon) {
+        mTitleResource = title;
+        mIconResource = icon;
+    }
+
+    void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
+        button.setTextAndDescription(mTitleResource);
+        button.setImageResource(mIconResource);
+        button.setEnabled(true);
+    }
+
+    PendingIntent getPendingIntent() {
+        return mBroadcastIntent;
+    }
+
+    @Override
+    Notification.Action toNotificationAction(Context context) {
+        Notification.Action.Builder builder = new Notification.Action.Builder(
+                Icon.createWithResource(context, mIconResource),
+                context.getString(mTitleResource),
+                mBroadcastIntent);
+
+        builder.setSemanticAction(isCloseAction()
+                ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE);
+        builder.setContextual(true);
+        return builder.build();
+    }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index e7ec15e..89538cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -16,9 +16,6 @@
 
 package com.android.wm.shell.splitscreen;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
-
 import android.content.Context;
 import android.view.SurfaceSession;
 import android.window.WindowContainerToken;
@@ -34,8 +31,6 @@
  * @see StageCoordinator
  */
 class MainStage extends StageTaskListener {
-    private static final String TAG = MainStage.class.getSimpleName();
-
     private boolean mIsActive = false;
 
     MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
@@ -52,15 +47,8 @@
     void activate(WindowContainerTransaction wct, boolean includingTopTask) {
         if (mIsActive) return;
 
-        final WindowContainerToken rootToken = mRootTaskInfo.token;
         if (includingTopTask) {
-            wct.reparentTasks(
-                    null /* currentParent */,
-                    rootToken,
-                    CONTROLLED_WINDOWING_MODES,
-                    CONTROLLED_ACTIVITY_TYPES,
-                    true /* onTop */,
-                    true /* reparentTopOnly */);
+            reparentTopTask(wct);
         }
 
         mIsActive = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 9329d02..ef70d9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -18,7 +18,6 @@
 
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -98,7 +97,6 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
@@ -123,7 +121,8 @@
     public static final int EXIT_REASON_SCREEN_LOCKED = 7;
     public static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
     public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
-    public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 10;
+    public static final int EXIT_REASON_RECREATE_SPLIT = 10;
+    public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 11;
     @IntDef(value = {
             EXIT_REASON_UNKNOWN,
             EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -135,6 +134,7 @@
             EXIT_REASON_SCREEN_LOCKED,
             EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
             EXIT_REASON_CHILD_TASK_ENTER_PIP,
+            EXIT_REASON_RECREATE_SPLIT,
             EXIT_REASON_FULLSCREEN_SHORTCUT,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -171,7 +171,7 @@
     private final IconProvider mIconProvider;
     private final Optional<RecentTasksController> mRecentTasksOptional;
     private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
-    private final String[] mMultiInstancesComponents;
+    private final String[] mAppsSupportMultiInstances;
 
     @VisibleForTesting
     StageCoordinator mStageCoordinator;
@@ -221,8 +221,8 @@
 
         // TODO(255224696): Remove the config once having a way for client apps to opt-in
         //                  multi-instances split.
-        mMultiInstancesComponents = mContext.getResources()
-                .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
+        mAppsSupportMultiInstances = mContext.getResources()
+                .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
     }
 
     @VisibleForTesting
@@ -261,8 +261,8 @@
         mStageCoordinator = stageCoordinator;
         mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
         shellInit.addInitCallback(this::onInit, this);
-        mMultiInstancesComponents = mContext.getResources()
-                .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
+        mAppsSupportMultiInstances = mContext.getResources()
+                .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
     }
 
     public SplitScreen asSplitScreen() {
@@ -472,7 +472,7 @@
      */
     public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
             @Nullable Bundle options, UserHandle user, @NonNull InstanceId instanceId) {
-        mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
+        mStageCoordinator.onRequestToSplit(instanceId, ENTER_REASON_LAUNCHER);
         startShortcut(packageName, shortcutId, position, options, user);
     }
 
@@ -520,7 +520,7 @@
      */
     public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
             @SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) {
-        mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
+        mStageCoordinator.onRequestToSplit(instanceId, ENTER_REASON_LAUNCHER);
         startIntent(intent, fillInIntent, position, options);
     }
 
@@ -529,7 +529,7 @@
             @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
             InstanceId instanceId) {
         Intent fillInIntent = null;
-        if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)) {
+        if (launchSameAppAdjacently(pendingIntent, taskId)) {
             if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
                 fillInIntent = new Intent();
                 fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -556,7 +556,7 @@
             int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
             float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         Intent fillInIntent = null;
-        if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)) {
+        if (launchSameAppAdjacently(pendingIntent, taskId)) {
             if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
                 fillInIntent = new Intent();
                 fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -578,7 +578,7 @@
             float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
         Intent fillInIntent1 = null;
         Intent fillInIntent2 = null;
-        if (launchSameComponentAdjacently(pendingIntent1, pendingIntent2)) {
+        if (launchSameAppAdjacently(pendingIntent1, pendingIntent2)) {
             if (supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
                 fillInIntent1 = new Intent();
                 fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -613,7 +613,7 @@
         if (fillInIntent == null) fillInIntent = new Intent();
         fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
 
-        if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) {
+        if (launchSameAppAdjacently(position, intent)) {
             final ComponentName launching = intent.getIntent().getComponent();
             if (supportMultiInstancesSplit(launching)) {
                 // To prevent accumulating large number of instances in the background, reuse task
@@ -647,47 +647,52 @@
         mStageCoordinator.startIntent(intent, fillInIntent, position, options);
     }
 
-    /** Returns {@code true} if it's launching the same component on both sides of the split. */
-    private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent,
-            @SplitPosition int position, int taskId) {
-        if (pendingIntent == null || pendingIntent.getIntent() == null) return false;
-
-        final ComponentName launchingActivity = pendingIntent.getIntent().getComponent();
-        if (launchingActivity == null) return false;
-
-        if (taskId != INVALID_TASK_ID) {
-            final ActivityManager.RunningTaskInfo taskInfo =
-                    mTaskOrganizer.getRunningTaskInfo(taskId);
-            if (taskInfo != null) {
-                return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
-            }
-            return false;
+    @Nullable
+    private String getPackageName(Intent intent) {
+        if (intent == null || intent.getComponent() == null) {
+            return null;
         }
-
-        if (!isSplitScreenVisible()) {
-            // Split screen is not yet activated, check if the current top running task is valid to
-            // split together.
-            final ActivityManager.RunningTaskInfo topRunningTask = mRecentTasksOptional
-                    .map(recentTasks -> recentTasks.getTopRunningTask()).orElse(null);
-            if (topRunningTask != null && isValidToEnterSplitScreen(topRunningTask)) {
-                return Objects.equals(topRunningTask.baseIntent.getComponent(), launchingActivity);
-            }
-            return false;
-        }
-
-        // Compare to the adjacent side of the split to determine if this is launching the same
-        // component adjacently.
-        final ActivityManager.RunningTaskInfo pairedTaskInfo =
-                getTaskInfo(SplitLayout.reversePosition(position));
-        final ComponentName pairedActivity = pairedTaskInfo != null
-                ? pairedTaskInfo.baseIntent.getComponent() : null;
-        return Objects.equals(launchingActivity, pairedActivity);
+        return intent.getComponent().getPackageName();
     }
 
-    private boolean launchSameComponentAdjacently(PendingIntent pendingIntent1,
+    private boolean launchSameAppAdjacently(@SplitPosition int position,
+            PendingIntent pendingIntent) {
+        ActivityManager.RunningTaskInfo adjacentTaskInfo = null;
+        if (isSplitScreenVisible()) {
+            adjacentTaskInfo = getTaskInfo(SplitLayout.reversePosition(position));
+        } else {
+            adjacentTaskInfo = mRecentTasksOptional
+                    .map(recentTasks -> recentTasks.getTopRunningTask()).orElse(null);
+            if (!isValidToEnterSplitScreen(adjacentTaskInfo)) {
+                return false;
+            }
+        }
+
+        if (adjacentTaskInfo == null) {
+            return false;
+        }
+
+        final String targetPackageName = getPackageName(pendingIntent.getIntent());
+        final String adjacentPackageName = getPackageName(adjacentTaskInfo.baseIntent);
+        return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
+    }
+
+    private boolean launchSameAppAdjacently(PendingIntent pendingIntent, int taskId) {
+        final ActivityManager.RunningTaskInfo adjacentTaskInfo =
+                mTaskOrganizer.getRunningTaskInfo(taskId);
+        if (adjacentTaskInfo == null) {
+            return false;
+        }
+        final String targetPackageName = getPackageName(pendingIntent.getIntent());
+        final String adjacentPackageName = getPackageName(adjacentTaskInfo.baseIntent);
+        return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
+    }
+
+    private boolean launchSameAppAdjacently(PendingIntent pendingIntent1,
             PendingIntent pendingIntent2) {
-        return Objects.equals(pendingIntent1.getIntent().getComponent(),
-                pendingIntent2.getIntent().getComponent());
+        final String targetPackageName = getPackageName(pendingIntent1.getIntent());
+        final String adjacentPackageName = getPackageName(pendingIntent2.getIntent());
+        return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
     }
 
     @VisibleForTesting
@@ -695,9 +700,9 @@
     boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
         if (launching == null) return false;
 
-        final String componentName = launching.flattenToString();
-        for (int i = 0; i < mMultiInstancesComponents.length; i++) {
-            if (mMultiInstancesComponents[i].equals(componentName)) {
+        final String packageName = launching.getPackageName();
+        for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
+            if (mAppsSupportMultiInstances[i].equals(packageName)) {
                 return true;
             }
         }
@@ -781,10 +786,10 @@
         return splitTasksLayer;
     }
     /**
-     * Sets drag info to be logged when splitscreen is entered.
+     * Drop callback when splitscreen is entered.
      */
-    public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
-        mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
+    public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+        mStageCoordinator.onDroppedToSplit(position, dragSessionId);
     }
 
     /**
@@ -812,6 +817,8 @@
                 return "APP_DOES_NOT_SUPPORT_MULTIWINDOW";
             case EXIT_REASON_CHILD_TASK_ENTER_PIP:
                 return "CHILD_TASK_ENTER_PIP";
+            case EXIT_REASON_RECREATE_SPLIT:
+                return "RECREATE_SPLIT";
             default:
                 return "unknown reason, reason int = " + exitReason;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 1016e1b..5483fa5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -21,9 +21,11 @@
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__UNKNOWN_ENTER;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__CHILD_TASK_ENTER_PIP;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
@@ -37,9 +39,11 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_UNKNOWN;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED;
@@ -182,6 +186,10 @@
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
             case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
+            case EXIT_REASON_CHILD_TASK_ENTER_PIP:
+                return SPLITSCREEN_UICHANGED__EXIT_REASON__CHILD_TASK_ENTER_PIP;
+            case EXIT_REASON_RECREATE_SPLIT:
+                return SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
             case EXIT_REASON_FULLSCREEN_SHORTCUT:
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
             case EXIT_REASON_UNKNOWN:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index da8dc87..5be29db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -49,10 +49,11 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
-import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
@@ -199,7 +200,8 @@
     // and exit, since exit itself can trigger a number of changes that update the stages.
     private boolean mShouldUpdateRecents;
     private boolean mExitSplitScreenOnHide;
-    private boolean mIsDividerRemoteAnimating;
+    private boolean mIsSplitEntering;
+    private boolean mIsDropEntering;
     private boolean mIsExiting;
 
     /** The target stage to dismiss to when unlock after folded. */
@@ -347,10 +349,14 @@
         return mSplitTransitions;
     }
 
-    boolean isSplitScreenVisible() {
+    public boolean isSplitScreenVisible() {
         return mSideStageListener.mVisible && mMainStageListener.mVisible;
     }
 
+    public boolean isSplitActive() {
+        return mMainStage.isActive();
+    }
+
     @StageType
     int getStageOfTask(int taskId) {
         if (mMainStage.containsTask(taskId)) {
@@ -373,11 +379,14 @@
             targetStage = mSideStage;
             sideStagePosition = stagePosition;
         } else {
-            if (mMainStage.isActive()) {
+            if (isSplitScreenVisible()) {
                 // If the split screen is activated, retrieves target stage based on position.
                 targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
                 sideStagePosition = mSideStagePosition;
             } else {
+                // Exit split if it running background.
+                exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+
                 targetStage = mSideStage;
                 sideStagePosition = stagePosition;
             }
@@ -673,6 +682,10 @@
             @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
             @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
+        if (!isSplitScreenVisible()) {
+            exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+        }
+
         // Init divider first to make divider leash for remote animation target.
         mSplitLayout.init();
         mSplitLayout.setDivideRatio(splitRatio);
@@ -685,11 +698,13 @@
 
         // Set false to avoid record new bounds with old task still on top;
         mShouldUpdateRecents = false;
-        mIsDividerRemoteAnimating = true;
+        mIsSplitEntering = true;
 
         final WindowContainerTransaction evictWct = new WindowContainerTransaction();
-        prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct);
-        prepareEvictChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, evictWct);
+        if (isSplitScreenVisible()) {
+            mMainStage.evictAllChildren(evictWct);
+            mSideStage.evictAllChildren(evictWct);
+        }
 
         IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
             @Override
@@ -769,7 +784,7 @@
 
     private void onRemoteAnimationFinishedOrCancelled(boolean cancel,
             WindowContainerTransaction evictWct) {
-        mIsDividerRemoteAnimating = false;
+        mIsSplitEntering = false;
         mShouldUpdateRecents = true;
         // If any stage has no child after animation finished, it means that split will display
         // nothing, such status will happen if task and intent is same app but not support
@@ -781,6 +796,9 @@
             mSplitUnsupportedToast.show();
         } else {
             mSyncQueue.queue(evictWct);
+            mSyncQueue.runInSync(t -> {
+                updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+            });
         }
     }
 
@@ -815,7 +833,7 @@
         switch (stage) {
             case STAGE_TYPE_UNDEFINED: {
                 if (position != SPLIT_POSITION_UNDEFINED) {
-                    if (mMainStage.isActive()) {
+                    if (isSplitScreenVisible()) {
                         // Use the stage of the specified position
                         options = resolveStartStage(
                                 position == mSideStagePosition ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN,
@@ -1045,14 +1063,13 @@
             }
         });
         mShouldUpdateRecents = false;
-        mIsDividerRemoteAnimating = false;
+        mIsSplitEntering = false;
 
         mSplitLayout.getInvisibleBounds(mTempRect1);
         if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) {
             mSideStage.removeAllTasks(wct, false /* toTop */);
             mMainStage.deactivate(wct, false /* toTop */);
             wct.reorder(mRootTaskInfo.token, false /* onTop */);
-            wct.setForceTranslucent(mRootTaskInfo.token, true);
             wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
             onTransitionAnimationComplete();
         } else {
@@ -1064,6 +1081,8 @@
             wct.setSmallestScreenWidthDp(childrenToTop.mRootTaskInfo.token,
                     SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
         }
+        wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+                false /* reparentLeafTaskIfRelaunch */);
         mSyncQueue.queue(wct);
         mSyncQueue.runInSync(t -> {
             t.setWindowCrop(mMainStage.mRootLeash, null)
@@ -1082,7 +1101,6 @@
                     mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
                     mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
                     finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
-                    finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
                     finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
                     mSyncQueue.queue(finishedWCT);
                     mSyncQueue.runInSync(at -> {
@@ -1374,7 +1392,7 @@
                 && !ENABLE_SHELL_TRANSITIONS) {
             // Clear the divider remote animating flag as the divider will be re-rendered to apply
             // the new rotation config.
-            mIsDividerRemoteAnimating = false;
+            mIsSplitEntering = false;
             mSplitLayout.update(null /* t */);
             onLayoutSizeChanged(mSplitLayout);
         }
@@ -1423,6 +1441,36 @@
         });
     }
 
+    void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
+        if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
+                && !mIsSplitEntering) {
+            // Handle entring split case here if split already running background.
+            if (mIsDropEntering) {
+                mSplitLayout.resetDividerPosition();
+            } else {
+                mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+            }
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            mMainStage.reparentTopTask(wct);
+            mMainStage.evictAllChildren(wct);
+            mSideStage.evictOtherChildren(wct, taskId);
+            updateWindowBounds(mSplitLayout, wct);
+            wct.reorder(mRootTaskInfo.token, true);
+            wct.setForceTranslucent(mRootTaskInfo.token, false);
+
+            mSyncQueue.queue(wct);
+            mSyncQueue.runInSync(t -> {
+                if (mIsDropEntering) {
+                    updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+                    mIsDropEntering = false;
+                } else {
+                    mShowDecorImmediately = true;
+                    mSplitLayout.flingDividerToCenter();
+                }
+            });
+        }
+    }
+
     private void onRootTaskVanished() {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (mRootTaskInfo != null) {
@@ -1441,20 +1489,22 @@
             return;
         }
 
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (!mainStageVisible) {
+            wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+                    true /* setReparentLeafTaskIfRelaunch */);
+            wct.setForceTranslucent(mRootTaskInfo.token, true);
             // Both stages are not visible, check if it needs to dismiss split screen.
-            if (mExitSplitScreenOnHide
-                    // Don't dismiss split screen when both stages are not visible due to sleeping
-                    // display.
-                    || (!mMainStage.mRootTaskInfo.isSleeping
-                    && !mSideStage.mRootTaskInfo.isSleeping)) {
+            if (mExitSplitScreenOnHide) {
                 exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
             }
+        } else {
+            wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+                    false /* setReparentLeafTaskIfRelaunch */);
+            wct.setForceTranslucent(mRootTaskInfo.token, false);
         }
-
+        mSyncQueue.queue(wct);
         mSyncQueue.runInSync(t -> {
-            t.setVisibility(mSideStage.mRootLeash, sideStageVisible)
-                    .setVisibility(mMainStage.mRootLeash, mainStageVisible);
             setDividerVisibility(mainStageVisible, t);
         });
     }
@@ -1479,7 +1529,7 @@
         mDividerVisible = visible;
         sendSplitVisibilityChanged();
 
-        if (mIsDividerRemoteAnimating) {
+        if (mIsSplitEntering) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                     "   Skip animating divider bar due to it's remote animating.");
             return;
@@ -1499,7 +1549,7 @@
                     "   Skip animating divider bar due to divider leash not ready.");
             return;
         }
-        if (mIsDividerRemoteAnimating) {
+        if (mIsSplitEntering) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                     "   Skip animating divider bar due to it's remote animating.");
             return;
@@ -1555,26 +1605,21 @@
         if (!hasChildren && !mIsExiting && mMainStage.isActive()) {
             if (isSideStage && mMainStageListener.mVisible) {
                 // Exit to main stage if side stage no longer has children.
-                if (ENABLE_SHELL_TRANSITIONS) {
-                    exitSplitScreen(mMainStage, EXIT_REASON_APP_FINISHED);
-                } else {
-                    mSplitLayout.flingDividerToDismiss(
-                            mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT,
-                            EXIT_REASON_APP_FINISHED);
-                }
+                mSplitLayout.flingDividerToDismiss(
+                        mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT,
+                        EXIT_REASON_APP_FINISHED);
             } else if (!isSideStage && mSideStageListener.mVisible) {
                 // Exit to side stage if main stage no longer has children.
-                if (ENABLE_SHELL_TRANSITIONS) {
-                    exitSplitScreen(mSideStage, EXIT_REASON_APP_FINISHED);
-                } else {
-                    mSplitLayout.flingDividerToDismiss(
-                            mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
-                            EXIT_REASON_APP_FINISHED);
-                }
+                mSplitLayout.flingDividerToDismiss(
+                        mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
+                        EXIT_REASON_APP_FINISHED);
+            } else if (!isSplitScreenVisible()) {
+                exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
             }
         } else if (isSideStage && hasChildren && !mMainStage.isActive()) {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
             mSplitLayout.init();
+
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
             if (mLogger.isEnterRequestedByDrag()) {
                 prepareEnterSplitScreen(wct);
             } else {
@@ -1589,8 +1634,9 @@
 
             mSyncQueue.queue(wct);
             mSyncQueue.runInSync(t -> {
-                if (mLogger.isEnterRequestedByDrag()) {
+                if (mIsDropEntering) {
                     updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+                    mIsDropEntering = false;
                 } else {
                     mShowDecorImmediately = true;
                     mSplitLayout.flingDividerToCenter();
@@ -1945,10 +1991,6 @@
         }
     }
 
-    public boolean isSplitActive() {
-        return mMainStage.isActive();
-    }
-
     @Override
     public void mergeAnimation(IBinder transition, TransitionInfo info,
             SurfaceControl.Transaction t, IBinder mergeTarget,
@@ -2304,11 +2346,29 @@
     /**
      * Sets drag info to be logged when splitscreen is next entered.
      */
-    public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+    public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+        if (!isSplitScreenVisible()) {
+            mIsDropEntering = true;
+        }
+        if (!isSplitScreenVisible()) {
+            // If split running background, exit split first.
+            exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+        }
         mLogger.enterRequestedByDrag(position, dragSessionId);
     }
 
     /**
+     * Sets info to be logged when splitscreen is next entered.
+     */
+    public void onRequestToSplit(InstanceId sessionId, int enterReason) {
+        if (!isSplitScreenVisible()) {
+            // If split running background, exit split first.
+            exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+        }
+        mLogger.enterRequested(sessionId, enterReason);
+    }
+
+    /**
      * Logs the exit of splitscreen.
      */
     private void logExit(@ExitReason int exitReason) {
@@ -2343,6 +2403,11 @@
         }
 
         @Override
+        public void onChildTaskAppeared(int taskId) {
+            StageCoordinator.this.onChildTaskAppeared(this, taskId);
+        }
+
+        @Override
         public void onStatusChanged(boolean visible, boolean hasChildren) {
             if (!mHasRootTask) return;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 8a52c87..a841b7f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -22,6 +22,7 @@
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
@@ -69,6 +70,8 @@
     public interface StageListenerCallbacks {
         void onRootTaskAppeared();
 
+        void onChildTaskAppeared(int taskId);
+
         void onStatusChanged(boolean visible, boolean hasChildren);
 
         void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
@@ -185,6 +188,7 @@
                 // Status is managed/synchronized by the transition lifecycle.
                 return;
             }
+            mCallbacks.onChildTaskAppeared(taskId);
             sendStatusChanged();
         } else {
             throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
@@ -338,6 +342,14 @@
         }
     }
 
+    void evictOtherChildren(WindowContainerTransaction wct, int taskId) {
+        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
+            final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+            if (taskId == taskInfo.taskId) continue;
+            wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
+        }
+    }
+
     void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) {
         final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone();
         for (int i = 0; i < apps.length; i++) {
@@ -360,6 +372,12 @@
         }
     }
 
+    void reparentTopTask(WindowContainerTransaction wct) {
+        wct.reparentTasks(null /* currentParent */, mRootTaskInfo.token,
+                CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES,
+                true /* onTop */, true /* reparentTopOnly */);
+    }
+
     void resetBounds(WindowContainerTransaction wct) {
         wct.setBounds(mRootTaskInfo.token, null);
         wct.setAppBounds(mRootTaskInfo.token, null);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 3cba929..a2d7bc4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -111,7 +111,7 @@
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
-        if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitActive()) {
+        if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitScreenVisible()) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while "
                     + "Split-Screen is active, so treat it as Mixed.");
             if (request.getRemoteTransition() != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index afefd5d..42e2b3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -46,6 +46,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -56,7 +57,6 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.Optional;
-import java.util.function.Supplier;
 
 /**
  * View model for the window decoration with a caption and shadows. Works with
@@ -66,7 +66,6 @@
 public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
     private static final String TAG = "CaptionViewModel";
     private final CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
-    private final Supplier<InputManager> mInputManagerSupplier;
     private final ActivityTaskManager mActivityTaskManager;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Context mContext;
@@ -82,7 +81,7 @@
 
     private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
     private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
-    private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
+    private InputMonitorFactory mInputMonitorFactory;
 
     public CaptionWindowDecorViewModel(
             Context context,
@@ -101,10 +100,11 @@
                 syncQueue,
                 desktopModeController,
                 new CaptionWindowDecoration.Factory(),
-                InputManager::getInstance);
+                new InputMonitorFactory());
     }
 
-    public CaptionWindowDecorViewModel(
+    @VisibleForTesting
+    CaptionWindowDecorViewModel(
             Context context,
             Handler mainHandler,
             Choreographer mainChoreographer,
@@ -113,8 +113,7 @@
             SyncTransactionQueue syncQueue,
             Optional<DesktopModeController> desktopModeController,
             CaptionWindowDecoration.Factory captionWindowDecorFactory,
-            Supplier<InputManager> inputManagerSupplier) {
-
+            InputMonitorFactory inputMonitorFactory) {
         mContext = context;
         mMainHandler = mainHandler;
         mMainChoreographer = mainChoreographer;
@@ -125,11 +124,7 @@
         mDesktopModeController = desktopModeController;
 
         mCaptionWindowDecorFactory = captionWindowDecorFactory;
-        mInputManagerSupplier = inputManagerSupplier;
-    }
-
-    void setEventReceiverFactory(EventReceiverFactory eventReceiverFactory) {
-        mEventReceiverFactory = eventReceiverFactory;
+        mInputMonitorFactory = inputMonitorFactory;
     }
 
     @Override
@@ -205,7 +200,6 @@
         decoration.close();
         int displayId = taskInfo.displayId;
         if (mEventReceiversByDisplay.contains(displayId)) {
-            EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
             removeTaskFromEventReceiver(displayId);
         }
     }
@@ -408,12 +402,6 @@
         }
     }
 
-    class EventReceiverFactory {
-        EventReceiver create(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
-            return new EventReceiver(inputMonitor, channel, looper);
-        }
-    }
-
     /**
      * Handle MotionEvents relevant to focused task's caption that don't directly touch it
      *
@@ -500,11 +488,11 @@
     }
 
     private void createInputChannel(int displayId) {
-        InputManager inputManager = mInputManagerSupplier.get();
+        InputManager inputManager = InputManager.getInstance();
         InputMonitor inputMonitor =
-                inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
-        EventReceiver eventReceiver = mEventReceiverFactory.create(
-                inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper());
+                mInputMonitorFactory.create(inputManager, mContext);
+        EventReceiver eventReceiver = new EventReceiver(inputMonitor,
+                inputMonitor.getInputChannel(), Looper.myLooper());
         mEventReceiversByDisplay.put(displayId, eventReceiver);
     }
 
@@ -562,4 +550,12 @@
             mWindowDecorByTaskId.get(taskId).closeHandleMenu();
         }
     }
+
+    static class InputMonitorFactory {
+        InputMonitor create(InputManager inputManager, Context context) {
+            return inputManager.monitorGestureInput("caption-touch", context.getDisplayId());
+        }
+    }
 }
+
+
diff --git a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
index 59d9104..fac0461 100644
--- a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
@@ -19,6 +19,8 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.android.wm.shell.tests">
 
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+
     <application android:debuggable="true" android:largeHeap="true">
         <uses-library android:name="android.test.mock" />
         <uses-library android:name="android.test.runner" />
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index b3c9e23..707c049 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -23,6 +23,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
@@ -334,10 +335,10 @@
     }
 
     @Test
-    public void testHandleTransitionRequest_notTransitOpen_returnsNull() {
+    public void testHandleTransitionRequest_unsupportedTransit_returnsNull() {
         WindowContainerTransaction wct = mController.handleRequest(
                 new Binder(),
-                new TransitionRequestInfo(TRANSIT_TO_FRONT, null /* trigger */, null /* remote */));
+                new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
         assertThat(wct).isNull();
     }
 
@@ -352,7 +353,7 @@
     }
 
     @Test
-    public void testHandleTransitionRequest_returnsWct() {
+    public void testHandleTransitionRequest_taskOpen_returnsWct() {
         RunningTaskInfo trigger = new RunningTaskInfo();
         trigger.token = new MockToken().mToken;
         trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -362,6 +363,17 @@
         assertThat(wct).isNotNull();
     }
 
+    @Test
+    public void testHandleTransitionRequest_taskToFront_returnsWct() {
+        RunningTaskInfo trigger = new RunningTaskInfo();
+        trigger.token = new MockToken().mToken;
+        trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        WindowContainerTransaction wct = mController.handleRequest(
+                mock(IBinder.class),
+                new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */));
+        assertThat(wct).isNotNull();
+    }
+
     private DesktopModeController createController() {
         return new DesktopModeController(mContext, mShellInit, mShellController,
                 mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
new file mode 100644
index 0000000..91040e9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.pip.tv;
+
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.PipMediaController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link TvPipActionsProvider}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class TvPipActionProviderTest extends ShellTestCase {
+    private static final String TAG = TvPipActionProviderTest.class.getSimpleName();
+    private TvPipActionsProvider mActionsProvider;
+
+    @Mock
+    private PipMediaController mMockPipMediaController;
+    @Mock
+    private TvPipActionsProvider.Listener mMockListener;
+    @Mock
+    private TvPipAction.SystemActionsHandler mMockSystemActionsHandler;
+    @Mock
+    private Icon mMockIcon;
+    @Mock
+    private PendingIntent mMockPendingIntent;
+
+    private RemoteAction createRemoteAction(int identifier) {
+        return new RemoteAction(mMockIcon, "" + identifier, "" + identifier, mMockPendingIntent);
+    }
+
+    private List<RemoteAction> createRemoteActions(int numberOfActions) {
+        List<RemoteAction> actions = new ArrayList<>();
+        for (int i = 0; i < numberOfActions; i++) {
+            actions.add(createRemoteAction(i));
+        }
+        return actions;
+    }
+
+    private boolean checkActionsMatch(List<TvPipAction> actions, int[] actionTypes) {
+        for (int i = 0; i < actions.size(); i++) {
+            int type = actions.get(i).getActionType();
+            if (type != actionTypes[i]) {
+                Log.e(TAG, "Action at index " + i + ": found " + type
+                        + ", expected " + actionTypes[i]);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mActionsProvider = new TvPipActionsProvider(mContext, mMockPipMediaController,
+                mMockSystemActionsHandler);
+    }
+
+    @Test
+    public void defaultSystemActions_regularPip() {
+        mActionsProvider.updateExpansionEnabled(false);
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+    }
+
+    @Test
+    public void defaultSystemActions_expandedPip() {
+        mActionsProvider.updateExpansionEnabled(true);
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+    }
+
+    @Test
+    public void expandedPip_enableExpansion_enable() {
+        // PiP has expanded PiP disabled.
+        mActionsProvider.updateExpansionEnabled(false);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.updateExpansionEnabled(true);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+        verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 0, /* index= */ 3);
+    }
+
+    @Test
+    public void expandedPip_enableExpansion_disable() {
+        mActionsProvider.updateExpansionEnabled(true);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.updateExpansionEnabled(false);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 0, /* index= */ 3);
+    }
+
+    @Test
+    public void expandedPip_enableExpansion_AlreadyEnabled() {
+        mActionsProvider.updateExpansionEnabled(true);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.updateExpansionEnabled(true);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+    }
+
+    @Test
+    public void expandedPip_toggleExpansion() {
+        // PiP has expanded PiP enabled, but is in a collapsed state
+        mActionsProvider.updateExpansionEnabled(true);
+        mActionsProvider.onPipExpansionToggled(/* expanded= */ false);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.onPipExpansionToggled(/* expanded= */ true);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+        verify(mMockListener).onActionsChanged(0, 1, 3);
+    }
+
+    @Test
+    public void customActions_added() {
+        mActionsProvider.updateExpansionEnabled(false);
+        mActionsProvider.addListener(mMockListener);
+
+        mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
+    }
+
+    @Test
+    public void customActions_replacedMore() {
+        mActionsProvider.updateExpansionEnabled(false);
+        mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(createRemoteActions(3), null);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_CUSTOM, ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 2, /* index= */ 2);
+    }
+
+    @Test
+    public void customActions_replacedLess() {
+        mActionsProvider.updateExpansionEnabled(false);
+        mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(createRemoteActions(0), null);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ -2, /* updated= */ 0, /* index= */ 2);
+    }
+
+    @Test
+    public void customCloseAdded() {
+        mActionsProvider.updateExpansionEnabled(false);
+
+        List<RemoteAction> customActions = new ArrayList<>();
+        mActionsProvider.setAppActions(customActions, null);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(customActions, createRemoteAction(0));
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+    }
+
+    @Test
+    public void customClose_matchesOtherCustomAction() {
+        mActionsProvider.updateExpansionEnabled(false);
+
+        List<RemoteAction> customActions = createRemoteActions(2);
+        RemoteAction customClose = createRemoteAction(/* id= */ 10);
+        customActions.add(customClose);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(customActions, customClose);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+        verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
+    }
+
+    @Test
+    public void mediaActions_added_whileCustomActionsExist() {
+        mActionsProvider.updateExpansionEnabled(false);
+        mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.onMediaActionsChanged(createRemoteActions(3));
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_MOVE}));
+        verify(mMockListener, times(0)).onActionsChanged(anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void customActions_removed_whileMediaActionsExist() {
+        mActionsProvider.updateExpansionEnabled(false);
+        mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
+        mActionsProvider.setAppActions(createRemoteActions(3), null);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(createRemoteActions(0), null);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 2, /* index= */ 2);
+    }
+
+    @Test
+    public void customCloseOnly_mediaActionsShowing() {
+        mActionsProvider.updateExpansionEnabled(false);
+        mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(createRemoteActions(0), createRemoteAction(5));
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+    }
+
+    @Test
+    public void customActions_showDisabledActions() {
+        mActionsProvider.updateExpansionEnabled(false);
+
+        List<RemoteAction> customActions = createRemoteActions(2);
+        customActions.get(0).setEnabled(false);
+        mActionsProvider.setAppActions(customActions, null);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_MOVE}));
+    }
+
+    @Test
+    public void mediaActions_hideDisabledActions() {
+        mActionsProvider.updateExpansionEnabled(false);
+
+        List<RemoteAction> customActions = createRemoteActions(2);
+        customActions.get(0).setEnabled(false);
+        mActionsProvider.onMediaActionsChanged(customActions);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE}));
+    }
+
+}
+
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
index ad6fced..0dbf30d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -21,14 +21,15 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
 import android.hardware.input.InputManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -37,9 +38,9 @@
 import android.view.InputChannel;
 import android.view.InputMonitor;
 import android.view.SurfaceControl;
+import android.view.SurfaceView;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.rule.GrantPermissionRule;
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
@@ -55,37 +56,28 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
-import java.util.function.Supplier;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /** Tests of {@link CaptionWindowDecorViewModel} */
 @SmallTest
 public class CaptionWindowDecorViewModelTests extends ShellTestCase {
-    @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
 
+    private static final String TAG = "CaptionWindowDecorViewModelTests";
+
+    @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
     @Mock private CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
 
     @Mock private Handler mMainHandler;
-
     @Mock private Choreographer mMainChoreographer;
-
     @Mock private ShellTaskOrganizer mTaskOrganizer;
-
     @Mock private DisplayController mDisplayController;
-
     @Mock private SyncTransactionQueue mSyncQueue;
-
     @Mock private DesktopModeController mDesktopModeController;
-
     @Mock private InputMonitor mInputMonitor;
-
-    @Mock private InputChannel mInputChannel;
-
-    @Mock private CaptionWindowDecorViewModel.EventReceiverFactory mEventReceiverFactory;
-
-    @Mock private CaptionWindowDecorViewModel.EventReceiver mEventReceiver;
-
     @Mock private InputManager mInputManager;
 
+    @Mock private CaptionWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
     private final List<InputManager> mMockInputManagers = new ArrayList<>();
 
     private CaptionWindowDecorViewModel mCaptionWindowDecorViewModel;
@@ -104,44 +96,46 @@
                 mSyncQueue,
                 Optional.of(mDesktopModeController),
                 mCaptionWindowDecorFactory,
-                new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
-        mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
+                mMockInputMonitorFactory
+            );
 
         doReturn(mCaptionWindowDecoration)
             .when(mCaptionWindowDecorFactory)
             .create(any(), any(), any(), any(), any(), any(), any(), any());
 
-        when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
-        when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver);
-        when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
+        when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
+        // InputChannel cannot be mocked because it passes to InputEventReceiver.
+        final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
+        inputChannels[0].dispose();
+        when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
     }
 
     @Test
     public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception {
-        Looper.prepare();
         final int taskId = 1;
         final ActivityManager.RunningTaskInfo taskInfo =
-                createTaskInfo(taskId, WINDOWING_MODE_FREEFORM);
+                createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
         SurfaceControl surfaceControl = mock(SurfaceControl.class);
-        final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
-        final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-        GrantPermissionRule.grant(android.Manifest.permission.MONITOR_INPUT);
+        runOnMainThread(() -> {
+            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
 
-        mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+            mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+
+            taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+            taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+            mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+        });
         verify(mCaptionWindowDecorFactory)
                 .create(
-                    mContext,
-                    mDisplayController,
-                    mTaskOrganizer,
-                    taskInfo,
-                    surfaceControl,
-                    mMainHandler,
-                    mMainChoreographer,
-                    mSyncQueue);
-
-        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
-        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
-        mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+                        mContext,
+                        mDisplayController,
+                        mTaskOrganizer,
+                        taskInfo,
+                        surfaceControl,
+                        mMainHandler,
+                        mMainChoreographer,
+                        mSyncQueue);
         verify(mCaptionWindowDecoration).close();
     }
 
@@ -149,70 +143,105 @@
     public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception {
         final int taskId = 1;
         final ActivityManager.RunningTaskInfo taskInfo =
-                createTaskInfo(taskId, WINDOWING_MODE_UNDEFINED);
+                createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_UNDEFINED);
         SurfaceControl surfaceControl = mock(SurfaceControl.class);
-        final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
-        final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+        runOnMainThread(() -> {
+            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+            taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
 
-        mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+            mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
 
-        verify(mCaptionWindowDecorFactory, never())
+            taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+            taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+
+            mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+        });
+        verify(mCaptionWindowDecorFactory, times(1))
                 .create(
-                    mContext,
-                    mDisplayController,
-                    mTaskOrganizer,
-                    taskInfo,
-                    surfaceControl,
-                    mMainHandler,
-                    mMainChoreographer,
-                    mSyncQueue);
-
-        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
-
-        mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
-
-        verify(mCaptionWindowDecorFactory)
-                .create(
-                    mContext,
-                    mDisplayController,
-                    mTaskOrganizer,
-                    taskInfo,
-                    surfaceControl,
-                    mMainHandler,
-                    mMainChoreographer,
-                    mSyncQueue);
+                        mContext,
+                        mDisplayController,
+                        mTaskOrganizer,
+                        taskInfo,
+                        surfaceControl,
+                        mMainHandler,
+                        mMainChoreographer,
+                        mSyncQueue);
     }
 
-    private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
+    @Test
+    public void testCreateAndDisposeEventReceiver() throws Exception {
+        final int taskId = 1;
+        final ActivityManager.RunningTaskInfo taskInfo =
+                createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+        runOnMainThread(() -> {
+            SurfaceControl surfaceControl = mock(SurfaceControl.class);
+            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+            mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+
+            mCaptionWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+        });
+        verify(mMockInputMonitorFactory).create(any(), any());
+        verify(mInputMonitor).dispose();
+    }
+
+    @Test
+    public void testEventReceiversOnMultipleDisplays() throws Exception {
+        runOnMainThread(() -> {
+            SurfaceView surfaceView = new SurfaceView(mContext);
+            final DisplayManager mDm = mContext.getSystemService(DisplayManager.class);
+            final VirtualDisplay secondaryDisplay = mDm.createVirtualDisplay(
+                    "testEventReceiversOnMultipleDisplays", /*width=*/ 400, /*height=*/ 400,
+                    /*densityDpi=*/ 320, surfaceView.getHolder().getSurface(),
+                    DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+            int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId();
+
+            final int taskId = 1;
+            final ActivityManager.RunningTaskInfo taskInfo =
+                    createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+            final ActivityManager.RunningTaskInfo secondTaskInfo =
+                    createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+            final ActivityManager.RunningTaskInfo thirdTaskInfo =
+                    createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+
+            SurfaceControl surfaceControl = mock(SurfaceControl.class);
+            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+            mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+            mCaptionWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl,
+                    startT, finishT);
+            mCaptionWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl,
+                    startT, finishT);
+            mCaptionWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo);
+            mCaptionWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+        });
+        verify(mMockInputMonitorFactory, times(2)).create(any(), any());
+        verify(mInputMonitor, times(1)).dispose();
+    }
+
+    private void runOnMainThread(Runnable r) throws Exception {
+        final Handler mainHandler = new Handler(Looper.getMainLooper());
+        final CountDownLatch latch = new CountDownLatch(1);
+        mainHandler.post(() -> {
+            r.run();
+            latch.countDown();
+        });
+        latch.await(20, TimeUnit.MILLISECONDS);
+    }
+
+    private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId,
+            int displayId, int windowingMode) {
         ActivityManager.RunningTaskInfo taskInfo =
                  new TestRunningTaskInfoBuilder()
-                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setDisplayId(displayId)
                 .setVisible(true)
                 .build();
         taskInfo.taskId = taskId;
         taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
         return taskInfo;
     }
-
-    private static class MockObjectSupplier<T> implements Supplier<T> {
-        private final List<T> mObjects;
-        private final Supplier<T> mDefaultSupplier;
-        private int mNumOfCalls = 0;
-
-        private MockObjectSupplier(List<T> objects, Supplier<T> defaultSupplier) {
-            mObjects = objects;
-            mDefaultSupplier = defaultSupplier;
-        }
-
-        @Override
-        public T get() {
-            final T mock =
-                    mNumOfCalls < mObjects.size() ? mObjects.get(mNumOfCalls)
-                        : mDefaultSupplier.get();
-            ++mNumOfCalls;
-            return mock;
-        }
-    }
 }
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
index 7c732d7..7b9a93f 100644
--- a/libs/hwui/jni/Mesh.cpp
+++ b/libs/hwui/jni/Mesh.cpp
@@ -37,16 +37,6 @@
     return indexBuffer;
 }
 
-// TODO(b/260252882): undefine SK_LEGACY_MESH_MAKE and remove this.
-template <typename T>
-SkMesh get_mesh_from_result(T&& result) {
-#ifdef SK_LEGACY_MESH_MAKE
-    return result;
-#else
-    return result.mesh;
-#endif
-}
-
 static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
                   jboolean isDirect, jint vertexCount, jint vertexOffset, jint left, jint top,
                   jint right, jint bottom) {
@@ -54,8 +44,9 @@
     sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
             genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect);
     auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto mesh = get_mesh_from_result(SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer,
-                                                  vertexCount, vertexOffset, nullptr, skRect));
+    auto mesh = SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
+                             vertexOffset, nullptr, skRect)
+                        .mesh;
     auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
     return reinterpret_cast<jlong>(meshPtr.release());
 }
@@ -70,9 +61,10 @@
     sk_sp<SkMesh::IndexBuffer> skIndexBuffer =
             genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
     auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto mesh = get_mesh_from_result(SkMesh::MakeIndexed(
-            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
-            skIndexBuffer, indexCount, indexOffset, nullptr, skRect));
+    auto mesh = SkMesh::MakeIndexed(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
+                                    vertexOffset, skIndexBuffer, indexCount, indexOffset, nullptr,
+                                    skRect)
+                        .mesh;
     auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
     return reinterpret_cast<jlong>(meshPtr.release());
 }
@@ -81,15 +73,17 @@
     auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
     auto mesh = wrapper->mesh;
     if (indexed) {
-        wrapper->mesh = get_mesh_from_result(SkMesh::MakeIndexed(
-                sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
-                mesh.vertexCount(), mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()),
-                mesh.indexCount(), mesh.indexOffset(), wrapper->builder.fUniforms, mesh.bounds()));
+        wrapper->mesh = SkMesh::MakeIndexed(sk_ref_sp(mesh.spec()), mesh.mode(),
+                                            sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(),
+                                            mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()),
+                                            mesh.indexCount(), mesh.indexOffset(),
+                                            wrapper->builder.fUniforms, mesh.bounds())
+                                .mesh;
     } else {
-        wrapper->mesh = get_mesh_from_result(
-                SkMesh::Make(sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
-                             mesh.vertexCount(), mesh.vertexOffset(), wrapper->builder.fUniforms,
-                             mesh.bounds()));
+        wrapper->mesh = SkMesh::Make(sk_ref_sp(mesh.spec()), mesh.mode(),
+                                     sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(),
+                                     mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds())
+                                .mesh;
     }
 }
 
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 9b81c09..3619d3a 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -195,6 +195,21 @@
     public static final String GPS_PROVIDER = "gps";
 
     /**
+     * Standard name of the GNSS hardware location provider.
+     *
+     * <p>This provider is similar to {@link LocationManager#GPS_PROVIDER}, but it directly uses the
+     * HAL GNSS implementation and doesn't go through any provider overrides that may exist. This
+     * provider will only be available when the GPS_PROVIDER is overridden with a proxy using {@link
+     * android.location.provider.LocationProviderBase#ACTION_GNSS_PROVIDER}, and is intended only
+     * for use internally by the location provider system.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
+    public static final String GPS_HARDWARE_PROVIDER = "gps_hardware";
+
+    /**
      * A special location provider for receiving locations without actively initiating a location
      * fix. This location provider is always present.
      *
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index 5acec79..18672b7 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -104,8 +104,6 @@
     /**
      * The action the wrapping service should have in its intent filter to implement the
      * {@link android.location.LocationManager#GPS_PROVIDER}.
-     *
-     * @hide
      */
     public static final String ACTION_GNSS_PROVIDER =
             "android.location.provider.action.GNSS_PROVIDER";
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index ccd4ed0..4d3f05b 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -373,6 +373,8 @@
      * Use {@link #ENCODING_DTS_UHD_P2} to transmit DTS UHD Profile 2 (aka DTS:X Profile 2)
      * bitstream. */
     public static final int ENCODING_DTS_UHD_P2 = 30;
+    /** Audio data format: Direct Stream Digital */
+    public static final int ENCODING_DSD = 31;
 
     /** @hide */
     public static String toLogFriendlyEncoding(int enc) {
@@ -437,6 +439,8 @@
                 return "ENCODING_DTS_HD_MA";
             case ENCODING_DTS_UHD_P2:
                 return "ENCODING_DTS_UHD_P2";
+            case ENCODING_DSD:
+                return "ENCODING_DSD";
             default :
                 return "invalid encoding " + enc;
         }
@@ -798,6 +802,7 @@
             case ENCODING_DRA:
             case ENCODING_DTS_HD_MA:
             case ENCODING_DTS_UHD_P2:
+            case ENCODING_DSD:
                 return true;
             default:
                 return false;
@@ -837,6 +842,7 @@
             case ENCODING_DRA:
             case ENCODING_DTS_HD_MA:
             case ENCODING_DTS_UHD_P2:
+            case ENCODING_DSD:
                 return true;
             default:
                 return false;
@@ -1211,6 +1217,7 @@
                 case ENCODING_DRA:
                 case ENCODING_DTS_HD_MA:
                 case ENCODING_DTS_UHD_P2:
+                case ENCODING_DSD:
                     mEncoding = encoding;
                     break;
                 case ENCODING_INVALID:
@@ -1441,7 +1448,8 @@
         ENCODING_DTS_UHD_P1,
         ENCODING_DRA,
         ENCODING_DTS_HD_MA,
-        ENCODING_DTS_UHD_P2 }
+        ENCODING_DTS_UHD_P2,
+        ENCODING_DSD }
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface Encoding {}
diff --git a/media/java/android/media/AudioMixerAttributes.java b/media/java/android/media/AudioMixerAttributes.java
index 320d6bd..3856da6 100644
--- a/media/java/android/media/AudioMixerAttributes.java
+++ b/media/java/android/media/AudioMixerAttributes.java
@@ -37,10 +37,17 @@
      */
     public static final int MIXER_BEHAVIOR_DEFAULT = 0;
 
+    /**
+     * Constant indicating the audio mixer behavior is bit-perfect, which indicates there will
+     * not be mixing happen, the audio data will be sent as is down to the HAL.
+     */
+    public static final int MIXER_BEHAVIOR_BIT_PERFECT = 1;
+
     /** @hide */
     @IntDef(flag = false, prefix = "MIXER_BEHAVIOR_", value = {
-            MIXER_BEHAVIOR_DEFAULT }
-    )
+            MIXER_BEHAVIOR_DEFAULT,
+            MIXER_BEHAVIOR_BIT_PERFECT
+    })
     @Retention(RetentionPolicy.SOURCE)
     public @interface MixerBehavior {}
 
@@ -103,12 +110,14 @@
 
         /**
          * Sets the mixer behavior for the audio mixer
-         * @param mixerBehavior must be {@link #MIXER_BEHAVIOR_DEFAULT}.
+         * @param mixerBehavior must be {@link #MIXER_BEHAVIOR_DEFAULT} or
+         *                      {@link #MIXER_BEHAVIOR_BIT_PERFECT}.
          * @return the same Builder instance.
          */
         public @NonNull Builder setMixerBehavior(@MixerBehavior int mixerBehavior) {
             switch (mixerBehavior) {
                 case MIXER_BEHAVIOR_DEFAULT:
+                case MIXER_BEHAVIOR_BIT_PERFECT:
                     mMixerBehavior = mixerBehavior;
                     break;
                 default:
@@ -137,6 +146,8 @@
         switch (mixerBehavior) {
             case MIXER_BEHAVIOR_DEFAULT:
                 return "default";
+            case MIXER_BEHAVIOR_BIT_PERFECT:
+                return "bit-perfect";
             default:
                 return "unknown";
         }
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index 62f233e..84e6d3c 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -142,7 +142,12 @@
         @Retention(RetentionPolicy.SOURCE)
         @IntDef(
                 prefix = {"DISABLE_REASON_"},
-                value = {DISABLE_REASON_NONE, DISABLE_REASON_SUBSCRIPTION_REQUIRED})
+                value = {
+                    DISABLE_REASON_NONE,
+                    DISABLE_REASON_SUBSCRIPTION_REQUIRED,
+                    DISABLE_REASON_DOWNLOADED_CONTENT,
+                    DISABLE_REASON_AD
+                })
         public @interface DisableReason {}
 
         /** The corresponding route is available for routing. */
@@ -152,6 +157,13 @@
          * routing.
          */
         public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1;
+        /**
+         * The corresponding route is not available because downloaded content cannot be routed to
+         * it.
+         */
+        public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2;
+        /** The corresponding route is not available because an ad is in progress. */
+        public static final int DISABLE_REASON_AD = 3;
 
         @NonNull
         public static final Creator<Item> CREATOR =
@@ -216,6 +228,8 @@
          *
          * @see #DISABLE_REASON_NONE
          * @see #DISABLE_REASON_SUBSCRIPTION_REQUIRED
+         * @see #DISABLE_REASON_DOWNLOADED_CONTENT
+         * @see #DISABLE_REASON_AD
          */
         @DisableReason
         public int getDisableReason() {
diff --git a/media/java/android/media/projection/IMediaProjectionCallback.aidl b/media/java/android/media/projection/IMediaProjectionCallback.aidl
index f3743d1..2c8de2e 100644
--- a/media/java/android/media/projection/IMediaProjectionCallback.aidl
+++ b/media/java/android/media/projection/IMediaProjectionCallback.aidl
@@ -19,4 +19,5 @@
 /** {@hide} */
 oneway interface IMediaProjectionCallback {
     void onStop();
+    void onCapturedContentResize(int width, int height);
 }
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 1d58a40..a63d02b 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -27,12 +27,32 @@
 interface IMediaProjectionManager {
     @UnsupportedAppUsage
     boolean hasProjectionPermission(int uid, String packageName);
+
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
     IMediaProjection createProjection(int uid, String packageName, int type,
             boolean permanentGrant);
+
     boolean isValidMediaProjection(IMediaProjection projection);
+
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
     MediaProjectionInfo getActiveProjectionInfo();
+
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
     void stopActiveProjection();
+
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
+    void notifyActiveProjectionCapturedContentResized(int width, int height);
+
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
     void addCallback(IMediaProjectionWatcherCallback callback);
+
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
     void removeCallback(IMediaProjectionWatcherCallback callback);
 
     /**
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index ae44fc5..3dfff1f 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -234,7 +234,7 @@
     /**
      * Callbacks for the projection session.
      */
-    public static abstract class Callback {
+    public abstract static class Callback {
         /**
          * Called when the MediaProjection session is no longer valid.
          * <p>
@@ -243,6 +243,46 @@
          * </p>
          */
         public void onStop() { }
+
+        /**
+         * Indicates the width and height of the captured region in pixels. Called immediately after
+         * capture begins to provide the app with accurate sizing for the stream. Also called
+         * when the region captured in this MediaProjection session is resized.
+         * <p>
+         * The given width and height, in pixels, corresponds to the same width and height that
+         * would be returned from {@link android.view.WindowMetrics#getBounds()}
+         * </p>
+         * <p>
+         * Without the application resizing the {@link VirtualDisplay} (returned from
+         * {@code MediaProjection#createVirtualDisplay}) and output {@link Surface} (provided
+         * to {@code MediaProjection#createVirtualDisplay}), the captured stream will have
+         * letterboxing (black bars) around the recorded content to make up for the
+         * difference in aspect ratio.
+         * </p>
+         * <p>
+         * The application can prevent the letterboxing by overriding this method, and
+         * updating the size of both the {@link VirtualDisplay} and output {@link Surface}:
+         * </p>
+         *
+         * <pre>
+         * &#x40;Override
+         * public String onCapturedContentResize(int width, int height) {
+         *     // VirtualDisplay instance from MediaProjection#createVirtualDisplay
+         *     virtualDisplay.resize(width, height, dpi);
+         *
+         *     // Create a new Surface with the updated size (depending on the application's use
+         *     // case, this may be through different APIs - see Surface documentation for
+         *     // options).
+         *     int texName; // the OpenGL texture object name
+         *     SurfaceTexture surfaceTexture = new SurfaceTexture(texName);
+         *     surfaceTexture.setDefaultBufferSize(width, height);
+         *     Surface surface = new Surface(surfaceTexture);
+         *
+         *     // Ensure the VirtualDisplay has the updated Surface to send the capture to.
+         *     virtualDisplay.setSurface(surface);
+         * }</pre>
+         */
+        public void onCapturedContentResize(int width, int height) { }
     }
 
     private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
@@ -252,6 +292,13 @@
                 cbr.onStop();
             }
         }
+
+        @Override
+        public void onCapturedContentResize(int width, int height) {
+            for (CallbackRecord cbr : mCallbacks.values()) {
+                cbr.onCapturedContentResize(width, height);
+            }
+        }
     }
 
     private final static class CallbackRecord {
@@ -271,5 +318,9 @@
                 }
             });
         }
+
+        public void onCapturedContentResize(int width, int height) {
+            mHandler.post(() -> mCallback.onCapturedContentResize(width, height));
+        }
     }
 }
diff --git a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt b/media/java/android/media/projection/MediaProjectionConfig.aidl
similarity index 71%
copy from services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
copy to media/java/android/media/projection/MediaProjectionConfig.aidl
index 528680e7..f78385f 100644
--- a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
+++ b/media/java/android/media/projection/MediaProjectionConfig.aidl
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.server.permission.access.external
+package android.media.projection;
 
-class RoSystemProperties {
-    companion object {
-        const val CONTROL_PRIVAPP_PERMISSIONS_DISABLE = false
-        const val CONTROL_PRIVAPP_PERMISSIONS_ENFORCE = false
-    }
-}
+parcelable MediaProjectionConfig;
diff --git a/media/java/android/media/projection/MediaProjectionConfig.java b/media/java/android/media/projection/MediaProjectionConfig.java
new file mode 100644
index 0000000..29afaa6
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionConfig.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2022 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 android.media.projection;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Configure the {@link MediaProjection} session requested from
+ * {@link MediaProjectionManager#createScreenCaptureIntent(MediaProjectionConfig)}.
+ */
+@DataClass(
+        genEqualsHashCode = true,
+        genAidl = true,
+        genSetters = false,
+        genConstructor = false,
+        genBuilder = false,
+        genToString = false,
+        genHiddenConstDefs = true,
+        genHiddenGetters = true,
+        genConstDefs = false
+)
+public final class MediaProjectionConfig implements Parcelable {
+
+    /**
+     * The user, rather than the host app, determines which region of the display to capture.
+     * @hide
+     */
+    public static final int CAPTURE_REGION_USER_CHOICE = 0;
+
+    /**
+     * The host app specifies a particular display to capture.
+     * @hide
+     */
+    public static final int CAPTURE_REGION_FIXED_DISPLAY = 1;
+
+    /** @hide */
+    @IntDef(prefix = "CAPTURE_REGION_", value = {
+            CAPTURE_REGION_USER_CHOICE,
+            CAPTURE_REGION_FIXED_DISPLAY
+    })
+    @Retention(SOURCE)
+    public @interface CaptureRegion {
+    }
+
+    /**
+     * The particular display to capture. Only used when {@link #getRegionToCapture()} is
+     * {@link #CAPTURE_REGION_FIXED_DISPLAY}; ignored otherwise.
+     *
+     * Only supports values of {@link android.view.Display#DEFAULT_DISPLAY}.
+     */
+    @IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY)
+    private int mDisplayToCapture;
+
+    /**
+     * The region to capture. Defaults to the user's choice.
+     */
+    @CaptureRegion
+    private int mRegionToCapture = CAPTURE_REGION_USER_CHOICE;
+
+    /**
+     * Default instance, with region set to the user's choice.
+     */
+    private MediaProjectionConfig() {
+    }
+
+    /**
+     * Customized instance, with region set to the provided value.
+     */
+    private MediaProjectionConfig(@CaptureRegion int captureRegion) {
+        mRegionToCapture = captureRegion;
+    }
+
+    /**
+     * Returns an instance which restricts the user to capturing a particular display.
+     *
+     * @param displayId The id of the display to capture. Only supports values of
+     *                  {@link android.view.Display#DEFAULT_DISPLAY}.
+     * @throws IllegalArgumentException If the given {@code displayId} is outside the range of
+     * supported values.
+     */
+    @NonNull
+    public static MediaProjectionConfig createConfigForDisplay(
+            @IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY) int displayId) {
+        if (displayId != DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException(
+                    "A config for capturing the non-default display is not supported; requested "
+                            + "display id "
+                            + displayId);
+        }
+        MediaProjectionConfig config = new MediaProjectionConfig(CAPTURE_REGION_FIXED_DISPLAY);
+        config.mDisplayToCapture = displayId;
+        return config;
+    }
+
+    /**
+     * Returns an instance which allows the user to decide which region is captured. The consent
+     * dialog presents the user with all possible options. If the user selects display capture,
+     * then only the {@link android.view.Display#DEFAULT_DISPLAY} is supported.
+     *
+     * <p>
+     * When passed in to
+     * {@link MediaProjectionManager#createScreenCaptureIntent(MediaProjectionConfig)}, the consent
+     * dialog shown to the user will be the same as if just
+     * {@link MediaProjectionManager#createScreenCaptureIntent()} was invoked.
+     * </p>
+     */
+    @NonNull
+    public static MediaProjectionConfig createConfigForUserChoice() {
+        return new MediaProjectionConfig(CAPTURE_REGION_USER_CHOICE);
+    }
+
+    /**
+     * Returns string representation of the captured region.
+     */
+    @NonNull
+    private static String captureRegionToString(int value) {
+        switch (value) {
+            case CAPTURE_REGION_USER_CHOICE:
+                return "CAPTURE_REGION_USERS_CHOICE";
+            case CAPTURE_REGION_FIXED_DISPLAY:
+                return "CAPTURE_REGION_GIVEN_DISPLAY";
+            default:
+                return Integer.toHexString(value);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "MediaProjectionConfig { "
+                + "displayToCapture = " + mDisplayToCapture + ", "
+                + "regionToCapture = " + captureRegionToString(mRegionToCapture)
+                + " }";
+    }
+
+
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/media/java/android/media/projection/MediaProjectionConfig.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * The particular display to capture. Only used when {@link #getRegionToCapture()} is
+     * {@link #CAPTURE_REGION_FIXED_DISPLAY}; ignored otherwise.
+     *
+     * Only supports values of {@link android.view.Display#DEFAULT_DISPLAY}.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY) int getDisplayToCapture() {
+        return mDisplayToCapture;
+    }
+
+    /**
+     * The region to capture. Defaults to the user's choice.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @CaptureRegion int getRegionToCapture() {
+        return mRegionToCapture;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(MediaProjectionConfig other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        MediaProjectionConfig that = (MediaProjectionConfig) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mDisplayToCapture == that.mDisplayToCapture
+                && mRegionToCapture == that.mRegionToCapture;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mDisplayToCapture;
+        _hash = 31 * _hash + mRegionToCapture;
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mDisplayToCapture);
+        dest.writeInt(mRegionToCapture);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ MediaProjectionConfig(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int displayToCapture = in.readInt();
+        int regionToCapture = in.readInt();
+
+        this.mDisplayToCapture = displayToCapture;
+        AnnotationValidations.validate(
+                IntRange.class, null, mDisplayToCapture,
+                "from", DEFAULT_DISPLAY,
+                "to", DEFAULT_DISPLAY);
+        this.mRegionToCapture = regionToCapture;
+        AnnotationValidations.validate(
+                CaptureRegion.class, null, mRegionToCapture);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<MediaProjectionConfig> CREATOR
+            = new Parcelable.Creator<MediaProjectionConfig>() {
+        @Override
+        public MediaProjectionConfig[] newArray(int size) {
+            return new MediaProjectionConfig[size];
+        }
+
+        @Override
+        public MediaProjectionConfig createFromParcel(@NonNull android.os.Parcel in) {
+            return new MediaProjectionConfig(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1671030124845L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/media/java/android/media/projection/MediaProjectionConfig.java",
+            inputSignatures = "public static final  int CAPTURE_REGION_USER_CHOICE\npublic static final  int CAPTURE_REGION_FIXED_DISPLAY\nprivate @android.annotation.IntRange int mDisplayToCapture\nprivate @android.media.projection.MediaProjectionConfig.CaptureRegion int mRegionToCapture\npublic static @android.annotation.NonNull android.media.projection.MediaProjectionConfig createConfigForDisplay(int)\npublic static @android.annotation.NonNull android.media.projection.MediaProjectionConfig createConfigForUserChoice()\nprivate static @android.annotation.NonNull java.lang.String captureRegionToString(int)\npublic @java.lang.Override java.lang.String toString()\nclass MediaProjectionConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genSetters=false, genConstructor=false, genBuilder=false, genToString=false, genHiddenConstDefs=true, genHiddenGetters=true, genConstDefs=false)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index b3bd980..a4215e68 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -38,6 +38,13 @@
 @SystemService(Context.MEDIA_PROJECTION_SERVICE)
 public final class MediaProjectionManager {
     private static final String TAG = "MediaProjectionManager";
+
+    /**
+     * Intent extra to customize the permission dialog based on the host app's preferences.
+     * @hide
+     */
+    public static final String EXTRA_MEDIA_PROJECTION_CONFIG =
+            "android.media.projection.extra.EXTRA_MEDIA_PROJECTION_CONFIG";
     /** @hide */
     public static final String EXTRA_APP_TOKEN = "android.media.projection.extra.EXTRA_APP_TOKEN";
     /** @hide */
@@ -64,11 +71,13 @@
     }
 
     /**
-     * Returns an Intent that <b>must</b> be passed to startActivityForResult()
-     * in order to start screen capture. The activity will prompt
-     * the user whether to allow screen capture.  The result of this
-     * activity should be passed to getMediaProjection.
+     * Returns an {@link Intent} that <b>must</b> be passed to
+     * {@link Activity#startActivityForResult(Intent, int)} (or similar) in order to start screen
+     * capture. The activity will prompt the user whether to allow screen capture.  The result of
+     * this activity (received by overriding {@link Activity#onActivityResult(int, int, Intent)})
+     * should be passed to {@link #getMediaProjection(int, Intent)}.
      */
+    @NonNull
     public Intent createScreenCaptureIntent() {
         Intent i = new Intent();
         final ComponentName mediaProjectionPermissionDialogComponent =
@@ -80,6 +89,49 @@
     }
 
     /**
+     * Returns an {@link Intent} that <b>must</b> be passed to
+     * {@link Activity#startActivityForResult(Intent, int)} (or similar) in order to start screen
+     * capture. Customizes the activity and resulting {@link MediaProjection} session based up
+     * the provided {@code config}. The activity will prompt the user whether to allow screen
+     * capture. The result of this activity (received by overriding
+     * {@link Activity#onActivityResult(int, int, Intent)}) should be passed to
+     * {@link #getMediaProjection(int, Intent)}.
+     *
+     * <p>
+     * If {@link MediaProjectionConfig} was created from:
+     * <li>
+     *     <ul>
+     *         {@link MediaProjectionConfig#createConfigForDisplay(int)}, then creates an
+     *         {@link Intent} for capturing this particular display. The activity limits the user's
+     *         choice to just the display specified.
+     *     </ul>
+     *     <ul>
+     *         {@link MediaProjectionConfig#createConfigForUserChoice()}, then creates an
+     *         {@link Intent} for deferring which region to capture to the user. This gives the
+     *         user the same behaviour as calling {@link #createScreenCaptureIntent()}. The
+     *         activity gives the user the choice between
+     *         {@link android.view.Display#DEFAULT_DISPLAY}, or a different region.
+     *     </ul>
+     * </li>
+     *
+     * @param config Customization for the {@link MediaProjection} that this {@link Intent} requests
+     *               the user's consent for.
+     * @return An {@link Intent} requesting the user's consent, specialized based upon the given
+     * configuration.
+     */
+    @NonNull
+    public Intent createScreenCaptureIntent(@NonNull MediaProjectionConfig config) {
+        Intent i = new Intent();
+        final ComponentName mediaProjectionPermissionDialogComponent =
+                ComponentName.unflattenFromString(mContext.getResources()
+                        .getString(com.android.internal.R.string
+                                .config_mediaProjectionPermissionDialogComponent));
+        i.setComponent(mediaProjectionPermissionDialogComponent);
+        i.putExtra(EXTRA_MEDIA_PROJECTION_CONFIG, config);
+        return i;
+    }
+
+    /**
      * Retrieves the {@link MediaProjection} obtained from a successful screen
      * capture request. The result code and data from the request are provided
      * by overriding {@link Activity#onActivityResult(int, int, Intent)
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 90eed9e..00150d5 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -457,7 +457,9 @@
         /**
          * Receives started recording's ID.
          *
-         * @param recordingId The ID of the recording started
+         * @param recordingId The ID of the recording started. The TV app should provide and
+         *                    maintain this ID to identify the recording in the future.
+         * @see #onRecordingStopped(String)
          */
         public void onRecordingStarted(@NonNull String recordingId) {
         }
@@ -465,13 +467,13 @@
         /**
          * Receives stopped recording's ID.
          *
-         * @param recordingId The ID of the recording stopped
-         * @hide
+         * @param recordingId The ID of the recording stopped. This ID is created and maintained by
+         *                    the TV app when the recording was started.
+         * @see #onRecordingStarted(String)
          */
         public void onRecordingStopped(@NonNull String recordingId) {
         }
 
-
         /**
          * Receives signing result.
          * @param signingId the ID to identify the request. It's the same as the corresponding ID in
@@ -952,13 +954,14 @@
         }
 
         /**
-         * Requests starting of recording
+         * Requests the recording associated with the recordingId to stop.
          *
-         * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
+         * <p> This is used to request the associated {@link android.media.tv.TvRecordingClient} to
          * call {@link android.media.tv.TvRecordingClient#stopRecording()}.
-         * @see android.media.tv.TvRecordingClient#stopRecording()
          *
-         * @hide
+         * @param recordingId The ID of the recording to stop. This is provided by the TV app in
+         *                    {@link TvInteractiveAppView#notifyRecordingStarted(String)}
+         * @see android.media.tv.TvRecordingClient#stopRecording()
          */
         @CallSuper
         public void requestStopRecording(@NonNull String recordingId) {
@@ -976,8 +979,6 @@
             });
         }
 
-
-
         /**
          * Requests signing of the given data.
          *
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index fcd781b..1177688 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -585,6 +585,7 @@
      *
      * @param recordingId The ID of the recording started. This ID is created and maintained by the
      *                    TV app and is used to identify the recording in the future.
+     * @see TvInteractiveAppView#notifyRecordingStopped(String)
      */
     public void notifyRecordingStarted(@NonNull String recordingId) {
         if (DEBUG) {
@@ -601,7 +602,6 @@
      * @param recordingId The ID of the recording stopped. This ID is created and maintained
      *                    by the TV app when a recording is started.
      * @see TvInteractiveAppView#notifyRecordingStarted(String)
-     * @hide
      */
     public void notifyRecordingStopped(@NonNull String recordingId) {
         if (DEBUG) {
@@ -877,7 +877,8 @@
          * is called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
-         * @param programUri The program URI to record
+         * @param programUri The URI of the program to record
+         *
          */
         public void onRequestStartRecording(
                 @NonNull String iAppServiceId,
@@ -885,12 +886,14 @@
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.Session#requestStopRecording()}
+         * This is called when {@link TvInteractiveAppService.Session#requestStopRecording(String)}
          * is called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
-         * @param recordingId The ID of the recording to stop.
-         * @hide
+         * @param recordingId The ID of the recording to stop. This is provided by the TV app in
+         *                    {@link #notifyRecordingStarted(String)}
+         * @see #notifyRecordingStarted(String)
+         * @see #notifyRecordingStopped(String)
          */
         public void onRequestStopRecording(
                 @NonNull String iAppServiceId,
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index a2eae2c..2b7bcbe 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -85,7 +85,8 @@
     public void testCameraInfo() throws Exception {
         for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
 
-            CameraInfo info = mUtils.getCameraService().getCameraInfo(cameraId);
+            CameraInfo info = mUtils.getCameraService().getCameraInfo(cameraId,
+                    /*overrideToPortrait*/false);
             assertTrue("Facing was not set for camera " + cameraId, info.info.facing != -1);
             assertTrue("Orientation was not set for camera " + cameraId,
                     info.info.orientation != -1);
@@ -159,7 +160,8 @@
                     .connect(dummyCallbacks, cameraId, clientPackageName,
                             ICameraService.USE_CALLING_UID,
                             ICameraService.USE_CALLING_PID,
-                            getContext().getApplicationInfo().targetSdkVersion);
+                            getContext().getApplicationInfo().targetSdkVersion,
+                            /*overrideToPortrait*/false);
             assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
 
             Log.v(TAG, String.format("Camera %s connected", cameraId));
@@ -264,7 +266,8 @@
                         dummyCallbacks, String.valueOf(cameraId),
                         clientPackageName, clientAttributionTag,
                         ICameraService.USE_CALLING_UID, 0 /*oomScoreOffset*/,
-                        getContext().getApplicationInfo().targetSdkVersion);
+                        getContext().getApplicationInfo().targetSdkVersion,
+                        /*overrideToPortrait*/false);
             assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
 
             Log.v(TAG, String.format("Camera %s connected", cameraId));
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 0890346..9d09dcc 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -244,7 +244,8 @@
 
         mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
                 clientPackageName, clientAttributionTag, ICameraService.USE_CALLING_UID,
-                /*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion);
+                /*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion,
+                /*overrideToPortrait*/false);
         assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
@@ -417,7 +418,7 @@
     @SmallTest
     public void testCameraCharacteristics() throws RemoteException {
         CameraMetadataNative info = mUtils.getCameraService().getCameraCharacteristics(mCameraId,
-                getContext().getApplicationInfo().targetSdkVersion);
+                getContext().getApplicationInfo().targetSdkVersion, /*overrideToPortrait*/false);
 
         assertFalse(info.isEmpty());
         assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp
new file mode 100644
index 0000000..08d9501
--- /dev/null
+++ b/media/tests/projection/Android.bp
@@ -0,0 +1,46 @@
+//########################################################################
+// Build MediaProjectionTests package
+//########################################################################
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "MediaProjectionTests",
+
+    srcs: ["**/*.java"],
+
+    libs: [
+        "android.test.base",
+        "android.test.mock",
+        "android.test.runner",
+    ],
+
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "mockito-target-extended-minus-junit4",
+        "platform-test-annotations",
+        "testng",
+        "truth-prebuilt",
+    ],
+
+    // Needed for mockito-target-extended-minus-junit4
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+
+    test_suites: ["device-tests"],
+
+    platform_apis: true,
+
+    certificate: "platform",
+}
diff --git a/media/tests/projection/AndroidManifest.xml b/media/tests/projection/AndroidManifest.xml
new file mode 100644
index 0000000..62f148c
--- /dev/null
+++ b/media/tests/projection/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          android:installLocation="internalOnly"
+          package="android.media.projection.mediaprojectiontests"
+          android:sharedUserId="com.android.uid.test">
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+
+    <application android:debuggable="true"
+                 android:testOnly="true">
+        <uses-library android:name="android.test.mock" android:required="true"/>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.media.projection.mediaprojectiontests"
+                     android:label="MediaProjection package tests"/>
+</manifest>
diff --git a/media/tests/projection/AndroidTest.xml b/media/tests/projection/AndroidTest.xml
new file mode 100644
index 0000000..f64930a
--- /dev/null
+++ b/media/tests/projection/AndroidTest.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<configuration description="Runs MediaProjection package Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="MediaProjectionTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="MediaProjectionTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.media.projection.mediaprojectiontests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/media/tests/projection/TEST_MAPPING b/media/tests/projection/TEST_MAPPING
new file mode 100644
index 0000000..ddb68af
--- /dev/null
+++ b/media/tests/projection/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+    "presubmit": [
+        {
+            "name": "FrameworksServicesTests",
+            "options": [
+                {"include-filter": "android.media.projection.mediaprojectiontests"},
+                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+                {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+                {"exclude-annotation": "org.junit.Ignore"}
+            ]
+        }
+    ]
+}
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
new file mode 100644
index 0000000..a30f2e3
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 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 android.media.projection;
+
+import static android.media.projection.MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY;
+import static android.media.projection.MediaProjectionConfig.CAPTURE_REGION_USER_CHOICE;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link MediaProjectionConfig} class.
+ *
+ * Build/Install/Run:
+ * atest MediaProjectionTests:MediaProjectionConfigTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionConfigTest {
+    private static final MediaProjectionConfig DISPLAY_CONFIG =
+            MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY);
+    private static final MediaProjectionConfig USERS_CHOICE_CONFIG =
+            MediaProjectionConfig.createConfigForUserChoice();
+
+    @Test
+    public void testParcelable() {
+        Parcel parcel = Parcel.obtain();
+        DISPLAY_CONFIG.writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+        MediaProjectionConfig config = MediaProjectionConfig.CREATOR.createFromParcel(parcel);
+        assertThat(DISPLAY_CONFIG).isEqualTo(config);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testCreateDisplayConfig() {
+        assertThrows(IllegalArgumentException.class,
+                () -> MediaProjectionConfig.createConfigForDisplay(-1));
+        assertThrows(IllegalArgumentException.class,
+                () -> MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY + 1));
+        assertThat(DISPLAY_CONFIG.getRegionToCapture()).isEqualTo(CAPTURE_REGION_FIXED_DISPLAY);
+        assertThat(DISPLAY_CONFIG.getDisplayToCapture()).isEqualTo(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testCreateUsersChoiceConfig() {
+        assertThat(USERS_CHOICE_CONFIG.getRegionToCapture()).isEqualTo(CAPTURE_REGION_USER_CHOICE);
+    }
+
+    @Test
+    public void testEquals() {
+        assertThat(MediaProjectionConfig.createConfigForUserChoice()).isEqualTo(
+                USERS_CHOICE_CONFIG);
+        assertThat(DISPLAY_CONFIG).isNotEqualTo(USERS_CHOICE_CONFIG);
+        assertThat(MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY)).isEqualTo(
+                DISPLAY_CONFIG);
+    }
+}
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java
new file mode 100644
index 0000000..a3e4908
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 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 android.media.projection;
+
+import static android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION_CONFIG;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.LENIENT;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+
+/**
+ * Tests for the {@link MediaProjectionManager} class.
+ *
+ * Build/Install/Run:
+ * atest MediaProjectionTests:MediaProjectionManagerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionManagerTest {
+    private MediaProjectionManager mMediaProjectionManager;
+    private Context mContext;
+    private MockitoSession mMockingSession;
+    private static final MediaProjectionConfig DISPLAY_CONFIG =
+            MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY);
+    private static final MediaProjectionConfig USERS_CHOICE_CONFIG =
+            MediaProjectionConfig.createConfigForUserChoice();
+
+    @Before
+    public void setup() throws Exception {
+        mMockingSession =
+                mockitoSession()
+                        .initMocks(this)
+                        .strictness(LENIENT)
+                        .startMocking();
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        spyOn(mContext);
+        mMediaProjectionManager = new MediaProjectionManager(mContext);
+    }
+
+    @After
+    public void teardown() {
+        mMockingSession.finishMocking();
+    }
+
+    @Test
+    public void testCreateScreenCaptureIntent() {
+        final String dialogPackage = "test.package";
+        preparePermissionDialogComponent(dialogPackage);
+
+        final Intent intent = mMediaProjectionManager.createScreenCaptureIntent();
+        assertThat(intent).isNotNull();
+        assertThat(intent.getComponent().getPackageName()).contains(dialogPackage);
+    }
+
+    @Test
+    public void testCreateScreenCaptureIntent_display() {
+        final String dialogPackage = "test.package";
+        preparePermissionDialogComponent(dialogPackage);
+
+        final Intent intent = mMediaProjectionManager.createScreenCaptureIntent(DISPLAY_CONFIG);
+        assertThat(intent).isNotNull();
+        assertThat(intent.getComponent().getPackageName()).contains(dialogPackage);
+        assertThat(intent.getParcelableExtra(EXTRA_MEDIA_PROJECTION_CONFIG,
+                MediaProjectionConfig.class)).isEqualTo(DISPLAY_CONFIG);
+    }
+
+    @Test
+    public void testCreateScreenCaptureIntent_usersChoice() {
+        final String dialogPackage = "test.package";
+        preparePermissionDialogComponent(dialogPackage);
+
+        final Intent intent = mMediaProjectionManager.createScreenCaptureIntent(
+                USERS_CHOICE_CONFIG);
+        assertThat(intent).isNotNull();
+        assertThat(intent.getComponent().getPackageName()).contains(dialogPackage);
+        assertThat(intent.getParcelableExtra(EXTRA_MEDIA_PROJECTION_CONFIG,
+                MediaProjectionConfig.class)).isEqualTo(USERS_CHOICE_CONFIG);
+    }
+
+    private void preparePermissionDialogComponent(@NonNull String dialogPackage) {
+        final Resources mockResources = mock(Resources.class);
+        when(mContext.getResources()).thenReturn(mockResources);
+        doReturn(dialogPackage + "/.TestActivity").when(mockResources).getString(
+                com.android.internal.R.string
+                        .config_mediaProjectionPermissionDialogComponent);
+    }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index b322b8b..367ae06 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -175,7 +175,9 @@
                 && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED)
                 && isPendingIntentValid(intent,
                         SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION)
-                && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS);
+                && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS)
+                && isPendingIntentValid(intent,
+                        SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
     }
 
     private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) {
@@ -208,6 +210,8 @@
             case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION:
                 return "not default data subscription";
             case SlicePurchaseController.EXTRA_INTENT_SUCCESS: return "success";
+            case SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN:
+                return "notification shown";
             default: {
                 loge("Unknown pending intent extra: " + extra);
                 return "unknown(" + extra + ")";
@@ -220,7 +224,7 @@
         logd("onReceive intent: " + intent.getAction());
         switch (intent.getAction()) {
             case SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP:
-                onDisplayBoosterNotification(context, intent);
+                onDisplayNetworkBoostNotification(context, intent);
                 break;
             case SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT:
                 onTimeout(context, intent);
@@ -233,7 +237,8 @@
         }
     }
 
-    private void onDisplayBoosterNotification(@NonNull Context context, @NonNull Intent intent) {
+    private void onDisplayNetworkBoostNotification(@NonNull Context context,
+            @NonNull Intent intent) {
         if (!isIntentValid(intent)) {
             sendSlicePurchaseAppResponse(intent,
                     SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
@@ -280,10 +285,12 @@
 
         int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
                 SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
-        logd("Display the booster notification for capability "
+        logd("Display the network boost notification for capability "
                 + TelephonyManager.convertPremiumCapabilityToString(capability));
         context.getSystemService(NotificationManager.class).notifyAsUser(
                 NETWORK_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
+        sendSlicePurchaseAppResponse(intent,
+                SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
     }
 
     /**
@@ -338,7 +345,7 @@
                 + " timed out.");
         if (sSlicePurchaseActivities.get(capability) == null) {
             // Notification is still active
-            logd("Closing booster notification since the user did not respond in time.");
+            logd("Closing network boost notification since the user did not respond in time.");
             context.getSystemService(NotificationManager.class).cancelAsUser(
                     NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
         } else {
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
index 5765e5b..ab99a76 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -64,6 +64,7 @@
     @Mock PendingIntent mCanceledIntent;
     @Mock PendingIntent mContentIntent1;
     @Mock PendingIntent mContentIntent2;
+    @Mock PendingIntent mNotificationShownIntent;
     @Mock Context mContext;
     @Mock Resources mResources;
     @Mock NotificationManager mNotificationManager;
@@ -136,7 +137,7 @@
     }
 
     @Test
-    public void testDisplayBoosterNotification() {
+    public void testDisplayNetworkBoostNotification() throws Exception {
         // set up intent
         doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
         doReturn(PHONE_ID).when(mIntent).getIntExtra(
@@ -148,11 +149,17 @@
         doReturn(TAG).when(mIntent).getStringExtra(
                 eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
 
-        // set up pending intent
+        // set up pending intents
         doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
         doReturn(true).when(mPendingIntent).isBroadcast();
         doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
                 anyString(), eq(PendingIntent.class));
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mNotificationShownIntent)
+                .getCreatorPackage();
+        doReturn(true).when(mNotificationShownIntent).isBroadcast();
+        doReturn(mNotificationShownIntent).when(mIntent).getParcelableExtra(
+                eq(SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN),
+                eq(PendingIntent.class));
 
         // set up notification
         doReturn(mResources).when(mContext).getResources();
@@ -185,8 +192,10 @@
         assertEquals(2, notification.actions.length);
         assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
         assertEquals(mContentIntent2, notification.actions[1].actionIntent);
-    }
 
+        // verify SlicePurchaseController was notified
+        verify(mNotificationShownIntent).send();
+    }
 
     @Test
     public void testNotificationCanceled() {
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_microphone.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_microphone.xml
new file mode 100644
index 0000000..161e4e6e
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_microphone.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@android:color/system_accent1_200">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,14Q10.75,14 9.875,13.125Q9,12.25 9,11V5Q9,3.75 9.875,2.875Q10.75,2 12,2Q13.25,2 14.125,2.875Q15,3.75 15,5V11Q15,12.25 14.125,13.125Q13.25,14 12,14ZM12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8ZM11,21V17.925Q8.4,17.575 6.7,15.6Q5,13.625 5,11H7Q7,13.075 8.463,14.537Q9.925,16 12,16Q14.075,16 15.538,14.537Q17,13.075 17,11H19Q19,13.625 17.3,15.6Q15.6,17.575 13,17.925V21ZM12,12Q12.425,12 12.713,11.712Q13,11.425 13,11V5Q13,4.575 12.713,4.287Q12.425,4 12,4Q11.575,4 11.288,4.287Q11,4.575 11,5V11Q11,11.425 11.288,11.712Q11.575,12 12,12Z"/>
+</vector>
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml b/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml
new file mode 100644
index 0000000..5f8d566
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+  android:height="24dp"
+  android:width="24dp"
+  android:viewportHeight="160"
+  android:viewportWidth="160" >
+  <path android:fillAlpha="0" android:fillColor="#000000"
+        android:pathData="M69.48,83.33A26.97,24.46 0,0 1,42.92 107.8,26.97 24.46,0 0,1 15.56,84.07 26.97,24.46 0,0 1,41.29 58.9,26.97 24.46,0 0,1 69.43,81.86"
+        android:strokeColor="#000000" android:strokeWidth="2.265"/>
+    <path android:fillAlpha="0" android:fillColor="#000000"
+        android:pathData="m143.73,83.58a26.97,24.46 0,0 1,-26.56 24.46,26.97 24.46,0 0,1 -27.36,-23.72 26.97,24.46 0,0 1,25.73 -25.18,26.97 24.46,0 0,1 28.14,22.96"
+        android:strokeColor="#000000" android:strokeWidth="2.265"/>
+    <path android:fillAlpha="0" android:fillColor="#000000"
+        android:pathData="m69.42,82.98c20.37,-0.25 20.37,-0.25 20.37,-0.25"
+        android:strokeColor="#000000" android:strokeWidth="2.265"/>
+    <path android:fillAlpha="0" android:fillColor="#000000"
+        android:pathData="M15.37,83.78 L1.9,56.83"
+        android:strokeColor="#000000" android:strokeWidth="2.265"/>
+    <path android:fillAlpha="0" android:fillColor="#000000"
+        android:pathData="M143.67,82.75C154.48,57.9 154.48,58.04 154.48,58.04"
+        android:strokeColor="#000000" android:strokeWidth="2.265"/>
+</vector>
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_microphone.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_microphone.xml
new file mode 100644
index 0000000..eca625d
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_microphone.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@android:color/system_accent1_600">
+  <path android:fillColor="@android:color/white" android:pathData="M12,14Q10.75,14 9.875,13.125Q9,12.25 9,11V5Q9,3.75 9.875,2.875Q10.75,2 12,2Q13.25,2 14.125,2.875Q15,3.75 15,5V11Q15,12.25 14.125,13.125Q13.25,14 12,14ZM12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8ZM11,21V17.925Q8.4,17.575 6.7,15.6Q5,13.625 5,11H7Q7,13.075 8.463,14.537Q9.925,16 12,16Q14.075,16 15.538,14.537Q17,13.075 17,11H19Q19,13.625 17.3,15.6Q15.6,17.575 13,17.925V21ZM12,12Q12.425,12 12.713,11.712Q13,11.425 13,11V5Q13,4.575 12.713,4.287Q12.425,4 12,4Q11.575,4 11.288,4.287Q11,4.575 11,5V11Q11,11.425 11.288,11.712Q11.575,12 12,12Z"/>
+</vector>
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_device_streaming.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_device_streaming.xml
new file mode 100644
index 0000000..7295e78
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_device_streaming.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_600">
+    <path
+        android:pathData="M6.2529,18.5H16.2529V17.5H18.2529V21.5C18.2529,22.6 17.3529,23.5 16.2529,23.5H6.2529C5.1529,23.5 4.2529,22.6 4.2529,21.5V3.5C4.2529,2.4 5.1529,1.51 6.2529,1.51L16.2529,1.5C17.3529,1.5 18.2529,2.4 18.2529,3.5V7.5H16.2529V6.5H6.2529V18.5ZM16.2529,3.5H6.2529V4.5H16.2529V3.5ZM6.2529,21.5V20.5H16.2529V21.5H6.2529ZM12.6553,9.4049C12.6553,8.8526 13.103,8.4049 13.6553,8.4049H20.5254C21.0776,8.4049 21.5254,8.8526 21.5254,9.4049V14.6055C21.5254,15.1578 21.0776,15.6055 20.5254,15.6055H14.355L12.6553,17.0871V9.4049Z"
+        android:fillColor="#3C4043"
+        android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 97201e2..3b21541 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -36,6 +36,18 @@
     <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
     <string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
 
+    <!-- TODO(b/256140614) To replace all glasses related strings with final versions -->
+    <!-- ================= DEVICE_PROFILE_GLASSES ================= -->
+
+    <!-- The name of the "glasses" device type [CHAR LIMIT=30] -->
+    <string name="profile_name_glasses">glasses</string>
+
+    <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile (type) [CHAR LIMIT=NONE] -->
+    <string name="summary_glasses">This app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
+
+    <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
+    <string name="summary_glasses_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
+
     <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
 
     <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
@@ -69,6 +81,18 @@
     <!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] -->
     <string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string>
 
+    <!-- TODO(b/256140614) To replace all nearby_device_streaming related strings with final versions -->
+    <!-- ================= DEVICE_PROFILE_NEARBY_DEVICE_STREAMING ================= -->
+
+    <!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
+    <string name="title_nearby_device_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="NearbyStreamer">%1$s</xliff:g>&lt;/strong&gt; to perform this action from your phone</string>
+
+    <!-- Title of the helper dialog for NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=30]. -->
+    <string name="helper_title_nearby_device_streaming">Cross-device services</string>
+
+    <!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
+    <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="NearbyDevice">%2$s</xliff:g> to stream content to nearby devices</string>
+
     <!-- ================= null profile ================= -->
 
     <!-- A noun for a companion device with unspecified profile (type) [CHAR LIMIT=30] -->
@@ -116,7 +140,10 @@
     <!-- Calendar permission will be granted of corresponding profile [CHAR LIMIT=30] -->
     <string name="permission_calendar">Calendar</string>
 
-    <!-- Calendar permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <!-- Microphone permission will be granted to corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_microphone">Microphone</string>
+
+    <!-- Nearby devices permission will be granted of corresponding profile [CHAR LIMIT=30] -->
     <string name="permission_nearby_devices">Nearby devices</string>
 
     <!-- Storage permission will be granted of corresponding profile [CHAR LIMIT=30] -->
@@ -128,6 +155,9 @@
     <!-- Apps permission will be granted of corresponding profile [CHAR LIMIT=30] -->
     <string name="permission_app_streaming">Apps</string>
 
+    <!-- Nearby_device_streaming permission will be granted to the corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_nearby_device_streaming">Nearby Device Streaming</string>
+
     <!-- Description of phone permission of corresponding profile [CHAR LIMIT=NONE] -->
     <string name="permission_phone_summary">Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect, and editing call logs</string>
 
@@ -142,6 +172,10 @@
     <!-- TODO(b/253644212) Need the description for calendar permission  -->
     <string name="permission_calendar_summary"></string>
 
+    <!-- Description of microphone permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <!-- TODO(b/256140614) Need the description for microphone permission  -->
+    <string name="permission_microphone_summary">Can record audio using the microphone</string>
+
     <!-- Description of nearby devices' permission of corresponding profile [CHAR LIMIT=NONE] -->
     <!-- TODO(b/253644212) Need the description for nearby devices' permission  -->
     <string name="permission_nearby_devices_summary"></string>
@@ -155,4 +189,8 @@
     <!-- Description of storage permission of corresponding profile [CHAR LIMIT=NONE] -->
     <string name="permission_storage_summary"></string>
 
+    <!-- Description of nearby_device_streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <!-- TODO(b/256140614) Need the description for nearby devices' permission  -->
+    <string name="permission_nearby_device_streaming_summary">Stream content to a nearby device</string>
+
 </resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 3a3a5d2..49a63345 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -19,6 +19,8 @@
 import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_GLASSES;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
 import static android.companion.CompanionDeviceManager.REASON_CANCELED;
 import static android.companion.CompanionDeviceManager.REASON_DISCOVERY_TIMEOUT;
@@ -34,7 +36,9 @@
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_APP_STREAMING;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALENDAR;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CONTACTS;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_MICROPHONE;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICES;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICE_STREAMING;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NOTIFICATION;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_PHONE;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_SMS;
@@ -501,6 +505,12 @@
                 mPermissionTypes.addAll(Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_STORAGE));
                 break;
 
+            case DEVICE_PROFILE_NEARBY_DEVICE_STREAMING:
+                title = getHtmlFromResources(this, R.string.title_nearby_device_streaming,
+                        deviceName);
+                mPermissionTypes.add(PERMISSION_NEARBY_DEVICE_STREAMING);
+                break;
+
             default:
                 throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
@@ -556,7 +566,7 @@
         }
 
         final String deviceName = mSelectedDevice.getDisplayName();
-        final String profileName = getString(R.string.profile_name_watch);
+        final String profileName;
         final Spanned title;
         final Spanned summary;
         final Drawable profileIcon;
@@ -569,6 +579,7 @@
             mSummary.setVisibility(View.GONE);
             mConstraintList.setVisibility(View.GONE);
         } else if (deviceProfile.equals(DEVICE_PROFILE_WATCH)) {
+            profileName = getString(R.string.profile_name_watch);
             title = getHtmlFromResources(this, R.string.confirmation_title, appLabel, deviceName);
             summary = getHtmlFromResources(
                     this, R.string.summary_watch_single_device, profileName, appLabel);
@@ -579,6 +590,18 @@
                     PERMISSION_CALENDAR, PERMISSION_NEARBY_DEVICES));
 
             setupPermissionList();
+        } else if (deviceProfile.equals(DEVICE_PROFILE_GLASSES)) {
+            profileName = getString(R.string.profile_name_glasses);
+            title = getHtmlFromResources(this, R.string.confirmation_title, appLabel, profileName);
+            summary = getHtmlFromResources(
+                    this, R.string.summary_glasses_single_device, profileName, appLabel);
+            profileIcon = getIcon(this, R.drawable.ic_glasses);
+
+            mPermissionTypes.addAll(Arrays.asList(
+                    PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS,
+                    PERMISSION_MICROPHONE, PERMISSION_NEARBY_DEVICES));
+
+            setupPermissionList();
         } else {
             throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
@@ -607,6 +630,10 @@
             profileName = getString(R.string.profile_name_watch);
             summary = getHtmlFromResources(this, R.string.summary_watch, profileName, appLabel);
             profileIcon = getIcon(this, R.drawable.ic_watch);
+        } else if (deviceProfile.equals(DEVICE_PROFILE_GLASSES)) {
+            profileName = getString(R.string.profile_name_glasses);
+            summary = getHtmlFromResources(this, R.string.summary_glasses, profileName, appLabel);
+            profileIcon = getIcon(this, R.drawable.ic_glasses);
         } else {
             throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index 804e7577..eae14a6 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -18,6 +18,7 @@
 
 import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
 
 import static com.android.companiondevicemanager.Utils.getApplicationIcon;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
@@ -134,6 +135,14 @@
                         getContext(), R.string.helper_summary_computer, title, displayName);
                 break;
 
+            case DEVICE_PROFILE_NEARBY_DEVICE_STREAMING:
+                title = getHtmlFromResources(getContext(),
+                        R.string.helper_title_nearby_device_streaming);
+                summary = getHtmlFromResources(
+                        getContext(), R.string.helper_summary_nearby_device_streaming, title,
+                        displayName);
+                break;
+
             default:
                 throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index 0ee94a2..00c44d6 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -50,6 +50,8 @@
     static final int PERMISSION_CONTACTS = 5;
     static final int PERMISSION_CALENDAR = 6;
     static final int PERMISSION_NEARBY_DEVICES = 7;
+    static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8;
+    static final int PERMISSION_MICROPHONE = 9;
 
     private static final Map<Integer, Integer> sTitleMap;
     static {
@@ -62,6 +64,8 @@
         map.put(PERMISSION_CONTACTS, R.string.permission_contacts);
         map.put(PERMISSION_CALENDAR, R.string.permission_calendar);
         map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices);
+        map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming);
+        map.put(PERMISSION_MICROPHONE, R.string.permission_microphone);
         sTitleMap = unmodifiableMap(map);
     }
 
@@ -76,6 +80,9 @@
         map.put(PERMISSION_CONTACTS, R.string.permission_contacts_summary);
         map.put(PERMISSION_CALENDAR, R.string.permission_calendar_summary);
         map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary);
+        map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
+                R.string.permission_nearby_device_streaming_summary);
+        map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary);
         sSummaryMap = unmodifiableMap(map);
     }
 
@@ -90,6 +97,9 @@
         map.put(PERMISSION_CONTACTS, R.drawable.ic_permission_contacts);
         map.put(PERMISSION_CALENDAR, R.drawable.ic_permission_calendar);
         map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices);
+        map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
+                R.drawable.ic_permission_nearby_device_streaming);
+        map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone);
         sIconMap = unmodifiableMap(map);
     }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 4faf00c..d2b2924 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -41,8 +41,6 @@
 import android.os.ResultReceiver
 import android.service.credentials.CredentialProviderService
 import com.android.credentialmanager.createflow.CreateCredentialUiState
-import com.android.credentialmanager.createflow.EnabledProviderInfo
-import com.android.credentialmanager.createflow.RemoteInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
 import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
@@ -63,7 +61,7 @@
     requestInfo = intent.extras?.getParcelable(
       RequestInfo.EXTRA_REQUEST_INFO,
       RequestInfo::class.java
-    ) ?: testGetRequestInfo()
+    ) ?: testCreatePasskeyRequestInfo()
 
     providerEnabledList = when (requestInfo.type) {
       RequestInfo.TYPE_CREATE ->
@@ -101,7 +99,7 @@
   }
 
   fun onOptionSelected(
-    providerPackageName: String,
+    providerId: String,
     entryKey: String,
     entrySubkey: String,
     resultCode: Int? = null,
@@ -109,7 +107,7 @@
   ) {
     val userSelectionDialogResult = UserSelectionDialogResult(
       requestInfo.token,
-      providerPackageName,
+      providerId,
       entryKey,
       entrySubkey,
       if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
@@ -138,36 +136,15 @@
     val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
       // Handle runtime cast error
       providerDisabledList, context)
-    var defaultProvider: EnabledProviderInfo? = null
-    var remoteEntry: RemoteInfo? = null
-    var createOptionSize = 0
-    var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
     providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
       providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
-      if (providerInfo.isDefault) {defaultProvider = providerInfo}
-      if (providerInfo.remoteEntry != null) {
-        remoteEntry = providerInfo.remoteEntry!!
-      }
-      if (providerInfo.createOptions.isNotEmpty()) {
-        createOptionSize += providerInfo.createOptions.size
-        lastSeenProviderWithNonEmptyCreateOptions = providerInfo
-      }
     }
-    return CreateCredentialUiState(
-      enabledProviders = providerEnabledList,
-      disabledProviders = providerDisabledList,
-      CreateFlowUtils.toCreateScreenState(
-        createOptionSize, false,
-        requestDisplayInfo, defaultProvider, remoteEntry),
-      requestDisplayInfo,
-      false,
-      CreateFlowUtils.toActiveEntry(
-        /*defaultProvider=*/defaultProvider, createOptionSize,
-        lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
-    )
+    return CreateFlowUtils.toCreateCredentialUiState(
+      providerEnabledList, providerDisabledList, requestDisplayInfo, false)
   }
 
   companion object {
+    // TODO: find a way to resolve this static field leak problem
     lateinit var repo: CredentialManagerRepo
 
     fun setup(
@@ -198,7 +175,6 @@
         .setRemoteEntry(
           newRemoteEntry("key2", "subkey-1")
         )
-        .setIsDefaultProvider(true)
         .build(),
       CreateCredentialProviderData
         .Builder("com.dashlane")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 6a4c599..cdff2d4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -43,6 +43,7 @@
   override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
     CredentialManagerRepo.setup(this, intent)
+    UserConfigRepo.setup(this)
     val requestInfo = CredentialManagerRepo.getInstance().requestInfo
     setContent {
       CredentialSelectorTheme {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 56fbf66..db676b2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -31,6 +31,8 @@
 import com.android.credentialmanager.createflow.EnabledProviderInfo
 import com.android.credentialmanager.createflow.CreateScreenState
 import com.android.credentialmanager.createflow.ActiveEntry
+import com.android.credentialmanager.createflow.DisabledProviderInfo
+import com.android.credentialmanager.createflow.CreateCredentialUiState
 import com.android.credentialmanager.getflow.ActionEntryInfo
 import com.android.credentialmanager.getflow.AuthenticationEntryInfo
 import com.android.credentialmanager.getflow.CredentialEntryInfo
@@ -208,14 +210,13 @@
         val pkgInfo = packageManager
           .getPackageInfo(packageName!!,
             PackageManager.PackageInfoFlags.of(0))
-        com.android.credentialmanager.createflow.EnabledProviderInfo(
+        EnabledProviderInfo(
           // TODO: decide what to do when failed to load a provider icon
           icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
           name = it.providerFlattenedComponentName,
           displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
           createOptions = toCreationOptionInfoList(
             it.providerFlattenedComponentName, it.saveEntries, requestDisplayInfo, context),
-          isDefault = it.isDefaultProvider,
           remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
         )
       }
@@ -256,8 +257,7 @@
             createCredentialRequestJetpack.password,
             createCredentialRequestJetpack.type,
             requestInfo.appPackageName,
-            context.getDrawable(R.drawable.ic_password)!!,
-            requestInfo.isFirstUsage
+            context.getDrawable(R.drawable.ic_password)!!
           )
         }
         is CreatePublicKeyCredentialRequest -> {
@@ -275,8 +275,7 @@
             displayName,
             createCredentialRequestJetpack.type,
             requestInfo.appPackageName,
-            context.getDrawable(R.drawable.ic_passkey)!!,
-            requestInfo.isFirstUsage)
+            context.getDrawable(R.drawable.ic_passkey)!!)
         }
         // TODO: correctly parsing for other sign-ins
         else -> {
@@ -285,20 +284,60 @@
             "Elisa Beckett",
             "other-sign-ins",
             requestInfo.appPackageName,
-            context.getDrawable(R.drawable.ic_other_sign_in)!!,
-            requestInfo.isFirstUsage)
+            context.getDrawable(R.drawable.ic_other_sign_in)!!)
         }
       }
     }
 
-    fun toCreateScreenState(
+    fun toCreateCredentialUiState(
+      enabledProviders: List<EnabledProviderInfo>,
+      disabledProviders: List<DisabledProviderInfo>?,
+      requestDisplayInfo: RequestDisplayInfo,
+      isOnPasskeyIntroStateAlready: Boolean,
+    ): CreateCredentialUiState {
+      var createOptionSize = 0
+      var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
+      var remoteEntry: RemoteInfo? = null
+      var defaultProvider: EnabledProviderInfo? = null
+      val defaultProviderId = UserConfigRepo.getInstance().getDefaultProviderId()
+      enabledProviders.forEach {
+          enabledProvider ->
+        if (defaultProviderId != null) {
+          if (enabledProvider.name == defaultProviderId) {
+            defaultProvider = enabledProvider
+          }
+        }
+        if (enabledProvider.createOptions.isNotEmpty()) {
+          createOptionSize += enabledProvider.createOptions.size
+          lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
+        }
+        if (enabledProvider.remoteEntry != null) {
+          remoteEntry = enabledProvider.remoteEntry!!
+        }
+      }
+      return CreateCredentialUiState(
+        enabledProviders = enabledProviders,
+        disabledProviders = disabledProviders,
+        toCreateScreenState(
+          createOptionSize, isOnPasskeyIntroStateAlready,
+          requestDisplayInfo, defaultProvider, remoteEntry),
+        requestDisplayInfo,
+        isOnPasskeyIntroStateAlready,
+        toActiveEntry(
+          /*defaultProvider=*/defaultProvider, createOptionSize,
+          lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
+      )
+    }
+
+    private fun toCreateScreenState(
       createOptionSize: Int,
       isOnPasskeyIntroStateAlready: Boolean,
       requestDisplayInfo: RequestDisplayInfo,
       defaultProvider: EnabledProviderInfo?,
       remoteEntry: RemoteInfo?,
     ): CreateScreenState {
-      return if (requestDisplayInfo.isFirstUsage && requestDisplayInfo
+      return if (
+        UserConfigRepo.getInstance().getIsFirstUse() && requestDisplayInfo
           .type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
         CreateScreenState.PASSKEY_INTRO
       } else if (
@@ -313,12 +352,12 @@
       } else if (createOptionSize == 0 && remoteEntry != null) {
         CreateScreenState.EXTERNAL_ONLY_SELECTION
       } else {
-          // TODO: properly handle error and gracefully finish itself
-          throw java.lang.IllegalStateException("Empty provider list.")
+        // TODO: properly handle error and gracefully finish itself
+        throw java.lang.IllegalStateException("Empty provider list.")
       }
     }
 
-   fun toActiveEntry(
+    private fun toActiveEntry(
       defaultProvider: EnabledProviderInfo?,
       createOptionSize: Int,
       lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
new file mode 100644
index 0000000..5e77663
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager
+
+import android.content.Context
+import android.content.SharedPreferences
+
+class UserConfigRepo(context: Context) {
+    val sharedPreferences: SharedPreferences = context.getSharedPreferences(
+        context.packageName, Context.MODE_PRIVATE)
+
+    fun setDefaultProvider(
+        providerId: String
+    ) {
+        sharedPreferences.edit().apply {
+            putString(DEFAULT_PROVIDER, providerId)
+            apply()
+        }
+    }
+
+    fun setIsFirstUse(
+        isFirstUse: Boolean
+    ) {
+        sharedPreferences.edit().apply {
+            putBoolean(IS_PASSKEY_FIRST_USE, isFirstUse)
+            apply()
+        }
+    }
+
+    fun getDefaultProviderId(): String? {
+        return sharedPreferences.getString(DEFAULT_PROVIDER, null)
+    }
+
+    fun getIsFirstUse(): Boolean {
+        return sharedPreferences.getBoolean(IS_PASSKEY_FIRST_USE, true)
+    }
+
+    companion object {
+        lateinit var repo: UserConfigRepo
+
+        const val DEFAULT_PROVIDER = "default_provider"
+        // This first use value only applies to passkeys, not related with if generally
+        // credential manager is first use or not
+        const val IS_PASSKEY_FIRST_USE = "is_passkey_first_use"
+
+        fun setup(
+            context: Context,
+        ) {
+            repo = UserConfigRepo(context)
+        }
+
+        fun getInstance(): UserConfigRepo {
+            return repo
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index c26fac8..38e2caa 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -108,7 +108,8 @@
                     )
                     CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
                         providerInfo = uiState.activeEntry?.activeProvider!!,
-                        onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
+                        onChangeDefaultSelected = viewModel::onChangeDefaultSelected,
+                        onUseOnceSelected = viewModel::onUseOnceSelected,
                     )
                     CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
                         requestDisplayInfo = uiState.requestDisplayInfo,
@@ -464,7 +465,8 @@
 @Composable
 fun MoreOptionsRowIntroCard(
     providerInfo: EnabledProviderInfo,
-    onDefaultOrNotSelected: () -> Unit,
+    onChangeDefaultSelected: () -> Unit,
+    onUseOnceSelected: () -> Unit,
 ) {
     ContainerCard() {
         Column() {
@@ -496,11 +498,11 @@
             ) {
                 CancelButton(
                     stringResource(R.string.use_once),
-                    onClick = onDefaultOrNotSelected
+                    onClick = onUseOnceSelected
                 )
                 ConfirmButton(
                     stringResource(R.string.set_as_default),
-                    onClick = onDefaultOrNotSelected
+                    onClick = onChangeDefaultSelected
                 )
             }
             Divider(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 518aaee..9d029dff 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -29,6 +29,7 @@
 import androidx.lifecycle.ViewModel
 import com.android.credentialmanager.CreateFlowUtils
 import com.android.credentialmanager.CredentialManagerRepo
+import com.android.credentialmanager.UserConfigRepo
 import com.android.credentialmanager.common.DialogResult
 import com.android.credentialmanager.common.ProviderActivityResult
 import com.android.credentialmanager.common.ResultState
@@ -61,32 +62,15 @@
   }
 
   fun onConfirmIntro() {
-    var createOptionSize = 0
-    var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
-    var remoteEntry: RemoteInfo? = null
-    uiState.enabledProviders.forEach {
-      enabledProvider ->
-      if (enabledProvider.createOptions.isNotEmpty()) {
-        createOptionSize += enabledProvider.createOptions.size
-        lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
-      }
-      if (enabledProvider.remoteEntry != null) {
-        remoteEntry = enabledProvider.remoteEntry!!
-      }
-    }
-    uiState = uiState.copy(
-      currentScreenState = CreateFlowUtils.toCreateScreenState(
-        createOptionSize, true,
-        uiState.requestDisplayInfo, null, remoteEntry),
-      showActiveEntryOnly = createOptionSize > 1,
-      activeEntry = CreateFlowUtils.toActiveEntry(
-        null, createOptionSize, lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
-    )
+    uiState = CreateFlowUtils.toCreateCredentialUiState(
+      uiState.enabledProviders, uiState.disabledProviders,
+      uiState.requestDisplayInfo, true)
+    UserConfigRepo.getInstance().setIsFirstUse(false)
   }
 
   fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
     return uiState.enabledProviders.single {
-      it.name.equals(providerName)
+      it.name == providerName
     }
   }
 
@@ -116,6 +100,8 @@
       showActiveEntryOnly = true,
       activeEntry = activeEntry
     )
+    val providerId = uiState.activeEntry?.activeProvider?.name
+    onDefaultChanged(providerId)
   }
 
   fun onDisabledPasswordManagerSelected() {
@@ -127,11 +113,29 @@
     dialogResult.value = DialogResult(ResultState.CANCELED)
   }
 
-  fun onDefaultOrNotSelected() {
+  fun onChangeDefaultSelected() {
     uiState = uiState.copy(
       currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
     )
-    // TODO: implement the if choose as default or not logic later
+    val providerId = uiState.activeEntry?.activeProvider?.name
+    onDefaultChanged(providerId)
+  }
+
+  fun onUseOnceSelected() {
+    uiState = uiState.copy(
+      currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+    )
+  }
+
+  fun onDefaultChanged(providerId: String?) {
+    if (providerId != null) {
+      Log.d(
+        "Account Selector", "Default provider changed to: " +
+                " {provider=$providerId")
+      UserConfigRepo.getInstance().setDefaultProvider(providerId)
+    } else {
+      Log.w("Account Selector", "Null provider is being changed")
+    }
   }
 
   fun onEntrySelected(selectedEntry: EntryInfo) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 21abe08..fda0b97 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -31,7 +31,6 @@
   name: String,
   displayName: String,
   var createOptions: List<CreateOptionInfo>,
-  val isDefault: Boolean,
   var remoteEntry: RemoteInfo?,
 ) : ProviderInfo(icon, name, displayName)
 
@@ -77,7 +76,6 @@
   val type: String,
   val appDomainName: String,
   val typeIcon: Drawable,
-  val isFirstUsage: Boolean,
 )
 
 /**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
index 1693eb6..47b5af0 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
@@ -16,8 +16,8 @@
 
 package com.android.credentialmanager.jetpack.provider
 
+import android.app.PendingIntent
 import android.app.slice.Slice
-import android.credentials.ui.Entry
 import android.graphics.drawable.Icon
 
 /**
@@ -32,38 +32,57 @@
   val userDisplayName: CharSequence?,
   val entryIcon: Icon?,
   val lastUsedTimeMillis: Long?,
+  // TODO: Remove note
   val note: CharSequence?,
 ) {
   companion object {
-    fun fromSlice(slice: Slice): CredentialEntryUi {
-      var credentialType = slice.spec!!.type
-      var credentialTypeDisplayName: CharSequence? = null
-      var userName: CharSequence? = null
-      var userDisplayName: CharSequence? = null
-      var entryIcon: Icon? = null
-      var lastUsedTimeMillis: Long? = null
-      var note: CharSequence? = null
+    // Copied over from jetpack
+    const val SLICE_HINT_TYPE_DISPLAY_NAME =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
+    const val SLICE_HINT_USERNAME =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_USER_NAME"
+    const val SLICE_HINT_DISPLAYNAME =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_CREDENTIAL_TYPE_DISPLAY_NAME"
+    const val SLICE_HINT_LAST_USED_TIME_MILLIS =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
+    const val SLICE_HINT_ICON =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PROFILE_ICON"
+    const val SLICE_HINT_PENDING_INTENT =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PENDING_INTENT"
 
-      val items = slice.items
-      items.forEach {
-        if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_DISPLAY_NAME)) {
-          credentialTypeDisplayName = it.text
-        } else if (it.hasHint(Entry.HINT_USER_NAME)) {
-          userName = it.text
-        } else if (it.hasHint(Entry.HINT_PASSKEY_USER_DISPLAY_NAME)) {
-          userDisplayName = it.text
-        } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
-          entryIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
+    /**
+     * Returns an instance of [CredentialEntryUi] derived from a [Slice] object.
+     *
+     * @param slice the [Slice] object constructed through jetpack library
+     */
+    @JvmStatic
+    fun fromSlice(slice: Slice): CredentialEntryUi {
+      var username: CharSequence? = null
+      var displayName: CharSequence = ""
+      var icon: Icon? = null
+      var pendingIntent: PendingIntent? = null
+      var lastUsedTimeMillis: Long = 0
+      var note: CharSequence? = null
+      var typeDisplayName: CharSequence = ""
+
+      slice.items.forEach {
+        if (it.hasHint(SLICE_HINT_TYPE_DISPLAY_NAME)) {
+          typeDisplayName = it.text
+        } else if (it.hasHint(SLICE_HINT_USERNAME)) {
+          username = it.text
+        } else if (it.hasHint(SLICE_HINT_DISPLAYNAME)) {
+          displayName = it.text
+        } else if (it.hasHint(SLICE_HINT_ICON)) {
+          icon = it.icon
+        } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
+          pendingIntent = it.action
+        } else if (it.hasHint(SLICE_HINT_LAST_USED_TIME_MILLIS)) {
           lastUsedTimeMillis = it.long
-        } else if (it.hasHint(Entry.HINT_NOTE)) {
-          note = it.text
         }
       }
-
       return CredentialEntryUi(
-        credentialType, credentialTypeDisplayName!!, userName!!, userDisplayName, entryIcon,
-        lastUsedTimeMillis, note,
+              slice.spec!!.type, typeDisplayName, username!!, displayName, icon,
+              lastUsedTimeMillis, note,
       )
     }
   }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
index bcc0531..313f0f9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
@@ -16,8 +16,8 @@
 
 package com.android.credentialmanager.jetpack.provider
 
+import android.app.PendingIntent
 import android.app.slice.Slice
-import android.credentials.ui.Entry
 import android.graphics.drawable.Icon
 
 /**
@@ -35,38 +35,45 @@
   val lastUsedTimeMillis: Long?,
 ) {
   companion object {
+    const val SLICE_HINT_ACCOUNT_NAME =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_USER_PROVIDER_ACCOUNT_NAME"
+    const val SLICE_HINT_ICON =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_PROFILE_ICON"
+    const val SLICE_HINT_CREDENTIAL_COUNT_INFORMATION =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_CREDENTIAL_COUNT_INFORMATION"
+    const val SLICE_HINT_LAST_USED_TIME_MILLIS =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
+    const val SLICE_HINT_PENDING_INTENT =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_PENDING_INTENT"
+
+    /**
+     * Returns an instance of [SaveEntryUi] derived from a [Slice] object.
+     *
+     * @param slice the [Slice] object constructed through the jetpack library
+     */
+    @JvmStatic
     fun fromSlice(slice: Slice): SaveEntryUi {
-      var userProviderAccountName: CharSequence? = null
-      var credentialTypeIcon: Icon? = null
-      var profileIcon: Icon? = null
-      var passwordCount: Int? = null
-      var passkeyCount: Int? = null
-      var totalCredentialCount: Int? = null
-      var lastUsedTimeMillis: Long? = null
+      var accountName: CharSequence? = null
+      var icon: Icon? = null
+      var pendingIntent: PendingIntent? = null
+      var lastUsedTimeMillis: Long = 0
 
-
-      val items = slice.items
-      items.forEach {
-        if (it.hasHint(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME)) {
-          userProviderAccountName = it.text
-        } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) {
-          credentialTypeIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
-          profileIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_PASSWORD_COUNT)) {
-          passwordCount = it.int
-        } else if (it.hasHint(Entry.HINT_PASSKEY_COUNT)) {
-          passkeyCount = it.int
-        } else if (it.hasHint(Entry.HINT_TOTAL_CREDENTIAL_COUNT)) {
-          totalCredentialCount = it.int
-        } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
+      slice.items.forEach {
+        if (it.hasHint(SLICE_HINT_ACCOUNT_NAME)) {
+          accountName = it.text
+        } else if (it.hasHint(SLICE_HINT_ICON)) {
+          icon = it.icon
+        } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
+          pendingIntent = it.action
+        } else if (it.hasHint(SLICE_HINT_LAST_USED_TIME_MILLIS)) {
           lastUsedTimeMillis = it.long
         }
       }
-      // TODO: fail NPE more elegantly.
+
       return SaveEntryUi(
-        userProviderAccountName!!, credentialTypeIcon, profileIcon,
-        passwordCount, passkeyCount, totalCredentialCount, lastUsedTimeMillis,
+              // TODO: Add count parsing
+              accountName!!, icon, icon,
+              0, 0, 0, lastUsedTimeMillis,
       )
     }
   }
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 6a3b239..b713c14 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -119,6 +119,10 @@
     <string name="uninstall_update_text_multiuser">Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles.</string>
     <!-- Label of a checkbox that allows to keep the data (e.g. files, settings) of the app on uninstall [CHAR LIMIT=none] -->
     <string name="uninstall_keep_data">Keep <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of app data.</string>
+    <!--  [CHAR LIMIT=none] -->
+    <string name="uninstall_application_text_current_user_clone_profile">Do you want to delete this app?</string>
+    <!--  [CHAR LIMIT=none] -->
+    <string name="uninstall_application_text_with_clone_instance">Do you want to uninstall this app? <xliff:g id="package_label">%1$s</xliff:g> clone will also be deleted.</string>
 
     <!-- Label for the notification channel containing notifications for current uninstall operations [CHAR LIMIT=40] -->
     <string name="uninstalling_notification_channel">Running uninstalls</string>
@@ -137,6 +141,8 @@
     <string name="uninstall_failed">Uninstall unsuccessful.</string>
     <!-- [CHAR LIMIT=100] -->
     <string name="uninstall_failed_app">Uninstalling <xliff:g id="package_label">%1$s</xliff:g> unsuccessful.</string>
+    <!-- [CHAR LIMIT=100] -->
+    <string name="uninstalling_cloned_app">Deleting <xliff:g id="package_label">%1$s</xliff:g> clone\u2026</string>
     <!-- String presented to the user when uninstalling a package failed because the target package
         is a current device administrator [CHAR LIMIT=80] -->
     <string name="uninstall_failed_device_policy_manager">Can\'t uninstall active device admin
@@ -219,6 +225,9 @@
         TV or loss of data that may result from its use.
     </string>
 
+    <!-- Label for cloned app in uninstall dialogue [CHAR LIMIT=40] -->
+    <string name="cloned_app_label"><xliff:g id="package_label">%1$s</xliff:g> Clone</string>
+
     <!-- Label for button to continue install of an app whose source cannot be identified [CHAR LIMIT=40] -->
     <string name="anonymous_source_continue">Continue</string>
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
index 7bf27df..1485352 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
@@ -33,8 +33,10 @@
 import android.content.pm.VersionedPackage;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -51,6 +53,7 @@
 
     static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL";
     static final String EXTRA_KEEP_DATA = "com.android.packageinstaller.extra.KEEP_DATA";
+    public static final String EXTRA_IS_CLONE_USER = "isCloneUser";
 
     private int mUninstallId;
     private ApplicationInfo mAppInfo;
@@ -76,6 +79,18 @@
                 boolean keepData = getIntent().getBooleanExtra(EXTRA_KEEP_DATA, false);
                 UserHandle user = getIntent().getParcelableExtra(Intent.EXTRA_USER);
 
+                boolean isCloneUser = false;
+                if (user == null) {
+                    user = Process.myUserHandle();
+                }
+
+                UserManager customUserManager = UninstallUninstalling.this
+                        .createContextAsUser(UserHandle.of(user.getIdentifier()), 0)
+                        .getSystemService(UserManager.class);
+                if (customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)) {
+                    isCloneUser = true;
+                }
+
                 // Show dialog, which is the whole UI
                 FragmentTransaction transaction = getFragmentManager().beginTransaction();
                 Fragment prev = getFragmentManager().findFragmentByTag("dialog");
@@ -83,6 +98,9 @@
                     transaction.remove(prev);
                 }
                 DialogFragment dialog = new UninstallUninstallingFragment();
+                Bundle args = new Bundle();
+                args.putBoolean(EXTRA_IS_CLONE_USER, isCloneUser);
+                dialog.setArguments(args);
                 dialog.setCancelable(false);
                 dialog.show(transaction, "dialog");
 
@@ -176,9 +194,20 @@
         public Dialog onCreateDialog(Bundle savedInstanceState) {
             AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
 
+            Bundle bundle = getArguments();
+            boolean isCloneUser = false;
+            if (bundle != null) {
+                isCloneUser = bundle.getBoolean(EXTRA_IS_CLONE_USER);
+            }
+
             dialogBuilder.setCancelable(false);
-            dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_app,
-                    ((UninstallUninstalling) getActivity()).mLabel));
+            if (isCloneUser) {
+                dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_cloned_app,
+                        ((UninstallUninstalling) getActivity()).mLabel));
+            } else {
+                dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_app,
+                        ((UninstallUninstalling) getActivity()).mLabel));
+            }
 
             Dialog dialog = dialogBuilder.create();
             dialog.setCanceledOnTouchOutside(false);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index c9230b4..a1bc992 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -123,6 +123,7 @@
                 messageBuilder.append(" ").append(appLabel).append(".\n\n");
             }
         }
+        boolean isClonedApp = false;
 
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
@@ -144,16 +145,36 @@
                     messageBuilder.append(
                             getString(R.string.uninstall_application_text_current_user_work_profile,
                                     userInfo.name));
+                } else if (userInfo.isCloneProfile()
+                        && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+                    isClonedApp = true;
+                    messageBuilder.append(getString(
+                            R.string.uninstall_application_text_current_user_clone_profile));
                 } else {
                     messageBuilder.append(
                             getString(R.string.uninstall_application_text_user, userInfo.name));
                 }
+            } else if (isCloneProfile(myUserHandle)) {
+                isClonedApp = true;
+                messageBuilder.append(getString(
+                        R.string.uninstall_application_text_current_user_clone_profile));
             } else {
-                messageBuilder.append(getString(R.string.uninstall_application_text));
+                if (Process.myUserHandle().equals(UserHandle.SYSTEM)
+                        && hasClonedInstance(dialogInfo.appInfo.packageName)) {
+                    messageBuilder.append(getString(
+                            R.string.uninstall_application_text_with_clone_instance,
+                            appLabel));
+                } else {
+                    messageBuilder.append(getString(R.string.uninstall_application_text));
+                }
             }
         }
 
-        dialogBuilder.setTitle(appLabel);
+        if (isClonedApp) {
+            dialogBuilder.setTitle(getString(R.string.cloned_app_label, appLabel));
+        } else {
+            dialogBuilder.setTitle(appLabel);
+        }
         dialogBuilder.setPositiveButton(android.R.string.ok, this);
         dialogBuilder.setNegativeButton(android.R.string.cancel, this);
 
@@ -192,6 +213,42 @@
         return dialogBuilder.create();
     }
 
+    private boolean isCloneProfile(UserHandle userHandle) {
+        UserManager customUserManager = getContext()
+                .createContextAsUser(UserHandle.of(userHandle.getIdentifier()), 0)
+                .getSystemService(UserManager.class);
+        if (customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean hasClonedInstance(String packageName) {
+        // Check if clone user is present on the device.
+        UserHandle cloneUser = null;
+        UserManager userManager = getContext().getSystemService(UserManager.class);
+        List<UserHandle> profiles = userManager.getUserProfiles();
+        for (UserHandle userHandle : profiles) {
+            if (!Process.myUserHandle().equals(UserHandle.SYSTEM) && isCloneProfile(userHandle)) {
+                cloneUser = userHandle;
+                break;
+            }
+        }
+
+        // Check if another instance of given package exists in clone user profile.
+        if (cloneUser != null) {
+            try {
+                if (getContext().getPackageManager()
+                        .getPackageUidAsUser(packageName, cloneUser.getIdentifier()) > 0) {
+                    return true;
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
+        }
+        return false;
+    }
+
     @Override
     public void onClick(DialogInterface dialog, int which) {
         if (which == Dialog.BUTTON_POSITIVE) {
diff --git a/packages/SettingsLib/AppPreference/Android.bp b/packages/SettingsLib/AppPreference/Android.bp
index 122f606..af7b8b4 100644
--- a/packages/SettingsLib/AppPreference/Android.bp
+++ b/packages/SettingsLib/AppPreference/Android.bp
@@ -23,5 +23,6 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.permission",
+        "com.android.healthconnect",
     ],
 }
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index d32d659..965fdcf 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -38,7 +38,12 @@
         <provider
             android:name="com.android.settingslib.spa.search.SpaSearchProvider"
             android:authorities="com.android.spa.gallery.search.provider"
-            android:exported="false">
+            android:exported="true"
+            android:grantUriPermissions="true"
+            android:permission="android.permission.READ_SEARCH_INDEXABLES">
+            <intent-filter>
+                <action android:name="android.content.action.SPA_SEARCH_PROVIDER" />
+            </intent-filter>
         </provider>
 
         <provider android:name="com.android.settingslib.spa.slice.SpaSliceProvider"
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png
deleted file mode 100644
index 6086e2d..0000000
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png
deleted file mode 100644
index aa6c5b7..0000000
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_actionButtons.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_actionButtons.png
new file mode 100644
index 0000000..74113d8
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_barChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_barChart.png
new file mode 100644
index 0000000..0d22c6a
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_footer.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_footer.png
new file mode 100644
index 0000000..f77b8a7
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_imageIllustration.png
new file mode 100644
index 0000000..9372791
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_lineChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_lineChart.png
new file mode 100644
index 0000000..dda9e9e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_mainSwitchPreference.png
new file mode 100644
index 0000000..bf19a2c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_pieChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_pieChart.png
new file mode 100644
index 0000000..b14e196e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
index cac990c..c77f9b1 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png
new file mode 100644
index 0000000..6bf20be
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_slider.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_slider.png
new file mode 100644
index 0000000..dda6f0a
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_spinner.png
new file mode 100644
index 0000000..7468169
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_switchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_switchPreference.png
new file mode 100644
index 0000000..669f443
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_twoTargetSwitchPreference.png
new file mode 100644
index 0000000..8e37cc0
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_actionButtons.png
new file mode 100644
index 0000000..b0543e0
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_barChart.png
new file mode 100644
index 0000000..3755928
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_footer.png
new file mode 100644
index 0000000..f77b8a7
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_imageIllustration.png
new file mode 100644
index 0000000..7800149
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_lineChart.png
new file mode 100644
index 0000000..be40959
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_mainSwitchPreference.png
new file mode 100644
index 0000000..2d5894e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_pieChart.png
new file mode 100644
index 0000000..2a54b9e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
index f6298c0..b0fc305 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png
new file mode 100644
index 0000000..fa01348
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_slider.png
new file mode 100644
index 0000000..213c0b2
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_spinner.png
new file mode 100644
index 0000000..7468169
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_switchPreference.png
new file mode 100644
index 0000000..ce7ab78
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_twoTargetSwitchPreference.png
new file mode 100644
index 0000000..9d92f7a
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png
deleted file mode 100644
index 9391eeb..0000000
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_actionButtons.png
new file mode 100644
index 0000000..5255d14
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_barChart.png
new file mode 100644
index 0000000..8f3f664
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_footer.png
new file mode 100644
index 0000000..cc1de55
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_imageIllustration.png
new file mode 100644
index 0000000..e29f26a
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_lineChart.png
new file mode 100644
index 0000000..8b9bc5c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_mainSwitchPreference.png
new file mode 100644
index 0000000..b1676b3
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_pieChart.png
new file mode 100644
index 0000000..2a7b341
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
index 94e2843..4845ea8 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png
new file mode 100644
index 0000000..7e57958
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_slider.png
new file mode 100644
index 0000000..d1abaa6
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_spinner.png
new file mode 100644
index 0000000..c211e0e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_switchPreference.png
new file mode 100644
index 0000000..9c57e33
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
new file mode 100644
index 0000000..d416a0b
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png
deleted file mode 100644
index b1d03c3..0000000
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png
deleted file mode 100644
index 95f19da..0000000
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/res/drawable/accessibility_captioning_banner.xml b/packages/SettingsLib/Spa/screenshot/res/drawable/accessibility_captioning_banner.xml
new file mode 100644
index 0000000..6597ffb
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/res/drawable/accessibility_captioning_banner.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <path
+      android:pathData="M383.9,300H28.1C12.6,300 0,287.4 0,271.9V28.1C0,12.6 12.6,0 28.1,0h355.8C399.4,0 412,12.6 412,28.1v243.8C412,287.4 399.4,300 383.9,300z"
+      android:fillColor="#FFFFFF"/>
+  <path
+      android:pathData="M79.2,179.6h53.6v8.5h-53.6z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M142.5,179.6h30.4v8.5h-30.4z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M79.2,195.5h79.2v8.5h-79.2z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M168.1,195.5h34.1v8.5h-34.1z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M211.9,195.5h34.1v8.5h-34.1z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M182.7,179.6h73.1v8.5h-73.1z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M265.5,179.6h26.8v8.5h-26.8z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M302.1,179.6h26.8v8.5h-26.8z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M142.7,67.9h-11.5c-1.6,0 -2.9,1.3 -2.9,2.9H67.8c-7.9,0 -14.4,6.5 -14.4,14.4v132.4c0,7.9 6.5,14.4 14.4,14.4h276.4c7.9,0 14.4,-6.5 14.4,-14.4V85.2c0,-7.9 -6.5,-14.4 -14.4,-14.4H203.1c0,-1.6 -1.3,-2.9 -2.9,-2.9h-28.8c-1.6,0 -2.9,1.3 -2.9,2.9h-23C145.5,69.2 144.3,67.9 142.7,67.9zM344.2,73.7c6.4,0 11.5,5.2 11.5,11.5v132.4c0,6.3 -5.2,11.5 -11.5,11.5H67.8c-6.4,0 -11.5,-5.2 -11.5,-11.5V85.2c0,-6.3 5.2,-11.5 11.5,-11.5H344.2z"
+      android:fillColor="#DADCE0"/>
+</vector>
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
new file mode 100644
index 0000000..b2e0b18
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.screenshot
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.Launch
+import androidx.compose.material.icons.outlined.WarningAmber
+import com.android.settingslib.spa.widget.button.ActionButton
+import com.android.settingslib.spa.widget.button.ActionButtons
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class ActionButtonsScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun test() {
+        screenshotRule.screenshotTest("actionButtons") {
+            val actionButtons = listOf(
+                ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
+                ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
+            )
+            ActionButtons(actionButtons)
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
new file mode 100644
index 0000000..27d270c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.screenshot
+
+import com.android.settingslib.spa.widget.chart.BarChart
+import com.android.settingslib.spa.widget.chart.BarChartData
+import com.android.settingslib.spa.widget.chart.BarChartModel
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class BarChartScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun test() {
+        screenshotRule.screenshotTest("barChart") {
+            BarChart(
+                barChartModel = object : BarChartModel {
+                    override val chartDataList = listOf(
+                        BarChartData(x = 0f, y = 12f),
+                        BarChartData(x = 1f, y = 5f),
+                        BarChartData(x = 2f, y = 21f),
+                        BarChartData(x = 3f, y = 5f),
+                        BarChartData(x = 4f, y = 10f),
+                        BarChartData(x = 5f, y = 9f),
+                        BarChartData(x = 6f, y = 1f),
+                    )
+                    override val xValueFormatter =
+                        IAxisValueFormatter { value, _ ->
+                            "${WeekDay.values()[value.toInt()]}"
+                        }
+                    override val yValueFormatter =
+                        IAxisValueFormatter { value, _ ->
+                            "${value.toInt()}m"
+                        }
+                    override val yAxisMaxValue = 30f
+                }
+            )
+        }
+    }
+
+    private enum class WeekDay(val num: Int) {
+        Sun(0), Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6),
+    }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
new file mode 100644
index 0000000..f9d93f8
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.screenshot
+
+import com.android.settingslib.spa.widget.chart.LineChart
+import com.android.settingslib.spa.widget.chart.LineChartData
+import com.android.settingslib.spa.widget.chart.LineChartModel
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+import java.text.NumberFormat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class LineChartScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun test() {
+        screenshotRule.screenshotTest("lineChart") {
+            LineChart(
+                lineChartModel = object : LineChartModel {
+                    override val chartDataList = listOf(
+                        LineChartData(x = 0f, y = 0f),
+                        LineChartData(x = 1f, y = 0.1f),
+                        LineChartData(x = 2f, y = 0.2f),
+                        LineChartData(x = 3f, y = 0.6f),
+                        LineChartData(x = 4f, y = 0.9f),
+                        LineChartData(x = 5f, y = 1.0f),
+                        LineChartData(x = 6f, y = 0.8f),
+                    )
+                    override val xValueFormatter =
+                        IAxisValueFormatter { value, _ ->
+                            "${WeekDay.values()[value.toInt()]}"
+                        }
+                    override val yValueFormatter =
+                        IAxisValueFormatter { value, _ ->
+                            NumberFormat.getPercentInstance().format(value)
+                        }
+                }
+            )
+        }
+    }
+
+    private enum class WeekDay(val num: Int) {
+        Sun(0), Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6),
+    }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
new file mode 100644
index 0000000..34ded3c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.screenshot
+
+import com.android.settingslib.spa.widget.chart.PieChart
+import com.android.settingslib.spa.widget.chart.PieChartData
+import com.android.settingslib.spa.widget.chart.PieChartModel
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class PieChartScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun test() {
+        screenshotRule.screenshotTest("pieChart") {
+            PieChart(
+                pieChartModel = object : PieChartModel {
+                    override val chartDataList = listOf(
+                        PieChartData(label = "Settings", value = 20f),
+                        PieChartData(label = "Chrome", value = 5f),
+                        PieChartData(label = "Gmail", value = 3f),
+                    )
+                    override val centerText = "Today"
+                }
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
new file mode 100644
index 0000000..91aca05
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.screenshot
+
+import com.android.settingslib.spa.widget.illustration.Illustration
+import com.android.settingslib.spa.widget.illustration.IllustrationModel
+import com.android.settingslib.spa.widget.illustration.ResourceType
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class ImageIllustrationScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun test() {
+        screenshotRule.screenshotTest("imageIllustration") {
+            Illustration(object : IllustrationModel {
+                override val resId = R.drawable.accessibility_captioning_banner
+                override val resourceType = ResourceType.IMAGE
+            })
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
new file mode 100644
index 0000000..a366b9e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.screenshot
+
+import androidx.compose.foundation.layout.Column
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.preference.MainSwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class MainSwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun test() {
+        screenshotRule.screenshotTest("mainSwitchPreference") {
+            Column {
+                MainSwitchPreference(
+                    object : SwitchPreferenceModel {
+                        override val title = "MainSwitchPreference"
+                        override val checked = stateOf(false)
+                        override val onCheckedChange = null
+                    })
+
+                MainSwitchPreference(object : SwitchPreferenceModel {
+                    override val title = "Not changeable"
+                    override val changeable = stateOf(false)
+                    override val checked = stateOf(true)
+                    override val onCheckedChange = null
+                })
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
similarity index 93%
rename from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt
rename to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
index 9631826..d72152c 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.spa.screenshot
 
+import androidx.compose.foundation.layout.Column
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.Autorenew
 import androidx.compose.material.icons.outlined.DisabledByDefault
@@ -23,7 +24,6 @@
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.SettingsIcon
 import org.junit.Rule
 import org.junit.Test
@@ -37,7 +37,7 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletFull
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
         private const val TITLE = "Title"
         private const val SUMMARY = "Summary"
         private const val LONG_SUMMARY =
@@ -52,9 +52,9 @@
         )
 
     @Test
-    fun testPreference() {
+    fun test() {
         screenshotRule.screenshotTest("preference") {
-            RegularScaffold(title = "Preference") {
+            Column {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
                 })
@@ -88,4 +88,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
new file mode 100644
index 0000000..5fcaf85
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.screenshot
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.SystemUpdate
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.widget.preference.ProgressBarPreference
+import com.android.settingslib.spa.widget.preference.ProgressBarPreferenceModel
+import com.android.settingslib.spa.widget.preference.ProgressBarWithDataPreference
+import com.android.settingslib.spa.widget.ui.CircularProgressBar
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class ProgressBarPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun test() {
+        screenshotRule.screenshotTest("progressBar") {
+            Column {
+                LargeProgressBar()
+                SimpleProgressBar()
+                ProgressBarWithData()
+                CircularProgressBar(progress = 0.8f, radius = 160f)
+            }
+        }
+    }
+}
+
+@Composable
+private fun LargeProgressBar() {
+    ProgressBarPreference(object : ProgressBarPreferenceModel {
+        override val title = "Large Progress Bar"
+        override val progress = 0.2f
+        override val height = 20f
+    })
+}
+
+@Composable
+private fun SimpleProgressBar() {
+    ProgressBarPreference(object : ProgressBarPreferenceModel {
+        override val title = "Simple Progress Bar"
+        override val progress = 0.2f
+        override val icon = Icons.Outlined.SystemUpdate
+    })
+}
+
+@Composable
+private fun ProgressBarWithData() {
+    ProgressBarWithDataPreference(model = object : ProgressBarPreferenceModel {
+        override val title = "Progress Bar with Data"
+        override val progress = 0.2f
+        override val icon = Icons.Outlined.Delete
+    }, data = "25G")
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
new file mode 100644
index 0000000..48c922d
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.screenshot
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AccessAlarm
+import com.android.settingslib.spa.widget.preference.SliderPreference
+import com.android.settingslib.spa.widget.preference.SliderPreferenceModel
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class SliderPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun test() {
+        screenshotRule.screenshotTest("slider") {
+            Column {
+                SliderPreference(object : SliderPreferenceModel {
+                    override val title = "Simple Slider"
+                    override val initValue = 40
+                })
+
+                SliderPreference(object : SliderPreferenceModel {
+                    override val title = "Slider with icon"
+                    override val initValue = 30
+                    override val onValueChangeFinished = {
+                        println("onValueChangeFinished")
+                    }
+                    override val icon = Icons.Outlined.AccessAlarm
+                })
+
+                SliderPreference(object : SliderPreferenceModel {
+                    override val title = "Slider with steps"
+                    override val initValue = 2
+                    override val valueRange = 1..5
+                    override val showSteps = true
+                })
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
new file mode 100644
index 0000000..2c84a8e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.screenshot
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.ui.SettingsIcon
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class SwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun test() {
+        screenshotRule.screenshotTest("switchPreference") {
+            Column {
+                SampleSwitchPreference()
+                SampleSwitchPreferenceWithSummary()
+                SampleNotChangeableSwitchPreference()
+                SampleSwitchPreferenceWithIcon()
+            }
+        }
+    }
+}
+
+@Composable
+private fun SampleSwitchPreference() {
+    SwitchPreference(object : SwitchPreferenceModel {
+        override val title = "SwitchPreference"
+        override val checked = stateOf(false)
+        override val onCheckedChange = null
+    })
+}
+
+@Composable
+private fun SampleSwitchPreferenceWithSummary() {
+    SwitchPreference(object : SwitchPreferenceModel {
+        override val title = "SwitchPreference"
+        override val summary = stateOf("With summary")
+        override val checked = stateOf(true)
+        override val onCheckedChange = null
+    })
+}
+
+@Composable
+private fun SampleNotChangeableSwitchPreference() {
+    SwitchPreference(object : SwitchPreferenceModel {
+        override val title = "SwitchPreference"
+        override val summary = stateOf("Not changeable")
+        override val changeable = stateOf(false)
+        override val checked = stateOf(true)
+        override val onCheckedChange = null
+    })
+}
+
+@Composable
+private fun SampleSwitchPreferenceWithIcon() {
+    SwitchPreference(object : SwitchPreferenceModel {
+        override val title = "SwitchPreference"
+        override val checked = stateOf(true)
+        override val onCheckedChange = null
+        override val icon = @Composable {
+            SettingsIcon(imageVector = Icons.Outlined.AirplanemodeActive)
+        }
+    })
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
new file mode 100644
index 0000000..2c37212
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.screenshot
+
+import androidx.compose.foundation.layout.Column
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class TwoTargetSwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun test() {
+        screenshotRule.screenshotTest("twoTargetSwitchPreference") {
+            Column {
+                TwoTargetSwitchPreference(object : SwitchPreferenceModel {
+                    override val title = "TwoTargetSwitchPreference"
+                    override val checked = stateOf(false)
+                    override val onCheckedChange = null
+                }) {}
+
+                TwoTargetSwitchPreference(object : SwitchPreferenceModel {
+                    override val title = "TwoTargetSwitchPreference"
+                    override val summary = stateOf("With summary")
+                    override val checked = stateOf(true)
+                    override val onCheckedChange = null
+                }) {}
+
+                TwoTargetSwitchPreference(object : SwitchPreferenceModel {
+                    override val title = "Not changeable"
+                    override val changeable = stateOf(false)
+                    override val checked = stateOf(true)
+                    override val onCheckedChange = null
+                }) {}
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
new file mode 100644
index 0000000..0a0faf6
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.screenshot
+
+import com.android.settingslib.spa.widget.ui.Footer
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class FooterScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun test() {
+        screenshotRule.screenshotTest("footer") {
+            Footer(footerText = "Footer text always at the end of page.")
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
new file mode 100644
index 0000000..e9b5b30
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.screenshot
+
+import com.android.settingslib.spa.widget.ui.Spinner
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class SpinnerScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun test() {
+        screenshotRule.screenshotTest("spinner") {
+            Spinner(
+                options = (1..3).map { "Option $it" },
+                selectedIndex = 0,
+                setIndex = {},
+            )
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
index 2301f04..83dcd13 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
@@ -33,6 +33,13 @@
 /** ContentProvider path for search mutable status */
 const val SEARCH_MUTABLE_STATUS = "search_mutable_status"
 
+/** ContentProvider path for search static row */
+const val SEARCH_STATIC_ROW = "search_static_row"
+
+/** ContentProvider path for search dynamic row */
+const val SEARCH_DYNAMIC_ROW = "search_dynamic_row"
+
+
 /** Enum to define all column names in provider. */
 enum class ColumnEnum(val id: String) {
     ENTRY_ID("entryId"),
@@ -76,6 +83,7 @@
             ColumnEnum.SEARCH_PATH,
             ColumnEnum.INTENT_TARGET_PACKAGE,
             ColumnEnum.INTENT_TARGET_CLASS,
+            ColumnEnum.INTENT_EXTRAS,
             ColumnEnum.SLICE_URI,
             ColumnEnum.LEGACY_KEY
         )
@@ -94,4 +102,34 @@
             ColumnEnum.ENTRY_DISABLED,
         )
     ),
+    SEARCH_STATIC_ROW_QUERY(
+        SEARCH_STATIC_ROW,
+        listOf(
+            ColumnEnum.ENTRY_ID,
+            ColumnEnum.SEARCH_TITLE,
+            ColumnEnum.SEARCH_KEYWORD,
+            ColumnEnum.SEARCH_PATH,
+            ColumnEnum.INTENT_TARGET_PACKAGE,
+            ColumnEnum.INTENT_TARGET_CLASS,
+            ColumnEnum.INTENT_EXTRAS,
+            ColumnEnum.SLICE_URI,
+            ColumnEnum.LEGACY_KEY,
+            ColumnEnum.ENTRY_DISABLED,
+        )
+    ),
+    SEARCH_DYNAMIC_ROW_QUERY(
+        SEARCH_DYNAMIC_ROW,
+        listOf(
+            ColumnEnum.ENTRY_ID,
+            ColumnEnum.SEARCH_TITLE,
+            ColumnEnum.SEARCH_KEYWORD,
+            ColumnEnum.SEARCH_PATH,
+            ColumnEnum.INTENT_TARGET_PACKAGE,
+            ColumnEnum.INTENT_TARGET_CLASS,
+            ColumnEnum.INTENT_EXTRAS,
+            ColumnEnum.SLICE_URI,
+            ColumnEnum.LEGACY_KEY,
+            ColumnEnum.ENTRY_DISABLED,
+        )
+    ),
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
index a1543d4..057f13f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
@@ -28,7 +28,6 @@
 import android.os.Parcelable
 import android.util.Log
 import androidx.annotation.VisibleForTesting
-import com.android.settingslib.spa.framework.common.EntryStatusData
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.util.SESSION_SEARCH
@@ -49,6 +48,8 @@
  *   $ adb shell content query --uri content://<AuthorityPath>/search_dynamic_data
  *   $ adb shell content query --uri content://<AuthorityPath>/search_immutable_status
  *   $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status
+ *   $ adb shell content query --uri content://<AuthorityPath>/search_static_row
+ *   $ adb shell content query --uri content://<AuthorityPath>/search_dynamic_row
  */
 class SpaSearchProvider : ContentProvider() {
     private val spaEnvironment get() = SpaEnvironmentFactory.instance
@@ -58,7 +59,9 @@
         SEARCH_STATIC_DATA to 301,
         SEARCH_DYNAMIC_DATA to 302,
         SEARCH_MUTABLE_STATUS to 303,
-        SEARCH_IMMUTABLE_STATUS to 304
+        SEARCH_IMMUTABLE_STATUS to 304,
+        SEARCH_STATIC_ROW to 305,
+        SEARCH_DYNAMIC_ROW to 306
     )
 
     override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
@@ -114,6 +117,8 @@
                     querySearchMutableStatusData()
                 queryMatchCode[SEARCH_IMMUTABLE_STATUS] ->
                     querySearchImmutableStatusData()
+                queryMatchCode[SEARCH_STATIC_ROW] -> querySearchStaticRow()
+                queryMatchCode[SEARCH_DYNAMIC_ROW] -> querySearchDynamicRow()
                 else -> throw UnsupportedOperationException("Unknown Uri $uri")
             }
         } catch (e: UnsupportedOperationException) {
@@ -168,6 +173,31 @@
         return cursor
     }
 
+    @VisibleForTesting
+    fun querySearchStaticRow(): Cursor {
+        val entryRepository by spaEnvironment.entryRepository
+        val cursor = MatrixCursor(QueryEnum.SEARCH_STATIC_ROW_QUERY.getColumns())
+        for (entry in entryRepository.getAllEntries()) {
+            if (!entry.isAllowSearch || entry.isSearchDataDynamic || entry.hasMutableStatus)
+                continue
+            fetchSearchRow(entry, cursor)
+        }
+        return cursor
+    }
+
+    @VisibleForTesting
+    fun querySearchDynamicRow(): Cursor {
+        val entryRepository by spaEnvironment.entryRepository
+        val cursor = MatrixCursor(QueryEnum.SEARCH_DYNAMIC_ROW_QUERY.getColumns())
+        for (entry in entryRepository.getAllEntries()) {
+            if (!entry.isAllowSearch || (!entry.isSearchDataDynamic && !entry.hasMutableStatus))
+                continue
+            fetchSearchRow(entry, cursor)
+        }
+        return cursor
+    }
+
+
     private fun fetchSearchData(entry: SettingsEntry, cursor: MatrixCursor) {
         val entryRepository by spaEnvironment.entryRepository
 
@@ -196,12 +226,42 @@
 
     private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
         // Fetch status data. We can add runtime arguments later if necessary
-        val statusData = entry.getStatusData() ?: EntryStatusData()
+        val statusData = entry.getStatusData() ?: return
         cursor.newRow()
             .add(ColumnEnum.ENTRY_ID.id, entry.id)
             .add(ColumnEnum.ENTRY_DISABLED.id, statusData.isDisabled)
     }
 
+    private fun fetchSearchRow(entry: SettingsEntry, cursor: MatrixCursor) {
+        val entryRepository by spaEnvironment.entryRepository
+
+        // Fetch search data. We can add runtime arguments later if necessary
+        val searchData = entry.getSearchData() ?: return
+        val intent = entry.createIntent(SESSION_SEARCH)
+        val row = cursor.newRow().add(ColumnEnum.ENTRY_ID.id, entry.id)
+            .add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
+            .add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
+            .add(
+                ColumnEnum.SEARCH_PATH.id,
+                entryRepository.getEntryPathWithTitle(entry.id, searchData.title)
+            )
+        intent?.let {
+            row.add(ColumnEnum.INTENT_TARGET_PACKAGE.id, spaEnvironment.appContext.packageName)
+                .add(ColumnEnum.INTENT_TARGET_CLASS.id, spaEnvironment.browseActivityClass?.name)
+                .add(ColumnEnum.INTENT_EXTRAS.id, marshall(intent.extras))
+        }
+        if (entry.hasSliceSupport)
+            row.add(
+                ColumnEnum.SLICE_URI.id, Uri.Builder()
+                    .fromEntry(entry, spaEnvironment.sliceProviderAuthorities)
+            )
+        // TODO: support legacy key
+
+        // Fetch status data. We can add runtime arguments later if necessary
+        val statusData = entry.getStatusData() ?: return
+        row.add(ColumnEnum.ENTRY_DISABLED.id, statusData.isDisabled)
+    }
+
     private fun QueryEnum.getColumns(): Array<String> {
         return columnNames.map { it.id }.toTypedArray()
     }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
index 831aded..007d08b 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
@@ -41,27 +41,25 @@
     private val pageOwner = spaEnvironment.createPage("SppForSearch")
 
     @Test
+    fun testQueryColumnSetup() {
+        Truth.assertThat(QueryEnum.SEARCH_STATIC_DATA_QUERY.columnNames)
+            .containsExactlyElementsIn(QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.columnNames)
+        Truth.assertThat(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.columnNames)
+            .containsExactlyElementsIn(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.columnNames)
+        Truth.assertThat(QueryEnum.SEARCH_STATIC_ROW_QUERY.columnNames)
+            .containsExactlyElementsIn(QueryEnum.SEARCH_DYNAMIC_ROW_QUERY.columnNames)
+    }
+
+    @Test
     fun testQuerySearchStatusData() {
         SpaEnvironmentFactory.reset(spaEnvironment)
 
         val immutableStatus = searchProvider.querySearchImmutableStatusData()
-        Truth.assertThat(immutableStatus.count).isEqualTo(2)
+        Truth.assertThat(immutableStatus.count).isEqualTo(1)
         immutableStatus.moveToFirst()
         immutableStatus.checkValue(
             QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
             ColumnEnum.ENTRY_ID,
-            pageOwner.getEntryId("SearchStaticWithNoStatus")
-        )
-        immutableStatus.checkValue(
-            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
-            ColumnEnum.ENTRY_DISABLED,
-            false.toString()
-        )
-
-        immutableStatus.moveToNext()
-        immutableStatus.checkValue(
-            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
-            ColumnEnum.ENTRY_ID,
             pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
         )
         immutableStatus.checkValue(
@@ -163,6 +161,97 @@
             listOf("kw1", "kw2").toString()
         )
     }
+
+    @Test
+    fun testQuerySearchIndexRow() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+
+        val staticRow = searchProvider.querySearchStaticRow()
+        Truth.assertThat(staticRow.count).isEqualTo(1)
+        staticRow.moveToFirst()
+        staticRow.checkValue(
+            QueryEnum.SEARCH_STATIC_ROW_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchStaticWithNoStatus")
+        )
+        staticRow.checkValue(
+            QueryEnum.SEARCH_STATIC_ROW_QUERY, ColumnEnum.SEARCH_TITLE, "SearchStaticWithNoStatus"
+        )
+        staticRow.checkValue(
+            QueryEnum.SEARCH_STATIC_ROW_QUERY, ColumnEnum.SEARCH_KEYWORD, listOf("").toString()
+        )
+        staticRow.checkValue(
+            QueryEnum.SEARCH_STATIC_ROW_QUERY,
+            ColumnEnum.SEARCH_PATH,
+            listOf("SearchStaticWithNoStatus", "SppForSearch").toString()
+        )
+        staticRow.checkValue(
+            QueryEnum.SEARCH_STATIC_ROW_QUERY,
+            ColumnEnum.INTENT_TARGET_PACKAGE,
+            spaEnvironment.appContext.packageName
+        )
+        staticRow.checkValue(
+            QueryEnum.SEARCH_STATIC_ROW_QUERY,
+            ColumnEnum.INTENT_TARGET_CLASS,
+            "com.android.settingslib.spa.tests.testutils.BlankActivity"
+        )
+
+        // Check extras in intent
+        val bundle =
+            staticRow.getExtras(QueryEnum.SEARCH_STATIC_ROW_QUERY, ColumnEnum.INTENT_EXTRAS)
+        Truth.assertThat(bundle).isNotNull()
+        Truth.assertThat(bundle!!.size()).isEqualTo(3)
+        Truth.assertThat(bundle.getString("spaActivityDestination")).isEqualTo("SppForSearch")
+        Truth.assertThat(bundle.getString("highlightEntry"))
+            .isEqualTo(pageOwner.getEntryId("SearchStaticWithNoStatus"))
+        Truth.assertThat(bundle.getString("sessionSource")).isEqualTo("search")
+
+        Truth.assertThat(
+            staticRow.getString(
+                QueryEnum.SEARCH_STATIC_ROW_QUERY.columnNames.indexOf(
+                    ColumnEnum.ENTRY_DISABLED
+                )
+            )
+        ).isNull()
+
+        val dynamicRow = searchProvider.querySearchDynamicRow()
+        Truth.assertThat(dynamicRow.count).isEqualTo(3)
+        dynamicRow.moveToFirst()
+        dynamicRow.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchStaticWithMutableStatus")
+        )
+        dynamicRow.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_ROW_QUERY, ColumnEnum.ENTRY_DISABLED, false.toString()
+        )
+
+        dynamicRow.moveToNext()
+        dynamicRow.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchDynamicWithMutableStatus")
+        )
+        dynamicRow.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_ROW_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
+        )
+
+
+        dynamicRow.moveToNext()
+        dynamicRow.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
+        )
+        dynamicRow.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
+            ColumnEnum.SEARCH_KEYWORD,
+            listOf("kw1", "kw2").toString()
+        )
+        dynamicRow.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_ROW_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
+        )
+    }
 }
 
 private fun Cursor.checkValue(query: QueryEnum, column: ColumnEnum, value: String) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 5ae5ada..62db7bd 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -93,7 +93,7 @@
         ) {
             val context = LocalContext.current
             val internalListModel = remember {
-                TogglePermissionInternalAppListModel(context, listModel)
+                TogglePermissionInternalAppListModel(context, listModel, ::RestrictionsProviderImpl)
             }
             val record = remember { listModel.transformItem(app) }
             if (!remember { listModel.isChangeable(record) }) return
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 00eb607..f65e310 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -42,6 +42,7 @@
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.userId
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
 import kotlinx.coroutines.flow.Flow
@@ -64,41 +65,18 @@
         val permissionType = parameter.getStringArg(PERMISSION, arguments)!!
         val appListPage = SettingsPage.create(name, parameter = parameter, arguments = arguments)
         val appInfoPage = TogglePermissionAppInfoPageProvider.buildPageData(permissionType)
-        val entryList = mutableListOf<SettingsEntry>()
         // TODO: add more categories, such as personal, work, cloned, etc.
-        for (category in listOf("personal")) {
-            entryList.add(
-                SettingsEntryBuilder.createLinkFrom("${ENTRY_NAME}_$category", appListPage)
-                    .setLink(toPage = appInfoPage)
-                    .build()
-            )
+        return listOf("personal").map { category ->
+            SettingsEntryBuilder.createLinkFrom("${ENTRY_NAME}_$category", appListPage)
+                .setLink(toPage = appInfoPage)
+                .build()
         }
-        return entryList
     }
 
     @Composable
     override fun Page(arguments: Bundle?) {
-        TogglePermissionAppList(arguments?.getString(PERMISSION)!!)
-    }
-
-    @Composable
-    private fun TogglePermissionAppList(permissionType: String) {
-        val listModel = appListTemplate.rememberModel(permissionType)
-        val context = LocalContext.current
-        val internalListModel = remember {
-            TogglePermissionInternalAppListModel(context, listModel)
-        }
-        AppListPage(
-            title = stringResource(listModel.pageTitleResId),
-            listModel = internalListModel,
-        ) {
-            AppListItem(
-                onClick = TogglePermissionAppInfoPageProvider.navigator(
-                    permissionType = permissionType,
-                    app = record.app,
-                ),
-            )
-        }
+        val permissionType = arguments?.getString(PERMISSION)!!
+        appListTemplate.rememberModel(permissionType).TogglePermissionAppList(permissionType)
     }
 
     companion object {
@@ -132,9 +110,34 @@
     }
 }
 
+@Composable
+internal fun <T : AppRecord> TogglePermissionAppListModel<T>.TogglePermissionAppList(
+    permissionType: String,
+    restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
+    appList: @Composable AppListInput<T>.() -> Unit = { AppList() },
+) {
+    val context = LocalContext.current
+    val internalListModel = remember {
+        TogglePermissionInternalAppListModel(context, this, restrictionsProviderFactory)
+    }
+    AppListPage(
+        title = stringResource(pageTitleResId),
+        listModel = internalListModel,
+        appList = appList,
+    ) {
+        AppListItem(
+            onClick = TogglePermissionAppInfoPageProvider.navigator(
+                permissionType = permissionType,
+                app = record.app,
+            ),
+        )
+    }
+}
+
 internal class TogglePermissionInternalAppListModel<T : AppRecord>(
     private val context: Context,
     private val listModel: TogglePermissionAppListModel<T>,
+    private val restrictionsProviderFactory: RestrictionsProviderFactory,
 ) : AppListModel<T> {
     override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
         listModel.transform(userIdFlow, appListFlow)
@@ -147,12 +150,12 @@
 
     @Composable
     fun getSummary(record: T): State<String> {
-        val restrictionsProvider = remember {
+        val restrictionsProvider = remember(record.app.userId) {
             val restrictions = Restrictions(
                 userId = record.app.userId,
                 keys = listModel.switchRestrictionKeys,
             )
-            RestrictionsProviderImpl(context, restrictions)
+            restrictionsProviderFactory(context, restrictions)
         }
         val restrictedMode = restrictionsProvider.restrictedModeState()
         val allowed = listModel.isAllowed(record)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
index 8c1421a..86b6f02 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.spaprivileged.template.scaffold
 
-import android.content.Context
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
@@ -24,7 +23,7 @@
 import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
-import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 
 @Composable
@@ -41,7 +40,7 @@
     text: String,
     restrictions: Restrictions,
     onClick: () -> Unit,
-    restrictionsProviderFactory: (Context, Restrictions) -> RestrictionsProvider,
+    restrictionsProviderFactory: RestrictionsProviderFactory,
 ) {
     val context = LocalContext.current
     val restrictionsProvider = remember(restrictions) {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
index 355dfb6..75b884c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -17,14 +17,22 @@
 package com.android.settingslib.spaprivileged.template.app
 
 import android.content.Context
+import android.content.pm.ApplicationInfo
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.State
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spaprivileged.test.R
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
 import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
@@ -38,10 +46,97 @@
 
     private val context: Context = ApplicationProvider.getApplicationContext()
 
+    private val fakeNavControllerWrapper = FakeNavControllerWrapper()
+
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
     @Test
-    fun appListInjectEntry_titleDisplayed() {
+    fun internalAppListModel_whenAllowed() {
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+        val listModel = TestTogglePermissionAppListModel(isAllowed = true)
+        val internalAppListModel = TogglePermissionInternalAppListModel(
+            context = context,
+            listModel = listModel,
+            restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+        )
+
+        val summaryState = getSummary(internalAppListModel)
+
+        assertThat(summaryState.value).isEqualTo(
+            context.getString(R.string.app_permission_summary_allowed)
+        )
+    }
+
+    @Test
+    fun internalAppListModel_whenNotAllowed() {
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+        val listModel = TestTogglePermissionAppListModel(isAllowed = false)
+        val internalAppListModel = TogglePermissionInternalAppListModel(
+            context = context,
+            listModel = listModel,
+            restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+        )
+
+        val summaryState = getSummary(internalAppListModel)
+
+        assertThat(summaryState.value).isEqualTo(
+            context.getString(R.string.app_permission_summary_not_allowed)
+        )
+    }
+
+    @Test
+    fun internalAppListModel_whenComputingAllowed() {
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+        val listModel = TestTogglePermissionAppListModel(isAllowed = null)
+        val internalAppListModel = TogglePermissionInternalAppListModel(
+            context = context,
+            listModel = listModel,
+            restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+        )
+
+        val summaryState = getSummary(internalAppListModel)
+
+        assertThat(summaryState.value).isEqualTo(
+            context.getString(R.string.summary_placeholder)
+        )
+    }
+
+    @Test
+    fun appListItem_onClick_navigate() {
+        val listModel = TestTogglePermissionAppListModel()
+        composeTestRule.setContent {
+            listModel.TogglePermissionAppList(
+                permissionType = PERMISSION_TYPE,
+                restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+            ) {
+                fakeNavControllerWrapper.Wrapper {
+                    AppListItemModel(
+                        record = listModel.transformItem(APP),
+                        label = LABEL,
+                        summary = stateOf(SUMMARY),
+                    ).appItem()
+                }
+            }
+        }
+
+        composeTestRule.onNodeWithText(LABEL).performClick()
+
+        assertThat(fakeNavControllerWrapper.navigateCalledWith)
+            .isEqualTo("TogglePermissionAppInfoPage/test.PERMISSION/package.name/0")
+    }
+
+    @Test
+    fun getRoute() {
+        val route = TogglePermissionAppListPageProvider.getRoute(PERMISSION_TYPE)
+
+        assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+    }
+
+    @Test
+    fun buildInjectEntry_titleDisplayed() {
+        val listModel = TestTogglePermissionAppListModel()
         val entry = TogglePermissionAppListPageProvider.buildInjectEntry(PERMISSION_TYPE) {
-            TestTogglePermissionAppListModel()
+            listModel
         }.build()
 
         composeTestRule.setContent {
@@ -50,18 +145,27 @@
             }
         }
 
-        composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+        composeTestRule.onNodeWithText(context.getString(listModel.pageTitleResId))
             .assertIsDisplayed()
     }
 
-    @Test
-    fun appListRoute() {
-        val route = TogglePermissionAppListPageProvider.getRoute(PERMISSION_TYPE)
-
-        assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+    private fun getSummary(
+        internalAppListModel: TogglePermissionInternalAppListModel<TestAppRecord>,
+    ): State<String> {
+        lateinit var summary: State<String>
+        composeTestRule.setContent {
+            summary = internalAppListModel.getSummary(record = TestAppRecord(APP))
+        }
+        return summary
     }
 
     private companion object {
         const val PERMISSION_TYPE = "test.PERMISSION"
+        const val PACKAGE_NAME = "package.name"
+        const val LABEL = "Label"
+        const val SUMMARY = "Summary"
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+        }
     }
 }
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 0234330..fb46dfb 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> oor tot vol"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> oor tot vol"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laaiproses is onderbreek"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Laaiproses is onderbreek"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laai tot <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laai"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Laai tans vinnig"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 29b188c..fa53f0d 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -233,7 +233,7 @@
     <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"የQR ኮድን በመጠቀም መሣሪያን ያጣምሩ"</string>
     <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"የQR ኮድ መቃኛን በመጠቀም አዲስ መሣሪያዎችን ያጣምሩ"</string>
     <string name="adb_pair_method_code_title" msgid="1122590300445142904">"የማጣመሪያ ኮድን በመጠቀም መሣሪያን ያጣምሩ"</string>
-    <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"የስድስት አኃዝ ኮድ በመጠቀም አዲስ መሣሪያዎችን ያጣምሩ"</string>
+    <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"የስድስት አሃዝ ኮድ በመጠቀም አዲስ መሣሪያዎችን ያጣምሩ"</string>
     <string name="adb_paired_devices_title" msgid="5268997341526217362">"የተጣመሩ መሣሪያዎች"</string>
     <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"አሁን ላይ ተገናኝቷል"</string>
     <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"የመሣሪያ ዝርዝሮች"</string>
@@ -344,7 +344,7 @@
     <string name="wait_for_debugger" msgid="7461199843335409809">"ስህተት ማስወገጃውን ጠብቅ"</string>
     <string name="wait_for_debugger_summary" msgid="6846330006113363286">"ስህተቱ የተወገደለት መተግበሪያ ከመፈጸሙ በፊት የስህተት ማስወገጃው እስኪያያዝ ድረስ እየጠበቀው ነው"</string>
     <string name="debug_input_category" msgid="7349460906970849771">"ግብዓት"</string>
-    <string name="debug_drawing_category" msgid="5066171112313666619">"ስዕል"</string>
+    <string name="debug_drawing_category" msgid="5066171112313666619">"ሥዕል"</string>
     <string name="debug_hw_drawing_category" msgid="5830815169336975162">"የተፋጠነ የሃርድዌር አሰጣጥ"</string>
     <string name="media_category" msgid="8122076702526144053">"ማህደረመረጃ"</string>
     <string name="debug_monitoring_category" msgid="1597387133765424994">"ቁጥጥር"</string>
@@ -467,7 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"እስኪሞላ ድረስ <xliff:g id="TIME">%1$s</xliff:g> ይቀራል"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - እስኪሞላ ድረስ <xliff:g id="TIME">%2$s</xliff:g> ይቀራል"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ኃይል መሙላት ባለበት ቆሟል"</string>
+    <!-- no translation found for power_charging_limited (6732738149313642521) -->
+    <skip />
     <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
     <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ያልታወቀ"</string>
@@ -548,10 +549,10 @@
     <string name="shared_data_title" msgid="1017034836800864953">"የተጋራ ውሂብ"</string>
     <string name="shared_data_summary" msgid="5516326713822885652">"የተጋራ ውሂብን ይመልከቱ እና ያሻሽሉ"</string>
     <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"ለዚህ ተጠቃሚ ምንም የተጋራ ውሂብ የለም።"</string>
-    <string name="shared_data_query_failure_text" msgid="3489828881998773687">"የተጋራውን ውሂብ በማግኘት ላይ ስሕተት ነበረ። እንደገና ይሞክሩ።"</string>
+    <string name="shared_data_query_failure_text" msgid="3489828881998773687">"የተጋራውን ውሂብ በማግኘት ላይ ስህተት ነበረ። እንደገና ይሞክሩ።"</string>
     <string name="blob_id_text" msgid="8680078988996308061">"የተጋራ ውሂብ መታወቂያ፦ <xliff:g id="BLOB_ID">%d</xliff:g>"</string>
     <string name="blob_expires_text" msgid="7882727111491739331">"በ<xliff:g id="DATE">%s</xliff:g> ላይ የአገልግሎት ጊዜው ያበቃል"</string>
-    <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"የተጋራውን ውሂብ በመሰረዝ ላይ ስሕተት ነበረ።"</string>
+    <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"የተጋራውን ውሂብ በመሰረዝ ላይ ስህተት ነበረ።"</string>
     <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"ለዚህ የተጋራ ውሂብ ምንም የሚያስፈልጉ ኪራዮች የሉም። ሊሰርዙት ይፈልጋሉ?"</string>
     <string name="accessor_info_title" msgid="8289823651512477787">"ውሂብ የሚጋሩ መተግበሪያዎች"</string>
     <string name="accessor_no_description_text" msgid="7510967452505591456">"በመተግበሪያው ምንም ዝርዝር መረጃ አልተሰጠም።"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 28a3f18..88db7be 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"يتبقّى <xliff:g id="TIME">%1$s</xliff:g> حتى اكتمال شحن البطارية."</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - يتبقّى <xliff:g id="TIME">%2$s</xliff:g> حتى اكتمال شحن البطارية."</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - تم إيقاف الشحن مؤقتًا"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - الشحن متوقّف مؤقتًا"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - الشحن حتى <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"غير معروف"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"جارٍ الشحن"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"جارٍ الشحن سريعًا"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index fc54e04..3f068b4 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"সম্পূৰ্ণ হ’বলৈ <xliff:g id="TIME">%1$s</xliff:g> বাকী আছে"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"সম্পূৰ্ণ হ’বলৈ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> বাকী আছে"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - চাৰ্জিং পজ কৰা হৈছে"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - চাৰ্জিং পজ কৰা হৈছে"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>লৈ চাৰ্জিং"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"অজ্ঞাত"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"চাৰ্জ কৰি থকা হৈছে"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"দ্ৰুততাৰে চাৰ্জ হৈছে"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index e2e294f..8eb8d5b 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Tam şarj edilənədək <xliff:g id="TIME">%1$s</xliff:g> qalıb"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - tam şarj edilənədək <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj durdurulub"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj durdurulub"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> olana qədər şarj edilir"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Naməlum"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Enerji doldurma"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Sürətlə doldurulur"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 479c9ca..bbdd8bf 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do kraja punjenja"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do kraja punjenja"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Punjenje je zaustavljeno"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Punjenje je pauzirano"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje do <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Puni se"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo se puni"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index b45996c..28c1e86 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Да поўнай зарадкі засталося <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – да поўнай зарадкі засталося: <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Зарадка прыпынена"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Зарадка прыпынена"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарадка да <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Невядома"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарадка"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хуткая зарадка"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index ecb5208..61e441d 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Оставащо време до пълно зареждане: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оставащо време до пълно зареждане: <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: Зареждането е на пауза"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – зареждането е на пауза"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарежда се до <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Зарежда се бързо"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index fda27ed..a9d32a9 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>-এ ব্যাটারি পুরো চার্জ হয়ে যাবে"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>-এ ব্যাটারি পুরো চার্জ হয়ে যাবে"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জিং পজ করা হয়েছে"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জিং পজ করা হয়েছে"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> পর্যন্ত চার্জ হচ্ছে"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"অজানা"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"চার্জ হচ্ছে"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"দ্রুত চার্জ হচ্ছে"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index cae96e9..b5c9177 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do potpune napunjenosti"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do potpune napunjenosti"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Punjenje je pauzirano"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Punjenje je pauzirano"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – Punjenje do <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 51a4a02..8add8c8 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> per completar la càrrega"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: la càrrega s\'ha posat en pausa"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g>: la càrrega s\'ha posat en pausa"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g>: s\'està carregant fins al <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconegut"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"S\'està carregant"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregant ràpidament"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index a50e527..4dc3c66 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do úplného nabití"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabití"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Nabíjení je pozastaveno"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Nabíjení pozastaveno"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – nabíjení do <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznámé"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíjí se"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Rychlé nabíjení"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 95e0130..9ab499a 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Fuldt opladet om <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – fuldt opladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Opladningen er sat på pause"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Opladning er sat på pause"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – Oplader til <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukendt"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Oplader"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Oplader hurtigt"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 12fce37..68d61c0 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Voll in <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – voll in <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladevorgang angehalten"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laden pausiert"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – Aufladung auf <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unbekannt"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Wird aufgeladen"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Schnelles Aufladen"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index f54d5d2..59ae638 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Απομένουν <xliff:g id="TIME">%1$s</xliff:g> για πλήρη φόρτιση"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Απομένουν <xliff:g id="TIME">%2$s</xliff:g> για πλήρη φόρτιση"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Η φόρτιση τέθηκε σε παύση"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Φόρτιση σε παύση"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Φόρτιση έως το <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Άγνωστο"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Φόρτιση"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ταχεία φόρτιση"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 208dbfa..3c98c25 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – charging paused"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – charging to <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index aac80ab..833a9e4 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -467,7 +467,7 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging paused"</string>
     <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging to <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 208dbfa..3c98c25 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – charging paused"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – charging to <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 208dbfa..3c98c25 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – charging paused"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – charging to <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index bfd7e83..b856eb1 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -467,7 +467,7 @@
     <string name="power_charging" msgid="6727132649743436802">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - ‎‏‎‎‏‏‎<xliff:g id="STATE">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="TIME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ left until full‎‏‎‎‏‎"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - ‎‏‎‎‏‏‎<xliff:g id="TIME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ left until full‎‏‎‎‏‎"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎‏‎‏‎‏‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - Charging is paused‎‏‎‎‏‎"</string>
+    <string name="power_charging_limited" msgid="6732738149313642521">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - Charging paused‎‏‎‎‏‎"</string>
     <string name="power_charging_future_paused" msgid="6829683663982987290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - Charging to ‎‏‎‎‏‏‎<xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎Unknown‎‏‎‎‏‎"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎Charging‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index c11d6eb0..78af70a 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> para completar"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> para completar"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Se pausó la carga"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Se pausó la carga"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Cargando hasta <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rápidamente"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 1ef351a..ea3047b 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> hasta la carga completa"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> hasta la carga completa"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga en pausa"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga pausada"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Cargando hasta <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carga rápida"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index e156011..78ade67 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Täislaadimiseks kulub <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – täislaadimiseks kulub <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine on peatatud"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine peatati"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine tasemeni <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tundmatu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laadimine"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Kiirlaadimine"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index e5f399c..748f991 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> guztiz kargatu arte"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kargatze-prozesua etenda dago"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kargatze-prozesua pausatuta dago"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> arte kargatzen"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ezezaguna"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Kargatzen"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Bizkor kargatzen"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index ccacfde..44e61ef 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - ‏<xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> تا شارژ کامل باقی مانده است"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> تا شارژ کامل باقی مانده است"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - شارژ موقتاً متوقف شده است"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - شارژ موقتاً متوقف شد"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - درحال شارژ تا <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ناشناس"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"در حال شارژ شدن"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"درحال شارژ شدن سریع"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 101dc67..2599eb3 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> kunnes täynnä"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kunnes täynnä"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Lataus on keskeytetty"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Lataaminen keskeytetty"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – Tavoite: <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tuntematon"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Ladataan"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Nopea lataus"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index ff58a51..314e8d1 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> jusqu\'à la recharge complète"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la recharge complète)"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - La recharge est interrompue"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Recharge interrompue"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - En charge jusqu\'à <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charge en cours…"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Recharge rapide"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index f9725f6..5cab1de 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Chargée à 100 %% dans <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - chargée à 100 %% dans <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – La recharge est en pause"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Recharge interrompue"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Recharge jusqu\'à <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Batterie en charge"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charge rapide"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 3ceb99c..52bdaf6 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> para completar a carga"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> para completar a carga)"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: a carga está en pausa"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g>: Carga en pausa"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g>: Cargando ata o <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Descoñecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rapidamente"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 1821546..5a875a7 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"પૂર્ણ ચાર્જ થવામાં <xliff:g id="TIME">%1$s</xliff:g> બાકી છે"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - પૂર્ણ ચાર્જ થવામાં <xliff:g id="TIME">%2$s</xliff:g> બાકી છે"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જિંગ થોભાવવામાં આવ્યું છે"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જિંગ થોભાવેલું છે"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> સુધી ચાર્જિંગ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"અજાણ્યું"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ચાર્જ થઈ રહ્યું છે"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ઝડપથી ચાર્જ થાય છે"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 4b899bb..0a38d16 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> में बैटरी पूरी चार्ज हो जाएगी"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> में बैटरी पूरी चार्ज हो जाएगी"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग को रोका गया है"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग रोकी गई है"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> तक चार्ज किया जा रहा है"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हो रही है"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"तेज़ चार्ज हो रही है"</string>
@@ -602,7 +601,7 @@
     <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"मिटाएं"</string>
     <string name="guest_exit_save_data_button" msgid="3690974510644963547">"सेव करें"</string>
     <string name="guest_exit_button" msgid="5774985819191803960">"मेहमान मोड से बाहर निकलें"</string>
-    <string name="guest_reset_button" msgid="2515069346223503479">"मेहमान मोड के सेशन को रीसेट करें?"</string>
+    <string name="guest_reset_button" msgid="2515069346223503479">"मेहमान मोड के सेशन को रीसेट करें"</string>
     <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"मेहमान मोड से बाहर निकलें"</string>
     <string name="guest_notification_ephemeral" msgid="7263252466950923871">"बाहर निकलने पर, सारी गतिविधि मिट जाएगी"</string>
     <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"बाहर निकलने पर, गतिविधि को मिटाया या सेव किया जा सकता है"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 46c8b90..03eba0f 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do napunjenosti"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do napunjenosti"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je pauzirano"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje pauzirano"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje do <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index d74490f..04a3ac6 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> a teljes töltöttségig"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> a teljes töltöttségig"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – A töltés szünetel"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Töltés szüneteltetve"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – Töltés eddig: <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ismeretlen"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Töltés"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Gyorstöltés"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index c26f94f..13f969c 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> մինչև լրիվ լիցքավորումը"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լրիվ լիցքավորումը"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորումը դադարեցված է"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորումը դադարեցվել է"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորում մինչև <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Անհայտ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Լիցքավորում"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Արագ լիցքավորում"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 203485f..650d50b 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> lagi sampai penuh"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi sampai penuh"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengisian daya dijeda"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengisian daya dijeda"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Mengisi daya sampai <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengisi daya"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengisi daya cepat"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 9185877..da576f5 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> fram að fullri hleðslu"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> fram að fullri hleðslu"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Hlé var gert á hleðslu"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Hlé gert á hleðslu"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - hleður upp að <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Óþekkt"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Í hleðslu"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hröð hleðsla"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 0cd9e1e..7042506 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> alla ricarica completa"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> alla ricarica completa"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ricarica in pausa"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ricarica in pausa"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ricarica fino a <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Sconosciuta"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"In carica"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ricarica veloce"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 6293105..143c99d 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g>‏ – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"הזמן הנותר לטעינה מלאה: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – הזמן הנותר לטעינה מלאה: <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – הטעינה הושהתה"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – הטעינה מושהית"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – טעינה עד מצב של <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"לא ידוע"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"בטעינה"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"הסוללה נטענת מהר"</string>
@@ -514,7 +513,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"לא רשום"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"לא זמין"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"‏כתובת ה-MAC אקראית"</string>
-    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{אין מכשירים מחוברים}=1{מכשיר אחד מחובר}two{# מכשירים מחוברים}many{# מכשירים מחוברים}other{# מכשירים מחוברים}}"</string>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{אין מכשירים מחוברים}=1{מכשיר אחד מחובר}one{# מכשירים מחוברים}two{# מכשירים מחוברים}other{# מכשירים מחוברים}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"יותר זמן."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"פחות זמן."</string>
     <string name="cancel" msgid="5665114069455378395">"ביטול"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 9a9d6ea..11a5258 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"完了まであと <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - 完了まであと <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電は一時停止中"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電を一時停止しています"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> まで充電"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"急速充電中"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 2fa2d80..d6c0f38 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"სრულ დატენვამდე დარჩენილია <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> — სრულ დატენვამდე დარჩენილია <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - დატენვა ᲨეᲩერებულია"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - დატენვა დაპაუზებულია"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – დატენვა შემდეგ ნიშნულამდე: <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"უცნობი"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"იტენება"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"სწრაფად იტენება"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index a332004..96f2595 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Толық зарядталғанға дейін <xliff:g id="TIME">%1$s</xliff:g> қалды."</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – толық зарядталғанға дейін <xliff:g id="TIME">%2$s</xliff:g> қалды."</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядтау кідіртілді."</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зарядтау кідіртілді"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> деңгейіне дейін зарядталады"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгісіз"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарядталуда"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Жылдам зарядталуда"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 2d78295..db4afdf 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> ទៀតទើបពេញ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - នៅសល់ <xliff:g id="TIME">%2$s</xliff:g> ទៀតទើបពេញ"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ការសាកថ្មត្រូវបានផ្អាក"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - បានផ្អាក​ការសាកថ្ម"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - កំពុង​សាកថ្ម​ឱ្យដល់កម្រិត <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"មិន​ស្គាល់"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"កំពុងសាក​ថ្ម"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"កំពុងសាកថ្មយ៉ាងឆាប់រហ័ស"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 5db4b16..74c2c74 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> - ಸಮಯದಲ್ಲಿ ಪೂರ್ತಿ ಚಾರ್ಜ್ ಆಗುತ್ತದೆ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ತಿ ಚಾರ್ಜ್ ಆಗುತ್ತದೆ"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ವಿರಾಮಗೊಂಡಿದೆ"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> ವರೆಗೆ ಚಾರ್ಜ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ಅಪರಿಚಿತ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ವೇಗದ ಚಾರ್ಜಿಂಗ್"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 30ab26c..7a8ce1a 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> 후 충전 완료"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> 후 충전 완료"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 일시중지됨"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 일시중지됨"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>까지 충전"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"알 수 없음"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"충전 중"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"고속 충전 중"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index baacfd7..534c94f 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> кийин толук кубатталат"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> кийин толук кубатталат"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Кубаттоо тындырылды"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Кубаттоо тындырылды"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> чейин кубаттоо"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгисиз"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Кубатталууда"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ыкчам кубатталууда"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 36755f7..f37a4ea 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ຍັງເຫຼືອອີກ <xliff:g id="TIME">%1$s</xliff:g> ຈຶ່ງຈະສາກເຕັມ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"ຍັງເຫຼືອອີກ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ຈຶ່ງຈະສາກເຕັມ"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ການສາກໄຟຖືກຢຸດໄວ້ຊົ່ວຄາວ"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - ຢຸດການສາກໄວ້ຊົ່ວຄາວແລ້ວ"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - ກຳລັງສາກຈົນເຖິງ <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ບໍ່ຮູ້ຈັກ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ກຳລັງສາກໄຟ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ກຳລັງສາກໄຟດ່ວນ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 50652ea..cdfa115 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Liko <xliff:g id="TIME">%1$s</xliff:g>, kol bus visiškai įkrauta"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – liko <xliff:g id="TIME">%2$s</xliff:g>, kol bus visiškai įkrauta"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – įkrovimas pristabdytas"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – įkrovimas pristabdytas"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – įkraunama iki <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nežinomas"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Kraunasi..."</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Greitai įkraunama"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index c199fea..a5ecfff 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> līdz pilnai uzlādei"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="TIME">%2$s</xliff:g> līdz pilnai uzlādei"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> — uzlāde ir pārtraukta"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Uzlāde ir apturēta"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g>. Tiks uzlādēts līdz <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nezināms"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Uzlāde"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Notiek ātrā uzlāde"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 957f68b..a41d953 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до полна батерија"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> до полна батерија"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Полнењето е паузирано"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Полнењето е паузирано"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Се полни на <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Се полни"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо полнење"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 1cc92ca..83b87f8 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"പൂർണ്ണമാകാൻ <xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - പൂർണ്ണമാകാൻ <xliff:g id="TIME">%2$s</xliff:g> ശേഷിക്കുന്നു"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ചാർജിംഗ് താൽക്കാലികമായി നിർത്തി"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - ചാർജ് ചെയ്യൽ താൽക്കാലികമായി നിർത്തി"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> വരെ ചാർജ് ചെയ്യുന്നു"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"അജ്ഞാതം"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ചാർജ് ചെയ്യുന്നു"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"അതിവേഗ ചാർജിംഗ്"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index a107c70..b77d8fb 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Дүүрэх хүртэл <xliff:g id="TIME">%1$s</xliff:g> үлдсэн"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - дүүрэх хүртэл <xliff:g id="TIME">%2$s</xliff:g> үлдсэн"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэхийг түр зогсоосон"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэлтийг түр зогсоосон"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> руу цэнэглэж байна"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Тодорхойгүй"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Цэнэглэж байна"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хурдан цэнэглэж байна"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index d5c83a8..9d91684 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"पूर्ण चार्ज होण्यासाठी <xliff:g id="TIME">%1$s</xliff:g> शिल्लक आहेत"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूर्ण चार्ज होण्यासाठी <xliff:g id="TIME">%2$s</xliff:g> शिल्लक आहे"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज करणे थांबवले आहे"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज करणे थांबवले"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> वर चार्ज करत आहे"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज होत आहे"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"वेगाने चार्ज होत आहे"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index ae8be8e..d302214 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> lagi sebelum penuh"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi sebelum penuh"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengecasan dijeda"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengecasan dijeda"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Mengecas kepada <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengecas"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengecas dgn cepat"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index f16cdca..1e26b08 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"အားပြည့်ရန် <xliff:g id="TIME">%1$s</xliff:g> လိုသည်"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"အားပြည့်ရန် <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> လိုသည်"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းခြင်းကို ခဏရပ်ထားသည်"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းမှု ခဏရပ်ထားသည်"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> အထိ အားသွင်းရန်"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"မသိ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"အားသွင်းနေပါသည်"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"အမြန် အားသွင်းနေသည်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index f7a10a6..be52597 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Fulladet om <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Fulladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ladingen er satt på pause"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ladingen er satt på pause"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – lader til <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukjent"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Lader"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Lader raskt"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index a966f15..4d2eb2e 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"पूरा चार्ज हुन <xliff:g id="TIME">%1$s</xliff:g> लाग्ने छ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूरा चार्ज हुन <xliff:g id="TIME">%2$s</xliff:g> लाग्ने छ"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज गर्ने प्रक्रिया रोकिएको छ"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज गर्ने प्रक्रिया पज गरिएको छ"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> सम्म चार्ज हुने छ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हुँदै छ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"द्रुत गतिमा चार्ज गरिँदै छ"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index c5406ee..898da11 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Vol over <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - vol over <xliff:g id="TIME">%2$s</xliff:g>"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Opladen is onderbroken"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Opladen onderbroken"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Opladen tot <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Opladen"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Snel opladen"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 37cc25e..6b6752f 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ପୂର୍ଣ୍ଣ ହେବାକୁ ଆଉ <xliff:g id="TIME">%1$s</xliff:g> ବାକି ଅଛି"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - ପୂର୍ଣ୍ଣ ହେବାକୁ ଆଉ <xliff:g id="TIME">%2$s</xliff:g> ବାକି ଅଛି"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ଚାର୍ଜିଂକୁ ବିରତ କରାଯାଇଛି"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - ଚାର୍ଜିଂ ବିରତ କରାଯାଇଛି"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> ପର୍ଯ୍ୟନ୍ତ ଚାର୍ଜ ହେଉଛି"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ଅଜ୍ଞାତ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ଚାର୍ଜ ହେଉଛି"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ଶୀଘ୍ର ଚାର୍ଜ ହେଉଛି"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 767cbc5..896b5ec 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ਬੈਟਰੀ ਪੂਰੀ ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਪੂਰੀ ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%2$s</xliff:g> ਬਾਕੀ"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਚਾਰਜਿੰਗ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਦੀ ਚਾਰਜਿੰਗ ਨੂੰ ਰੋਕਿਆ ਗਿਆ"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> ਤੱਕ ਚਾਰਜ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ਅਗਿਆਤ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 48bc82e..263e0d7 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do pełnego naładowania"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do pełnego naładowania"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – ładowanie zostało wstrzymane"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – wstrzymano ładowanie"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ładuję do <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Szybkie ładowanie"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 68a1c49..e107064 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até a conclusão"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a conclusão"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: o carregamento está pausado"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g>: carregamento pausado"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g>: carregando até <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregando rápido"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 87bab50..165ecc4 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até à carga máxima"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> até à carga máxima"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – O carregamento está pausado"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Carregamento em pausa"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – A carregar até <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"A carregar"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregamento rápido"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 68a1c49..e107064 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até a conclusão"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a conclusão"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: o carregamento está pausado"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g>: carregamento pausado"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g>: carregando até <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregando rápido"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index f81ef4c..44ec043 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> până la finalizare"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> până la finalizare"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Încărcarea este întreruptă"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Încărcarea s-a întrerupt"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – Se încarcă până la <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Necunoscut"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Se încarcă"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Se încarcă rapid"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 4dcee13..102cf1b 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до полной зарядки"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полной зарядки"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: зарядка приостановлена"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядка приостановлена"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – заряжается до <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Идет зарядка"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Быстрая зарядка"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 17c59ea..a8963af 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"සම්පූර්ණ වීමට <xliff:g id="TIME">%1$s</xliff:g>ක් ඉතිරියි"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - සම්පූර්ණ වීමට <xliff:g id="TIME">%2$s</xliff:g>ක් ඉතිරියි"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය විරාම කර ඇත"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය විරාම කළා"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> වෙත ආරෝපණය"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"නොදනී"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ආරෝපණය වෙමින්"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ශීඝ්‍ර ආරෝපණය"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index d9ce6cf..d7bdd89 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do úplného nabitia"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Nabíjanie je pozastavené"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – nabíjanie bolo pozastavené"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – nabíja sa na úroveň <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznáme"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíja sa"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Rýchle nabíjanie"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 8f9b059..6cb5b3c 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Še <xliff:g id="TIME">%1$s</xliff:g> do napolnjenosti"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – še <xliff:g id="TIME">%2$s</xliff:g> do napolnjenosti"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Polnjenje je začasno zaustavljeno"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – polnjenje je začasno zaustavljeno"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – polnjenje do <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznano"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Polnjenje"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hitro polnjenje"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index d467eea..54a8361 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> derisa të mbushet"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> derisa të mbushet"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Karikimi është vendosur në pauzë"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Karikimi në pauzë"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Po karikohet deri në <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"I panjohur"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Po karikohet"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Karikim i shpejtë"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 9cc43d9..d61938b 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до краја пуњења"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до краја пуњења"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Пуњење је заустављено"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Пуњење је паузирано"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – пуњење до <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Пуни се"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо се пуни"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 89deafa..3ba4e9b 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> kvar tills fulladdat"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kvar tills fulladdat"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Laddningen har pausats"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laddningen har pausats"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laddar till <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Okänd"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laddar"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Laddas snabbt"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 1fa5edd..c167697 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Zimesalia <xliff:g id="TIME">%1$s</xliff:g> ijae chaji"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> zimesalia ijae chaji"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Imesitisha kuchaji"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Imesitisha kuchaji"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Itachaji hadi <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Haijulikani"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Inachaji"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Inachaji kwa kasi"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 1b7b643..57661ba 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"முழுவதும் சார்ஜாக <xliff:g id="TIME">%1$s</xliff:g> ஆகும்"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழுவதும் சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - சார்ஜ் ஏறுவது இடைநிறுத்தப்பட்டுள்ளது"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - சார்ஜிங் இடைநிறுத்தப்பட்டது"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> வரை சார்ஜ் செய்யப்படும்"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"அறியப்படாத"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"சார்ஜ் ஆகிறது"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"வேகமாக சார்ஜாகிறது"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 27e9dbd..365a8f2 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జింగ్ పాజ్ చేయబడింది"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జింగ్ పాజ్ చేయబడింది"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> ఛార్జింగ్"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"తెలియదు"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ఛార్జ్ అవుతోంది"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"వేగవంతమైన ఛార్జింగ్"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 6b9c077..cea4dd2 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"อีก <xliff:g id="TIME">%1$s</xliff:g>จึงจะเต็ม"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - อีก <xliff:g id="TIME">%2$s</xliff:g> จึงจะเต็ม"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - การชาร์จหยุดชั่วคราว"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - หยุดชาร์จชั่วคราว"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - กำลังชาร์จจนถึง <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ไม่ทราบ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"กำลังชาร์จ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"กำลังชาร์จอย่างเร็ว"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index fddc1d0..e4503a0 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> na lang bago mapuno"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> na lang bago mapuno"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Naka-pause ang pag-charge"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Na-pause ang pag-charge"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - China-charge hanggang <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Hindi Kilala"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Nagcha-charge"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mabilis na charge"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 31dcedb8..eada480 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Tamamen şarj olmasına <xliff:g id="TIME">%1$s</xliff:g> kaldı"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tamamen şarj olmasına <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: Şarj işlemi duraklatıldı"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj işlemi duraklatıldı"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> seviyesine kadar şarj ediliyor"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Bilinmiyor"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Şarj oluyor"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hızlı şarj oluyor"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 2fdc227..8ab8db7 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до повного заряду"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Заряджання призупинено"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – заряджання призупинено"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Заряджання до <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Невідомо"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Заряджається"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Швидке заряджання"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 82dbdb3..ebc6edc 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"‎<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>‎"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"مکمل چارج ہونے میں <xliff:g id="TIME">%1$s</xliff:g> باقی ہے"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"مکمل چارج ہونے میں <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> باقی ہے"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - چارجنگ موقوف ہے"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - چارجنگ موقوف کی گئی"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> چارج کیا جائے گا"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"نامعلوم"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"چارج ہو رہا ہے"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"تیزی سے چارج ہو رہا ہے"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index ab997b6..19601dd 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Toʻlishiga <xliff:g id="TIME">%1$s</xliff:g> qoldi"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Toʻlishiga <xliff:g id="TIME">%2$s</xliff:g> qoldi"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Quvvatlash pauza qilindi"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Quvvatlash pauzada"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g>, <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> gacha quvvat oladi"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Noma’lum"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Quvvat olmoqda"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Tezkor quvvat olmoqda"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 44c820a5..f900480 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> nữa là pin đầy"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> nữa là pin đầy"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đã tạm dừng sạc"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đã tạm dừng sạc"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> – Sạc đến <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Không xác định"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Đang sạc"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Đang sạc nhanh"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index eae1c39..6909f22 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -141,7 +141,7 @@
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"取消"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"配对之后,所配对的设备将可以在建立连接后访问您的通讯录和通话记录。"</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"无法与“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”进行配对。"</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN 码或密钥不正确,因此无法与“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”配对。"</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN 码或通行密钥不正确,因此无法与“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”配对。"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"无法与“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”进行通信。"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> 已拒绝配对。"</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"计算机"</string>
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"还需<xliff:g id="TIME">%1$s</xliff:g>充满"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - 还需<xliff:g id="TIME">%2$s</xliff:g>充满"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充电已暂停"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - 已暂停充电"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在充到 <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"正在充电"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"正在快速充电"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 4371bb3..2d59d2d 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>後充滿電"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充滿電"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 已暫停充電"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - 已暫停充電"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在充電至 <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index d76a4a4..40b42fc 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>後充飽"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 已暫停充電"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - 已暫停充電"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在充電至 <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 4dc7651..a73f067 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -467,9 +467,8 @@
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> okusele kuze kugcwale"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> okusele kuze kugcwale"</string>
-    <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ukushaja kumisiwe okwesikhashana"</string>
-    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
-    <skip />
+    <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ukushaja kumiswe isikhashana"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ishaja ku-<xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Akwaziwa"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Iyashaja"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ishaja ngokushesha"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 1573edb..5610ac4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -17,6 +17,7 @@
 package com.android.settingslib;
 
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
+import static android.app.admin.DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
 import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
 
 import android.annotation.NonNull;
@@ -733,6 +734,26 @@
     }
 
     /**
+     * Checks whether MTE (Advanced memory protection) controls are disabled by the enterprise
+     * policy.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static EnforcedAdmin checkIfMteIsDisabled(Context context) {
+        final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        if (dpm.getMtePolicy() == MTE_NOT_CONTROLLED_BY_POLICY) {
+            return null;
+        }
+        EnforcedAdmin admin =
+                RestrictedLockUtils.getProfileOrDeviceOwner(
+                        context, UserHandle.of(UserHandle.USER_SYSTEM));
+        if (admin != null) {
+            return admin;
+        }
+        int profileId = getManagedProfileId(context, UserHandle.USER_SYSTEM);
+        return RestrictedLockUtils.getProfileOrDeviceOwner(context, UserHandle.of(profileId));
+    }
+
+    /**
      * Show restricted setting dialog.
      */
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
index 1745379..7516d2e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
@@ -28,6 +28,7 @@
 import android.hardware.biometrics.ParentalControlsUtilsInternal;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.text.TextUtils;
 
 /**
@@ -82,6 +83,12 @@
 
     private static boolean isFinancedDevice(Context context) {
         DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        // TODO(b/259908270): remove
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                DevicePolicyManager.ADD_ISFINANCED_DEVICE_FLAG,
+                DevicePolicyManager.ADD_ISFINANCED_FEVICE_DEFAULT)) {
+            return dpm.isFinancedDevice();
+        }
         return dpm.isDeviceManaged() && dpm.getDeviceOwnerType(
                 dpm.getDeviceOwnerComponentOnAnyUser()) == DEVICE_OWNER_TYPE_FINANCED;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index f4355c3..3e63052 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -161,10 +161,7 @@
      * @return {@code true} if successfully call, otherwise return {@code false}
      */
     public boolean connectDevice(MediaDevice connectDevice) {
-        MediaDevice device = null;
-        synchronized (mMediaDevicesLock) {
-            device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
-        }
+        MediaDevice device = getMediaDeviceById(connectDevice.getId());
         if (device == null) {
             Log.w(TAG, "connectDevice() connectDevice not in the list!");
             return false;
@@ -277,23 +274,6 @@
     /**
      * Find the MediaDevice through id.
      *
-     * @param devices the list of MediaDevice
-     * @param id the unique id of MediaDevice
-     * @return MediaDevice
-     */
-    public MediaDevice getMediaDeviceById(List<MediaDevice> devices, String id) {
-        for (MediaDevice mediaDevice : devices) {
-            if (TextUtils.equals(mediaDevice.getId(), id)) {
-                return mediaDevice;
-            }
-        }
-        Log.i(TAG, "getMediaDeviceById() can't found device");
-        return null;
-    }
-
-    /**
-     * Find the MediaDevice from all media devices by id.
-     *
      * @param id the unique id of MediaDevice
      * @return MediaDevice
      */
@@ -305,7 +285,7 @@
                 }
             }
         }
-        Log.i(TAG, "Unable to find device " + id);
+        Log.i(TAG, "getMediaDeviceById() failed to find device with id: " + id);
         return null;
     }
 
@@ -672,10 +652,7 @@
 
         @Override
         public void onConnectedDeviceChanged(String id) {
-            MediaDevice connectDevice = null;
-            synchronized (mMediaDevicesLock) {
-                connectDevice = getMediaDeviceById(mMediaDevices, id);
-            }
+            MediaDevice connectDevice = getMediaDeviceById(id);
             connectDevice = connectDevice != null
                     ? connectDevice : updateCurrentConnectedDevice();
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 24bb1bc..3ec0ba6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -206,8 +206,7 @@
         when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
         when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
 
-        final MediaDevice device = mLocalMediaManager
-                .getMediaDeviceById(mLocalMediaManager.mMediaDevices, TEST_DEVICE_ID_2);
+        MediaDevice device = mLocalMediaManager.getMediaDeviceById(TEST_DEVICE_ID_2);
 
         assertThat(device.getId()).isEqualTo(TEST_DEVICE_ID_2);
     }
@@ -222,8 +221,7 @@
         when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
         when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
 
-        final MediaDevice device = mLocalMediaManager
-                .getMediaDeviceById(mLocalMediaManager.mMediaDevices, TEST_CURRENT_DEVICE_ID);
+        MediaDevice device = mLocalMediaManager.getMediaDeviceById(TEST_CURRENT_DEVICE_ID);
 
         assertThat(device).isNull();
     }
@@ -238,12 +236,7 @@
         when(device1.getId()).thenReturn(null);
         when(device2.getId()).thenReturn(null);
 
-        MediaDevice device = mLocalMediaManager
-                .getMediaDeviceById(mLocalMediaManager.mMediaDevices, TEST_CURRENT_DEVICE_ID);
-
-        assertThat(device).isNull();
-
-        device = mLocalMediaManager.getMediaDeviceById(TEST_CURRENT_DEVICE_ID);
+        MediaDevice device = mLocalMediaManager.getMediaDeviceById(TEST_CURRENT_DEVICE_ID);
 
         assertThat(device).isNull();
     }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 48114ba1e..4365a9b 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -425,6 +425,7 @@
                     Settings.Global.RESTRICTED_NETWORKING_MODE,
                     Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT,
                     Settings.Global.SAFE_BOOT_DISALLOWED,
+                    Settings.Global.SECURE_FRP_MODE,
                     Settings.Global.SELINUX_STATUS,
                     Settings.Global.SELINUX_UPDATE_CONTENT_URL,
                     Settings.Global.SELINUX_UPDATE_METADATA_URL,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index dd58743..47794b8 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -322,7 +322,9 @@
     <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" />
     <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_COMPUTER" />
     <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION" />
+    <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING" />
     <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
+    <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_GLASSES" />
     <uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
 
     <uses-permission android:name="android.permission.MANAGE_APPOPS" />
@@ -781,80 +783,6 @@
     <!-- Permission required for CTS test - CtsHardwareTestCases -->
     <uses-permission android:name="android.permission.REMAP_MODIFIER_KEYS" />
 
-    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
-    <uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED" />
-    <uses-permission android:name="android.permission.health.READ_BASAL_BODY_TEMPERATURE" />
-    <uses-permission android:name="android.permission.health.READ_BASAL_METABOLIC_RATE" />
-    <uses-permission android:name="android.permission.health.READ_BLOOD_GLUCOSE" />
-    <uses-permission android:name="android.permission.health.READ_BLOOD_PRESSURE" />
-    <uses-permission android:name="android.permission.health.READ_BODY_FAT" />
-    <uses-permission android:name="android.permission.health.READ_BODY_TEMPERATURE" />
-    <uses-permission android:name="android.permission.health.READ_BODY_WATER_MASS" />
-    <uses-permission android:name="android.permission.health.READ_BONE_MASS" />
-    <uses-permission android:name="android.permission.health.READ_CERVICAL_MUCUS" />
-    <uses-permission android:name="android.permission.health.READ_DISTANCE" />
-    <uses-permission android:name="android.permission.health.READ_ELEVATION_GAINED" />
-    <uses-permission android:name="android.permission.health.READ_EXERCISE" />
-    <uses-permission android:name="android.permission.health.READ_FLOORS_CLIMBED" />
-    <uses-permission android:name="android.permission.health.READ_HEART_RATE" />
-    <uses-permission android:name="android.permission.health.READ_HEART_RATE_VARIABILITY" />
-    <uses-permission android:name="android.permission.health.READ_HEIGHT" />
-    <uses-permission android:name="android.permission.health.READ_HIP_CIRCUMFERENCE" />
-    <uses-permission android:name="android.permission.health.READ_HYDRATION" />
-    <uses-permission android:name="android.permission.health.READ_LEAN_BODY_MASS" />
-    <uses-permission android:name="android.permission.health.READ_MENSTRUATION" />
-    <uses-permission android:name="android.permission.health.READ_NUTRITION" />
-    <uses-permission android:name="android.permission.health.READ_OVULATION_TEST" />
-    <uses-permission android:name="android.permission.health.READ_OXYGEN_SATURATION" />
-    <uses-permission android:name="android.permission.health.READ_POWER" />
-    <uses-permission android:name="android.permission.health.READ_RESPIRATORY_RATE" />
-    <uses-permission android:name="android.permission.health.READ_RESTING_HEART_RATE" />
-    <uses-permission android:name="android.permission.health.READ_SEXUAL_ACTIVITY" />
-    <uses-permission android:name="android.permission.health.READ_SLEEP" />
-    <uses-permission android:name="android.permission.health.READ_SPEED" />
-    <uses-permission android:name="android.permission.health.READ_STEPS" />
-    <uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED" />
-    <uses-permission android:name="android.permission.health.READ_VO2_MAX" />
-    <uses-permission android:name="android.permission.health.READ_WAIST_CIRCUMFERENCE" />
-    <uses-permission android:name="android.permission.health.READ_WEIGHT" />
-    <uses-permission android:name="android.permission.health.READ_WHEELCHAIR_PUSHES" />
-    <uses-permission android:name="android.permission.health.WRITE_ACTIVE_CALORIES_BURNED" />
-    <uses-permission android:name="android.permission.health.WRITE_BASAL_BODY_TEMPERATURE" />
-    <uses-permission android:name="android.permission.health.WRITE_BASAL_METABOLIC_RATE" />
-    <uses-permission android:name="android.permission.health.WRITE_BLOOD_GLUCOSE" />
-    <uses-permission android:name="android.permission.health.WRITE_BLOOD_PRESSURE" />
-    <uses-permission android:name="android.permission.health.WRITE_BODY_FAT" />
-    <uses-permission android:name="android.permission.health.WRITE_BODY_TEMPERATURE" />
-    <uses-permission android:name="android.permission.health.WRITE_BODY_WATER_MASS" />
-    <uses-permission android:name="android.permission.health.WRITE_BONE_MASS" />
-    <uses-permission android:name="android.permission.health.WRITE_CERVICAL_MUCUS" />
-    <uses-permission android:name="android.permission.health.WRITE_DISTANCE" />
-    <uses-permission android:name="android.permission.health.WRITE_ELEVATION_GAINED" />
-    <uses-permission android:name="android.permission.health.WRITE_EXERCISE" />
-    <uses-permission android:name="android.permission.health.WRITE_FLOORS_CLIMBED" />
-    <uses-permission android:name="android.permission.health.WRITE_HEART_RATE" />
-    <uses-permission android:name="android.permission.health.WRITE_HEART_RATE_VARIABILITY" />
-    <uses-permission android:name="android.permission.health.WRITE_HEIGHT" />
-    <uses-permission android:name="android.permission.health.WRITE_HIP_CIRCUMFERENCE" />
-    <uses-permission android:name="android.permission.health.WRITE_HYDRATION" />
-    <uses-permission android:name="android.permission.health.WRITE_LEAN_BODY_MASS" />
-    <uses-permission android:name="android.permission.health.WRITE_MENSTRUATION" />
-    <uses-permission android:name="android.permission.health.WRITE_NUTRITION" />
-    <uses-permission android:name="android.permission.health.WRITE_OVULATION_TEST" />
-    <uses-permission android:name="android.permission.health.WRITE_OXYGEN_SATURATION" />
-    <uses-permission android:name="android.permission.health.WRITE_POWER" />
-    <uses-permission android:name="android.permission.health.WRITE_RESPIRATORY_RATE" />
-    <uses-permission android:name="android.permission.health.WRITE_RESTING_HEART_RATE" />
-    <uses-permission android:name="android.permission.health.WRITE_SEXUAL_ACTIVITY" />
-    <uses-permission android:name="android.permission.health.WRITE_SLEEP" />
-    <uses-permission android:name="android.permission.health.WRITE_SPEED" />
-    <uses-permission android:name="android.permission.health.WRITE_STEPS" />
-    <uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED" />
-    <uses-permission android:name="android.permission.health.WRITE_VO2_MAX" />
-    <uses-permission android:name="android.permission.health.WRITE_WAIST_CIRCUMFERENCE" />
-    <uses-permission android:name="android.permission.health.WRITE_WEIGHT" />
-    <uses-permission android:name="android.permission.health.WRITE_WHEELCHAIR_PUSHES" />
-
     <!-- Permission required for CTS test - ApplicationExemptionsTests -->
     <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS" />
 
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 6f7d20a..68679c79 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -455,7 +455,8 @@
         intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH, bugreportHash);
         intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_NONCE, nonce);
         intent.putExtra(EXTRA_BUGREPORT, bugreportFileName);
-        context.sendBroadcast(intent, android.Manifest.permission.DUMP);
+        context.sendBroadcastAsUser(intent, UserHandle.SYSTEM,
+                android.Manifest.permission.DUMP);
     }
 
     /**
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index a8216e8..6984b5a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -23,11 +23,6 @@
         xmlns:tools="http://schemas.android.com/tools"
         coreApp="true">
 
-    <!-- Using OpenGL ES 2.0 -->
-    <uses-feature
-        android:glEsVersion="0x00020000"
-        android:required="true" />
-
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 
     <!-- Used to read wallpaper -->
@@ -416,6 +411,7 @@
 
         <service android:name=".screenshot.ScreenshotCrossProfileService"
                  android:permission="com.android.systemui.permission.SELF"
+                 android:process=":screenshot_cross_profile"
                  android:exported="false" />
 
         <service android:name=".screenrecord.RecordingService" />
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index aaee42f..f92625b 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -33,6 +33,18 @@
       ]
     },
     {
+      // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
+      "name": "SystemUIGoogleBiometricsScreenshotTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
       // Permission indicators
       "name": "CtsPermission4TestCases",
       "options": [
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
index 93e78ac..8cd8bf6 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
@@ -21,9 +21,20 @@
 /** Controller that handles playing [RippleAnimation]. */
 class MultiRippleController(private val multipleRippleView: MultiRippleView) {
 
+    private val ripplesFinishedListeners = ArrayList<RipplesFinishedListener>()
+
     companion object {
         /** Max number of ripple animations at a time. */
         @VisibleForTesting const val MAX_RIPPLE_NUMBER = 10
+
+        interface RipplesFinishedListener {
+            /** Triggered when all the ripples finish running. */
+            fun onRipplesFinish()
+        }
+    }
+
+    fun addRipplesFinishedListener(listener: RipplesFinishedListener) {
+        ripplesFinishedListeners.add(listener)
     }
 
     /** Updates all the ripple colors during the animation. */
@@ -38,8 +49,13 @@
 
         multipleRippleView.ripples.add(rippleAnimation)
 
-        // Remove ripple once the animation is done
-        rippleAnimation.play { multipleRippleView.ripples.remove(rippleAnimation) }
+        rippleAnimation.play {
+            // Remove ripple once the animation is done
+            multipleRippleView.ripples.remove(rippleAnimation)
+            if (multipleRippleView.ripples.isEmpty()) {
+                ripplesFinishedListeners.forEach { listener -> listener.onRipplesFinish() }
+            }
+        }
 
         // Trigger drawing
         multipleRippleView.invalidate()
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
index b8dc223..550d2c6 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
@@ -33,21 +33,11 @@
 
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
     val ripples = ArrayList<RippleAnimation>()
-    private val listeners = ArrayList<RipplesFinishedListener>()
     private val ripplePaint = Paint()
     private var isWarningLogged = false
 
     companion object {
         private const val TAG = "MultiRippleView"
-
-        interface RipplesFinishedListener {
-            /** Triggered when all the ripples finish running. */
-            fun onRipplesFinish()
-        }
-    }
-
-    fun addRipplesFinishedListener(listener: RipplesFinishedListener) {
-        listeners.add(listener)
     }
 
     override fun onDraw(canvas: Canvas?) {
@@ -76,8 +66,6 @@
 
         if (shouldInvalidate) {
             invalidate()
-        } else { // Nothing is playing.
-            listeners.forEach { listener -> listener.onRipplesFinish() }
         }
     }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index c540f0f..e138ef8 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -20,6 +20,7 @@
 import android.icu.text.NumberFormat
 import android.util.TypedValue
 import android.view.LayoutInflater
+import android.view.View
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.customization.R
@@ -151,9 +152,15 @@
         view: AnimatableClockView,
     ) : DefaultClockFaceController(view) {
         override fun recomputePadding(targetRegion: Rect?) {
-            // Ignore Target Region until top padding fixed in aod
+            // We center the view within the targetRegion instead of within the parent
+            // view by computing the difference and adding that to the padding.
+            val parent = view.parent
+            val yDiff =
+                if (targetRegion != null && parent is View && parent.isLaidOut())
+                    targetRegion.centerY() - parent.height / 2f
+                else 0f
             val lp = view.getLayoutParams() as FrameLayout.LayoutParams
-            lp.topMargin = (-0.5f * view.bottom).toInt()
+            lp.topMargin = (-0.5f * view.bottom + yDiff).toInt()
             view.setLayoutParams(lp)
         }
 
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 218c5cc..b49afee 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -35,7 +35,6 @@
         android:visibility="invisible" />
     <FrameLayout
         android:id="@+id/lockscreen_clock_view_large"
-        android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:clipChildren="false"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
index e64b586..8497ff0 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
@@ -27,6 +27,7 @@
     android:layout_height="match_parent"
     android:clipChildren="false"
     android:clipToPadding="false"
+    android:paddingTop="@dimen/keyguard_lock_padding"
     android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
                                                   from this view when bouncer is shown -->
 
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index da485a9..cb704fd 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -61,25 +61,25 @@
     <!-- SIM messages --><skip />
     <!-- When the user inserts a sim card from an unsupported network, it becomes network locked -->
     <string name="keyguard_network_locked_message">Network locked</string>
-    <!-- Shown when there is no SIM card. -->
-    <string name="keyguard_missing_sim_message_short">No SIM card</string>
-    <!-- Shown to ask the user to insert a SIM card. -->
-    <string name="keyguard_missing_sim_instructions">Insert a SIM card.</string>
-    <!-- Shown to ask the user to insert a SIM card when sim is missing or not readable. -->
-    <string name="keyguard_missing_sim_instructions_long">The SIM card is missing or not readable. Insert a SIM card.</string>
-    <!-- Shown when SIM card is permanently disabled. -->
-    <string name="keyguard_permanent_disabled_sim_message_short">Unusable SIM card.</string>
-    <!-- Shown to inform the user to SIM card is permanently disabled. -->
-    <string name="keyguard_permanent_disabled_sim_instructions">Your SIM card has been permanently disabled.\n
-    Contact your wireless service provider for another SIM card.</string>
+    <!-- Shown when there is no SIM. -->
+    <string name="keyguard_missing_sim_message_short">No SIM</string>
+    <!-- Shown to ask the user to add a SIM. -->
+    <string name="keyguard_missing_sim_instructions">Add a SIM.</string>
+    <!-- Shown to ask the user to add a SIM when sim is missing or not readable. -->
+    <string name="keyguard_missing_sim_instructions_long">The SIM is missing or not readable. Add a SIM.</string>
+    <!-- Shown when SIM is permanently disabled. -->
+    <string name="keyguard_permanent_disabled_sim_message_short">Unusable SIM.</string>
+    <!-- Shown to inform the user to SIM is permanently deactivated. -->
+    <string name="keyguard_permanent_disabled_sim_instructions">Your SIM has been permanently deactivated.\n
+    Contact your wireless service provider for another SIM.</string>
     <!-- Shown to tell the user that their SIM is locked and they must unlock it. -->
-    <string name="keyguard_sim_locked_message">SIM card is locked.</string>
+    <string name="keyguard_sim_locked_message">SIM is locked.</string>
     <!-- When the user enters a wrong sim pin too many times, it becomes PUK locked (Pin Unlock Kode) -->
-    <string name="keyguard_sim_puk_locked_message">SIM card is PUK-locked.</string>
+    <string name="keyguard_sim_puk_locked_message">SIM is PUK-locked.</string>
     <!-- For the unlock screen, When the user enters a sim unlock code, it takes a little while to check
          whether it is valid, and to unlock the sim if it is valid.  we display a
          progress dialog in the meantime.  this is the emssage. -->
-    <string name="keyguard_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string>
+    <string name="keyguard_sim_unlock_progress_dialog_message">Unlocking SIM\u2026</string>
     <!-- Composes together the carrier name and the SIM card locked message. Example: CarrierName (SIM LOCKED) -->
     <string name="keyguard_carrier_name_with_sim_locked_template" translatable="false"><xliff:g id="carrier">%s</xliff:g> (<xliff:g id="message">%s</xliff:g>)</string>
 
@@ -139,8 +139,8 @@
     <string name="kg_puk_enter_pin_hint">Enter desired PIN code</string>
     <!-- Message shown when the user needs to confirm the PIN they just entered in the PUK screen -->
     <string name="kg_enter_confirm_pin_hint">Confirm desired PIN code</string>
-    <!-- Message shown in dialog while the device is unlocking the SIM card -->
-    <string name="kg_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string>
+    <!-- Message shown in dialog while the device is unlocking the SIM -->
+    <string name="kg_sim_unlock_progress_dialog_message">Unlocking SIM\u2026</string>
     <!-- Message shown when the user enters an invalid SIM pin password in PUK screen -->
     <string name="kg_invalid_sim_pin_hint">Type a PIN that is 4 to 8 numbers.</string>
     <!-- Message shown when the user enters an invalid PUK code in the PUK screen -->
diff --git a/packages/SystemUI/res-product/values/strings.xml b/packages/SystemUI/res-product/values/strings.xml
index b71caef..75c8286 100644
--- a/packages/SystemUI/res-product/values/strings.xml
+++ b/packages/SystemUI/res-product/values/strings.xml
@@ -28,10 +28,10 @@
     <!-- Message of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=NONE] -->
     <string name="inattentive_sleep_warning_message" product="default">The device will soon turn off; press to keep it on.</string>
 
-    <!-- Shown when there is no SIM card. -->
-    <string name="keyguard_missing_sim_message" product="tablet">No SIM card in tablet.</string>
-    <!-- Shown when there is no SIM card. -->
-    <string name="keyguard_missing_sim_message" product="default">No SIM card in phone.</string>
+    <!-- Shown when there is no SIM. -->
+    <string name="keyguard_missing_sim_message" product="tablet">No SIM in tablet.</string>
+    <!-- Shown when there is no SIM. -->
+    <string name="keyguard_missing_sim_message" product="default">No SIM in phone.</string>
 
     <!-- String shown in PUK screen when PIN codes don't match -->
     <string name="kg_invalid_confirm_pin_hint" product="default">PIN codes does not match</string>
diff --git a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml b/packages/SystemUI/res/drawable/controls_panel_background.xml
similarity index 82%
rename from packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
rename to packages/SystemUI/res/drawable/controls_panel_background.xml
index 1992c77..9092877 100644
--- a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
+++ b/packages/SystemUI/res/drawable/controls_panel_background.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
@@ -12,9 +13,10 @@
   ~ 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.
+  ~
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/dream_overlay_aqi_unknown" />
-    <corners android:radius="@dimen/dream_aqi_badge_corner_radius" />
+    <solid android:color="#1F1F1F" />
+    <corners android:radius="@dimen/notification_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 0e9abee..9134f96 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -102,6 +102,7 @@
         android:layout_margin="@dimen/overlay_border_width"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
+        app:layout_constraintHorizontal_bias="0"
         app:layout_constraintBottom_toBottomOf="@id/preview_border"
         app:layout_constraintStart_toStartOf="@id/preview_border"
         app:layout_constraintEnd_toEndOf="@id/preview_border"
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index 9efad22..ee3adba 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -90,7 +90,7 @@
       android:layout_weight="1"
       android:layout_marginLeft="@dimen/global_actions_side_margin"
       android:layout_marginRight="@dimen/global_actions_side_margin"
-      android:background="#ff0000"
+      android:background="@drawable/controls_panel_background"
       android:padding="@dimen/global_actions_side_margin"
       android:visibility="gone"
       />
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml b/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml
deleted file mode 100644
index fcebb8d..0000000
--- a/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-  ~ Copyright (C) 2022 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.
-  -->
-
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/aqi_view"
-    style="@style/clock_subtitle"
-    android:visibility="gone"
-    android:background="@drawable/dream_aqi_badge_bg"
-    android:paddingHorizontal="@dimen/dream_aqi_badge_padding_horizontal"
-    android:paddingVertical="@dimen/dream_aqi_badge_padding_vertical"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
deleted file mode 100644
index efbdd1a..0000000
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 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.
--->
-<TextClock
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/date_view"
-    style="@style/clock_subtitle"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:format12Hour="@string/dream_date_complication_date_format"
-    android:format24Hour="@string/dream_date_complication_date_format"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml b/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
deleted file mode 100644
index f05922f..0000000
--- a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 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.
--->
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/weather_view"
-    style="@style/clock_subtitle"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content" />
diff --git a/packages/SystemUI/res/layout/media_output_list_group_divider.xml b/packages/SystemUI/res/layout/media_output_list_group_divider.xml
new file mode 100644
index 0000000..5e96866
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_output_list_group_divider.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/device_container"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="36dp"
+        android:layout_gravity="center_vertical|start"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="56dp"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+        android:textSize="16sp"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 8842992..65983b7 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -100,6 +100,7 @@
         android:background="@drawable/overlay_preview_background"
         android:adjustViewBounds="true"
         android:clickable="true"
+        app:layout_constraintHorizontal_bias="0"
         app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
         app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
         app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"
diff --git a/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl b/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl
deleted file mode 100644
index e4b6e07..0000000
--- a/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl
+++ /dev/null
@@ -1,11 +0,0 @@
-precision mediump float;
-
-// The actual wallpaper texture.
-uniform sampler2D uTexture;
-
-varying vec2 vTextureCoordinates;
-
-void main() {
-    // gets the pixel value of the wallpaper for this uv coordinates on screen.
-    gl_FragColor = texture2D(uTexture, vTextureCoordinates);
-}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/image_wallpaper_vertex_shader.glsl b/packages/SystemUI/res/raw/image_wallpaper_vertex_shader.glsl
deleted file mode 100644
index 4393e2b..0000000
--- a/packages/SystemUI/res/raw/image_wallpaper_vertex_shader.glsl
+++ /dev/null
@@ -1,8 +0,0 @@
-attribute vec4 aPosition;
-attribute vec2 aTextureCoordinates;
-varying vec2 vTextureCoordinates;
-
-void main() {
-    vTextureCoordinates = aTextureCoordinates;
-    gl_Position = aPosition;
-}
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml b/packages/SystemUI/res/values-h411dp/dimens.xml
similarity index 74%
copy from packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
copy to packages/SystemUI/res/values-h411dp/dimens.xml
index 1992c77..6b21353 100644
--- a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
+++ b/packages/SystemUI/res/values-h411dp/dimens.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
@@ -13,8 +14,6 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/dream_overlay_aqi_unknown" />
-    <corners android:radius="@dimen/dream_aqi_badge_corner_radius" />
-</shape>
\ No newline at end of file
+<resources>
+    <dimen name="volume_row_slider_height">137dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-h700dp/dimens.xml b/packages/SystemUI/res/values-h700dp/dimens.xml
index 055308f..39777ab 100644
--- a/packages/SystemUI/res/values-h700dp/dimens.xml
+++ b/packages/SystemUI/res/values-h700dp/dimens.xml
@@ -17,4 +17,5 @@
 <resources>
     <!-- Margin above the ambient indication container -->
     <dimen name="ambient_indication_container_margin_top">15dp</dimen>
-</resources>
\ No newline at end of file
+    <dimen name="volume_row_slider_height">177dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml b/packages/SystemUI/res/values-h841dp/dimens.xml
similarity index 74%
copy from packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
copy to packages/SystemUI/res/values-h841dp/dimens.xml
index 1992c77..412da19 100644
--- a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
+++ b/packages/SystemUI/res/values-h841dp/dimens.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
@@ -13,8 +14,6 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/dream_overlay_aqi_unknown" />
-    <corners android:radius="@dimen/dream_aqi_badge_corner_radius" />
-</shape>
\ No newline at end of file
+<resources>
+    <dimen name="volume_row_slider_height">237dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 70d53c7..ba6977a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -246,15 +246,6 @@
 
     <color name="dream_overlay_camera_mic_off_dot_color">#FCBE03</color>
 
-    <!-- Air Quality -->
-    <color name="dream_overlay_aqi_good">#689F38</color>
-    <color name="dream_overlay_aqi_moderate">#FBC02D</color>
-    <color name="dream_overlay_aqi_unhealthy_sensitive">#F57C00</color>
-    <color name="dream_overlay_aqi_unhealthy">#C53929</color>
-    <color name="dream_overlay_aqi_very_unhealthy">#AD1457</color>
-    <color name="dream_overlay_aqi_hazardous">#880E4F</color>
-    <color name="dream_overlay_aqi_unknown">#BDC1C6</color>
-
     <!-- Dream overlay text shadows -->
     <color name="dream_overlay_clock_key_text_shadow_color">#4D000000</color>
     <color name="dream_overlay_clock_ambient_text_shadow_color">#4D000000</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 73baeac..ea51a89 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1583,10 +1583,6 @@
     <dimen name="dream_overlay_y_offset">80dp</dimen>
     <dimen name="dream_overlay_exit_y_offset">40dp</dimen>
 
-    <dimen name="dream_aqi_badge_corner_radius">28dp</dimen>
-    <dimen name="dream_aqi_badge_padding_vertical">6dp</dimen>
-    <dimen name="dream_aqi_badge_padding_horizontal">16dp</dimen>
-
     <dimen name="status_view_margin_horizontal">0dp</dimen>
 
     <!-- Media output broadcast dialog QR code picture size -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5d4fa58..ae1ff1a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2792,9 +2792,6 @@
     <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is unknown -->
     <string name="bt_le_audio_broadcast_dialog_unknown_name">Unknown</string>
 
-    <!-- Date format for the Dream Date Complication [CHAR LIMIT=NONE] -->
-    <string name="dream_date_complication_date_format">EEE, MMM d</string>
-
     <!-- Time format for the Dream Time Complication for 12-hour time format [CHAR LIMIT=NONE] -->
     <string name="dream_time_complication_12_hr_time_format">h:mm</string>
 
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index c32de70..38c1640 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -124,6 +124,11 @@
         </KeyFrameSet>
     </Transition>
 
+    <Transition
+        android:id="@+id/large_screen_header_transition"
+        app:constraintSetStart="@id/large_screen_header_constraint"
+        app:constraintSetEnd="@id/large_screen_header_constraint"/>
+
     <Include app:constraintSet="@xml/large_screen_shade_header"/>
 
     <Include app:constraintSet="@xml/qs_header"/>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
index e226d58..b057fe4 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
@@ -362,8 +362,7 @@
         nb.addAction(new Action.Builder(null, "Disable plugin", pi).build());
         mNotificationManager.notify(SystemMessage.NOTE_PLUGIN, nb.build());
         // TODO: Warn user.
-        Log.w(TAG, "Plugin has invalid interface version " + e.getActualVersion()
-                + ", expected " + e.getExpectedVersion());
+        Log.w(TAG, "Error loading plugin; " + e.getMessage());
     }
 
     /**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index 5883b6c..b927155 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -39,7 +39,6 @@
     private boolean mIsOrientationChanged;
     private SplitBounds mSplitBounds;
     private int mDesiredStagePosition;
-    private boolean mTaskbarInApp;
 
     public Matrix getMatrix() {
         return mMatrix;
@@ -58,10 +57,6 @@
         mDesiredStagePosition = desiredStagePosition;
     }
 
-    public void setTaskbarInApp(boolean taskbarInApp) {
-        mTaskbarInApp = taskbarInApp;
-    }
-
     /**
      * Updates the matrix based on the provided parameters
      */
@@ -79,34 +74,21 @@
         float scaledTaskbarSize;
         float canvasScreenRatio;
         if (mSplitBounds != null) {
-            float fullscreenTaskWidth;
-            float fullscreenTaskHeight;
-
-            float taskPercent;
             if (mSplitBounds.appsStackedVertically) {
-                taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
-                        ? mSplitBounds.topTaskPercent
-                        : (1 - (mSplitBounds.topTaskPercent + mSplitBounds.dividerHeightPercent));
-                // Scale portrait height to that of the actual screen
-                fullscreenTaskHeight = screenHeightPx * taskPercent;
-                if (mTaskbarInApp) {
-                    canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
+                if (mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+                    // Top app isn't cropped at all by taskbar
+                    canvasScreenRatio = 0;
                 } else {
-                    if (mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
-                        // Top app isn't cropped at all by taskbar
-                        canvasScreenRatio = 0;
-                    } else {
-                        // Same as fullscreen ratio
-                        canvasScreenRatio = (float) canvasWidth / screenWidthPx;
-                    }
+                    // Same as fullscreen ratio
+                    canvasScreenRatio = (float) canvasWidth / screenWidthPx;
                 }
             } else {
                 // For landscape, scale the width
-                taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
+                float taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
                         ? mSplitBounds.leftTaskPercent
                         : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
                 // Scale landscape width to that of actual screen
-                fullscreenTaskWidth = screenWidthPx * taskPercent;
+                float fullscreenTaskWidth = screenWidthPx * taskPercent;
                 canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
             }
         } else {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index e6aae9b..6ce84a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -78,6 +78,7 @@
     private int mCurrentClockSize = SMALL;
 
     private int mKeyguardSmallClockTopMargin = 0;
+    private int mKeyguardLargeClockTopMargin = 0;
     private final ClockRegistry.ClockChangeListener mClockChangedListener;
 
     private ViewGroup mStatusArea;
@@ -164,6 +165,8 @@
         mClockEventController.registerListeners(mView);
         mKeyguardSmallClockTopMargin =
                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
+        mKeyguardLargeClockTopMargin =
+                mView.getResources().getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
 
         if (mOnlyClock) {
             View ksv = mView.findViewById(R.id.keyguard_slice_view);
@@ -246,6 +249,8 @@
         mView.onDensityOrFontScaleChanged();
         mKeyguardSmallClockTopMargin =
                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
+        mKeyguardLargeClockTopMargin =
+                mView.getResources().getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
         mView.updateClockTargetRegions();
     }
 
@@ -324,10 +329,18 @@
         }
 
         if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
+            // This gets the expected clock bottom if mLargeClockFrame had a top margin, but it's
+            // top margin only contributed to height and didn't move the top of the view (as this
+            // was the computation previously). As we no longer have a margin, we add this back
+            // into the computation manually.
             int frameHeight = mLargeClockFrame.getHeight();
             int clockHeight = clock.getLargeClock().getView().getHeight();
-            return frameHeight / 2 + clockHeight / 2;
+            return frameHeight / 2 + clockHeight / 2 + mKeyguardLargeClockTopMargin / -2;
         } else {
+            // This is only called if we've never shown the large clock as the frame is inflated
+            // with 'gone', but then the visibility is never set when it is animated away by
+            // KeyguardClockSwitch, instead it is removed from the view hierarchy.
+            // TODO(b/261755021): Cleanup Large Frame Visibility
             int clockHeight = clock.getSmallClock().getView().getHeight();
             return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
         }
@@ -345,11 +358,15 @@
         if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
             return clock.getLargeClock().getView().getHeight();
         } else {
+            // Is not called except in certain edge cases, see comment in getClockBottom
+            // TODO(b/261755021): Cleanup Large Frame Visibility
             return clock.getSmallClock().getView().getHeight();
         }
     }
 
     boolean isClockTopAligned() {
+        // Returns false except certain edge cases, see comment in getClockBottom
+        // TODO(b/261755021): Cleanup Large Frame Visibility
         return mLargeClockFrame.getVisibility() != View.VISIBLE;
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 53b569a..d1c9a30 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -142,7 +142,8 @@
     }
 
     public void startAppearAnimation() {
-        if (TextUtils.isEmpty(mMessageAreaController.getMessage())) {
+        if (TextUtils.isEmpty(mMessageAreaController.getMessage())
+                && getInitialMessageResId() != 0) {
             mMessageAreaController.setMessage(
                     mView.getResources().getString(getInitialMessageResId()),
                     /* animate= */ false);
@@ -165,9 +166,7 @@
     }
 
     /** Determines the message to show in the bouncer when it first appears. */
-    protected int getInitialMessageResId() {
-        return 0;
-    }
+    protected abstract int getInitialMessageResId();
 
     /** Factory for a {@link KeyguardInputViewController}. */
     public static class Factory {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index f7423ed..8011efd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -139,4 +139,9 @@
         super.startErrorAnimation();
         mView.startErrorAnimation();
     }
+
+    @Override
+    protected int getInitialMessageResId() {
+        return R.string.keyguard_enter_your_pin;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index f51ac32..35b2db2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -74,9 +74,4 @@
         return mView.startDisappearAnimation(
                 mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
     }
-
-    @Override
-    protected int getInitialMessageResId() {
-        return R.string.keyguard_enter_your_pin;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index a5c8c78..39b567f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -156,5 +156,10 @@
         @Override
         public void onStartingToHide() {
         }
+
+        @Override
+        protected int getInitialMessageResId() {
+            return 0;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 84ef505..3a592a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1644,7 +1644,7 @@
 
                 @Override
                 public void onAuthenticationFailed() {
-                    requestActiveUnlock(
+                    requestActiveUnlockDismissKeyguard(
                             ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
                             "fingerprintFailure");
                     handleFingerprintAuthFailed();
@@ -2576,6 +2576,18 @@
     }
 
     /**
+     * Attempts to trigger active unlock from trust agent with a request to dismiss the keyguard.
+     */
+    public void requestActiveUnlockDismissKeyguard(
+            @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+            String extraReason
+    ) {
+        requestActiveUnlock(
+                requestOrigin,
+                extraReason + "-dismissKeyguard", true);
+    }
+
+    /**
      * Whether the UDFPS bouncer is showing.
      */
     public void setUdfpsBouncerShowing(boolean showing) {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 9e58500..886d110 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -54,35 +54,6 @@
         buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" })
     }
 
-    // TODO: remove after b/237743330 is fixed
-    fun logStatusBarCalculatedAlpha(alpha: Float) {
-        buffer.log(TAG, DEBUG, { double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
-    }
-
-    // TODO: remove after b/237743330 is fixed
-    fun logStatusBarExplicitAlpha(alpha: Float) {
-        buffer.log(
-            TAG,
-            DEBUG,
-            { double1 = alpha.toDouble() },
-            { "new mExplicitAlpha value: $double1" }
-        )
-    }
-
-    // TODO: remove after b/237743330 is fixed
-    fun logStatusBarAlphaVisibility(visibility: Int, alpha: Float, state: String) {
-        buffer.log(
-            TAG,
-            DEBUG,
-            {
-                int1 = visibility
-                double1 = alpha.toDouble()
-                str1 = state
-            },
-            { "changing visibility to $int1 with alpha $double1 in state: $str1" }
-        )
-    }
-
     @JvmOverloads
     fun logBiometricMessage(
         @CompileTimeConstant context: String,
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 632fcdc..0fc9ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -22,6 +22,8 @@
 import android.os.HandlerThread;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.WMComponent;
@@ -53,6 +55,7 @@
         mContext = context;
     }
 
+    @Nullable
     protected abstract GlobalRootComponent.Builder getGlobalRootComponentBuilder();
 
     /**
@@ -69,6 +72,11 @@
      * Starts the initialization process. This stands up the Dagger graph.
      */
     public void init(boolean fromTest) throws ExecutionException, InterruptedException {
+        GlobalRootComponent.Builder globalBuilder = getGlobalRootComponentBuilder();
+        if (globalBuilder == null) {
+            return;
+        }
+
         mRootComponent = getGlobalRootComponentBuilder()
                 .context(mContext)
                 .instrumentationTest(fromTest)
@@ -119,6 +127,7 @@
                     .setBackAnimation(Optional.ofNullable(null))
                     .setDesktopMode(Optional.ofNullable(null));
         }
+
         mSysUIComponent = builder.build();
         if (initializeComponents) {
             mSysUIComponent.init();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
index 8aa3040..55c095b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui
 
+import android.app.Application
 import android.content.Context
 import com.android.systemui.dagger.DaggerReferenceGlobalRootComponent
 import com.android.systemui.dagger.GlobalRootComponent
@@ -24,7 +25,17 @@
  * {@link SystemUIInitializer} that stands up AOSP SystemUI.
  */
 class SystemUIInitializerImpl(context: Context) : SystemUIInitializer(context) {
-    override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder {
-        return DaggerReferenceGlobalRootComponent.builder()
+
+    override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder? {
+        return when (Application.getProcessName()) {
+            SCREENSHOT_CROSS_PROFILE_PROCESS -> null
+            else -> DaggerReferenceGlobalRootComponent.builder()
+        }
+    }
+
+    companion object {
+        private const val SYSTEMUI_PROCESS = "com.android.systemui"
+        private const val SCREENSHOT_CROSS_PROFILE_PROCESS =
+                "$SYSTEMUI_PROCESS:screenshot_cross_profile"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 6785a43..9708d9a 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -64,7 +64,7 @@
 @SysUISingleton
 public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController,
         AppOpsManager.OnOpActiveChangedListener,
-        AppOpsManager.OnOpNotedListener, IndividualSensorPrivacyController.Callback,
+        AppOpsManager.OnOpNotedInternalListener, IndividualSensorPrivacyController.Callback,
         Dumpable {
 
     // This is the minimum time that we will keep AppOps that are noted on record. If multiple
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
deleted file mode 100644
index 794eba4..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "presubmit": [
-    {
-      // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
-      "name": "SystemUIGoogleBiometricsScreenshotTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
-    }
-  ]
-}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index e2ef247..58d40d3 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -28,7 +28,6 @@
 import android.os.UserHandle
 import android.util.Log
 import android.view.WindowManager
-import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.ActivityIntentHelper
 import com.android.systemui.dagger.qualifiers.Main
@@ -83,7 +82,7 @@
      */
     fun launchCamera(source: Int) {
         val intent: Intent = getStartCameraIntent()
-        intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source)
+        intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
         val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
             intent, KeyguardUpdateMonitor.getCurrentUser()
         )
@@ -149,9 +148,4 @@
             cameraIntents.getInsecureCameraIntent()
         }
     }
-
-    companion object {
-        @VisibleForTesting
-        const val EXTRA_CAMERA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
index f8a2002..867faf9 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -29,6 +29,7 @@
                 MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
         val DEFAULT_INSECURE_CAMERA_INTENT_ACTION =
                 MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+        const val EXTRA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
 
         @JvmStatic
         fun getOverrideCameraPackage(context: Context): String? {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
index 4aa597e..8d0edf8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
@@ -50,7 +50,12 @@
      * Setup an activity to handle enter/exit animations. [view] should be the root of the content.
      * Fade and translate together.
      */
-    fun observerForAnimations(view: ViewGroup, window: Window, intent: Intent): LifecycleObserver {
+    fun observerForAnimations(
+            view: ViewGroup,
+            window: Window,
+            intent: Intent,
+            animateY: Boolean = true
+    ): LifecycleObserver {
         return object : LifecycleObserver {
             var showAnimation = intent.getBooleanExtra(ControlsUiController.EXTRA_ANIMATE, false)
 
@@ -61,8 +66,12 @@
                 view.transitionAlpha = 0.0f
 
                 if (translationY == -1f) {
-                    translationY = view.context.resources.getDimensionPixelSize(
-                        R.dimen.global_actions_controls_y_translation).toFloat()
+                    if (animateY) {
+                        translationY = view.context.resources.getDimensionPixelSize(
+                                R.dimen.global_actions_controls_y_translation).toFloat()
+                    } else {
+                        translationY = 0f
+                    }
                 }
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 5d611c4..d8d8c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -70,7 +70,8 @@
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.control_detail_root),
                 window,
-                intent
+                intent,
+                !featureFlags.isEnabled(Flags.USE_APP_PANELS)
             )
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index fb678aa..1e3e5cd 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -186,7 +186,7 @@
         val allStructures = controlsController.get().getFavorites()
         val selected = getPreferredSelectedItem(allStructures)
         val anyPanels = controlsListingController.get().getCurrentServices()
-                .none { it.panelActivity != null }
+                .any { it.panelActivity != null }
 
         return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
             ControlsActivity::class.java
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index 7143be2..f5764c2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -24,6 +24,10 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
+import com.android.systemui.R
 import com.android.systemui.util.boundsOnScreen
 import com.android.wm.shell.TaskView
 import java.util.concurrent.Executor
@@ -64,6 +68,16 @@
                 options.taskAlwaysOnTop = true
 
                 taskView.post {
+                    val roundedCorner =
+                        activityContext.resources.getDimensionPixelSize(
+                            R.dimen.notification_corner_radius
+                        )
+                    val radii = FloatArray(8) { roundedCorner.toFloat() }
+                    taskView.background =
+                        ShapeDrawable(RoundRectShape(radii, null, null)).apply {
+                            setTint(Color.TRANSPARENT)
+                        }
+                    taskView.clipToOutline = true
                     taskView.startActivity(
                         pendingIntent,
                         fillInIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 3a59f4b..2465286 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -40,6 +40,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.dagger.DemoModeModule;
 import com.android.systemui.doze.dagger.DozeComponent;
+import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
 import com.android.systemui.dreams.dagger.DreamModule;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
@@ -163,6 +164,7 @@
         },
         subcomponents = {
             CentralSurfacesComponent.class,
+            ComplicationComponent.class,
             NavigationBarComponent.class,
             NotificationRowComponent.class,
             DozeComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt
new file mode 100644
index 0000000..8325356
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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.systemui.dreams
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import javax.inject.Inject
+
+/**
+ * {@link DreamOverlayLifecycleOwner} is a concrete implementation of {@link LifecycleOwner}, which
+ * provides access to an associated {@link LifecycleRegistry}.
+ */
+class DreamOverlayLifecycleOwner @Inject constructor() : LifecycleOwner {
+    val registry: LifecycleRegistry = LifecycleRegistry(this)
+
+    override fun getLifecycle(): Lifecycle {
+        return registry
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
index d145f5c..87c5f51 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.dreams;
 
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_SERVICE_COMPONENT;
+
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -35,6 +37,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * {@link DreamOverlayRegistrant} is responsible for telling system server that SystemUI should be
@@ -98,12 +101,13 @@
     }
 
     @Inject
-    public DreamOverlayRegistrant(Context context, @Main Resources resources) {
+    public DreamOverlayRegistrant(Context context, @Main Resources resources,
+            @Named(DREAM_OVERLAY_SERVICE_COMPONENT) ComponentName dreamOverlayServiceComponent) {
         mContext = context;
         mResources = resources;
         mDreamManager = IDreamManager.Stub.asInterface(
                 ServiceManager.getService(DreamService.DREAM_SERVICE));
-        mOverlayServiceComponent = new ComponentName(mContext, DreamOverlayService.class);
+        mOverlayServiceComponent = dreamOverlayServiceComponent;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index e76d5b3..16b4f99 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -30,6 +30,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
 import androidx.lifecycle.ViewModelStore;
 
@@ -41,9 +42,13 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
+import com.android.systemui.touch.TouchInsetManager;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
@@ -80,8 +85,14 @@
     // True if the service has been destroyed.
     private boolean mDestroyed = false;
 
+    private final ComplicationComponent mComplicationComponent;
+
+    private final com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent
+            mDreamComplicationComponent;
+
     private final DreamOverlayComponent mDreamOverlayComponent;
 
+    private final DreamOverlayLifecycleOwner mLifecycleOwner;
     private final LifecycleRegistry mLifecycleRegistry;
 
     private DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
@@ -127,11 +138,16 @@
     public DreamOverlayService(
             Context context,
             @Main Executor executor,
+            DreamOverlayLifecycleOwner lifecycleOwner,
             WindowManager windowManager,
+            ComplicationComponent.Factory complicationComponentFactory,
+            com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent.Factory
+                    dreamComplicationComponentFactory,
             DreamOverlayComponent.Factory dreamOverlayComponentFactory,
             DreamOverlayStateController stateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             UiEventLogger uiEventLogger,
+            TouchInsetManager touchInsetManager,
             @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
                     ComponentName lowLightDreamComponent) {
         mContext = context;
@@ -146,8 +162,17 @@
         final ViewModelStore viewModelStore = new ViewModelStore();
         final Complication.Host host =
                 () -> mExecutor.execute(DreamOverlayService.this::requestExit);
-        mDreamOverlayComponent = dreamOverlayComponentFactory.create(viewModelStore, host);
-        mLifecycleRegistry = mDreamOverlayComponent.getLifecycleRegistry();
+
+        mComplicationComponent = complicationComponentFactory.create(lifecycleOwner, host,
+                viewModelStore, touchInsetManager);
+        mDreamComplicationComponent = dreamComplicationComponentFactory.create(
+                mComplicationComponent.getVisibilityController(), touchInsetManager);
+        mDreamOverlayComponent = dreamOverlayComponentFactory.create(lifecycleOwner,
+                mComplicationComponent.getComplicationHostViewController(), touchInsetManager,
+                new HashSet<>(Arrays.asList(
+                        mDreamComplicationComponent.getHideComplicationTouchHandler())));
+        mLifecycleOwner = lifecycleOwner;
+        mLifecycleRegistry = mLifecycleOwner.getRegistry();
 
         mExecutor.execute(() -> setCurrentStateLocked(Lifecycle.State.CREATED));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 5f942b6..ccfdd096 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.dreams;
 
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_ENABLED;
+
 import android.service.dreams.DreamService;
 import android.util.Log;
 
@@ -37,6 +39,7 @@
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * {@link DreamOverlayStateController} is the source of truth for Dream overlay configurations and
@@ -83,6 +86,7 @@
     }
 
     private final Executor mExecutor;
+    private final boolean mOverlayEnabled;
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
 
     @Complication.ComplicationType
@@ -94,14 +98,27 @@
 
     @VisibleForTesting
     @Inject
-    public DreamOverlayStateController(@Main Executor executor) {
+    public DreamOverlayStateController(@Main Executor executor,
+            @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled) {
         mExecutor = executor;
+        mOverlayEnabled = overlayEnabled;
+        if (DEBUG) {
+            Log.d(TAG, "Dream overlay enabled:" + mOverlayEnabled);
+        }
     }
 
     /**
      * Adds a complication to be included on the dream overlay.
      */
     public void addComplication(Complication complication) {
+        if (!mOverlayEnabled) {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "Ignoring adding complication due to overlay disabled:" + complication);
+            }
+            return;
+        }
+
         mExecutor.execute(() -> {
             if (mComplications.add(complication)) {
                 if (DEBUG) {
@@ -116,6 +133,14 @@
      * Removes a complication from inclusion on the dream overlay.
      */
     public void removeComplication(Complication complication) {
+        if (!mOverlayEnabled) {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "Ignoring removing complication due to overlay disabled:" + complication);
+            }
+            return;
+        }
+
         mExecutor.execute(() -> {
             if (mComplications.remove(complication)) {
                 if (DEBUG) {
@@ -193,7 +218,7 @@
      * @return {@code true} if overlay is active, {@code false} otherwise.
      */
     public boolean isOverlayActive() {
-        return containsState(STATE_DREAM_OVERLAY_ACTIVE);
+        return mOverlayEnabled && containsState(STATE_DREAM_OVERLAY_ACTIVE);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index 100ccc3..a2e11b2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -138,19 +138,27 @@
                     final ComplicationId id = complication.getId();
                     final Complication.ViewHolder viewHolder = complication.getComplication()
                             .createView(complication);
+
+                    final View view = viewHolder.getView();
+
+                    if (view == null) {
+                        Log.e(TAG, "invalid complication view. null view supplied by ViewHolder");
+                        return;
+                    }
+
                     // Complications to be added before dream entry animations are finished are set
                     // to invisible and are animated in.
                     if (!mEntryAnimationsFinished) {
-                        viewHolder.getView().setVisibility(View.INVISIBLE);
+                        view.setVisibility(View.INVISIBLE);
                     }
                     mComplications.put(id, viewHolder);
-                    if (viewHolder.getView().getParent() != null) {
+                    if (view.getParent() != null) {
                         Log.e(TAG, "View for complication "
                                 + complication.getComplication().getClass()
                                 + " already has a parent. Make sure not to reuse complication "
                                 + "views!");
                     }
-                    mLayoutEngine.addComplication(id, viewHolder.getView(),
+                    mLayoutEngine.addComplication(id, view,
                             viewHolder.getLayoutParams(), viewHolder.getCategory());
                 });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 46ce7a9..3e9b010 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -30,7 +30,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position;
-import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.dreams.complication.dagger.ComplicationModule;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.touch.TouchInsetManager;
 
@@ -50,7 +50,7 @@
  * their layout parameters and attributes. The management of this set is done by
  * {@link ComplicationHostViewController}.
  */
-@DreamOverlayComponent.DreamOverlayScope
+@ComplicationModule.ComplicationScope
 public class ComplicationLayoutEngine implements Complication.VisibilityController {
     public static final String TAG = "ComplicationLayoutEng";
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index ee00512..1065b94 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -136,8 +136,15 @@
             final boolean hasFavorites = mControlsComponent.getControlsController()
                     .map(c -> !c.getFavorites().isEmpty())
                     .orElse(false);
+            boolean hasPanels = false;
+            for (int i = 0; i < controlsServices.size(); i++) {
+                if (controlsServices.get(i).getPanelActivity() != null) {
+                    hasPanels = true;
+                    break;
+                }
+            }
             final ControlsComponent.Visibility visibility = mControlsComponent.getVisibility();
-            return hasFavorites && visibility != UNAVAILABLE;
+            return (hasFavorites || hasPanels) && visibility != UNAVAILABLE;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
new file mode 100644
index 0000000..8d133bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
@@ -0,0 +1,29 @@
+package com.android.systemui.dreams.complication.dagger
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ViewModelStore
+import com.android.systemui.dreams.complication.Complication
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.dreams.complication.ComplicationLayoutEngine
+import com.android.systemui.touch.TouchInsetManager
+import dagger.BindsInstance
+import dagger.Subcomponent
+
+@Subcomponent(modules = [ComplicationModule::class])
+@ComplicationModule.ComplicationScope
+interface ComplicationComponent {
+    /** Factory for generating [ComplicationComponent]. */
+    @Subcomponent.Factory
+    interface Factory {
+        fun create(
+            @BindsInstance lifecycleOwner: LifecycleOwner,
+            @BindsInstance host: Complication.Host,
+            @BindsInstance viewModelStore: ViewModelStore,
+            @BindsInstance touchInsetManager: TouchInsetManager
+        ): ComplicationComponent
+    }
+
+    fun getComplicationHostViewController(): ComplicationHostViewController
+
+    fun getVisibilityController(): ComplicationLayoutEngine
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
index 09cc7c5..797906f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
@@ -24,13 +24,12 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.dagger.DreamOverlayComponent;
-
-import javax.inject.Named;
 
 import dagger.Module;
 import dagger.Provides;
 
+import javax.inject.Named;
+
 /**
  * Module for providing a scoped host view.
  */
@@ -49,7 +48,7 @@
      */
     @Provides
     @Named(SCOPED_COMPLICATIONS_LAYOUT)
-    @DreamOverlayComponent.DreamOverlayScope
+    @ComplicationModule.ComplicationScope
     static ConstraintLayout providesComplicationHostView(
             LayoutInflater layoutInflater) {
         return Preconditions.checkNotNull((ConstraintLayout)
@@ -60,7 +59,6 @@
 
     @Provides
     @Named(COMPLICATION_MARGIN_DEFAULT)
-    @DreamOverlayComponent.DreamOverlayScope
     static int providesComplicationPadding(@Main Resources resources) {
         return resources.getDimensionPixelSize(R.dimen.dream_overlay_complication_margin);
     }
@@ -70,7 +68,6 @@
      */
     @Provides
     @Named(COMPLICATIONS_FADE_OUT_DURATION)
-    @DreamOverlayComponent.DreamOverlayScope
     static int providesComplicationsFadeOutDuration(@Main Resources resources) {
         return resources.getInteger(R.integer.complicationFadeOutMs);
     }
@@ -80,7 +77,6 @@
      */
     @Provides
     @Named(COMPLICATIONS_FADE_OUT_DELAY)
-    @DreamOverlayComponent.DreamOverlayScope
     static int providesComplicationsFadeOutDelay(@Main Resources resources) {
         return resources.getInteger(R.integer.complicationFadeOutDelayMs);
     }
@@ -90,7 +86,6 @@
      */
     @Provides
     @Named(COMPLICATIONS_FADE_IN_DURATION)
-    @DreamOverlayComponent.DreamOverlayScope
     static int providesComplicationsFadeInDuration(@Main Resources resources) {
         return resources.getInteger(R.integer.complicationFadeInMs);
     }
@@ -100,7 +95,6 @@
      */
     @Provides
     @Named(COMPLICATIONS_RESTORE_TIMEOUT)
-    @DreamOverlayComponent.DreamOverlayScope
     static int providesComplicationsRestoreTimeout(@Main Resources resources) {
         return resources.getInteger(R.integer.complicationRestoreMs);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
index 5c2fdf5..dbf5ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
@@ -24,16 +24,16 @@
 import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.dreams.complication.ComplicationCollectionViewModel;
 import com.android.systemui.dreams.complication.ComplicationLayoutEngine;
+import com.android.systemui.touch.TouchInsetManager;
+
+import dagger.Module;
+import dagger.Provides;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 
 import javax.inject.Named;
 import javax.inject.Scope;
-
-import dagger.Module;
-import dagger.Provides;
-
 /**
  * Module for housing components related to rendering complications.
  */
@@ -73,4 +73,13 @@
             ComplicationLayoutEngine engine) {
         return engine;
     }
+
+    /**
+     * Provides a new touch inset session instance for complication logic.
+     */
+    @Provides
+    static TouchInsetManager.TouchInsetSession providesTouchInsetSession(
+            TouchInsetManager manager) {
+        return manager.createSession();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 101f4a4..8770cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.dreams.dagger;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 
 import com.android.dream.lowlight.dagger.LowLightDreamModule;
@@ -24,14 +26,17 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayNotificationCountProvider;
+import com.android.systemui.dreams.DreamOverlayService;
 import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule;
+import com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent;
+
+import dagger.Module;
+import dagger.Provides;
 
 import java.util.Optional;
 
 import javax.inject.Named;
 
-import dagger.Module;
-import dagger.Provides;
 
 /**
  * Dagger Module providing Dream-related functionality.
@@ -41,14 +46,40 @@
             LowLightDreamModule.class,
         },
         subcomponents = {
+            ComplicationComponent.class,
             DreamOverlayComponent.class,
         })
 public interface DreamModule {
     String DREAM_ONLY_ENABLED_FOR_DOCK_USER = "dream_only_enabled_for_dock_user";
+    String DREAM_OVERLAY_SERVICE_COMPONENT = "dream_overlay_service_component";
+    String DREAM_OVERLAY_ENABLED = "dream_overlay_enabled";
 
     String DREAM_SUPPORTED = "dream_supported";
 
     /**
+     * Provides the dream component
+     */
+    @Provides
+    @Named(DREAM_OVERLAY_SERVICE_COMPONENT)
+    static ComponentName providesDreamOverlayService(Context context) {
+        return new ComponentName(context, DreamOverlayService.class);
+    }
+
+    /**
+     * Provides whether dream overlay is enabled.
+     */
+    @Provides
+    @Named(DREAM_OVERLAY_ENABLED)
+    static Boolean providesDreamOverlayEnabled(PackageManager packageManager,
+            @Named(DREAM_OVERLAY_SERVICE_COMPONENT) ComponentName component) {
+        try {
+            return packageManager.getServiceInfo(component, PackageManager.GET_META_DATA).enabled;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
      * Provides an instance of the dream backend.
      */
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
index f927ba6..0332f88 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -16,41 +16,49 @@
 
 package com.android.systemui.dreams.dagger;
 
+import static com.android.systemui.dreams.dagger.DreamOverlayModule.DREAM_TOUCH_HANDLERS;
+
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
+import android.annotation.Nullable;
+
 import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
-import androidx.lifecycle.ViewModelStore;
 
 import com.android.systemui.dreams.DreamOverlayContainerViewController;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.complication.dagger.ComplicationModule;
+import com.android.systemui.dreams.complication.ComplicationHostViewController;
 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
+import com.android.systemui.dreams.touch.DreamTouchHandler;
 import com.android.systemui.dreams.touch.dagger.DreamTouchModule;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Scope;
+import com.android.systemui.touch.TouchInsetManager;
 
 import dagger.BindsInstance;
 import dagger.Subcomponent;
 
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.util.Set;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
 /**
  * Dagger subcomponent for {@link DreamOverlayModule}.
  */
 @Subcomponent(modules = {
         DreamTouchModule.class,
         DreamOverlayModule.class,
-        ComplicationModule.class,
 })
 @DreamOverlayComponent.DreamOverlayScope
 public interface DreamOverlayComponent {
     /** Simple factory for {@link DreamOverlayComponent}. */
     @Subcomponent.Factory
     interface Factory {
-        DreamOverlayComponent create(@BindsInstance ViewModelStore store,
-                @BindsInstance Complication.Host host);
+        DreamOverlayComponent create(
+                @BindsInstance LifecycleOwner lifecycleOwner,
+                @BindsInstance ComplicationHostViewController complicationHostViewController,
+                @BindsInstance TouchInsetManager touchInsetManager,
+                @BindsInstance @Named(DREAM_TOUCH_HANDLERS) @Nullable
+                        Set<DreamTouchHandler> dreamTouchHandlers);
     }
 
     /** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */
@@ -62,12 +70,6 @@
     /** Builds a {@link DreamOverlayContainerViewController}. */
     DreamOverlayContainerViewController getDreamOverlayContainerViewController();
 
-    /** Builds a {@link androidx.lifecycle.LifecycleRegistry} */
-    LifecycleRegistry getLifecycleRegistry();
-
-    /** Builds a {@link androidx.lifecycle.LifecycleOwner} */
-    LifecycleOwner getLifecycleOwner();
-
     /** Builds a {@link DreamOverlayTouchMonitor} */
     DreamOverlayTouchMonitor getDreamOverlayTouchMonitor();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index ed0e1d9..4485381 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -16,32 +16,35 @@
 
 package com.android.systemui.dreams.dagger;
 
+import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
 
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
 
 import com.android.internal.util.Preconditions;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayContainerView;
 import com.android.systemui.dreams.DreamOverlayStatusBarView;
+import com.android.systemui.dreams.touch.DreamTouchHandler;
 import com.android.systemui.touch.TouchInsetManager;
 
-import java.util.concurrent.Executor;
-
-import javax.inject.Named;
-
-import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.ElementsIntoSet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.inject.Named;
 
 /** Dagger module for {@link DreamOverlayComponent}. */
 @Module
 public abstract class DreamOverlayModule {
+    public static final String DREAM_TOUCH_HANDLERS = "dream_touch_handlers";
     public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
     public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
     public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
@@ -101,14 +104,6 @@
     /** */
     @Provides
     @DreamOverlayComponent.DreamOverlayScope
-    public static TouchInsetManager providesTouchInsetManager(@Main Executor executor,
-            DreamOverlayContainerView view) {
-        return new TouchInsetManager(executor, view);
-    }
-
-    /** */
-    @Provides
-    @DreamOverlayComponent.DreamOverlayScope
     public static DreamOverlayStatusBarView providesDreamOverlayStatusBarView(
             DreamOverlayContainerView view) {
         return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_status_bar),
@@ -246,19 +241,14 @@
 
     @Provides
     @DreamOverlayComponent.DreamOverlayScope
-    static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {
-        return () -> lifecycleRegistryLazy.get();
-    }
-
-    @Provides
-    @DreamOverlayComponent.DreamOverlayScope
-    static LifecycleRegistry providesLifecycleRegistry(LifecycleOwner lifecycleOwner) {
-        return new LifecycleRegistry(lifecycleOwner);
-    }
-
-    @Provides
-    @DreamOverlayComponent.DreamOverlayScope
     static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) {
         return lifecycleOwner.getLifecycle();
     }
+
+    @Provides
+    @ElementsIntoSet
+    static Set<DreamTouchHandler> providesDreamTouchHandlers(
+            @Named(DREAM_TOUCH_HANDLERS) @Nullable Set<DreamTouchHandler> touchHandlers) {
+        return touchHandlers != null ? touchHandlers : new HashSet<>();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/dreamcomplication/HideComplicationTouchHandler.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
rename to packages/SystemUI/src/com/android/systemui/dreams/dreamcomplication/HideComplicationTouchHandler.java
index e276e0c..3a4578b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dreamcomplication/HideComplicationTouchHandler.java
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch;
+package com.android.systemui.dreams.dreamcomplication;
 
-import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DELAY;
-import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_RESTORE_TIMEOUT;
+import static com.android.systemui.dreams.dreamcomplication.dagger.ComplicationModule.COMPLICATIONS_FADE_OUT_DELAY;
+import static com.android.systemui.dreams.dreamcomplication.dagger.ComplicationModule.COMPLICATIONS_RESTORE_TIMEOUT;
 
 import android.util.Log;
 import android.view.MotionEvent;
@@ -28,6 +28,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
+import com.android.systemui.dreams.touch.DreamTouchHandler;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.concurrency.DelayableExecutor;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dreamcomplication/dagger/ComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/dreamcomplication/dagger/ComplicationComponent.kt
new file mode 100644
index 0000000..f2fb48d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dreamcomplication/dagger/ComplicationComponent.kt
@@ -0,0 +1,21 @@
+package com.android.systemui.dreams.dreamcomplication.dagger
+
+import com.android.systemui.dreams.complication.Complication
+import com.android.systemui.dreams.dreamcomplication.HideComplicationTouchHandler
+import com.android.systemui.touch.TouchInsetManager
+import dagger.BindsInstance
+import dagger.Subcomponent
+
+@Subcomponent(modules = [ComplicationModule::class])
+interface ComplicationComponent {
+    /** Factory for generating [ComplicationComponent]. */
+    @Subcomponent.Factory
+    interface Factory {
+        fun create(
+            @BindsInstance visibilityController: Complication.VisibilityController,
+            @BindsInstance touchInsetManager: TouchInsetManager
+        ): ComplicationComponent
+    }
+
+    fun getHideComplicationTouchHandler(): HideComplicationTouchHandler
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dreamcomplication/dagger/ComplicationModule.kt b/packages/SystemUI/src/com/android/systemui/dreams/dreamcomplication/dagger/ComplicationModule.kt
new file mode 100644
index 0000000..ef75ce1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dreamcomplication/dagger/ComplicationModule.kt
@@ -0,0 +1,28 @@
+package com.android.systemui.dreams.dreamcomplication.dagger
+
+import android.content.res.Resources
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Main
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+@Module
+object ComplicationModule {
+    const val COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout"
+    const val COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay"
+
+    /** Provides the delay to wait for before fading out complications. */
+    @Provides
+    @Named(COMPLICATIONS_FADE_OUT_DELAY)
+    fun providesComplicationsFadeOutDelay(@Main resources: Resources): Int {
+        return resources.getInteger(R.integer.complicationFadeOutDelayMs)
+    }
+
+    /** Provides the timeout for restoring complication visibility. */
+    @Provides
+    @Named(COMPLICATIONS_RESTORE_TIMEOUT)
+    fun providesComplicationsRestoreTimeout(@Main resources: Resources): Int {
+        return resources.getInteger(R.integer.complicationRestoreMs)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
index 7338ecb..dad0004 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
@@ -23,7 +23,6 @@
  */
 @Module(includes = {
             BouncerSwipeModule.class,
-            HideComplicationModule.class,
         }, subcomponents = {
             InputSessionComponent.class,
 })
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/HideComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/HideComplicationModule.java
deleted file mode 100644
index 3800ff7..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/HideComplicationModule.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2021 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.systemui.dreams.touch.dagger;
-
-import com.android.systemui.dreams.touch.DreamTouchHandler;
-import com.android.systemui.dreams.touch.HideComplicationTouchHandler;
-
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.IntoSet;
-
-/**
- * Module for {@link HideComplicationTouchHandler}.
- */
-@Module
-public class HideComplicationModule {
-    /**
-     * Provides {@link HideComplicationTouchHandler} for inclusion in touch handling over the dream.
-     */
-    @Provides
-    @IntoSet
-    public static DreamTouchHandler providesHideComplicationTouchHandler(
-            HideComplicationTouchHandler touchHandler) {
-        return touchHandler;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c712a94..d4cd57f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -83,7 +83,7 @@
     val STABILITY_INDEX_FIX = releasedFlag(114, "stability_index_fix")
 
     // TODO(b/259559750): Tracking Bug
-    val SEMI_STABLE_SORT = unreleasedFlag(115, "semi_stable_sort", teamfood = true)
+    val SEMI_STABLE_SORT = releasedFlag(115, "semi_stable_sort")
 
     @JvmField
     val USE_ROUNDNESS_SOURCETYPES = unreleasedFlag(116, "use_roundness_sourcetype", teamfood = true)
@@ -172,7 +172,7 @@
      * new KeyguardTransitionRepository.
      */
     @JvmField
-    val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = true)
+    val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = false)
 
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
@@ -252,10 +252,6 @@
     // TODO(b/254512848): Tracking Bug
     val REGION_SAMPLING = unreleasedFlag(801, "region_sampling", teamfood = true)
 
-    // 802 - wallpaper rendering
-    // TODO(b/254512923): Tracking Bug
-    @JvmField val USE_CANVAS_RENDERER = unreleasedFlag(802, "use_canvas_renderer")
-
     // 803 - screen contents translation
     // TODO(b/254513187): Tracking Bug
     val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag(803, "screen_contents_translation")
@@ -287,7 +283,11 @@
     // TODO(b/254513168): Tracking Bug
     @JvmField val UMO_SURFACE_RIPPLE = unreleasedFlag(907, "umo_surface_ripple")
 
-    @JvmField val MEDIA_FALSING_PENALTY = unreleasedFlag(908, "media_falsing_media")
+    @JvmField
+    val MEDIA_FALSING_PENALTY = unreleasedFlag(908, "media_falsing_media", teamfood = true)
+
+    // TODO(b/261734857): Tracking Bug
+    @JvmField val UMO_TURBULENCE_NOISE = unreleasedFlag(909, "umo_turbulence_noise")
 
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -399,9 +399,10 @@
     val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true)
 
     // 1600 - accessibility
+    // TODO(b/262224538): Tracking Bug
     @JvmField
     val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
-        unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations", teamfood = true)
+        releasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
 
     // 1700 - clipboard
     @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
@@ -434,6 +435,8 @@
     // 2300 - stylus
     @JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used")
     @JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui")
+    @JvmField
+    val ENABLE_USI_BATTERY_NOTIFICATIONS = unreleasedFlag(2302, "enable_usi_battery_notifications")
 
     // 2400 - performance tools and debugging info
     // TODO(b/238923086): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index db2cd91..c3e163f 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -733,7 +733,7 @@
     @VisibleForTesting
     boolean shouldDisplayBugReport(UserInfo currentUser) {
         return mGlobalSettings.getInt(Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0
-                && (currentUser == null || currentUser.isPrimary());
+                && (currentUser == null || currentUser.isAdmin());
     }
 
     @Override
@@ -1058,8 +1058,9 @@
 
         @Override
         public boolean showBeforeProvisioning() {
-            return Build.isDebuggable() && mGlobalSettings.getInt(
-                    Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0;
+            return Build.isDebuggable() && mGlobalSettings.getIntForUser(
+                    Settings.Global.BUGREPORT_IN_POWER_MENU, 0, getCurrentUser().id) != 0
+                    && getCurrentUser().isAdmin();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 36c939d..d6418d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1614,7 +1614,7 @@
         // TODO: Rename all screen off/on references to interactive/sleeping
         synchronized (this) {
             mDeviceInteractive = true;
-            if (mPendingLock && !cameraGestureTriggered) {
+            if (mPendingLock && !cameraGestureTriggered && !mWakeAndUnlocking) {
                 doKeyguardLocked(null);
             }
             mAnimatingScreenOff = false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 2558fab..394426d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -130,6 +130,7 @@
                             state(
                                 isFeatureEnabled = component.isEnabled(),
                                 hasFavorites = favorites?.isNotEmpty() == true,
+                                hasPanels = serviceInfos.any { it.panelActivity != null },
                                 hasServiceInfos = serviceInfos.isNotEmpty(),
                                 iconResourceId = component.getTileImageId(),
                                 visibility = component.getVisibility(),
@@ -148,13 +149,14 @@
     private fun state(
         isFeatureEnabled: Boolean,
         hasFavorites: Boolean,
+        hasPanels: Boolean,
         hasServiceInfos: Boolean,
         visibility: ControlsComponent.Visibility,
         @DrawableRes iconResourceId: Int?,
     ): KeyguardQuickAffordanceConfig.LockScreenState {
         return if (
             isFeatureEnabled &&
-                hasFavorites &&
+                (hasFavorites || hasPanels) &&
                 hasServiceInfos &&
                 iconResourceId != null &&
                 visibility == ControlsComponent.Visibility.AVAILABLE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 783f752..90f3c7d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -16,23 +16,36 @@
 
 package com.android.systemui.keyguard.data.repository
 
-import com.android.keyguard.KeyguardUpdateMonitor
+import android.os.Build
 import com.android.keyguard.ViewMediatorCallback
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.log.dagger.BouncerLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.phone.KeyguardBouncer
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
 
-/** Encapsulates app state for the lock screen primary and alternate bouncer. */
+/**
+ * Encapsulates app state for the lock screen primary and alternate bouncer.
+ *
+ * Make sure to add newly added flows to the logger.
+ */
 @SysUISingleton
 class KeyguardBouncerRepository
 @Inject
 constructor(
     private val viewMediatorCallback: ViewMediatorCallback,
-    keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    @Application private val applicationScope: CoroutineScope,
+    @BouncerLog private val buffer: TableLogBuffer,
 ) {
     /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
     private val _primaryBouncerVisible = MutableStateFlow(false)
@@ -77,6 +90,10 @@
     val bouncerErrorMessage: CharSequence?
         get() = viewMediatorCallback.consumeCustomMessage()
 
+    init {
+        setUpLogging()
+    }
+
     fun setPrimaryScrimmed(isScrimmed: Boolean) {
         _primaryBouncerScrimmed.value = isScrimmed
     }
@@ -132,4 +149,57 @@
     fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
         _onScreenTurnedOff.value = onScreenTurnedOff
     }
+
+    /** Sets up logs for state flows. */
+    private fun setUpLogging() {
+        if (!Build.IS_DEBUGGABLE) {
+            return
+        }
+
+        primaryBouncerVisible
+            .logDiffsForTable(buffer, "", "PrimaryBouncerVisible", false)
+            .launchIn(applicationScope)
+        primaryBouncerShow
+            .map { it != null }
+            .logDiffsForTable(buffer, "", "PrimaryBouncerShow", false)
+            .launchIn(applicationScope)
+        primaryBouncerShowingSoon
+            .logDiffsForTable(buffer, "", "PrimaryBouncerShowingSoon", false)
+            .launchIn(applicationScope)
+        primaryBouncerHide
+            .logDiffsForTable(buffer, "", "PrimaryBouncerHide", false)
+            .launchIn(applicationScope)
+        primaryBouncerStartingToHide
+            .logDiffsForTable(buffer, "", "PrimaryBouncerStartingToHide", false)
+            .launchIn(applicationScope)
+        primaryBouncerStartingDisappearAnimation
+            .map { it != null }
+            .logDiffsForTable(buffer, "", "PrimaryBouncerStartingDisappearAnimation", false)
+            .launchIn(applicationScope)
+        primaryBouncerScrimmed
+            .logDiffsForTable(buffer, "", "PrimaryBouncerScrimmed", false)
+            .launchIn(applicationScope)
+        panelExpansionAmount
+            .map { (it * 1000).toInt() }
+            .logDiffsForTable(buffer, "", "PanelExpansionAmountMillis", -1)
+            .launchIn(applicationScope)
+        keyguardPosition
+            .map { it.toInt() }
+            .logDiffsForTable(buffer, "", "KeyguardPosition", -1)
+            .launchIn(applicationScope)
+        onScreenTurnedOff
+            .logDiffsForTable(buffer, "", "OnScreenTurnedOff", false)
+            .launchIn(applicationScope)
+        isBackButtonEnabled
+            .filterNotNull()
+            .logDiffsForTable(buffer, "", "IsBackButtonEnabled", false)
+            .launchIn(applicationScope)
+        showMessage
+            .map { it?.message }
+            .logDiffsForTable(buffer, "", "ShowMessage", null)
+            .launchIn(applicationScope)
+        resourceUpdateRequests
+            .logDiffsForTable(buffer, "", "ResourceUpdateRequests", false)
+            .launchIn(applicationScope)
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerLog.kt
similarity index 64%
copy from services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/BouncerLog.kt
index 528680e7..2251a7b 100644
--- a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerLog.kt
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.server.permission.access.external
+package com.android.systemui.log.dagger
 
-class RoSystemProperties {
-    companion object {
-        const val CONTROL_PRIVAPP_PERMISSIONS_DISABLE = false
-        const val CONTROL_PRIVAPP_PERMISSIONS_ENFORCE = false
-    }
-}
+import java.lang.annotation.Documented
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import javax.inject.Qualifier
+
+/** Logger for the primary and alternative bouncers. */
+@Qualifier @Documented @Retention(RetentionPolicy.RUNTIME) annotation class BouncerLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index f5a97ce..a7d60a1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -23,6 +23,8 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.log.LogBufferFactory;
+import com.android.systemui.log.table.TableLogBuffer;
+import com.android.systemui.log.table.TableLogBufferFactory;
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.plugins.log.LogcatEchoTracker;
 import com.android.systemui.plugins.log.LogcatEchoTrackerDebug;
@@ -345,6 +347,14 @@
         return factory.create("BluetoothLog", 50);
     }
 
+    /** Provides a logging buffer for the primary bouncer. */
+    @Provides
+    @SysUISingleton
+    @BouncerLog
+    public static TableLogBuffer provideBouncerLogBuffer(TableLogBufferFactory factory) {
+        return factory.create("BouncerLog", 250);
+    }
+
     /**
      * Provides a {@link LogBuffer} for Udfps logs.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
index bb04b6b4..348d941 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -100,3 +100,46 @@
         newVal
     }
 }
+/**
+ * Each time the Int flow is updated with a new value that's different from the previous value, logs
+ * the new value to the given [tableLogBuffer].
+ */
+fun Flow<Int>.logDiffsForTable(
+    tableLogBuffer: TableLogBuffer,
+    columnPrefix: String,
+    columnName: String,
+    initialValue: Int,
+): Flow<Int> {
+    val initialValueFun = {
+        tableLogBuffer.logChange(columnPrefix, columnName, initialValue)
+        initialValue
+    }
+    return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int ->
+        if (prevVal != newVal) {
+            tableLogBuffer.logChange(columnPrefix, columnName, newVal)
+        }
+        newVal
+    }
+}
+
+/**
+ * Each time the String? flow is updated with a new value that's different from the previous value,
+ * logs the new value to the given [tableLogBuffer].
+ */
+fun Flow<String?>.logDiffsForTable(
+    tableLogBuffer: TableLogBuffer,
+    columnPrefix: String,
+    columnName: String,
+    initialValue: String?,
+): Flow<String?> {
+    val initialValueFun = {
+        tableLogBuffer.logChange(columnPrefix, columnName, initialValue)
+        initialValue
+    }
+    return this.pairwiseBy(initialValueFun) { prevVal, newVal: String? ->
+        if (prevVal != newVal) {
+            tableLogBuffer.logChange(columnPrefix, columnName, newVal)
+        }
+        newVal
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 9d0b833..2c299d6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -127,11 +127,21 @@
         rowInitializer(row)
     }
 
+    /** Logs a String? change. */
+    fun logChange(prefix: String, columnName: String, value: String?) {
+        logChange(systemClock.currentTimeMillis(), prefix, columnName, value)
+    }
+
     /** Logs a boolean change. */
     fun logChange(prefix: String, columnName: String, value: Boolean) {
         logChange(systemClock.currentTimeMillis(), prefix, columnName, value)
     }
 
+    /** Logs a Int change. */
+    fun logChange(prefix: String, columnName: String, value: Int) {
+        logChange(systemClock.currentTimeMillis(), prefix, columnName, value)
+    }
+
     // Keep these individual [logChange] methods private (don't let clients give us their own
     // timestamps.)
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index df8fb91..db7a145 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -224,6 +224,8 @@
     private TurbulenceNoiseController mTurbulenceNoiseController;
     private FeatureFlags mFeatureFlags;
     private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig = null;
+    @VisibleForTesting
+    MultiRippleController.Companion.RipplesFinishedListener mRipplesFinishedListener = null;
 
     /**
      * Initialize a new control panel
@@ -404,15 +406,17 @@
         MultiRippleView multiRippleView = vh.getMultiRippleView();
         mMultiRippleController = new MultiRippleController(multiRippleView);
         mTurbulenceNoiseController = new TurbulenceNoiseController(vh.getTurbulenceNoiseView());
-        multiRippleView.addRipplesFinishedListener(
-                () -> {
-                    if (mTurbulenceNoiseAnimationConfig == null) {
-                        mTurbulenceNoiseAnimationConfig = createLingeringNoiseAnimation();
-                    }
-                    // Color will be correctly updated in ColorSchemeTransition.
-                    mTurbulenceNoiseController.play(mTurbulenceNoiseAnimationConfig);
+        if (mFeatureFlags.isEnabled(Flags.UMO_TURBULENCE_NOISE)) {
+            mRipplesFinishedListener = () -> {
+                if (mTurbulenceNoiseAnimationConfig == null) {
+                    mTurbulenceNoiseAnimationConfig = createLingeringNoiseAnimation();
                 }
-        );
+                // Color will be correctly updated in ColorSchemeTransition.
+                mTurbulenceNoiseController.play(mTurbulenceNoiseAnimationConfig);
+            };
+            mMultiRippleController.addRipplesFinishedListener(mRipplesFinishedListener);
+        }
+
         mColorSchemeTransition = new ColorSchemeTransition(
                 mContext, mMediaViewHolder, mMultiRippleController, mTurbulenceNoiseController);
         mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 3e5d337..bb833df 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -30,9 +30,11 @@
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
+import com.android.systemui.media.taptotransfer.receiver.ChipReceiverInfo;
 import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
 import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo;
 
 import java.util.Optional;
 
@@ -95,19 +97,19 @@
     @Provides
     @SysUISingleton
     @MediaTttSenderLogger
-    static MediaTttLogger providesMediaTttSenderLogger(
+    static MediaTttLogger<ChipbarInfo> providesMediaTttSenderLogger(
             @MediaTttSenderLogBuffer LogBuffer buffer
     ) {
-        return new MediaTttLogger("Sender", buffer);
+        return new MediaTttLogger<>("Sender", buffer);
     }
 
     @Provides
     @SysUISingleton
     @MediaTttReceiverLogger
-    static MediaTttLogger providesMediaTttReceiverLogger(
+    static MediaTttLogger<ChipReceiverInfo> providesMediaTttReceiverLogger(
             @MediaTttReceiverLogBuffer LogBuffer buffer
     ) {
-        return new MediaTttLogger("Receiver", buffer);
+        return new MediaTttLogger<>("Receiver", buffer);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java
new file mode 100644
index 0000000..875a010
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 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.systemui.media.dialog;
+
+import androidx.annotation.IntDef;
+
+import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Optional;
+
+/**
+ * MediaItem represents an item in OutputSwitcher list (could be a MediaDevice, group divider or
+ * connect new device item).
+ */
+public class MediaItem {
+    private final Optional<MediaDevice> mMediaDeviceOptional;
+    private final String mTitle;
+    @MediaItemType
+    private final int mMediaItemType;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            MediaItemType.TYPE_DEVICE,
+            MediaItemType.TYPE_GROUP_DIVIDER,
+            MediaItemType.TYPE_PAIR_NEW_DEVICE})
+    public @interface MediaItemType {
+        int TYPE_DEVICE = 0;
+        int TYPE_GROUP_DIVIDER = 1;
+        int TYPE_PAIR_NEW_DEVICE = 2;
+    }
+
+    public MediaItem() {
+        this.mMediaDeviceOptional = Optional.empty();
+        this.mTitle = null;
+        this.mMediaItemType = MediaItemType.TYPE_PAIR_NEW_DEVICE;
+    }
+
+    public MediaItem(String title, int mediaItemType) {
+        this.mMediaDeviceOptional = Optional.empty();
+        this.mTitle = title;
+        this.mMediaItemType = mediaItemType;
+    }
+
+    public MediaItem(MediaDevice mediaDevice) {
+        this.mMediaDeviceOptional = Optional.of(mediaDevice);
+        this.mTitle = mediaDevice.getName();
+        this.mMediaItemType = MediaItemType.TYPE_DEVICE;
+    }
+
+    public Optional<MediaDevice> getMediaDevice() {
+        return mMediaDeviceOptional;
+    }
+
+    /**
+     * Get layout id based on media item Type.
+     */
+    public static int getMediaLayoutId(int mediaItemType) {
+        switch (mediaItemType) {
+            case MediaItemType.TYPE_DEVICE:
+            case MediaItemType.TYPE_PAIR_NEW_DEVICE:
+                return R.layout.media_output_list_item_advanced;
+            case MediaItemType.TYPE_GROUP_DIVIDER:
+            default:
+                return R.layout.media_output_list_group_divider;
+        }
+    }
+
+    public String getTitle() {
+        return mTitle;
+    }
+
+    public boolean isMutingExpectedDevice() {
+        return mMediaDeviceOptional.isPresent()
+                && mMediaDeviceOptional.get().isMutingExpectedDevice();
+    }
+
+    public int getMediaItemType() {
+        return mMediaItemType;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 3dccae0..fb47d97 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -24,9 +24,11 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.CheckBox;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.core.widget.CompoundButtonCompat;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.settingslib.media.LocalMediaManager.MediaDeviceState;
 import com.android.settingslib.media.MediaDevice;
@@ -48,29 +50,68 @@
     }
 
     @Override
-    public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
+    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
             int viewType) {
         super.onCreateViewHolder(viewGroup, viewType);
-        return new MediaDeviceViewHolder(mHolderView);
+        if (mController.isAdvancedLayoutSupported()) {
+            switch (viewType) {
+                case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
+                    return new MediaGroupDividerViewHolder(mHolderView);
+                case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
+                case MediaItem.MediaItemType.TYPE_DEVICE:
+                default:
+                    return new MediaDeviceViewHolder(mHolderView);
+            }
+        } else {
+            return new MediaDeviceViewHolder(mHolderView);
+        }
     }
 
     @Override
-    public void onBindViewHolder(@NonNull MediaDeviceBaseViewHolder viewHolder, int position) {
-        final int size = mController.getMediaDevices().size();
-        if (position == size) {
-            viewHolder.onBind(CUSTOMIZED_ITEM_PAIR_NEW, false /* topMargin */,
-                    true /* bottomMargin */);
-        } else if (position < size) {
-            viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices())).get(position),
-                    position == 0 /* topMargin */, position == (size - 1) /* bottomMargin */,
-                    position);
-        } else if (DEBUG) {
-            Log.d(TAG, "Incorrect position: " + position);
+    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
+        if (mController.isAdvancedLayoutSupported()) {
+            MediaItem currentMediaItem = mController.getMediaItemList().get(position);
+            switch (currentMediaItem.getMediaItemType()) {
+                case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
+                    ((MediaGroupDividerViewHolder) viewHolder).onBind(currentMediaItem.getTitle());
+                    break;
+                case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
+                    ((MediaDeviceViewHolder) viewHolder).onBind(CUSTOMIZED_ITEM_PAIR_NEW);
+                    break;
+                case MediaItem.MediaItemType.TYPE_DEVICE:
+                    ((MediaDeviceViewHolder) viewHolder).onBind(
+                            currentMediaItem.getMediaDevice().get(),
+                            position);
+                    break;
+                default:
+                    Log.d(TAG, "Incorrect position: " + position);
+            }
+        } else {
+            final int size = mController.getMediaDevices().size();
+            if (position == size) {
+                ((MediaDeviceViewHolder) viewHolder).onBind(CUSTOMIZED_ITEM_PAIR_NEW);
+            } else if (position < size) {
+                ((MediaDeviceViewHolder) viewHolder).onBind(
+                        ((List<MediaDevice>) (mController.getMediaDevices())).get(position),
+                        position);
+            } else if (DEBUG) {
+                Log.d(TAG, "Incorrect position: " + position);
+            }
         }
     }
 
     @Override
     public long getItemId(int position) {
+        if (mController.isAdvancedLayoutSupported()) {
+            if (position >= mController.getMediaItemList().size()) {
+                Log.d(TAG, "Incorrect position for item id: " + position);
+                return position;
+            }
+            MediaItem currentMediaItem = mController.getMediaItemList().get(position);
+            return currentMediaItem.getMediaDevice().isPresent()
+                    ? currentMediaItem.getMediaDevice().get().getId().hashCode()
+                    : position;
+        }
         final int size = mController.getMediaDevices().size();
         if (position == size) {
             return -1;
@@ -84,9 +125,18 @@
     }
 
     @Override
+    public int getItemViewType(int position) {
+        return mController.isAdvancedLayoutSupported()
+                ? mController.getMediaItemList().get(position).getMediaItemType()
+                : super.getItemViewType(position);
+    }
+
+    @Override
     public int getItemCount() {
         // Add extra one for "pair new"
-        return mController.getMediaDevices().size() + 1;
+        return mController.isAdvancedLayoutSupported()
+                ? mController.getMediaItemList().size()
+                : mController.getMediaDevices().size() + 1;
     }
 
     class MediaDeviceViewHolder extends MediaDeviceBaseViewHolder {
@@ -96,8 +146,8 @@
         }
 
         @Override
-        void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
-            super.onBind(device, topMargin, bottomMargin, position);
+        void onBind(MediaDevice device, int position) {
+            super.onBind(device, position);
             boolean isMutingExpectedDeviceExist = mController.hasMutingExpectedDevice();
             final boolean currentlyConnected = isCurrentlyConnected(device);
             boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE;
@@ -261,7 +311,7 @@
         }
 
         @Override
-        void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
+        void onBind(int customizedItem) {
             if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
                 mTitleText.setTextColor(mController.getColorItemContent());
                 mCheckBox.setVisibility(View.GONE);
@@ -318,4 +368,18 @@
                             : R.string.accessibility_cast_name, device.getName()));
         }
     }
+
+    class MediaGroupDividerViewHolder extends RecyclerView.ViewHolder {
+        final TextView mTitleText;
+
+        MediaGroupDividerViewHolder(@NonNull View itemView) {
+            super(itemView);
+            mTitleText = itemView.requireViewById(R.id.title);
+        }
+
+        void onBind(String groupDividerTitle) {
+            mTitleText.setTextColor(mController.getColorItemContent());
+            mTitleText.setText(groupDividerTitle);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index db62e51..3b1d861 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -58,7 +58,7 @@
  * Base adapter for media output dialog.
  */
 public abstract class MediaOutputBaseAdapter extends
-        RecyclerView.Adapter<MediaOutputBaseAdapter.MediaDeviceBaseViewHolder> {
+        RecyclerView.Adapter<RecyclerView.ViewHolder> {
 
     static final int CUSTOMIZED_ITEM_PAIR_NEW = 1;
     static final int CUSTOMIZED_ITEM_GROUP = 2;
@@ -80,11 +80,12 @@
     }
 
     @Override
-    public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
+    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
             int viewType) {
         mContext = viewGroup.getContext();
         mHolderView = LayoutInflater.from(mContext).inflate(
-                mController.isAdvancedLayoutSupported() ? R.layout.media_output_list_item_advanced
+                mController.isAdvancedLayoutSupported() ? MediaItem.getMediaLayoutId(
+                        viewType) /*R.layout.media_output_list_item_advanced*/
                         : R.layout.media_output_list_item, viewGroup, false);
 
         return null;
@@ -175,7 +176,7 @@
             initAnimator();
         }
 
-        void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
+        void onBind(MediaDevice device, int position) {
             mDeviceId = device.getId();
             mCheckBox.setVisibility(View.GONE);
             mStatusIcon.setVisibility(View.GONE);
@@ -196,7 +197,7 @@
                             PorterDuff.Mode.SRC_IN));
         }
 
-        abstract void onBind(int customizedItem, boolean topMargin, boolean bottomMargin);
+        abstract void onBind(int customizedItem);
 
         void setSingleLineLayout(CharSequence title) {
             setSingleLineLayout(title, false, false, false, false);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 9b361e3..b436562 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -93,6 +93,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
@@ -121,6 +122,7 @@
     @VisibleForTesting
     final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
     final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
+    private final List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
     private final AudioManager mAudioManager;
     private final PowerExemptionManager mPowerExemptionManager;
     private final KeyguardManager mKeyGuardManager;
@@ -212,6 +214,7 @@
         synchronized (mMediaDevicesLock) {
             mCachedMediaDevices.clear();
             mMediaDevices.clear();
+            mMediaItemList.clear();
         }
         mNearbyDeviceInfoMap.clear();
         if (mNearbyMediaDevicesManager != null) {
@@ -262,6 +265,7 @@
         synchronized (mMediaDevicesLock) {
             mCachedMediaDevices.clear();
             mMediaDevices.clear();
+            mMediaItemList.clear();
         }
         if (mNearbyMediaDevicesManager != null) {
             mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this);
@@ -287,7 +291,11 @@
     public void onSelectedDeviceStateChanged(MediaDevice device,
             @LocalMediaManager.MediaDeviceState int state) {
         mCallback.onRouteChanged();
-        mMetricLogger.logOutputSuccess(device.toString(), new ArrayList<>(mMediaDevices));
+        if (isAdvancedLayoutSupported()) {
+            mMetricLogger.logOutputItemSuccess(device.toString(), new ArrayList<>(mMediaItemList));
+        } else {
+            mMetricLogger.logOutputSuccess(device.toString(), new ArrayList<>(mMediaDevices));
+        }
     }
 
     @Override
@@ -298,7 +306,11 @@
     @Override
     public void onRequestFailed(int reason) {
         mCallback.onRouteChanged();
-        mMetricLogger.logOutputFailure(new ArrayList<>(mMediaDevices), reason);
+        if (isAdvancedLayoutSupported()) {
+            mMetricLogger.logOutputItemFailure(new ArrayList<>(mMediaItemList), reason);
+        } else {
+            mMetricLogger.logOutputFailure(new ArrayList<>(mMediaDevices), reason);
+        }
     }
 
     /**
@@ -318,6 +330,7 @@
         try {
             synchronized (mMediaDevicesLock) {
                 mMediaDevices.removeIf(MediaDevice::isMutingExpectedDevice);
+                mMediaItemList.removeIf((MediaItem::isMutingExpectedDevice));
             }
             mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice());
         } catch (Exception e) {
@@ -547,6 +560,14 @@
     }
 
     private void buildMediaDevices(List<MediaDevice> devices) {
+        if (isAdvancedLayoutSupported()) {
+            buildMediaItems(devices);
+        } else {
+            buildDefaultMediaDevices(devices);
+        }
+    }
+
+    private void buildDefaultMediaDevices(List<MediaDevice> devices) {
         synchronized (mMediaDevicesLock) {
             attachRangeInfo(devices);
             Collections.sort(devices, Comparator.naturalOrder());
@@ -603,6 +624,81 @@
         }
     }
 
+    private void buildMediaItems(List<MediaDevice> devices) {
+        synchronized (mMediaDevicesLock) {
+            //TODO(b/257851968): do the organization only when there's no suggested sorted order
+            // we get from application
+            attachRangeInfo(devices);
+            Collections.sort(devices, Comparator.naturalOrder());
+            // For the first time building list, to make sure the top device is the connected
+            // device.
+            if (mMediaItemList.isEmpty()) {
+                boolean needToHandleMutingExpectedDevice =
+                        hasMutingExpectedDevice() && !isCurrentConnectedDeviceRemote();
+                final MediaDevice connectedMediaDevice =
+                        needToHandleMutingExpectedDevice ? null
+                                : getCurrentConnectedMediaDevice();
+                if (connectedMediaDevice == null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "No connected media device or muting expected device exist.");
+                    }
+                    if (needToHandleMutingExpectedDevice) {
+                        for (MediaDevice device : devices) {
+                            if (device.isMutingExpectedDevice()) {
+                                mMediaItemList.add(0, new MediaItem(device));
+                            } else {
+                                mMediaItemList.add(new MediaItem(device));
+                            }
+                        }
+                    } else {
+                        mMediaItemList.addAll(
+                                devices.stream().map(MediaItem::new).collect(Collectors.toList()));
+                    }
+
+                    categorizeMediaItems();
+                    return;
+                }
+                // selected device exist
+                for (MediaDevice device : devices) {
+                    if (TextUtils.equals(device.getId(), connectedMediaDevice.getId())) {
+                        mMediaItemList.add(0, new MediaItem(device));
+                    } else {
+                        mMediaItemList.add(new MediaItem(device));
+                    }
+                }
+                categorizeMediaItems();
+                return;
+            }
+            // To keep the same list order
+            final List<MediaDevice> targetMediaDevices = new ArrayList<>();
+            for (MediaItem originalMediaItem : mMediaItemList) {
+                for (MediaDevice newDevice : devices) {
+                    if (originalMediaItem.getMediaDevice().isPresent()
+                            && TextUtils.equals(originalMediaItem.getMediaDevice().get().getId(),
+                            newDevice.getId())) {
+                        targetMediaDevices.add(newDevice);
+                        break;
+                    }
+                }
+            }
+            if (targetMediaDevices.size() != devices.size()) {
+                devices.removeAll(targetMediaDevices);
+                targetMediaDevices.addAll(devices);
+            }
+            mMediaItemList.clear();
+            mMediaItemList.addAll(
+                    targetMediaDevices.stream().map(MediaItem::new).collect(Collectors.toList()));
+            categorizeMediaItems();
+        }
+    }
+
+    private void categorizeMediaItems() {
+        synchronized (mMediaDevicesLock) {
+            //TODO(255124239): do the categorization here
+            mMediaItemList.add(new MediaItem());
+        }
+    }
+
     private void attachRangeInfo(List<MediaDevice> devices) {
         for (MediaDevice mediaDevice : devices) {
             if (mNearbyDeviceInfoMap.containsKey(mediaDevice.getId())) {
@@ -670,6 +766,10 @@
         return mMediaDevices;
     }
 
+    public List<MediaItem> getMediaItemList() {
+        return mMediaItemList;
+    }
+
     MediaDevice getCurrentConnectedMediaDevice() {
         return mLocalMediaManager.getCurrentConnectedDevice();
     }
@@ -749,9 +849,19 @@
 
     boolean isAnyDeviceTransferring() {
         synchronized (mMediaDevicesLock) {
-            for (MediaDevice device : mMediaDevices) {
-                if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
-                    return true;
+            if (isAdvancedLayoutSupported()) {
+                for (MediaItem mediaItem : mMediaItemList) {
+                    if (mediaItem.getMediaDevice().isPresent()
+                            && mediaItem.getMediaDevice().get().getState()
+                            == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
+                        return true;
+                    }
+                }
+            } else {
+                for (MediaDevice device : mMediaDevices) {
+                    if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
+                        return true;
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 7d3e82c..2250d72 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -96,6 +96,31 @@
     }
 
     /**
+     * Do the metric logging of content switching success.
+     * @param selectedDeviceType string representation of the target media device
+     * @param deviceItemList media item list for device count updating
+     */
+    public void logOutputItemSuccess(String selectedDeviceType, List<MediaItem> deviceItemList) {
+        if (DEBUG) {
+            Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
+        }
+
+        updateLoggingMediaItemCount(deviceItemList);
+
+        SysUiStatsLog.write(
+                SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
+                getLoggingDeviceType(mSourceDevice, true),
+                getLoggingDeviceType(mTargetDevice, false),
+                SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__OK,
+                SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NO_ERROR,
+                getLoggingPackageName(),
+                mWiredDeviceCount,
+                mConnectedBluetoothDeviceCount,
+                mRemoteDeviceCount,
+                mAppliedDeviceCountWithinRemoteGroup);
+    }
+
+    /**
      * Do the metric logging of volume adjustment.
      * @param source the device been adjusted
      */
@@ -166,6 +191,31 @@
                 mAppliedDeviceCountWithinRemoteGroup);
     }
 
+    /**
+     * Do the metric logging of content switching failure.
+     * @param deviceItemList media item list for device count updating
+     * @param reason the reason of content switching failure
+     */
+    public void logOutputItemFailure(List<MediaItem> deviceItemList, int reason) {
+        if (DEBUG) {
+            Log.e(TAG, "logRequestFailed - " + reason);
+        }
+
+        updateLoggingMediaItemCount(deviceItemList);
+
+        SysUiStatsLog.write(
+                SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
+                getLoggingDeviceType(mSourceDevice, true),
+                getLoggingDeviceType(mTargetDevice, false),
+                SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__ERROR,
+                getLoggingSwitchOpSubResult(reason),
+                getLoggingPackageName(),
+                mWiredDeviceCount,
+                mConnectedBluetoothDeviceCount,
+                mRemoteDeviceCount,
+                mAppliedDeviceCountWithinRemoteGroup);
+    }
+
     private void updateLoggingDeviceCount(List<MediaDevice> deviceList) {
         mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
         mAppliedDeviceCountWithinRemoteGroup = 0;
@@ -196,6 +246,37 @@
         }
     }
 
+    private void updateLoggingMediaItemCount(List<MediaItem> deviceItemList) {
+        mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
+        mAppliedDeviceCountWithinRemoteGroup = 0;
+
+        for (MediaItem mediaItem: deviceItemList) {
+            if (mediaItem.getMediaDevice().isPresent()
+                    && mediaItem.getMediaDevice().get().isConnected()) {
+                switch (mediaItem.getMediaDevice().get().getDeviceType()) {
+                    case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
+                    case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
+                        mWiredDeviceCount++;
+                        break;
+                    case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
+                        mConnectedBluetoothDeviceCount++;
+                        break;
+                    case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
+                    case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
+                        mRemoteDeviceCount++;
+                        break;
+                    default:
+                }
+            }
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "connected devices:" + " wired: " + mWiredDeviceCount
+                    + " bluetooth: " + mConnectedBluetoothDeviceCount
+                    + " remote: " + mRemoteDeviceCount);
+        }
+    }
+
     private int getLoggingDeviceType(MediaDevice device, boolean isSourceDevice) {
         if (device == null) {
             return isSourceDevice
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index b55bedd..8aef938 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -18,17 +18,21 @@
 
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
 import com.android.systemui.temporarydisplay.TemporaryViewLogger
 
 /**
  * A logger for media tap-to-transfer events.
  *
  * @param deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver".
+ *
+ * TODO(b/245610654): We should de-couple the sender and receiver loggers, since they're vastly
+ * different experiences.
  */
-class MediaTttLogger(
+class MediaTttLogger<T : TemporaryViewInfo>(
     deviceTypeTag: String,
     buffer: LogBuffer
-) : TemporaryViewLogger(buffer, BASE_TAG + deviceTypeTag) {
+) : TemporaryViewLogger<T>(buffer, BASE_TAG + deviceTypeTag) {
     /** Logs a change in the chip state for the given [mediaRouteId]. */
     fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) {
         buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 009595a..066c185 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
 
 /** Utility methods for media tap-to-transfer. */
 class MediaTttUtils {
@@ -47,7 +48,7 @@
         fun getIconInfoFromPackageName(
             context: Context,
             appPackageName: String?,
-            logger: MediaTttLogger
+            logger: MediaTttLogger<out TemporaryViewInfo>
         ): IconInfo {
             if (appPackageName != null) {
                 val packageManager = context.packageManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 1c3a53c..9ca5fad 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -45,8 +45,10 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
 import com.android.systemui.temporarydisplay.TemporaryViewInfo
+import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.util.animation.AnimationUtil.Companion.frames
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
 import com.android.systemui.util.view.ViewUtil
 import com.android.systemui.util.wakelock.WakeLock
 import javax.inject.Inject
@@ -62,7 +64,7 @@
 open class MediaTttChipControllerReceiver @Inject constructor(
         private val commandQueue: CommandQueue,
         context: Context,
-        @MediaTttReceiverLogger logger: MediaTttLogger,
+        @MediaTttReceiverLogger logger: MediaTttLogger<ChipReceiverInfo>,
         windowManager: WindowManager,
         mainExecutor: DelayableExecutor,
         accessibilityManager: AccessibilityManager,
@@ -73,7 +75,8 @@
         private val uiEventLogger: MediaTttReceiverUiEventLogger,
         private val viewUtil: ViewUtil,
         wakeLockBuilder: WakeLock.Builder,
-) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
+        systemClock: SystemClock,
+) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger<ChipReceiverInfo>>(
         context,
         logger,
         windowManager,
@@ -83,6 +86,7 @@
         powerManager,
         R.layout.media_ttt_chip_receiver,
         wakeLockBuilder,
+        systemClock,
 ) {
     @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
     override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -290,4 +294,5 @@
     override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
     override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
     override val id: String,
+    override val priority: ViewPriority = ViewPriority.NORMAL,
 ) : TemporaryViewInfo()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index ec1984d..9f44d98 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.media.taptotransfer.common.MediaTttUtils
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
 import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
@@ -46,7 +47,7 @@
     private val chipbarCoordinator: ChipbarCoordinator,
     private val commandQueue: CommandQueue,
     private val context: Context,
-    @MediaTttSenderLogger private val logger: MediaTttLogger,
+    @MediaTttSenderLogger private val logger: MediaTttLogger<ChipbarInfo>,
     private val mediaTttFlags: MediaTttFlags,
     private val uiEventLogger: MediaTttSenderUiEventLogger,
 ) : CoreStartable {
@@ -146,7 +147,7 @@
         routeInfo: MediaRoute2Info,
         undoCallback: IUndoMediaTransferCallback?,
         context: Context,
-        logger: MediaTttLogger,
+        logger: MediaTttLogger<ChipbarInfo>,
     ): ChipbarInfo {
         val packageName = routeInfo.clientPackageName
         val otherDeviceName = routeInfo.name.toString()
@@ -180,6 +181,7 @@
             wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
             timeoutMs = chipStateSender.timeout,
             id = routeInfo.id,
+            priority = ViewPriority.NORMAL,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index f92bbf7..8ceee1a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -741,6 +741,14 @@
         }
     };
 
+    /**
+     * Force all tiles to be redistributed across pages.
+     * Should be called when one of the following changes: rows, columns, number of tiles.
+     */
+    public void forceTilesRedistribution() {
+        mDistributeTiles = true;
+    }
+
     public interface PageListener {
         int INVALID_PAGE = -1;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 1827eaf..b2ca6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -148,6 +148,11 @@
         }
     }
 
+    @Override
+    protected void onSplitShadeChanged() {
+        ((PagedTileLayout) mView.getOrCreateTileLayout()).forceTilesRedistribution();
+    }
+
     /** */
     public void setVisibility(int visibility) {
         mView.setVisibility(visibility);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index dd88c83..60d2c17 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -96,17 +96,23 @@
                         /* newOrientation= */ newConfig.orientation,
                         /* containerName= */ mView.getDumpableTag());
 
+                    boolean previousSplitShadeState = mShouldUseSplitNotificationShade;
                     mShouldUseSplitNotificationShade =
                         LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
                     mLastOrientation = newConfig.orientation;
 
                     switchTileLayoutIfNeeded();
                     onConfigurationChanged();
+                    if (previousSplitShadeState != mShouldUseSplitNotificationShade) {
+                        onSplitShadeChanged();
+                    }
                 }
             };
 
     protected void onConfigurationChanged() { }
 
+    protected void onSplitShadeChanged() { }
+
     private final Function1<Boolean, Unit> mMediaHostVisibilityListener = (visible) -> {
         if (mMediaVisibilityChangedListener != null) {
             mMediaVisibilityChangedListener.accept(visible);
@@ -264,14 +270,6 @@
             }
         }
     }
-    protected QSTile getTile(String subPanel) {
-        for (int i = 0; i < mRecords.size(); i++) {
-            if (subPanel.equals(mRecords.get(i).tile.getTileSpec())) {
-                return mRecords.get(i).tile;
-            }
-        }
-        return mHost.createTile(subPanel);
-    }
 
     boolean areThereTiles() {
         return !mRecords.isEmpty();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index 5dbf0f8..0bce1f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -57,6 +57,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.text.SpannableStringBuilder;
 import android.text.method.LinkMovementMethod;
@@ -770,13 +771,6 @@
         }
     }
 
-    private boolean isFinancedDevice() {
-        return mSecurityController.isDeviceManaged()
-                && mSecurityController.getDeviceOwnerType(
-                mSecurityController.getDeviceOwnerComponentOnAnyUser())
-                == DEVICE_OWNER_TYPE_FINANCED;
-    }
-
     protected class VpnSpan extends ClickableSpan {
         @Override
         public void onClick(View widget) {
@@ -797,4 +791,18 @@
             return 314159257; // prime
         }
     }
+
+    // TODO(b/259908270): remove and inline direct call to mSecurityController.isFinancedDevice()
+    private boolean isFinancedDevice() {
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                DevicePolicyManager.ADD_ISFINANCED_DEVICE_FLAG,
+                DevicePolicyManager.ADD_ISFINANCED_FEVICE_DEFAULT)) {
+            return mSecurityController.isFinancedDevice();
+        } else {
+            return mSecurityController.isDeviceManaged()
+                    && mSecurityController.getDeviceOwnerType(
+                    mSecurityController.getDeviceOwnerComponentOnAnyUser())
+                    == DEVICE_OWNER_TYPE_FINANCED;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 9743c3e..206a620 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -358,6 +358,9 @@
                     if (!isChecked && shouldShowMobileDialog()) {
                         showTurnOffMobileDialog();
                     } else if (!shouldShowMobileDialog()) {
+                        if (mInternetDialogController.isMobileDataEnabled() == isChecked) {
+                            return;
+                        }
                         mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
                                 isChecked, false);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index b511b54..7fc0a5f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -101,6 +101,8 @@
         @VisibleForTesting
         internal val HEADER_TRANSITION_ID = R.id.header_transition
         @VisibleForTesting
+        internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
+        @VisibleForTesting
         internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
         @VisibleForTesting
         internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
@@ -429,8 +431,11 @@
         }
         header as MotionLayout
         if (largeScreenActive) {
-            header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header)
+            logInstantEvent("Large screen constraints set")
+            header.setTransition(HEADER_TRANSITION_ID)
+            header.transitionToStart()
         } else {
+            logInstantEvent("Small screen constraints set")
             header.setTransition(HEADER_TRANSITION_ID)
             header.transitionToStart()
             updatePosition()
@@ -440,15 +445,19 @@
 
     private fun updatePosition() {
         if (header is MotionLayout && !largeScreenActive && visible) {
-            Trace.instantForTrack(
-                TRACE_TAG_APP,
-                "LargeScreenHeaderController - updatePosition",
-                "position: $qsExpandedFraction"
-            )
+            logInstantEvent("updatePosition: $qsExpandedFraction")
             header.progress = qsExpandedFraction
         }
     }
 
+    private fun logInstantEvent(message: String) {
+        Trace.instantForTrack(
+                TRACE_TAG_APP,
+                "LargeScreenHeaderController",
+                message
+        )
+    }
+
     private fun updateListeners() {
         qsCarrierGroupController.setListening(visible)
         if (visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 507dec6..5c2c1b6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -40,6 +40,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
 import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
 import static com.android.systemui.util.DumpUtilsKt.asIndenting;
@@ -2141,7 +2142,6 @@
                 if ((h > touchSlop || (h < -touchSlop && mQsExpanded))
                         && Math.abs(h) > Math.abs(x - mInitialTouchX)
                         && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
-                    debugLog("onQsIntercept - start tracking expansion");
                     mView.getParent().requestDisallowInterceptTouchEvent(true);
                     mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
                     mQsTracking = true;
@@ -2326,7 +2326,7 @@
 
 
     private boolean handleQsTouch(MotionEvent event) {
-        if (mSplitShadeEnabled && touchXOutsideOfQs(event.getX())) {
+        if (isSplitShadeAndTouchXOutsideQs(event.getX())) {
             return false;
         }
         final int action = event.getActionMasked();
@@ -2352,7 +2352,7 @@
         if (!mSplitShadeEnabled
                 && computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) {
             mShadeLog.logMotionEvent(event,
-                    "handleQsTouch: QQS touched while shade collapsing");
+                    "handleQsTouch: QQS touched while shade collapsing, QS tracking disabled");
             mQsTracking = false;
         }
         if (!mQsExpandImmediate && mQsTracking) {
@@ -2383,12 +2383,14 @@
         return false;
     }
 
-    private boolean touchXOutsideOfQs(float touchX) {
-        return touchX < mQsFrame.getX() || touchX > mQsFrame.getX() + mQsFrame.getWidth();
+    /** Returns whether split shade is enabled and an x coordinate is outside of the QS frame. */
+    private boolean isSplitShadeAndTouchXOutsideQs(float touchX) {
+        return mSplitShadeEnabled && (touchX < mQsFrame.getX()
+                || touchX > mQsFrame.getX() + mQsFrame.getWidth());
     }
 
     private boolean isInQsArea(float x, float y) {
-        if (touchXOutsideOfQs(x)) {
+        if (isSplitShadeAndTouchXOutsideQs(x)) {
             return false;
         }
         // Let's reject anything at the very bottom around the home handle in gesture nav
@@ -4721,6 +4723,7 @@
             if (!openingWithTouch || !mHasVibratedOnOpen) {
                 mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
                 mHasVibratedOnOpen = true;
+                mShadeLog.v("Vibrating on opening, mHasVibratedOnOpen=true");
             }
         }
     }
@@ -5317,7 +5320,7 @@
         @Override
         public void flingTopOverscroll(float velocity, boolean open) {
             // in split shade mode we want to expand/collapse QS only when touch happens within QS
-            if (mSplitShadeEnabled && touchXOutsideOfQs(mInitialTouchX)) {
+            if (isSplitShadeAndTouchXOutsideQs(mInitialTouchX)) {
                 return;
             }
             mLastOverscroll = 0f;
@@ -5478,6 +5481,15 @@
             mBarState = statusBarState;
             mKeyguardShowing = keyguardShowing;
 
+            boolean fromShadeToKeyguard = statusBarState == KEYGUARD
+                    && (oldState == SHADE || oldState == SHADE_LOCKED);
+            if (mSplitShadeEnabled && fromShadeToKeyguard) {
+                // user can go to keyguard from different shade states and closing animation
+                // may not fully run - we always want to make sure we close QS when that happens
+                // as we never need QS open in fresh keyguard state
+                closeQs();
+            }
+
             if (oldState == KEYGUARD && (goingToFullShade
                     || statusBarState == StatusBarState.SHADE_LOCKED)) {
 
@@ -5497,27 +5509,12 @@
                 mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
 
                 mNotificationStackScrollLayoutController.resetScrollPosition();
-                // Only animate header if the header is visible. If not, it will partially
-                // animate out
-                // the top of QS
-                if (!mQsExpanded) {
-                    // TODO(b/185683835) Nicer clipping when using new spacial model
-                    if (mSplitShadeEnabled) {
-                        mQs.animateHeaderSlidingOut();
-                    }
-                }
             } else {
                 // this else branch means we are doing one of:
                 //  - from KEYGUARD to SHADE (but not fully expanded as when swiping from the top)
                 //  - from SHADE to KEYGUARD
                 //  - from SHADE_LOCKED to SHADE
                 //  - getting notified again about the current SHADE or KEYGUARD state
-                if (mSplitShadeEnabled && oldState == SHADE && statusBarState == KEYGUARD) {
-                    // user can go to keyguard from different shade states and closing animation
-                    // may not fully run - we always want to make sure we close QS when that happens
-                    // as we never need QS open in fresh keyguard state
-                    closeQs();
-                }
                 final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
                         && statusBarState == KEYGUARD
                         && mScreenOffAnimationController.isKeyguardShowDelayed();
@@ -5796,12 +5793,9 @@
         /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
         public boolean onInterceptTouchEvent(MotionEvent event) {
             mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent");
-            if (SPEW_LOGCAT) {
-                Log.v(TAG,
-                        "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
-                                + "," + event.getY() + ")");
-            }
             if (mQs.disallowPanelTouches()) {
+                mShadeLog.logMotionEvent(event,
+                        "NPVC not intercepting touch, panel touches disallowed");
                 return false;
             }
             initDownStates(event);
@@ -5834,8 +5828,15 @@
                         + "QsIntercept");
                 return true;
             }
-            if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
-                    && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+
+            if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled) {
+                mShadeLog.logNotInterceptingTouchInstantExpanding(mInstantExpanding,
+                        !mNotificationsDragEnabled, mTouchDisabled);
+                return false;
+            }
+            if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+                mShadeLog.logMotionEventStatusBarState(event, mStatusBarStateController.getState(),
+                        "NPVC MotionEvent not intercepted: non-down action, motion was aborted");
                 return false;
             }
 
@@ -5890,6 +5891,9 @@
                     }
                     break;
                 case MotionEvent.ACTION_POINTER_DOWN:
+                    mShadeLog.logMotionEventStatusBarState(event,
+                            mStatusBarStateController.getState(),
+                            "onInterceptTouchEvent: pointer down action");
                     if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                         mMotionAborted = true;
                         mVelocityTracker.clear();
@@ -5931,7 +5935,8 @@
                     // events are received in this handler with identical downTimes. Until the
                     // source of the issue can be located, detect this case and ignore.
                     // see b/193350347
-                    Log.w(TAG, "Duplicate down event detected... ignoring");
+                    mShadeLog.logMotionEvent(event,
+                            "onTouch: duplicate down event detected... ignoring");
                     return true;
                 }
                 mLastTouchDownTime = event.getDownTime();
@@ -5939,6 +5944,8 @@
 
 
             if (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches()) {
+                mShadeLog.logMotionEvent(event,
+                        "onTouch: ignore touch, panel touches disallowed and qs fully expanded");
                 return false;
             }
 
@@ -5946,6 +5953,8 @@
             // otherwise user would be able to pull down QS or expand the shade.
             if (mCentralSurfaces.isBouncerShowingScrimmed()
                     || mCentralSurfaces.isBouncerShowingOverDream()) {
+                mShadeLog.logMotionEvent(event,
+                        "onTouch: ignore touch, bouncer scrimmed or showing over dream");
                 return false;
             }
 
@@ -6003,15 +6012,17 @@
 
         private boolean handleTouch(MotionEvent event) {
             if (mInstantExpanding) {
-                mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
+                mShadeLog.logMotionEvent(event,
+                        "handleTouch: touch ignored due to instant expanding");
                 return false;
             }
             if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
-                mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
+                mShadeLog.logMotionEvent(event, "handleTouch: non-cancel action, touch disabled");
                 return false;
             }
             if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
-                mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted");
+                mShadeLog.logMotionEventStatusBarState(event, mStatusBarStateController.getState(),
+                        "handleTouch: non-down action, motion was aborted");
                 return false;
             }
 
@@ -6021,7 +6032,7 @@
                     // Turn off tracking if it's on or the shade can get stuck in the down position.
                     onTrackingStopped(true /* expand */);
                 }
-                mShadeLog.logMotionEvent(event, "onTouch: drag not enabled");
+                mShadeLog.logMotionEvent(event, "handleTouch: drag not enabled");
                 return false;
             }
 
@@ -6094,6 +6105,9 @@
                     }
                     break;
                 case MotionEvent.ACTION_POINTER_DOWN:
+                    mShadeLog.logMotionEventStatusBarState(event,
+                            mStatusBarStateController.getState(),
+                            "handleTouch: pointer down action");
                     if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                         mMotionAborted = true;
                         endMotionEvent(event, x, y, true /* forceCancel */);
@@ -6104,6 +6118,7 @@
                     if (isFullyCollapsed()) {
                         // If panel is fully collapsed, reset haptic effect before adding movement.
                         mHasVibratedOnOpen = false;
+                        mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction);
                     }
                     addMovement(event);
                     if (!isFullyCollapsed()) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 40ed40a..5fedbeb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -98,6 +98,29 @@
         )
     }
 
+    fun logMotionEventStatusBarState(event: MotionEvent, statusBarState: Int, message: String) {
+        log(
+                LogLevel.VERBOSE,
+                {
+                    str1 = message
+                    long1 = event.eventTime
+                    long2 = event.downTime
+                    int1 = event.action
+                    int2 = statusBarState
+                    double1 = event.y.toDouble()
+                },
+                {
+                    "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1," +
+                            "statusBarState=${when (int2) {
+                                0 -> "SHADE"
+                                1 -> "KEYGUARD"
+                                2 -> "SHADE_LOCKED"
+                                else -> "UNKNOWN:$int2"
+                            }}"
+                }
+        )
+    }
+
     fun logExpansionChanged(
             message: String,
             fraction: Float,
@@ -117,6 +140,15 @@
         })
     }
 
+    fun logHasVibrated(hasVibratedOnOpen: Boolean, fraction: Float) {
+        log(LogLevel.VERBOSE, {
+            bool1 = hasVibratedOnOpen
+            double1 = fraction.toDouble()
+        }, {
+            "hasVibratedOnOpen=$bool1, expansionFraction=$double1"
+        })
+    }
+
     fun logQsExpansionChanged(
             message: String,
             qsExpanded: Boolean,
@@ -164,4 +196,19 @@
                     "tap to be detected: proximityIsNotNear: $bool1, isNotFalseTap: $bool2"
         })
     }
+
+    fun logNotInterceptingTouchInstantExpanding(
+            instantExpanding: Boolean,
+            notificationsDragEnabled: Boolean,
+            touchDisabled: Boolean
+    ) {
+        log(LogLevel.VERBOSE, {
+            bool1 = instantExpanding
+            bool2 = notificationsDragEnabled
+            bool3 = touchDisabled
+        }, {
+            "NPVC not intercepting touch, instantExpanding: $bool1, " +
+                    "!notificationsDragEnabled: $bool2, touchDisabled: $bool3"
+        })
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 0f27420..e39879d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -61,6 +61,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.text.format.Formatter;
 import android.view.View;
@@ -413,14 +414,25 @@
 
     private CharSequence getDisclosureText(@Nullable CharSequence organizationName) {
         final Resources packageResources = mContext.getResources();
+
+        // TODO(b/259908270): remove and inline
+        boolean isFinanced;
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                DevicePolicyManager.ADD_ISFINANCED_DEVICE_FLAG,
+                DevicePolicyManager.ADD_ISFINANCED_FEVICE_DEFAULT)) {
+            isFinanced = mDevicePolicyManager.isFinancedDevice();
+        } else {
+            isFinanced = mDevicePolicyManager.isDeviceManaged()
+                    && mDevicePolicyManager.getDeviceOwnerType(
+                    mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
+                    == DEVICE_OWNER_TYPE_FINANCED;
+        }
+
         if (organizationName == null) {
             return mDevicePolicyManager.getResources().getString(
                     KEYGUARD_MANAGEMENT_DISCLOSURE,
                     () -> packageResources.getString(R.string.do_disclosure_generic));
-        } else if (mDevicePolicyManager.isDeviceManaged()
-                && mDevicePolicyManager.getDeviceOwnerType(
-                mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
-                == DEVICE_OWNER_TYPE_FINANCED) {
+        } else if (isFinanced) {
             return packageResources.getString(R.string.do_financed_disclosure_with_name,
                     organizationName);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 336356e..b9074f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -68,7 +68,7 @@
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.LongRunning;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
@@ -225,15 +225,12 @@
 
     /**
      * Construct this controller object and register for updates.
-     *
-     * {@code @LongRunning} looper and bgExecutor instead {@code @Background} ones are used to
-     * address the b/246456655. This can be reverted after b/240663726 is fixed.
      */
     @Inject
     public NetworkControllerImpl(
             Context context,
-            @LongRunning Looper bgLooper,
-            @LongRunning Executor bgExecutor,
+            @Background Looper bgLooper,
+            @Background Executor bgExecutor,
             SubscriptionManager subscriptionManager,
             CallbackHandler callbackHandler,
             DeviceProvisionedController deviceProvisionedController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 39daa13..3072c81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -33,6 +33,8 @@
     fun fullScreenIntentRequiresKeyguard(): Boolean =
         featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD)
 
+    fun fsiOnDNDUpdate(): Boolean = featureFlags.isEnabled(Flags.FSI_ON_DND_UPDATE)
+
     val isStabilityIndexFixEnabled: Boolean by lazy {
         featureFlags.isEnabled(Flags.STABILITY_INDEX_FIX)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 3e2dd05..aeae89c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -244,7 +244,7 @@
     }
 
     override fun onDozeAmountChanged(linear: Float, eased: Float) {
-        logger.logOnDozeAmountChanged(linear, eased)
+        logger.logOnDozeAmountChanged(linear = linear, eased = eased)
         if (overrideDozeAmountIfAnimatingScreenOff(linear)) {
             return
         }
@@ -263,6 +263,7 @@
 
     fun setDozeAmount(linear: Float, eased: Float, source: String) {
         val changed = linear != mLinearDozeAmount
+        logger.logSetDozeAmount(linear, eased, source, statusBarStateController.state, changed)
         mLinearDozeAmount = linear
         mDozeAmount = eased
         mDozeAmountSource = source
@@ -276,7 +277,7 @@
     }
 
     override fun onStateChanged(newState: Int) {
-        logger.logOnStateChanged(newState)
+        logger.logOnStateChanged(newState = newState, storedState = state)
         if (state == StatusBarState.SHADE && newState == StatusBarState.SHADE) {
             // The SHADE -> SHADE transition is only possible as part of cancelling the screen-off
             // animation (e.g. by fingerprint unlock).  This is done because the system is in an
@@ -324,12 +325,8 @@
     private fun overrideDozeAmountIfBypass(): Boolean {
         if (bypassController.bypassEnabled) {
             if (statusBarStateController.state == StatusBarState.KEYGUARD) {
-                logger.logSetDozeAmount("1.0", "1.0",
-                        "Override: bypass (keyguard)", StatusBarState.KEYGUARD)
                 setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)")
             } else {
-                logger.logSetDozeAmount("0.0", "0.0",
-                        "Override: bypass (shade)", statusBarStateController.state)
                 setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
             }
             return true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index b40ce25..de18b0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -16,22 +16,33 @@
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.statusbar.StatusBarState
 import javax.inject.Inject
 
 class NotificationWakeUpCoordinatorLogger
 @Inject
 constructor(@NotificationLog private val buffer: LogBuffer) {
-    fun logSetDozeAmount(linear: String, eased: String, source: String, state: Int) {
+    fun logSetDozeAmount(
+        linear: Float,
+        eased: Float,
+        source: String,
+        state: Int,
+        changed: Boolean,
+    ) {
         buffer.log(
             TAG,
             DEBUG,
             {
-                str1 = linear
-                str2 = eased
+                double1 = linear.toDouble()
+                str2 = eased.toString()
                 str3 = source
                 int1 = state
+                bool1 = changed
             },
-            { "setDozeAmount: linear: $str1, eased: $str2, source: $str3, state: $int1" }
+            {
+                "setDozeAmount(linear=$double1, eased=$str2, source=$str3)" +
+                    " state=${StatusBarState.toString(int1)} changed=$bool1"
+            }
         )
     }
 
@@ -43,12 +54,23 @@
                 double1 = linear.toDouble()
                 str2 = eased.toString()
             },
-            { "onDozeAmountChanged($double1, $str2)" }
+            { "onDozeAmountChanged(linear=$double1, eased=$str2)" }
         )
     }
 
-    fun logOnStateChanged(newState: Int) {
-        buffer.log(TAG, DEBUG, { int1 = newState }, { "onStateChanged($int1)" })
+    fun logOnStateChanged(newState: Int, storedState: Int) {
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                int1 = newState
+                int2 = storedState
+            },
+            {
+                "onStateChanged(newState=${StatusBarState.toString(int1)})" +
+                    " stored=${StatusBarState.toString(int2)}"
+            }
+        )
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 5dbb4f9..1004ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -22,6 +22,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -38,6 +39,7 @@
 import com.android.systemui.statusbar.notification.dagger.IncomingHeader
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
 import com.android.systemui.statusbar.notification.logKey
 import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -70,11 +72,13 @@
     private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
     private val mRemoteInputManager: NotificationRemoteInputManager,
     private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
+    private val mFlags: NotifPipelineFlags,
     @IncomingHeader private val mIncomingHeaderController: NodeController,
     @Main private val mExecutor: DelayableExecutor,
 ) : Coordinator {
     private val mEntriesBindingUntil = ArrayMap<String, Long>()
     private val mEntriesUpdateTimes = ArrayMap<String, Long>()
+    private val mFSIUpdateCandidates = ArrayMap<String, Long>()
     private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null
     private lateinit var mNotifPipeline: NotifPipeline
     private var mNow: Long = -1
@@ -278,7 +282,7 @@
         mPostedEntries.clear()
 
         // Also take this opportunity to clean up any stale entry update times
-        cleanUpEntryUpdateTimes()
+        cleanUpEntryTimes()
     }
 
     /**
@@ -384,8 +388,15 @@
         override fun onEntryAdded(entry: NotificationEntry) {
             // First check whether this notification should launch a full screen intent, and
             // launch it if needed.
-            if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
+            val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
+            if (fsiDecision != null && fsiDecision.shouldLaunch) {
+                mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision)
                 mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+            } else if (mFlags.fsiOnDNDUpdate() &&
+                fsiDecision.equals(FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)) {
+                // If DND was the only reason this entry was suppressed, note it for potential
+                // reconsideration on later ranking updates.
+                addForFSIReconsideration(entry, mSystemClock.currentTimeMillis())
             }
 
             // shouldHeadsUp includes check for whether this notification should be filtered
@@ -488,11 +499,32 @@
                 if (!isNewEnoughForRankingUpdate(entry)) continue
 
                 // The only entries we consider alerting for here are entries that have never
-                // interrupted and that now say they should heads up; if they've alerted in the
-                // past, we don't want to incorrectly alert a second time if there wasn't an
+                // interrupted and that now say they should heads up or FSI; if they've alerted in
+                // the past, we don't want to incorrectly alert a second time if there wasn't an
                 // explicit notification update.
                 if (entry.hasInterrupted()) continue
 
+                // Before potentially allowing heads-up, check for any candidates for a FSI launch.
+                // Any entry that is a candidate meets two criteria:
+                //   - was suppressed from FSI launch only by a DND suppression
+                //   - is within the recency window for reconsideration
+                // If any of these entries are no longer suppressed, launch the FSI now.
+                if (mFlags.fsiOnDNDUpdate() && isCandidateForFSIReconsideration(entry)) {
+                    val decision =
+                        mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
+                    if (decision.shouldLaunch) {
+                        // Log both the launch of the full screen and also that this was via a
+                        // ranking update.
+                        mLogger.logEntryUpdatedToFullScreen(entry.key)
+                        mNotificationInterruptStateProvider.logFullScreenIntentDecision(
+                            entry, decision)
+                        mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+
+                        // if we launch the FSI then this is no longer a candidate for HUN
+                        continue
+                    }
+                }
+
                 // The cases where we should consider this notification to be updated:
                 // - if this entry is not present in PostedEntries, and is now in a shouldHeadsUp
                 //   state
@@ -528,6 +560,15 @@
     }
 
     /**
+     * Add the entry to the list of entries potentially considerable for FSI ranking update, where
+     * the provided time is the time the entry was added.
+     */
+    @VisibleForTesting
+    fun addForFSIReconsideration(entry: NotificationEntry, time: Long) {
+        mFSIUpdateCandidates[entry.key] = time
+    }
+
+    /**
      * Checks whether the entry is new enough to be updated via ranking update.
      * We want to avoid updating an entry too long after it was originally posted/updated when we're
      * only reacting to a ranking change, as relevant ranking updates are expected to come in
@@ -541,17 +582,38 @@
         return (mSystemClock.currentTimeMillis() - updateTime) <= MAX_RANKING_UPDATE_DELAY_MS
     }
 
-    private fun cleanUpEntryUpdateTimes() {
+    /**
+     * Checks whether the entry is present new enough for reconsideration for full screen launch.
+     * The time window is the same as for ranking update, but this doesn't allow a potential update
+     * to an entry with full screen intent to count for timing purposes.
+     */
+    private fun isCandidateForFSIReconsideration(entry: NotificationEntry): Boolean {
+        val addedTime = mFSIUpdateCandidates[entry.key] ?: return false
+        return (mSystemClock.currentTimeMillis() - addedTime) <= MAX_RANKING_UPDATE_DELAY_MS
+    }
+
+    private fun cleanUpEntryTimes() {
         // Because we won't update entries that are older than this amount of time anyway, clean
-        // up any entries that are too old to notify.
+        // up any entries that are too old to notify from both the general and FSI specific lists.
+
+        // Anything newer than this time is still within the window.
+        val timeThreshold = mSystemClock.currentTimeMillis() - MAX_RANKING_UPDATE_DELAY_MS
+
         val toRemove = ArraySet<String>()
         for ((key, updateTime) in mEntriesUpdateTimes) {
-            if (updateTime == null ||
-                    (mSystemClock.currentTimeMillis() - updateTime) > MAX_RANKING_UPDATE_DELAY_MS) {
+            if (updateTime == null || timeThreshold > updateTime) {
                 toRemove.add(key)
             }
         }
         mEntriesUpdateTimes.removeAll(toRemove)
+
+        val toRemoveForFSI = ArraySet<String>()
+        for ((key, addedTime) in mFSIUpdateCandidates) {
+            if (addedTime == null || timeThreshold > addedTime) {
+                toRemoveForFSI.add(key)
+            }
+        }
+        mFSIUpdateCandidates.removeAll(toRemoveForFSI)
     }
 
     /** When an action is pressed on a notification, end HeadsUp lifetime extension. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index 473c35d..2c6bf6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -70,6 +70,14 @@
         })
     }
 
+    fun logEntryUpdatedToFullScreen(key: String) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = key
+        }, {
+            "updating entry to launch full screen intent: $str1"
+        })
+    }
+
     fun logSummaryMarkedInterrupted(summaryKey: String, childKey: String) {
         buffer.log(TAG, LogLevel.DEBUG, {
             str1 = summaryKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 6cf4bf3..7136cad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -25,6 +25,79 @@
  */
 public interface NotificationInterruptStateProvider {
     /**
+     * Enum representing a decision of whether to show a full screen intent. While many of the
+     * relevant properties could overlap, the decision represents the deciding factor for whether
+     * the full screen intent should or shouldn't launch.
+     */
+    enum FullScreenIntentDecision {
+        /**
+         * No full screen intent included, so there is nothing to show.
+         */
+        NO_FULL_SCREEN_INTENT(false),
+        /**
+         * Suppressed by DND settings.
+         */
+        NO_FSI_SUPPRESSED_BY_DND(false),
+        /**
+         * Full screen intent was suppressed *only* by DND, and if not for DND would have shown. We
+         * track this separately in order to allow the intent to be shown if the DND decision
+         * changes.
+         */
+        NO_FSI_SUPPRESSED_ONLY_BY_DND(false),
+        /**
+         * Notification importance not high enough to show FSI.
+         */
+        NO_FSI_NOT_IMPORTANT_ENOUGH(false),
+        /**
+         * Notification should not FSI due to having suppressive GroupAlertBehavior. This blocks a
+         * potentially malicious use of flags that previously allowed apps to escalate a HUN to an
+         * FSI even while the device was unlocked.
+         */
+        NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(false),
+        /**
+         * Device screen is off, so the FSI should launch.
+         */
+        FSI_DEVICE_NOT_INTERACTIVE(true),
+        /**
+         * Device is currently dreaming, so FSI should launch.
+         */
+        FSI_DEVICE_IS_DREAMING(true),
+        /**
+         * Keyguard is showing, so FSI should launch.
+         */
+        FSI_KEYGUARD_SHOWING(true),
+        /**
+         * The notification is expected to show heads-up, so FSI is not needed.
+         */
+        NO_FSI_EXPECTED_TO_HUN(false),
+        /**
+         * The notification is not expected to HUN while the keyguard is occluded, so show FSI.
+         */
+        FSI_KEYGUARD_OCCLUDED(true),
+        /**
+         * The notification is not expected to HUN when the keyguard is showing but not occluded,
+         * which likely means that the shade is showing over the lockscreen; show FSI in this case.
+         */
+        FSI_LOCKED_SHADE(true),
+        /**
+         * FSI requires keyguard to be showing, but there is no keyguard. This is a (potentially
+         * malicious) warning state where we suppress the FSI because the device is in use knowing
+         * that the HUN will probably not display.
+         */
+        NO_FSI_NO_HUN_OR_KEYGUARD(false),
+        /**
+         * No conditions blocking FSI launch.
+         */
+        FSI_EXPECTED_NOT_TO_HUN(true);
+
+        public final boolean shouldLaunch;
+
+        FullScreenIntentDecision(boolean shouldLaunch) {
+            this.shouldLaunch = shouldLaunch;
+        }
+    }
+
+    /**
      * If the device is awake (not dozing):
      *  Whether the notification should peek in from the top and alert the user.
      *
@@ -66,6 +139,27 @@
     boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry);
 
     /**
+     * Whether an entry's full screen intent would be launched.
+     *
+     * This method differs from shouldLaunchFullScreenIntentWhenAdded by returning more information
+     * on the decision, and only optionally logging the outcome. It should be used in cases where
+     * the caller needs to know what the decision would be, but may not actually launch the full
+     * screen intent.
+     *
+     * @param entry the entry to evaluate
+     * @return FullScreenIntentDecision representing the decision for whether to show the intent
+     */
+    FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry);
+
+    /**
+     * Write the full screen launch decision for the given entry to logs.
+     *
+     * @param entry the NotificationEntry for which the decision applies
+     * @param decision reason for launch or no-launch of FSI for entry
+     */
+    void logFullScreenIntentDecision(NotificationEntry entry, FullScreenIntentDecision decision);
+
+    /**
      * Add a component that can suppress visual interruptions.
      */
     void addSuppressor(NotificationInterruptSuppressor suppressor);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index ec5bd68..d9dacfd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -211,20 +211,49 @@
      */
     @Override
     public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) {
-        if (entry.getSbn().getNotification().fullScreenIntent == null) {
-            return false;
+        FullScreenIntentDecision decision = getFullScreenIntentDecision(entry);
+        logFullScreenIntentDecision(entry, decision);
+        return decision.shouldLaunch;
+    }
+
+    // Given whether the relevant entry was suppressed by DND, and the full screen intent launch
+    // decision independent of the DND decision, returns the combined FullScreenIntentDecision that
+    // results. If the entry was suppressed by DND but the decision otherwise would launch the
+    // FSI, then it is suppressed *only* by DND, whereas (because the DND decision happens before
+    // all others) if the entry would not otherwise have launched the FSI, DND is the effective
+    // suppressor.
+    //
+    // If the entry was not suppressed by DND, just returns the given decision.
+    private FullScreenIntentDecision getDecisionGivenSuppression(FullScreenIntentDecision decision,
+            boolean suppressedByDND) {
+        if (suppressedByDND) {
+            return decision.shouldLaunch
+                    ? FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
+                    : FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND;
         }
+        return decision;
+    }
+
+    @Override
+    public FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry) {
+        if (entry.getSbn().getNotification().fullScreenIntent == null) {
+            return FullScreenIntentDecision.NO_FULL_SCREEN_INTENT;
+        }
+
+        // Boolean indicating whether this FSI would have been suppressed by DND. Because we
+        // want to be able to identify when something would have shown an FSI if not for being
+        // suppressed, we need to keep track of this value for future decisions.
+        boolean suppressedByDND = false;
 
         // Never show FSI when suppressed by DND
         if (entry.shouldSuppressFullScreenIntent()) {
-            mLogger.logNoFullscreen(entry, "Suppressed by DND");
-            return false;
+            suppressedByDND = true;
         }
 
         // Never show FSI if importance is not HIGH
         if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
-            mLogger.logNoFullscreen(entry, "Not important enough");
-            return false;
+            return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH,
+                    suppressedByDND);
         }
 
         // If the notification has suppressive GroupAlertBehavior, block FSI and warn.
@@ -232,36 +261,35 @@
         if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
             // b/231322873: Detect and report an event when a notification has both an FSI and a
             // suppressive groupAlertBehavior, and now correctly block the FSI from firing.
-            final int uid = entry.getSbn().getUid();
-            final String packageName = entry.getSbn().getPackageName();
-            android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior");
-            mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid, packageName);
-            mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
-            return false;
+            return getDecisionGivenSuppression(
+                    FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
+                    suppressedByDND);
         }
 
         // If the screen is off, then launch the FullScreenIntent
         if (!mPowerManager.isInteractive()) {
-            mLogger.logFullscreen(entry, "Device is not interactive");
-            return true;
+            return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE,
+                    suppressedByDND);
         }
 
         // If the device is currently dreaming, then launch the FullScreenIntent
         if (isDreaming()) {
-            mLogger.logFullscreen(entry, "Device is dreaming");
-            return true;
+            return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_DEVICE_IS_DREAMING,
+                    suppressedByDND);
         }
 
         // If the keyguard is showing, then launch the FullScreenIntent
         if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
-            mLogger.logFullscreen(entry, "Keyguard is showing");
-            return true;
+            return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_KEYGUARD_SHOWING,
+                    suppressedByDND);
         }
 
         // If the notification should HUN, then we don't need FSI
-        if (shouldHeadsUp(entry)) {
-            mLogger.logNoFullscreen(entry, "Expected to HUN");
-            return false;
+        // Because this is not the heads-up decision-making point, and checking whether it would
+        // HUN, don't log this specific check.
+        if (checkHeadsUp(entry, /* log= */ false)) {
+            return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN,
+                    suppressedByDND);
         }
 
         // Check whether FSI requires the keyguard to be showing.
@@ -270,27 +298,77 @@
             // If notification won't HUN and keyguard is showing, launch the FSI.
             if (mKeyguardStateController.isShowing()) {
                 if (mKeyguardStateController.isOccluded()) {
-                    mLogger.logFullscreen(entry, "Expected not to HUN while keyguard occluded");
+                    return getDecisionGivenSuppression(
+                            FullScreenIntentDecision.FSI_KEYGUARD_OCCLUDED,
+                            suppressedByDND);
                 } else {
                     // Likely LOCKED_SHADE, but launch FSI anyway
-                    mLogger.logFullscreen(entry, "Keyguard is showing and not occluded");
+                    return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_LOCKED_SHADE,
+                            suppressedByDND);
                 }
-                return true;
             }
 
             // Detect the case determined by b/231322873 to launch FSI while device is in use,
             // as blocked by the correct implementation, and report the event.
-            final int uid = entry.getSbn().getUid();
-            final String packageName = entry.getSbn().getPackageName();
-            android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard");
-            mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName);
-            mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
-            return false;
+            return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD,
+                    suppressedByDND);
         }
 
         // If the notification won't HUN for some other reason (DND/snooze/etc), launch FSI.
-        mLogger.logFullscreen(entry, "Expected not to HUN");
-        return true;
+        return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN,
+                suppressedByDND);
+    }
+
+    @Override
+    public void logFullScreenIntentDecision(NotificationEntry entry,
+            FullScreenIntentDecision decision) {
+        final int uid = entry.getSbn().getUid();
+        final String packageName = entry.getSbn().getPackageName();
+        switch (decision) {
+            case NO_FULL_SCREEN_INTENT:
+                return;
+            case NO_FSI_SUPPRESSED_BY_DND:
+            case NO_FSI_SUPPRESSED_ONLY_BY_DND:
+                mLogger.logNoFullscreen(entry, "Suppressed by DND");
+                return;
+            case NO_FSI_NOT_IMPORTANT_ENOUGH:
+                mLogger.logNoFullscreen(entry, "Not important enough");
+                return;
+            case NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR:
+                android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
+                        "groupAlertBehavior");
+                mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid,
+                        packageName);
+                mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
+                return;
+            case FSI_DEVICE_NOT_INTERACTIVE:
+                mLogger.logFullscreen(entry, "Device is not interactive");
+                return;
+            case FSI_DEVICE_IS_DREAMING:
+                mLogger.logFullscreen(entry, "Device is dreaming");
+                return;
+            case FSI_KEYGUARD_SHOWING:
+                mLogger.logFullscreen(entry, "Keyguard is showing");
+                return;
+            case NO_FSI_EXPECTED_TO_HUN:
+                mLogger.logNoFullscreen(entry, "Expected to HUN");
+                return;
+            case FSI_KEYGUARD_OCCLUDED:
+                mLogger.logFullscreen(entry,
+                        "Expected not to HUN while keyguard occluded");
+                return;
+            case FSI_LOCKED_SHADE:
+                mLogger.logFullscreen(entry, "Keyguard is showing and not occluded");
+                return;
+            case NO_FSI_NO_HUN_OR_KEYGUARD:
+                android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
+                        "no hun or keyguard");
+                mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName);
+                mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
+                return;
+            case FSI_EXPECTED_NOT_TO_HUN:
+                mLogger.logFullscreen(entry, "Expected not to HUN");
+        }
     }
 
     private boolean isDreaming() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
index 0380fff..1fcf17f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
@@ -17,10 +17,14 @@
 
 package com.android.systemui.statusbar.notification.logging
 
+import android.app.Notification
+
 /** Describes usage of a notification. */
 data class NotificationMemoryUsage(
     val packageName: String,
+    val uid: Int,
     val notificationKey: String,
+    val notification: Notification,
     val objectUsage: NotificationObjectUsage,
     val viewUsage: List<NotificationViewUsage>
 )
@@ -34,7 +38,8 @@
     val smallIcon: Int,
     val largeIcon: Int,
     val extras: Int,
-    val style: String?,
+    /** Style type, integer from [android.stats.sysui.NotificationEnums] */
+    val style: Int,
     val styleIcon: Int,
     val bigPicture: Int,
     val extender: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
new file mode 100644
index 0000000..ffd931c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
@@ -0,0 +1,173 @@
+/*
+ *
+ * Copyright (C) 2022 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.systemui.statusbar.notification.logging
+
+import android.stats.sysui.NotificationEnums
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Dumps current notification memory use to bug reports for easier debugging. */
+@SysUISingleton
+class NotificationMemoryDumper
+@Inject
+constructor(val dumpManager: DumpManager, val notificationPipeline: NotifPipeline) : Dumpable {
+
+    fun init() {
+        dumpManager.registerNormalDumpable(javaClass.simpleName, this)
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        val memoryUse =
+            NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
+                .sortedWith(compareBy({ it.packageName }, { it.notificationKey }))
+        dumpNotificationObjects(pw, memoryUse)
+        dumpNotificationViewUsage(pw, memoryUse)
+    }
+
+    /** Renders a table of notification object usage into passed [PrintWriter]. */
+    private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
+        pw.println("Notification Object Usage")
+        pw.println("-----------")
+        pw.println(
+            "Package".padEnd(35) +
+                "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
+        )
+        pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
+        pw.println()
+
+        memoryUse.forEach { use ->
+            pw.println(
+                use.packageName.padEnd(35) +
+                    "\t\t" +
+                    "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
+                    (styleEnumToString(use.objectUsage.style).take(15) ?: "").padEnd(15) +
+                    "\t\t${use.objectUsage.styleIcon}\t" +
+                    "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
+                    "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
+                    use.notificationKey
+            )
+        }
+
+        // Calculate totals for easily glanceable summary.
+        data class Totals(
+            var smallIcon: Int = 0,
+            var largeIcon: Int = 0,
+            var styleIcon: Int = 0,
+            var bigPicture: Int = 0,
+            var extender: Int = 0,
+            var extras: Int = 0,
+        )
+
+        val totals =
+            memoryUse.fold(Totals()) { t, usage ->
+                t.smallIcon += usage.objectUsage.smallIcon
+                t.largeIcon += usage.objectUsage.largeIcon
+                t.styleIcon += usage.objectUsage.styleIcon
+                t.bigPicture += usage.objectUsage.bigPicture
+                t.extender += usage.objectUsage.extender
+                t.extras += usage.objectUsage.extras
+                t
+            }
+
+        pw.println()
+        pw.println("TOTALS")
+        pw.println(
+            "".padEnd(35) +
+                "\t\t" +
+                "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
+                "".padEnd(15) +
+                "\t\t${toKb(totals.styleIcon)}\t" +
+                "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
+                toKb(totals.extras)
+        )
+        pw.println()
+    }
+
+    /** Renders a table of notification view usage into passed [PrintWriter] */
+    private fun dumpNotificationViewUsage(
+        pw: PrintWriter,
+        memoryUse: List<NotificationMemoryUsage>,
+    ) {
+
+        data class Totals(
+            var smallIcon: Int = 0,
+            var largeIcon: Int = 0,
+            var style: Int = 0,
+            var customViews: Int = 0,
+            var softwareBitmapsPenalty: Int = 0,
+        )
+
+        val totals = Totals()
+        pw.println("Notification View Usage")
+        pw.println("-----------")
+        pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
+        pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
+        pw.println()
+        memoryUse
+            .filter { it.viewUsage.isNotEmpty() }
+            .forEach { use ->
+                pw.println(use.packageName + " " + use.notificationKey)
+                use.viewUsage.forEach { view ->
+                    pw.println(
+                        "  ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
+                            "\t${view.largeIcon}\t${view.style}" +
+                            "\t${view.customViews}\t${view.softwareBitmapsPenalty}"
+                    )
+
+                    if (view.viewType == ViewType.TOTAL) {
+                        totals.smallIcon += view.smallIcon
+                        totals.largeIcon += view.largeIcon
+                        totals.style += view.style
+                        totals.customViews += view.customViews
+                        totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
+                    }
+                }
+            }
+        pw.println()
+        pw.println("TOTALS")
+        pw.println(
+            "  ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
+                "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
+                "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
+        )
+        pw.println()
+    }
+
+    private fun styleEnumToString(styleEnum: Int): String =
+        when (styleEnum) {
+            NotificationEnums.STYLE_UNSPECIFIED -> "Unspecified"
+            NotificationEnums.STYLE_NONE -> "None"
+            NotificationEnums.STYLE_BIG_PICTURE -> "BigPicture"
+            NotificationEnums.STYLE_BIG_TEXT -> "BigText"
+            NotificationEnums.STYLE_CALL -> "Call"
+            NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW -> "DCustomView"
+            NotificationEnums.STYLE_INBOX -> "Inbox"
+            NotificationEnums.STYLE_MEDIA -> "Media"
+            NotificationEnums.STYLE_MESSAGING -> "Messaging"
+            NotificationEnums.STYLE_RANKER_GROUP -> "RankerGroup"
+            else -> "Unknown"
+        }
+
+    private fun toKb(bytes: Int): String {
+        return (bytes / 1024).toString() + " KB"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
new file mode 100644
index 0000000..ec8501a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
@@ -0,0 +1,194 @@
+/*
+ *
+ * Copyright (C) 2022 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.systemui.statusbar.notification.logging
+
+import android.app.StatsManager
+import android.util.StatsEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.util.traceSection
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.runBlocking
+
+/** Periodically logs current state of notification memory consumption. */
+@SysUISingleton
+class NotificationMemoryLogger
+@Inject
+constructor(
+    private val notificationPipeline: NotifPipeline,
+    private val statsManager: StatsManager,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Background private val backgroundExecutor: Executor
+) : StatsManager.StatsPullAtomCallback {
+
+    /**
+     * This class is used to accumulate and aggregate data - the fields mirror values in statd Atom
+     * with ONE IMPORTANT difference - the values are in bytes, not KB!
+     */
+    internal data class NotificationMemoryUseAtomBuilder(val uid: Int, val style: Int) {
+        var count: Int = 0
+        var countWithInflatedViews: Int = 0
+        var smallIconObject: Int = 0
+        var smallIconBitmapCount: Int = 0
+        var largeIconObject: Int = 0
+        var largeIconBitmapCount: Int = 0
+        var bigPictureObject: Int = 0
+        var bigPictureBitmapCount: Int = 0
+        var extras: Int = 0
+        var extenders: Int = 0
+        var smallIconViews: Int = 0
+        var largeIconViews: Int = 0
+        var systemIconViews: Int = 0
+        var styleViews: Int = 0
+        var customViews: Int = 0
+        var softwareBitmaps: Int = 0
+        var seenCount = 0
+    }
+
+    fun init() {
+        statsManager.setPullAtomCallback(
+            SysUiStatsLog.NOTIFICATION_MEMORY_USE,
+            null,
+            backgroundExecutor,
+            this
+        )
+    }
+
+    /** Called by statsd to pull data. */
+    override fun onPullAtom(atomTag: Int, data: MutableList<StatsEvent>): Int =
+        traceSection("NML#onPullAtom") {
+            if (atomTag != SysUiStatsLog.NOTIFICATION_MEMORY_USE) {
+                return StatsManager.PULL_SKIP
+            }
+
+            // Notifications can only be retrieved on the main thread, so switch to that thread.
+            val notifications = getAllNotificationsOnMainThread()
+            val notificationMemoryUse =
+                NotificationMemoryMeter.notificationMemoryUse(notifications)
+                    .sortedWith(
+                        compareBy(
+                            { it.packageName },
+                            { it.objectUsage.style },
+                            { it.notificationKey }
+                        )
+                    )
+            val usageData = aggregateMemoryUsageData(notificationMemoryUse)
+            usageData.forEach { (_, use) ->
+                data.add(
+                    SysUiStatsLog.buildStatsEvent(
+                        SysUiStatsLog.NOTIFICATION_MEMORY_USE,
+                        use.uid,
+                        use.style,
+                        use.count,
+                        use.countWithInflatedViews,
+                        toKb(use.smallIconObject),
+                        use.smallIconBitmapCount,
+                        toKb(use.largeIconObject),
+                        use.largeIconBitmapCount,
+                        toKb(use.bigPictureObject),
+                        use.bigPictureBitmapCount,
+                        toKb(use.extras),
+                        toKb(use.extenders),
+                        toKb(use.smallIconViews),
+                        toKb(use.largeIconViews),
+                        toKb(use.systemIconViews),
+                        toKb(use.styleViews),
+                        toKb(use.customViews),
+                        toKb(use.softwareBitmaps),
+                        use.seenCount
+                    )
+                )
+            }
+
+            return StatsManager.PULL_SUCCESS
+        }
+
+    private fun getAllNotificationsOnMainThread() =
+        runBlocking(mainDispatcher) {
+            traceSection("NML#getNotifications") { notificationPipeline.allNotifs }
+        }
+
+    /** Aggregates memory usage data by package and style, returning sums. */
+    private fun aggregateMemoryUsageData(
+        notificationMemoryUse: List<NotificationMemoryUsage>
+    ): Map<Pair<String, Int>, NotificationMemoryUseAtomBuilder> {
+        return notificationMemoryUse
+            .groupingBy { Pair(it.packageName, it.objectUsage.style) }
+            .aggregate {
+                _,
+                accumulator: NotificationMemoryUseAtomBuilder?,
+                element: NotificationMemoryUsage,
+                first ->
+                val use =
+                    if (first) {
+                        NotificationMemoryUseAtomBuilder(element.uid, element.objectUsage.style)
+                    } else {
+                        accumulator!!
+                    }
+
+                use.count++
+                // If the views of the notification weren't inflated, the list of memory usage
+                // parameters will be empty.
+                if (element.viewUsage.isNotEmpty()) {
+                    use.countWithInflatedViews++
+                }
+
+                use.smallIconObject += element.objectUsage.smallIcon
+                if (element.objectUsage.smallIcon > 0) {
+                    use.smallIconBitmapCount++
+                }
+
+                use.largeIconObject += element.objectUsage.largeIcon
+                if (element.objectUsage.largeIcon > 0) {
+                    use.largeIconBitmapCount++
+                }
+
+                use.bigPictureObject += element.objectUsage.bigPicture
+                if (element.objectUsage.bigPicture > 0) {
+                    use.bigPictureBitmapCount++
+                }
+
+                use.extras += element.objectUsage.extras
+                use.extenders += element.objectUsage.extender
+
+                // Use totals count which are more accurate when aggregated
+                // in this manner.
+                element.viewUsage
+                    .firstOrNull { vu -> vu.viewType == ViewType.TOTAL }
+                    ?.let {
+                        use.smallIconViews += it.smallIcon
+                        use.largeIconViews += it.largeIcon
+                        use.systemIconViews += it.systemIcons
+                        use.styleViews += it.style
+                        use.customViews += it.style
+                        use.softwareBitmaps += it.softwareBitmapsPenalty
+                    }
+
+                return@aggregate use
+            }
+    }
+
+    /** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */
+    private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
index 7d39e18..41fb91e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
@@ -1,12 +1,20 @@
 package com.android.systemui.statusbar.notification.logging
 
 import android.app.Notification
+import android.app.Notification.BigPictureStyle
+import android.app.Notification.BigTextStyle
+import android.app.Notification.CallStyle
+import android.app.Notification.DecoratedCustomViewStyle
+import android.app.Notification.InboxStyle
+import android.app.Notification.MediaStyle
+import android.app.Notification.MessagingStyle
 import android.app.Person
 import android.graphics.Bitmap
 import android.graphics.drawable.Icon
 import android.os.Bundle
 import android.os.Parcel
 import android.os.Parcelable
+import android.stats.sysui.NotificationEnums
 import androidx.annotation.WorkerThread
 import com.android.systemui.statusbar.notification.NotificationUtils
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -19,6 +27,7 @@
     private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
     private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
     private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+    private const val AUTOGROUP_KEY = "ranker_group"
 
     /** Returns a list of memory use entries for currently shown notifications. */
     @WorkerThread
@@ -29,12 +38,15 @@
             .asSequence()
             .map { entry ->
                 val packageName = entry.sbn.packageName
+                val uid = entry.sbn.uid
                 val notificationObjectUsage =
                     notificationMemoryUse(entry.sbn.notification, hashSetOf())
                 val notificationViewUsage = NotificationMemoryViewWalker.getViewUsage(entry.row)
                 NotificationMemoryUsage(
                     packageName,
+                    uid,
                     NotificationUtils.logKey(entry.sbn.key),
+                    entry.sbn.notification,
                     notificationObjectUsage,
                     notificationViewUsage
                 )
@@ -49,7 +61,9 @@
     ): NotificationMemoryUsage {
         return NotificationMemoryUsage(
             entry.sbn.packageName,
+            entry.sbn.uid,
             NotificationUtils.logKey(entry.sbn.key),
+            entry.sbn.notification,
             notificationMemoryUse(entry.sbn.notification, seenBitmaps),
             NotificationMemoryViewWalker.getViewUsage(entry.row)
         )
@@ -116,7 +130,13 @@
         val wearExtenderBackground =
             computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
 
-        val style = notification.notificationStyle
+        val style =
+            if (notification.group == AUTOGROUP_KEY) {
+                NotificationEnums.STYLE_RANKER_GROUP
+            } else {
+                styleEnum(notification.notificationStyle)
+            }
+
         val hasCustomView = notification.contentView != null || notification.bigContentView != null
         val extrasSize = computeBundleSize(extras)
 
@@ -124,7 +144,7 @@
             smallIcon = smallIconUse,
             largeIcon = largeIconUse,
             extras = extrasSize,
-            style = style?.simpleName,
+            style = style,
             styleIcon =
                 bigPictureIconUse +
                     peopleUse +
@@ -144,6 +164,25 @@
     }
 
     /**
+     * Returns logging style enum based on current style class.
+     *
+     * @return style value in [NotificationEnums]
+     */
+    private fun styleEnum(style: Class<out Notification.Style>?): Int =
+        when (style?.name) {
+            null -> NotificationEnums.STYLE_NONE
+            BigTextStyle::class.java.name -> NotificationEnums.STYLE_BIG_TEXT
+            BigPictureStyle::class.java.name -> NotificationEnums.STYLE_BIG_PICTURE
+            InboxStyle::class.java.name -> NotificationEnums.STYLE_INBOX
+            MediaStyle::class.java.name -> NotificationEnums.STYLE_MEDIA
+            DecoratedCustomViewStyle::class.java.name ->
+                NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW
+            MessagingStyle::class.java.name -> NotificationEnums.STYLE_MESSAGING
+            CallStyle::class.java.name -> NotificationEnums.STYLE_CALL
+            else -> NotificationEnums.STYLE_UNSPECIFIED
+        }
+
+    /**
      * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
      * bitmaps). Can be slow.
      */
@@ -176,7 +215,7 @@
      *
      * @return memory usage in bytes or 0 if the icon is Uri/Resource based
      */
-    private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
+    private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>): Int =
         when (icon?.type) {
             Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
             Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
index c09cc43..f38c1e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -18,11 +18,10 @@
 package com.android.systemui.statusbar.notification.logging
 
 import android.util.Log
-import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import java.io.PrintWriter
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import dagger.Lazy
 import javax.inject.Inject
 
 /** This class monitors and logs current Notification memory use. */
@@ -30,9 +29,10 @@
 class NotificationMemoryMonitor
 @Inject
 constructor(
-    val notificationPipeline: NotifPipeline,
-    val dumpManager: DumpManager,
-) : Dumpable {
+    private val featureFlags: FeatureFlags,
+    private val notificationMemoryDumper: NotificationMemoryDumper,
+    private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>,
+) {
 
     companion object {
         private const val TAG = "NotificationMemory"
@@ -40,127 +40,10 @@
 
     fun init() {
         Log.d(TAG, "NotificationMemoryMonitor initialized.")
-        dumpManager.registerDumpable(javaClass.simpleName, this)
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        val memoryUse =
-            NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
-                .sortedWith(compareBy({ it.packageName }, { it.notificationKey }))
-        dumpNotificationObjects(pw, memoryUse)
-        dumpNotificationViewUsage(pw, memoryUse)
-    }
-
-    /** Renders a table of notification object usage into passed [PrintWriter]. */
-    private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
-        pw.println("Notification Object Usage")
-        pw.println("-----------")
-        pw.println(
-            "Package".padEnd(35) +
-                "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
-        )
-        pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
-        pw.println()
-
-        memoryUse.forEach { use ->
-            pw.println(
-                use.packageName.padEnd(35) +
-                    "\t\t" +
-                    "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
-                    (use.objectUsage.style?.take(15) ?: "").padEnd(15) +
-                    "\t\t${use.objectUsage.styleIcon}\t" +
-                    "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
-                    "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
-                    use.notificationKey
-            )
+        notificationMemoryDumper.init()
+        if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_LOGGING_ENABLED)) {
+            Log.d(TAG, "Notification memory logging enabled.")
+            notificationMemoryLogger.get().init()
         }
-
-        // Calculate totals for easily glanceable summary.
-        data class Totals(
-            var smallIcon: Int = 0,
-            var largeIcon: Int = 0,
-            var styleIcon: Int = 0,
-            var bigPicture: Int = 0,
-            var extender: Int = 0,
-            var extras: Int = 0,
-        )
-
-        val totals =
-            memoryUse.fold(Totals()) { t, usage ->
-                t.smallIcon += usage.objectUsage.smallIcon
-                t.largeIcon += usage.objectUsage.largeIcon
-                t.styleIcon += usage.objectUsage.styleIcon
-                t.bigPicture += usage.objectUsage.bigPicture
-                t.extender += usage.objectUsage.extender
-                t.extras += usage.objectUsage.extras
-                t
-            }
-
-        pw.println()
-        pw.println("TOTALS")
-        pw.println(
-            "".padEnd(35) +
-                "\t\t" +
-                "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
-                "".padEnd(15) +
-                "\t\t${toKb(totals.styleIcon)}\t" +
-                "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
-                toKb(totals.extras)
-        )
-        pw.println()
-    }
-
-    /** Renders a table of notification view usage into passed [PrintWriter] */
-    private fun dumpNotificationViewUsage(
-        pw: PrintWriter,
-        memoryUse: List<NotificationMemoryUsage>,
-    ) {
-
-        data class Totals(
-            var smallIcon: Int = 0,
-            var largeIcon: Int = 0,
-            var style: Int = 0,
-            var customViews: Int = 0,
-            var softwareBitmapsPenalty: Int = 0,
-        )
-
-        val totals = Totals()
-        pw.println("Notification View Usage")
-        pw.println("-----------")
-        pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
-        pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
-        pw.println()
-        memoryUse
-            .filter { it.viewUsage.isNotEmpty() }
-            .forEach { use ->
-                pw.println(use.packageName + " " + use.notificationKey)
-                use.viewUsage.forEach { view ->
-                    pw.println(
-                        "  ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
-                            "\t${view.largeIcon}\t${view.style}" +
-                            "\t${view.customViews}\t${view.softwareBitmapsPenalty}"
-                    )
-
-                    if (view.viewType == ViewType.TOTAL) {
-                        totals.smallIcon += view.smallIcon
-                        totals.largeIcon += view.largeIcon
-                        totals.style += view.style
-                        totals.customViews += view.customViews
-                        totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
-                    }
-                }
-            }
-        pw.println()
-        pw.println("TOTALS")
-        pw.println(
-            "  ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
-                "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
-                "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
-        )
-        pw.println()
-    }
-
-    private fun toKb(bytes: Int): String {
-        return (bytes / 1024).toString() + " KB"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
index a0bee15..2d04211 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
@@ -50,7 +50,11 @@
 
     /**
      * Returns memory usage of public and private views contained in passed
-     * [ExpandableNotificationRow]
+     * [ExpandableNotificationRow]. Each entry will correspond to one of the [ViewType] values with
+     * [ViewType.TOTAL] totalling all memory use. If a type of view is missing, the corresponding
+     * entry will not appear in resulting list.
+     *
+     * This will return an empty list if the ExpandableNotificationRow has no views inflated.
      */
     fun getViewUsage(row: ExpandableNotificationRow?): List<NotificationViewUsage> {
         if (row == null) {
@@ -58,42 +62,72 @@
         }
 
         // The ordering here is significant since it determines deduplication of seen drawables.
-        return listOf(
-            getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
-            getViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, row.privateLayout?.contractedChild),
-            getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
-            getViewUsage(ViewType.PUBLIC_VIEW, row.publicLayout),
-            getTotalUsage(row)
-        )
+        val perViewUsages =
+            listOf(
+                    getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
+                    getViewUsage(
+                        ViewType.PRIVATE_CONTRACTED_VIEW,
+                        row.privateLayout?.contractedChild
+                    ),
+                    getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
+                    getViewUsage(
+                        ViewType.PUBLIC_VIEW,
+                        row.publicLayout?.expandedChild,
+                        row.publicLayout?.contractedChild,
+                        row.publicLayout?.headsUpChild
+                    ),
+                )
+                .filterNotNull()
+
+        return if (perViewUsages.isNotEmpty()) {
+            // Attach summed totals field only if there was any view actually measured.
+            // This reduces bug report noise and makes checks for collapsed views easier.
+            val totals = getTotalUsage(row)
+            if (totals == null) {
+                perViewUsages
+            } else {
+                perViewUsages + totals
+            }
+        } else {
+            listOf()
+        }
     }
 
     /**
      * Calculate total usage of all views - we need to do a separate traversal to make sure we don't
      * double count fields.
      */
-    private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage {
-        val totalUsage = UsageBuilder()
+    private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage? {
         val seenObjects = hashSetOf<Int>()
-
-        row.publicLayout?.let { computeViewHierarchyUse(it, totalUsage, seenObjects) }
-        row.privateLayout?.let { child ->
-            for (view in listOf(child.expandedChild, child.contractedChild, child.headsUpChild)) {
-                (view as? ViewGroup)?.let { v ->
-                    computeViewHierarchyUse(v, totalUsage, seenObjects)
-                }
-            }
-        }
-        return totalUsage.build(ViewType.TOTAL)
+        return getViewUsage(
+            ViewType.TOTAL,
+            row.privateLayout?.expandedChild,
+            row.privateLayout?.contractedChild,
+            row.privateLayout?.headsUpChild,
+            row.publicLayout?.expandedChild,
+            row.publicLayout?.contractedChild,
+            row.publicLayout?.headsUpChild,
+            seenObjects = seenObjects
+        )
     }
 
     private fun getViewUsage(
         type: ViewType,
-        rootView: View?,
+        vararg rootViews: View?,
         seenObjects: HashSet<Int> = hashSetOf()
-    ): NotificationViewUsage {
-        val usageBuilder = UsageBuilder()
-        (rootView as? ViewGroup)?.let { computeViewHierarchyUse(it, usageBuilder, seenObjects) }
-        return usageBuilder.build(type)
+    ): NotificationViewUsage? {
+        val usageBuilder = lazy { UsageBuilder() }
+        rootViews.forEach { rootView ->
+            (rootView as? ViewGroup)?.let { rootViewGroup ->
+                computeViewHierarchyUse(rootViewGroup, usageBuilder.value, seenObjects)
+            }
+        }
+
+        return if (usageBuilder.isInitialized()) {
+            usageBuilder.value.build(type)
+        } else {
+            null
+        }
     }
 
     private fun computeViewHierarchyUse(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index aff7b4c..b6cf948 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -871,8 +871,7 @@
         }
 
         for (int i = childCount - 1; i >= 0; i--) {
-            childrenOnTop = updateChildZValue(i, childrenOnTop,
-                    algorithmState, ambientState, i == topHunIndex);
+            updateChildZValue(i, algorithmState, ambientState, i == topHunIndex);
         }
     }
 
@@ -882,15 +881,11 @@
      *
      * @param isTopHun      Whether the child is a top HUN. A top HUN means a HUN that shows on the
      *                      vertically top of screen. Top HUNs should have drop shadows
-     * @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated
-     * @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height
-     *                      that overlaps with QQS Panel. The integer part represents the count of
-     *                      previous HUNs whose Z positions are greater than 0.
      */
-    protected float updateChildZValue(int i, float childrenOnTop,
-                                      StackScrollAlgorithmState algorithmState,
-                                      AmbientState ambientState,
-                                      boolean isTopHun) {
+    protected void updateChildZValue(int i,
+                                     StackScrollAlgorithmState algorithmState,
+                                     AmbientState ambientState,
+                                     boolean isTopHun) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
         ExpandableViewState childViewState = child.getViewState();
         float baseZ = ambientState.getBaseZHeight();
@@ -904,22 +899,16 @@
             // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
             // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
             // When scrolling down shade to make HUN back to in-position in Notification Panel,
-            // The over-lapping fraction goes to 0, and shadows hides gradually.
-            if (childrenOnTop != 0.0f) {
-                // To elevate the later HUN over previous HUN
-                childrenOnTop++;
-            } else {
-                float overlap = ambientState.getTopPadding()
-                        + ambientState.getStackTranslation() - childViewState.getYTranslation();
-                // To prevent over-shadow during HUN entry
-                childrenOnTop += Math.min(
-                        1.0f,
-                        overlap / childViewState.height
-                );
-                MathUtils.saturate(childrenOnTop);
+            // the overlapFraction goes to 0, and the pinned HUN's shadows hides gradually.
+            float overlap = ambientState.getTopPadding()
+                    + ambientState.getStackTranslation() - childViewState.getYTranslation();
+
+            if (childViewState.height > 0) { // To avoid 0/0 problems
+                // To prevent over-shadow
+                float overlapFraction = MathUtils.saturate(overlap / childViewState.height);
+                childViewState.setZTranslation(baseZ
+                        + overlapFraction * mPinnedZTranslationExtra);
             }
-            childViewState.setZTranslation(baseZ
-                    + childrenOnTop * mPinnedZTranslationExtra);
         } else if (isTopHun) {
             // In case this is a new view that has never been measured before, we don't want to
             // elevate if we are currently expanded more than the notification
@@ -947,15 +936,14 @@
         }
 
         // Handles HUN shadow when shade is closed.
-        // While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays.
+        // While shade is closed, and during HUN's entry: headerVisibleAmount stays 0, shadow stays.
+        // While shade is closed, and HUN is showing: headerVisibleAmount stays 0, shadow stays.
         // During HUN-to-Shade (eg. dragging down HUN to open Shade): headerVisibleAmount goes
         // gradually from 0 to 1, shadow hides gradually.
         // Header visibility is a deprecated concept, we are using headerVisibleAmount only because
         // this value nicely goes from 0 to 1 during the HUN-to-Shade process.
-
         childViewState.setZTranslation(childViewState.getZTranslation()
                 + (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra);
-        return childrenOnTop;
     }
 
     public void setIsExpanded(boolean isExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 6b72e96..936589c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -371,6 +371,7 @@
 
         if (!mKeyguardStateController.isShowing()) {
             final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
+            cameraIntent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source);
             mCentralSurfaces.startActivityDismissingKeyguard(cameraIntent,
                     false /* onlyProvisioned */, true /* dismissShade */,
                     true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index c217ab3..69f7c71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -22,6 +22,8 @@
 import android.util.MathUtils;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.R;
@@ -197,6 +199,11 @@
         updateHeadsUpAndPulsingRoundness(entry);
     }
 
+    @Override
+    public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
+        updateHeadsUpAndPulsingRoundness(entry);
+    }
+
     private void updateTopEntry() {
         NotificationEntry newEntry = null;
         if (shouldBeVisible()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index d4dc1dc..3483574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -475,9 +475,6 @@
             newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
                     * mKeyguardStatusBarAnimateAlpha
                     * (1.0f - mKeyguardHeadsUpShowingAmount);
-            if (newAlpha != mView.getAlpha() && (newAlpha == 0 || newAlpha == 1)) {
-                mLogger.logStatusBarCalculatedAlpha(newAlpha);
-            }
         }
 
         boolean hideForBypass =
@@ -500,10 +497,6 @@
         if (mDisableStateTracker.isDisabled()) {
             visibility = View.INVISIBLE;
         }
-        if (visibility != mView.getVisibility()) {
-            mLogger.logStatusBarAlphaVisibility(visibility, alpha,
-                    StatusBarState.toString(mStatusBarState));
-        }
         mView.setAlpha(alpha);
         mView.setVisibility(visibility);
     }
@@ -608,10 +601,6 @@
      * @param alpha a value between 0 and 1. -1 if the value is to be reset/ignored.
      */
     public void setAlpha(float alpha) {
-        if (mExplicitAlpha != alpha && (mExplicitAlpha == -1 || alpha == -1)) {
-            // logged if value changed to ignored or from ignored
-            mLogger.logStatusBarExplicitAlpha(alpha);
-        }
         mExplicitAlpha = alpha;
         updateViewState();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index f5b5950..cc67c84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -46,6 +46,7 @@
  * view-model to be reused for multiple view/view-binder bindings.
  */
 @OptIn(InternalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 object WifiViewBinder {
 
     /**
@@ -59,6 +60,12 @@
 
         /** Notifies that the visibility state has changed. */
         fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
+
+        /** Notifies that the icon tint has been updated. */
+        fun onIconTintChanged(newTint: Int)
+
+        /** Notifies that the decor tint has been updated (used only for the dot). */
+        fun onDecorTintChanged(newTint: Int)
     }
 
     /** Binds the view to the view-model, continuing to update the former based on the latter. */
@@ -82,6 +89,9 @@
         @StatusBarIconView.VisibleState
         val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
 
+        val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+        val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch {
@@ -101,7 +111,7 @@
                 }
 
                 launch {
-                    viewModel.tint.collect { tint ->
+                    iconTint.collect { tint ->
                         val tintList = ColorStateList.valueOf(tint)
                         iconView.imageTintList = tintList
                         activityInView.imageTintList = tintList
@@ -110,6 +120,8 @@
                     }
                 }
 
+                launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+
                 launch {
                     viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible ->
                         activityInView.isVisible = visible
@@ -144,6 +156,20 @@
             override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
                 visibilityState.value = state
             }
+
+            override fun onIconTintChanged(newTint: Int) {
+                if (viewModel.useDebugColoring) {
+                    return
+                }
+                iconTint.value = newTint
+            }
+
+            override fun onDecorTintChanged(newTint: Int) {
+                if (viewModel.useDebugColoring) {
+                    return
+                }
+                decorTint.value = newTint
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index a45076b..be7782c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -22,6 +22,7 @@
 import android.view.Gravity
 import android.view.LayoutInflater
 import com.android.systemui.R
+import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.statusbar.BaseStatusBarFrameLayout
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
@@ -51,18 +52,20 @@
             binding.onVisibilityStateChanged(value)
         }
 
-    override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
-        // TODO(b/238425913)
-    }
-
     override fun getSlot() = slot
 
+    override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+        val newTint = DarkIconDispatcher.getTint(areas, this, tint)
+        binding.onIconTintChanged(newTint)
+        binding.onDecorTintChanged(newTint)
+    }
+
     override fun setStaticDrawableColor(color: Int) {
-        // TODO(b/238425913)
+        binding.onIconTintChanged(color)
     }
 
     override fun setDecorColor(color: Int) {
-        // TODO(b/238425913)
+        binding.onDecorTintChanged(color)
     }
 
     override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index e35a8fe..a4615cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flowOf
 
 /**
  * A view model for a wifi icon in a specific location. This allows us to control parameters that
@@ -48,24 +47,12 @@
     /** True if the airplane spacer view should be visible. */
     val isAirplaneSpacerVisible: Flow<Boolean>,
 ) {
-    /** The color that should be used to tint the icon. */
-    val tint: Flow<Int> =
-        flowOf(
-            if (statusBarPipelineFlags.useWifiDebugColoring()) {
-                debugTint
-            } else {
-                DEFAULT_TINT
-            }
-        )
+    val useDebugColoring: Boolean = statusBarPipelineFlags.useWifiDebugColoring()
 
-    companion object {
-        /**
-         * A default icon tint.
-         *
-         * TODO(b/238425913): The tint is actually controlled by
-         * [com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager]. We
-         * should use that logic instead of white as a default.
-         */
-        private const val DEFAULT_TINT = Color.WHITE
-    }
+    val defaultColor: Int =
+        if (useDebugColoring) {
+            debugTint
+        } else {
+            Color.WHITE
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 2a93844..3be14bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -36,9 +36,14 @@
     String getProfileOwnerName();
     CharSequence getDeviceOwnerOrganizationName();
     CharSequence getWorkProfileOrganizationName();
+
+    boolean isFinancedDevice();
+
     /** Device owner component even if not on this user. **/
     ComponentName getDeviceOwnerComponentOnAnyUser();
+    // TODO(b/259908270): remove
     /** Device owner type for a device owner. **/
+    @Deprecated
     int getDeviceOwnerType(ComponentName admin);
     boolean isNetworkLoggingEnabled();
     boolean isVpnEnabled();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index ba94714..03656f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -254,6 +254,7 @@
         return mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser();
     }
 
+    // TODO(b/259908270): remove
     @Override
     @DeviceOwnerType
     public int getDeviceOwnerType(@NonNull ComponentName admin) {
@@ -261,6 +262,11 @@
     }
 
     @Override
+    public boolean isFinancedDevice() {
+        return mDevicePolicyManager.isFinancedDevice();
+    }
+
+    @Override
     public boolean isNetworkLoggingEnabled() {
         return mDevicePolicyManager.isNetworkLoggingEnabled(null);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/OWNERS b/packages/SystemUI/src/com/android/systemui/stylus/OWNERS
new file mode 100644
index 0000000..7ccb316
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stylus/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 1254381
+azappone@google.com
+achalke@google.com
+juliacr@google.com
+madym@google.com
+mgalhardo@google.com
+petrcermak@google.com
+vanjan@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index ea40208..db7315f 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
 import com.android.systemui.util.wakelock.WakeLock
 
 /**
@@ -44,8 +45,24 @@
  *
  * The generic type T is expected to contain all the information necessary for the subclasses to
  * display the view in a certain state, since they receive <T> in [updateView].
+ *
+ * Some information about display ordering:
+ *
+ * [ViewPriority] defines different priorities for the incoming views. The incoming view will be
+ * displayed so long as its priority is equal to or greater than the currently displayed view.
+ * (Concretely, this means that a [ViewPriority.NORMAL] won't be displayed if a
+ * [ViewPriority.CRITICAL] is currently displayed. But otherwise, the incoming view will get
+ * displayed and kick out the old view).
+ *
+ * Once the currently displayed view times out, we *may* display a previously requested view if it
+ * still has enough time left before its own timeout. The same priority ordering applies.
+ *
+ * Note: [TemporaryViewInfo.id] is the identifier that we use to determine if a call to
+ * [displayView] will just update the current view with new information, or display a completely new
+ * view. This means that you *cannot* change the [TemporaryViewInfo.priority] or
+ * [TemporaryViewInfo.windowTitle] while using the same ID.
  */
-abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>(
+abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger<T>>(
     internal val context: Context,
     internal val logger: U,
     internal val windowManager: WindowManager,
@@ -55,6 +72,7 @@
     private val powerManager: PowerManager,
     @LayoutRes private val viewLayoutRes: Int,
     private val wakeLockBuilder: WakeLock.Builder,
+    private val systemClock: SystemClock,
 ) : CoreStartable {
     /**
      * Window layout params that will be used as a starting point for the [windowLayoutParams] of
@@ -78,27 +96,18 @@
      */
     internal abstract val windowLayoutParams: WindowManager.LayoutParams
 
-    /** A container for all the display-related objects. Null if the view is not being displayed. */
-    private var displayInfo: DisplayInfo? = null
-
-    /** A [Runnable] that, when run, will cancel the pending timeout of the view. */
-    private var cancelViewTimeout: Runnable? = null
-
     /**
-     * A wakelock that is acquired when view is displayed and screen off,
-     * then released when view is removed.
+     * A list of the currently active views, ordered from highest priority in the beginning to
+     * lowest priority at the end.
+     *
+     * Whenever the current view disappears, the next-priority view will be displayed if it's still
+     * valid.
      */
-    private var wakeLock: WakeLock? = null
+    internal val activeViews: MutableList<DisplayInfo> = mutableListOf()
 
-    /** A string that keeps track of wakelock reason once it is acquired till it gets released */
-    private var wakeReasonAcquired: String? = null
-
-    /**
-     * A stack of pairs of device id and temporary view info. This is used when there may be
-     * multiple devices in range, and we want to always display the chip for the most recently
-     * active device.
-     */
-    internal val activeViews: ArrayDeque<Pair<String, T>> = ArrayDeque()
+    private fun getCurrentDisplayInfo(): DisplayInfo? {
+        return activeViews.getOrNull(0)
+    }
 
     /**
      * Displays the view with the provided [newInfo].
@@ -107,94 +116,139 @@
      * display the correct information in the view.
      * @param onViewTimeout a runnable that runs after the view timeout.
      */
+    @Synchronized
     fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
-        val currentDisplayInfo = displayInfo
-
-        // Update our list of active devices by removing it if necessary, then adding back at the
-        // front of the list
-        val id = newInfo.id
-        val position = findAndRemoveFromActiveViewsList(id)
-        activeViews.addFirst(Pair(id, newInfo))
-
-        if (currentDisplayInfo != null &&
-            currentDisplayInfo.info.windowTitle == newInfo.windowTitle) {
-            // We're already displaying information in the correctly-titled window, so we just need
-            // to update the view.
-            currentDisplayInfo.info = newInfo
-            updateView(currentDisplayInfo.info, currentDisplayInfo.view)
-        } else {
-            if (currentDisplayInfo != null) {
-                // We're already displaying information but that information is under a different
-                // window title. So, we need to remove the old window with the old title and add a
-                // new window with the new title.
-                removeView(
-                    id,
-                    removalReason = "New info has new window title: ${newInfo.windowTitle}"
-                )
-            }
-
-            // At this point, we're guaranteed to no longer be displaying a view.
-            // So, set up all our callbacks and inflate the view.
-            configurationController.addCallback(displayScaleListener)
-
-            wakeLock = if (!powerManager.isScreenOn) {
-                // If the screen is off, fully wake it so the user can see the view.
-                wakeLockBuilder
-                    .setTag(newInfo.windowTitle)
-                    .setLevelsAndFlags(
-                            PowerManager.FULL_WAKE_LOCK or
-                                PowerManager.ACQUIRE_CAUSES_WAKEUP
-                    )
-                    .build()
-            } else {
-                // Per b/239426653, we want the view to show over the dream state.
-                // If the screen is on, using screen bright level will leave screen on the dream
-                // state but ensure the screen will not go off before wake lock is released.
-                wakeLockBuilder
-                    .setTag(newInfo.windowTitle)
-                    .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
-                    .build()
-            }
-            wakeLock?.acquire(newInfo.wakeReason)
-            wakeReasonAcquired = newInfo.wakeReason
-            logger.logViewAddition(id, newInfo.windowTitle)
-            inflateAndUpdateView(newInfo)
-        }
-
-        // Cancel and re-set the view timeout each time we get a new state.
         val timeout = accessibilityManager.getRecommendedTimeoutMillis(
             newInfo.timeoutMs,
             // Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
             // include it just to be safe.
             FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
-       )
+        )
+        val timeExpirationMillis = systemClock.currentTimeMillis() + timeout
 
-        // Only cancel timeout of the most recent view displayed, as it will be reset.
-        if (position == 0) {
-            cancelViewTimeout?.run()
+        val currentDisplayInfo = getCurrentDisplayInfo()
+
+        // We're current displaying a chipbar with the same ID, we just need to update its info
+        if (currentDisplayInfo != null && currentDisplayInfo.info.id == newInfo.id) {
+            val view = checkNotNull(currentDisplayInfo.view) {
+                "First item in activeViews list must have a valid view"
+            }
+            logger.logViewUpdate(newInfo)
+            currentDisplayInfo.info = newInfo
+            currentDisplayInfo.timeExpirationMillis = timeExpirationMillis
+            updateTimeout(currentDisplayInfo, timeout, onViewTimeout)
+            updateView(newInfo, view)
+            return
         }
-        cancelViewTimeout = mainExecutor.executeDelayed(
+
+        val newDisplayInfo = DisplayInfo(
+            info = newInfo,
+            onViewTimeout = onViewTimeout,
+            timeExpirationMillis = timeExpirationMillis,
+            // Null values will be updated to non-null if/when this view actually gets displayed
+            view = null,
+            wakeLock = null,
+            cancelViewTimeout = null,
+        )
+
+        // We're not displaying anything, so just render this new info
+        if (currentDisplayInfo == null) {
+            addCallbacks()
+            activeViews.add(newDisplayInfo)
+            showNewView(newDisplayInfo, timeout)
+            return
+        }
+
+        // The currently displayed info takes higher priority than the new one.
+        // So, just store the new one in case the current one disappears.
+        if (currentDisplayInfo.info.priority > newInfo.priority) {
+            logger.logViewAdditionDelayed(newInfo)
+            // Remove any old information for this id (if it exists) and re-add it to the list in
+            // the right priority spot
+            removeFromActivesIfNeeded(newInfo.id)
+            var insertIndex = 0
+            while (insertIndex < activeViews.size &&
+                activeViews[insertIndex].info.priority > newInfo.priority) {
+                insertIndex++
+            }
+            activeViews.add(insertIndex, newDisplayInfo)
+            return
+        }
+
+        // Else: The newInfo should be displayed and the currentInfo should be hidden
+        hideView(currentDisplayInfo)
+        // Remove any old information for this id (if it exists) and put this info at the beginning
+        removeFromActivesIfNeeded(newDisplayInfo.info.id)
+        activeViews.add(0, newDisplayInfo)
+        showNewView(newDisplayInfo, timeout)
+    }
+
+    private fun showNewView(newDisplayInfo: DisplayInfo, timeout: Int) {
+        logger.logViewAddition(newDisplayInfo.info)
+        createAndAcquireWakeLock(newDisplayInfo)
+        updateTimeout(newDisplayInfo, timeout, newDisplayInfo.onViewTimeout)
+        inflateAndUpdateView(newDisplayInfo)
+    }
+
+    private fun createAndAcquireWakeLock(displayInfo: DisplayInfo) {
+        // TODO(b/262009503): Migrate off of isScrenOn, since it's deprecated.
+        val newWakeLock = if (!powerManager.isScreenOn) {
+            // If the screen is off, fully wake it so the user can see the view.
+            wakeLockBuilder
+                .setTag(displayInfo.info.windowTitle)
+                .setLevelsAndFlags(
+                    PowerManager.FULL_WAKE_LOCK or
+                        PowerManager.ACQUIRE_CAUSES_WAKEUP
+                )
+                .build()
+        } else {
+            // Per b/239426653, we want the view to show over the dream state.
+            // If the screen is on, using screen bright level will leave screen on the dream
+            // state but ensure the screen will not go off before wake lock is released.
+            wakeLockBuilder
+                .setTag(displayInfo.info.windowTitle)
+                .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
+                .build()
+        }
+        displayInfo.wakeLock = newWakeLock
+        newWakeLock.acquire(displayInfo.info.wakeReason)
+    }
+
+    /**
+     * Creates a runnable that will remove [displayInfo] in [timeout] ms from now.
+     *
+     * @param onViewTimeout an optional runnable that will be run if the view times out.
+     * @return a runnable that, when run, will *cancel* the view's timeout.
+     */
+    private fun updateTimeout(displayInfo: DisplayInfo, timeout: Int, onViewTimeout: Runnable?) {
+        val cancelViewTimeout = mainExecutor.executeDelayed(
             {
-                removeView(id, REMOVAL_REASON_TIMEOUT)
+                removeView(displayInfo.info.id, REMOVAL_REASON_TIMEOUT)
                 onViewTimeout?.run()
             },
             timeout.toLong()
         )
+
+        displayInfo.onViewTimeout = onViewTimeout
+        // Cancel old view timeout and re-set it.
+        displayInfo.cancelViewTimeout?.run()
+        displayInfo.cancelViewTimeout = cancelViewTimeout
     }
 
-    /** Inflates a new view, updates it with [newInfo], and adds the view to the window. */
-    private fun inflateAndUpdateView(newInfo: T) {
+    /** Inflates a new view, updates it with [DisplayInfo.info], and adds the view to the window. */
+    private fun inflateAndUpdateView(displayInfo: DisplayInfo) {
+        val newInfo = displayInfo.info
         val newView = LayoutInflater
                 .from(context)
                 .inflate(viewLayoutRes, null) as ViewGroup
-        val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
-        newViewController.init()
+        displayInfo.view = newView
 
         // We don't need to hold on to the view controller since we never set anything additional
         // on it -- it will be automatically cleaned up when the view is detached.
-        val newDisplayInfo = DisplayInfo(newView, newInfo)
-        displayInfo = newDisplayInfo
-        updateView(newDisplayInfo.info, newDisplayInfo.view)
+        val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
+        newViewController.init()
+
+        updateView(newInfo, newView)
 
         val paramsWithTitle = WindowManager.LayoutParams().also {
             it.copyFrom(windowLayoutParams)
@@ -206,11 +260,15 @@
     }
 
     /** Removes then re-inflates the view. */
+    @Synchronized
     private fun reinflateView() {
-        val currentViewInfo = displayInfo ?: return
+        val currentDisplayInfo = getCurrentDisplayInfo() ?: return
 
-        windowManager.removeView(currentViewInfo.view)
-        inflateAndUpdateView(currentViewInfo.info)
+        val view = checkNotNull(currentDisplayInfo.view) {
+            "First item in activeViews list must have a valid view"
+        }
+        windowManager.removeView(view)
+        inflateAndUpdateView(currentDisplayInfo)
     }
 
     private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
@@ -219,68 +277,109 @@
         }
     }
 
+    private fun addCallbacks() {
+        configurationController.addCallback(displayScaleListener)
+    }
+
+    private fun removeCallbacks() {
+        configurationController.removeCallback(displayScaleListener)
+    }
+
     /**
-     * Hides the view given its [id].
+     * Completely removes the view for the given [id], both visually and from our internal store.
      *
      * @param id the id of the device responsible of displaying the temp view.
      * @param removalReason a short string describing why the view was removed (timeout, state
      *     change, etc.)
      */
+    @Synchronized
     fun removeView(id: String, removalReason: String) {
-        val currentDisplayInfo = displayInfo ?: return
-
-        val removalPosition = findAndRemoveFromActiveViewsList(id)
-        if (removalPosition == null) {
-            logger.logViewRemovalIgnored(id, "view not found in the list")
-            return
-        }
-        if (removalPosition != 0) {
-            logger.logViewRemovalIgnored(id, "most recent view is being displayed.")
-            return
-        }
         logger.logViewRemoval(id, removalReason)
 
-        val newViewToDisplay = if (activeViews.isEmpty()) {
-            null
-        } else {
-            activeViews[0].second
+        val displayInfo = activeViews.firstOrNull { it.info.id == id }
+        if (displayInfo == null) {
+            logger.logViewRemovalIgnored(id, "View not found in list")
+            return
         }
 
-        val currentView = currentDisplayInfo.view
-        animateViewOut(currentView) {
-            windowManager.removeView(currentView)
-            wakeLock?.release(wakeReasonAcquired)
-        }
+        val currentlyDisplayedView = activeViews[0]
+        // Remove immediately (instead as part of the animation end runnable) so that if a new view
+        // event comes in while this view is animating out, we still display the new view
+        // appropriately.
+        activeViews.remove(displayInfo)
 
-        configurationController.removeCallback(displayScaleListener)
-        // Re-set to null immediately (instead as part of the animation end runnable) so
-        // that if a new view event comes in while this view is animating out, we still display
-        // the new view appropriately.
-        displayInfo = null
         // No need to time the view out since it's already gone
-        cancelViewTimeout?.run()
+        displayInfo.cancelViewTimeout?.run()
+
+        if (displayInfo.view == null) {
+            logger.logViewRemovalIgnored(id, "No view to remove")
+            return
+        }
+
+        if (currentlyDisplayedView.info.id != id) {
+            logger.logViewRemovalIgnored(id, "View isn't the currently displayed view")
+            return
+        }
+
+        removeViewFromWindow(displayInfo)
+
+        // Prune anything that's already timed out before determining if we should re-display a
+        // different chipbar.
+        removeTimedOutViews()
+        val newViewToDisplay = getCurrentDisplayInfo()
 
         if (newViewToDisplay != null) {
-            mainExecutor.executeDelayed({ displayView(newViewToDisplay)}, DISPLAY_VIEW_DELAY)
+            val timeout = newViewToDisplay.timeExpirationMillis - systemClock.currentTimeMillis()
+            // TODO(b/258019006): We may want to have a delay before showing the new view so
+            // that the UI translation looks a bit smoother. But, we expect this to happen
+            // rarely so it may not be worth the extra complexity.
+            showNewView(newViewToDisplay, timeout.toInt())
+        } else {
+            removeCallbacks()
         }
     }
 
     /**
-     * Finds and removes the active view with the given [id] from the stack, or null if there is no
-     * active view with that ID
-     *
-     * @param id that temporary view belonged to.
-     *
-     * @return index of the view in the stack , otherwise null.
+     * Hides the view from the window, but keeps [displayInfo] around in [activeViews] in case it
+     * should be re-displayed later.
      */
-    private fun findAndRemoveFromActiveViewsList(id: String): Int? {
-        for (i in 0 until activeViews.size) {
-            if (activeViews[i].first == id) {
-                activeViews.removeAt(i)
-                return i
-            }
+    private fun hideView(displayInfo: DisplayInfo) {
+        logger.logViewHidden(displayInfo.info)
+        removeViewFromWindow(displayInfo)
+    }
+
+    private fun removeViewFromWindow(displayInfo: DisplayInfo) {
+        val view = displayInfo.view
+        if (view == null) {
+            logger.logViewRemovalIgnored(displayInfo.info.id, "View is null")
+            return
         }
-        return null
+        displayInfo.view = null // Need other places??
+        animateViewOut(view) {
+            windowManager.removeView(view)
+            displayInfo.wakeLock?.release(displayInfo.info.wakeReason)
+        }
+    }
+
+    @Synchronized
+    private fun removeTimedOutViews() {
+        val invalidViews = activeViews
+            .filter { it.timeExpirationMillis <
+                systemClock.currentTimeMillis() + MIN_REQUIRED_TIME_FOR_REDISPLAY }
+
+        invalidViews.forEach {
+            activeViews.remove(it)
+            logger.logViewExpiration(it.info)
+        }
+    }
+
+    @Synchronized
+    private fun removeFromActivesIfNeeded(id: String) {
+        val toRemove = activeViews.find { it.info.id == id }
+        toRemove?.let {
+            it.cancelViewTimeout?.run()
+            activeViews.remove(it)
+        }
     }
 
     /**
@@ -311,17 +410,47 @@
     }
 
     /** A container for all the display-related state objects. */
-    private inner class DisplayInfo(
-        /** The view currently being displayed. */
-        val view: ViewGroup,
+    inner class DisplayInfo(
+        /**
+         * The view currently being displayed.
+         *
+         * Null if this info isn't currently being displayed.
+         */
+        var view: ViewGroup?,
 
-        /** The info currently being displayed. */
+        /** The info that should be displayed if/when this is the highest priority view. */
         var info: T,
+
+        /**
+         * The system time at which this display info should expire and never be displayed again.
+         */
+        var timeExpirationMillis: Long,
+
+        /**
+         * The wake lock currently held by this view. Must be released when the view disappears.
+         *
+         * Null if this info isn't currently being displayed.
+         */
+        var wakeLock: WakeLock?,
+
+        /**
+         * See [displayView].
+         */
+        var onViewTimeout: Runnable?,
+
+        /**
+         * A runnable that, when run, will cancel this view's timeout.
+         *
+         * Null if this info isn't currently being displayed.
+         */
+        var cancelViewTimeout: Runnable?,
     )
+
+    // TODO(b/258019006): Add a dump method that dumps the currently active views.
 }
 
 private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
-const val DISPLAY_VIEW_DELAY = 50L
+private const val MIN_REQUIRED_TIME_FOR_REDISPLAY = 1000
 
 private data class IconInfo(
     val iconName: String,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
index df83960..5596cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
@@ -42,6 +42,20 @@
      * The id of the temporary view.
      */
     abstract val id: String
+
+    /** The priority for this view. */
+    abstract val priority: ViewPriority
 }
 
 const val DEFAULT_TIMEOUT_MILLIS = 10000
+
+/**
+ * The priority of the view being displayed.
+ *
+ * Must be ordered from lowest priority to highest priority. (CRITICAL is currently the highest
+ * priority.)
+ */
+enum class ViewPriority {
+    NORMAL,
+    CRITICAL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 133a384..ec6965a 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -20,20 +20,79 @@
 import com.android.systemui.plugins.log.LogLevel
 
 /** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
-open class TemporaryViewLogger(
+open class TemporaryViewLogger<T : TemporaryViewInfo>(
     internal val buffer: LogBuffer,
     internal val tag: String,
 ) {
-    /** Logs that we added the view with the given [id] in a window titled [windowTitle]. */
-    fun logViewAddition(id: String, windowTitle: String) {
+    fun logViewExpiration(info: T) {
         buffer.log(
             tag,
             LogLevel.DEBUG,
             {
-                str1 = windowTitle
-                str2 = id
+                str1 = info.id
+                str2 = info.windowTitle
+                str3 = info.priority.name
             },
-            { "View added. window=$str1 id=$str2" }
+            { "View timeout has already expired; removing. id=$str1 window=$str2 priority=$str3" }
+        )
+    }
+
+    fun logViewUpdate(info: T) {
+        buffer.log(
+            tag,
+            LogLevel.DEBUG,
+            {
+                str1 = info.id
+                str2 = info.windowTitle
+                str3 = info.priority.name
+            },
+            { "Existing view updated with new data. id=$str1 window=$str2 priority=$str3" }
+        )
+    }
+
+    fun logViewAdditionDelayed(info: T) {
+        buffer.log(
+            tag,
+            LogLevel.DEBUG,
+            {
+                str1 = info.id
+                str2 = info.windowTitle
+                str3 = info.priority.name
+            },
+            {
+                "New view can't be displayed because higher priority view is currently " +
+                    "displayed. New view id=$str1 window=$str2 priority=$str3"
+            }
+        )
+    }
+
+    /** Logs that we added the view with the given information. */
+    fun logViewAddition(info: T) {
+        buffer.log(
+            tag,
+            LogLevel.DEBUG,
+            {
+                str1 = info.id
+                str2 = info.windowTitle
+                str3 = info.priority.name
+            },
+            { "View added. id=$str1 window=$str2 priority=$str3" }
+        )
+    }
+
+    fun logViewHidden(info: T) {
+        buffer.log(
+            tag,
+            LogLevel.DEBUG,
+            {
+                str1 = info.id
+                str2 = info.windowTitle
+                str3 = info.priority.name
+            },
+            {
+                "View hidden in favor of newer view. " +
+                    "Hidden view id=$str1 window=$str2 priority=$str3"
+            }
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 4d91e35..14ba63a 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
 import com.android.systemui.util.view.ViewUtil
 import com.android.systemui.util.wakelock.WakeLock
 import javax.inject.Inject
@@ -77,6 +78,7 @@
         private val viewUtil: ViewUtil,
         private val vibratorHelper: VibratorHelper,
         wakeLockBuilder: WakeLock.Builder,
+        systemClock: SystemClock,
 ) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
         context,
         logger,
@@ -87,6 +89,7 @@
         powerManager,
         R.layout.chipbar,
         wakeLockBuilder,
+        systemClock,
 ) {
 
     private lateinit var parent: ChipbarRootView
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index a3eef80..dd4bd26 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
 import com.android.systemui.temporarydisplay.TemporaryViewInfo
+import com.android.systemui.temporarydisplay.ViewPriority
 
 /**
  * A container for all the state needed to display a chipbar via [ChipbarCoordinator].
@@ -42,6 +43,7 @@
     override val wakeReason: String,
     override val timeoutMs: Int,
     override val id: String,
+    override val priority: ViewPriority,
 ) : TemporaryViewInfo() {
     companion object {
         @AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
index e477cd6..fcfbe0a 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -29,7 +29,7 @@
 @Inject
 constructor(
     @ChipbarLog buffer: LogBuffer,
-) : TemporaryViewLogger(buffer, "ChipbarLog") {
+) : TemporaryViewLogger<ChipbarInfo>(buffer, "ChipbarLog") {
     /**
      * Logs that the chipbar was updated to display in a window named [windowTitle], with [text] and
      * [endItemDesc].
diff --git a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
index 3d07491..166ac9e 100644
--- a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
@@ -18,41 +18,58 @@
 
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.util.Log;
+import android.view.AttachedSurfaceControl;
 import android.view.View;
-import android.view.ViewRootImpl;
 
 import androidx.concurrent.futures.CallbackToFutureAdapter;
 
+import com.android.systemui.dagger.qualifiers.Main;
+
 import com.google.common.util.concurrent.ListenableFuture;
 
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.concurrent.Executor;
 
+import javax.inject.Inject;
+
 /**
  * {@link TouchInsetManager} handles setting the touchable inset regions for a given View. This
  * is useful for passing through touch events for all but select areas.
  */
 public class TouchInsetManager {
+    private static final String TAG = "TouchInsetManager";
     /**
      * {@link TouchInsetSession} provides an individualized session with the
      * {@link TouchInsetManager}, linking any action to the client.
      */
     public static class TouchInsetSession {
         private final TouchInsetManager mManager;
-
         private final HashSet<View> mTrackedViews;
         private final Executor mExecutor;
 
         private final View.OnLayoutChangeListener mOnLayoutChangeListener =
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
-                        -> updateTouchRegion();
+                        -> updateTouchRegions();
+
+        private final View.OnAttachStateChangeListener mAttachListener =
+                new View.OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(View v) {
+                        updateTouchRegions();
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(View v) {
+                        updateTouchRegions();
+                    }
+                };
 
         /**
          * Default constructor
          * @param manager The parent {@link TouchInsetManager} which will be affected by actions on
          *                this session.
-         * @param rootView The parent of views that will be tracked.
          * @param executor An executor for marshalling operations.
          */
         TouchInsetSession(TouchInsetManager manager, Executor executor) {
@@ -68,8 +85,9 @@
         public void addViewToTracking(View view) {
             mExecutor.execute(() -> {
                 mTrackedViews.add(view);
+                view.addOnAttachStateChangeListener(mAttachListener);
                 view.addOnLayoutChangeListener(mOnLayoutChangeListener);
-                updateTouchRegion();
+                updateTouchRegions();
             });
         }
 
@@ -81,22 +99,30 @@
             mExecutor.execute(() -> {
                 mTrackedViews.remove(view);
                 view.removeOnLayoutChangeListener(mOnLayoutChangeListener);
-                updateTouchRegion();
+                view.removeOnAttachStateChangeListener(mAttachListener);
+                updateTouchRegions();
             });
         }
 
-        private void updateTouchRegion() {
-            final Region cumulativeRegion = Region.obtain();
+        private void updateTouchRegions() {
+            mExecutor.execute(() -> {
+                final HashMap<AttachedSurfaceControl, Region> affectedSurfaces = new HashMap<>();
+                mTrackedViews.stream().forEach(view -> {
+                    if (!view.isAttachedToWindow()) {
+                        return;
+                    }
 
-            mTrackedViews.stream().forEach(view -> {
-                final Rect boundaries = new Rect();
-                view.getBoundsOnScreen(boundaries);
-                cumulativeRegion.op(boundaries, Region.Op.UNION);
+                    final AttachedSurfaceControl surface = view.getRootSurfaceControl();
+
+                    if (!affectedSurfaces.containsKey(surface)) {
+                        affectedSurfaces.put(surface, Region.obtain());
+                    }
+                    final Rect boundaries = new Rect();
+                    view.getBoundsOnScreen(boundaries);
+                    affectedSurfaces.get(surface).op(boundaries, Region.Op.UNION);
+                });
+                mManager.setTouchRegions(this, affectedSurfaces);
             });
-
-            mManager.setTouchRegion(this, cumulativeRegion);
-
-            cumulativeRegion.recycle();
         }
 
         /**
@@ -110,32 +136,18 @@
         }
     }
 
-    private final HashMap<TouchInsetSession, Region> mDefinedRegions = new HashMap<>();
+    private final HashMap<TouchInsetSession, HashMap<AttachedSurfaceControl, Region>>
+            mSessionRegions = new HashMap<>();
+    private final HashMap<AttachedSurfaceControl, Region> mLastAffectedSurfaces = new HashMap();
     private final Executor mExecutor;
-    private final View mRootView;
-
-    private final View.OnAttachStateChangeListener mAttachListener =
-            new View.OnAttachStateChangeListener() {
-                @Override
-                public void onViewAttachedToWindow(View v) {
-                    updateTouchInset();
-                }
-
-                @Override
-                public void onViewDetachedFromWindow(View v) {
-                }
-            };
 
     /**
      * Default constructor.
      * @param executor An {@link Executor} to marshal all operations on.
-     * @param rootView The root {@link View} for all views in sessions.
      */
-    public TouchInsetManager(Executor executor, View rootView) {
+    @Inject
+    public TouchInsetManager(@Main Executor executor) {
         mExecutor = executor;
-        mRootView = rootView;
-        mRootView.addOnAttachStateChangeListener(mAttachListener);
-
     }
 
     /**
@@ -151,47 +163,68 @@
     public ListenableFuture<Boolean> checkWithinTouchRegion(int x, int y) {
         return CallbackToFutureAdapter.getFuture(completer -> {
             mExecutor.execute(() -> completer.set(
-                    mDefinedRegions.values().stream().anyMatch(region -> region.contains(x, y))));
+                    mLastAffectedSurfaces.values().stream().anyMatch(
+                            region -> region.contains(x, y))));
 
             return "DreamOverlayTouchMonitor::checkWithinTouchRegion";
         });
     }
 
-    private void updateTouchInset() {
-        final ViewRootImpl viewRootImpl = mRootView.getViewRootImpl();
+    private void updateTouchInsets() {
+        // Get affected
+        final HashMap<AttachedSurfaceControl, Region> affectedSurfaces = new HashMap<>();
+        mSessionRegions.values().stream().forEach(regionMapping -> {
+            regionMapping.entrySet().stream().forEach(entry -> {
+                final AttachedSurfaceControl surface = entry.getKey();
+                if (!affectedSurfaces.containsKey(surface)) {
+                    affectedSurfaces.put(surface, Region.obtain());
+                }
 
-        if (viewRootImpl == null) {
+                affectedSurfaces.get(surface).op(entry.getValue(), Region.Op.UNION);
+            });
+        });
+
+        affectedSurfaces.entrySet().stream().forEach(entry -> {
+            entry.getKey().setTouchableRegion(entry.getValue());
+        });
+
+        mLastAffectedSurfaces.entrySet().forEach(entry -> {
+            final AttachedSurfaceControl surface = entry.getKey();
+            if (!affectedSurfaces.containsKey(surface)) {
+                surface.setTouchableRegion(null);
+            }
+            entry.getValue().recycle();
+        });
+
+        mLastAffectedSurfaces.clear();
+        mLastAffectedSurfaces.putAll(affectedSurfaces);
+    }
+
+    protected void setTouchRegions(TouchInsetSession session,
+            HashMap<AttachedSurfaceControl, Region> regions) {
+        mExecutor.execute(() -> {
+            recycleRegions(session);
+            mSessionRegions.put(session, regions);
+            updateTouchInsets();
+        });
+    }
+
+    private void recycleRegions(TouchInsetSession session) {
+        if (!mSessionRegions.containsKey(session)) {
+            Log.w(TAG,  "Removing a session with no regions:" + session);
             return;
         }
 
-        final Region aggregateRegion = Region.obtain();
-
-        for (Region region : mDefinedRegions.values()) {
-            aggregateRegion.op(region, Region.Op.UNION);
+        for (Region region : mSessionRegions.get(session).values()) {
+            region.recycle();
         }
-
-        viewRootImpl.setTouchableRegion(aggregateRegion);
-
-        aggregateRegion.recycle();
-    }
-
-    protected void setTouchRegion(TouchInsetSession session, Region region) {
-        final Region introducedRegion = Region.obtain(region);
-        mExecutor.execute(() -> {
-            mDefinedRegions.put(session, introducedRegion);
-            updateTouchInset();
-        });
     }
 
     private void clearRegion(TouchInsetSession session) {
         mExecutor.execute(() -> {
-            final Region storedRegion = mDefinedRegions.remove(session);
-
-            if (storedRegion != null) {
-                storedRegion.recycle();
-            }
-
-            updateTouchInset();
+            recycleRegions(session);
+            mSessionRegions.remove(session);
+            updateTouchInsets();
         });
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 1bc0d08..fa3c73a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -115,9 +115,11 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.view.RotationPolicy;
 import com.android.settingslib.Utils;
+import com.android.systemui.Dumpable;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
@@ -146,7 +148,7 @@
  *
  * Methods ending in "H" must be called on the (ui) handler.
  */
-public class VolumeDialogImpl implements VolumeDialog,
+public class VolumeDialogImpl implements VolumeDialog, Dumpable,
         ConfigurationController.ConfigurationListener,
         ViewTreeObserver.OnComputeInternalInsetsListener {
     private static final String TAG = Util.logTag(VolumeDialogImpl.class);
@@ -302,7 +304,8 @@
             ActivityStarter activityStarter,
             InteractionJankMonitor interactionJankMonitor,
             DeviceConfigProxy deviceConfigProxy,
-            Executor executor) {
+            Executor executor,
+            DumpManager dumpManager) {
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
         mController = volumeDialogController;
@@ -329,6 +332,8 @@
             mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur);
         mInteractionJankMonitor = interactionJankMonitor;
 
+        dumpManager.registerDumpable("VolumeDialogImpl", this);
+
         if (mUseBackgroundBlur) {
             final int dialogRowsViewColorAboveBlur = mContext.getColor(
                     R.color.volume_dialog_background_color_above_blur);
@@ -793,7 +798,10 @@
         return null;
     }
 
-    public void dump(PrintWriter writer) {
+    /**
+     * Print dump info for debugging.
+     */
+    public void dump(PrintWriter writer, String[] unusedArgs) {
         writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
         writer.print("  mShowing: "); writer.println(mShowing);
         writer.print("  mActiveStream: "); writer.println(mActiveStream);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 8f10fa6..0ab6c69 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -21,6 +21,7 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
@@ -61,7 +62,8 @@
             ActivityStarter activityStarter,
             InteractionJankMonitor interactionJankMonitor,
             DeviceConfigProxy deviceConfigProxy,
-            @Main Executor executor) {
+            @Main Executor executor,
+            DumpManager dumpManager) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
                 volumeDialogController,
@@ -73,7 +75,8 @@
                 activityStarter,
                 interactionJankMonitor,
                 deviceConfigProxy,
-                executor);
+                executor,
+                dumpManager);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
         impl.setSilentMode(false);
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index aa48900..51742990 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.wallpapers;
 
-import static com.android.systemui.flags.Flags.USE_CANVAS_RENDERER;
-
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.graphics.Bitmap;
@@ -27,17 +25,12 @@
 import android.graphics.RectF;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
-import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.service.wallpaper.WallpaperService;
-import android.util.ArraySet;
 import android.util.Log;
-import android.util.MathUtils;
-import android.util.Size;
 import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.view.WindowManager;
@@ -46,16 +39,10 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.wallpapers.canvas.WallpaperLocalColorExtractor;
-import com.android.systemui.wallpapers.gl.EglHelper;
-import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
 
 import java.io.FileDescriptor;
-import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
@@ -65,33 +52,28 @@
  */
 @SuppressWarnings({"UnusedDeclaration"})
 public class ImageWallpaper extends WallpaperService {
+
     private static final String TAG = ImageWallpaper.class.getSimpleName();
-    // We delayed destroy render context that subsequent render requests have chance to cancel it.
-    // This is to avoid destroying then recreating render context in a very short time.
-    private static final int DELAY_FINISH_RENDERING = 1000;
-    private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS =
-            new RectF(0, 0, 1, 1);
     private static final boolean DEBUG = false;
 
-    private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
-    private final ArraySet<RectF> mColorAreas = new ArraySet<>();
+    // keep track of the number of pages of the launcher for local color extraction purposes
     private volatile int mPages = 1;
     private boolean mPagesComputed = false;
-    private HandlerThread mWorker;
-    // scaled down version
-    private Bitmap mMiniBitmap;
-    private final FeatureFlags mFeatureFlags;
 
-    // used in canvasEngine to load/unload the bitmap and extract the colors
+    // used to handle WallpaperService messages (e.g. DO_ATTACH, MSG_UPDATE_SURFACE)
+    // and to receive WallpaperService callbacks (e.g. onCreateEngine, onSurfaceRedrawNeeded)
+    private HandlerThread mWorker;
+
+    // used for most tasks (call canvas.drawBitmap, load/unload the bitmap)
     @Background
     private final DelayableExecutor mBackgroundExecutor;
+
+    // wait at least this duration before unloading the bitmap
     private static final int DELAY_UNLOAD_BITMAP = 2000;
 
     @Inject
-    public ImageWallpaper(FeatureFlags featureFlags,
-            @Background DelayableExecutor backgroundExecutor) {
+    public ImageWallpaper(@Background DelayableExecutor backgroundExecutor) {
         super();
-        mFeatureFlags = featureFlags;
         mBackgroundExecutor = backgroundExecutor;
     }
 
@@ -113,416 +95,9 @@
 
     @Override
     public Engine onCreateEngine() {
-        return mFeatureFlags.isEnabled(USE_CANVAS_RENDERER) ? new CanvasEngine() : new GLEngine();
+        return new CanvasEngine();
     }
 
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mWorker.quitSafely();
-        mWorker = null;
-        mMiniBitmap = null;
-    }
-
-    class GLEngine extends Engine implements DisplayListener {
-        // Surface is rejected if size below a threshold on some devices (ie. 8px on elfin)
-        // set min to 64 px (CTS covers this), please refer to ag/4867989 for detail.
-        @VisibleForTesting
-        static final int MIN_SURFACE_WIDTH = 128;
-        @VisibleForTesting
-        static final int MIN_SURFACE_HEIGHT = 128;
-
-        private ImageWallpaperRenderer mRenderer;
-        private EglHelper mEglHelper;
-        private final Runnable mFinishRenderingTask = this::finishRendering;
-        private boolean mNeedRedraw;
-
-        private boolean mDisplaySizeValid = false;
-        private int mDisplayWidth = 1;
-        private int mDisplayHeight = 1;
-
-        private int mImgWidth = 1;
-        private int mImgHeight = 1;
-
-        GLEngine() { }
-
-        @VisibleForTesting
-        GLEngine(Handler handler) {
-            super(SystemClock::elapsedRealtime, handler);
-        }
-
-        @Override
-        public void onCreate(SurfaceHolder surfaceHolder) {
-            Trace.beginSection("ImageWallpaper.Engine#onCreate");
-            mEglHelper = getEglHelperInstance();
-            // Deferred init renderer because we need to get wallpaper by display context.
-            mRenderer = getRendererInstance();
-            setFixedSizeAllowed(true);
-            updateSurfaceSize();
-            setShowForAllUsers(true);
-
-            mRenderer.setOnBitmapChanged(b -> {
-                mLocalColorsToAdd.addAll(mColorAreas);
-                if (mLocalColorsToAdd.size() > 0) {
-                    updateMiniBitmapAndNotify(b);
-                }
-            });
-            getDisplayContext().getSystemService(DisplayManager.class)
-                    .registerDisplayListener(this, mWorker.getThreadHandler());
-            Trace.endSection();
-        }
-
-        @Override
-        public void onDisplayAdded(int displayId) { }
-
-        @Override
-        public void onDisplayRemoved(int displayId) { }
-
-        @Override
-        public void onDisplayChanged(int displayId) {
-            if (displayId == getDisplayContext().getDisplayId()) {
-                mDisplaySizeValid = false;
-            }
-        }
-
-        EglHelper getEglHelperInstance() {
-            return new EglHelper();
-        }
-
-        ImageWallpaperRenderer getRendererInstance() {
-            return new ImageWallpaperRenderer(getDisplayContext());
-        }
-
-        @Override
-        public void onOffsetsChanged(float xOffset, float yOffset,
-                float xOffsetStep, float yOffsetStep,
-                int xPixelOffset, int yPixelOffset) {
-            final int pages;
-            if (xOffsetStep > 0 && xOffsetStep <= 1) {
-                pages = (int) Math.round(1 / xOffsetStep) + 1;
-            } else {
-                pages = 1;
-            }
-            if (pages == mPages) return;
-            mPages = pages;
-            if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return;
-            mWorker.getThreadHandler().post(() ->
-                    computeAndNotifyLocalColors(new ArrayList<>(mColorAreas), mMiniBitmap));
-        }
-
-        private void updateMiniBitmapAndNotify(Bitmap b) {
-            if (b == null) return;
-            int size = Math.min(b.getWidth(), b.getHeight());
-            float scale = 1.0f;
-            if (size > MIN_SURFACE_WIDTH) {
-                scale = (float) MIN_SURFACE_WIDTH / (float) size;
-            }
-            mImgHeight = b.getHeight();
-            mImgWidth = b.getWidth();
-            mMiniBitmap = Bitmap.createScaledBitmap(b,  (int) Math.max(scale * b.getWidth(), 1),
-                    (int) Math.max(scale * b.getHeight(), 1), false);
-            computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap);
-            mLocalColorsToAdd.clear();
-        }
-
-        private void updateSurfaceSize() {
-            Trace.beginSection("ImageWallpaper#updateSurfaceSize");
-            SurfaceHolder holder = getSurfaceHolder();
-            Size frameSize = mRenderer.reportSurfaceSize();
-            int width = Math.max(MIN_SURFACE_WIDTH, frameSize.getWidth());
-            int height = Math.max(MIN_SURFACE_HEIGHT, frameSize.getHeight());
-            holder.setFixedSize(width, height);
-            Trace.endSection();
-        }
-
-        @Override
-        public boolean shouldZoomOutWallpaper() {
-            return true;
-        }
-
-        @Override
-        public boolean shouldWaitForEngineShown() {
-            return true;
-        }
-
-        @Override
-        public void onDestroy() {
-            getDisplayContext().getSystemService(DisplayManager.class)
-                    .unregisterDisplayListener(this);
-            mMiniBitmap = null;
-            mWorker.getThreadHandler().post(() -> {
-                mRenderer.finish();
-                mRenderer = null;
-                mEglHelper.finish();
-                mEglHelper = null;
-            });
-        }
-
-        @Override
-        public boolean supportsLocalColorExtraction() {
-            return true;
-        }
-
-        @Override
-        public void addLocalColorsAreas(@NonNull List<RectF> regions) {
-            mWorker.getThreadHandler().post(() -> {
-                if (mColorAreas.size() + mLocalColorsToAdd.size() == 0) {
-                    setOffsetNotificationsEnabled(true);
-                }
-                Bitmap bitmap = mMiniBitmap;
-                if (bitmap == null) {
-                    mLocalColorsToAdd.addAll(regions);
-                    if (mRenderer != null) mRenderer.use(this::updateMiniBitmapAndNotify);
-                } else {
-                    computeAndNotifyLocalColors(regions, bitmap);
-                }
-            });
-        }
-
-        private void computeAndNotifyLocalColors(@NonNull List<RectF> regions, Bitmap b) {
-            List<WallpaperColors> colors = getLocalWallpaperColors(regions, b);
-            mColorAreas.addAll(regions);
-            try {
-                notifyLocalColorsChanged(regions, colors);
-            } catch (RuntimeException e) {
-                Log.e(TAG, e.getMessage(), e);
-            }
-        }
-
-        @Override
-        public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
-            mWorker.getThreadHandler().post(() -> {
-                mColorAreas.removeAll(regions);
-                mLocalColorsToAdd.removeAll(regions);
-                if (mColorAreas.size() + mLocalColorsToAdd.size() == 0) {
-                    setOffsetNotificationsEnabled(false);
-                }
-            });
-        }
-
-        /**
-         * Transform the logical coordinates into wallpaper coordinates.
-         *
-         * Logical coordinates are organised such that the various pages are non-overlapping. So,
-         * if there are n pages, the first page will have its X coordinate on the range [0-1/n].
-         *
-         * The real pages are overlapping. If the Wallpaper are a width Ww and the screen a width
-         * Ws, the relative width of a page Wr is Ws/Ww. This does not change if the number of
-         * pages increase.
-         * If there are n pages, the page k starts at the offset k * (1 - Wr) / (n - 1), as the
-         * last page is at position (1-Wr) and the others are regularly spread on the range [0-
-         * (1-Wr)].
-         */
-        private RectF pageToImgRect(RectF area) {
-            if (!mDisplaySizeValid) {
-                Rect window = getDisplayContext()
-                        .getSystemService(WindowManager.class)
-                        .getCurrentWindowMetrics()
-                        .getBounds();
-                mDisplayWidth = window.width();
-                mDisplayHeight = window.height();
-                mDisplaySizeValid = true;
-            }
-
-            // Width of a page for the caller of this API.
-            float virtualPageWidth = 1f / (float) mPages;
-            float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth;
-            float rightPosOnPage = (area.right % virtualPageWidth) / virtualPageWidth;
-            int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth);
-
-            RectF imgArea = new RectF();
-
-            if (mImgWidth == 0 || mImgHeight == 0 || mDisplayWidth <= 0 || mDisplayHeight <= 0) {
-                return imgArea;
-            }
-
-            imgArea.bottom = area.bottom;
-            imgArea.top = area.top;
-
-            float imageScale = Math.min(((float) mImgHeight) / mDisplayHeight, 1);
-            float mappedScreenWidth = mDisplayWidth * imageScale;
-            float pageWidth = Math.min(1.0f,
-                    mImgWidth > 0 ? mappedScreenWidth / (float) mImgWidth : 1.f);
-            float pageOffset = (1 - pageWidth) / (float) (mPages - 1);
-
-            imgArea.left = MathUtils.constrain(
-                    leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
-            imgArea.right = MathUtils.constrain(
-                    rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
-            if (imgArea.left > imgArea.right) {
-                // take full page
-                imgArea.left = 0;
-                imgArea.right = 1;
-            }
-            return imgArea;
-        }
-
-        private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
-                Bitmap b) {
-            List<WallpaperColors> colors = new ArrayList<>(areas.size());
-            for (int i = 0; i < areas.size(); i++) {
-                RectF area = pageToImgRect(areas.get(i));
-                if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
-                    colors.add(null);
-                    continue;
-                }
-                Rect subImage = new Rect(
-                        (int) Math.floor(area.left * b.getWidth()),
-                        (int) Math.floor(area.top * b.getHeight()),
-                        (int) Math.ceil(area.right * b.getWidth()),
-                        (int) Math.ceil(area.bottom * b.getHeight()));
-                if (subImage.isEmpty()) {
-                    // Do not notify client. treat it as too small to sample
-                    colors.add(null);
-                    continue;
-                }
-                Bitmap colorImg = Bitmap.createBitmap(b,
-                        subImage.left, subImage.top, subImage.width(), subImage.height());
-                WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
-                colors.add(color);
-            }
-            return colors;
-        }
-
-        @Override
-        public void onSurfaceCreated(SurfaceHolder holder) {
-            if (mWorker == null) return;
-            mWorker.getThreadHandler().post(() -> {
-                Trace.beginSection("ImageWallpaper#onSurfaceCreated");
-                mEglHelper.init(holder, needSupportWideColorGamut());
-                mRenderer.onSurfaceCreated();
-                Trace.endSection();
-            });
-        }
-
-        @Override
-        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-            if (mWorker == null) return;
-            mWorker.getThreadHandler().post(() -> mRenderer.onSurfaceChanged(width, height));
-        }
-
-        @Override
-        public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
-            if (mWorker == null) return;
-            mWorker.getThreadHandler().post(this::drawFrame);
-        }
-
-        private void drawFrame() {
-            Trace.beginSection("ImageWallpaper#drawFrame");
-            preRender();
-            requestRender();
-            postRender();
-            Trace.endSection();
-        }
-
-        public void preRender() {
-            // This method should only be invoked from worker thread.
-            Trace.beginSection("ImageWallpaper#preRender");
-            preRenderInternal();
-            Trace.endSection();
-        }
-
-        private void preRenderInternal() {
-            boolean contextRecreated = false;
-            Rect frame = getSurfaceHolder().getSurfaceFrame();
-            cancelFinishRenderingTask();
-
-            // Check if we need to recreate egl context.
-            if (!mEglHelper.hasEglContext()) {
-                mEglHelper.destroyEglSurface();
-                if (!mEglHelper.createEglContext()) {
-                    Log.w(TAG, "recreate egl context failed!");
-                } else {
-                    contextRecreated = true;
-                }
-            }
-
-            // Check if we need to recreate egl surface.
-            if (mEglHelper.hasEglContext() && !mEglHelper.hasEglSurface()) {
-                if (!mEglHelper.createEglSurface(getSurfaceHolder(), needSupportWideColorGamut())) {
-                    Log.w(TAG, "recreate egl surface failed!");
-                }
-            }
-
-            // If we recreate egl context, notify renderer to setup again.
-            if (mEglHelper.hasEglContext() && mEglHelper.hasEglSurface() && contextRecreated) {
-                mRenderer.onSurfaceCreated();
-                mRenderer.onSurfaceChanged(frame.width(), frame.height());
-            }
-        }
-
-        public void requestRender() {
-            // This method should only be invoked from worker thread.
-            Trace.beginSection("ImageWallpaper#requestRender");
-            requestRenderInternal();
-            Trace.endSection();
-        }
-
-        private void requestRenderInternal() {
-            Rect frame = getSurfaceHolder().getSurfaceFrame();
-            boolean readyToRender = mEglHelper.hasEglContext() && mEglHelper.hasEglSurface()
-                    && frame.width() > 0 && frame.height() > 0;
-
-            if (readyToRender) {
-                mRenderer.onDrawFrame();
-                if (!mEglHelper.swapBuffer()) {
-                    Log.e(TAG, "drawFrame failed!");
-                }
-            } else {
-                Log.e(TAG, "requestRender: not ready, has context=" + mEglHelper.hasEglContext()
-                        + ", has surface=" + mEglHelper.hasEglSurface()
-                        + ", frame=" + frame);
-            }
-        }
-
-        public void postRender() {
-            // This method should only be invoked from worker thread.
-            scheduleFinishRendering();
-            reportEngineShown(false /* waitForEngineShown */);
-        }
-
-        private void cancelFinishRenderingTask() {
-            if (mWorker == null) return;
-            mWorker.getThreadHandler().removeCallbacks(mFinishRenderingTask);
-        }
-
-        private void scheduleFinishRendering() {
-            if (mWorker == null) return;
-            cancelFinishRenderingTask();
-            mWorker.getThreadHandler().postDelayed(mFinishRenderingTask, DELAY_FINISH_RENDERING);
-        }
-
-        private void finishRendering() {
-            Trace.beginSection("ImageWallpaper#finishRendering");
-            if (mEglHelper != null) {
-                mEglHelper.destroyEglSurface();
-                mEglHelper.destroyEglContext();
-            }
-            Trace.endSection();
-        }
-
-        private boolean needSupportWideColorGamut() {
-            return mRenderer.isWcgContent();
-        }
-
-        @Override
-        protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
-            super.dump(prefix, fd, out, args);
-            out.print(prefix); out.print("Engine="); out.println(this);
-            out.print(prefix); out.print("valid surface=");
-            out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null
-                    ? getSurfaceHolder().getSurface().isValid()
-                    : "null");
-
-            out.print(prefix); out.print("surface frame=");
-            out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null");
-
-            mEglHelper.dump(prefix, fd, out, args);
-            mRenderer.dump(prefix, fd, out, args);
-        }
-    }
-
-
     class CanvasEngine extends WallpaperService.Engine implements DisplayListener {
         private WallpaperManager mWallpaperManager;
         private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
@@ -746,13 +321,8 @@
                 // be loaded, we will go into a cycle. Don't do a build where the
                 // default wallpaper can't be loaded.
                 Log.w(TAG, "Unable to load wallpaper!", exception);
-                try {
-                    mWallpaperManager.clear(WallpaperManager.FLAG_SYSTEM);
-                } catch (IOException ex) {
-                    // now we're really screwed.
-                    Log.w(TAG, "Unable reset to default wallpaper!", ex);
-                }
-
+                mWallpaperManager.clearWallpaper(
+                        WallpaperManager.FLAG_SYSTEM, UserHandle.USER_CURRENT);
                 try {
                     bitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT, false);
                 } catch (RuntimeException | OutOfMemoryError e) {
@@ -878,7 +448,6 @@
             mWallpaperLocalColorExtractor.setDisplayDimensions(window.width(), window.height());
         }
 
-
         @Override
         protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
             super.dump(prefix, fd, out, args);
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractor.java
rename to packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
index 6cac5c9..988fd71 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
@@ -15,7 +15,7 @@
  */
 
 
-package com.android.systemui.wallpapers.canvas;
+package com.android.systemui.wallpapers;
 
 import android.app.WallpaperColors;
 import android.graphics.Bitmap;
@@ -31,7 +31,6 @@
 
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.util.Assert;
-import com.android.systemui.wallpapers.ImageWallpaper;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/gl/EglHelper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/gl/EglHelper.java
deleted file mode 100644
index f9ddce8..0000000
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/gl/EglHelper.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * 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.systemui.wallpapers.gl;
-
-import static android.opengl.EGL14.EGL_ALPHA_SIZE;
-import static android.opengl.EGL14.EGL_BLUE_SIZE;
-import static android.opengl.EGL14.EGL_CONFIG_CAVEAT;
-import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
-import static android.opengl.EGL14.EGL_DEFAULT_DISPLAY;
-import static android.opengl.EGL14.EGL_DEPTH_SIZE;
-import static android.opengl.EGL14.EGL_EXTENSIONS;
-import static android.opengl.EGL14.EGL_GREEN_SIZE;
-import static android.opengl.EGL14.EGL_NONE;
-import static android.opengl.EGL14.EGL_NO_CONTEXT;
-import static android.opengl.EGL14.EGL_NO_DISPLAY;
-import static android.opengl.EGL14.EGL_NO_SURFACE;
-import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT;
-import static android.opengl.EGL14.EGL_RED_SIZE;
-import static android.opengl.EGL14.EGL_RENDERABLE_TYPE;
-import static android.opengl.EGL14.EGL_STENCIL_SIZE;
-import static android.opengl.EGL14.EGL_SUCCESS;
-import static android.opengl.EGL14.eglChooseConfig;
-import static android.opengl.EGL14.eglCreateContext;
-import static android.opengl.EGL14.eglCreateWindowSurface;
-import static android.opengl.EGL14.eglDestroyContext;
-import static android.opengl.EGL14.eglDestroySurface;
-import static android.opengl.EGL14.eglGetDisplay;
-import static android.opengl.EGL14.eglGetError;
-import static android.opengl.EGL14.eglInitialize;
-import static android.opengl.EGL14.eglMakeCurrent;
-import static android.opengl.EGL14.eglQueryString;
-import static android.opengl.EGL14.eglSwapBuffers;
-import static android.opengl.EGL14.eglTerminate;
-
-import android.opengl.EGLConfig;
-import android.opengl.EGLContext;
-import android.opengl.EGLDisplay;
-import android.opengl.EGLSurface;
-import android.opengl.GLUtils;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.SurfaceHolder;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * A helper class to handle EGL management.
- */
-public class EglHelper {
-    private static final String TAG = EglHelper.class.getSimpleName();
-    private static final int OPENGLES_VERSION = 2;
-    // Below two constants make drawing at low priority, so other things can preempt our drawing.
-    private static final int EGL_CONTEXT_PRIORITY_LEVEL_IMG = 0x3100;
-    private static final int EGL_CONTEXT_PRIORITY_LOW_IMG = 0x3103;
-    private static final boolean DEBUG = true;
-
-    private static final int EGL_GL_COLORSPACE_KHR = 0x309D;
-    private static final int EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490;
-
-    private static final String EGL_IMG_CONTEXT_PRIORITY = "EGL_IMG_context_priority";
-
-    /**
-     * https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_gl_colorspace.txt
-     */
-    private static final String KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace";
-
-    /**
-     * https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_display_p3_passthrough.txt
-     */
-    private static final String EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH =
-            "EGL_EXT_gl_colorspace_display_p3_passthrough";
-
-    private EGLDisplay mEglDisplay;
-    private EGLConfig mEglConfig;
-    private EGLContext mEglContext;
-    private EGLSurface mEglSurface;
-    private final int[] mEglVersion = new int[2];
-    private boolean mEglReady;
-    private final Set<String> mExts;
-
-    public EglHelper() {
-        mExts = new HashSet<>();
-        connectDisplay();
-    }
-
-    /**
-     * Initialize render context.
-     * @param surfaceHolder surface holder.
-     * @param wideColorGamut claim if a wcg surface is necessary.
-     * @return true if the render context is ready.
-     */
-    public boolean init(SurfaceHolder surfaceHolder, boolean wideColorGamut) {
-        if (!hasEglDisplay() && !connectDisplay()) {
-            Log.w(TAG, "Can not connect display, abort!");
-            return false;
-        }
-
-        if (!eglInitialize(mEglDisplay, mEglVersion, 0 /* majorOffset */,
-                    mEglVersion, 1 /* minorOffset */)) {
-            Log.w(TAG, "eglInitialize failed: " + GLUtils.getEGLErrorString(eglGetError()));
-            return false;
-        }
-
-        mEglConfig = chooseEglConfig();
-        if (mEglConfig == null) {
-            Log.w(TAG, "eglConfig not initialized!");
-            return false;
-        }
-
-        if (!createEglContext()) {
-            Log.w(TAG, "Can't create EGLContext!");
-            return false;
-        }
-
-        if (!createEglSurface(surfaceHolder, wideColorGamut)) {
-            Log.w(TAG, "Can't create EGLSurface!");
-            return false;
-        }
-
-        mEglReady = true;
-        return true;
-    }
-
-    private boolean connectDisplay() {
-        mExts.clear();
-        mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-        if (!hasEglDisplay()) {
-            Log.w(TAG, "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError()));
-            return false;
-        }
-        String queryString = eglQueryString(mEglDisplay, EGL_EXTENSIONS);
-        if (!TextUtils.isEmpty(queryString)) {
-            Collections.addAll(mExts, queryString.split(" "));
-        }
-        return true;
-    }
-
-    boolean checkExtensionCapability(String extName) {
-        return mExts.contains(extName);
-    }
-
-    int getWcgCapability() {
-        if (checkExtensionCapability(EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH)) {
-            return EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
-        }
-        return 0;
-    }
-
-    private EGLConfig chooseEglConfig() {
-        int[] configsCount = new int[1];
-        EGLConfig[] configs = new EGLConfig[1];
-        int[] configSpec = getConfig();
-        if (!eglChooseConfig(mEglDisplay, configSpec, 0, configs, 0, 1, configsCount, 0)) {
-            Log.w(TAG, "eglChooseConfig failed: " + GLUtils.getEGLErrorString(eglGetError()));
-            return null;
-        } else {
-            if (configsCount[0] <= 0) {
-                Log.w(TAG, "eglChooseConfig failed, invalid configs count: " + configsCount[0]);
-                return null;
-            } else {
-                return configs[0];
-            }
-        }
-    }
-
-    private int[] getConfig() {
-        return new int[] {
-            EGL_RED_SIZE, 8,
-            EGL_GREEN_SIZE, 8,
-            EGL_BLUE_SIZE, 8,
-            EGL_ALPHA_SIZE, 0,
-            EGL_DEPTH_SIZE, 0,
-            EGL_STENCIL_SIZE, 0,
-            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-            EGL_CONFIG_CAVEAT, EGL_NONE,
-            EGL_NONE
-        };
-    }
-
-    /**
-     * Prepare an EglSurface.
-     * @param surfaceHolder surface holder.
-     * @param wcg if need to support wcg.
-     * @return true if EglSurface is ready.
-     */
-    public boolean createEglSurface(SurfaceHolder surfaceHolder, boolean wcg) {
-        if (DEBUG) {
-            Log.d(TAG, "createEglSurface start");
-        }
-
-        if (hasEglDisplay() && surfaceHolder.getSurface().isValid()) {
-            int[] attrs = null;
-            int wcgCapability = getWcgCapability();
-            if (wcg && checkExtensionCapability(KHR_GL_COLOR_SPACE) && wcgCapability > 0) {
-                attrs = new int[] {EGL_GL_COLORSPACE_KHR, wcgCapability, EGL_NONE};
-            }
-            mEglSurface = askCreatingEglWindowSurface(surfaceHolder, attrs, 0 /* offset */);
-        } else {
-            Log.w(TAG, "Create EglSurface failed: hasEglDisplay=" + hasEglDisplay()
-                    + ", has valid surface=" + surfaceHolder.getSurface().isValid());
-            return false;
-        }
-
-        if (!hasEglSurface()) {
-            Log.w(TAG, "createWindowSurface failed: " + GLUtils.getEGLErrorString(eglGetError()));
-            return false;
-        }
-
-        if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
-            Log.w(TAG, "eglMakeCurrent failed: " + GLUtils.getEGLErrorString(eglGetError()));
-            return false;
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "createEglSurface done");
-        }
-        return true;
-    }
-
-    EGLSurface askCreatingEglWindowSurface(SurfaceHolder holder, int[] attrs, int offset) {
-        return eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, attrs, offset);
-    }
-
-    /**
-     * Destroy EglSurface.
-     */
-    public void destroyEglSurface() {
-        if (hasEglSurface()) {
-            eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-            eglDestroySurface(mEglDisplay, mEglSurface);
-            mEglSurface = EGL_NO_SURFACE;
-        }
-    }
-
-    /**
-     * Check if we have a valid EglSurface.
-     * @return true if EglSurface is ready.
-     */
-    public boolean hasEglSurface() {
-        return mEglSurface != null && mEglSurface != EGL_NO_SURFACE;
-    }
-
-    /**
-     * Prepare EglContext.
-     * @return true if EglContext is ready.
-     */
-    public boolean createEglContext() {
-        if (DEBUG) {
-            Log.d(TAG, "createEglContext start");
-        }
-
-        int[] attrib_list = new int[5];
-        int idx = 0;
-        attrib_list[idx++] = EGL_CONTEXT_CLIENT_VERSION;
-        attrib_list[idx++] = OPENGLES_VERSION;
-        if (checkExtensionCapability(EGL_IMG_CONTEXT_PRIORITY)) {
-            attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG;
-            attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LOW_IMG;
-        }
-        attrib_list[idx] = EGL_NONE;
-        if (hasEglDisplay()) {
-            mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list, 0);
-        } else {
-            Log.w(TAG, "mEglDisplay is null");
-            return false;
-        }
-
-        if (!hasEglContext()) {
-            Log.w(TAG, "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError()));
-            return false;
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "createEglContext done");
-        }
-        return true;
-    }
-
-    /**
-     * Destroy EglContext.
-     */
-    public void destroyEglContext() {
-        if (hasEglContext()) {
-            eglDestroyContext(mEglDisplay, mEglContext);
-            mEglContext = EGL_NO_CONTEXT;
-        }
-    }
-
-    /**
-     * Check if we have EglContext.
-     * @return true if EglContext is ready.
-     */
-    public boolean hasEglContext() {
-        return mEglContext != null && mEglContext != EGL_NO_CONTEXT;
-    }
-
-    /**
-     * Check if we have EglDisplay.
-     * @return true if EglDisplay is ready.
-     */
-    public boolean hasEglDisplay() {
-        return mEglDisplay != null && mEglDisplay != EGL_NO_DISPLAY;
-    }
-
-    /**
-     * Swap buffer to display.
-     * @return true if swap successfully.
-     */
-    public boolean swapBuffer() {
-        boolean status = eglSwapBuffers(mEglDisplay, mEglSurface);
-        int error = eglGetError();
-        if (error != EGL_SUCCESS) {
-            Log.w(TAG, "eglSwapBuffers failed: " + GLUtils.getEGLErrorString(error));
-        }
-        return status;
-    }
-
-    /**
-     * Destroy EglSurface and EglContext, then terminate EGL.
-     */
-    public void finish() {
-        if (hasEglSurface()) {
-            destroyEglSurface();
-        }
-        if (hasEglContext()) {
-            destroyEglContext();
-        }
-        if (hasEglDisplay()) {
-            terminateEglDisplay();
-        }
-        mEglReady = false;
-    }
-
-    void terminateEglDisplay() {
-        eglTerminate(mEglDisplay);
-        mEglDisplay = EGL_NO_DISPLAY;
-    }
-
-    /**
-     * Called to dump current state.
-     * @param prefix prefix.
-     * @param fd fd.
-     * @param out out.
-     * @param args args.
-     */
-    public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
-        String eglVersion = mEglVersion[0] + "." + mEglVersion[1];
-        out.print(prefix); out.print("EGL version="); out.print(eglVersion);
-        out.print(", "); out.print("EGL ready="); out.print(mEglReady);
-        out.print(", "); out.print("has EglContext="); out.print(hasEglContext());
-        out.print(", "); out.print("has EglSurface="); out.println(hasEglSurface());
-
-        int[] configs = getConfig();
-        StringBuilder sb = new StringBuilder();
-        sb.append('{');
-        for (int egl : configs) {
-            sb.append("0x").append(Integer.toHexString(egl)).append(",");
-        }
-        sb.setCharAt(sb.length() - 1, '}');
-        out.print(prefix); out.print("EglConfig="); out.println(sb.toString());
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/gl/GLWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/wallpapers/gl/GLWallpaperRenderer.java
deleted file mode 100644
index 692ced0..0000000
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/gl/GLWallpaperRenderer.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.systemui.wallpapers.gl;
-
-import android.util.Size;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * A renderer which is responsible for making OpenGL calls to render a frame.
- */
-public interface GLWallpaperRenderer {
-
-    /**
-     * Check if the content to render is a WCG content.
-     */
-    boolean isWcgContent();
-
-    /**
-     * Called when the surface is created or recreated.
-     */
-    void onSurfaceCreated();
-
-    /**
-     * Called when the surface changed size.
-     * @param width surface width.
-     * @param height surface height.
-     */
-    void onSurfaceChanged(int width, int height);
-
-    /**
-     * Called to draw the current frame.
-     */
-    void onDrawFrame();
-
-    /**
-     * Ask renderer to report the surface size it needs.
-     */
-    Size reportSurfaceSize();
-
-    /**
-     * Called when no need to render any more.
-     */
-    void finish();
-
-    /**
-     * Called to dump current state.
-     * @param prefix prefix.
-     * @param fd fd.
-     * @param out out.
-     * @param args args.
-     */
-    void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args);
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/gl/ImageGLProgram.java b/packages/SystemUI/src/com/android/systemui/wallpapers/gl/ImageGLProgram.java
deleted file mode 100644
index d34eca4..0000000
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/gl/ImageGLProgram.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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.systemui.wallpapers.gl;
-
-import static android.opengl.GLES20.GL_FRAGMENT_SHADER;
-import static android.opengl.GLES20.GL_VERTEX_SHADER;
-import static android.opengl.GLES20.glAttachShader;
-import static android.opengl.GLES20.glCompileShader;
-import static android.opengl.GLES20.glCreateProgram;
-import static android.opengl.GLES20.glCreateShader;
-import static android.opengl.GLES20.glGetAttribLocation;
-import static android.opengl.GLES20.glGetUniformLocation;
-import static android.opengl.GLES20.glLinkProgram;
-import static android.opengl.GLES20.glShaderSource;
-import static android.opengl.GLES20.glUseProgram;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
-/**
- * This class takes charge of linking shader codes and then return a handle for OpenGL ES program.
- */
-class ImageGLProgram {
-    private static final String TAG = ImageGLProgram.class.getSimpleName();
-
-    private Context mContext;
-    private int mProgramHandle;
-
-    ImageGLProgram(Context context) {
-        mContext = context.getApplicationContext();
-    }
-
-    private int loadShaderProgram(int vertexId, int fragmentId) {
-        final String vertexSrc = getShaderResource(vertexId);
-        final String fragmentSrc = getShaderResource(fragmentId);
-        final int vertexHandle = getShaderHandle(GL_VERTEX_SHADER, vertexSrc);
-        final int fragmentHandle = getShaderHandle(GL_FRAGMENT_SHADER, fragmentSrc);
-        return getProgramHandle(vertexHandle, fragmentHandle);
-    }
-
-    private String getShaderResource(int shaderId) {
-        Resources res = mContext.getResources();
-        StringBuilder code = new StringBuilder();
-
-        try (BufferedReader reader = new BufferedReader(
-                new InputStreamReader(res.openRawResource(shaderId)))) {
-            String nextLine;
-            while ((nextLine = reader.readLine()) != null) {
-                code.append(nextLine).append("\n");
-            }
-        } catch (IOException | Resources.NotFoundException ex) {
-            Log.d(TAG, "Can not read the shader source", ex);
-            code = null;
-        }
-
-        return code == null ? "" : code.toString();
-    }
-
-    private int getShaderHandle(int type, String src) {
-        final int shader = glCreateShader(type);
-        if (shader == 0) {
-            Log.d(TAG, "Create shader failed, type=" + type);
-            return 0;
-        }
-        glShaderSource(shader, src);
-        glCompileShader(shader);
-        return shader;
-    }
-
-    private int getProgramHandle(int vertexHandle, int fragmentHandle) {
-        final int program = glCreateProgram();
-        if (program == 0) {
-            Log.d(TAG, "Can not create OpenGL ES program");
-            return 0;
-        }
-
-        glAttachShader(program, vertexHandle);
-        glAttachShader(program, fragmentHandle);
-        glLinkProgram(program);
-        return program;
-    }
-
-    boolean useGLProgram(int vertexResId, int fragmentResId) {
-        mProgramHandle = loadShaderProgram(vertexResId, fragmentResId);
-        glUseProgram(mProgramHandle);
-        return true;
-    }
-
-    int getAttributeHandle(String name) {
-        return glGetAttribLocation(mProgramHandle, name);
-    }
-
-    int getUniformHandle(String name) {
-        return glGetUniformLocation(mProgramHandle, name);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/gl/ImageGLWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/gl/ImageGLWallpaper.java
deleted file mode 100644
index f1659b9..0000000
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/gl/ImageGLWallpaper.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * 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.systemui.wallpapers.gl;
-
-import static android.opengl.GLES20.GL_FLOAT;
-import static android.opengl.GLES20.GL_LINEAR;
-import static android.opengl.GLES20.GL_TEXTURE0;
-import static android.opengl.GLES20.GL_TEXTURE_2D;
-import static android.opengl.GLES20.GL_TEXTURE_MAG_FILTER;
-import static android.opengl.GLES20.GL_TEXTURE_MIN_FILTER;
-import static android.opengl.GLES20.GL_TRIANGLES;
-import static android.opengl.GLES20.glActiveTexture;
-import static android.opengl.GLES20.glBindTexture;
-import static android.opengl.GLES20.glDrawArrays;
-import static android.opengl.GLES20.glEnableVertexAttribArray;
-import static android.opengl.GLES20.glGenTextures;
-import static android.opengl.GLES20.glTexParameteri;
-import static android.opengl.GLES20.glUniform1i;
-import static android.opengl.GLES20.glVertexAttribPointer;
-
-import android.graphics.Bitmap;
-import android.opengl.GLUtils;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-
-/**
- * This class takes charge of the geometry data like vertices and texture coordinates.
- * It delivers these data to opengl runtime and triggers draw calls if necessary.
- */
-class ImageGLWallpaper {
-    private static final String TAG = ImageGLWallpaper.class.getSimpleName();
-
-    private static final String A_POSITION = "aPosition";
-    private static final String A_TEXTURE_COORDINATES = "aTextureCoordinates";
-    private static final String U_TEXTURE = "uTexture";
-    private static final int POSITION_COMPONENT_COUNT = 2;
-    private static final int TEXTURE_COMPONENT_COUNT = 2;
-    private static final int BYTES_PER_FLOAT = 4;
-
-    // Vertices to define the square with 2 triangles.
-    private static final float[] VERTICES = {
-            -1.0f,  -1.0f,
-            +1.0f,  -1.0f,
-            +1.0f,  +1.0f,
-            +1.0f,  +1.0f,
-            -1.0f,  +1.0f,
-            -1.0f,  -1.0f
-    };
-
-    // Texture coordinates that maps to vertices.
-    private static final float[] TEXTURES = {
-            0f, 1f,
-            1f, 1f,
-            1f, 0f,
-            1f, 0f,
-            0f, 0f,
-            0f, 1f
-    };
-
-    private final FloatBuffer mVertexBuffer;
-    private final FloatBuffer mTextureBuffer;
-    private final ImageGLProgram mProgram;
-
-    private int mAttrPosition;
-    private int mAttrTextureCoordinates;
-    private int mUniTexture;
-    private int mTextureId;
-
-    ImageGLWallpaper(ImageGLProgram program) {
-        mProgram = program;
-
-        // Create an float array in opengles runtime (native) and put vertex data.
-        mVertexBuffer = ByteBuffer.allocateDirect(VERTICES.length * BYTES_PER_FLOAT)
-            .order(ByteOrder.nativeOrder())
-            .asFloatBuffer();
-        mVertexBuffer.put(VERTICES);
-        mVertexBuffer.position(0);
-
-        // Create an float array in opengles runtime (native) and put texture data.
-        mTextureBuffer = ByteBuffer.allocateDirect(TEXTURES.length * BYTES_PER_FLOAT)
-            .order(ByteOrder.nativeOrder())
-            .asFloatBuffer();
-        mTextureBuffer.put(TEXTURES);
-        mTextureBuffer.position(0);
-    }
-
-    void setup(Bitmap bitmap) {
-        setupAttributes();
-        setupUniforms();
-        setupTexture(bitmap);
-    }
-
-    private void setupAttributes() {
-        mAttrPosition = mProgram.getAttributeHandle(A_POSITION);
-        mVertexBuffer.position(0);
-        glVertexAttribPointer(mAttrPosition, POSITION_COMPONENT_COUNT, GL_FLOAT,
-                false, 0, mVertexBuffer);
-        glEnableVertexAttribArray(mAttrPosition);
-
-        mAttrTextureCoordinates = mProgram.getAttributeHandle(A_TEXTURE_COORDINATES);
-        mTextureBuffer.position(0);
-        glVertexAttribPointer(mAttrTextureCoordinates, TEXTURE_COMPONENT_COUNT, GL_FLOAT,
-                false, 0, mTextureBuffer);
-        glEnableVertexAttribArray(mAttrTextureCoordinates);
-    }
-
-    private void setupUniforms() {
-        mUniTexture = mProgram.getUniformHandle(U_TEXTURE);
-    }
-
-    void draw() {
-        glDrawArrays(GL_TRIANGLES, 0, VERTICES.length / 2);
-    }
-
-    private void setupTexture(Bitmap bitmap) {
-        final int[] tids = new int[1];
-
-        if (bitmap == null || bitmap.isRecycled()) {
-            Log.w(TAG, "setupTexture: invalid bitmap");
-            return;
-        }
-
-        // Generate one texture object and store the id in tids[0].
-        glGenTextures(1, tids, 0);
-        if (tids[0] == 0) {
-            Log.w(TAG, "setupTexture: glGenTextures() failed");
-            return;
-        }
-
-        try {
-            // Bind a named texture to a target.
-            glBindTexture(GL_TEXTURE_2D, tids[0]);
-            // Load the bitmap data and copy it over into the texture object
-            // that is currently bound.
-            GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
-            // Use bilinear texture filtering when minification.
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-            // Use bilinear texture filtering when magnification.
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-            mTextureId = tids[0];
-        } catch (IllegalArgumentException e) {
-            Log.w(TAG, "Failed uploading texture: " + e.getLocalizedMessage());
-        }
-    }
-
-    void useTexture() {
-        // Set the active texture unit to texture unit 0.
-        glActiveTexture(GL_TEXTURE0);
-        // Bind the texture to this unit.
-        glBindTexture(GL_TEXTURE_2D, mTextureId);
-        // Let the texture sampler in fragment shader to read form this texture unit.
-        glUniform1i(mUniTexture, 0);
-    }
-
-    /**
-     * Called to dump current state.
-     * @param prefix prefix.
-     * @param fd fd.
-     * @param out out.
-     * @param args args.
-     */
-    public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/gl/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/wallpapers/gl/ImageWallpaperRenderer.java
deleted file mode 100644
index e393786..0000000
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/gl/ImageWallpaperRenderer.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * 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.systemui.wallpapers.gl;
-
-import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
-import static android.opengl.GLES20.glClear;
-import static android.opengl.GLES20.glClearColor;
-import static android.opengl.GLES20.glViewport;
-
-import android.app.WallpaperManager;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.Size;
-
-import com.android.systemui.R;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
-
-/**
- * A GL renderer for image wallpaper.
- */
-public class ImageWallpaperRenderer implements GLWallpaperRenderer {
-    private static final String TAG = ImageWallpaperRenderer.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
-    private final ImageGLProgram mProgram;
-    private final ImageGLWallpaper mWallpaper;
-    private final Rect mSurfaceSize = new Rect();
-    private final WallpaperTexture mTexture;
-    private Consumer<Bitmap> mOnBitmapUpdated;
-
-    public ImageWallpaperRenderer(Context context) {
-        final WallpaperManager wpm = context.getSystemService(WallpaperManager.class);
-        if (wpm == null) {
-            Log.w(TAG, "WallpaperManager not available");
-        }
-
-        mTexture = new WallpaperTexture(wpm);
-        mProgram = new ImageGLProgram(context);
-        mWallpaper = new ImageGLWallpaper(mProgram);
-    }
-
-    /**
-     * @hide
-     */
-    public void setOnBitmapChanged(Consumer<Bitmap> c) {
-        mOnBitmapUpdated = c;
-    }
-
-    /**
-     * @hide
-     */
-    public void use(Consumer<Bitmap> c) {
-        mTexture.use(c);
-    }
-
-    @Override
-    public boolean isWcgContent() {
-        return mTexture.isWcgContent();
-    }
-
-    @Override
-    public void onSurfaceCreated() {
-        glClearColor(0f, 0f, 0f, 1.0f);
-        mProgram.useGLProgram(
-                R.raw.image_wallpaper_vertex_shader, R.raw.image_wallpaper_fragment_shader);
-
-        mTexture.use(bitmap -> {
-            if (bitmap == null) {
-                Log.w(TAG, "reload texture failed!");
-            } else if (mOnBitmapUpdated != null) {
-                mOnBitmapUpdated.accept(bitmap);
-            }
-            mWallpaper.setup(bitmap);
-        });
-    }
-
-    @Override
-    public void onSurfaceChanged(int width, int height) {
-        glViewport(0, 0, width, height);
-    }
-
-    @Override
-    public void onDrawFrame() {
-        glClear(GL_COLOR_BUFFER_BIT);
-        glViewport(0, 0, mSurfaceSize.width(), mSurfaceSize.height());
-        mWallpaper.useTexture();
-        mWallpaper.draw();
-    }
-
-    @Override
-    public Size reportSurfaceSize() {
-        mSurfaceSize.set(mTexture.getTextureDimensions());
-        return new Size(mSurfaceSize.width(), mSurfaceSize.height());
-    }
-
-    @Override
-    public void finish() {
-    }
-
-    @Override
-    public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
-        out.print(prefix); out.print("mSurfaceSize="); out.print(mSurfaceSize);
-        out.print(prefix); out.print("mWcgContent="); out.print(isWcgContent());
-        mWallpaper.dump(prefix, fd, out, args);
-    }
-
-    static class WallpaperTexture {
-        private final AtomicInteger mRefCount;
-        private final Rect mDimensions;
-        private final WallpaperManager mWallpaperManager;
-        private Bitmap mBitmap;
-        private boolean mWcgContent;
-        private boolean mTextureUsed;
-
-        private WallpaperTexture(WallpaperManager wallpaperManager) {
-            mWallpaperManager = wallpaperManager;
-            mRefCount = new AtomicInteger();
-            mDimensions = new Rect();
-        }
-
-        public void use(Consumer<Bitmap> consumer) {
-            mRefCount.incrementAndGet();
-            synchronized (mRefCount) {
-                if (mBitmap == null) {
-                    mBitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT,
-                            false /* hardware */);
-                    mWcgContent = mWallpaperManager.wallpaperSupportsWcg(
-                            WallpaperManager.FLAG_SYSTEM);
-                    mWallpaperManager.forgetLoadedWallpaper();
-                    if (mBitmap != null) {
-                        mDimensions.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
-                        mTextureUsed = true;
-                    } else {
-                        Log.w(TAG, "Can't get bitmap");
-                    }
-                }
-            }
-            if (consumer != null) {
-                consumer.accept(mBitmap);
-            }
-            synchronized (mRefCount) {
-                final int count = mRefCount.decrementAndGet();
-                if (count == 0 && mBitmap != null) {
-                    if (DEBUG) {
-                        Log.v(TAG, "WallpaperTexture: release 0x" + getHash()
-                                + ", refCount=" + count);
-                    }
-                    mBitmap.recycle();
-                    mBitmap = null;
-                }
-            }
-        }
-
-        private boolean isWcgContent() {
-            return mWcgContent;
-        }
-
-        private String getHash() {
-            return mBitmap != null ? Integer.toHexString(mBitmap.hashCode()) : "null";
-        }
-
-        private Rect getTextureDimensions() {
-            if (!mTextureUsed) {
-                mDimensions.set(mWallpaperManager.peekBitmapDimensions());
-            }
-            return mDimensions;
-        }
-
-        @Override
-        public String toString() {
-            return "{" + getHash() + ", " + mRefCount.get() + "}";
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 4903d31..1059543 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -101,6 +101,11 @@
             public void onResume(int reason) {
                 super.onResume(reason);
             }
+
+            @Override
+            protected int getInitialMessageResId() {
+                return 0;
+            }
         };
         mKeyguardAbsKeyInputViewController.init();
         reset(mKeyguardMessageAreaController);  // Clear out implicit call to init.
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 61c7bb5..c8e7538 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -29,7 +29,6 @@
 
 import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.graphics.Rect;
 import android.net.Uri;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -47,6 +46,8 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.ClockEvents;
+import com.android.systemui.plugins.ClockFaceController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.AnimatableClockView;
 import com.android.systemui.shared.clocks.ClockRegistry;
@@ -88,7 +89,15 @@
     @Mock
     KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock
-    private ClockController mClock;
+    private ClockController mClockController;
+    @Mock
+    private ClockFaceController mLargeClockController;
+    @Mock
+    private ClockFaceController mSmallClockController;
+    @Mock
+    private ClockAnimations mClockAnimations;
+    @Mock
+    private ClockEvents mClockEvents;
     @Mock
     DumpManager mDumpManager;
     @Mock
@@ -97,10 +106,12 @@
     @Mock
     private NotificationIconContainer mNotificationIcons;
     @Mock
-    private AnimatableClockView mClockView;
+    private AnimatableClockView mSmallClockView;
     @Mock
     private AnimatableClockView mLargeClockView;
     @Mock
+    private FrameLayout mSmallClockFrame;
+    @Mock
     private FrameLayout mLargeClockFrame;
     @Mock
     private SecureSettings mSecureSettings;
@@ -121,9 +132,14 @@
                 mock(RelativeLayout.LayoutParams.class));
         when(mView.getContext()).thenReturn(getContext());
         when(mView.getResources()).thenReturn(mResources);
+        when(mResources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin))
+                .thenReturn(100);
+        when(mResources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin))
+                .thenReturn(-200);
 
         when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
-        when(mClockView.getContext()).thenReturn(getContext());
+        when(mView.findViewById(R.id.lockscreen_clock_view)).thenReturn(mSmallClockFrame);
+        when(mSmallClockView.getContext()).thenReturn(getContext());
         when(mLargeClockView.getContext()).thenReturn(getContext());
 
         when(mView.isAttachedToWindow()).thenReturn(true);
@@ -144,7 +160,14 @@
         );
 
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
-        when(mClockRegistry.createCurrentClock()).thenReturn(mClock);
+        when(mLargeClockController.getView()).thenReturn(mLargeClockView);
+        when(mSmallClockController.getView()).thenReturn(mSmallClockView);
+        when(mClockController.getLargeClock()).thenReturn(mLargeClockController);
+        when(mClockController.getSmallClock()).thenReturn(mSmallClockController);
+        when(mClockController.getEvents()).thenReturn(mClockEvents);
+        when(mClockController.getAnimations()).thenReturn(mClockAnimations);
+        when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
+        when(mClockEventController.getClock()).thenReturn(mClockController);
 
         mSliceView = new View(getContext());
         when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
@@ -203,8 +226,8 @@
         verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
 
         listenerArgumentCaptor.getValue().onClockChanged();
-        verify(mView, times(2)).setClock(mClock, StatusBarState.SHADE);
-        verify(mClockEventController, times(2)).setClock(mClock);
+        verify(mView, times(2)).setClock(mClockController, StatusBarState.SHADE);
+        verify(mClockEventController, times(2)).setClock(mClockController);
     }
 
     @Test
@@ -262,17 +285,40 @@
 
     @Test
     public void testGetClockAnimationsForwardsToClock() {
-        ClockController mockClockController = mock(ClockController.class);
-        ClockAnimations mockClockAnimations = mock(ClockAnimations.class);
-        when(mClockEventController.getClock()).thenReturn(mockClockController);
-        when(mockClockController.getAnimations()).thenReturn(mockClockAnimations);
-
-        Rect r1 = new Rect(1, 2, 3, 4);
-        Rect r2 = new Rect(5, 6, 7, 8);
-        mController.getClockAnimations().onPositionUpdated(r1, r2, 0.2f);
-        verify(mockClockAnimations).onPositionUpdated(r1, r2, 0.2f);
+        assertEquals(mClockAnimations, mController.getClockAnimations());
     }
 
+    @Test
+    public void testGetLargeClockBottom_returnsExpectedValue() {
+        when(mLargeClockFrame.getVisibility()).thenReturn(View.VISIBLE);
+        when(mLargeClockFrame.getHeight()).thenReturn(100);
+        when(mSmallClockFrame.getHeight()).thenReturn(50);
+        when(mLargeClockView.getHeight()).thenReturn(40);
+        when(mSmallClockView.getHeight()).thenReturn(20);
+        mController.init();
+
+        assertEquals(170, mController.getClockBottom(1000));
+    }
+
+    @Test
+    public void testGetSmallLargeClockBottom_returnsExpectedValue() {
+        when(mLargeClockFrame.getVisibility()).thenReturn(View.GONE);
+        when(mLargeClockFrame.getHeight()).thenReturn(100);
+        when(mSmallClockFrame.getHeight()).thenReturn(50);
+        when(mLargeClockView.getHeight()).thenReturn(40);
+        when(mSmallClockView.getHeight()).thenReturn(20);
+        mController.init();
+
+        assertEquals(1120, mController.getClockBottom(1000));
+    }
+
+    @Test
+    public void testGetClockBottom_nullClock_returnsZero() {
+        when(mClockEventController.getClock()).thenReturn(null);
+        assertEquals(0, mController.getClockBottom(10));
+    }
+
+
     private void verifyAttachment(VerificationMode times) {
         verify(mClockRegistry, times).registerClockChangeListener(
                 any(ClockRegistry.ClockChangeListener.class));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index ce1101f..b742100 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -113,4 +115,9 @@
         mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
         verify(mPasswordEntry).requestFocus();
     }
+
+    @Test
+    public void testGetInitialMessageResId() {
+        assertThat(mKeyguardPinViewController.getInitialMessageResId()).isNotEqualTo(0);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index bdd29aa..0f4cf41 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -2083,6 +2083,96 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
     }
 
+    @Test
+    public void fingerprintFailure_requestActiveUnlock_dismissKeyguard()
+            throws RemoteException {
+        // GIVEN shouldTriggerActiveUnlock
+        bouncerFullyVisible();
+        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+        // GIVEN active unlock triggers on biometric failures
+        when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+                .thenReturn(true);
+
+        // WHEN fingerprint fails
+        mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback.onAuthenticationFailed();
+
+        // ALWAYS request unlock with a keyguard dismissal
+        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+                eq(true));
+    }
+
+    @Test
+    public void faceNonBypassFailure_requestActiveUnlock_doesNotDismissKeyguard()
+            throws RemoteException {
+        // GIVEN shouldTriggerActiveUnlock
+        when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+        keyguardIsVisible();
+        keyguardNotGoingAway();
+        statusBarShadeIsNotLocked();
+        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+        // GIVEN active unlock triggers on biometric failures
+        when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+                .thenReturn(true);
+
+        // WHEN face fails & bypass is not allowed
+        lockscreenBypassIsNotAllowed();
+        mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+
+        // THEN request unlock with NO keyguard dismissal
+        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+                eq(false));
+    }
+
+    @Test
+    public void faceBypassFailure_requestActiveUnlock_dismissKeyguard()
+            throws RemoteException {
+        // GIVEN shouldTriggerActiveUnlock
+        when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+        keyguardIsVisible();
+        keyguardNotGoingAway();
+        statusBarShadeIsNotLocked();
+        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+        // GIVEN active unlock triggers on biometric failures
+        when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+                .thenReturn(true);
+
+        // WHEN face fails & bypass is not allowed
+        lockscreenBypassIsAllowed();
+        mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+
+        // THEN request unlock with a keyguard dismissal
+        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+                eq(true));
+    }
+
+    @Test
+    public void faceNonBypassFailure_requestActiveUnlock_dismissKeyguard()
+            throws RemoteException {
+        // GIVEN shouldTriggerActiveUnlock
+        when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+        lockscreenBypassIsNotAllowed();
+        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+        // GIVEN active unlock triggers on biometric failures
+        when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+                .thenReturn(true);
+
+        // WHEN face fails & on the bouncer
+        bouncerFullyVisible();
+        mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+
+        // THEN request unlock with a keyguard dismissal
+        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+                eq(true));
+    }
+
     private void userDeviceLockDown() {
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
         when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId))
@@ -2100,6 +2190,9 @@
     }
 
     private void mockCanBypassLockscreen(boolean canBypass) {
+        // force update the isFaceEnrolled cache:
+        mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(getCurrentUser());
+
         mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
         when(mKeyguardBypassController.canBypass()).thenReturn(canBypass);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 517e27a..2d412dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -27,16 +27,19 @@
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.KeyguardBouncer
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineScope
 import kotlinx.coroutines.yield
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.MockitoAnnotations
 
@@ -45,6 +48,7 @@
 @TestableLooper.RunWithLooper
 class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControllerBaseTest() {
     lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+    @Mock private lateinit var bouncerLogger: TableLogBuffer
 
     @Before
     override fun setUp() {
@@ -53,7 +57,8 @@
         keyguardBouncerRepository =
             KeyguardBouncerRepository(
                 mock(com.android.keyguard.ViewMediatorCallback::class.java),
-                mKeyguardUpdateMonitor
+                TestCoroutineScope(),
+                bouncerLogger,
             )
         super.setUp()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index ca94ea8..262b4b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -301,7 +301,7 @@
         val intent = intentCaptor.value
 
         assertThat(CameraIntents.isSecureCameraIntent(intent)).isEqualTo(isSecure)
-        assertThat(intent.getIntExtra(CameraGestureHelper.EXTRA_CAMERA_LAUNCH_SOURCE, -1))
+        assertThat(intent.getIntExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, -1))
             .isEqualTo(source)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 779788a..d172c9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.management.ControlsProviderSelectorActivity
 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.ActivityStarter
@@ -53,6 +54,7 @@
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.wm.shell.TaskView
 import com.android.wm.shell.TaskViewFactory
@@ -322,6 +324,45 @@
             .isFalse()
     }
 
+    @Test
+    fun testResolveActivityWhileSeeding_ControlsActivity() {
+        whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(true)
+        assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java)
+    }
+
+    @Test
+    fun testResolveActivityNotSeedingNoFavoritesNoPanels_ControlsProviderSelectorActivity() {
+        whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(false)
+        whenever(controlsController.getFavorites()).thenReturn(emptyList())
+
+        val selectedItems =
+            listOf(
+                SelectedItem.StructureItem(
+                    StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
+                ),
+            )
+        sharedPreferences
+            .edit()
+            .putString("controls_component", selectedItems[0].componentName.flattenToString())
+            .putString("controls_structure", selectedItems[0].name.toString())
+            .commit()
+
+        assertThat(underTest.resolveActivity())
+            .isEqualTo(ControlsProviderSelectorActivity::class.java)
+    }
+
+    @Test
+    fun testResolveActivityNotSeedingNoDefaultNoFavoritesPanel_ControlsActivity() {
+        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        val activity = ComponentName("pkg", "activity")
+        val csi = ControlsServiceInfo(panel.componentName, panel.appName, activity)
+        whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(true)
+        whenever(controlsController.getFavorites()).thenReturn(emptyList())
+        whenever(controlsListingController.getCurrentServices()).thenReturn(listOf(csi))
+
+        assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java)
+    }
+
     private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
         val activity = ComponentName("pkg", "activity")
         sharedPreferences
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
index 5cd2ace..de04ef8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
@@ -75,6 +75,7 @@
             uiExecutor.execute(it.arguments[0] as Runnable)
             true
         }
+        whenever(activityContext.resources).thenReturn(context.resources)
 
         uiExecutor = FakeExecutor(FakeSystemClock())
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index ffb8342..430575c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -38,15 +38,18 @@
 import android.view.WindowManagerImpl;
 
 import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.ComplicationLayoutEngine;
+import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.dreams.dreamcomplication.HideComplicationTouchHandler;
 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
+import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -70,7 +73,7 @@
     private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
 
     @Mock
-    LifecycleOwner mLifecycleOwner;
+    DreamOverlayLifecycleOwner mLifecycleOwner;
 
     @Mock
     LifecycleRegistry mLifecycleRegistry;
@@ -87,6 +90,26 @@
     WindowManagerImpl mWindowManager;
 
     @Mock
+    ComplicationComponent.Factory mComplicationComponentFactory;
+
+    @Mock
+    ComplicationComponent mComplicationComponent;
+
+    @Mock
+    ComplicationLayoutEngine mComplicationVisibilityController;
+
+    @Mock
+    com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent.Factory
+            mDreamComplicationComponentFactory;
+
+    @Mock
+    com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent
+            mDreamComplicationComponent;
+
+    @Mock
+    HideComplicationTouchHandler mHideComplicationTouchHandler;
+
+    @Mock
     DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
 
     @Mock
@@ -111,6 +134,9 @@
     ViewGroup mDreamOverlayContainerViewParent;
 
     @Mock
+    TouchInsetManager mTouchInsetManager;
+
+    @Mock
     UiEventLogger mUiEventLogger;
 
     @Captor
@@ -124,23 +150,38 @@
 
         when(mDreamOverlayComponent.getDreamOverlayContainerViewController())
                 .thenReturn(mDreamOverlayContainerViewController);
-        when(mDreamOverlayComponent.getLifecycleOwner())
-                .thenReturn(mLifecycleOwner);
-        when(mDreamOverlayComponent.getLifecycleRegistry())
+        when(mLifecycleOwner.getRegistry())
                 .thenReturn(mLifecycleRegistry);
         when(mDreamOverlayComponent.getDreamOverlayTouchMonitor())
                 .thenReturn(mDreamOverlayTouchMonitor);
-        when(mDreamOverlayComponentFactory
+        when(mComplicationComponentFactory
+                .create(any(), any(), any(), any()))
+                .thenReturn(mComplicationComponent);
+        when(mComplicationComponent.getVisibilityController())
+                .thenReturn(mComplicationVisibilityController);
+        when(mDreamComplicationComponent.getHideComplicationTouchHandler())
+                .thenReturn(mHideComplicationTouchHandler);
+        when(mDreamComplicationComponentFactory
                 .create(any(), any()))
+                .thenReturn(mDreamComplicationComponent);
+        when(mDreamOverlayComponentFactory
+                .create(any(), any(), any(), any()))
                 .thenReturn(mDreamOverlayComponent);
         when(mDreamOverlayContainerViewController.getContainerView())
                 .thenReturn(mDreamOverlayContainerView);
 
-        mService = new DreamOverlayService(mContext, mMainExecutor, mWindowManager,
+        mService = new DreamOverlayService(
+                mContext,
+                mMainExecutor,
+                mLifecycleOwner,
+                mWindowManager,
+                mComplicationComponentFactory,
+                mDreamComplicationComponentFactory,
                 mDreamOverlayComponentFactory,
                 mStateController,
                 mKeyguardUpdateMonitor,
                 mUiEventLogger,
+                mTouchInsetManager,
                 LOW_LIGHT_COMPONENT);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index c21c7a2..ee989d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -63,7 +63,7 @@
     @Test
     public void testStateChange_overlayActive() {
         final DreamOverlayStateController stateController = new DreamOverlayStateController(
-                mExecutor);
+                mExecutor, true);
         stateController.addCallback(mCallback);
         stateController.setOverlayActive(true);
         mExecutor.runAllReady();
@@ -85,7 +85,7 @@
     @Test
     public void testCallback() {
         final DreamOverlayStateController stateController = new DreamOverlayStateController(
-                mExecutor);
+                mExecutor, true);
         stateController.addCallback(mCallback);
 
         // Add complication and verify callback is notified.
@@ -111,7 +111,7 @@
     @Test
     public void testNotifyOnCallbackAdd() {
         final DreamOverlayStateController stateController =
-                new DreamOverlayStateController(mExecutor);
+                new DreamOverlayStateController(mExecutor, true);
 
         stateController.addComplication(mComplication);
         mExecutor.runAllReady();
@@ -123,9 +123,24 @@
     }
 
     @Test
+    public void testNotifyOnCallbackAddOverlayDisabled() {
+        final DreamOverlayStateController stateController =
+                new DreamOverlayStateController(mExecutor, false);
+
+        stateController.addComplication(mComplication);
+        mExecutor.runAllReady();
+
+        // Verify callback occurs on add when an overlay is already present.
+        stateController.addCallback(mCallback);
+        mExecutor.runAllReady();
+        verify(mCallback, never()).onComplicationsChanged();
+    }
+
+
+    @Test
     public void testComplicationFilteringWhenShouldShowComplications() {
         final DreamOverlayStateController stateController =
-                new DreamOverlayStateController(mExecutor);
+                new DreamOverlayStateController(mExecutor, true);
         stateController.setShouldShowComplications(true);
 
         final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
@@ -165,7 +180,7 @@
     @Test
     public void testComplicationFilteringWhenShouldHideComplications() {
         final DreamOverlayStateController stateController =
-                new DreamOverlayStateController(mExecutor);
+                new DreamOverlayStateController(mExecutor, true);
         stateController.setShouldShowComplications(true);
 
         final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
@@ -212,7 +227,7 @@
     public void testComplicationWithNoTypeNotFiltered() {
         final Complication complication = Mockito.mock(Complication.class);
         final DreamOverlayStateController stateController =
-                new DreamOverlayStateController(mExecutor);
+                new DreamOverlayStateController(mExecutor, true);
         stateController.addComplication(complication);
         mExecutor.runAllReady();
         assertThat(stateController.getComplications(true).contains(complication))
@@ -222,7 +237,7 @@
     @Test
     public void testNotifyLowLightChanged() {
         final DreamOverlayStateController stateController =
-                new DreamOverlayStateController(mExecutor);
+                new DreamOverlayStateController(mExecutor, true);
 
         stateController.addCallback(mCallback);
         mExecutor.runAllReady();
@@ -238,7 +253,7 @@
     @Test
     public void testNotifyEntryAnimationsFinishedChanged() {
         final DreamOverlayStateController stateController =
-                new DreamOverlayStateController(mExecutor);
+                new DreamOverlayStateController(mExecutor, true);
 
         stateController.addCallback(mCallback);
         mExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
index b477592..dcd8736 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.dreams.complication;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -139,6 +141,21 @@
     }
 
     @Test
+    public void testMalformedComplicationAddition() {
+        final Observer<Collection<ComplicationViewModel>> observer =
+                captureComplicationViewModelsObserver();
+
+        // Add a complication and ensure it is added to the view.
+        final HashSet<ComplicationViewModel> complications = new HashSet<>(
+                Collections.singletonList(mComplicationViewModel));
+        when(mViewHolder.getView()).thenReturn(null);
+        observer.onChanged(complications);
+
+        verify(mLayoutEngine, never()).addComplication(any(), any(), any(), anyInt());
+
+    }
+
+    @Test
     public void testNewComplicationsBeforeEntryAnimationsFinishSetToInvisible() {
         final Observer<Collection<ComplicationViewModel>> observer =
                 captureComplicationViewModelsObserver();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index e6d3a69..89c7280 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
@@ -54,6 +55,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 
@@ -147,6 +149,19 @@
     }
 
     @Test
+    public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() {
+        final DreamHomeControlsComplication.Registrant registrant =
+                new DreamHomeControlsComplication.Registrant(mComplication,
+                        mDreamOverlayStateController, mControlsComponent);
+        registrant.start();
+
+        setHaveFavorites(false);
+        setServiceWithPanel();
+
+        verify(mDreamOverlayStateController).addComplication(mComplication);
+    }
+
+    @Test
     public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() {
         final DreamHomeControlsComplication.Registrant registrant =
                 new DreamHomeControlsComplication.Registrant(mComplication,
@@ -232,6 +247,15 @@
         triggerControlsListingCallback(serviceInfos);
     }
 
+    private void setServiceWithPanel() {
+        final List<ControlsServiceInfo> serviceInfos = new ArrayList<>();
+        ControlsServiceInfo csi = mock(ControlsServiceInfo.class);
+        serviceInfos.add(csi);
+        when(csi.getPanelActivity()).thenReturn(new ComponentName("a", "b"));
+        when(mControlsListingController.getCurrentServices()).thenReturn(serviceInfos);
+        triggerControlsListingCallback(serviceInfos);
+    }
+
     private void setDreamOverlayActive(boolean value) {
         when(mDreamOverlayStateController.isOverlayActive()).thenReturn(value);
         verify(mDreamOverlayStateController).addCallback(mStateCallbackCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/dreamcomplication/HideComplicationTouchHandlerTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/dreams/dreamcomplication/HideComplicationTouchHandlerTest.java
index 4e3aca7..d68f032 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/dreamcomplication/HideComplicationTouchHandlerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch;
+package com.android.systemui.dreams.dreamcomplication;
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -33,6 +33,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.touch.DreamTouchHandler;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.touch.TouchInsetManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index d52616b..c8a352d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -16,11 +16,12 @@
 
 package com.android.systemui.globalactions;
 
+import static android.content.pm.UserInfo.FLAG_ADMIN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
@@ -32,11 +33,13 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.media.AudioManager;
 import android.os.Handler;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.service.dreams.IDreamManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -552,10 +555,32 @@
 
     @Test
     public void testBugreportAction_whenDebugMode_shouldOfferBugreportButtonBeforeProvisioning() {
-        doReturn(1).when(mGlobalSettings).getInt(anyString(), anyInt());
+        UserInfo currentUser = mockCurrentUser(FLAG_ADMIN);
+
+        when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser);
+        doReturn(1).when(mGlobalSettings)
+                .getIntForUser(Settings.Global.BUGREPORT_IN_POWER_MENU, 0, currentUser.id);
 
         GlobalActionsDialogLite.BugReportAction bugReportAction =
                 mGlobalActionsDialogLite.makeBugReportActionForTesting();
         assertThat(bugReportAction.showBeforeProvisioning()).isTrue();
     }
+
+    @Test
+    public void testBugreportAction_whenUserIsNotAdmin_noBugReportActionBeforeProvisioning() {
+        UserInfo currentUser = mockCurrentUser(0);
+
+        when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser);
+        doReturn(1).when(mGlobalSettings)
+                .getIntForUser(Settings.Global.BUGREPORT_IN_POWER_MENU, 0, currentUser.id);
+
+        GlobalActionsDialogLite.BugReportAction bugReportAction =
+                mGlobalActionsDialogLite.makeBugReportActionForTesting();
+        assertThat(bugReportAction.showBeforeProvisioning()).isFalse();
+    }
+
+    private UserInfo mockCurrentUser(int flags) {
+        return new UserInfo(10, "A User", flags);
+
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 798839d..804960d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -168,6 +168,45 @@
     }
 
     @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void testOnStartedWakingUp_whileSleeping_ifWakeAndUnlocking_doesNotShowKeyguard() {
+        when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
+        when(mLockPatternUtils.getPowerButtonInstantlyLocks(anyInt())).thenReturn(true);
+        mViewMediator.onSystemReady();
+        TestableLooper.get(this).processAllMessages();
+
+        mViewMediator.setShowingLocked(false);
+        TestableLooper.get(this).processAllMessages();
+
+        mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
+        mViewMediator.onWakeAndUnlocking();
+        mViewMediator.onStartedWakingUp(OFF_BECAUSE_OF_USER, false);
+        TestableLooper.get(this).processAllMessages();
+
+        assertFalse(mViewMediator.isShowingAndNotOccluded());
+        verify(mKeyguardStateController, never()).notifyKeyguardState(eq(true), anyBoolean());
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void testOnStartedWakingUp_whileSleeping_ifNotWakeAndUnlocking_showsKeyguard() {
+        when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
+        when(mLockPatternUtils.getPowerButtonInstantlyLocks(anyInt())).thenReturn(true);
+        mViewMediator.onSystemReady();
+        TestableLooper.get(this).processAllMessages();
+
+        mViewMediator.setShowingLocked(false);
+        TestableLooper.get(this).processAllMessages();
+
+        mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
+        mViewMediator.onStartedWakingUp(OFF_BECAUSE_OF_USER, false);
+
+        TestableLooper.get(this).processAllMessages();
+
+        assertTrue(mViewMediator.isShowingAndNotOccluded());
+    }
+
+    @Test
     public void testRegisterDumpable() {
         verify(mDumpManager).registerDumpable(KeyguardViewMediator.class.getName(), mViewMediator);
         verify(mStatusBarKeyguardViewManager, never()).setKeyguardGoingAwayState(anyBoolean());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index 322014a..f8cb408 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -20,13 +20,14 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import java.util.*
+import java.util.Optional
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -50,20 +51,22 @@
     companion object {
         @Parameters(
             name =
-                "feature enabled = {0}, has favorites = {1}, has service infos = {2}, can show" +
-                    " while locked = {3}, visibility is AVAILABLE {4} - expected visible = {5}"
+                "feature enabled = {0}, has favorites = {1}, has panels = {2}, " +
+                    "has service infos = {3}, can show while locked = {4}, " +
+                    "visibility is AVAILABLE {5} - expected visible = {6}"
         )
         @JvmStatic
         fun data() =
-            (0 until 32)
+            (0 until 64)
                 .map { combination ->
                     arrayOf(
-                        /* isFeatureEnabled= */ combination and 0b10000 != 0,
-                        /* hasFavorites= */ combination and 0b01000 != 0,
-                        /* hasServiceInfos= */ combination and 0b00100 != 0,
-                        /* canShowWhileLocked= */ combination and 0b00010 != 0,
-                        /* visibilityAvailable= */ combination and 0b00001 != 0,
-                        /* isVisible= */ combination == 0b11111,
+                        /* isFeatureEnabled= */ combination and 0b100000 != 0,
+                        /* hasFavorites = */ combination and 0b010000 != 0,
+                        /* hasPanels = */ combination and 0b001000 != 0,
+                        /* hasServiceInfos= */ combination and 0b000100 != 0,
+                        /* canShowWhileLocked= */ combination and 0b000010 != 0,
+                        /* visibilityAvailable= */ combination and 0b000001 != 0,
+                        /* isVisible= */ combination in setOf(0b111111, 0b110111, 0b101111),
                     )
                 }
                 .toList()
@@ -72,6 +75,7 @@
     @Mock private lateinit var component: ControlsComponent
     @Mock private lateinit var controlsController: ControlsController
     @Mock private lateinit var controlsListingController: ControlsListingController
+    @Mock private lateinit var controlsServiceInfo: ControlsServiceInfo
     @Captor
     private lateinit var callbackCaptor:
         ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -80,10 +84,11 @@
 
     @JvmField @Parameter(0) var isFeatureEnabled: Boolean = false
     @JvmField @Parameter(1) var hasFavorites: Boolean = false
-    @JvmField @Parameter(2) var hasServiceInfos: Boolean = false
-    @JvmField @Parameter(3) var canShowWhileLocked: Boolean = false
-    @JvmField @Parameter(4) var isVisibilityAvailable: Boolean = false
-    @JvmField @Parameter(5) var isVisibleExpected: Boolean = false
+    @JvmField @Parameter(2) var hasPanels: Boolean = false
+    @JvmField @Parameter(3) var hasServiceInfos: Boolean = false
+    @JvmField @Parameter(4) var canShowWhileLocked: Boolean = false
+    @JvmField @Parameter(5) var isVisibilityAvailable: Boolean = false
+    @JvmField @Parameter(6) var isVisibleExpected: Boolean = false
 
     @Before
     fun setUp() {
@@ -93,10 +98,13 @@
         whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
         whenever(component.getControlsListingController())
             .thenReturn(Optional.of(controlsListingController))
+        if (hasPanels) {
+            whenever(controlsServiceInfo.panelActivity).thenReturn(mock())
+        }
         whenever(controlsListingController.getCurrentServices())
             .thenReturn(
                 if (hasServiceInfos) {
-                    listOf(mock(), mock())
+                    listOf(controlsServiceInfo, mock())
                 } else {
                     emptyList()
                 }
@@ -134,10 +142,15 @@
         val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
 
         if (canShowWhileLocked) {
+            val serviceInfoMock: ControlsServiceInfo = mock {
+                if (hasPanels) {
+                    whenever(panelActivity).thenReturn(mock())
+                }
+            }
             verify(controlsListingController).addCallback(callbackCaptor.capture())
             callbackCaptor.value.onServicesUpdated(
                 if (hasServiceInfos) {
-                    listOf(mock())
+                    listOf(serviceInfoMock)
                 } else {
                     emptyList()
                 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
new file mode 100644
index 0000000..9970a67
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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.systemui.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.keyguard.ViewMediatorCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineScope
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardBouncerRepositoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
+    @Mock private lateinit var bouncerLogger: TableLogBuffer
+    lateinit var underTest: KeyguardBouncerRepository
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        val testCoroutineScope = TestCoroutineScope()
+        underTest =
+            KeyguardBouncerRepository(viewMediatorCallback, testCoroutineScope, bouncerLogger)
+    }
+
+    @Test
+    fun changingFlowValueTriggersLogging() = runBlocking {
+        underTest.setPrimaryHide(true)
+        verify(bouncerLogger).logChange("", "PrimaryBouncerHide", false)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index fdef344..b65f5cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -214,6 +214,7 @@
     private val fakeFeatureFlag =
         FakeFeatureFlags().apply {
             this.set(Flags.UMO_SURFACE_RIPPLE, false)
+            this.set(Flags.UMO_TURBULENCE_NOISE, false)
             this.set(Flags.MEDIA_FALSING_PENALTY, true)
         }
 
@@ -2062,6 +2063,26 @@
         assertThat(viewHolder.multiRippleView.ripples.size).isEqualTo(0)
     }
 
+    @Test
+    fun onButtonClick_turbulenceNoiseFlagEnabled_createsRipplesFinishedListener() {
+        fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
+        fakeFeatureFlag.set(Flags.UMO_TURBULENCE_NOISE, true)
+
+        player.attachPlayer(viewHolder)
+
+        assertThat(player.mRipplesFinishedListener).isNotNull()
+    }
+
+    @Test
+    fun onButtonClick_turbulenceNoiseFlagDisabled_doesNotCreateRipplesFinishedListener() {
+        fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
+        fakeFeatureFlag.set(Flags.UMO_TURBULENCE_NOISE, false)
+
+        player.attachPlayer(viewHolder)
+
+        assertThat(player.mRipplesFinishedListener).isNull()
+    }
+
     private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
         withArgCaptor {
             verify(seekBarViewModel).setScrubbingChangeListener(capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 5c0f0fe..7c3c9d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -46,6 +46,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -71,10 +72,13 @@
     private MediaOutputAdapter mMediaOutputAdapter;
     private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder;
     private List<MediaDevice> mMediaDevices = new ArrayList<>();
+    private List<MediaItem> mMediaItems = new ArrayList<>();
     MediaOutputSeekbar mSpyMediaOutputSeekbar;
 
     @Before
     public void setUp() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(false);
+        when(mMediaOutputController.getMediaItemList()).thenReturn(mMediaItems);
         when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
         when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
         when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
@@ -82,7 +86,6 @@
         when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat);
         when(mMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1);
         when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(true);
-        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(false);
         when(mIconCompat.toIcon(mContext)).thenReturn(mIcon);
         when(mMediaDevice1.getName()).thenReturn(TEST_DEVICE_NAME_1);
         when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_ID_1);
@@ -94,6 +97,8 @@
                 LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
         mMediaDevices.add(mMediaDevice1);
         mMediaDevices.add(mMediaDevice2);
+        mMediaItems.add(new MediaItem(mMediaDevice1));
+        mMediaItems.add(new MediaItem(mMediaDevice2));
 
         mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
         mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
@@ -119,6 +124,26 @@
     }
 
     @Test
+    public void advanced_getItemCount_returnsMediaItemSize() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaItems.size());
+    }
+
+    @Test
+    public void advanced_getItemId_validPosition_returnCorrespondingId() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        assertThat(mMediaOutputAdapter.getItemId(0)).isEqualTo(mMediaItems.get(
+                0).getMediaDevice().get().getId().hashCode());
+    }
+
+    @Test
+    public void advanced_getItemId_invalidPosition_returnPosition() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        int invalidPosition = mMediaItems.size() + 1;
+        assertThat(mMediaOutputAdapter.getItemId(invalidPosition)).isEqualTo(invalidPosition);
+    }
+
+    @Test
     public void onBindViewHolder_bindPairNew_verifyView() {
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
 
@@ -159,6 +184,63 @@
     }
 
     @Test
+    public void advanced_onBindViewHolder_bindPairNew_verifyView() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        mMediaItems.add(new MediaItem());
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
+
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTitleText.getText()).isEqualTo(mContext.getText(
+                R.string.media_output_dialog_pairing_new));
+    }
+
+    @Test
+    public void advanced_onBindViewHolder_bindGroup_withSessionName_verifyView() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
+                mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
+                        Collectors.toList()));
+        when(mMediaOutputController.getSessionName()).thenReturn(TEST_SESSION_NAME);
+        mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        mMediaOutputAdapter.getItemCount();
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void advanced_onBindViewHolder_bindGroup_noSessionName_verifyView() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
+                mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
+                        Collectors.toList()));
+        when(mMediaOutputController.getSessionName()).thenReturn(null);
+        mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        mMediaOutputAdapter.getItemCount();
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
     public void onBindViewHolder_bindConnectedDevice_verifyView() {
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
 
@@ -376,6 +458,19 @@
     }
 
     @Test
+    public void advanced_onItemClick_clickPairNew_verifyLaunchBluetoothPairing() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        mMediaItems.add(new MediaItem());
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
+        mViewHolder.mContainerLayout.performClick();
+
+        verify(mMediaOutputController).launchBluetoothPairing(mViewHolder.mContainerLayout);
+    }
+
+    @Test
     public void onItemClick_clickDevice_verifyConnectDevice() {
         assertThat(mMediaDevice2.getState()).isEqualTo(
                 LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
@@ -458,6 +553,26 @@
     }
 
     @Test
+    public void advanced_onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
+                mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
+                        Collectors.toList()));
+        mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        List<MediaDevice> selectableDevices = new ArrayList<>();
+        selectableDevices.add(mMediaDevice1);
+        when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+        when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(true);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        mViewHolder.mContainerLayout.performClick();
+
+        assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse();
+    }
+
+    @Test
     public void onBindViewHolder_volumeControlChangeToEnabled_enableSeekbarAgain() {
         when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false);
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index c544c0e..71c300c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -63,6 +63,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -144,6 +145,7 @@
                 mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags);
+        when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(false);
         mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -282,6 +284,25 @@
     }
 
     @Test
+    public void advanced_onDeviceListUpdate_verifyDeviceListCallback() {
+        when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true);
+        mMediaOutputController.start(mCb);
+        reset(mCb);
+
+        mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+        final List<MediaDevice> devices = new ArrayList<>();
+        for (MediaItem item : mMediaOutputController.getMediaItemList()) {
+            if (item.getMediaDevice().isPresent()) {
+                devices.add(item.getMediaDevice().get());
+            }
+        }
+
+        assertThat(devices.containsAll(mMediaDevices)).isTrue();
+        assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+        verify(mCb).onDeviceListChanged();
+    }
+
+    @Test
     public void onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
         mMediaOutputController.start(mCb);
         reset(mCb);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
index e009e86..0e7bf8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -33,7 +34,7 @@
 class MediaTttLoggerTest : SysuiTestCase() {
 
     private lateinit var buffer: LogBuffer
-    private lateinit var logger: MediaTttLogger
+    private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
 
     @Before
     fun setUp () {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index cce3e36..561867f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -40,7 +41,7 @@
     private lateinit var appIconFromPackageName: Drawable
     @Mock private lateinit var packageManager: PackageManager
     @Mock private lateinit var applicationInfo: ApplicationInfo
-    @Mock private lateinit var logger: MediaTttLogger
+    @Mock private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
 
     @Before
     fun setUp() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
index 4aa982e..bad3f03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -27,13 +27,14 @@
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
 import com.android.systemui.util.view.ViewUtil
 import com.android.systemui.util.wakelock.WakeLock
 
 class FakeMediaTttChipControllerReceiver(
     commandQueue: CommandQueue,
     context: Context,
-    logger: MediaTttLogger,
+    logger: MediaTttLogger<ChipReceiverInfo>,
     windowManager: WindowManager,
     mainExecutor: DelayableExecutor,
     accessibilityManager: AccessibilityManager,
@@ -44,6 +45,7 @@
     uiEventLogger: MediaTttReceiverUiEventLogger,
     viewUtil: ViewUtil,
     wakeLockBuilder: WakeLock.Builder,
+    systemClock: SystemClock,
 ) :
     MediaTttChipControllerReceiver(
         commandQueue,
@@ -59,6 +61,7 @@
         uiEventLogger,
         viewUtil,
         wakeLockBuilder,
+        systemClock,
     ) {
     override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
         // Just bypass the animation in tests
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 23f7cdb..ffa261a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -67,7 +67,7 @@
     @Mock
     private lateinit var applicationInfo: ApplicationInfo
     @Mock
-    private lateinit var logger: MediaTttLogger
+    private lateinit var logger: MediaTttLogger<ChipReceiverInfo>
     @Mock
     private lateinit var accessibilityManager: AccessibilityManager
     @Mock
@@ -128,6 +128,7 @@
             receiverUiEventLogger,
             viewUtil,
             fakeWakeLockBuilder,
+            fakeClock,
         )
         controllerReceiver.start()
 
@@ -155,6 +156,7 @@
             receiverUiEventLogger,
             viewUtil,
             fakeWakeLockBuilder,
+            fakeClock,
         )
         controllerReceiver.start()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 311740e..b03a545 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
 import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
 import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -83,7 +84,7 @@
     @Mock private lateinit var falsingManager: FalsingManager
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var chipbarLogger: ChipbarLogger
-    @Mock private lateinit var logger: MediaTttLogger
+    @Mock private lateinit var logger: MediaTttLogger<ChipbarInfo>
     @Mock private lateinit var mediaTttFlags: MediaTttFlags
     @Mock private lateinit var packageManager: PackageManager
     @Mock private lateinit var powerManager: PowerManager
@@ -142,6 +143,7 @@
                 viewUtil,
                 vibratorHelper,
                 fakeWakeLockBuilder,
+                fakeClock,
             )
         chipbarCoordinator.start()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 9f28708..5e082f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -1,9 +1,12 @@
 package com.android.systemui.qs
 
+import android.content.res.Configuration
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
+import android.testing.TestableResources
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
@@ -26,10 +29,11 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.any
+import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -54,8 +58,11 @@
     @Mock private lateinit var otherTile: QSTile
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var featureFlags: FeatureFlags
+    @Mock private lateinit var configuration: Configuration
+    @Mock private lateinit var pagedTileLayout: PagedTileLayout
 
     private lateinit var controller: QSPanelController
+    private val testableResources: TestableResources = mContext.orCreateTestableResources
 
     @Before
     fun setUp() {
@@ -63,7 +70,9 @@
 
         whenever(brightnessSliderFactory.create(any(), any())).thenReturn(brightnessSlider)
         whenever(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
-        whenever(qsPanel.resources).thenReturn(mContext.orCreateTestableResources.resources)
+        testableResources.addOverride(R.bool.config_use_split_notification_shade, false)
+        whenever(qsPanel.resources).thenReturn(testableResources.resources)
+        whenever(qsPanel.getOrCreateTileLayout()).thenReturn(pagedTileLayout)
         whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false)
         whenever(qsPanel.setListening(anyBoolean())).then {
             whenever(qsPanel.isListening).thenReturn(it.getArgument(0))
@@ -121,4 +130,15 @@
         whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false)
         assertThat(controller.isBouncerInTransit()).isEqualTo(false)
     }
+
+    @Test
+    fun configurationChange_onlySplitShadeConfigChanges_tileAreRedistributed() {
+        testableResources.addOverride(R.bool.config_use_split_notification_shade, false)
+        controller.mOnConfigurationChangedListener.onConfigurationChange(configuration)
+        verify(pagedTileLayout, never()).forceTilesRedistribution()
+
+        testableResources.addOverride(R.bool.config_use_split_notification_shade, true)
+        controller.mOnConfigurationChangedListener.onConfigurationChange(configuration)
+        verify(pagedTileLayout).forceTilesRedistribution()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index c656d6d..88d7e9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -40,6 +40,7 @@
 import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
 import android.os.Looper;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -115,6 +116,10 @@
         mTestableLooper = TestableLooper.get(this);
         Looper looper = mTestableLooper.getLooper();
         Handler mainHandler = new Handler(looper);
+        // TODO(b/259908270): remove
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                DevicePolicyManager.ADD_ISFINANCED_DEVICE_FLAG, "true",
+                /* makeDefault= */ false);
         when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
         mFooterUtils = new QSSecurityFooterUtils(getContext(),
                 getContext().getSystemService(DevicePolicyManager.class), mUserTracker,
@@ -122,6 +127,8 @@
 
         when(mSecurityController.getDeviceOwnerComponentOnAnyUser())
                 .thenReturn(DEVICE_OWNER_COMPONENT);
+        when(mSecurityController.isFinancedDevice()).thenReturn(false);
+        // TODO(b/259908270): remove
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
     }
@@ -184,6 +191,8 @@
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
         when(mSecurityController.getDeviceOwnerOrganizationName())
                 .thenReturn(MANAGING_ORGANIZATION);
+        when(mSecurityController.isFinancedDevice()).thenReturn(true);
+        // TODO(b/259908270): remove
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
 
@@ -495,6 +504,8 @@
     @Test
     public void testGetManagementTitleForFinancedDevice() {
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
+        when(mSecurityController.isFinancedDevice()).thenReturn(true);
+        // TODO(b/259908270): remove
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
 
@@ -524,6 +535,8 @@
     @Test
     public void testGetManagementMessage_deviceOwner_asFinancedDevice() {
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
+        when(mSecurityController.isFinancedDevice()).thenReturn(true);
+        // TODO(b/259908270): remove
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
 
@@ -689,6 +702,8 @@
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
         when(mSecurityController.getDeviceOwnerOrganizationName())
                 .thenReturn(MANAGING_ORGANIZATION);
+        when(mSecurityController.isFinancedDevice()).thenReturn(true);
+        // TODO(b/259908270): remove
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
 
@@ -711,6 +726,8 @@
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
         when(mSecurityController.getDeviceOwnerOrganizationName())
                 .thenReturn(MANAGING_ORGANIZATION);
+        when(mSecurityController.isFinancedDevice()).thenReturn(true);
+        // TODO(b/259908270): remove
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 3ae8428..b59005a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -86,7 +86,8 @@
     private View mDialogView;
     private View mSubTitle;
     private LinearLayout mEthernet;
-    private LinearLayout mMobileDataToggle;
+    private LinearLayout mMobileDataLayout;
+    private Switch mMobileToggleSwitch;
     private LinearLayout mWifiToggle;
     private Switch mWifiToggleSwitch;
     private TextView mWifiToggleSummary;
@@ -135,7 +136,8 @@
         mDialogView = mInternetDialog.mDialogView;
         mSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
         mEthernet = mDialogView.requireViewById(R.id.ethernet_layout);
-        mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_network_layout);
+        mMobileDataLayout = mDialogView.requireViewById(R.id.mobile_network_layout);
+        mMobileToggleSwitch = mDialogView.requireViewById(R.id.mobile_toggle);
         mWifiToggle = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
         mWifiToggleSwitch = mDialogView.requireViewById(R.id.wifi_toggle);
         mWifiToggleSummary = mDialogView.requireViewById(R.id.wifi_toggle_summary);
@@ -236,7 +238,7 @@
 
         mInternetDialog.updateDialog(true);
 
-        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
@@ -248,7 +250,7 @@
 
         mInternetDialog.updateDialog(true);
 
-        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
 
         // Carrier network should be visible if airplane mode ON and Wi-Fi is ON.
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
@@ -257,7 +259,7 @@
 
         mInternetDialog.updateDialog(true);
 
-        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
     @Test
@@ -267,7 +269,7 @@
 
         mInternetDialog.updateDialog(true);
 
-        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
@@ -279,7 +281,7 @@
 
         mInternetDialog.updateDialog(true);
 
-        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
@@ -316,6 +318,30 @@
     }
 
     @Test
+    public void updateDialog_mobileDataIsEnabled_checkMobileDataSwitch() {
+        doReturn(true).when(mInternetDialogController).hasActiveSubId();
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+        when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true);
+        mMobileToggleSwitch.setChecked(false);
+
+        mInternetDialog.updateDialog(true);
+
+        assertThat(mMobileToggleSwitch.isChecked()).isTrue();
+    }
+
+    @Test
+    public void updateDialog_mobileDataIsNotChanged_checkMobileDataSwitch() {
+        doReturn(true).when(mInternetDialogController).hasActiveSubId();
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+        when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false);
+        mMobileToggleSwitch.setChecked(false);
+
+        mInternetDialog.updateDialog(true);
+
+        assertThat(mMobileToggleSwitch.isChecked()).isFalse();
+    }
+
+    @Test
     public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
         mInternetDialog.dismissDialog();
         doReturn(true).when(mInternetDialogController).hasActiveSubId();
@@ -695,7 +721,7 @@
     private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible,
             boolean connectedWifiVisible) {
         mEthernet.setVisibility(ethernetVisible ? View.VISIBLE : View.GONE);
-        mMobileDataToggle.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE);
+        mMobileDataLayout.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE);
         mConnectedWifi.setVisibility(connectedWifiVisible ? View.VISIBLE : View.GONE);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 0302dad..3512749 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1102,6 +1102,17 @@
 
         mStatusBarStateController.setState(KEYGUARD);
 
+        assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
+        assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+    }
+
+    @Test
+    public void testLockedSplitShadeTransitioningToKeyguard_closesQS() {
+        enableSplitShade(true);
+        mStatusBarStateController.setState(SHADE_LOCKED);
+        mNotificationPanelViewController.setQsExpanded(true);
+
+        mStatusBarStateController.setState(KEYGUARD);
 
         assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
         assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index c8a392b..63065a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -75,6 +75,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.ViewGroup;
@@ -217,6 +218,10 @@
         mTextView = new KeyguardIndicationTextView(mContext);
         mTextView.setAnimationsEnabled(false);
 
+        // TODO(b/259908270): remove
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                DevicePolicyManager.ADD_ISFINANCED_DEVICE_FLAG, "true",
+                /* makeDefault= */ false);
         mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager);
         mContext.addMockSystemService(UserManager.class, mUserManager);
         mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
@@ -238,8 +243,12 @@
         when(mDevicePolicyManager.getResources()).thenReturn(mDevicePolicyResourcesManager);
         when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
                 .thenReturn(DEVICE_OWNER_COMPONENT);
+        when(mDevicePolicyManager.isFinancedDevice()).thenReturn(false);
+        // TODO(b/259908270): remove
         when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
+
+
         when(mDevicePolicyResourcesManager.getString(anyString(), any()))
                 .thenReturn(mDisclosureGeneric);
         when(mDevicePolicyResourcesManager.getString(anyString(), any(), anyString()))
@@ -489,6 +498,8 @@
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
+        when(mDevicePolicyManager.isFinancedDevice()).thenReturn(true);
+        // TODO(b/259908270): remove
         when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
         sendUpdateDisclosureBroadcast();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index aa1114b..cb4f119 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -38,6 +39,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
 import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
 import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -88,6 +90,7 @@
     private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
     private val mHeaderController: NodeController = mock()
     private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock()
+    private val mFlags: NotifPipelineFlags = mock()
 
     private lateinit var mEntry: NotificationEntry
     private lateinit var mGroupSummary: NotificationEntry
@@ -113,6 +116,7 @@
             mNotificationInterruptStateProvider,
             mRemoteInputManager,
             mLaunchFullScreenIntentProvider,
+            mFlags,
             mHeaderController,
             mExecutor)
         mCoordinator.attach(mNotifPipeline)
@@ -246,14 +250,14 @@
 
     @Test
     fun testOnEntryAdded_shouldFullScreen() {
-        setShouldFullScreen(mEntry)
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN)
         mCollectionListener.onEntryAdded(mEntry)
         verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
     }
 
     @Test
     fun testOnEntryAdded_shouldNotFullScreen() {
-        setShouldFullScreen(mEntry, should = false)
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
         mCollectionListener.onEntryAdded(mEntry)
         verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
     }
@@ -805,15 +809,96 @@
         verify(mHeadsUpManager, never()).showNotification(any())
     }
 
+    @Test
+    fun testOnRankingApplied_noFSIOnUpdateWhenFlagOff() {
+        // Ensure the feature flag is off
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(false)
+
+        // GIVEN that mEntry was previously suppressed from full-screen only by DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // and it is then updated to allow full screen
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+        mCollectionListener.onRankingApplied()
+
+        // THEN it should not full screen because the feature is off
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+    }
+
+    @Test
+    fun testOnRankingApplied_updateToFullScreen() {
+        // Turn on the feature
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+        // GIVEN that mEntry was previously suppressed from full-screen only by DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // at this point, it should not have full screened
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+
+        // and it is then updated to allow full screen AND HUN
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        setShouldHeadsUp(mEntry)
+        whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN it should full screen but it should NOT HUN
+        verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        verify(mHeadsUpManager, never()).showNotification(any())
+    }
+
+    @Test
+    fun testOnRankingApplied_noFSIWhenAlsoSuppressedForOtherReasons() {
+        // Feature on
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+        // GIVEN that mEntry is suppressed by DND (functionally), but not *only* DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // and it is updated to full screen later
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        mCollectionListener.onRankingApplied()
+
+        // THEN it should still not full screen because something else was blocking it before
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+    }
+
+    @Test
+    fun testOnRankingApplied_noFSIWhenTooOld() {
+        // Feature on
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+        // GIVEN that mEntry is suppressed only by DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // but it's >10s old
+        mCoordinator.addForFSIReconsideration(mEntry, mSystemClock.currentTimeMillis() - 10000)
+
+        // and it is updated to full screen later
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN)
+        mCollectionListener.onRankingApplied()
+
+        // THEN it should still not full screen because it's too old
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+    }
+
     private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
         whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
         whenever(mNotificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
                 .thenReturn(should)
     }
 
-    private fun setShouldFullScreen(entry: NotificationEntry, should: Boolean = true) {
-        whenever(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
-            .thenReturn(should)
+    private fun setShouldFullScreen(entry: NotificationEntry, decision: FullScreenIntentDecision) {
+        whenever(mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry))
+            .thenReturn(decision)
     }
 
     private fun finishBind(entry: NotificationEntry) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 21aae00..601771d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -23,6 +23,7 @@
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
@@ -61,6 +62,7 @@
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -527,6 +529,8 @@
         when(mDreamManager.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
 
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.NO_FULL_SCREEN_INTENT);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
         verify(mLogger, never()).logNoFullscreen(any(), any());
@@ -535,6 +539,44 @@
     }
 
     @Test
+    public void testShouldNotFullScreen_suppressedOnlyByDND() throws RemoteException {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        modifyRanking(entry)
+                .setSuppressedVisualEffects(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT)
+                .build();
+        when(mPowerManager.isInteractive()).thenReturn(false);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND);
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isFalse();
+        verify(mLogger, never()).logFullscreen(any(), any());
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+        verify(mLogger).logNoFullscreen(entry, "Suppressed by DND");
+    }
+
+    @Test
+    public void testShouldNotFullScreen_suppressedByDNDAndOther() throws RemoteException {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_LOW, /* silenced */ false);
+        modifyRanking(entry)
+                .setSuppressedVisualEffects(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT)
+                .build();
+        when(mPowerManager.isInteractive()).thenReturn(false);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND);
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isFalse();
+        verify(mLogger, never()).logFullscreen(any(), any());
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+        verify(mLogger).logNoFullscreen(entry, "Suppressed by DND");
+    }
+
+    @Test
     public void testShouldNotFullScreen_notHighImportance_withStrictFlag() throws Exception {
         when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
         testShouldNotFullScreen_notHighImportance();
@@ -547,6 +589,8 @@
         when(mDreamManager.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
 
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
         verify(mLogger).logNoFullscreen(entry, "Not important enough");
@@ -567,6 +611,8 @@
         when(mDreamManager.isDreaming()).thenReturn(true);
         when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
 
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
         verify(mLogger, never()).logNoFullscreen(any(), any());
@@ -594,6 +640,8 @@
         when(mDreamManager.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
 
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isTrue();
         verify(mLogger, never()).logNoFullscreen(any(), any());
@@ -614,6 +662,8 @@
         when(mDreamManager.isDreaming()).thenReturn(true);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
 
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.FSI_DEVICE_IS_DREAMING);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isTrue();
         verify(mLogger, never()).logNoFullscreen(any(), any());
@@ -634,6 +684,8 @@
         when(mDreamManager.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
 
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.FSI_KEYGUARD_SHOWING);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isTrue();
         verify(mLogger, never()).logNoFullscreen(any(), any());
@@ -655,6 +707,8 @@
         when(mDreamManager.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
 
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
         verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
@@ -671,9 +725,10 @@
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
         when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
 
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isTrue();
-        verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger).logFullscreen(entry, "Expected not to HUN");
@@ -691,9 +746,10 @@
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mKeyguardStateController.isOccluded()).thenReturn(true);
 
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.FSI_KEYGUARD_OCCLUDED);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isTrue();
-        verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger).logFullscreen(entry, "Expected not to HUN while keyguard occluded");
@@ -711,9 +767,10 @@
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mKeyguardStateController.isOccluded()).thenReturn(false);
 
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.FSI_LOCKED_SHADE);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isTrue();
-        verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger).logFullscreen(entry, "Keyguard is showing and not occluded");
@@ -731,9 +788,10 @@
         when(mKeyguardStateController.isShowing()).thenReturn(false);
         when(mKeyguardStateController.isOccluded()).thenReturn(false);
 
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
-        verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger).logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
         verify(mLogger, never()).logFullscreen(any(), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
new file mode 100644
index 0000000..33b94e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 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.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.StatsManager
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.util.StatsEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationMemoryLoggerTest : SysuiTestCase() {
+
+    private val bgExecutor = FakeExecutor(FakeSystemClock())
+    private val immediate = Dispatchers.Main.immediate
+
+    @Mock private lateinit var statsManager: StatsManager
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun onInit_registersCallback() {
+        val logger = createLoggerWithNotifications(listOf())
+        logger.init()
+        verify(statsManager)
+            .setPullAtomCallback(SysUiStatsLog.NOTIFICATION_MEMORY_USE, null, bgExecutor, logger)
+    }
+
+    @Test
+    fun onPullAtom_wrongAtomId_returnsSkip() {
+        val logger = createLoggerWithNotifications(listOf())
+        val data: MutableList<StatsEvent> = mutableListOf()
+        assertThat(logger.onPullAtom(111, data)).isEqualTo(StatsManager.PULL_SKIP)
+        assertThat(data).isEmpty()
+    }
+
+    @Test
+    fun onPullAtom_emptyNotifications_returnsZeros() {
+        val logger = createLoggerWithNotifications(listOf())
+        val data: MutableList<StatsEvent> = mutableListOf()
+        assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data))
+            .isEqualTo(StatsManager.PULL_SUCCESS)
+        assertThat(data).isEmpty()
+    }
+
+    @Test
+    fun onPullAtom_notificationPassed_populatesData() {
+        val icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888))
+        val notification =
+            Notification.Builder(context).setSmallIcon(icon).setContentTitle("title").build()
+        val logger = createLoggerWithNotifications(listOf(notification))
+        val data: MutableList<StatsEvent> = mutableListOf()
+
+        assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data))
+            .isEqualTo(StatsManager.PULL_SUCCESS)
+        assertThat(data).hasSize(1)
+    }
+
+    @Test
+    fun onPullAtom_multipleNotificationsPassed_populatesData() {
+        val icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888))
+        val notification =
+            Notification.Builder(context).setSmallIcon(icon).setContentTitle("title").build()
+        val iconTwo = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888))
+
+        val notificationTwo =
+            Notification.Builder(context)
+                .setStyle(Notification.BigTextStyle().bigText("text"))
+                .setSmallIcon(iconTwo)
+                .setContentTitle("titleTwo")
+                .build()
+        val logger = createLoggerWithNotifications(listOf(notification, notificationTwo))
+        val data: MutableList<StatsEvent> = mutableListOf()
+
+        assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data))
+            .isEqualTo(StatsManager.PULL_SUCCESS)
+        assertThat(data).hasSize(2)
+    }
+
+    private fun createLoggerWithNotifications(
+        notifications: List<Notification>
+    ): NotificationMemoryLogger {
+        val pipeline: NotifPipeline = mock()
+        val notifications =
+            notifications.map { notification ->
+                NotificationEntryBuilder().setTag("test").setNotification(notification).build()
+            }
+        whenever(pipeline.allNotifs).thenReturn(notifications)
+        return NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
index f69839b..072a497 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
@@ -23,6 +23,7 @@
 import android.content.Intent
 import android.graphics.Bitmap
 import android.graphics.drawable.Icon
+import android.stats.sysui.NotificationEnums
 import android.testing.AndroidTestingRunner
 import android.widget.RemoteViews
 import androidx.test.filters.SmallTest
@@ -50,7 +51,27 @@
             extras = 3316,
             bigPicture = 0,
             extender = 0,
-            style = null,
+            style = NotificationEnums.STYLE_NONE,
+            styleIcon = 0,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_rankerGroupNotification() {
+        val notification = createBasicNotification().build()
+        val memoryUse =
+            NotificationMemoryMeter.notificationMemoryUse(
+                createNotificationEntry(createBasicNotification().setGroup("ranker_group").build())
+            )
+        assertNotificationObjectSizes(
+            memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 3316,
+            bigPicture = 0,
+            extender = 0,
+            style = NotificationEnums.STYLE_RANKER_GROUP,
             styleIcon = 0,
             hasCustomView = false,
         )
@@ -69,7 +90,7 @@
             extras = 3316,
             bigPicture = 0,
             extender = 0,
-            style = null,
+            style = NotificationEnums.STYLE_NONE,
             styleIcon = 0,
             hasCustomView = false,
         )
@@ -92,7 +113,7 @@
             extras = 3384,
             bigPicture = 0,
             extender = 0,
-            style = null,
+            style = NotificationEnums.STYLE_NONE,
             styleIcon = 0,
             hasCustomView = true,
         )
@@ -112,7 +133,7 @@
             extras = 3212,
             bigPicture = 0,
             extender = 0,
-            style = null,
+            style = NotificationEnums.STYLE_NONE,
             styleIcon = 0,
             hasCustomView = false,
         )
@@ -141,7 +162,7 @@
             extras = 4092,
             bigPicture = bigPicture.bitmap.allocationByteCount,
             extender = 0,
-            style = "BigPictureStyle",
+            style = NotificationEnums.STYLE_BIG_PICTURE,
             styleIcon = bigPictureIcon.bitmap.allocationByteCount,
             hasCustomView = false,
         )
@@ -167,7 +188,7 @@
             extras = 4084,
             bigPicture = 0,
             extender = 0,
-            style = "CallStyle",
+            style = NotificationEnums.STYLE_CALL,
             styleIcon = personIcon.bitmap.allocationByteCount,
             hasCustomView = false,
         )
@@ -203,7 +224,7 @@
             extras = 5024,
             bigPicture = 0,
             extender = 0,
-            style = "MessagingStyle",
+            style = NotificationEnums.STYLE_MESSAGING,
             styleIcon =
                 personIcon.bitmap.allocationByteCount +
                     historicPersonIcon.bitmap.allocationByteCount,
@@ -225,7 +246,7 @@
             extras = 3612,
             bigPicture = 0,
             extender = 556656,
-            style = null,
+            style = NotificationEnums.STYLE_NONE,
             styleIcon = 0,
             hasCustomView = false,
         )
@@ -246,7 +267,7 @@
             extras = 3820,
             bigPicture = 0,
             extender = 388 + wearBackground.allocationByteCount,
-            style = null,
+            style = NotificationEnums.STYLE_NONE,
             styleIcon = 0,
             hasCustomView = false,
         )
@@ -272,7 +293,7 @@
         extras: Int,
         bigPicture: Int,
         extender: Int,
-        style: String?,
+        style: Int,
         styleIcon: Int,
         hasCustomView: Boolean,
     ) {
@@ -282,11 +303,7 @@
         assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
         assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
         assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
-        if (style == null) {
-            assertThat(memoryUse.objectUsage.style).isNull()
-        } else {
-            assertThat(memoryUse.objectUsage.style).isEqualTo(style)
-        }
+        assertThat(memoryUse.objectUsage.style).isEqualTo(style)
         assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon)
         assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
index 3a16fb3..a0f5048 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
@@ -8,6 +8,7 @@
 import android.widget.RemoteViews
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.tests.R
 import com.google.common.truth.Truth.assertThat
@@ -39,16 +40,84 @@
     fun testViewWalker_plainNotification() {
         val row = testHelper.createRow()
         val result = NotificationMemoryViewWalker.getViewUsage(row)
-        assertThat(result).hasSize(5)
-        assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
-        assertThat(result)
-            .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
+        assertThat(result).hasSize(3)
         assertThat(result)
             .contains(NotificationViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, 0, 0, 0, 0, 0, 0))
         assertThat(result)
             .contains(NotificationViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, 0, 0, 0, 0, 0, 0))
+        assertThat(result).contains(NotificationViewUsage(ViewType.TOTAL, 0, 0, 0, 0, 0, 0))
+    }
+
+    @Test
+    fun testViewWalker_plainNotification_withPublicView() {
+        val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
+        val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888))
+        testHelper.setDefaultInflationFlags(NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL)
+        val row =
+            testHelper.createRow(
+                Notification.Builder(mContext)
+                    .setContentText("Test")
+                    .setContentTitle("title")
+                    .setSmallIcon(icon)
+                    .setPublicVersion(
+                        Notification.Builder(mContext)
+                            .setContentText("Public Test")
+                            .setContentTitle("title")
+                            .setSmallIcon(publicIcon)
+                            .build()
+                    )
+                    .build()
+            )
+        val result = NotificationMemoryViewWalker.getViewUsage(row)
+        assertThat(result).hasSize(4)
         assertThat(result)
-            .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
+            .contains(
+                NotificationViewUsage(
+                    ViewType.PRIVATE_EXPANDED_VIEW,
+                    icon.bitmap.allocationByteCount,
+                    0,
+                    0,
+                    0,
+                    0,
+                    icon.bitmap.allocationByteCount
+                )
+            )
+        assertThat(result)
+            .contains(
+                NotificationViewUsage(
+                    ViewType.PRIVATE_CONTRACTED_VIEW,
+                    icon.bitmap.allocationByteCount,
+                    0,
+                    0,
+                    0,
+                    0,
+                    icon.bitmap.allocationByteCount
+                )
+            )
+        assertThat(result)
+            .contains(
+                NotificationViewUsage(
+                    ViewType.PUBLIC_VIEW,
+                    publicIcon.bitmap.allocationByteCount,
+                    0,
+                    0,
+                    0,
+                    0,
+                    publicIcon.bitmap.allocationByteCount
+                )
+            )
+        assertThat(result)
+            .contains(
+                NotificationViewUsage(
+                    ViewType.TOTAL,
+                    icon.bitmap.allocationByteCount + publicIcon.bitmap.allocationByteCount,
+                    0,
+                    0,
+                    0,
+                    0,
+                    icon.bitmap.allocationByteCount + publicIcon.bitmap.allocationByteCount
+                )
+            )
     }
 
     @Test
@@ -67,7 +136,7 @@
                     .build()
             )
         val result = NotificationMemoryViewWalker.getViewUsage(row)
-        assertThat(result).hasSize(5)
+        assertThat(result).hasSize(3)
         assertThat(result)
             .contains(
                 NotificationViewUsage(
@@ -95,8 +164,20 @@
                     icon.bitmap.allocationByteCount + largeIcon.bitmap.allocationByteCount
                 )
             )
-        // Due to deduplication, this should all be 0.
-        assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+        assertThat(result)
+            .contains(
+                NotificationViewUsage(
+                    ViewType.TOTAL,
+                    icon.bitmap.allocationByteCount,
+                    largeIcon.bitmap.allocationByteCount,
+                    0,
+                    bigPicture.allocationByteCount,
+                    0,
+                    bigPicture.allocationByteCount +
+                        icon.bitmap.allocationByteCount +
+                        largeIcon.bitmap.allocationByteCount
+                )
+            )
     }
 
     @Test
@@ -117,7 +198,7 @@
                     .build()
             )
         val result = NotificationMemoryViewWalker.getViewUsage(row)
-        assertThat(result).hasSize(5)
+        assertThat(result).hasSize(3)
         assertThat(result)
             .contains(
                 NotificationViewUsage(
@@ -142,7 +223,17 @@
                     bitmap.allocationByteCount + icon.bitmap.allocationByteCount
                 )
             )
-        // Due to deduplication, this should all be 0.
-        assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+        assertThat(result)
+            .contains(
+                NotificationViewUsage(
+                    ViewType.TOTAL,
+                    icon.bitmap.allocationByteCount,
+                    0,
+                    0,
+                    0,
+                    bitmap.allocationByteCount,
+                    bitmap.allocationByteCount + icon.bitmap.allocationByteCount
+                )
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 4d9db8c..5832569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -518,7 +518,7 @@
         val childHunView = createHunViewMock(
                 isShadeOpen = true,
                 fullyVisible = false,
-                headerVisibleAmount = 1f
+                headerVisibleAmount = 1f,
         )
         val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
         algorithmState.visibleChildren.add(childHunView)
@@ -526,7 +526,6 @@
         // When: updateChildZValue() is called for the top HUN
         stackScrollAlgorithm.updateChildZValue(
                 /* i= */ 0,
-                /* childrenOnTop= */ 0.0f,
                 /* StackScrollAlgorithmState= */ algorithmState,
                 /* ambientState= */ ambientState,
                 /* shouldElevateHun= */ true
@@ -546,7 +545,7 @@
         val childHunView = createHunViewMock(
                 isShadeOpen = true,
                 fullyVisible = false,
-                headerVisibleAmount = 1f
+                headerVisibleAmount = 1f,
         )
         // Use half of the HUN's height as overlap
         childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat()
@@ -556,7 +555,6 @@
         // When: updateChildZValue() is called for the top HUN
         stackScrollAlgorithm.updateChildZValue(
                 /* i= */ 0,
-                /* childrenOnTop= */ 0.0f,
                 /* StackScrollAlgorithmState= */ algorithmState,
                 /* ambientState= */ ambientState,
                 /* shouldElevateHun= */ true
@@ -580,7 +578,7 @@
         val childHunView = createHunViewMock(
                 isShadeOpen = true,
                 fullyVisible = true,
-                headerVisibleAmount = 1f
+                headerVisibleAmount = 1f,
         )
         // HUN doesn't overlap with QQS Panel
         childHunView.viewState.yTranslation = ambientState.topPadding +
@@ -591,7 +589,6 @@
         // When: updateChildZValue() is called for the top HUN
         stackScrollAlgorithm.updateChildZValue(
                 /* i= */ 0,
-                /* childrenOnTop= */ 0.0f,
                 /* StackScrollAlgorithmState= */ algorithmState,
                 /* ambientState= */ ambientState,
                 /* shouldElevateHun= */ true
@@ -611,7 +608,7 @@
         val childHunView = createHunViewMock(
                 isShadeOpen = false,
                 fullyVisible = false,
-                headerVisibleAmount = 0f
+                headerVisibleAmount = 0f,
         )
         childHunView.viewState.yTranslation = 0f
         // Shade is closed, thus childHunView's headerVisibleAmount is 0
@@ -622,7 +619,6 @@
         // When: updateChildZValue() is called for the top HUN
         stackScrollAlgorithm.updateChildZValue(
                 /* i= */ 0,
-                /* childrenOnTop= */ 0.0f,
                 /* StackScrollAlgorithmState= */ algorithmState,
                 /* ambientState= */ ambientState,
                 /* shouldElevateHun= */ true
@@ -642,7 +638,7 @@
         val childHunView = createHunViewMock(
                 isShadeOpen = false,
                 fullyVisible = false,
-                headerVisibleAmount = 0.5f
+                headerVisibleAmount = 0.5f,
         )
         childHunView.viewState.yTranslation = 0f
         // Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1
@@ -654,7 +650,6 @@
         // When: updateChildZValue() is called for the top HUN
         stackScrollAlgorithm.updateChildZValue(
                 /* i= */ 0,
-                /* childrenOnTop= */ 0.0f,
                 /* StackScrollAlgorithmState= */ algorithmState,
                 /* ambientState= */ ambientState,
                 /* shouldElevateHun= */ true
@@ -669,7 +664,7 @@
     private fun createHunViewMock(
             isShadeOpen: Boolean,
             fullyVisible: Boolean,
-            headerVisibleAmount: Float
+            headerVisibleAmount: Float,
     ) =
             mock<ExpandableNotificationRow>().apply {
                 val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
@@ -680,7 +675,10 @@
             }
 
 
-    private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) =
+    private fun createHunChildViewState(
+            isShadeOpen: Boolean,
+            fullyVisible: Boolean,
+    ) =
             ExpandableViewState().apply {
                 // Mock the HUN's height with ambientState.topPadding +
                 // ambientState.stackTranslation
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 9727b6c5..e5e5d94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.mock;
@@ -33,12 +35,14 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.HeadsUpStatusBarView;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
@@ -64,7 +68,9 @@
             mock(NotificationPanelViewController.class);
     private final DarkIconDispatcher mDarkIconDispatcher = mock(DarkIconDispatcher.class);
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
-    private ExpandableNotificationRow mFirst;
+    private NotificationTestHelper mTestHelper;
+    private ExpandableNotificationRow mRow;
+    private NotificationEntry mEntry;
     private HeadsUpStatusBarView mHeadsUpStatusBarView;
     private HeadsUpManagerPhone mHeadsUpManager;
     private View mOperatorNameView;
@@ -79,11 +85,12 @@
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
-        NotificationTestHelper testHelper = new NotificationTestHelper(
+        mTestHelper = new NotificationTestHelper(
                 mContext,
                 mDependency,
                 TestableLooper.get(this));
-        mFirst = testHelper.createRow();
+        mRow = mTestHelper.createRow();
+        mEntry = mRow.getEntry();
         mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class),
                 mock(TextView.class));
         mHeadsUpManager = mock(HeadsUpManagerPhone.class);
@@ -95,6 +102,7 @@
         mCommandQueue = mock(CommandQueue.class);
         mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
         mFeatureFlag = mock(FeatureFlags.class);
+        when(mFeatureFlag.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)).thenReturn(true);
         mHeadsUpAppearanceController = new HeadsUpAppearanceController(
                 mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
@@ -116,60 +124,60 @@
 
     @Test
     public void testShowinEntryUpdated() {
-        mFirst.setPinned(true);
+        mRow.setPinned(true);
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
-        when(mHeadsUpManager.getTopEntry()).thenReturn(mFirst.getEntry());
-        mHeadsUpAppearanceController.onHeadsUpPinned(mFirst.getEntry());
-        Assert.assertEquals(mFirst.getEntry(), mHeadsUpStatusBarView.getShowingEntry());
+        when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
+        mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
+        assertEquals(mRow.getEntry(), mHeadsUpStatusBarView.getShowingEntry());
 
-        mFirst.setPinned(false);
+        mRow.setPinned(false);
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
-        mHeadsUpAppearanceController.onHeadsUpUnPinned(mFirst.getEntry());
-        Assert.assertEquals(null, mHeadsUpStatusBarView.getShowingEntry());
+        mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
+        assertEquals(null, mHeadsUpStatusBarView.getShowingEntry());
     }
 
     @Test
     public void testShownUpdated() {
-        mFirst.setPinned(true);
+        mRow.setPinned(true);
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
-        when(mHeadsUpManager.getTopEntry()).thenReturn(mFirst.getEntry());
-        mHeadsUpAppearanceController.onHeadsUpPinned(mFirst.getEntry());
-        Assert.assertTrue(mHeadsUpAppearanceController.isShown());
+        when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
+        mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
+        assertTrue(mHeadsUpAppearanceController.isShown());
 
-        mFirst.setPinned(false);
+        mRow.setPinned(false);
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
-        mHeadsUpAppearanceController.onHeadsUpUnPinned(mFirst.getEntry());
+        mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
         Assert.assertFalse(mHeadsUpAppearanceController.isShown());
     }
 
     @Test
     public void testHeaderUpdated() {
-        mFirst.setPinned(true);
+        mRow.setPinned(true);
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
-        when(mHeadsUpManager.getTopEntry()).thenReturn(mFirst.getEntry());
-        mHeadsUpAppearanceController.onHeadsUpPinned(mFirst.getEntry());
-        Assert.assertEquals(mFirst.getHeaderVisibleAmount(), 0.0f, 0.0f);
+        when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
+        mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
+        assertEquals(mRow.getHeaderVisibleAmount(), 0.0f, 0.0f);
 
-        mFirst.setPinned(false);
+        mRow.setPinned(false);
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
-        mHeadsUpAppearanceController.onHeadsUpUnPinned(mFirst.getEntry());
-        Assert.assertEquals(mFirst.getHeaderVisibleAmount(), 1.0f, 0.0f);
+        mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
+        assertEquals(mRow.getHeaderVisibleAmount(), 1.0f, 0.0f);
     }
 
     @Test
     public void testOperatorNameViewUpdated() {
         mHeadsUpAppearanceController.setAnimationsEnabled(false);
 
-        mFirst.setPinned(true);
+        mRow.setPinned(true);
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
-        when(mHeadsUpManager.getTopEntry()).thenReturn(mFirst.getEntry());
-        mHeadsUpAppearanceController.onHeadsUpPinned(mFirst.getEntry());
-        Assert.assertEquals(View.INVISIBLE, mOperatorNameView.getVisibility());
+        when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
+        mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
+        assertEquals(View.INVISIBLE, mOperatorNameView.getVisibility());
 
-        mFirst.setPinned(false);
+        mRow.setPinned(false);
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
-        mHeadsUpAppearanceController.onHeadsUpUnPinned(mFirst.getEntry());
-        Assert.assertEquals(View.VISIBLE, mOperatorNameView.getVisibility());
+        mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
+        assertEquals(View.VISIBLE, mOperatorNameView.getVisibility());
     }
 
     @Test
@@ -196,8 +204,8 @@
                 new Clock(mContext, null),
                 Optional.empty());
 
-        Assert.assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f);
-        Assert.assertEquals(appearFraction, newController.mAppearFraction, 0.0f);
+        assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f);
+        assertEquals(appearFraction, newController.mAppearFraction, 0.0f);
     }
 
     @Test
@@ -215,4 +223,68 @@
         verify(mPanelView).setHeadsUpAppearanceController(isNull());
         verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any());
     }
+
+    @Test
+    public void testPulsingRoundness_onUpdateHeadsUpAndPulsingRoundness() {
+        // Pulsing: Enable flag and dozing
+        when(mNotificationRoundnessManager.shouldRoundNotificationPulsing()).thenReturn(true);
+        when(mTestHelper.getStatusBarStateController().isDozing()).thenReturn(true);
+
+        // Pulsing: Enabled
+        mRow.setHeadsUp(true);
+        mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(mEntry);
+
+        String debugString = mRow.getRoundableState().debugString();
+        assertEquals(
+                "If Pulsing is enabled, roundness should be set to 1. Value: " + debugString,
+                /* expected = */ 1,
+                /* actual = */ mRow.getTopRoundness(),
+                /* delta = */ 0.001
+        );
+        assertTrue(debugString.contains("Pulsing"));
+
+        // Pulsing: Disabled
+        mRow.setHeadsUp(false);
+        mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(mEntry);
+
+        assertEquals(
+                "If Pulsing is disabled, roundness should be set to 0. Value: "
+                        + mRow.getRoundableState().debugString(),
+                /* expected = */ 0,
+                /* actual = */ mRow.getTopRoundness(),
+                /* delta = */ 0.001
+        );
+    }
+
+    @Test
+    public void testPulsingRoundness_onHeadsUpStateChanged() {
+        // Pulsing: Enable flag and dozing
+        when(mNotificationRoundnessManager.shouldRoundNotificationPulsing()).thenReturn(true);
+        when(mTestHelper.getStatusBarStateController().isDozing()).thenReturn(true);
+
+        // Pulsing: Enabled
+        mEntry.setHeadsUp(true);
+        mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true);
+
+        String debugString = mRow.getRoundableState().debugString();
+        assertEquals(
+                "If Pulsing is enabled, roundness should be set to 1. Value: " + debugString,
+                /* expected = */ 1,
+                /* actual = */ mRow.getTopRoundness(),
+                /* delta = */ 0.001
+        );
+        assertTrue(debugString.contains("Pulsing"));
+
+        // Pulsing: Disabled
+        mEntry.setHeadsUp(false);
+        mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false);
+
+        assertEquals(
+                "If Pulsing is disabled, roundness should be set to 0. Value: "
+                        + mRow.getRoundableState().debugString(),
+                /* expected = */ 0,
+                /* actual = */ mRow.getTopRoundness(),
+                /* delta = */ 0.001
+        );
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 3d9fd96..22c0ea1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -16,11 +16,14 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.ui.view
 
+import android.content.res.ColorStateList
+import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.ViewUtils
 import android.view.View
+import android.widget.ImageView
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
@@ -44,6 +47,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -229,10 +233,43 @@
         ViewUtils.detachView(view)
     }
 
+    @Test
+    fun onDarkChanged_iconHasNewColor() {
+        whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false)
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        val areas = ArrayList(listOf(Rect(0, 0, 1000, 1000)))
+        val color = 0x12345678
+        view.onDarkChanged(areas, 1.0f, color)
+        testableLooper.processAllMessages()
+
+        assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+    }
+
+    @Test
+    fun setStaticDrawableColor_iconHasNewColor() {
+        whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false)
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        val color = 0x23456789
+        view.setStaticDrawableColor(color)
+        testableLooper.processAllMessages()
+
+        assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+    }
+
     private fun View.getIconGroupView(): View {
         return this.requireViewById(R.id.wifi_group)
     }
 
+    private fun View.getIconView(): ImageView {
+        return this.requireViewById(R.id.wifi_signal)
+    }
+
     private fun View.getDotView(): View {
         return this.requireViewById(R.id.status_bar_dot)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index 15235b6..c35bc69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -149,11 +149,12 @@
     }
 
     @Test
-    public void testGetDeviceOwnerType() {
+    public void testIsFinancedDevice() {
+        when(mDevicePolicyManager.isFinancedDevice()).thenReturn(true);
+        // TODO(b/259908270): remove
         when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
-        assertEquals(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT),
-                DEVICE_OWNER_TYPE_FINANCED);
+        assertEquals(mSecurityController.isFinancedDevice(), true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
index 0d19ab1..056e386 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
@@ -101,4 +101,52 @@
             assertThat(multiRippleView.ripples.size).isEqualTo(0)
         }
     }
+
+    @Test
+    fun play_onFinishesAllRipples_triggersRipplesFinished() {
+        var isTriggered = false
+        val listener =
+            object : MultiRippleController.Companion.RipplesFinishedListener {
+                override fun onRipplesFinish() {
+                    isTriggered = true
+                }
+            }
+        multiRippleController.addRipplesFinishedListener(listener)
+
+        fakeExecutor.execute {
+            multiRippleController.play(RippleAnimation(RippleAnimationConfig(duration = 1000)))
+            multiRippleController.play(RippleAnimation(RippleAnimationConfig(duration = 2000)))
+
+            assertThat(multiRippleView.ripples.size).isEqualTo(2)
+
+            fakeSystemClock.advanceTime(2000L)
+
+            assertThat(multiRippleView.ripples.size).isEqualTo(0)
+            assertThat(isTriggered).isTrue()
+        }
+    }
+
+    @Test
+    fun play_notAllRipplesFinished_doesNotTriggerRipplesFinished() {
+        var isTriggered = false
+        val listener =
+            object : MultiRippleController.Companion.RipplesFinishedListener {
+                override fun onRipplesFinish() {
+                    isTriggered = true
+                }
+            }
+        multiRippleController.addRipplesFinishedListener(listener)
+
+        fakeExecutor.execute {
+            multiRippleController.play(RippleAnimation(RippleAnimationConfig(duration = 1000)))
+            multiRippleController.play(RippleAnimation(RippleAnimationConfig(duration = 2000)))
+
+            assertThat(multiRippleView.ripples.size).isEqualTo(2)
+
+            fakeSystemClock.advanceTime(1000L)
+
+            assertThat(multiRippleView.ripples.size).isEqualTo(1)
+            assertThat(isTriggered).isFalse()
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
deleted file mode 100644
index 2024d53..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2022 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.systemui.surfaceeffects.ripple
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class MultiRippleViewTest : SysuiTestCase() {
-    private val fakeSystemClock = FakeSystemClock()
-    // FakeExecutor is needed to run animator.
-    private val fakeExecutor = FakeExecutor(fakeSystemClock)
-
-    @Test
-    fun onRippleFinishes_triggersRippleFinished() {
-        val multiRippleView = MultiRippleView(context, null)
-        val multiRippleController = MultiRippleController(multiRippleView)
-        val rippleAnimationConfig = RippleAnimationConfig(duration = 1000L)
-
-        var isTriggered = false
-        val listener =
-            object : MultiRippleView.Companion.RipplesFinishedListener {
-                override fun onRipplesFinish() {
-                    isTriggered = true
-                }
-            }
-        multiRippleView.addRipplesFinishedListener(listener)
-
-        fakeExecutor.execute {
-            val rippleAnimation = RippleAnimation(rippleAnimationConfig)
-            multiRippleController.play(rippleAnimation)
-
-            fakeSystemClock.advanceTime(rippleAnimationConfig.duration)
-
-            assertThat(isTriggered).isTrue()
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 09f0d4a..82153d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.SystemClock
 import com.android.systemui.util.wakelock.WakeLock
 import com.android.systemui.util.wakelock.WakeLockFake
 import com.google.common.truth.Truth.assertThat
@@ -59,7 +60,7 @@
     private lateinit var fakeWakeLock: WakeLockFake
 
     @Mock
-    private lateinit var logger: TemporaryViewLogger
+    private lateinit var logger: TemporaryViewLogger<ViewInfo>
     @Mock
     private lateinit var accessibilityManager: AccessibilityManager
     @Mock
@@ -74,7 +75,7 @@
         MockitoAnnotations.initMocks(this)
 
         whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any()))
-            .thenReturn(TIMEOUT_MS.toInt())
+            .thenAnswer { it.arguments[0] }
 
         fakeClock = FakeSystemClock()
         fakeExecutor = FakeExecutor(fakeClock)
@@ -84,14 +85,15 @@
         fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
 
         underTest = TestController(
-                context,
-                logger,
-                windowManager,
-                fakeExecutor,
-                accessibilityManager,
-                configurationController,
-                powerManager,
-                fakeWakeLockBuilder,
+            context,
+            logger,
+            windowManager,
+            fakeExecutor,
+            accessibilityManager,
+            configurationController,
+            powerManager,
+            fakeWakeLockBuilder,
+            fakeClock,
         )
         underTest.start()
     }
@@ -112,14 +114,14 @@
 
     @Test
     fun displayView_logged() {
-        underTest.displayView(
-            ViewInfo(
-                name = "name",
-                windowTitle = "Fake Window Title",
-            )
+        val info = ViewInfo(
+            name = "name",
+            windowTitle = "Fake Window Title",
         )
 
-        verify(logger).logViewAddition("id", "Fake Window Title")
+        underTest.displayView(info)
+
+        verify(logger).logViewAddition(info)
     }
 
     @Test
@@ -168,10 +170,11 @@
     }
 
     @Test
-    fun displayView_twiceWithDifferentWindowTitles_oldViewRemovedNewViewAdded() {
+    fun displayView_twiceWithDifferentIds_oldViewRemovedNewViewAdded() {
         underTest.displayView(
             ViewInfo(
                 name = "name",
+                id = "First",
                 windowTitle = "First Fake Window Title",
             )
         )
@@ -179,6 +182,7 @@
         underTest.displayView(
             ViewInfo(
                 name = "name",
+                id = "Second",
                 windowTitle = "Second Fake Window Title",
             )
         )
@@ -263,19 +267,69 @@
     }
 
     @Test
+    fun viewUpdatedWithNewOnViewTimeoutRunnable_newRunnableUsed() {
+        var runnable1Run = false
+        underTest.displayView(ViewInfo(name = "name", id = "id1", windowTitle = "1")) {
+            runnable1Run = true
+        }
+
+        var runnable2Run = false
+        underTest.displayView(ViewInfo(name = "name", id = "id1", windowTitle = "1")) {
+            runnable2Run = true
+        }
+
+        fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+        assertThat(runnable1Run).isFalse()
+        assertThat(runnable2Run).isTrue()
+    }
+
+    @Test
+    fun multipleViewsWithDifferentIds_moreRecentReplacesOlder() {
+        underTest.displayView(
+            ViewInfo(
+                name = "name",
+                windowTitle = "First Fake Window Title",
+                id = "id1"
+            )
+        )
+
+        underTest.displayView(
+            ViewInfo(
+                name = "name",
+                windowTitle = "Second Fake Window Title",
+                id = "id2"
+            )
+        )
+
+        val viewCaptor = argumentCaptor<View>()
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+
+        verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
+
+        assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title")
+        assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
+        verify(windowManager).removeView(viewCaptor.allValues[0])
+        verify(configurationController, never()).removeCallback(any())
+    }
+
+    @Test
     fun multipleViewsWithDifferentIds_recentActiveViewIsDisplayed() {
         underTest.displayView(ViewInfo("First name", id = "id1"))
 
         verify(windowManager).addView(any(), any())
-
         reset(windowManager)
+
         underTest.displayView(ViewInfo("Second name", id = "id2"))
+
+        verify(windowManager).removeView(any())
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
         underTest.removeView("id2", "test reason")
 
         verify(windowManager).removeView(any())
-
-        fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
-
+        verify(windowManager).addView(any(), any())
         assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
         assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
 
@@ -284,6 +338,7 @@
 
         verify(windowManager).removeView(any())
         assertThat(underTest.activeViews.size).isEqualTo(0)
+        verify(configurationController).removeCallback(any())
     }
 
     @Test
@@ -291,19 +346,28 @@
         underTest.displayView(ViewInfo("First name", id = "id1"))
 
         verify(windowManager).addView(any(), any())
-
         reset(windowManager)
+
         underTest.displayView(ViewInfo("Second name", id = "id2"))
+
+        verify(windowManager).removeView(any())
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        // WHEN an old view is removed
         underTest.removeView("id1", "test reason")
 
+        // THEN we don't update anything
         verify(windowManager, never()).removeView(any())
         assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
         assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
+        verify(configurationController, never()).removeCallback(any())
 
         fakeClock.advanceTime(TIMEOUT_MS + 1)
 
         verify(windowManager).removeView(any())
         assertThat(underTest.activeViews.size).isEqualTo(0)
+        verify(configurationController).removeCallback(any())
     }
 
     @Test
@@ -312,33 +376,31 @@
         underTest.displayView(ViewInfo("Second name", id = "id2"))
         underTest.displayView(ViewInfo("Third name", id = "id3"))
 
-        verify(windowManager).addView(any(), any())
+        verify(windowManager, times(3)).addView(any(), any())
+        verify(windowManager, times(2)).removeView(any())
 
         reset(windowManager)
         underTest.removeView("id3", "test reason")
 
         verify(windowManager).removeView(any())
-
-        fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
-
         assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
         assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
+        verify(configurationController, never()).removeCallback(any())
 
         reset(windowManager)
         underTest.removeView("id2", "test reason")
 
         verify(windowManager).removeView(any())
-
-        fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
-
         assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
         assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
+        verify(configurationController, never()).removeCallback(any())
 
         reset(windowManager)
         fakeClock.advanceTime(TIMEOUT_MS + 1)
 
         verify(windowManager).removeView(any())
         assertThat(underTest.activeViews.size).isEqualTo(0)
+        verify(configurationController).removeCallback(any())
     }
 
     @Test
@@ -347,18 +409,21 @@
         underTest.displayView(ViewInfo("New name", id = "id1"))
 
         verify(windowManager).addView(any(), any())
-
         reset(windowManager)
+
         underTest.displayView(ViewInfo("Second name", id = "id2"))
+
+        verify(windowManager).removeView(any())
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
         underTest.removeView("id2", "test reason")
 
         verify(windowManager).removeView(any())
-
-        fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
-
+        verify(windowManager).addView(any(), any())
         assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
         assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("New name")
-        assertThat(underTest.activeViews[0].second.name).isEqualTo("New name")
+        assertThat(underTest.activeViews[0].info.name).isEqualTo("New name")
 
         reset(windowManager)
         fakeClock.advanceTime(TIMEOUT_MS + 1)
@@ -368,19 +433,523 @@
     }
 
     @Test
-    fun multipleViewsWithDifferentIds_viewsTimeouts_noViewLeftToDisplay() {
-        underTest.displayView(ViewInfo("First name", id = "id1"))
-        fakeClock.advanceTime(TIMEOUT_MS / 3)
-        underTest.displayView(ViewInfo("Second name", id = "id2"))
-        fakeClock.advanceTime(TIMEOUT_MS / 3)
-        underTest.displayView(ViewInfo("Third name", id = "id3"))
+    fun multipleViews_mostRecentViewRemoved_otherViewsTimedOutAndNotDisplayed() {
+        underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
+        fakeClock.advanceTime(1000)
+        underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 4000))
+        fakeClock.advanceTime(1000)
+        underTest.displayView(ViewInfo("Third name", id = "id3", timeoutMs = 20000))
 
         reset(windowManager)
-        fakeClock.advanceTime(TIMEOUT_MS + 1)
+        fakeClock.advanceTime(20000 + 1)
 
         verify(windowManager).removeView(any())
         verify(windowManager, never()).addView(any(), any())
         assertThat(underTest.activeViews.size).isEqualTo(0)
+        verify(configurationController).removeCallback(any())
+    }
+
+    @Test
+    fun multipleViews_mostRecentViewRemoved_viewWithShortTimeLeftNotDisplayed() {
+        underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
+        fakeClock.advanceTime(1000)
+        underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 2500))
+
+        reset(windowManager)
+        fakeClock.advanceTime(2500 + 1)
+        // At this point, 3501ms have passed, so id1 only has 499ms left which is not enough.
+        // So, it shouldn't be displayed.
+
+        verify(windowManager, never()).addView(any(), any())
+        assertThat(underTest.activeViews.size).isEqualTo(0)
+        verify(configurationController).removeCallback(any())
+    }
+
+    @Test
+    fun lowerThenHigherPriority_higherReplacesLower() {
+        underTest.displayView(
+            ViewInfo(
+                name = "normal",
+                windowTitle = "Normal Window Title",
+                id = "normal",
+                priority = ViewPriority.NORMAL,
+            )
+        )
+
+        val viewCaptor = argumentCaptor<View>()
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+        reset(windowManager)
+
+        underTest.displayView(
+            ViewInfo(
+                name = "critical",
+                windowTitle = "Critical Window Title",
+                id = "critical",
+                priority = ViewPriority.CRITICAL,
+            )
+        )
+
+        verify(windowManager).removeView(viewCaptor.value)
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+        verify(configurationController, never()).removeCallback(any())
+    }
+
+    @Test
+    fun lowerThenHigherPriority_lowerPriorityRedisplayed() {
+        underTest.displayView(
+            ViewInfo(
+                name = "normal",
+                windowTitle = "Normal Window Title",
+                id = "normal",
+                priority = ViewPriority.NORMAL,
+                timeoutMs = 10000
+            )
+        )
+
+        underTest.displayView(
+            ViewInfo(
+                name = "critical",
+                windowTitle = "Critical Window Title",
+                id = "critical",
+                priority = ViewPriority.CRITICAL,
+                timeoutMs = 2000
+            )
+        )
+
+        val viewCaptor = argumentCaptor<View>()
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+        verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("Normal Window Title")
+        assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Critical Window Title")
+        verify(windowManager).removeView(viewCaptor.allValues[0])
+
+        reset(windowManager)
+
+        // WHEN the critical's timeout has expired
+        fakeClock.advanceTime(2000 + 1)
+
+        // THEN the normal view is re-displayed
+        verify(windowManager).removeView(viewCaptor.allValues[1])
+        verify(windowManager).addView(any(), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+        verify(configurationController, never()).removeCallback(any())
+    }
+
+    @Test
+    fun lowerThenHigherPriority_lowerPriorityNotRedisplayedBecauseTimedOut() {
+        underTest.displayView(
+            ViewInfo(
+                name = "normal",
+                windowTitle = "Normal Window Title",
+                id = "normal",
+                priority = ViewPriority.NORMAL,
+                timeoutMs = 1000
+            )
+        )
+
+        underTest.displayView(
+            ViewInfo(
+                name = "critical",
+                windowTitle = "Critical Window Title",
+                id = "critical",
+                priority = ViewPriority.CRITICAL,
+                timeoutMs = 2000
+            )
+        )
+        reset(windowManager)
+
+        // WHEN the critical's timeout has expired
+        fakeClock.advanceTime(2000 + 1)
+
+        // THEN the normal view is not re-displayed since it already timed out
+        verify(windowManager).removeView(any())
+        verify(windowManager, never()).addView(any(), any())
+        assertThat(underTest.activeViews).isEmpty()
+        verify(configurationController).removeCallback(any())
+    }
+
+    @Test
+    fun higherThenLowerPriority_higherStaysDisplayed() {
+        underTest.displayView(
+            ViewInfo(
+                name = "critical",
+                windowTitle = "Critical Window Title",
+                id = "critical",
+                priority = ViewPriority.CRITICAL,
+            )
+        )
+
+        val viewCaptor = argumentCaptor<View>()
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+        reset(windowManager)
+
+        underTest.displayView(
+            ViewInfo(
+                name = "normal",
+                windowTitle = "Normal Window Title",
+                id = "normal",
+                priority = ViewPriority.NORMAL,
+            )
+        )
+
+        verify(windowManager, never()).removeView(viewCaptor.value)
+        verify(windowManager, never()).addView(any(), any())
+        assertThat(underTest.activeViews.size).isEqualTo(2)
+        verify(configurationController, never()).removeCallback(any())
+    }
+
+    @Test
+    fun higherThenLowerPriority_lowerEventuallyDisplayed() {
+        underTest.displayView(
+            ViewInfo(
+                name = "critical",
+                windowTitle = "Critical Window Title",
+                id = "critical",
+                priority = ViewPriority.CRITICAL,
+                timeoutMs = 3000,
+            )
+        )
+
+        val viewCaptor = argumentCaptor<View>()
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+        reset(windowManager)
+
+        underTest.displayView(
+            ViewInfo(
+                name = "normal",
+                windowTitle = "Normal Window Title",
+                id = "normal",
+                priority = ViewPriority.NORMAL,
+                timeoutMs = 5000,
+            )
+        )
+
+        verify(windowManager, never()).removeView(viewCaptor.value)
+        verify(windowManager, never()).addView(any(), any())
+        assertThat(underTest.activeViews.size).isEqualTo(2)
+
+        // WHEN the first critical view has timed out
+        fakeClock.advanceTime(3000 + 1)
+
+        // THEN the second normal view is displayed
+        verify(windowManager).removeView(viewCaptor.value)
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+        assertThat(underTest.activeViews.size).isEqualTo(1)
+        verify(configurationController, never()).removeCallback(any())
+    }
+
+    @Test
+    fun higherThenLowerPriority_lowerNotDisplayedBecauseTimedOut() {
+        underTest.displayView(
+            ViewInfo(
+                name = "critical",
+                windowTitle = "Critical Window Title",
+                id = "critical",
+                priority = ViewPriority.CRITICAL,
+                timeoutMs = 3000,
+            )
+        )
+
+        val viewCaptor = argumentCaptor<View>()
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+        reset(windowManager)
+
+        underTest.displayView(
+            ViewInfo(
+                name = "normal",
+                windowTitle = "Normal Window Title",
+                id = "normal",
+                priority = ViewPriority.NORMAL,
+                timeoutMs = 200,
+            )
+        )
+
+        verify(windowManager, never()).removeView(viewCaptor.value)
+        verify(windowManager, never()).addView(any(), any())
+        assertThat(underTest.activeViews.size).isEqualTo(2)
+        reset(windowManager)
+
+        // WHEN the first critical view has timed out
+        fakeClock.advanceTime(3000 + 1)
+
+        // THEN the second normal view is not displayed because it's already timed out
+        verify(windowManager).removeView(viewCaptor.value)
+        verify(windowManager, never()).addView(any(), any())
+        assertThat(underTest.activeViews).isEmpty()
+        verify(configurationController).removeCallback(any())
+    }
+
+    @Test
+    fun criticalThenNewCritical_newCriticalDisplayed() {
+        underTest.displayView(
+            ViewInfo(
+                name = "critical 1",
+                windowTitle = "Critical Window Title 1",
+                id = "critical1",
+                priority = ViewPriority.CRITICAL,
+            )
+        )
+
+        val viewCaptor = argumentCaptor<View>()
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title 1")
+        reset(windowManager)
+
+        underTest.displayView(
+            ViewInfo(
+                name = "critical 2",
+                windowTitle = "Critical Window Title 2",
+                id = "critical2",
+                priority = ViewPriority.CRITICAL,
+            )
+        )
+
+        verify(windowManager).removeView(viewCaptor.value)
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title 2")
+        assertThat(underTest.activeViews.size).isEqualTo(2)
+        verify(configurationController, never()).removeCallback(any())
+    }
+
+    @Test
+    fun normalThenNewNormal_newNormalDisplayed() {
+        underTest.displayView(
+            ViewInfo(
+                name = "normal 1",
+                windowTitle = "Normal Window Title 1",
+                id = "normal1",
+                priority = ViewPriority.NORMAL,
+            )
+        )
+
+        val viewCaptor = argumentCaptor<View>()
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title 1")
+        reset(windowManager)
+
+        underTest.displayView(
+            ViewInfo(
+                name = "normal 2",
+                windowTitle = "Normal Window Title 2",
+                id = "normal2",
+                priority = ViewPriority.NORMAL,
+            )
+        )
+
+        verify(windowManager).removeView(viewCaptor.value)
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title 2")
+        assertThat(underTest.activeViews.size).isEqualTo(2)
+        verify(configurationController, never()).removeCallback(any())
+    }
+
+    @Test
+    fun lowerPriorityViewUpdatedWhileHigherPriorityDisplayed_eventuallyDisplaysUpdated() {
+        // First, display a lower priority view
+        underTest.displayView(
+            ViewInfo(
+                name = "normal",
+                windowTitle = "Normal Window Title",
+                id = "normal",
+                priority = ViewPriority.NORMAL,
+                // At the end of the test, we'll verify that this information isn't re-displayed.
+                // Use a super long timeout so that, when we verify it wasn't re-displayed, we know
+                // that it wasn't because the view just timed out.
+                timeoutMs = 100000,
+            )
+        )
+
+        val viewCaptor = argumentCaptor<View>()
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+        reset(windowManager)
+
+        // Then, display a higher priority view
+        fakeClock.advanceTime(1000)
+        underTest.displayView(
+            ViewInfo(
+                name = "critical",
+                windowTitle = "Critical Window Title",
+                id = "critical",
+                priority = ViewPriority.CRITICAL,
+                timeoutMs = 3000,
+            )
+        )
+
+        verify(windowManager).removeView(viewCaptor.value)
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+        assertThat(underTest.activeViews.size).isEqualTo(2)
+        reset(windowManager)
+
+        // While the higher priority view is displayed, update the lower priority view with new
+        // information
+        fakeClock.advanceTime(1000)
+        val updatedViewInfo = ViewInfo(
+            name = "normal with update",
+            windowTitle = "Normal Window Title",
+            id = "normal",
+            priority = ViewPriority.NORMAL,
+            timeoutMs = 4000,
+        )
+        underTest.displayView(updatedViewInfo)
+
+        verify(windowManager, never()).removeView(viewCaptor.value)
+        verify(windowManager, never()).addView(any(), any())
+        assertThat(underTest.activeViews.size).isEqualTo(2)
+        reset(windowManager)
+
+        // WHEN the higher priority view times out
+        fakeClock.advanceTime(2001)
+
+        // THEN the higher priority view disappears and the lower priority view *with the updated
+        // information* gets displayed.
+        verify(windowManager).removeView(viewCaptor.value)
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+        assertThat(underTest.activeViews.size).isEqualTo(1)
+        assertThat(underTest.mostRecentViewInfo).isEqualTo(updatedViewInfo)
+        reset(windowManager)
+
+        // WHEN the updated view times out
+        fakeClock.advanceTime(2001)
+
+        // THEN the old information is never displayed
+        verify(windowManager).removeView(viewCaptor.value)
+        verify(windowManager, never()).addView(any(), any())
+        assertThat(underTest.activeViews.size).isEqualTo(0)
+    }
+
+    @Test
+    fun oldViewUpdatedWhileNewViewDisplayed_eventuallyDisplaysUpdated() {
+        // First, display id1 view
+        underTest.displayView(
+            ViewInfo(
+                name = "name 1",
+                windowTitle = "Name 1 Title",
+                id = "id1",
+                priority = ViewPriority.NORMAL,
+                // At the end of the test, we'll verify that this information isn't re-displayed.
+                // Use a super long timeout so that, when we verify it wasn't re-displayed, we know
+                // that it wasn't because the view just timed out.
+                timeoutMs = 100000,
+            )
+        )
+
+        val viewCaptor = argumentCaptor<View>()
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title")
+        reset(windowManager)
+
+        // Then, display a new id2 view
+        fakeClock.advanceTime(1000)
+        underTest.displayView(
+            ViewInfo(
+                name = "name 2",
+                windowTitle = "Name 2 Title",
+                id = "id2",
+                priority = ViewPriority.NORMAL,
+                timeoutMs = 3000,
+            )
+        )
+
+        verify(windowManager).removeView(viewCaptor.value)
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Name 2 Title")
+        assertThat(underTest.activeViews.size).isEqualTo(2)
+        reset(windowManager)
+
+        // While the id2 view is displayed, re-display the id1 view with new information
+        fakeClock.advanceTime(1000)
+        val updatedViewInfo = ViewInfo(
+            name = "name 1 with update",
+            windowTitle = "Name 1 Title",
+            id = "id1",
+            priority = ViewPriority.NORMAL,
+            timeoutMs = 3000,
+        )
+        underTest.displayView(updatedViewInfo)
+
+        verify(windowManager).removeView(viewCaptor.value)
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title")
+        assertThat(underTest.activeViews.size).isEqualTo(2)
+        reset(windowManager)
+
+        // WHEN the id1 view with new information times out
+        fakeClock.advanceTime(3001)
+
+        // THEN the id1 view disappears and the old id1 information is never displayed
+        verify(windowManager).removeView(viewCaptor.value)
+        verify(windowManager, never()).addView(any(), any())
+        assertThat(underTest.activeViews.size).isEqualTo(0)
+    }
+
+    @Test
+    fun oldViewUpdatedWhileNewViewDisplayed_usesNewTimeout() {
+        // First, display id1 view
+        underTest.displayView(
+            ViewInfo(
+                name = "name 1",
+                windowTitle = "Name 1 Title",
+                id = "id1",
+                priority = ViewPriority.NORMAL,
+                timeoutMs = 5000,
+            )
+        )
+
+        // Then, display a new id2 view
+        fakeClock.advanceTime(1000)
+        underTest.displayView(
+            ViewInfo(
+                name = "name 2",
+                windowTitle = "Name 2 Title",
+                id = "id2",
+                priority = ViewPriority.NORMAL,
+                timeoutMs = 3000,
+            )
+        )
+        reset(windowManager)
+
+        // While the id2 view is displayed, re-display the id1 view with new information *and a
+        // longer timeout*
+        fakeClock.advanceTime(1000)
+        val updatedViewInfo = ViewInfo(
+            name = "name 1 with update",
+            windowTitle = "Name 1 Title",
+            id = "id1",
+            priority = ViewPriority.NORMAL,
+            timeoutMs = 30000,
+        )
+        underTest.displayView(updatedViewInfo)
+
+        val viewCaptor = argumentCaptor<View>()
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+        verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title")
+        assertThat(underTest.activeViews.size).isEqualTo(2)
+        reset(windowManager)
+
+        // WHEN id1's *old* timeout occurs
+        fakeClock.advanceTime(3001)
+
+        // THEN id1 is still displayed because it was updated with a new timeout
+        verify(windowManager, never()).removeView(viewCaptor.value)
+        assertThat(underTest.activeViews.size).isEqualTo(1)
     }
 
     @Test
@@ -395,6 +964,7 @@
 
         verify(windowManager).removeView(any())
         verify(logger).logViewRemoval(deviceId, reason)
+        verify(configurationController).removeCallback(any())
     }
 
     @Test
@@ -414,14 +984,15 @@
 
     inner class TestController(
         context: Context,
-        logger: TemporaryViewLogger,
+        logger: TemporaryViewLogger<ViewInfo>,
         windowManager: WindowManager,
         @Main mainExecutor: DelayableExecutor,
         accessibilityManager: AccessibilityManager,
         configurationController: ConfigurationController,
         powerManager: PowerManager,
         wakeLockBuilder: WakeLock.Builder,
-    ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger>(
+        systemClock: SystemClock,
+    ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger<ViewInfo>>(
         context,
         logger,
         windowManager,
@@ -431,6 +1002,7 @@
         powerManager,
         R.layout.chipbar,
         wakeLockBuilder,
+        systemClock,
     ) {
         var mostRecentViewInfo: ViewInfo? = null
 
@@ -447,12 +1019,13 @@
         override fun start() {}
     }
 
-    inner class ViewInfo(
+    data class ViewInfo(
         val name: String,
         override val windowTitle: String = "Window Title",
         override val wakeReason: String = "WAKE_REASON",
-        override val timeoutMs: Int = 1,
+        override val timeoutMs: Int = TIMEOUT_MS.toInt(),
         override val id: String = "id",
+        override val priority: ViewPriority = ViewPriority.NORMAL,
     ) : TemporaryViewInfo()
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index 116b8fe..2e66b20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -32,7 +32,7 @@
 @SmallTest
 class TemporaryViewLoggerTest : SysuiTestCase() {
     private lateinit var buffer: LogBuffer
-    private lateinit var logger: TemporaryViewLogger
+    private lateinit var logger: TemporaryViewLogger<TemporaryViewInfo>
 
     @Before
     fun setUp() {
@@ -44,13 +44,22 @@
 
     @Test
     fun logViewAddition_bufferHasLog() {
-        logger.logViewAddition("test id", "Test Window Title")
+        val info =
+            object : TemporaryViewInfo() {
+                override val id: String = "test id"
+                override val priority: ViewPriority = ViewPriority.CRITICAL
+                override val windowTitle: String = "Test Window Title"
+                override val wakeReason: String = "wake reason"
+            }
+
+        logger.logViewAddition(info)
 
         val stringWriter = StringWriter()
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
         val actualString = stringWriter.toString()
 
         assertThat(actualString).contains(TAG)
+        assertThat(actualString).contains("test id")
         assertThat(actualString).contains("Test Window Title")
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 7014f93..2e4d8e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -105,6 +106,7 @@
                 viewUtil,
                 vibratorHelper,
                 fakeWakeLockBuilder,
+                fakeClock,
             )
         underTest.start()
     }
@@ -408,6 +410,7 @@
             wakeReason = WAKE_REASON,
             timeoutMs = TIMEOUT,
             id = DEVICE_ID,
+            priority = ViewPriority.NORMAL,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index beedf9f..d5167b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
 import com.android.systemui.util.view.ViewUtil
 import com.android.systemui.util.wakelock.WakeLock
 
@@ -43,6 +44,7 @@
     viewUtil: ViewUtil,
     vibratorHelper: VibratorHelper,
     wakeLockBuilder: WakeLock.Builder,
+    systemClock: SystemClock,
 ) :
     ChipbarCoordinator(
         context,
@@ -57,6 +59,7 @@
         viewUtil,
         vibratorHelper,
         wakeLockBuilder,
+        systemClock,
     ) {
     override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
         // Just bypass the animation in tests
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
index 14b9bfb..a707222 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
@@ -26,8 +26,8 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.testing.AndroidTestingRunner;
+import android.view.AttachedSurfaceControl;
 import android.view.View;
-import android.view.ViewRootImpl;
 
 import androidx.test.filters.SmallTest;
 
@@ -47,41 +47,78 @@
 @RunWith(AndroidTestingRunner.class)
 public class TouchInsetManagerTest extends SysuiTestCase {
     @Mock
-    private View mRootView;
-
-    @Mock
-    private ViewRootImpl mRootViewImpl;
+    private AttachedSurfaceControl mAttachedSurfaceControl;
 
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        when(mRootView.getViewRootImpl()).thenReturn(mRootViewImpl);
     }
 
     @Test
-    public void testRootViewOnAttachedHandling() {
+    public void testViewOnAttachedHandling() {
         // Create inset manager
-        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
-                mRootView);
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
 
         final ArgumentCaptor<View.OnAttachStateChangeListener> listener =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+        final View view = createView(new Rect(0, 0, 0, 0));
+        when(view.isAttachedToWindow()).thenReturn(false);
 
+
+        // Create session
+        final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
+        session.addViewToTracking(view);
+
+        mFakeExecutor.runAllReady();
         // Ensure manager has registered to listen to attached state of root view.
-        verify(mRootView).addOnAttachStateChangeListener(listener.capture());
+        verify(view).addOnAttachStateChangeListener(listener.capture());
+
+        clearInvocations(mAttachedSurfaceControl);
+        when(view.isAttachedToWindow()).thenReturn(true);
 
         // Trigger attachment and verify touchable region is set.
-        listener.getValue().onViewAttachedToWindow(mRootView);
-        verify(mRootViewImpl).setTouchableRegion(any());
+        listener.getValue().onViewAttachedToWindow(view);
+
+        mFakeExecutor.runAllReady();
+
+        verify(mAttachedSurfaceControl).setTouchableRegion(any());
+    }
+
+    @Test
+    public void testViewOnDetachedHandling() {
+        // Create inset manager
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
+
+        final ArgumentCaptor<View.OnAttachStateChangeListener> listener =
+                ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+        final View view = createView(new Rect(0, 0, 0, 0));
+        when(view.isAttachedToWindow()).thenReturn(true);
+
+        // Create session
+        final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
+        session.addViewToTracking(view);
+
+        mFakeExecutor.runAllReady();
+        // Ensure manager has registered to listen to attached state of root view.
+        verify(view).addOnAttachStateChangeListener(listener.capture());
+
+        clearInvocations(mAttachedSurfaceControl);
+        when(view.isAttachedToWindow()).thenReturn(false);
+
+        // Trigger detachment and verify touchable region is set.
+        listener.getValue().onViewDetachedFromWindow(view);
+
+        mFakeExecutor.runAllReady();
+
+        verify(mAttachedSurfaceControl).setTouchableRegion(any());
     }
 
     @Test
     public void testInsetRegionPropagation() {
         // Create inset manager
-        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
-                mRootView);
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
 
         // Create session
         final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
@@ -95,14 +132,13 @@
         // Check to see if view was properly accounted for.
         final Region expectedRegion = Region.obtain();
         expectedRegion.op(rect, Region.Op.UNION);
-        verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+        verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
     }
 
     @Test
     public void testMultipleRegions() {
         // Create inset manager
-        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
-                mRootView);
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
 
         // Create session
         final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
@@ -112,7 +148,7 @@
         session.addViewToTracking(createView(firstBounds));
 
         mFakeExecutor.runAllReady();
-        clearInvocations(mRootViewImpl);
+        clearInvocations(mAttachedSurfaceControl);
 
         // Create second session
         final TouchInsetManager.TouchInsetSession secondSession = insetManager.createSession();
@@ -128,27 +164,26 @@
             final Region expectedRegion = Region.obtain();
             expectedRegion.op(firstBounds, Region.Op.UNION);
             expectedRegion.op(secondBounds, Region.Op.UNION);
-            verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+            verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
         }
 
 
-        clearInvocations(mRootViewImpl);
+        clearInvocations(mAttachedSurfaceControl);
 
         // clear first session, ensure second session is still reflected.
         session.clear();
         mFakeExecutor.runAllReady();
         {
             final Region expectedRegion = Region.obtain();
-            expectedRegion.op(firstBounds, Region.Op.UNION);
-            verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+            expectedRegion.op(secondBounds, Region.Op.UNION);
+            verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
         }
     }
 
     @Test
     public void testMultipleViews() {
         // Create inset manager
-        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
-                mRootView);
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
 
         // Create session
         final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
@@ -159,7 +194,7 @@
 
         // only capture second invocation.
         mFakeExecutor.runAllReady();
-        clearInvocations(mRootViewImpl);
+        clearInvocations(mAttachedSurfaceControl);
 
         // Add a second view to the session
         final Rect secondViewBounds = new Rect(4, 4, 9, 10);
@@ -173,20 +208,20 @@
             final Region expectedRegion = Region.obtain();
             expectedRegion.op(firstViewBounds, Region.Op.UNION);
             expectedRegion.op(secondViewBounds, Region.Op.UNION);
-            verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+            verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
         }
 
         // Remove second view.
         session.removeViewFromTracking(secondView);
 
-        clearInvocations(mRootViewImpl);
+        clearInvocations(mAttachedSurfaceControl);
         mFakeExecutor.runAllReady();
 
         // Ensure first view still reflected in touch region.
         {
             final Region expectedRegion = Region.obtain();
             expectedRegion.op(firstViewBounds, Region.Op.UNION);
-            verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+            verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
         }
     }
 
@@ -197,6 +232,8 @@
             ((Rect) invocation.getArgument(0)).set(rect);
             return null;
         }).when(view).getBoundsOnScreen(any());
+        when(view.isAttachedToWindow()).thenReturn(true);
+        when(view.getRootSurfaceControl()).thenReturn(mAttachedSurfaceControl);
 
         return view;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index a0b4eab..c3c6975 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialogController;
@@ -98,6 +99,8 @@
     ActivityStarter mActivityStarter;
     @Mock
     InteractionJankMonitor mInteractionJankMonitor;
+    @Mock
+    private DumpManager mDumpManager;
 
     @Before
     public void setup() throws Exception {
@@ -119,7 +122,9 @@
                 mActivityStarter,
                 mInteractionJankMonitor,
                 mDeviceConfigProxy,
-                mExecutor);
+                mExecutor,
+                mDumpManager
+            );
         mDialog.init(0, null);
         State state = createShellState();
         mDialog.onStateChangedH(state);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 30dc0d2..0fdcb95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -19,15 +19,13 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.equalTo;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -36,19 +34,13 @@
 
 import android.app.WallpaperManager;
 import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManagerGlobal;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.Display;
-import android.view.DisplayInfo;
 import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.view.WindowManager;
@@ -57,28 +49,21 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.concurrent.CountDownLatch;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class ImageWallpaperTest extends SysuiTestCase {
     private static final int LOW_BMP_WIDTH = 128;
     private static final int LOW_BMP_HEIGHT = 128;
-    private static final int INVALID_BMP_WIDTH = 1;
-    private static final int INVALID_BMP_HEIGHT = 1;
     private static final int DISPLAY_WIDTH = 1920;
     private static final int DISPLAY_HEIGHT = 1080;
 
@@ -99,19 +84,9 @@
 
     @Mock
     private Bitmap mWallpaperBitmap;
-    private int mBitmapWidth = 1;
-    private int mBitmapHeight = 1;
-
-    @Mock
-    private Handler mHandler;
-    @Mock
-    private FeatureFlags mFeatureFlags;
-
     FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
 
-    private CountDownLatch mEventCountdown;
-
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
@@ -131,12 +106,8 @@
         // set up bitmap
         when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
         when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
-        when(mWallpaperBitmap.getWidth()).thenReturn(mBitmapWidth);
-        when(mWallpaperBitmap.getHeight()).thenReturn(mBitmapHeight);
 
         // set up wallpaper manager
-        when(mWallpaperManager.peekBitmapDimensions())
-                .thenReturn(new Rect(0, 0, mBitmapWidth, mBitmapHeight));
         when(mWallpaperManager.getBitmapAsUser(eq(UserHandle.USER_CURRENT), anyBoolean()))
                 .thenReturn(mWallpaperBitmap);
         when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
@@ -144,104 +115,62 @@
         // set up surface
         when(mSurfaceHolder.getSurface()).thenReturn(mSurface);
         doNothing().when(mSurface).hwuiDestroy();
-
-        // TODO remove code below. Outdated, used in only in old GL tests (that are ignored)
-        Resources resources = mock(Resources.class);
-        when(resources.getConfiguration()).thenReturn(mock(Configuration.class));
-        when(mMockContext.getResources()).thenReturn(resources);
-        DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.logicalWidth = DISPLAY_WIDTH;
-        displayInfo.logicalHeight = DISPLAY_HEIGHT;
-        when(mMockContext.getDisplay()).thenReturn(
-                new Display(mock(DisplayManagerGlobal.class), 0, displayInfo, (Resources) null));
-    }
-
-    private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
-        mBitmapWidth = bitmapWidth;
-        mBitmapHeight = bitmapHeight;
-    }
-
-    private ImageWallpaper createImageWallpaper() {
-        return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor) {
-            @Override
-            public Engine onCreateEngine() {
-                return new GLEngine(mHandler) {
-                    @Override
-                    public Context getDisplayContext() {
-                        return mMockContext;
-                    }
-
-                    @Override
-                    public SurfaceHolder getSurfaceHolder() {
-                        return mSurfaceHolder;
-                    }
-
-                    @Override
-                    public void setFixedSizeAllowed(boolean allowed) {
-                        super.setFixedSizeAllowed(allowed);
-                        assertWithMessage("mFixedSizeAllowed should be true").that(
-                                allowed).isTrue();
-                        mEventCountdown.countDown();
-                    }
-                };
-            }
-        };
     }
 
     @Test
-    @Ignore
     public void testBitmapWallpaper_normal() {
         // Will use a image wallpaper with dimensions DISPLAY_WIDTH x DISPLAY_WIDTH.
         // Then we expect the surface size will be also DISPLAY_WIDTH x DISPLAY_WIDTH.
-        verifySurfaceSize(DISPLAY_WIDTH /* bmpWidth */,
-                DISPLAY_WIDTH /* bmpHeight */,
-                DISPLAY_WIDTH /* surfaceWidth */,
-                DISPLAY_WIDTH /* surfaceHeight */);
+        int bitmapSide = DISPLAY_WIDTH;
+        testSurfaceHelper(
+                bitmapSide /* bitmapWidth */,
+                bitmapSide /* bitmapHeight */,
+                bitmapSide /* expectedSurfaceWidth */,
+                bitmapSide /* expectedSurfaceHeight */);
     }
 
     @Test
-    @Ignore
     public void testBitmapWallpaper_low_resolution() {
         // Will use a image wallpaper with dimensions BMP_WIDTH x BMP_HEIGHT.
         // Then we expect the surface size will be also BMP_WIDTH x BMP_HEIGHT.
-        verifySurfaceSize(LOW_BMP_WIDTH /* bmpWidth */,
-                LOW_BMP_HEIGHT /* bmpHeight */,
-                LOW_BMP_WIDTH /* surfaceWidth */,
-                LOW_BMP_HEIGHT /* surfaceHeight */);
+        testSurfaceHelper(LOW_BMP_WIDTH /* bitmapWidth */,
+                LOW_BMP_HEIGHT /* bitmapHeight */,
+                LOW_BMP_WIDTH /* expectedSurfaceWidth */,
+                LOW_BMP_HEIGHT /* expectedSurfaceHeight */);
     }
 
     @Test
-    @Ignore
     public void testBitmapWallpaper_too_small() {
-        // Will use a image wallpaper with dimensions INVALID_BMP_WIDTH x INVALID_BMP_HEIGHT.
-        // Then we expect the surface size will be also MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT.
-        verifySurfaceSize(INVALID_BMP_WIDTH /* bmpWidth */,
-                INVALID_BMP_HEIGHT /* bmpHeight */,
-                ImageWallpaper.GLEngine.MIN_SURFACE_WIDTH /* surfaceWidth */,
-                ImageWallpaper.GLEngine.MIN_SURFACE_HEIGHT /* surfaceHeight */);
+
+        // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
+        testMinSurfaceHelper(8, 8);
+        testMinSurfaceHelper(100, 2000);
+        testMinSurfaceHelper(200, 1);
     }
 
-    private void verifySurfaceSize(int bmpWidth, int bmpHeight,
-            int surfaceWidth, int surfaceHeight) {
-        ImageWallpaper.GLEngine wallpaperEngine =
-                (ImageWallpaper.GLEngine) createImageWallpaper().onCreateEngine();
+    @Test
+    public void testLoadDrawAndUnloadBitmap() {
+        setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT);
 
-        ImageWallpaper.GLEngine engineSpy = spy(wallpaperEngine);
+        ImageWallpaper.CanvasEngine spyEngine = getSpyEngine();
+        spyEngine.onCreate(mSurfaceHolder);
+        spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+        assertThat(mFakeBackgroundExecutor.numPending()).isAtLeast(1);
 
-        setBitmapDimensions(bmpWidth, bmpHeight);
+        int n = 0;
+        while (mFakeBackgroundExecutor.numPending() >= 1) {
+            n++;
+            assertThat(n).isAtMost(10);
+            mFakeBackgroundExecutor.runNextReady();
+            mFakeSystemClock.advanceTime(1000);
+        }
 
-        ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mMockContext);
-        doReturn(renderer).when(engineSpy).getRendererInstance();
-        engineSpy.onCreate(engineSpy.getSurfaceHolder());
-
-        verify(mSurfaceHolder, times(1)).setFixedSize(surfaceWidth, surfaceHeight);
-        assertWithMessage("setFixedSizeAllowed should have been called.").that(
-                mEventCountdown.getCount()).isEqualTo(0);
+        verify(spyEngine, times(1)).drawFrameOnCanvas(mWallpaperBitmap);
+        assertThat(spyEngine.isBitmapLoaded()).isFalse();
     }
 
-
-    private ImageWallpaper createImageWallpaperCanvas() {
-        return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor) {
+    private ImageWallpaper createImageWallpaper() {
+        return new ImageWallpaper(mFakeBackgroundExecutor) {
             @Override
             public Engine onCreateEngine() {
                 return new CanvasEngine() {
@@ -267,7 +196,7 @@
     }
 
     private ImageWallpaper.CanvasEngine getSpyEngine() {
-        ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+        ImageWallpaper imageWallpaper = createImageWallpaper();
         ImageWallpaper.CanvasEngine engine =
                 (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
         ImageWallpaper.CanvasEngine spyEngine = spy(engine);
@@ -280,48 +209,32 @@
         return spyEngine;
     }
 
-    @Test
-    public void testMinSurface() {
-
-        // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
-        testMinSurfaceHelper(8, 8);
-        testMinSurfaceHelper(100, 2000);
-        testMinSurfaceHelper(200, 1);
+    private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
+        when(mWallpaperManager.peekBitmapDimensions())
+                .thenReturn(new Rect(0, 0, bitmapWidth, bitmapHeight));
+        when(mWallpaperBitmap.getWidth()).thenReturn(bitmapWidth);
+        when(mWallpaperBitmap.getHeight()).thenReturn(bitmapHeight);
     }
 
     private void testMinSurfaceHelper(int bitmapWidth, int bitmapHeight) {
+        testSurfaceHelper(bitmapWidth, bitmapHeight,
+                Math.max(ImageWallpaper.CanvasEngine.MIN_SURFACE_WIDTH, bitmapWidth),
+                Math.max(ImageWallpaper.CanvasEngine.MIN_SURFACE_HEIGHT, bitmapHeight));
+    }
+
+    private void testSurfaceHelper(int bitmapWidth, int bitmapHeight,
+            int expectedSurfaceWidth, int expectedSurfaceHeight) {
 
         clearInvocations(mSurfaceHolder);
         setBitmapDimensions(bitmapWidth, bitmapHeight);
 
-        ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+        ImageWallpaper imageWallpaper = createImageWallpaper();
         ImageWallpaper.CanvasEngine engine =
                 (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
         engine.onCreate(mSurfaceHolder);
 
         verify(mSurfaceHolder, times(1)).setFixedSize(
-                intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_WIDTH)),
-                intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_HEIGHT)));
-    }
-
-    @Test
-    public void testLoadDrawAndUnloadBitmap() {
-        setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT);
-
-        ImageWallpaper.CanvasEngine spyEngine = getSpyEngine();
-        spyEngine.onCreate(mSurfaceHolder);
-        spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
-        assertThat(mFakeBackgroundExecutor.numPending()).isAtLeast(1);
-
-        int n = 0;
-        while (mFakeBackgroundExecutor.numPending() >= 1) {
-            n++;
-            assertThat(n).isAtMost(10);
-            mFakeBackgroundExecutor.runNextReady();
-            mFakeSystemClock.advanceTime(1000);
-        }
-
-        verify(spyEngine, times(1)).drawFrameOnCanvas(mWallpaperBitmap);
-        assertThat(spyEngine.isBitmapLoaded()).isFalse();
+                intThat(equalTo(expectedSurfaceWidth)),
+                intThat(equalTo(expectedSurfaceHeight)));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractorTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
index 7e8ffeb..fc5f782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.wallpapers.canvas;
+package com.android.systemui.wallpapers;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/gl/EglHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/gl/EglHelperTest.java
deleted file mode 100644
index a42bade..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/gl/EglHelperTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.systemui.wallpapers.gl;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atMost;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.graphics.PixelFormat;
-import android.testing.AndroidTestingRunner;
-import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceHolder;
-import android.view.SurfaceSession;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@Ignore
-public class EglHelperTest extends SysuiTestCase {
-
-    @Spy
-    private EglHelper mEglHelper;
-
-    @Mock
-    private SurfaceHolder mSurfaceHolder;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        prepareSurface();
-    }
-
-    @After
-    public void tearDown() {
-        mSurfaceHolder.getSurface().destroy();
-        mSurfaceHolder = null;
-    }
-
-    private void prepareSurface() {
-        final SurfaceSession session = new SurfaceSession();
-        final SurfaceControl control = new SurfaceControl.Builder(session)
-                .setName("Test")
-                .setBufferSize(100, 100)
-                .setFormat(PixelFormat.RGB_888)
-                .build();
-        final Surface surface = new Surface();
-        surface.copyFrom(control);
-        when(mSurfaceHolder.getSurface()).thenReturn(surface);
-        assertThat(mSurfaceHolder.getSurface()).isNotNull();
-        assertThat(mSurfaceHolder.getSurface().isValid()).isTrue();
-    }
-
-    @Test
-    public void testInit_normal() {
-        mEglHelper.init(mSurfaceHolder, false /* wideColorGamut */);
-        assertThat(mEglHelper.hasEglDisplay()).isTrue();
-        assertThat(mEglHelper.hasEglContext()).isTrue();
-        assertThat(mEglHelper.hasEglSurface()).isTrue();
-        verify(mEglHelper).askCreatingEglWindowSurface(
-                any(SurfaceHolder.class), eq(null), anyInt());
-    }
-
-    @Test
-    public void testInit_wide_gamut() {
-        // In EglHelper, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490;
-        doReturn(0x3490).when(mEglHelper).getWcgCapability();
-        // In EglHelper, KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace";
-        doReturn(true).when(mEglHelper).checkExtensionCapability("EGL_KHR_gl_colorspace");
-        ArgumentCaptor<int[]> ac = ArgumentCaptor.forClass(int[].class);
-        // {EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT, EGL_NONE}
-        final int[] expectedArgument = new int[] {0x309D, 0x3490, 0x3038};
-
-        mEglHelper.init(mSurfaceHolder, true /* wideColorGamut */);
-        verify(mEglHelper)
-                .askCreatingEglWindowSurface(any(SurfaceHolder.class), ac.capture(), anyInt());
-        assertThat(ac.getValue()).isNotNull();
-        assertThat(ac.getValue()).isEqualTo(expectedArgument);
-    }
-
-    @Test
-    @Ignore
-    public void testFinish_shouldNotCrash() {
-        mEglHelper.terminateEglDisplay();
-        assertThat(mEglHelper.hasEglDisplay()).isFalse();
-        assertThat(mEglHelper.hasEglSurface()).isFalse();
-        assertThat(mEglHelper.hasEglContext()).isFalse();
-
-        mEglHelper.finish();
-        verify(mEglHelper, never()).destroyEglContext();
-        verify(mEglHelper, never()).destroyEglSurface();
-        verify(mEglHelper, atMost(1)).terminateEglDisplay();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/gl/ImageWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/gl/ImageWallpaperRendererTest.java
deleted file mode 100644
index 89b2222..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/gl/ImageWallpaperRendererTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2020 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.systemui.wallpapers.gl;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-
-import android.app.WallpaperManager;
-import android.app.WallpaperManager.ColorManagementProxy;
-import android.graphics.Bitmap;
-import android.graphics.ColorSpace;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@Ignore
-public class ImageWallpaperRendererTest extends SysuiTestCase {
-
-    private WallpaperManager mWpmSpy;
-
-    @Before
-    public void setUp() throws Exception {
-        final WallpaperManager wpm = mContext.getSystemService(WallpaperManager.class);
-        mWpmSpy = spy(wpm);
-        mContext.addMockSystemService(WallpaperManager.class, mWpmSpy);
-    }
-
-    @Test
-    public void testWcgContent() throws IOException {
-        final Bitmap srgbBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        final Bitmap p3Bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888,
-                false /* hasAlpha */, ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
-
-        final ColorManagementProxy proxy = new ColorManagementProxy(mContext);
-        final ColorManagementProxy cmProxySpy = spy(proxy);
-        final Set<ColorSpace> supportedWideGamuts = new HashSet<>();
-        supportedWideGamuts.add(ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
-
-        try {
-            doReturn(true).when(mWpmSpy).shouldEnableWideColorGamut();
-            doReturn(cmProxySpy).when(mWpmSpy).getColorManagementProxy();
-            doReturn(supportedWideGamuts).when(cmProxySpy).getSupportedColorSpaces();
-
-            mWpmSpy.setBitmap(p3Bitmap);
-            ImageWallpaperRenderer rendererP3 = new ImageWallpaperRenderer(mContext);
-            rendererP3.reportSurfaceSize();
-            assertThat(rendererP3.isWcgContent()).isTrue();
-
-            mWpmSpy.setBitmap(srgbBitmap);
-            ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mContext);
-            assertThat(renderer.isWcgContent()).isFalse();
-        } finally {
-            srgbBitmap.recycle();
-            p3Bitmap.recycle();
-        }
-    }
-
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
index c6aa395..021e7df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
@@ -67,6 +67,8 @@
 
     override fun getDeviceOwnerType(admin: ComponentName?): Int = 0
 
+    override fun isFinancedDevice(): Boolean = false
+
     override fun isNetworkLoggingEnabled(): Boolean = fakeState.isNetworkLoggingEnabled
 
     override fun isVpnEnabled(): Boolean = fakeState.isVpnEnabled
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
index d5348dc..76199e3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
@@ -84,6 +84,11 @@
     }
 
     @Override
+    public boolean isFinancedDevice() {
+        return false;
+    }
+
+    @Override
     public boolean isNetworkLoggingEnabled() {
         return false;
     }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 2ad2119..dd9f1d8 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -48,6 +48,7 @@
 import android.hardware.camera2.extension.IRequestProcessorImpl;
 import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
 import android.hardware.camera2.extension.ISessionProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
 import android.hardware.camera2.extension.LatencyRange;
 import android.hardware.camera2.extension.OutputConfigId;
 import android.hardware.camera2.extension.OutputSurface;
@@ -1266,6 +1267,21 @@
         public int startCapture(ICaptureCallback callback) {
             return mSessionProcessor.startCapture(new CaptureCallbackStub(callback, mCameraId));
         }
+
+        @Override
+        public LatencyPair getRealtimeCaptureLatency() {
+            if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                Pair<Long, Long> latency = mSessionProcessor.getRealtimeCaptureLatency();
+                if (latency != null) {
+                    LatencyPair ret = new LatencyPair();
+                    ret.first = latency.first;
+                    ret.second = latency.second;
+                    return ret;
+                }
+            }
+
+            return null;
+        }
     }
 
     private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
@@ -1578,6 +1594,21 @@
         }
 
         @Override
+        public LatencyPair getRealtimeCaptureLatency() {
+            if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                Pair<Long, Long> latency = mImageExtender.getRealtimeCaptureLatency();
+                if (latency != null) {
+                    LatencyPair ret = new LatencyPair();
+                    ret.first = latency.first;
+                    ret.second = latency.second;
+                    return ret;
+                }
+            }
+
+            return null;
+        }
+
+        @Override
         public CameraMetadataNative getAvailableCaptureRequestKeys() {
             if (RESULT_API_SUPPORTED) {
                 List<CaptureRequest.Key> supportedCaptureKeys =
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3145139..59c1c54 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -26,8 +26,11 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
+import static android.provider.Settings.Secure.CONTRAST_LEVEL;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
 import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
@@ -1018,10 +1021,13 @@
     }
 
     private void dispatchAccessibilityEventLocked(AccessibilityEvent event) {
-        notifyAccessibilityServicesDelayedLocked(event, false);
-        notifyAccessibilityServicesDelayedLocked(event, true);
+        if (mProxyManager.isProxyed(event.getDisplayId())) {
+            mProxyManager.sendAccessibilityEventLocked(event);
+        } else {
+            notifyAccessibilityServicesDelayedLocked(event, false);
+            notifyAccessibilityServicesDelayedLocked(event, true);
+        }
         mUiAutomationManager.sendAccessibilityEventLocked(event);
-        mProxyManager.sendAccessibilityEvent(event);
     }
 
     private void sendAccessibilityEventToInputFilter(AccessibilityEvent event) {
@@ -1162,7 +1168,7 @@
             }
             List<AccessibilityServiceConnection> services =
                     getUserStateLocked(resolvedUserId).mBoundServices;
-            int numServices = services.size() + mProxyManager.getNumProxys();
+            int numServices = services.size() + mProxyManager.getNumProxysLocked();
             interfacesToInterrupt = new ArrayList<>(numServices);
             for (int i = 0; i < services.size(); i++) {
                 AccessibilityServiceConnection service = services.get(i);
@@ -1172,7 +1178,7 @@
                     interfacesToInterrupt.add(a11yServiceInterface);
                 }
             }
-            mProxyManager.addServiceInterfaces(interfacesToInterrupt);
+            mProxyManager.addServiceInterfacesLocked(interfacesToInterrupt);
         }
         for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
             try {
@@ -1899,6 +1905,16 @@
         return false;
     }
 
+    private boolean readUiContrastLocked(AccessibilityUserState userState) {
+        float contrast = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+                CONTRAST_LEVEL, CONTRAST_DEFAULT_VALUE, userState.mUserId);
+        if (Math.abs(userState.getUiContrastLocked() - contrast) >= 1e-10) {
+            userState.setUiContrastLocked(contrast);
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Performs {@link AccessibilityService}s delayed notification. The delay is configurable
      * and denotes the period after the last event before notifying the service.
@@ -1963,7 +1979,7 @@
                 mUiAutomationManager.getServiceInfo(), client)
                 ? mUiAutomationManager.getRelevantEventTypes()
                 : 0;
-        relevantEventTypes |= mProxyManager.getRelevantEventTypes();
+        relevantEventTypes |= mProxyManager.getRelevantEventTypesLocked();
         return relevantEventTypes;
     }
 
@@ -2565,6 +2581,7 @@
         somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState);
         somethingChanged |= readMagnificationCapabilitiesLocked(userState);
         somethingChanged |= readMagnificationFollowTypingLocked(userState);
+        somethingChanged |= readUiContrastLocked(userState);
         return somethingChanged;
     }
 
@@ -3706,6 +3723,19 @@
         return mProxyManager.unregisterProxy(displayId);
     }
 
+    @Override public float getUiContrast() {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+            mTraceManager.logTrace(LOG_TAG + ".getUiContrast", FLAGS_ACCESSIBILITY_MANAGER);
+        }
+        synchronized (mLock) {
+            AccessibilityUserState userState = getCurrentUserStateLocked();
+            float contrast = userState.getUiContrastLocked();
+            if (contrast != CONTRAST_NOT_SET) return contrast;
+            readUiContrastLocked(userState);
+            return userState.getUiContrastLocked();
+        }
+    }
+
     @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -4153,6 +4183,9 @@
         private final Uri mMagnificationFollowTypingUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED);
 
+        private final Uri mUiContrastUri = Settings.Secure.getUriFor(
+                CONTRAST_LEVEL);
+
         public AccessibilityContentObserver(Handler handler) {
             super(handler);
         }
@@ -4193,6 +4226,8 @@
                     mMagnificationCapabilityUri, false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(
                     mMagnificationFollowTypingUri, false, this, UserHandle.USER_ALL);
+            contentResolver.registerContentObserver(
+                    mUiContrastUri, false, this, UserHandle.USER_ALL);
         }
 
         @Override
@@ -4262,6 +4297,10 @@
                     }
                 } else if (mMagnificationFollowTypingUri.equals(uri)) {
                     readMagnificationFollowTypingLocked(userState);
+                } else if (mUiContrastUri.equals(uri)) {
+                    if (readUiContrastLocked(userState)) {
+                        updateUiContrastLocked(userState);
+                    }
                 }
             }
         }
@@ -4551,7 +4590,22 @@
                         userState.getFocusColorLocked());
             }));
         });
+    }
 
+    private void updateUiContrastLocked(AccessibilityUserState userState) {
+        if (userState.mUserId != mCurrentUserId) {
+            return;
+        }
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+            mTraceManager.logTrace(LOG_TAG + ".updateUiContrastLocked",
+                    FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState);
+        }
+        float contrast = userState.getUiContrastLocked();
+        mMainHandler.post(() -> {
+            broadcastToClients(userState, ignoreRemoteException(client -> {
+                client.mCallback.setUiContrast(contrast);
+            }));
+        });
     }
 
     public AccessibilityTraceManager getTraceManager() {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0db169f..43730fc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -26,6 +26,8 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
 import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -143,6 +145,8 @@
     private final int mFocusStrokeWidthDefaultValue;
     // The default value of the focus color.
     private final int mFocusColorDefaultValue;
+    /** The color contrast in [-1, 1] */
+    private float mUiContrast = CONTRAST_DEFAULT_VALUE;
 
     private Context mContext;
 
@@ -217,6 +221,7 @@
         mFocusStrokeWidth = mFocusStrokeWidthDefaultValue;
         mFocusColor = mFocusColorDefaultValue;
         mMagnificationFollowTypingEnabled = true;
+        mUiContrast = CONTRAST_NOT_SET;
     }
 
     void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
@@ -983,6 +988,7 @@
         return mFocusColor;
     }
 
+
     /**
      * Sets the stroke width and color of the focus rectangle.
      *
@@ -1008,4 +1014,20 @@
         }
         return false;
     }
+
+    /**
+     * Get the color contrast
+     * @return color contrast in [-1, 1]
+     */
+    public float getUiContrastLocked() {
+        return mUiContrast;
+    }
+
+    /**
+     * Set the color contrast
+     * @param contrast the new color contrast in [-1, 1]
+     */
+    public void setUiContrastLocked(float contrast) {
+        mUiContrast = contrast;
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 2184878..f28191f 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -37,6 +37,7 @@
  * proxy connection will belong to a separate user state.
  *
  * TODO(241117292): Remove or cut down during simultaneous user refactoring.
+ * TODO(262244375): Add unit tests.
  */
 public class ProxyManager {
     // Names used to populate ComponentName and ResolveInfo in connection.mA11yServiceInfo and in
@@ -84,7 +85,9 @@
                         windowManagerInternal,
                         awm, displayId);
 
-        mProxyA11yServiceConnections.put(displayId, connection);
+        synchronized (mLock) {
+            mProxyA11yServiceConnections.put(displayId, connection);
+        }
 
         // If the client dies, make sure to remove the connection.
         IBinder.DeathRecipient deathRecipient =
@@ -98,7 +101,9 @@
         client.asBinder().linkToDeath(deathRecipient, 0);
         // Notify apps that the service state has changed.
         // A11yManager#A11yServicesStateChangeListener
-        connection.mSystemSupport.onClientChangeLocked(true);
+        synchronized (mLock) {
+            connection.mSystemSupport.onClientChangeLocked(true);
+        }
 
         connection.initializeServiceInterface(client);
     }
@@ -111,9 +116,11 @@
     }
 
     private boolean clearConnection(int displayId) {
-        if (mProxyA11yServiceConnections.contains(displayId)) {
-            mProxyA11yServiceConnections.remove(displayId);
-            return true;
+        synchronized (mLock) {
+            if (mProxyA11yServiceConnections.contains(displayId)) {
+                mProxyA11yServiceConnections.remove(displayId);
+                return true;
+            }
         }
         return false;
     }
@@ -122,7 +129,9 @@
      * Checks if a display id is being proxy-ed.
      */
     public boolean isProxyed(int displayId) {
-        return mProxyA11yServiceConnections.contains(displayId);
+        synchronized (mLock) {
+            return mProxyA11yServiceConnections.contains(displayId);
+        }
     }
 
     /**
@@ -130,10 +139,10 @@
      * {@link android.view.accessibility.AccessibilityDisplayProxy} will filter based on display.
      * TODO(b/250929565): Filtering should happen in the system, not in the proxy.
      */
-    public void sendAccessibilityEvent(AccessibilityEvent event) {
-        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
-            ProxyAccessibilityServiceConnection proxy =
-                    mProxyA11yServiceConnections.valueAt(i);
+    public void sendAccessibilityEventLocked(AccessibilityEvent event) {
+        final ProxyAccessibilityServiceConnection proxy =
+                mProxyA11yServiceConnections.get(event.getDisplayId());
+        if (proxy != null) {
             proxy.notifyAccessibilityEvent(event);
         }
     }
@@ -187,7 +196,7 @@
      * Returns the relevant event types of every proxy.
      * TODO(254545943): When A11yManager is separated, return based on the A11yManager display.
      */
-    public int getRelevantEventTypes() {
+    public int getRelevantEventTypesLocked() {
         int relevantEventTypes = 0;
         for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
             ProxyAccessibilityServiceConnection proxy =
@@ -201,7 +210,7 @@
      * Gets the number of current proxy connections.
      * @return
      */
-    public int getNumProxys() {
+    public int getNumProxysLocked() {
         return mProxyA11yServiceConnections.size();
     }
 
@@ -209,7 +218,7 @@
      * Adds the service interfaces to a list.
      * @param interfaces
      */
-    public void addServiceInterfaces(List<IAccessibilityServiceClient> interfaces) {
+    public void addServiceInterfacesLocked(List<IAccessibilityServiceClient> interfaces) {
         for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
             final ProxyAccessibilityServiceConnection proxy =
                     mProxyA11yServiceConnections.valueAt(i);
diff --git a/services/companion/java/com/android/server/companion/MetricUtils.java b/services/companion/java/com/android/server/companion/MetricUtils.java
index 09238d8..cf867b6 100644
--- a/services/companion/java/com/android/server/companion/MetricUtils.java
+++ b/services/companion/java/com/android/server/companion/MetricUtils.java
@@ -19,6 +19,8 @@
 import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_GLASSES;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
 
 import static com.android.internal.util.FrameworkStatsLog.CDM_ASSOCIATION_ACTION;
@@ -27,6 +29,8 @@
 import static com.android.internal.util.FrameworkStatsLog.CDM_ASSOCIATION_ACTION__DEVICE_PROFILE__DEVICE_PROFILE_APP_STREAMING;
 import static com.android.internal.util.FrameworkStatsLog.CDM_ASSOCIATION_ACTION__DEVICE_PROFILE__DEVICE_PROFILE_AUTO_PROJECTION;
 import static com.android.internal.util.FrameworkStatsLog.CDM_ASSOCIATION_ACTION__DEVICE_PROFILE__DEVICE_PROFILE_COMPUTER;
+import static com.android.internal.util.FrameworkStatsLog.CDM_ASSOCIATION_ACTION__DEVICE_PROFILE__DEVICE_PROFILE_GLASSES;
+import static com.android.internal.util.FrameworkStatsLog.CDM_ASSOCIATION_ACTION__DEVICE_PROFILE__DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
 import static com.android.internal.util.FrameworkStatsLog.CDM_ASSOCIATION_ACTION__DEVICE_PROFILE__DEVICE_PROFILE_NULL;
 import static com.android.internal.util.FrameworkStatsLog.CDM_ASSOCIATION_ACTION__DEVICE_PROFILE__DEVICE_PROFILE_WATCH;
 import static com.android.internal.util.FrameworkStatsLog.write;
@@ -59,6 +63,14 @@
                 DEVICE_PROFILE_COMPUTER,
                 CDM_ASSOCIATION_ACTION__DEVICE_PROFILE__DEVICE_PROFILE_COMPUTER
         );
+        map.put(
+                DEVICE_PROFILE_GLASSES,
+                CDM_ASSOCIATION_ACTION__DEVICE_PROFILE__DEVICE_PROFILE_GLASSES
+        );
+        map.put(
+                DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+                CDM_ASSOCIATION_ACTION__DEVICE_PROFILE__DEVICE_PROFILE_NEARBY_DEVICE_STREAMING
+        );
 
         METRIC_DEVICE_PROFILE = unmodifiableMap(map);
     }
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index a41ac03..0ff3fb7 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -23,6 +23,8 @@
 import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_GLASSES;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Binder.getCallingPid;
@@ -65,6 +67,9 @@
         map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION,
                 Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION);
         map.put(DEVICE_PROFILE_COMPUTER, Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER);
+        map.put(DEVICE_PROFILE_GLASSES, Manifest.permission.REQUEST_COMPANION_PROFILE_GLASSES);
+        map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+                Manifest.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING);
 
         DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
     }
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 0cea3d0..97b5d6d 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -61,15 +61,19 @@
 
     private static final AtomicLong sNextPhysId = new AtomicLong(1);
 
+    static final String NAVIGATION_TOUCHPAD_DEVICE_TYPE = "touchNavigation";
+
     static final String PHYS_TYPE_DPAD = "Dpad";
     static final String PHYS_TYPE_KEYBOARD = "Keyboard";
     static final String PHYS_TYPE_MOUSE = "Mouse";
     static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen";
+    static final String PHYS_TYPE_NAVIGATION_TOUCHPAD = "NavigationTouchpad";
     @StringDef(prefix = { "PHYS_TYPE_" }, value = {
             PHYS_TYPE_DPAD,
             PHYS_TYPE_KEYBOARD,
             PHYS_TYPE_MOUSE,
             PHYS_TYPE_TOUCHSCREEN,
+            PHYS_TYPE_NAVIGATION_TOUCHPAD,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface PhysType {
@@ -190,6 +194,28 @@
         }
     }
 
+    void createNavigationTouchpad(
+            @NonNull String deviceName,
+            int vendorId,
+            int productId,
+            @NonNull IBinder deviceToken,
+            int displayId,
+            int touchpadHeight,
+            int touchpadWidth) {
+        final String phys = createPhys(PHYS_TYPE_NAVIGATION_TOUCHPAD);
+        mInputManagerInternal.setTypeAssociation(phys, NAVIGATION_TOUCHPAD_DEVICE_TYPE);
+        try {
+            createDeviceInternal(InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD, deviceName,
+                    vendorId, productId, deviceToken, displayId, phys,
+                    () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+                            phys, touchpadHeight, touchpadWidth));
+        } catch (DeviceCreationException e) {
+            mInputManagerInternal.unsetTypeAssociation(phys);
+            throw new RuntimeException(
+                    "Failed to create virtual navigation touchpad device '" + deviceName + "'.", e);
+        }
+    }
+
     void unregisterInputDevice(@NonNull IBinder token) {
         synchronized (mLock) {
             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove(
@@ -207,7 +233,13 @@
             InputDeviceDescriptor inputDeviceDescriptor) {
         token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
         mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor());
+
         InputManager.getInstance().removeUniqueIdAssociation(inputDeviceDescriptor.getPhys());
+        // Type associations are added in the case of navigation touchpads. Those should be removed
+        // once the input device gets closed.
+        if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD) {
+            mInputManagerInternal.unsetTypeAssociation(inputDeviceDescriptor.getPhys());
+        }
 
         // Reset values to the default if all virtual mice are unregistered, or set display
         // id if there's another mouse (choose the most recent). The inputDeviceDescriptor must be
@@ -509,11 +541,13 @@
         static final int TYPE_MOUSE = 2;
         static final int TYPE_TOUCHSCREEN = 3;
         static final int TYPE_DPAD = 4;
+        static final int TYPE_NAVIGATION_TOUCHPAD = 5;
         @IntDef(prefix = { "TYPE_" }, value = {
                 TYPE_KEYBOARD,
                 TYPE_MOUSE,
                 TYPE_TOUCHSCREEN,
                 TYPE_DPAD,
+                TYPE_NAVIGATION_TOUCHPAD,
         })
         @Retention(RetentionPolicy.SOURCE)
         @interface Type {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 5819861..12ad9f1 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -53,6 +53,7 @@
 import android.hardware.input.VirtualMouseConfig;
 import android.hardware.input.VirtualMouseRelativeEvent;
 import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualNavigationTouchpadConfig;
 import android.hardware.input.VirtualTouchEvent;
 import android.hardware.input.VirtualTouchscreenConfig;
 import android.os.Binder;
@@ -108,7 +109,7 @@
     private VirtualAudioController mVirtualAudioController;
     @VisibleForTesting
     final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
-    private final OnDeviceCloseListener mListener;
+    private final OnDeviceCloseListener mOnDeviceCloseListener;
     private final IBinder mAppToken;
     private final VirtualDeviceParams mParams;
     private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
@@ -155,7 +156,7 @@
             IBinder token,
             int ownerUid,
             int deviceId,
-            OnDeviceCloseListener listener,
+            OnDeviceCloseListener onDeviceCloseListener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
@@ -168,7 +169,7 @@
                 deviceId,
                 /* inputController= */ null,
                 /* sensorController= */ null,
-                listener,
+                onDeviceCloseListener,
                 pendingTrampolineCallback,
                 activityListener,
                 runningAppsChangedCallback,
@@ -184,7 +185,7 @@
             int deviceId,
             InputController inputController,
             SensorController sensorController,
-            OnDeviceCloseListener listener,
+            OnDeviceCloseListener onDeviceCloseListener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
@@ -212,7 +213,7 @@
         } else {
             mSensorController = sensorController;
         }
-        mListener = listener;
+        mOnDeviceCloseListener = onDeviceCloseListener;
         try {
             token.linkToDeath(this, 0);
         } catch (RemoteException e) {
@@ -330,7 +331,7 @@
                 mVirtualAudioController = null;
             }
         }
-        mListener.onClose(mAssociationInfo.getId());
+        mOnDeviceCloseListener.onClose(mDeviceId);
         mAppToken.unlinkToDeath(this, 0);
 
         final long ident = Binder.clearCallingIdentity();
@@ -491,6 +492,38 @@
     }
 
     @Override // Binder call
+    public void createVirtualNavigationTouchpad(VirtualNavigationTouchpadConfig config,
+            @NonNull IBinder deviceToken) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to create a virtual navigation touchpad");
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+                throw new SecurityException(
+                        "Cannot create a virtual navigation touchpad for a display not associated "
+                                + "with this virtual device");
+            }
+        }
+        int touchpadHeight = config.getHeight();
+        int touchpadWidth = config.getWidth();
+        if (touchpadHeight <= 0 || touchpadWidth <= 0) {
+            throw new IllegalArgumentException(
+                "Cannot create a virtual navigation touchpad, touchpad dimensions must be positive."
+                    + " Got: (" + touchpadHeight + ", " + touchpadWidth + ")");
+        }
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mInputController.createNavigationTouchpad(
+                    config.getInputDeviceName(), config.getVendorId(),
+                    config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
+                    touchpadHeight, touchpadWidth);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
     public void unregisterInputDevice(IBinder token) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
@@ -650,6 +683,7 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
         fout.println("  VirtualDevice: ");
+        fout.println("    mDeviceId: " + mDeviceId);
         fout.println("    mAssociationId: " + mAssociationInfo.getId());
         fout.println("    mParams: " + mParams);
         fout.println("    mVirtualDisplayIds: ");
@@ -839,7 +873,7 @@
     }
 
     interface OnDeviceCloseListener {
-        void onClose(int associationId);
+        void onClose(int deviceId);
     }
 
     interface PendingTrampolineCallback {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index f07d285..da2c516 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -41,6 +41,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Parcel;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArraySet;
@@ -62,6 +63,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -78,7 +80,7 @@
     private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
 
     private static AtomicInteger sNextUniqueIndex = new AtomicInteger(
-            VirtualDeviceManager.DEFAULT_DEVICE_ID + 1);
+            VirtualDeviceManager.DEVICE_ID_DEFAULT + 1);
 
     /**
      * Mapping from user IDs to CameraAccessControllers.
@@ -88,14 +90,20 @@
             new SparseArray<>();
 
     /**
-     * Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
-     * each CDM associated device.
+     * Mapping from device IDs to CameraAccessControllers.
+     */
+    @GuardedBy("mVirtualDeviceManagerLock")
+    private final SparseArray<CameraAccessController> mCameraAccessControllersByDeviceId =
+            new SparseArray<>();
+
+    /**
+     * Mapping from device IDs to virtual devices.
      */
     @GuardedBy("mVirtualDeviceManagerLock")
     private final SparseArray<VirtualDeviceImpl> mVirtualDevices = new SparseArray<>();
 
     /**
-     * Mapping from CDM association IDs to app UIDs running on the corresponding virtual device.
+     * Mapping from device IDs to app UIDs running on the corresponding virtual device.
      */
     @GuardedBy("mVirtualDeviceManagerLock")
     private final SparseArray<ArraySet<Integer>> mAppsOnVirtualDevices = new SparseArray<>();
@@ -158,7 +166,7 @@
     @GuardedBy("mVirtualDeviceManagerLock")
     private boolean isValidVirtualDeviceLocked(IVirtualDevice virtualDevice) {
         try {
-            return mVirtualDevices.contains(virtualDevice.getAssociationId());
+            return mVirtualDevices.contains(virtualDevice.getDeviceId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -228,9 +236,9 @@
     }
 
     @VisibleForTesting
-    void notifyRunningAppsChanged(int associationId, ArraySet<Integer> uids) {
+    void notifyRunningAppsChanged(int deviceId, ArraySet<Integer> uids) {
         synchronized (mVirtualDeviceManagerLock) {
-            mAppsOnVirtualDevices.put(associationId, uids);
+            mAppsOnVirtualDevices.put(deviceId, uids);
         }
         mLocalService.onAppsOnVirtualDeviceChanged();
     }
@@ -238,7 +246,7 @@
     @VisibleForTesting
     void addVirtualDevice(VirtualDeviceImpl virtualDevice) {
         synchronized (mVirtualDeviceManagerLock) {
-            mVirtualDevices.put(virtualDevice.getAssociationId(), virtualDevice);
+            mVirtualDevices.put(virtualDevice.getDeviceId(), virtualDevice);
         }
     }
 
@@ -266,60 +274,26 @@
                 throw new IllegalArgumentException("No association with ID " + associationId);
             }
             synchronized (mVirtualDeviceManagerLock) {
-                if (mVirtualDevices.contains(associationId)) {
-                    throw new IllegalStateException(
-                            "Virtual device for association ID " + associationId
-                                    + " already exists");
-                }
                 final int userId = UserHandle.getUserId(callingUid);
                 final CameraAccessController cameraAccessController =
                         mCameraAccessControllers.get(userId);
-                final int uniqueId = sNextUniqueIndex.getAndIncrement();
-
+                final int deviceId = sNextUniqueIndex.getAndIncrement();
                 VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
-                        associationInfo, token, callingUid, uniqueId,
-                        new VirtualDeviceImpl.OnDeviceCloseListener() {
-                            @Override
-                            public void onClose(int associationId) {
-                                synchronized (mVirtualDeviceManagerLock) {
-                                    VirtualDeviceImpl removedDevice =
-                                            mVirtualDevices.removeReturnOld(associationId);
-                                    if (removedDevice != null) {
-                                        Intent i = new Intent(
-                                                VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
-                                        i.putExtra(
-                                                VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID,
-                                                removedDevice.getDeviceId());
-                                        i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                                        final long identity = Binder.clearCallingIdentity();
-                                        try {
-                                            getContext().sendBroadcastAsUser(i, UserHandle.ALL);
-                                        } finally {
-                                            Binder.restoreCallingIdentity(identity);
-                                        }
-                                    }
-                                    mAppsOnVirtualDevices.remove(associationId);
-                                    if (cameraAccessController != null) {
-                                        cameraAccessController.stopObservingIfNeeded();
-                                    } else {
-                                        Slog.w(TAG, "cameraAccessController not found for user "
-                                                + userId);
-                                    }
-                                }
-                            }
-                        },
+                        associationInfo, token, callingUid, deviceId,
+                        /* onDeviceCloseListener= */ this::onDeviceClosed,
                         this, activityListener,
                         runningUids -> {
                             cameraAccessController.blockCameraAccessIfNeeded(runningUids);
-                            notifyRunningAppsChanged(associationInfo.getId(), runningUids);
+                            notifyRunningAppsChanged(deviceId, runningUids);
                         },
                         params);
                 if (cameraAccessController != null) {
                     cameraAccessController.startObservingIfNeeded();
+                    mCameraAccessControllersByDeviceId.put(deviceId, cameraAccessController);
                 } else {
                     Slog.w(TAG, "cameraAccessController not found for user " + userId);
                 }
-                mVirtualDevices.put(associationInfo.getId(), virtualDevice);
+                mVirtualDevices.put(deviceId, virtualDevice);
                 return virtualDevice;
             }
         }
@@ -336,7 +310,7 @@
             }
             VirtualDeviceImpl virtualDeviceImpl;
             synchronized (mVirtualDeviceManagerLock) {
-                virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getAssociationId());
+                virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getDeviceId());
                 if (virtualDeviceImpl == null) {
                     throw new SecurityException("Invalid VirtualDevice");
                 }
@@ -401,7 +375,7 @@
         @Override // Binder call
         public int getDeviceIdForDisplayId(int displayId) {
             if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
-                return VirtualDeviceManager.DEFAULT_DEVICE_ID;
+                return VirtualDeviceManager.DEVICE_ID_DEFAULT;
             }
             synchronized (mVirtualDeviceManagerLock) {
                 for (int i = 0; i < mVirtualDevices.size(); i++) {
@@ -411,14 +385,13 @@
                     }
                 }
             }
-            return VirtualDeviceManager.DEFAULT_DEVICE_ID;
+            return VirtualDeviceManager.DEVICE_ID_DEFAULT;
         }
 
         @Nullable
         private AssociationInfo getAssociationInfo(String packageName, int associationId) {
             final int callingUserId = getCallingUserHandle().getIdentifier();
-            final List<AssociationInfo> associations =
-                    mAllAssociations.get(callingUserId);
+            final List<AssociationInfo> associations = mAllAssociations.get(callingUserId);
             if (associations != null) {
                 final int associationSize = associations.size();
                 for (int i = 0; i < associationSize; i++) {
@@ -434,6 +407,29 @@
             return null;
         }
 
+        private void onDeviceClosed(int deviceId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                mVirtualDevices.remove(deviceId);
+                Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+                i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
+                i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+                mAppsOnVirtualDevices.remove(deviceId);
+                final CameraAccessController cameraAccessController =
+                        mCameraAccessControllersByDeviceId.removeReturnOld(deviceId);
+                if (cameraAccessController != null) {
+                    cameraAccessController.stopObservingIfNeeded();
+                } else {
+                    Slog.w(TAG, "cameraAccessController not found for device Id " + deviceId);
+                }
+            }
+        }
+
         @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
@@ -495,6 +491,35 @@
         }
 
         @Override
+        public int getDeviceOwnerUid(int deviceId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                int size = mVirtualDevices.size();
+                for (int i = 0; i < size; i++) {
+                    VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
+                    if (device.getDeviceId() == deviceId) {
+                        return device.getOwnerUid();
+                    }
+                }
+            }
+            return Process.INVALID_UID;
+        }
+
+        @Override
+        public @NonNull Set<Integer> getDeviceIdsForUid(int uid) {
+            ArraySet<Integer> result = new ArraySet<>();
+            synchronized (mVirtualDeviceManagerLock) {
+                int size = mVirtualDevices.size();
+                for (int i = 0; i < size; i++) {
+                    VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
+                    if (device.isAppRunningOnVirtualDevice(uid)) {
+                        result.add(device.getDeviceId());
+                    }
+                }
+            }
+            return result;
+        }
+
+        @Override
         public void onVirtualDisplayCreated(int displayId) {
             final VirtualDisplayListener[] listeners;
             synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index cbe6091..17002d5 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
+import android.apex.ApexInfo;
+import android.apex.IApexService;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
@@ -39,6 +41,7 @@
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -61,7 +64,6 @@
 
 import libcore.util.HexEncoding;
 
-import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.security.PublicKey;
@@ -73,7 +75,6 @@
 import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * @hide
@@ -105,7 +106,6 @@
     @VisibleForTesting
     static final String BUNDLE_CONTENT_DIGEST = "content-digest";
 
-    static final String APEX_PRELOAD_LOCATION = "/system/apex/";
     static final String APEX_PRELOAD_LOCATION_ERROR = "could-not-be-determined";
 
     // used for indicating any type of error during MBA measurement
@@ -119,7 +119,7 @@
     // used for indicating newly installed MBAs that are updated (but unused currently)
     static final int MBA_STATUS_UPDATED_NEW_INSTALL = 4;
 
-    private static final boolean DEBUG = true;     // set this to false upon submission
+    private static final boolean DEBUG = false;     // toggle this for local debug
 
     private final Context mContext;
     private String mVbmetaDigest;
@@ -320,9 +320,7 @@
                     FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
                             packageInfo.packageName,
                             packageInfo.getLongVersionCode(),
-                            (cDigest != null) ? HexEncoding.encodeToString(
-                                    packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST),
-                                    false) : null,
+                            (cDigest != null) ? HexEncoding.encodeToString(cDigest, false) : null,
                             packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
                             signerDigestHexStrings, // signer_cert_digest
                             mba_status,             // mba_status
@@ -381,9 +379,7 @@
                     FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
                             packageInfo.packageName,
                             packageInfo.getLongVersionCode(),
-                            (cDigest != null) ? HexEncoding.encodeToString(
-                                    packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST),
-                                    false) : null,
+                            (cDigest != null) ? HexEncoding.encodeToString(cDigest, false) : null,
                             packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
                             signerDigestHexStrings,
                             MBA_STATUS_NEW_INSTALL,   // mba_status
@@ -1168,19 +1164,18 @@
 
     @NonNull
     private String getOriginalApexPreinstalledLocation(String packageName,
-                                                   String currentInstalledLocation) {
-        // get a listing of all apex files in /system/apex/
-        Set<String> originalApexs = Stream.of(new File(APEX_PRELOAD_LOCATION).listFiles())
-                                        .filter(f -> !f.isDirectory())
-                                        .map(File::getName)
-                                        .collect(Collectors.toSet());
-
-        for (String originalApex : originalApexs) {
-            if (originalApex.startsWith(packageName)) {
-                return APEX_PRELOAD_LOCATION + originalApex;
+            String currentInstalledLocation) {
+        try {
+            IApexService apexService = IApexService.Stub.asInterface(
+                    Binder.allowBlocking(ServiceManager.waitForService("apexservice")));
+            for (ApexInfo info : apexService.getAllPackages()) {
+                if (packageName.equals(info.moduleName)) {
+                    return info.preinstalledModulePath;
+                }
             }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to get package list from apexservice", e);
         }
-
         return APEX_PRELOAD_LOCATION_ERROR;
     }
 
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index b2fc574..5eb0db1 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -36,6 +36,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
+import com.android.server.pm.UserManagerInternal;
 
 import libcore.io.IoUtils;
 
@@ -156,14 +157,19 @@
         mBlockDeviceSize = -1; // Load lazily
     }
 
-    private int getAllowedUid(int userHandle) {
+    private int getAllowedUid() {
+        final UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
+        final int mainUserId = umInternal.getMainUserId();
+        if (mainUserId < 0) {
+            return -1;
+        }
         String allowedPackage = mContext.getResources()
                 .getString(R.string.config_persistentDataPackageName);
         int allowedUid = -1;
         if (!TextUtils.isEmpty(allowedPackage)) {
             try {
                 allowedUid = mContext.getPackageManager().getPackageUidAsUser(
-                        allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, userHandle);
+                        allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, mainUserId);
             } catch (PackageManager.NameNotFoundException e) {
                 // not expected
                 Slog.e(TAG, "not able to find package " + allowedPackage, e);
@@ -176,7 +182,7 @@
     public void onStart() {
         // Do init on a separate thread, will join in PHASE_ACTIVITY_MANAGER_READY
         SystemServerInitThreadPool.submit(() -> {
-            mAllowedUid = getAllowedUid(UserHandle.USER_SYSTEM);
+            mAllowedUid = getAllowedUid();
             enforceChecksumValidity();
             formatIfOemUnlockEnabled();
             publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index b7f8d38..75788f2 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -47,6 +47,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.build.UnboundedSdkLevel;
+import com.android.server.pm.permission.PermissionAllowlist;
 
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
@@ -304,24 +305,7 @@
     final ArrayMap<String, List<CarrierAssociatedAppEntry>>
             mDisabledUntilUsedPreinstalledCarrierAssociatedApps = new ArrayMap<>();
 
-    final ArrayMap<String, ArraySet<String>> mPrivAppPermissions = new ArrayMap<>();
-    final ArrayMap<String, ArraySet<String>> mPrivAppDenyPermissions = new ArrayMap<>();
-
-    final ArrayMap<String, ArraySet<String>> mVendorPrivAppPermissions = new ArrayMap<>();
-    final ArrayMap<String, ArraySet<String>> mVendorPrivAppDenyPermissions = new ArrayMap<>();
-
-    final ArrayMap<String, ArraySet<String>> mProductPrivAppPermissions = new ArrayMap<>();
-    final ArrayMap<String, ArraySet<String>> mProductPrivAppDenyPermissions = new ArrayMap<>();
-
-    final ArrayMap<String, ArraySet<String>> mSystemExtPrivAppPermissions = new ArrayMap<>();
-    final ArrayMap<String, ArraySet<String>> mSystemExtPrivAppDenyPermissions = new ArrayMap<>();
-
-    final ArrayMap<String, ArrayMap<String, ArraySet<String>>> mApexPrivAppPermissions =
-            new ArrayMap<>();
-    final ArrayMap<String, ArrayMap<String, ArraySet<String>>> mApexPrivAppDenyPermissions =
-            new ArrayMap<>();
-
-    final ArrayMap<String, ArrayMap<String, Boolean>> mOemPermissions = new ArrayMap<>();
+    private final PermissionAllowlist mPermissionAllowlist = new PermissionAllowlist();
 
     // Allowed associations between applications.  If there are any entries
     // for an app, those are the only associations allowed; otherwise, all associations
@@ -459,64 +443,8 @@
         return mDisabledUntilUsedPreinstalledCarrierAssociatedApps;
     }
 
-    public ArraySet<String> getPrivAppPermissions(String packageName) {
-        return mPrivAppPermissions.get(packageName);
-    }
-
-    public ArraySet<String> getPrivAppDenyPermissions(String packageName) {
-        return mPrivAppDenyPermissions.get(packageName);
-    }
-
-    /** Get privapp permission allowlist for an apk-in-apex. */
-    public ArraySet<String> getApexPrivAppPermissions(String apexName, String apkPackageName) {
-        return mApexPrivAppPermissions.getOrDefault(apexName, EMPTY_PERMISSIONS)
-                .get(apkPackageName);
-    }
-
-    /** Get privapp permissions denylist for an apk-in-apex. */
-    public ArraySet<String> getApexPrivAppDenyPermissions(String apexName, String apkPackageName) {
-        return mApexPrivAppDenyPermissions.getOrDefault(apexName, EMPTY_PERMISSIONS)
-                .get(apkPackageName);
-    }
-
-    public ArraySet<String> getVendorPrivAppPermissions(String packageName) {
-        return mVendorPrivAppPermissions.get(packageName);
-    }
-
-    public ArraySet<String> getVendorPrivAppDenyPermissions(String packageName) {
-        return mVendorPrivAppDenyPermissions.get(packageName);
-    }
-
-    public ArraySet<String> getProductPrivAppPermissions(String packageName) {
-        return mProductPrivAppPermissions.get(packageName);
-    }
-
-    public ArraySet<String> getProductPrivAppDenyPermissions(String packageName) {
-        return mProductPrivAppDenyPermissions.get(packageName);
-    }
-
-    /**
-     * Read from "permission" tags in /system_ext/etc/permissions/*.xml
-     * @return Set of privileged permissions that are explicitly granted.
-     */
-    public ArraySet<String> getSystemExtPrivAppPermissions(String packageName) {
-        return mSystemExtPrivAppPermissions.get(packageName);
-    }
-
-    /**
-     * Read from "deny-permission" tags in /system_ext/etc/permissions/*.xml
-     * @return Set of privileged permissions that are explicitly denied.
-     */
-    public ArraySet<String> getSystemExtPrivAppDenyPermissions(String packageName) {
-        return mSystemExtPrivAppDenyPermissions.get(packageName);
-    }
-
-    public Map<String, Boolean> getOemPermissions(String packageName) {
-        final Map<String, Boolean> oemPermissions = mOemPermissions.get(packageName);
-        if (oemPermissions != null) {
-            return oemPermissions;
-        }
-        return Collections.emptyMap();
+    public PermissionAllowlist getPermissionAllowlist() {
+        return mPermissionAllowlist;
     }
 
     public ArrayMap<String, ArraySet<String>> getAllowedAssociations() {
@@ -1253,20 +1181,20 @@
                                     Environment.getApexDirectory().toPath() + "/")
                                     && ApexProperties.updatable().orElse(false);
                             if (vendor) {
-                                readPrivAppPermissions(parser, mVendorPrivAppPermissions,
-                                        mVendorPrivAppDenyPermissions);
+                                readPrivAppPermissions(parser,
+                                        mPermissionAllowlist.getVendorPrivilegedAppAllowlist());
                             } else if (product) {
-                                readPrivAppPermissions(parser, mProductPrivAppPermissions,
-                                        mProductPrivAppDenyPermissions);
+                                readPrivAppPermissions(parser,
+                                        mPermissionAllowlist.getProductPrivilegedAppAllowlist());
                             } else if (systemExt) {
-                                readPrivAppPermissions(parser, mSystemExtPrivAppPermissions,
-                                        mSystemExtPrivAppDenyPermissions);
+                                readPrivAppPermissions(parser,
+                                        mPermissionAllowlist.getSystemExtPrivilegedAppAllowlist());
                             } else if (apex) {
                                 readApexPrivAppPermissions(parser, permFile,
                                         Environment.getApexDirectory().toPath());
                             } else {
-                                readPrivAppPermissions(parser, mPrivAppPermissions,
-                                        mPrivAppDenyPermissions);
+                                readPrivAppPermissions(parser,
+                                        mPermissionAllowlist.getPrivilegedAppAllowlist());
                             }
                         } else {
                             logNotAllowedInPartition(name, permFile, parser);
@@ -1589,50 +1517,10 @@
         }
     }
 
-    private void readPrivAppPermissions(XmlPullParser parser,
-            ArrayMap<String, ArraySet<String>> grantMap,
-            ArrayMap<String, ArraySet<String>> denyMap)
+    private void readPrivAppPermissions(@NonNull XmlPullParser parser,
+            @NonNull ArrayMap<String, ArrayMap<String, Boolean>> allowlist)
             throws IOException, XmlPullParserException {
-        String packageName = parser.getAttributeValue(null, "package");
-        if (TextUtils.isEmpty(packageName)) {
-            Slog.w(TAG, "package is required for <privapp-permissions> in "
-                    + parser.getPositionDescription());
-            return;
-        }
-
-        ArraySet<String> permissions = grantMap.get(packageName);
-        if (permissions == null) {
-            permissions = new ArraySet<>();
-        }
-        ArraySet<String> denyPermissions = denyMap.get(packageName);
-        int depth = parser.getDepth();
-        while (XmlUtils.nextElementWithin(parser, depth)) {
-            String name = parser.getName();
-            if ("permission".equals(name)) {
-                String permName = parser.getAttributeValue(null, "name");
-                if (TextUtils.isEmpty(permName)) {
-                    Slog.w(TAG, "name is required for <permission> in "
-                            + parser.getPositionDescription());
-                    continue;
-                }
-                permissions.add(permName);
-            } else if ("deny-permission".equals(name)) {
-                String permName = parser.getAttributeValue(null, "name");
-                if (TextUtils.isEmpty(permName)) {
-                    Slog.w(TAG, "name is required for <deny-permission> in "
-                            + parser.getPositionDescription());
-                    continue;
-                }
-                if (denyPermissions == null) {
-                    denyPermissions = new ArraySet<>();
-                }
-                denyPermissions.add(permName);
-            }
-        }
-        grantMap.put(packageName, permissions);
-        if (denyPermissions != null) {
-            denyMap.put(packageName, denyPermissions);
-        }
+        readPermissionAllowlist(parser, allowlist, "privapp-permissions");
     }
 
     private void readInstallInUserType(XmlPullParser parser,
@@ -1683,14 +1571,21 @@
     }
 
     void readOemPermissions(XmlPullParser parser) throws IOException, XmlPullParserException {
+        readPermissionAllowlist(parser, mPermissionAllowlist.getOemAppAllowlist(),
+                "oem-permissions");
+    }
+
+    private static void readPermissionAllowlist(@NonNull XmlPullParser parser,
+            @NonNull ArrayMap<String, ArrayMap<String, Boolean>> allowlist, @NonNull String tagName)
+            throws IOException, XmlPullParserException {
         final String packageName = parser.getAttributeValue(null, "package");
         if (TextUtils.isEmpty(packageName)) {
-            Slog.w(TAG, "package is required for <oem-permissions> in "
+            Slog.w(TAG, "package is required for <" + tagName + "> in "
                     + parser.getPositionDescription());
             return;
         }
 
-        ArrayMap<String, Boolean> permissions = mOemPermissions.get(packageName);
+        ArrayMap<String, Boolean> permissions = allowlist.get(packageName);
         if (permissions == null) {
             permissions = new ArrayMap<>();
         }
@@ -1698,24 +1593,24 @@
         while (XmlUtils.nextElementWithin(parser, depth)) {
             final String name = parser.getName();
             if ("permission".equals(name)) {
-                final String permName = parser.getAttributeValue(null, "name");
-                if (TextUtils.isEmpty(permName)) {
+                final String permissionName = parser.getAttributeValue(null, "name");
+                if (TextUtils.isEmpty(permissionName)) {
                     Slog.w(TAG, "name is required for <permission> in "
                             + parser.getPositionDescription());
                     continue;
                 }
-                permissions.put(permName, Boolean.TRUE);
+                permissions.put(permissionName, Boolean.TRUE);
             } else if ("deny-permission".equals(name)) {
-                String permName = parser.getAttributeValue(null, "name");
-                if (TextUtils.isEmpty(permName)) {
+                String permissionName = parser.getAttributeValue(null, "name");
+                if (TextUtils.isEmpty(permissionName)) {
                     Slog.w(TAG, "name is required for <deny-permission> in "
                             + parser.getPositionDescription());
                     continue;
                 }
-                permissions.put(permName, Boolean.FALSE);
+                permissions.put(permissionName, Boolean.FALSE);
             }
         }
-        mOemPermissions.put(packageName, permissions);
+        allowlist.put(packageName, permissions);
     }
 
     private void readSplitPermission(XmlPullParser parser, File permFile)
@@ -1865,21 +1760,14 @@
             Path apexDirectoryPath) throws IOException, XmlPullParserException {
         final String moduleName =
                 getApexModuleNameFromFilePath(permFile.toPath(), apexDirectoryPath);
-        final ArrayMap<String, ArraySet<String>> privAppPermissions;
-        if (mApexPrivAppPermissions.containsKey(moduleName)) {
-            privAppPermissions = mApexPrivAppPermissions.get(moduleName);
-        } else {
-            privAppPermissions = new ArrayMap<>();
-            mApexPrivAppPermissions.put(moduleName, privAppPermissions);
+        final ArrayMap<String, ArrayMap<String, ArrayMap<String, Boolean>>> allowlists =
+                mPermissionAllowlist.getApexPrivilegedAppAllowlists();
+        ArrayMap<String, ArrayMap<String, Boolean>> allowlist = allowlists.get(moduleName);
+        if (allowlist == null) {
+            allowlist = new ArrayMap<>();
+            allowlists.put(moduleName, allowlist);
         }
-        final ArrayMap<String, ArraySet<String>> privAppDenyPermissions;
-        if (mApexPrivAppDenyPermissions.containsKey(moduleName)) {
-            privAppDenyPermissions = mApexPrivAppDenyPermissions.get(moduleName);
-        } else {
-            privAppDenyPermissions = new ArrayMap<>();
-            mApexPrivAppDenyPermissions.put(moduleName, privAppDenyPermissions);
-        }
-        readPrivAppPermissions(parser, privAppPermissions, privAppDenyPermissions);
+        readPrivAppPermissions(parser, allowlist);
     }
 
     private static boolean isSystemProcess() {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 32afcca..ee922f9 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -92,6 +92,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.telephony.ICarrierConfigChangeListener;
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.IPhoneStateListener;
@@ -155,6 +156,7 @@
         IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
         IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
         ICarrierPrivilegesCallback carrierPrivilegesCallback;
+        ICarrierConfigChangeListener carrierConfigChangeListener;
 
         int callerUid;
         int callerPid;
@@ -183,6 +185,10 @@
             return carrierPrivilegesCallback != null;
         }
 
+        boolean matchCarrierConfigChangeListener() {
+            return carrierConfigChangeListener != null;
+        }
+
         boolean canReadCallLog() {
             try {
                 return TelephonyPermissions.checkReadCallLog(
@@ -201,6 +207,7 @@
                     + " onOpportunisticSubscriptionsChangedListenererCallback="
                     + onOpportunisticSubscriptionsChangedListenerCallback
                     + " carrierPrivilegesCallback=" + carrierPrivilegesCallback
+                    + " carrierConfigChangeListener=" + carrierConfigChangeListener
                     + " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}";
         }
     }
@@ -3044,6 +3051,82 @@
         }
     }
 
+    @Override
+    public void addCarrierConfigChangeListener(ICarrierConfigChangeListener listener,
+            String pkg, String featureId) {
+        final int callerUserId = UserHandle.getCallingUserId();
+        mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+        if (VDBG) {
+            log("addCarrierConfigChangeListener pkg=" + pii(pkg) + " uid=" + Binder.getCallingUid()
+                    + " myUserId=" + UserHandle.myUserId() + " callerUerId" + callerUserId
+                    + " listener=" + listener + " listener.asBinder=" + listener.asBinder());
+        }
+
+        synchronized (mRecords) {
+            IBinder b = listener.asBinder();
+            boolean doesLimitApply = doesLimitApplyForListeners(Binder.getCallingUid(),
+                    Process.myUid());
+            Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply);
+
+            if (r == null) {
+                loge("Can not create Record instance!");
+                return;
+            }
+
+            r.context = mContext;
+            r.carrierConfigChangeListener = listener;
+            r.callingPackage = pkg;
+            r.callingFeatureId = featureId;
+            r.callerUid = Binder.getCallingUid();
+            r.callerPid = Binder.getCallingPid();
+            r.eventList = new ArraySet<>();
+            if (DBG) {
+                log("addCarrierConfigChangeListener:  Register r=" + r);
+            }
+        }
+    }
+
+    @Override
+    public void removeCarrierConfigChangeListener(ICarrierConfigChangeListener listener,
+            String pkg) {
+        if (DBG) log("removeCarrierConfigChangeListener listener=" + listener + ", pkg=" + pkg);
+        mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+        remove(listener.asBinder());
+    }
+
+    @Override
+    public void notifyCarrierConfigChanged(int phoneId, int subId, int carrierId,
+            int specificCarrierId) {
+        if (!validatePhoneId(phoneId)) {
+            throw new IllegalArgumentException("Invalid phoneId: " + phoneId);
+        }
+        if (!checkNotifyPermission("notifyCarrierConfigChanged")) {
+            loge("Caller has no notify permission!");
+            return;
+        }
+        if (VDBG) {
+            log("notifyCarrierConfigChanged: phoneId=" + phoneId + ", subId=" + subId
+                    + ", carrierId=" + carrierId + ", specificCarrierId=" + specificCarrierId);
+        }
+
+        synchronized (mRecords) {
+            mRemoveList.clear();
+            for (Record r : mRecords) {
+                // Listeners are "global", neither per-slot nor per-sub, so no idMatch check here
+                if (!r.matchCarrierConfigChangeListener()) {
+                    continue;
+                }
+                try {
+                    r.carrierConfigChangeListener.onCarrierConfigChanged(phoneId, subId, carrierId,
+                            specificCarrierId);
+                } catch (RemoteException re) {
+                    mRemoveList.add(r.binder);
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     @NeverCompile // Avoid size overhead of debugging code.
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 61f7f30..f652cb0 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -371,8 +371,9 @@
             return new LocationPermissionChecker(context);
         }
 
-        /** Gets the transports that need to be marked as restricted by the VCN */
-        public Set<Integer> getRestrictedTransports(
+        /** Gets transports that need to be marked as restricted by the VCN from CarrierConfig */
+        @VisibleForTesting(visibility = Visibility.PRIVATE)
+        public Set<Integer> getRestrictedTransportsFromCarrierConfig(
                 ParcelUuid subGrp, TelephonySubscriptionSnapshot lastSnapshot) {
             if (!Build.IS_ENG && !Build.IS_USERDEBUG) {
                 return RESTRICTED_TRANSPORTS_DEFAULT;
@@ -398,6 +399,22 @@
             }
             return restrictedTransports;
         }
+
+        /** Gets the transports that need to be marked as restricted by the VCN */
+        public Set<Integer> getRestrictedTransports(
+                ParcelUuid subGrp,
+                TelephonySubscriptionSnapshot lastSnapshot,
+                VcnConfig vcnConfig) {
+            final Set<Integer> restrictedTransports = new ArraySet<>();
+            restrictedTransports.addAll(vcnConfig.getRestrictedUnderlyingNetworkTransports());
+
+            // TODO: b/262269892 Remove the ability to configure restricted transports
+            // via CarrierConfig
+            restrictedTransports.addAll(
+                    getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot));
+
+            return restrictedTransports;
+        }
     }
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -719,6 +736,7 @@
         if (mVcns.containsKey(subscriptionGroup)) {
             final Vcn vcn = mVcns.get(subscriptionGroup);
             vcn.updateConfig(config);
+            notifyAllPolicyListenersLocked();
         } else {
             // TODO(b/193687515): Support multiple VCNs active at the same time
             if (isActiveSubGroup(subscriptionGroup, mLastSnapshot)) {
@@ -936,7 +954,6 @@
     }
 
     /** Adds the provided listener for receiving VcnUnderlyingNetworkPolicy updates. */
-    @GuardedBy("mLock")
     @Override
     public void addVcnUnderlyingNetworkPolicyListener(
             @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
@@ -963,16 +980,7 @@
         });
     }
 
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void addVcnUnderlyingNetworkPolicyListenerForTest(
-            @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
-        synchronized (mLock) {
-            addVcnUnderlyingNetworkPolicyListener(listener);
-        }
-    }
-
     /** Removes the provided listener from receiving VcnUnderlyingNetworkPolicy updates. */
-    @GuardedBy("mLock")
     @Override
     public void removeVcnUnderlyingNetworkPolicyListener(
             @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
@@ -1062,8 +1070,8 @@
                         isVcnManagedNetwork = true;
                     }
 
-                    final Set<Integer> restrictedTransports =
-                            mDeps.getRestrictedTransports(subGrp, mLastSnapshot);
+                    final Set<Integer> restrictedTransports = mDeps.getRestrictedTransports(
+                            subGrp, mLastSnapshot, mConfigs.get(subGrp));
                     for (int restrictedTransport : restrictedTransports) {
                         if (ncCopy.hasTransport(restrictedTransport)) {
                             if (restrictedTransport == TRANSPORT_CELLULAR) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index a2755be..88492ed 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -3558,8 +3558,7 @@
             Bundle.setDefusable(result, true);
             mNumResults++;
             Intent intent = null;
-            if (result != null
-                    && (intent = result.getParcelable(AccountManager.KEY_INTENT, android.content.Intent.class)) != null) {
+            if (result != null) {
                 if (!checkKeyIntent(
                         Binder.getCallingUid(),
                         result)) {
@@ -4928,8 +4927,10 @@
                 EventLog.writeEvent(0x534e4554, "250588548", authUid, "");
                 return false;
             }
-
             Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
+            if (intent == null) {
+                return true;
+            }
             // Explicitly set an empty ClipData to ensure that we don't offer to
             // promote any Uris contained inside for granting purposes
             if (intent.getClipData() == null) {
@@ -4979,8 +4980,12 @@
             Bundle simulateBundle = p.readBundle();
             p.recycle();
             Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
-            return (intent.filterEquals(simulateBundle.getParcelable(AccountManager.KEY_INTENT,
-                Intent.class)));
+            Intent simulateIntent = simulateBundle.getParcelable(AccountManager.KEY_INTENT,
+                    Intent.class);
+            if (intent == null) {
+                return (simulateIntent == null);
+            }
+            return intent.filterEquals(simulateIntent);
         }
 
         private boolean isExportedSystemActivity(ActivityInfo activityInfo) {
@@ -5129,8 +5134,7 @@
                     }
                 }
             }
-            if (result != null
-                    && (intent = result.getParcelable(AccountManager.KEY_INTENT, android.content.Intent.class)) != null) {
+            if (result != null) {
                 if (!checkKeyIntent(
                         Binder.getCallingUid(),
                         result)) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index fa62b89..f72321c 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2787,8 +2787,8 @@
             }
         }
 
-        private final AppOpsManager.OnOpNotedListener mOpNotedCallback =
-                new AppOpsManager.OnOpNotedListener() {
+        private final AppOpsManager.OnOpNotedInternalListener mOpNotedCallback =
+                new AppOpsManager.OnOpNotedInternalListener() {
                     @Override
                     public void onOpNoted(int op, int uid, String pkgName,
                             String attributionTag, int flags, int result) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index b0a2912..70f07a9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -129,7 +129,6 @@
     static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE = "kill_bg_restricted_cached_idle";
     static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME =
             "kill_bg_restricted_cached_idle_settle_time";
-    static final String KEY_MAX_PREVIOUS_TIME = "max_previous_time";
     /**
      * Note this key is on {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS}.
      * @see #mEnableComponentAlias
@@ -193,7 +192,6 @@
     private static final float DEFAULT_FGS_START_DENIED_LOG_SAMPLE_RATE = 1; // 100%
     private static final long DEFAULT_PROCESS_KILL_TIMEOUT_MS = 10 * 1000;
     private static final long DEFAULT_NETWORK_ACCESS_TIMEOUT_MS = 200; // 0.2 sec
-    private static final long DEFAULT_MAX_PREVIOUS_TIME = 60 * 1000; // 60s
 
     static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60 * 1000;
     static final long DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME_MS = 60 * 1000;
@@ -524,9 +522,6 @@
     public long TOP_TO_ALMOST_PERCEPTIBLE_GRACE_DURATION =
             DEFAULT_TOP_TO_ALMOST_PERCEPTIBLE_GRACE_DURATION;
 
-    // How long a process can remain at previous oom_adj before dropping to cached
-    public static long MAX_PREVIOUS_TIME = DEFAULT_MAX_PREVIOUS_TIME;
-
     /**
      * The minimum time we allow between crashes, for us to consider this
      * application to be bad and stop its services and reject broadcasts.
@@ -1149,10 +1144,6 @@
                                 break;
                             case KEY_TOP_TO_FGS_GRACE_DURATION:
                                 updateTopToFgsGraceDuration();
-                                break;
-                            case KEY_MAX_PREVIOUS_TIME:
-                                updateMaxPreviousTime();
-                                break;
                             default:
                                 break;
                         }
@@ -1799,7 +1790,6 @@
                 DEFAULT_LOW_SWAP_THRESHOLD_PERCENT);
     }
 
-
     private void updateTopToFgsGraceDuration() {
         TOP_TO_FGS_GRACE_DURATION = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1807,13 +1797,6 @@
                 DEFAULT_TOP_TO_FGS_GRACE_DURATION);
     }
 
-    private void updateMaxPreviousTime() {
-        MAX_PREVIOUS_TIME = DeviceConfig.getLong(
-                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                KEY_MAX_PREVIOUS_TIME,
-                DEFAULT_MAX_PREVIOUS_TIME);
-    }
-
     private void updateMinAssocLogDuration() {
         MIN_ASSOC_LOG_DURATION = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MIN_ASSOC_LOG_DURATION,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5b93500..045c757 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7438,11 +7438,10 @@
         if (shareDescription != null) {
             triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription);
         }
-        UserHandle callingUser = Binder.getCallingUserHandle();
         final long identity = Binder.clearCallingIdentity();
         try {
             // Send broadcast to shell to trigger bugreport using Bugreport API
-            mContext.sendBroadcastAsUser(triggerShellBugreport, callingUser);
+            mContext.sendBroadcastAsUser(triggerShellBugreport, UserHandle.SYSTEM);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -13578,9 +13577,10 @@
             // updating their receivers to be exempt from this requirement until their receivers
             // are flagged.
             if (requireExplicitFlagForDynamicReceivers) {
-                if ("com.google.android.apps.messaging".equals(callerPackage)) {
-                    // Note, a versionCode check for this package is not performed because it could
-                    // cause breakage with a subsequent update outside the system image.
+                if ("com.shannon.imsservice".equals(callerPackage)) {
+                    // Note, a versionCode check for this package is not performed because this
+                    // package consumes the SecurityException, so it wouldn't be caught during
+                    // presubmit.
                     requireExplicitFlagForDynamicReceivers = false;
                 }
             }
@@ -18389,6 +18389,25 @@
     }
 
     @Override
+    public void forceDelayBroadcastDelivery(@NonNull String targetPackage,
+            long delayedDurationMs) {
+        Objects.requireNonNull(targetPackage);
+        Preconditions.checkArgumentNonnegative(delayedDurationMs);
+        Preconditions.checkState(mEnableModernQueue, "Not valid in legacy queue");
+        enforceCallingPermission(permission.DUMP, "forceDelayBroadcastDelivery()");
+
+        for (BroadcastQueue queue : mBroadcastQueues) {
+            queue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
+        }
+    }
+
+    @Override
+    public boolean isModernBroadcastQueueEnabled() {
+        enforceCallingPermission(permission.DUMP, "isModernBroadcastQueueEnabled()");
+        return mEnableModernQueue;
+    }
+
+    @Override
     @ReasonCode
     public int getBackgroundRestrictionExemptionReason(int uid) {
         enforceCallingPermission(android.Manifest.permission.DEVICE_POWER,
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 94c15ba..4a6e5a3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1413,6 +1413,7 @@
                 mPw.println("shortMsg: " + shortMsg);
                 mPw.println("longMsg: " + longMsg);
                 mPw.println("timeMillis: " + timeMillis);
+                mPw.println("uptime: " + SystemClock.uptimeMillis());
                 mPw.println("stack:");
                 mPw.print(stackTrace);
                 mPw.println("#");
@@ -1429,6 +1430,7 @@
                 mPw.println("processName: " + processName);
                 mPw.println("processPid: " + pid);
                 mPw.println("annotation: " + annotation);
+                mPw.println("uptime: " + SystemClock.uptimeMillis());
                 mPw.flush();
                 int result = waitControllerLocked(pid, STATE_EARLY_ANR);
                 if (result == RESULT_EARLY_ANR_KILL) return -1;
@@ -1442,6 +1444,7 @@
                 mPw.println("** ERROR: PROCESS NOT RESPONDING");
                 mPw.println("processName: " + processName);
                 mPw.println("processPid: " + pid);
+                mPw.println("uptime: " + SystemClock.uptimeMillis());
                 mPw.println("processStats:");
                 mPw.print(processStats);
                 mPw.println("#");
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index f5d1c10..fe5888d 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -236,6 +236,15 @@
     private static final int DEFAULT_MAX_HISTORY_SUMMARY_SIZE =
             ActivityManager.isLowRamDeviceStatic() ? 25 : 300;
 
+    /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of broadcast receivers to process in a
+     * single synchronized block.  Up to this many messages may be dispatched in a single binder
+     * call.  Set this to 1 (or zero) for pre-batch behavior.
+     */
+    public int MAX_BROADCAST_BATCH_SIZE = DEFAULT_MAX_BROADCAST_BATCH_SIZE;
+    private static final String KEY_MAX_BROADCAST_BATCH_SIZE = "bcast_max_batch_size";
+    private static final int DEFAULT_MAX_BROADCAST_BATCH_SIZE = 1;
+
     // Settings override tracking for this instance
     private String mSettingsKey;
     private SettingsObserver mSettingsObserver;
@@ -373,6 +382,8 @@
                     DEFAULT_MAX_HISTORY_COMPLETE_SIZE);
             MAX_HISTORY_SUMMARY_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_SUMMARY_SIZE,
                     DEFAULT_MAX_HISTORY_SUMMARY_SIZE);
+            MAX_BROADCAST_BATCH_SIZE = getDeviceConfigInt(KEY_MAX_BROADCAST_BATCH_SIZE,
+                    DEFAULT_MAX_BROADCAST_BATCH_SIZE);
         }
     }
 
@@ -418,6 +429,7 @@
                     MAX_CONSECUTIVE_URGENT_DISPATCHES).println();
             pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
                     MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
+            pw.print(KEY_MAX_BROADCAST_BATCH_SIZE, MAX_BROADCAST_BATCH_SIZE).println();
             pw.decreaseIndent();
             pw.println();
         }
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 632f614..fceefad 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -183,6 +183,11 @@
     private String mCachedToString;
     private String mCachedToShortString;
 
+    /**
+     * The duration by which any broadcasts to this process need to be delayed
+     */
+    private long mForcedDelayedDurationMs;
+
     public BroadcastProcessQueue(@NonNull BroadcastConstants constants,
             @NonNull String processName, int uid) {
         this.constants = Objects.requireNonNull(constants);
@@ -275,7 +280,7 @@
      */
     @FunctionalInterface
     public interface BroadcastPredicate {
-        public boolean test(@NonNull BroadcastRecord r, int index);
+        boolean test(@NonNull BroadcastRecord r, int index);
     }
 
     /**
@@ -284,7 +289,7 @@
      */
     @FunctionalInterface
     public interface BroadcastConsumer {
-        public void accept(@NonNull BroadcastRecord r, int index);
+        void accept(@NonNull BroadcastRecord r, int index);
     }
 
     /**
@@ -418,6 +423,13 @@
     }
 
     /**
+     * Get package name of the first application loaded into this process.
+     */
+    public String getPackageName() {
+        return app.getApplicationInfo().packageName;
+    }
+
+    /**
      * Set the currently active broadcast to the next pending broadcast.
      */
     public void makeActiveNextPending() {
@@ -520,15 +532,13 @@
     }
 
     public void traceActiveBegin() {
-        final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
         Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                runningTraceTrackName, mActive.toShortString() + " scheduled", cookie);
+                runningTraceTrackName, mActive.toShortString() + " scheduled", hashCode());
     }
 
     public void traceActiveEnd() {
-        final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
         Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                runningTraceTrackName, cookie);
+                runningTraceTrackName, hashCode());
     }
 
     /**
@@ -555,6 +565,10 @@
         return mActive != null;
     }
 
+    void forceDelayBroadcastDelivery(long delayedDurationMs) {
+        mForcedDelayedDurationMs = delayedDurationMs;
+    }
+
     /**
      * Will thrown an exception if there are no pending broadcasts; relies on
      * {@link #isEmpty()} being false.
@@ -729,6 +743,7 @@
     static final int REASON_BLOCKED = 4;
     static final int REASON_INSTRUMENTED = 5;
     static final int REASON_PERSISTENT = 6;
+    static final int REASON_FORCE_DELAYED = 7;
     static final int REASON_CONTAINS_FOREGROUND = 10;
     static final int REASON_CONTAINS_ORDERED = 11;
     static final int REASON_CONTAINS_ALARM = 12;
@@ -746,6 +761,7 @@
             REASON_BLOCKED,
             REASON_INSTRUMENTED,
             REASON_PERSISTENT,
+            REASON_FORCE_DELAYED,
             REASON_CONTAINS_FOREGROUND,
             REASON_CONTAINS_ORDERED,
             REASON_CONTAINS_ALARM,
@@ -767,6 +783,7 @@
             case REASON_BLOCKED: return "BLOCKED";
             case REASON_INSTRUMENTED: return "INSTRUMENTED";
             case REASON_PERSISTENT: return "PERSISTENT";
+            case REASON_FORCE_DELAYED: return "FORCE_DELAYED";
             case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND";
             case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED";
             case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM";
@@ -810,7 +827,10 @@
                 return;
             }
 
-            if (mCountForeground > 0) {
+            if (mForcedDelayedDurationMs > 0) {
+                mRunnableAt = runnableAt + mForcedDelayedDurationMs;
+                mRunnableAtReason = REASON_FORCE_DELAYED;
+            } else if (mCountForeground > 0) {
                 mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
                 mRunnableAtReason = REASON_CONTAINS_FOREGROUND;
             } else if (mCountInteractive > 0) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 153ad1e..75e9336 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -233,6 +233,16 @@
     public abstract void waitForBarrier(@Nullable PrintWriter pw);
 
     /**
+     * Delays delivering broadcasts to the specified package.
+     *
+     * <p> Note that this is only valid for modern queue.
+     */
+    public void forceDelayBroadcastDelivery(@NonNull String targetPackage,
+            long delayedDurationMs) {
+        // No default implementation.
+    }
+
+    /**
      * Brief summary of internal state, useful for debugging purposes.
      */
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 1a72fef..8946ada1 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -73,6 +73,7 @@
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
@@ -186,6 +187,14 @@
         }
     }
 
+    /**
+     * This single object allows the queue to dispatch receivers using scheduleReceiverList
+     * without constantly allocating new ReceiverInfo objects or ArrayLists.  This queue
+     * implementation is known to have a maximum size of one entry.
+     */
+    @VisibleForTesting
+    final BroadcastReceiverBatch mReceiverBatch = new BroadcastReceiverBatch(1);
+
     BroadcastQueueImpl(ActivityManagerService service, Handler handler,
             String name, BroadcastConstants constants, boolean allowDelayBehindServices,
             int schedGroup) {
@@ -388,10 +397,11 @@
                     + ": " + r);
             mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
                                       PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
-            thread.scheduleReceiver(prepareReceiverIntent(r.intent, r.curFilteredExtras),
+            thread.scheduleReceiverList(mReceiverBatch.manifestReceiver(
+                    prepareReceiverIntent(r.intent, r.curFilteredExtras),
                     r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
                     r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
-                    app.mState.getReportedProcState());
+                    app.mState.getReportedProcState()));
             if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                     "Process cur broadcast " + r + " DELIVERED for app " + app);
             started = true;
@@ -725,9 +735,10 @@
                 // If we have an app thread, do the call through that so it is
                 // correctly ordered with other one-way calls.
                 try {
-                    thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
+                    thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
+                            receiver, intent, resultCode,
                             data, extras, ordered, sticky, sendingUser,
-                            app.mState.getReportedProcState());
+                            app.mState.getReportedProcState()));
                 } catch (RemoteException ex) {
                     // Failed to call into the process. It's either dying or wedged. Kill it gently.
                     synchronized (mService) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index eb5c03b..a850c8a 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -87,6 +87,7 @@
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -140,6 +141,11 @@
         // We configure runnable size only once at boot; it'd be too complex to
         // try resizing dynamically at runtime
         mRunning = new BroadcastProcessQueue[mConstants.getMaxRunningQueues()];
+
+        // Set up the statistics for batched broadcasts.
+        final int batchSize = mConstants.MAX_BROADCAST_BATCH_SIZE;
+        mReceiverBatch = new BroadcastReceiverBatch(batchSize);
+        Slog.i(TAG, "maximum broadcast batch size " + batchSize);
     }
 
     /**
@@ -202,6 +208,15 @@
     private final BroadcastConstants mBgConstants;
 
     /**
+     * The sole instance of BroadcastReceiverBatch that is used by scheduleReceiverWarmLocked().
+     * The class is not a true singleton but only one instance is needed for the broadcast queue.
+     * Although this is guarded by mService, it should never be accessed by any other function.
+     */
+    @VisibleForTesting
+    @GuardedBy("mService")
+    final BroadcastReceiverBatch mReceiverBatch;
+
+    /**
      * Timestamp when last {@link #testAllProcessQueues} failure was observed;
      * used for throttling log messages.
      */
@@ -568,7 +583,7 @@
         if (queue != null) {
             // If queue was running a broadcast, fail it
             if (queue.isActive()) {
-                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+                finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
                         "onApplicationCleanupLocked");
             }
 
@@ -741,58 +756,62 @@
      * case where a broadcast is handled by a remote app, and the case where the
      * broadcast was finished locally without the remote app being involved.
      */
+    @GuardedBy("mService")
     private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
         checkState(queue.isActive(), "isActive");
+        BroadcastReceiverBatch batch = mReceiverBatch;
+        batch.reset();
 
-        final BroadcastRecord r = queue.getActive();
-        final int index = queue.getActiveIndex();
-
-        if (r.terminalCount == 0) {
-            r.dispatchTime = SystemClock.uptimeMillis();
-            r.dispatchRealTime = SystemClock.elapsedRealtime();
-            r.dispatchClockTime = System.currentTimeMillis();
+        while (collectReceiverList(queue, batch)) {
+            if (batch.isFull()) {
+                break;
+            }
+            if (!shouldContinueScheduling(queue)) {
+                break;
+             }
+            if (queue.isEmpty()) {
+                break;
+            }
+            queue.makeActiveNextPending();
         }
-
-        if (maybeSkipReceiver(queue, r, index)) {
-            return;
-        }
-        dispatchReceivers(queue, r, index);
+        processReceiverList(queue, batch);
     }
 
     /**
      * Examine a receiver and possibly skip it.  The method returns true if the receiver is
      * skipped (and therefore no more work is required).
      */
-    private boolean maybeSkipReceiver(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
+    private boolean maybeSkipReceiver(@NonNull BroadcastProcessQueue queue,
+            @NonNull BroadcastReceiverBatch batch, @NonNull BroadcastRecord r, int index) {
         final int oldDeliveryState = getDeliveryState(r, index);
         final ProcessRecord app = queue.app;
         final Object receiver = r.receivers.get(index);
 
         // If someone already finished this broadcast, finish immediately
         if (isDeliveryStateTerminal(oldDeliveryState)) {
-            enqueueFinishReceiver(queue, oldDeliveryState, "already terminal state");
+            batch.finish(r, index, oldDeliveryState, "already terminal state");
             return true;
         }
 
         // Consider additional cases where we'd want to finish immediately
         if (app.isInFullBackup()) {
-            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
+            batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
             return true;
         }
         if (mSkipPolicy.shouldSkip(r, receiver)) {
-            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
+            batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
             return true;
         }
         final Intent receiverIntent = r.getReceiverIntent(receiver);
         if (receiverIntent == null) {
-            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
+            batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
             return true;
         }
 
         // Ignore registered receivers from a previous PID
         if ((receiver instanceof BroadcastFilter)
                 && ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
-            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED,
+            batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED,
                     "BroadcastFilter for mismatched PID");
             return true;
         }
@@ -801,6 +820,133 @@
     }
 
     /**
+     * Collect receivers into a list, to be dispatched in a single receiver list call.  Return
+     * true if remaining receivers in the queue should be examined, and false if the current list
+     * is complete.
+     */
+    private boolean collectReceiverList(@NonNull BroadcastProcessQueue queue,
+            @NonNull BroadcastReceiverBatch batch) {
+        final ProcessRecord app = queue.app;
+        final BroadcastRecord r = queue.getActive();
+        final int index = queue.getActiveIndex();
+        final Object receiver = r.receivers.get(index);
+        final Intent receiverIntent = r.getReceiverIntent(receiver);
+
+        if (r.terminalCount == 0) {
+            r.dispatchTime = SystemClock.uptimeMillis();
+            r.dispatchRealTime = SystemClock.elapsedRealtime();
+            r.dispatchClockTime = System.currentTimeMillis();
+        }
+        if (maybeSkipReceiver(queue, batch, r, index)) {
+            return true;
+        }
+
+        final IApplicationThread thread = app.getOnewayThread();
+        if (thread == null) {
+            batch.finish(r, index, BroadcastRecord.DELIVERY_FAILURE, "missing IApplicationThread");
+            return true;
+        }
+
+        if (receiver instanceof BroadcastFilter) {
+            batch.schedule(((BroadcastFilter) receiver).receiverList.receiver,
+                    receiverIntent, r.resultCode, r.resultData, r.resultExtras,
+                    r.ordered, r.initialSticky, r.userId,
+                    app.mState.getReportedProcState(), r, index);
+            // TODO: consider making registered receivers of unordered
+            // broadcasts report results to detect ANRs
+            if (!r.ordered) {
+                batch.success(r, index, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered");
+                return true;
+            }
+        } else {
+            batch.schedule(receiverIntent, ((ResolveInfo) receiver).activityInfo,
+                    null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
+                    app.mState.getReportedProcState(), r, index);
+        }
+
+        return false;
+    }
+
+    /**
+     * Process the information in a BroadcastReceiverBatch.  Elements in the finish and success
+     * lists are sent to enqueueFinishReceiver().  Elements in the receivers list are transmitted
+     * to the target in a single binder call.
+     */
+    private void processReceiverList(@NonNull BroadcastProcessQueue queue,
+            @NonNull BroadcastReceiverBatch batch) {
+        // Transmit the receiver list.
+        final ProcessRecord app = queue.app;
+        final IApplicationThread thread = app.getOnewayThread();
+
+        batch.recordBatch(thread instanceof SameProcessApplicationThread);
+
+        // Mark all the receivers that were discarded.  None of these have actually been scheduled.
+        for (int i = 0; i < batch.finished().size(); i++) {
+            final var finish = batch.finished().get(i);
+            enqueueFinishReceiver(queue, finish.r, finish.index, finish.deliveryState,
+                    finish.reason);
+        }
+        // Prepare for delivery of all receivers that are about to be scheduled.
+        for (int i = 0; i < batch.cookies().size(); i++) {
+            final var cookie = batch.cookies().get(i);
+            prepareToDispatch(queue, cookie.r, cookie.index);
+        }
+
+        // Notify on dispatch.  Note that receiver/cookies are recorded only if the thread is
+        // non-null and the list will therefore be sent.
+        for (int i = 0; i < batch.cookies().size(); i++) {
+            // Cookies and receivers are 1:1
+            final var cookie = batch.cookies().get(i);
+            final BroadcastRecord r = cookie.r;
+            final int index = cookie.index;
+            final Object receiver = r.receivers.get(index);
+            if (receiver instanceof BroadcastFilter) {
+                notifyScheduleRegisteredReceiver(queue.app, r, (BroadcastFilter) receiver);
+            } else {
+                notifyScheduleReceiver(queue.app, r, (ResolveInfo) receiver);
+            }
+        }
+
+        // Transmit the enqueued receivers.  The thread cannot be null because the lock has been
+        // held since collectReceiverList(), which will not add any receivers if the thread is null.
+        boolean remoteFailed = false;
+        if (batch.receivers().size()  > 0) {
+            try {
+                thread.scheduleReceiverList(batch.receivers());
+            } catch (RemoteException e) {
+                // Log the failure of the first receiver in the list.  Note that there must be at
+                // least one receiver/cookie to reach this point in the code, which means
+                // cookie[0] is a valid element.
+                final var info = batch.cookies().get(0);
+                final BroadcastRecord r = info.r;
+                final int index = info.index;
+                final Object receiver = r.receivers.get(index);
+                final String msg = "Failed to schedule " + r + " to " + receiver
+                                   + " via " + app + ": " + e;
+                logw(msg);
+                app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, true);
+                remoteFailed = true;
+            }
+        }
+
+        if (!remoteFailed) {
+            // If transmission succeed, report all receivers that are assumed to be delivered.
+            for (int i = 0; i < batch.success().size(); i++) {
+                final var finish = batch.success().get(i);
+                enqueueFinishReceiver(queue, finish.r, finish.index, finish.deliveryState,
+                        finish.reason);
+            }
+        } else {
+            // If transmission failed, fail all receivers in the list.
+            for (int i = 0; i < batch.cookies().size(); i++) {
+                final var cookie = batch.cookies().get(i);
+                enqueueFinishReceiver(queue, cookie.r, cookie.index,
+                        BroadcastRecord.DELIVERY_FAILURE, "remote app");
+            }
+        }
+    }
+
+    /**
      * Return true if this receiver should be assumed to have been delivered.
      */
     private boolean isAssumedDelivered(BroadcastRecord r, int index) {
@@ -810,7 +956,8 @@
     /**
      * A receiver is about to be dispatched.  Start ANR timers, if necessary.
      */
-    private void dispatchReceivers(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
+    private void prepareToDispatch(@NonNull BroadcastProcessQueue queue,
+            @NonNull BroadcastRecord r, int index) {
         final ProcessRecord app = queue.app;
         final Object receiver = r.receivers.get(index);
 
@@ -848,42 +995,6 @@
         if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
         setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED,
                 "scheduleReceiverWarmLocked");
-
-        final Intent receiverIntent = r.getReceiverIntent(receiver);
-        final IApplicationThread thread = app.getOnewayThread();
-        if (thread != null) {
-            try {
-                if (receiver instanceof BroadcastFilter) {
-                    notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
-                    thread.scheduleRegisteredReceiver(
-                            ((BroadcastFilter) receiver).receiverList.receiver, receiverIntent,
-                            r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky,
-                            r.userId, app.mState.getReportedProcState());
-
-                    // TODO: consider making registered receivers of unordered
-                    // broadcasts report results to detect ANRs
-                    if (assumeDelivered) {
-                        enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_DELIVERED,
-                                "assuming delivered");
-                    }
-                } else {
-                    notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
-                    thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo,
-                            null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
-                            app.mState.getReportedProcState());
-                }
-            } catch (RemoteException e) {
-                final String msg = "Failed to schedule " + r + " to " + receiver
-                        + " via " + app + ": " + e;
-                logw(msg);
-                app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
-                        ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
-                enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app");
-            }
-        } else {
-            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE,
-                    "missing IApplicationThread");
-        }
     }
 
     /**
@@ -898,9 +1009,10 @@
             mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
                     app, OOM_ADJ_REASON_FINISH_RECEIVER);
             try {
-                thread.scheduleRegisteredReceiver(r.resultTo, r.intent,
+                thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
+                        r.resultTo, r.intent,
                         r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky,
-                        r.userId, app.mState.getReportedProcState());
+                        r.userId, app.mState.getReportedProcState()));
             } catch (RemoteException e) {
                 final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e;
                 logw(msg);
@@ -929,7 +1041,7 @@
     }
 
     private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
-        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
+        finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
                 "deliveryTimeoutHardLocked");
     }
 
@@ -962,7 +1074,7 @@
             }
         }
 
-        return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+        return finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
     }
 
     /**
@@ -978,7 +1090,10 @@
         return queue.isRunnable() && queue.isProcessWarm() && !shouldRetire;
     }
 
-    private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
+    /**
+     * Terminate all active broadcasts on the queue.
+     */
+    private boolean finishReceiverActiveLocked(@NonNull BroadcastProcessQueue queue,
             @DeliveryState int deliveryState, @NonNull String reason) {
         if (!queue.isActive()) {
             logw("Ignoring finish; no active broadcast for " + queue);
@@ -1004,16 +1119,25 @@
 
         setDeliveryState(queue, app, r, index, receiver, deliveryState, reason);
 
+        final boolean early = r != queue.getActive() || index != queue.getActiveIndex();
+
         if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
             r.anrCount++;
             if (app != null && !app.isDebugging()) {
                 mService.appNotResponding(queue.app, TimeoutRecord.forBroadcastReceiver(r.intent));
             }
-        } else {
+        } else if (!early) {
             mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
             mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
         }
 
+        if (early) {
+            // This is an early receiver that was transmitted as part of a group.  The delivery
+            // state has been updated but don't make any further decisions.
+            traceEnd(cookie);
+            return false;
+        }
+
         final boolean res = shouldContinueScheduling(queue);
         if (res) {
             // We're on a roll; move onto the next broadcast for this process
@@ -1228,12 +1352,16 @@
         return didSomething;
     }
 
-    private void forEachQueue(@NonNull Consumer<BroadcastProcessQueue> consumer) {
-        for (int i = 0; i < mProcessQueues.size(); ++i) {
+    private void forEachMatchingQueue(
+            @NonNull Predicate<BroadcastProcessQueue> queuePredicate,
+            @NonNull Consumer<BroadcastProcessQueue> queueConsumer) {
+        for (int i = 0; i < mProcessQueues.size(); i++) {
             BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
             while (leaf != null) {
-                consumer.accept(leaf);
-                updateRunnableList(leaf);
+                if (queuePredicate.test(leaf)) {
+                    queueConsumer.accept(leaf);
+                    updateRunnableList(leaf);
+                }
                 leaf = leaf.processNameNext;
             }
         }
@@ -1297,7 +1425,8 @@
         final CountDownLatch latch = new CountDownLatch(1);
         synchronized (mService) {
             mWaitingFor.add(Pair.create(condition, latch));
-            forEachQueue(q -> q.setPrioritizeEarliest(true));
+            forEachMatchingQueue(QUEUE_PREDICATE_ANY,
+                    (q) -> q.setPrioritizeEarliest(true));
         }
         enqueueUpdateRunningList();
         try {
@@ -1307,13 +1436,24 @@
         } finally {
             synchronized (mService) {
                 if (mWaitingFor.isEmpty()) {
-                    forEachQueue(q -> q.setPrioritizeEarliest(false));
+                    forEachMatchingQueue(QUEUE_PREDICATE_ANY,
+                            (q) -> q.setPrioritizeEarliest(false));
                 }
             }
         }
     }
 
     @Override
+    public void forceDelayBroadcastDelivery(@NonNull String targetPackage,
+            long delayedDurationMs) {
+        synchronized (mService) {
+            forEachMatchingQueue(
+                    (q) -> targetPackage.equals(q.getPackageName()),
+                    (q) -> q.forceDelayBroadcastDelivery(delayedDurationMs));
+        }
+    }
+
+    @Override
     public String describeStateLocked() {
         return getRunningSize() + " running";
     }
@@ -1694,6 +1834,17 @@
         ipw.decreaseIndent();
         ipw.println();
 
+        ipw.println("Batch statistics:");
+        ipw.increaseIndent();
+        {
+            final var stats = mReceiverBatch.getStatistics();
+            ipw.println("Finished         " + Arrays.toString(stats.finish));
+            ipw.println("DispatchedLocal  " + Arrays.toString(stats.local));
+            ipw.println("DispatchedRemote " + Arrays.toString(stats.remote));
+        }
+        ipw.decreaseIndent();
+        ipw.println();
+
         if (dumpConstants) {
             mConstants.dump(ipw);
         }
diff --git a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
new file mode 100644
index 0000000..a826458
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2022 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.am;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ReceiverInfo;
+import android.content.Intent;
+import android.content.IIntentReceiver;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.os.Bundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A batch of receiver instructions. This includes a list of finish requests and a list of
+ * receivers.  The instructions are for a single queue.  It is constructed and consumed in a single
+ * call to {@link BroadcastQueueModernImpl#scheduleReceiverWarmLocked}.  The list size is bounded by
+ * {@link BroadcastConstants#MAX_BROADCAST_BATCH_SIZE}.  Because this class is ephemeral and its use
+ * is bounded, it is pre-allocated to avoid allocating new objects every time it is used.
+ *
+ * The {@link #single} methods support the use of this class in {@link BroadcastQueueImpl}.  These
+ * methods simplify the use of {@link IApplicationThread#scheduleReceiverList} as a replacement
+ * for scheduleReceiver and scheduleRegisteredReceiver.
+ *
+ * This class is designed to be allocated once and constantly reused (call {@link #reset} between
+ * uses).  Objects needed by the instance are kept in a pool - all object allocation occurs when
+ * the instance is created, and no allocation occurs thereafter.  However, if the variable
+ * mDeepReceiverCopy is set true (it is false by default) then the method {@link #receivers} always
+ * returns newly allocated objects.  This is required for mock testing.
+ *
+ * This class is not thread-safe.  Instances must be protected by the caller.
+ * @hide
+ */
+final class BroadcastReceiverBatch {
+
+    /**
+     * If this is true then receivers() returns a deep copy of the ReceiveInfo array.  If this is
+     * false, receivers() returns a reference to the array.  A deep copy is needed only for the
+     * broadcast queue mocking tests.
+     */
+    @VisibleForTesting
+    boolean mDeepReceiverCopy = false;
+
+    /**
+     * A private pool implementation this class.
+     */
+    private static class Pool<T> {
+        final int size;
+        final ArrayList<T> pool;
+        int next;
+        Pool(int n, @NonNull Class<T> c) {
+            size = n;
+            pool = new ArrayList<>(size);
+            try {
+                for (int i = 0; i < size; i++) {
+                    pool.add(c.getDeclaredConstructor().newInstance());
+                }
+            } catch (Exception e) {
+                // This class is only used locally.  Turn any exceptions into something fatal.
+                throw new RuntimeException(e);
+            }
+        }
+        T next() {
+            return pool.get(next++);
+        }
+        void reset() {
+            next = 0;
+        }
+    }
+
+    /**
+     * The information needed to finish off a receiver.  This is valid only in the context of
+     * a queue.
+     */
+    static class FinishInfo {
+        BroadcastRecord r = null;
+        int index = 0;
+        int deliveryState = 0;
+        String reason = null;
+        FinishInfo set(@Nullable BroadcastRecord r, int index,
+                int deliveryState, @Nullable String reason) {
+            this.r = r;
+            this.index = index;
+            this.deliveryState = deliveryState;
+            this.reason = reason;
+            return this;
+        }
+    }
+
+    /**
+     * The information needed to recreate a receiver info.  The broadcast record can be null if the
+     * caller does not expect to need it later.
+     */
+    static class ReceiverCookie {
+        BroadcastRecord r = null;
+        int index = 0;
+        ReceiverCookie set(@Nullable BroadcastRecord r, int index) {
+            this.r = r;
+            this.index = index;
+            return this;
+        }
+    }
+
+    // The object pools.
+    final int mSize;
+    private final Pool<ReceiverInfo> receiverPool;
+    private final Pool<FinishInfo> finishPool;
+    private final Pool<ReceiverCookie> cookiePool;
+
+    // The accumulated data.  The receivers should be an ArrayList to be directly compatible
+    // with scheduleReceiverList().  The receivers array is not final because a new array must
+    // be created for every new call to scheduleReceiverList().
+    private final ArrayList<ReceiverInfo> mReceivers;
+    private final ArrayList<ReceiverCookie> mCookies;
+    private final ArrayList<FinishInfo> mFinished;
+    // The list of finish records to complete if the binder succeeds or fails.
+    private final ArrayList<FinishInfo> mSuccess;
+
+    BroadcastReceiverBatch(int size) {
+        mSize = size;
+        mReceivers = new ArrayList<>(mSize);
+        mCookies = new ArrayList<>(mSize);
+        mFinished = new ArrayList<>(mSize);
+        mSuccess = new ArrayList<>(mSize);
+
+        receiverPool = new Pool<>(mSize, ReceiverInfo.class);
+        finishPool = new Pool<>(mSize, FinishInfo.class);
+        cookiePool = new Pool<>(mSize, ReceiverCookie.class);
+        mStats = new Statistics(mSize);
+        reset();
+    }
+
+    void reset() {
+        mReceivers.clear();
+        mCookies.clear();
+        mFinished.clear();
+        mSuccess.clear();
+
+        receiverPool.reset();
+        finishPool.reset();
+        cookiePool.reset();
+    }
+
+    void finish(@Nullable BroadcastRecord r, int index,
+            int deliveryState, @Nullable String reason) {
+        mFinished.add(finishPool.next().set(r, index, deliveryState, reason));
+    }
+    void success(@Nullable BroadcastRecord r, int index,
+            int deliveryState, @Nullable String reason) {
+        mSuccess.add(finishPool.next().set(r, index, deliveryState, reason));
+    }
+    // Add a ReceiverInfo for a registered receiver.
+    void schedule(@Nullable IIntentReceiver receiver, Intent intent,
+            int resultCode, @Nullable String data, @Nullable Bundle extras, boolean ordered,
+            boolean sticky, int sendingUser, int processState,
+            @Nullable BroadcastRecord r, int index) {
+        ReceiverInfo ri = new ReceiverInfo();
+        ri.intent = intent;
+        ri.data = data;
+        ri.extras = extras;
+        ri.sendingUser = sendingUser;
+        ri.processState = processState;
+        ri.resultCode = resultCode;
+        ri.registered = true;
+        ri.receiver = receiver;
+        ri.ordered = ordered;
+        ri.sticky = sticky;
+        mReceivers.add(ri);
+        mCookies.add(cookiePool.next().set(r, index));
+    }
+    // Add a ReceiverInfo for a manifest receiver.
+    void schedule(@Nullable Intent intent, @Nullable ActivityInfo activityInfo,
+            @Nullable CompatibilityInfo compatInfo, int resultCode, @Nullable String data,
+            @Nullable Bundle extras, boolean sync, int sendingUser, int processState,
+            @Nullable BroadcastRecord r, int index) {
+        ReceiverInfo ri = new ReceiverInfo();
+        ri.intent = intent;
+        ri.data = data;
+        ri.extras = extras;
+        ri.sendingUser = sendingUser;
+        ri.processState = processState;
+        ri.resultCode = resultCode;
+        ri.registered = false;
+        ri.activityInfo = activityInfo;
+        ri.compatInfo = compatInfo;
+        ri.sync = sync;
+        mReceivers.add(ri);
+        mCookies.add(cookiePool.next().set(r, index));
+    }
+
+    /**
+     * Two convenience functions for dispatching a single receiver.  The functions start with a
+     * reset.  Then they create the ReceiverInfo array and return it.  Statistics are not
+     * collected.
+     */
+    ArrayList<ReceiverInfo> registeredReceiver(@Nullable IIntentReceiver receiver,
+            @Nullable Intent intent, int resultCode, @Nullable String data,
+            @Nullable Bundle extras, boolean ordered, boolean sticky,
+            int sendingUser, int processState) {
+        reset();
+        schedule(receiver, intent, resultCode, data, extras, ordered, sticky,
+                sendingUser, processState, null, 0);
+        return receivers();
+    }
+
+    ArrayList<ReceiverInfo> manifestReceiver(@Nullable Intent intent,
+            @Nullable ActivityInfo activityInfo, @Nullable CompatibilityInfo compatInfo,
+            int resultCode, @Nullable String data, @Nullable Bundle extras, boolean sync,
+            int sendingUser, int processState) {
+        reset();
+        schedule(intent, activityInfo, compatInfo, resultCode, data, extras, sync,
+                sendingUser, processState, null, 0);
+        return receivers();
+    }
+
+    // Return true if the batch is full.  Adding any more entries will throw an exception.
+    boolean isFull() {
+        return (mFinished.size() + mReceivers.size()) >= mSize;
+    }
+    int finishCount() {
+        return mFinished.size() + mSuccess.size();
+    }
+    int receiverCount() {
+        return mReceivers.size();
+    }
+
+    /**
+     * Create a deep copy of the receiver list.  This is only for testing which is confused when
+     * objects are reused.
+     */
+    private ArrayList<ReceiverInfo> copyReceiverInfo() {
+        ArrayList<ReceiverInfo> copy = new ArrayList<>();
+        for (int i = 0; i < mReceivers.size(); i++) {
+            final ReceiverInfo r = mReceivers.get(i);
+            final ReceiverInfo n = new ReceiverInfo();
+            n.intent = r.intent;
+            n.data = r.data;
+            n.extras = r.extras;
+            n.sendingUser = r.sendingUser;
+            n.processState = r.processState;
+            n.resultCode = r.resultCode;
+            n.registered = r.registered;
+            n.receiver = r.receiver;
+            n.ordered = r.ordered;
+            n.sticky = r.sticky;
+            n.activityInfo = r.activityInfo;
+            n.compatInfo = r.compatInfo;
+            n.sync = r.sync;
+            copy.add(n);
+        }
+        return copy;
+    }
+
+    /**
+     * Accessors for the accumulated instructions.  The important accessor is receivers(), since
+     * it can be modified to return a deep copy of the mReceivers array.
+     */
+    @NonNull
+    ArrayList<ReceiverInfo> receivers() {
+        if (!mDeepReceiverCopy) {
+            return mReceivers;
+        } else {
+            return copyReceiverInfo();
+        }
+    }
+    @NonNull
+    ArrayList<ReceiverCookie> cookies() {
+        return mCookies;
+    }
+    @NonNull
+    ArrayList<FinishInfo> finished() {
+        return mFinished;
+    }
+    @NonNull
+    ArrayList<FinishInfo> success() {
+        return mSuccess;
+    }
+
+    /**
+     * A simple POD for statistics.  The parameter is the size of the BroadcastReceiverBatch.
+     */
+    static class Statistics {
+        final int[] finish;
+        final int[] local;
+        final int[] remote;
+        Statistics(int size) {
+            finish = new int[size+1];
+            local = new int[size+1];
+            remote = new int[size+1];
+        }
+    }
+
+    private final Statistics mStats;
+
+    /**
+     * A unique counter that identifies individual transmission groups.  This is only used for
+     * debugging.  It is used to determine which receivers were sent in the same batch, and in
+     * which order.  This is static to distinguish between batches across all queues in the
+     * system.
+     */
+    private static final AtomicInteger sTransmitGroup = new AtomicInteger(0);
+
+    /**
+     * Record statistics for this batch of instructions.  This updates the local statistics and it
+     * updates the transmitGroup and transmitOrder fields of the BroadcastRecords being
+     * dispatched.
+     */
+    void recordBatch(boolean local) {
+        final int group = sTransmitGroup.addAndGet(1);
+        for (int i = 0; i < cookies().size(); i++) {
+            final var cookie = cookies().get(i);
+            cookie.r.transmitGroup[cookie.index] = group;
+            cookie.r.transmitOrder[cookie.index] = i;
+        }
+        mStats.finish[finishCount()]++;
+        if (local) {
+            mStats.local[receiverCount()]++;
+        } else {
+            mStats.remote[receiverCount()]++;
+        }
+    }
+
+    @NonNull
+    Statistics getStatistics() {
+        return mStats;
+    }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 100b2db..24cf3d2 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -113,6 +113,8 @@
     @UptimeMillisLong       long finishTime;         // when broadcast finished
     final @UptimeMillisLong long[] scheduledTime;    // when each receiver was scheduled
     final @UptimeMillisLong long[] terminalTime;     // when each receiver was terminal
+    final                   int[] transmitGroup;     // the batch group for each receiver
+    final                   int[] transmitOrder;     // the position of the receiver in the group
     final boolean timeoutExempt;  // true if this broadcast is not subject to receiver timeouts
     int resultCode;         // current result code value.
     @Nullable String resultData;      // current result data value.
@@ -380,6 +382,8 @@
         blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
         scheduledTime = new long[delivery.length];
         terminalTime = new long[delivery.length];
+        transmitGroup = new int[delivery.length];
+        transmitOrder = new int[delivery.length];
         resultToApp = _resultToApp;
         resultTo = _resultTo;
         resultCode = _resultCode;
@@ -433,6 +437,8 @@
         blockedUntilTerminalCount = from.blockedUntilTerminalCount;
         scheduledTime = from.scheduledTime;
         terminalTime = from.terminalTime;
+        transmitGroup = from.transmitGroup;
+        transmitOrder = from.transmitOrder;
         resultToApp = from.resultToApp;
         resultTo = from.resultTo;
         enqueueTime = from.enqueueTime;
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 6478757..dbd58eb 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1966,37 +1966,25 @@
                 }
             }
         }
+
         if (state.getCachedIsPreviousProcess() && state.getCachedHasActivities()) {
-            // This was the previous process that showed UI to the user.  We want to
-            // try to keep it around more aggressively, to give a good experience
-            // around switching between two apps. However, we don't want to keep the
-            // process in this privileged state indefinitely. Eventually, allow the
-            // app to be demoted to cached.
-            if ((state.getSetProcState() == PROCESS_STATE_LAST_ACTIVITY
-                    && (state.getLastStateTime() + mConstants.MAX_PREVIOUS_TIME) < now)) {
-                procState = PROCESS_STATE_LAST_ACTIVITY;
+            if (adj > PREVIOUS_APP_ADJ) {
+                // This was the previous process that showed UI to the user.
+                // We want to try to keep it around more aggressively, to give
+                // a good experience around switching between two apps.
+                adj = PREVIOUS_APP_ADJ;
                 schedGroup = SCHED_GROUP_BACKGROUND;
-                state.setAdjType("previous-expired");
-                adj = CACHED_APP_MIN_ADJ;
+                state.setCached(false);
+                state.setAdjType("previous");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Expire prev adj: " + app);
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
                 }
-            } else {
-                if (adj > PREVIOUS_APP_ADJ) {
-                    adj = PREVIOUS_APP_ADJ;
-                    schedGroup = SCHED_GROUP_BACKGROUND;
-                    state.setCached(false);
-                    state.setAdjType("previous");
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
-                    }
-                }
-                if (procState > PROCESS_STATE_LAST_ACTIVITY) {
-                    procState = PROCESS_STATE_LAST_ACTIVITY;
-                    state.setAdjType("previous");
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
-                    }
+            }
+            if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+                procState = PROCESS_STATE_LAST_ACTIVITY;
+                state.setAdjType("previous");
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 4706c268..33d7f9d 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1016,6 +1016,11 @@
         return mWindowProcessController.hasRecentTasks();
     }
 
+    @GuardedBy("mService")
+    public ApplicationInfo getApplicationInfo() {
+        return info;
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     boolean onCleanupApplicationRecordLSP(ProcessStatsService processStats, boolean allowRestart,
             boolean unlinkDeath) {
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 2a69363..4e1d1ca 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -97,6 +97,15 @@
         { "include-filter": "com.android.server.am.BroadcastQueueTest" },
         { "include-filter": "com.android.server.am.BroadcastQueueModernImplTest" }
       ]
+    },
+    {
+      "file_patterns": ["Broadcast"],
+      "name": "CtsBroadcastTestCases",
+      "options": [
+        { "exclude-annotation": "androidx.test.filters.LargeTest" },
+        { "exclude-annotation": "androidx.test.filters.FlakyTest" },
+        { "exclude-annotation": "org.junit.Ignore" }
+      ]
     }
   ],
   "presubmit-large": [
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 280256f..e5123ef 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1571,12 +1571,6 @@
         checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay",
                 MANAGE_USERS, INTERACT_ACROSS_USERS);
 
-        // DEFAULT_DISPLAY is used for the current foreground user only
-        // TODO(b/245939659): might need to move this check to UserVisibilityMediator to support
-        // passenger-only screens
-        Preconditions.checkArgument(displayId != Display.DEFAULT_DISPLAY,
-                "Cannot use DEFAULT_DISPLAY");
-
         try {
             return startUserNoChecks(userId, displayId, USER_START_MODE_BACKGROUND_VISIBLE,
                     /* unlockListener= */ null);
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index 587fb04..ac25f4e 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -20,7 +20,7 @@
 import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
 import static android.app.AppOpsManager.opRestrictsRead;
 
-import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
+import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -85,8 +85,8 @@
 
 
     AppOpsCheckingServiceImpl(PersistenceScheduler persistenceScheduler,
-                              @NonNull Object lock, Handler handler, Context context,
-                              SparseArray<int[]> switchedOps) {
+            @NonNull Object lock, Handler handler, Context context,
+            SparseArray<int[]> switchedOps) {
         this.mPersistenceScheduler = persistenceScheduler;
         this.mLock = lock;
         this.mHandler = handler;
@@ -218,7 +218,7 @@
     }
 
     @Override
-    public boolean arePackageModesDefault(@NonNull String packageMode, @UserIdInt int userId) {
+    public boolean arePackageModesDefault(String packageMode, @UserIdInt int userId) {
         synchronized (mLock) {
             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
             if (packageModes == null) {
@@ -490,16 +490,15 @@
     }
 
     @Override
-    public SparseBooleanArray evalForegroundUidOps(int uid,
-            @Nullable SparseBooleanArray foregroundOps) {
+    public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
         synchronized (mLock) {
             return evalForegroundOps(mUidModes.get(uid), foregroundOps);
         }
     }
 
     @Override
-    public SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
-            @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId) {
+    public SparseBooleanArray evalForegroundPackageOps(String packageName,
+            SparseBooleanArray foregroundOps, @UserIdInt int userId) {
         synchronized (mLock) {
             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
             return evalForegroundOps(packageModes == null ? null : packageModes.get(packageName),
@@ -538,8 +537,8 @@
     }
 
     @Override
-    public boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
-            @NonNull PrintWriter printWriter) {
+    public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
+            PrintWriter printWriter) {
         boolean needSep = false;
         if (mOpModeWatchers.size() > 0) {
             boolean printedHeader = false;
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index ef3e368..d8d0d48 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -103,7 +103,7 @@
      * @param packageName package name.
      * @param userId user id associated with the package.
      */
-    boolean arePackageModesDefault(@NonNull String packageName, @UserIdInt int userId);
+    boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
 
     /**
      * Stop tracking app-op modes for all uid and packages.
@@ -184,7 +184,7 @@
      * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
      * @return  foregroundOps.
      */
-    SparseBooleanArray evalForegroundUidOps(int uid, @Nullable SparseBooleanArray foregroundOps);
+    SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
 
     /**
      * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
@@ -194,8 +194,8 @@
      * @param userId user id associated with the package.
      * @return foregroundOps.
      */
-    SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
-            @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId);
+    SparseBooleanArray evalForegroundPackageOps(String packageName,
+            SparseBooleanArray foregroundOps, @UserIdInt int userId);
 
     /**
      * Dump op mode and package mode listeners and their details.
@@ -205,6 +205,5 @@
      * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
      * @param printWriter writer to dump to.
      */
-    boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
-            @NonNull PrintWriter printWriter);
+    boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
deleted file mode 100644
index 4436002..0000000
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2022 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.appop;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager;
-import android.os.Trace;
-import android.util.ArraySet;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-
-import java.io.PrintWriter;
-
-/**
- * Surrounds all AppOpsCheckingServiceInterface method calls with Trace.traceBegin and
- * Trace.traceEnd. These traces are used for performance testing.
- */
-public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServiceInterface {
-    private static final long TRACE_TAG = Trace.TRACE_TAG_SYSTEM_SERVER;
-    private final AppOpsCheckingServiceInterface mService;
-
-    AppOpsCheckingServiceTracingDecorator(
-            @NonNull AppOpsCheckingServiceInterface appOpsCheckingServiceInterface) {
-        mService = appOpsCheckingServiceInterface;
-    }
-
-    @Override
-    public SparseIntArray getNonDefaultUidModes(int uid) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getNonDefaultUidModes");
-        try {
-            return mService.getNonDefaultUidModes(uid);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public int getUidMode(int uid, int op) {
-        Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getUidMode");
-        try {
-            return mService.getUidMode(uid, op);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public boolean setUidMode(int uid, int op, @AppOpsManager.Mode int mode) {
-        Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#setUidMode");
-        try {
-            return mService.setUidMode(uid, op, mode);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getPackageMode");
-        try {
-            return mService.getPackageMode(packageName, op, userId);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public void setPackageMode(@NonNull String packageName, int op, @AppOpsManager.Mode int mode,
-            @UserIdInt int userId) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#setPackageMode");
-        try {
-            mService.setPackageMode(packageName, op, mode, userId);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public boolean removePackage(@NonNull String packageName, @UserIdInt int userId) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removePackage");
-        try {
-            return mService.removePackage(packageName, userId);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public void removeUid(int uid) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removeUid");
-        try {
-            mService.removeUid(uid);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public boolean areUidModesDefault(int uid) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#areUidModesDefault");
-        try {
-            return mService.areUidModesDefault(uid);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public boolean arePackageModesDefault(String packageName, @UserIdInt int userId) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#arePackageModesDefault");
-        try {
-            return mService.arePackageModesDefault(packageName, userId);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public void clearAllModes() {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#clearAllModes");
-        try {
-            mService.clearAllModes();
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            int op) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#startWatchingOpModeChanged");
-        try {
-            mService.startWatchingOpModeChanged(changedListener, op);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            @NonNull String packageName) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#startWatchingPackageModeChanged");
-        try {
-            mService.startWatchingPackageModeChanged(changedListener, packageName);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removeListener");
-        try {
-            mService.removeListener(changedListener);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getOpModeChangedListeners");
-        try {
-            return mService.getOpModeChangedListeners(op);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
-            @NonNull String packageName) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getPackageModeChangedListeners");
-        try {
-            return mService.getPackageModeChangedListeners(packageName);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public void notifyWatchersOfChange(int op, int uid) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyWatchersOfChange");
-        try {
-            mService.notifyWatchersOfChange(op, uid);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
-            @Nullable String packageName) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyOpChanged");
-        try {
-            mService.notifyOpChanged(changedListener, op, uid, packageName);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
-            @Nullable OnOpModeChangedListener callbackToIgnore) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyOpChangedForAllPkgsInUid");
-        try {
-            mService.notifyOpChangedForAllPkgsInUid(op, uid, onlyForeground, callbackToIgnore);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#evalForegroundUidOps");
-        try {
-            return mService.evalForegroundUidOps(uid, foregroundOps);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, @UserIdInt int userId) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#evalForegroundPackageOps");
-        try {
-            return mService.evalForegroundPackageOps(packageName, foregroundOps, userId);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
-            PrintWriter printWriter) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#dumpListeners");
-        try {
-            return mService.dumpListeners(dumpOp, dumpUid, dumpPackage, printWriter);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
index af5b07e..f51200f2 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -42,7 +42,7 @@
 
     private Context mContext;
     private Handler mHandler;
-    private AppOpsCheckingServiceInterface mAppOpsServiceInterface;
+    private AppOpsCheckingServiceInterface mAppOpsCheckingServiceInterface;
 
     // Map from (Object token) to (int code) to (boolean restricted)
     private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>();
@@ -56,10 +56,10 @@
             mUserRestrictionExcludedPackageTags = new ArrayMap<>();
 
     public AppOpsRestrictionsImpl(Context context, Handler handler,
-            AppOpsCheckingServiceInterface appOpsServiceInterface) {
+            AppOpsCheckingServiceInterface appOpsCheckingServiceInterface) {
         mContext = context;
         mHandler = handler;
-        mAppOpsServiceInterface = appOpsServiceInterface;
+        mAppOpsCheckingServiceInterface = appOpsCheckingServiceInterface;
     }
 
     @Override
@@ -219,7 +219,7 @@
         int restrictedCodesSize = allUserRestrictedCodes.size();
         for (int j = 0; j < restrictedCodesSize; j++) {
             int code = allUserRestrictedCodes.keyAt(j);
-            mHandler.post(() -> mAppOpsServiceInterface.notifyWatchersOfChange(code, UID_ANY));
+            mHandler.post(() -> mAppOpsCheckingServiceInterface.notifyWatchersOfChange(code, UID_ANY));
         }
     }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 39338c6..9345422 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -18,22 +18,56 @@
 
 import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
 import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
+import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
+import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
+import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
+import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_FLAG_SELF;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
 import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
+import static android.app.AppOpsManager.OP_VIBRATE;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
+import static android.app.AppOpsManager.OpEventProxyInfo;
+import static android.app.AppOpsManager.RestrictionBypass;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS;
+import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
 import static android.app.AppOpsManager._NUM_OP;
+import static android.app.AppOpsManager.extractFlagsFromKey;
+import static android.app.AppOpsManager.extractUidStateFromKey;
+import static android.app.AppOpsManager.modeToName;
+import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
 import static android.app.AppOpsManager.opRestrictsRead;
+import static android.app.AppOpsManager.opToName;
 import static android.app.AppOpsManager.opToPublicName;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
 
+import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -42,16 +76,21 @@
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributedOpEntry;
 import android.app.AppOpsManager.AttributionFlags;
 import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.Mode;
+import android.app.AppOpsManager.OpEntry;
 import android.app.AppOpsManager.OpFlags;
 import android.app.AppOpsManagerInternal;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
 import android.app.AsyncNotedAppOp;
 import android.app.RuntimeAppOpAccessMessage;
 import android.app.SyncNotedAppOp;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -59,11 +98,15 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PermissionInfo;
+import android.database.ContentObserver;
 import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.PackageTagsList;
 import android.os.Process;
@@ -74,14 +117,22 @@
 import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
+import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.storage.StorageManagerInternal;
+import android.permission.PermissionManager;
+import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.KeyValueListParser;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.Immutable;
@@ -93,37 +144,61 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IAppOpsStartedCallback;
 import com.android.internal.app.MessageSamplingConfig;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.os.Clock;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
+import com.android.server.LockGuard;
+import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemServiceManager;
 import com.android.server.pm.PackageList;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedAttribution;
 import com.android.server.policy.AppOpsPolicy;
 
+import dalvik.annotation.optimization.NeverCompile;
+
+import libcore.util.EmptyArray;
+
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Scanner;
+import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.function.Consumer;
 
-/**
- * The system service component to {@link AppOpsManager}.
- */
-public class AppOpsService extends IAppOpsService.Stub {
-
-    private final AppOpsServiceInterface mAppOpsService;
-
+public class AppOpsService extends IAppOpsService.Stub implements PersistenceScheduler {
     static final String TAG = "AppOps";
     static final boolean DEBUG = false;
 
@@ -132,19 +207,74 @@
      */
     private final ArraySet<NoteOpTrace> mNoteOpCallerStacktraces = new ArraySet<>();
 
+    /**
+     * Sentinel integer version to denote that there was no appops.xml found on boot.
+     * This will happen when a device boots with no existing userdata.
+     */
+    private static final int NO_FILE_VERSION = -2;
+
+    /**
+     * Sentinel integer version to denote that there was no version in the appops.xml found on boot.
+     * This means the file is coming from a build before versioning was added.
+     */
+    private static final int NO_VERSION = -1;
+
+    /** Increment by one every time and add the corresponding upgrade logic in
+     *  {@link #upgradeLocked(int)} below. The first version was 1 */
+    static final int CURRENT_VERSION = 2;
+
+    /**
+     * This stores the version of appops.xml seen at boot. If this is smaller than
+     * {@link #CURRENT_VERSION}, then we will run {@link #upgradeLocked(int)} on startup.
+     */
+    private int mVersionAtBoot = NO_FILE_VERSION;
+
+    // Write at most every 30 minutes.
+    static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
+
     // Constant meaning that any UID should be matched when dispatching callbacks
     private static final int UID_ANY = -2;
 
-    private static final int MAX_UNFORWARDED_OPS = 10;
+    private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
+            OP_PLAY_AUDIO,
+            OP_RECORD_AUDIO,
+            OP_CAMERA,
+            OP_VIBRATE,
+    };
 
+    private static final int MAX_UNFORWARDED_OPS = 10;
+    private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
     private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
 
     final Context mContext;
+    final AtomicFile mFile;
     private final @Nullable File mNoteOpCallerStacktracesFile;
     final Handler mHandler;
 
+    /**
+     * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
+     * objects
+     */
+    @GuardedBy("this")
+    final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
+            new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
+
+    /**
+     * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
+     * new objects
+     */
+    @GuardedBy("this")
+    final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
+            new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
+                    MAX_UNUSED_POOLED_OBJECTS);
+
     private final AppOpsManagerInternalImpl mAppOpsManagerInternal
             = new AppOpsManagerInternalImpl();
+    @Nullable private final DevicePolicyManagerInternal dpmi =
+            LocalServices.getService(DevicePolicyManagerInternal.class);
+
+    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
 
     /**
      * Registered callbacks, called from {@link #collectAsyncNotedOp}.
@@ -171,9 +301,54 @@
 
     boolean mWriteNoteOpsScheduled;
 
+    boolean mWriteScheduled;
+    boolean mFastWriteScheduled;
+    final Runnable mWriteRunner = new Runnable() {
+        public void run() {
+            synchronized (AppOpsService.this) {
+                mWriteScheduled = false;
+                mFastWriteScheduled = false;
+                AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+                    @Override protected Void doInBackground(Void... params) {
+                        writeState();
+                        return null;
+                    }
+                };
+                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
+            }
+        }
+    };
+
+    @GuardedBy("this")
+    @VisibleForTesting
+    final SparseArray<UidState> mUidStates = new SparseArray<>();
+
+    volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+
+    /*
+     * These are app op restrictions imposed per user from various parties.
+     */
+    private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
+            new ArrayMap<>();
+
+    /*
+     * These are app op restrictions imposed globally from various parties within the system.
+     */
+    private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
+            new ArrayMap<>();
+
+    SparseIntArray mProfileOwners;
+
     private volatile CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher =
             new CheckOpsDelegateDispatcher(/*policy*/ null, /*delegate*/ null);
 
+    /**
+      * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
+      * changed
+      */
+    private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
+
+    private ActivityManagerInternal mActivityManagerInternal;
 
     /** Package sampled for message collection in the current session */
     @GuardedBy("this")
@@ -207,8 +382,546 @@
     /** Package Manager internal. Access via {@link #getPackageManagerInternal()} */
     private @Nullable PackageManagerInternal mPackageManagerInternal;
 
+    /** Interface for app-op modes.*/
+    @VisibleForTesting
+    AppOpsCheckingServiceInterface mAppOpsCheckingService;
+
+    /** Interface for app-op restrictions.*/
+    @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
+
+    private AppOpsUidStateTracker mUidStateTracker;
+
+    /** Hands the definition of foreground and uid states */
+    @GuardedBy("this")
+    public AppOpsUidStateTracker getUidStateTracker() {
+        if (mUidStateTracker == null) {
+            mUidStateTracker = new AppOpsUidStateTrackerImpl(
+                    LocalServices.getService(ActivityManagerInternal.class),
+                    mHandler,
+                    r -> {
+                        synchronized (AppOpsService.this) {
+                            r.run();
+                        }
+                    },
+                    Clock.SYSTEM_CLOCK, mConstants);
+
+            mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
+                    this::onUidStateChanged);
+        }
+        return mUidStateTracker;
+    }
+
+    /**
+     * All times are in milliseconds. These constants are kept synchronized with the system
+     * global Settings. Any access to this class or its fields should be done while
+     * holding the AppOpsService lock.
+     */
+    final class Constants extends ContentObserver {
+
+        /**
+         * How long we want for a drop in uid state from top to settle before applying it.
+         * @see Settings.Global#APP_OPS_CONSTANTS
+         * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
+         */
+        public long TOP_STATE_SETTLE_TIME;
+
+        /**
+         * How long we want for a drop in uid state from foreground to settle before applying it.
+         * @see Settings.Global#APP_OPS_CONSTANTS
+         * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
+         */
+        public long FG_SERVICE_STATE_SETTLE_TIME;
+
+        /**
+         * How long we want for a drop in uid state from background to settle before applying it.
+         * @see Settings.Global#APP_OPS_CONSTANTS
+         * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
+         */
+        public long BG_STATE_SETTLE_TIME;
+
+        private final KeyValueListParser mParser = new KeyValueListParser(',');
+        private ContentResolver mResolver;
+
+        public Constants(Handler handler) {
+            super(handler);
+            updateConstants();
+        }
+
+        public void startMonitoring(ContentResolver resolver) {
+            mResolver = resolver;
+            mResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
+                    false, this);
+            updateConstants();
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            updateConstants();
+        }
+
+        private void updateConstants() {
+            String value = mResolver != null ? Settings.Global.getString(mResolver,
+                    Settings.Global.APP_OPS_CONSTANTS) : "";
+
+            synchronized (AppOpsService.this) {
+                try {
+                    mParser.setString(value);
+                } catch (IllegalArgumentException e) {
+                    // Failed to parse the settings string, log this and move on
+                    // with defaults.
+                    Slog.e(TAG, "Bad app ops settings", e);
+                }
+                TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
+                        KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
+                FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
+                        KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
+                BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
+                        KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
+            }
+        }
+
+        void dump(PrintWriter pw) {
+            pw.println("  Settings:");
+
+            pw.print("    "); pw.print(KEY_TOP_STATE_SETTLE_TIME); pw.print("=");
+            TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
+            pw.println();
+            pw.print("    "); pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME); pw.print("=");
+            TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
+            pw.println();
+            pw.print("    "); pw.print(KEY_BG_STATE_SETTLE_TIME); pw.print("=");
+            TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
+            pw.println();
+        }
+    }
+
+    @VisibleForTesting
+    final Constants mConstants;
+
+    @VisibleForTesting
+    final class UidState {
+        public final int uid;
+
+        public ArrayMap<String, Ops> pkgOps;
+
+        // true indicates there is an interested observer, false there isn't but it has such an op
+        //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
+        public SparseBooleanArray foregroundOps;
+        public boolean hasForegroundWatchers;
+
+        public UidState(int uid) {
+            this.uid = uid;
+        }
+
+        public void clear() {
+            mAppOpsCheckingService.removeUid(uid);
+            if (pkgOps != null) {
+                for (String packageName : pkgOps.keySet()) {
+                    mAppOpsCheckingService.removePackage(packageName, UserHandle.getUserId(uid));
+                }
+            }
+            pkgOps = null;
+        }
+
+        public boolean isDefault() {
+            boolean areAllPackageModesDefault = true;
+            if (pkgOps != null) {
+                for (String packageName : pkgOps.keySet()) {
+                    if (!mAppOpsCheckingService.arePackageModesDefault(packageName,
+                            UserHandle.getUserId(uid))) {
+                        areAllPackageModesDefault = false;
+                        break;
+                    }
+                }
+            }
+            return (pkgOps == null || pkgOps.isEmpty())
+                    && mAppOpsCheckingService.areUidModesDefault(uid)
+                    && areAllPackageModesDefault;
+        }
+
+        // Functions for uid mode access and manipulation.
+        public SparseIntArray getNonDefaultUidModes() {
+            return mAppOpsCheckingService.getNonDefaultUidModes(uid);
+        }
+
+        public int getUidMode(int op) {
+            return mAppOpsCheckingService.getUidMode(uid, op);
+        }
+
+        public boolean setUidMode(int op, int mode) {
+            return mAppOpsCheckingService.setUidMode(uid, op, mode);
+        }
+
+        @SuppressWarnings("GuardedBy")
+        int evalMode(int op, int mode) {
+            return getUidStateTracker().evalMode(uid, op, mode);
+        }
+
+        public void evalForegroundOps() {
+            foregroundOps = null;
+            foregroundOps = mAppOpsCheckingService.evalForegroundUidOps(uid, foregroundOps);
+            if (pkgOps != null) {
+                for (int i = pkgOps.size() - 1; i >= 0; i--) {
+                    foregroundOps = mAppOpsCheckingService
+                            .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps,
+                                    UserHandle.getUserId(uid));
+                }
+            }
+            hasForegroundWatchers = false;
+            if (foregroundOps != null) {
+                for (int i = 0;  i < foregroundOps.size(); i++) {
+                    if (foregroundOps.valueAt(i)) {
+                        hasForegroundWatchers = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        @SuppressWarnings("GuardedBy")
+        public int getState() {
+            return getUidStateTracker().getUidState(uid);
+        }
+
+        @SuppressWarnings("GuardedBy")
+        public void dump(PrintWriter pw, long nowElapsed) {
+            getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
+        }
+    }
+
+    final static class Ops extends SparseArray<Op> {
+        final String packageName;
+        final UidState uidState;
+
+        /**
+         * The restriction properties of the package. If {@code null} it could not have been read
+         * yet and has to be refreshed.
+         */
+        @Nullable RestrictionBypass bypass;
+
+        /** Lazily populated cache of attributionTags of this package */
+        final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
+
+        /**
+         * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
+         * than or equal to {@link #knownAttributionTags}.
+         */
+        final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
+
+        Ops(String _packageName, UidState _uidState) {
+            packageName = _packageName;
+            uidState = _uidState;
+        }
+    }
+
+    /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
+    private static final class PackageVerificationResult {
+
+        final RestrictionBypass bypass;
+        final boolean isAttributionTagValid;
+
+        PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
+            this.bypass = bypass;
+            this.isAttributionTagValid = isAttributionTagValid;
+        }
+    }
+
+    final class Op {
+        int op;
+        int uid;
+        final UidState uidState;
+        final @NonNull String packageName;
+
+        /** attributionTag -> AttributedOp */
+        final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
+
+        Op(UidState uidState, String packageName, int op, int uid) {
+            this.op = op;
+            this.uid = uid;
+            this.uidState = uidState;
+            this.packageName = packageName;
+        }
+
+        @Mode int getMode() {
+            return mAppOpsCheckingService.getPackageMode(packageName, this.op,
+                    UserHandle.getUserId(this.uid));
+        }
+        void setMode(@Mode int mode) {
+            mAppOpsCheckingService.setPackageMode(packageName, this.op, mode,
+                    UserHandle.getUserId(this.uid));
+        }
+
+        void removeAttributionsWithNoTime() {
+            for (int i = mAttributions.size() - 1; i >= 0; i--) {
+                if (!mAttributions.valueAt(i).hasAnyTime()) {
+                    mAttributions.removeAt(i);
+                }
+            }
+        }
+
+        private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
+                @Nullable String attributionTag) {
+            AttributedOp attributedOp;
+
+            attributedOp = mAttributions.get(attributionTag);
+            if (attributedOp == null) {
+                attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
+                mAttributions.put(attributionTag, attributedOp);
+            }
+
+            return attributedOp;
+        }
+
+        @NonNull OpEntry createEntryLocked() {
+            final int numAttributions = mAttributions.size();
+
+            final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
+                    new ArrayMap<>(numAttributions);
+            for (int i = 0; i < numAttributions; i++) {
+                attributionEntries.put(mAttributions.keyAt(i),
+                        mAttributions.valueAt(i).createAttributedOpEntryLocked());
+            }
+
+            return new OpEntry(op, getMode(), attributionEntries);
+        }
+
+        @NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
+            final int numAttributions = mAttributions.size();
+
+            final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
+            for (int i = 0; i < numAttributions; i++) {
+                if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
+                    attributionEntries.put(mAttributions.keyAt(i),
+                            mAttributions.valueAt(i).createAttributedOpEntryLocked());
+                    break;
+                }
+            }
+
+            return new OpEntry(op, getMode(), attributionEntries);
+        }
+
+        boolean isRunning() {
+            final int numAttributions = mAttributions.size();
+            for (int i = 0; i < numAttributions; i++) {
+                if (mAttributions.valueAt(i).isRunning()) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
+    final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
+    final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
+    final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
     final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
 
+    final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient  {
+        /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
+        public static final int ALL_OPS = -2;
+
+        // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
+        // Otherwise we can just use the IBinder object.
+        private final IAppOpsCallback mCallback;
+
+        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
+                int callingUid, int callingPid) {
+            super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
+            this.mCallback = callback;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("ModeCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, getWatchingUid());
+            sb.append(" flags=0x");
+            sb.append(Integer.toHexString(getFlags()));
+            switch (getWatchedOpCode()) {
+                case OP_NONE:
+                    break;
+                case ALL_OPS:
+                    sb.append(" op=(all)");
+                    break;
+                default:
+                    sb.append(" op=");
+                    sb.append(opToName(getWatchedOpCode()));
+                    break;
+            }
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, getCallingUid());
+            sb.append(" pid=");
+            sb.append(getCallingPid());
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void unlinkToDeath() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingMode(mCallback);
+        }
+
+        @Override
+        public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
+            mCallback.opChanged(op, uid, packageName);
+        }
+    }
+
+    final class ActiveCallback implements DeathRecipient {
+        final IAppOpsActiveCallback mCallback;
+        final int mWatchingUid;
+        final int mCallingUid;
+        final int mCallingPid;
+
+        ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
+                int callingPid) {
+            mCallback = callback;
+            mWatchingUid = watchingUid;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("ActiveCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, mWatchingUid);
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, mCallingUid);
+            sb.append(" pid=");
+            sb.append(mCallingPid);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void destroy() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingActive(mCallback);
+        }
+    }
+
+    final class StartedCallback implements DeathRecipient {
+        final IAppOpsStartedCallback mCallback;
+        final int mWatchingUid;
+        final int mCallingUid;
+        final int mCallingPid;
+
+        StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
+                int callingPid) {
+            mCallback = callback;
+            mWatchingUid = watchingUid;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("StartedCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, mWatchingUid);
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, mCallingUid);
+            sb.append(" pid=");
+            sb.append(mCallingPid);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void destroy() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingStarted(mCallback);
+        }
+    }
+
+    final class NotedCallback implements DeathRecipient {
+        final IAppOpsNotedCallback mCallback;
+        final int mWatchingUid;
+        final int mCallingUid;
+        final int mCallingPid;
+
+        NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
+                int callingPid) {
+            mCallback = callback;
+            mWatchingUid = watchingUid;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("NotedCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, mWatchingUid);
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, mCallingUid);
+            sb.append(" pid=");
+            sb.append(mCallingPid);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void destroy() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingNoted(mCallback);
+        }
+    }
+
+    /**
+     * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
+     */
+    static void onClientDeath(@NonNull AttributedOp attributedOp,
+            @NonNull IBinder clientId) {
+        attributedOp.onClientDeath(clientId);
+    }
+
+
     /**
      * Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces}
      * so that we do not log the same operation twice between instances
@@ -233,12 +946,20 @@
     }
 
     public AppOpsService(File storagePath, Handler handler, Context context) {
-        this(handler, context, new AppOpsServiceImpl(storagePath, handler, context));
-    }
+        mContext = context;
 
-    @VisibleForTesting
-    public AppOpsService(Handler handler, Context context,
-            AppOpsServiceInterface appOpsServiceInterface) {
+        for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
+            int switchCode = AppOpsManager.opToSwitch(switchedCode);
+            mSwitchedOps.put(switchCode,
+                    ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
+        }
+        mAppOpsCheckingService =
+                new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps);
+        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
+                mAppOpsCheckingService);
+
+        LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
+        mFile = new AtomicFile(storagePath, "appops");
         if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
             mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
                     "noteOpStackTraces.json");
@@ -246,25 +967,189 @@
         } else {
             mNoteOpCallerStacktracesFile = null;
         }
-
-        mAppOpsService = appOpsServiceInterface;
-        mContext = context;
         mHandler = handler;
+        mConstants = new Constants(mHandler);
+        readState();
     }
 
-    /**
-     * Publishes binder and local service.
-     */
     public void publish() {
         ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
         LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
     }
 
-    /**
-     * Finishes boot sequence.
-     */
+    /** Handler for work when packages are removed or updated */
+    private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+            int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
+
+            if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+                synchronized (AppOpsService.this) {
+                    UidState uidState = mUidStates.get(uid);
+                    if (uidState == null || uidState.pkgOps == null) {
+                        return;
+                    }
+                    mAppOpsCheckingService.removePackage(pkgName, UserHandle.getUserId(uid));
+                    Ops removedOps = uidState.pkgOps.remove(pkgName);
+                    if (removedOps != null) {
+                        scheduleFastWriteLocked();
+                    }
+                }
+            } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
+                AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
+                if (pkg == null) {
+                    return;
+                }
+
+                ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
+                ArraySet<String> attributionTags = new ArraySet<>();
+                attributionTags.add(null);
+                if (pkg.getAttributions() != null) {
+                    int numAttributions = pkg.getAttributions().size();
+                    for (int attributionNum = 0; attributionNum < numAttributions;
+                            attributionNum++) {
+                        ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
+                        attributionTags.add(attribution.getTag());
+
+                        int numInheritFrom = attribution.getInheritFrom().size();
+                        for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
+                                inheritFromNum++) {
+                            dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
+                                    attribution.getTag());
+                        }
+                    }
+                }
+
+                synchronized (AppOpsService.this) {
+                    UidState uidState = mUidStates.get(uid);
+                    if (uidState == null || uidState.pkgOps == null) {
+                        return;
+                    }
+
+                    Ops ops = uidState.pkgOps.get(pkgName);
+                    if (ops == null) {
+                        return;
+                    }
+
+                    // Reset cached package properties to re-initialize when needed
+                    ops.bypass = null;
+                    ops.knownAttributionTags.clear();
+
+                    // Merge data collected for removed attributions into their successor
+                    // attributions
+                    int numOps = ops.size();
+                    for (int opNum = 0; opNum < numOps; opNum++) {
+                        Op op = ops.valueAt(opNum);
+
+                        int numAttributions = op.mAttributions.size();
+                        for (int attributionNum = numAttributions - 1; attributionNum >= 0;
+                                attributionNum--) {
+                            String attributionTag = op.mAttributions.keyAt(attributionNum);
+
+                            if (attributionTags.contains(attributionTag)) {
+                                // attribution still exist after upgrade
+                                continue;
+                            }
+
+                            String newAttributionTag = dstAttributionTags.get(attributionTag);
+
+                            AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
+                                    newAttributionTag);
+                            newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
+                            op.mAttributions.removeAt(attributionNum);
+
+                            scheduleFastWriteLocked();
+                        }
+                    }
+                }
+            }
+        }
+    };
+
     public void systemReady() {
-        mAppOpsService.systemReady();
+        synchronized (this) {
+            upgradeLocked(mVersionAtBoot);
+        }
+
+        mConstants.startMonitoring(mContext.getContentResolver());
+        mHistoricalRegistry.systemReady(mContext.getContentResolver());
+
+        IntentFilter packageUpdateFilter = new IntentFilter();
+        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        packageUpdateFilter.addDataScheme("package");
+
+        mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
+                packageUpdateFilter, null, null);
+
+        synchronized (this) {
+            for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
+                int uid = mUidStates.keyAt(uidNum);
+                UidState uidState = mUidStates.valueAt(uidNum);
+
+                String[] pkgsInUid = getPackagesForUid(uidState.uid);
+                if (ArrayUtils.isEmpty(pkgsInUid)) {
+                    uidState.clear();
+                    mUidStates.removeAt(uidNum);
+                    scheduleFastWriteLocked();
+                    continue;
+                }
+
+                ArrayMap<String, Ops> pkgs = uidState.pkgOps;
+                if (pkgs == null) {
+                    continue;
+                }
+
+                int numPkgs = pkgs.size();
+                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+                    String pkg = pkgs.keyAt(pkgNum);
+
+                    String action;
+                    if (!ArrayUtils.contains(pkgsInUid, pkg)) {
+                        action = Intent.ACTION_PACKAGE_REMOVED;
+                    } else {
+                        action = Intent.ACTION_PACKAGE_REPLACED;
+                    }
+
+                    SystemServerInitThreadPool.submit(
+                            () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
+                                    .setData(Uri.fromParts("package", pkg, null))
+                                    .putExtra(Intent.EXTRA_UID, uid)),
+                            "Update app-ops uidState in case package " + pkg + " changed");
+                }
+            }
+        }
+
+        final IntentFilter packageSuspendFilter = new IntentFilter();
+        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+                final String[] changedPkgs = intent.getStringArrayExtra(
+                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+                    ArraySet<OnOpModeChangedListener> onModeChangedListeners;
+                    synchronized (AppOpsService.this) {
+                        onModeChangedListeners =
+                                mAppOpsCheckingService.getOpModeChangedListeners(code);
+                        if (onModeChangedListeners == null) {
+                            continue;
+                        }
+                    }
+                    for (int i = 0; i < changedUids.length; i++) {
+                        final int changedUid = changedUids[i];
+                        final String changedPkg = changedPkgs[i];
+                        // We trust packagemanager to insert matching uid and packageNames in the
+                        // extras
+                        notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
+                    }
+                }
+            }
+        }, UserHandle.ALL, packageSuspendFilter, null, null);
 
         final IntentFilter packageAddedFilter = new IntentFilter();
         packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -272,8 +1157,9 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
+                final Uri data = intent.getData();
 
-                final String packageName = intent.getData().getSchemeSpecificPart();
+                final String packageName = data.getSchemeSpecificPart();
                 PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
                         PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
                 if (isSamplingTarget(pi)) {
@@ -308,6 +1194,8 @@
                         }
                     }
                 });
+
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
     }
 
     /**
@@ -322,18 +1210,132 @@
         mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(policy, delegate);
     }
 
-    /**
-     * Notify when a package is removed
-     */
     public void packageRemoved(int uid, String packageName) {
-        mAppOpsService.packageRemoved(uid, packageName);
+        synchronized (this) {
+            UidState uidState = mUidStates.get(uid);
+            if (uidState == null) {
+                return;
+            }
+
+            Ops removedOps = null;
+
+            // Remove any package state if such.
+            if (uidState.pkgOps != null) {
+                removedOps = uidState.pkgOps.remove(packageName);
+                mAppOpsCheckingService.removePackage(packageName, UserHandle.getUserId(uid));
+            }
+
+            // If we just nuked the last package state check if the UID is valid.
+            if (removedOps != null && uidState.pkgOps.isEmpty()
+                    && getPackagesForUid(uid).length <= 0) {
+                uidState.clear();
+                mUidStates.remove(uid);
+            }
+
+            if (removedOps != null) {
+                scheduleFastWriteLocked();
+
+                final int numOps = removedOps.size();
+                for (int opNum = 0; opNum < numOps; opNum++) {
+                    final Op op = removedOps.valueAt(opNum);
+
+                    final int numAttributions = op.mAttributions.size();
+                    for (int attributionNum = 0; attributionNum < numAttributions;
+                            attributionNum++) {
+                        AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
+
+                        while (attributedOp.isRunning()) {
+                            attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
+                        }
+                        while (attributedOp.isPaused()) {
+                            attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+                        }
+                    }
+                }
+            }
+        }
+
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
+                    mHistoricalRegistry, uid, packageName));
     }
 
-    /**
-     * Notify when a uid is removed.
-     */
     public void uidRemoved(int uid) {
-        mAppOpsService.uidRemoved(uid);
+        synchronized (this) {
+            if (mUidStates.indexOfKey(uid) >= 0) {
+                mUidStates.get(uid).clear();
+                mUidStates.remove(uid);
+                scheduleFastWriteLocked();
+            }
+        }
+    }
+
+    // The callback method from ForegroundPolicyInterface
+    private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, true);
+
+            if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
+                for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
+                    if (!uidState.foregroundOps.valueAt(fgi)) {
+                        continue;
+                    }
+                    final int code = uidState.foregroundOps.keyAt(fgi);
+
+                    if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
+                            && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
+                        mHandler.sendMessage(PooledLambda.obtainMessage(
+                                AppOpsService::notifyOpChangedForAllPkgsInUid,
+                                this, code, uidState.uid, true, null));
+                    } else if (uidState.pkgOps != null) {
+                        final ArraySet<OnOpModeChangedListener> listenerSet =
+                                mAppOpsCheckingService.getOpModeChangedListeners(code);
+                        if (listenerSet != null) {
+                            for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
+                                final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
+                                if ((listener.getFlags()
+                                        & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
+                                        || !listener.isWatchingUid(uidState.uid)) {
+                                    continue;
+                                }
+                                for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
+                                    final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
+                                    if (op == null) {
+                                        continue;
+                                    }
+                                    if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
+                                        mHandler.sendMessage(PooledLambda.obtainMessage(
+                                                AppOpsService::notifyOpChanged,
+                                                this, listenerSet.valueAt(cbi), code, uidState.uid,
+                                                uidState.pkgOps.keyAt(pkgi)));
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (uidState != null && uidState.pkgOps != null) {
+                int numPkgs = uidState.pkgOps.size();
+                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+                    Ops ops = uidState.pkgOps.valueAt(pkgNum);
+
+                    int numOps = ops.size();
+                    for (int opNum = 0; opNum < numOps; opNum++) {
+                        Op op = ops.valueAt(opNum);
+
+                        int numAttributions = op.mAttributions.size();
+                        for (int attributionNum = 0; attributionNum < numAttributions;
+                                attributionNum++) {
+                            AttributedOp attributedOp = op.mAttributions.valueAt(
+                                    attributionNum);
+
+                            attributedOp.onUidStateChanged(state);
+                        }
+                    }
+                }
+            }
+        }
     }
 
     /**
@@ -341,60 +1343,542 @@
      */
     public void updateUidProcState(int uid, int procState,
             @ActivityManager.ProcessCapability int capability) {
-        mAppOpsService.updateUidProcState(uid, procState, capability);
+        synchronized (this) {
+            getUidStateTracker().updateUidProcState(uid, procState, capability);
+            if (!mUidStates.contains(uid)) {
+                UidState uidState = new UidState(uid);
+                mUidStates.put(uid, uidState);
+                onUidStateChanged(uid,
+                        AppOpsUidStateTracker.processStateToUidState(procState), false);
+            }
+        }
     }
 
-    /**
-     * Initiates shutdown.
-     */
     public void shutdown() {
-        mAppOpsService.shutdown();
-
+        Slog.w(TAG, "Writing app ops before shutdown...");
+        boolean doWrite = false;
+        synchronized (this) {
+            if (mWriteScheduled) {
+                mWriteScheduled = false;
+                mFastWriteScheduled = false;
+                mHandler.removeCallbacks(mWriteRunner);
+                doWrite = true;
+            }
+        }
+        if (doWrite) {
+            writeState();
+        }
         if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
             writeNoteOps();
         }
+
+        mHistoricalRegistry.shutdown();
+    }
+
+    private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
+        ArrayList<AppOpsManager.OpEntry> resOps = null;
+        if (ops == null) {
+            resOps = new ArrayList<>();
+            for (int j=0; j<pkgOps.size(); j++) {
+                Op curOp = pkgOps.valueAt(j);
+                resOps.add(getOpEntryForResult(curOp));
+            }
+        } else {
+            for (int j=0; j<ops.length; j++) {
+                Op curOp = pkgOps.get(ops[j]);
+                if (curOp != null) {
+                    if (resOps == null) {
+                        resOps = new ArrayList<>();
+                    }
+                    resOps.add(getOpEntryForResult(curOp));
+                }
+            }
+        }
+        return resOps;
+    }
+
+    @Nullable
+    private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
+            @Nullable int[] ops) {
+        final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+        if (opModes == null) {
+            return null;
+        }
+
+        int opModeCount = opModes.size();
+        if (opModeCount == 0) {
+            return null;
+        }
+        ArrayList<AppOpsManager.OpEntry> resOps = null;
+        if (ops == null) {
+            resOps = new ArrayList<>();
+            for (int i = 0; i < opModeCount; i++) {
+                int code = opModes.keyAt(i);
+                resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+            }
+        } else {
+            for (int j=0; j<ops.length; j++) {
+                int code = ops[j];
+                if (opModes.indexOfKey(code) >= 0) {
+                    if (resOps == null) {
+                        resOps = new ArrayList<>();
+                    }
+                    resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+                }
+            }
+        }
+        return resOps;
+    }
+
+    private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
+        return op.createEntryLocked();
     }
 
     @Override
     public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
-        return mAppOpsService.getPackagesForOps(ops);
+        final int callingUid = Binder.getCallingUid();
+        final boolean hasAllPackageAccess = mContext.checkPermission(
+                Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
+                Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
+        ArrayList<AppOpsManager.PackageOps> res = null;
+        synchronized (this) {
+            final int uidStateCount = mUidStates.size();
+            for (int i = 0; i < uidStateCount; i++) {
+                UidState uidState = mUidStates.valueAt(i);
+                if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
+                    continue;
+                }
+                ArrayMap<String, Ops> packages = uidState.pkgOps;
+                final int packageCount = packages.size();
+                for (int j = 0; j < packageCount; j++) {
+                    Ops pkgOps = packages.valueAt(j);
+                    ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+                    if (resOps != null) {
+                        if (res == null) {
+                            res = new ArrayList<>();
+                        }
+                        AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                                pkgOps.packageName, pkgOps.uidState.uid, resOps);
+                        // Caller can always see their packages and with a permission all.
+                        if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
+                            res.add(resPackage);
+                        }
+                    }
+                }
+            }
+        }
+        return res;
     }
 
     @Override
     public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
             int[] ops) {
-        return mAppOpsService.getOpsForPackage(uid, packageName, ops);
+        enforceGetAppOpsStatsPermissionIfNeeded(uid,packageName);
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return Collections.emptyList();
+        }
+        synchronized (this) {
+            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
+                    /* edit */ false);
+            if (pkgOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+            if (resOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                    pkgOps.packageName, pkgOps.uidState.uid, resOps);
+            res.add(resPackage);
+            return res;
+        }
+    }
+
+    private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
+        final int callingUid = Binder.getCallingUid();
+        // We get to access everything
+        if (callingUid == Process.myPid()) {
+            return;
+        }
+        // Apps can access their own data
+        if (uid == callingUid && packageName != null
+                && checkPackage(uid, packageName) == MODE_ALLOWED) {
+            return;
+        }
+        // Otherwise, you need a permission...
+        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                Binder.getCallingPid(), callingUid, null);
+    }
+
+    /**
+     * Verify that historical appop request arguments are valid.
+     */
+    private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
+            String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags) {
+        if ((filter & FILTER_BY_UID) != 0) {
+            Preconditions.checkArgument(uid != Process.INVALID_UID);
+        } else {
+            Preconditions.checkArgument(uid == Process.INVALID_UID);
+        }
+
+        if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
+            Objects.requireNonNull(packageName);
+        } else {
+            Preconditions.checkArgument(packageName == null);
+        }
+
+        if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
+            Preconditions.checkArgument(attributionTag == null);
+        }
+
+        if ((filter & FILTER_BY_OP_NAMES) != 0) {
+            Objects.requireNonNull(opNames);
+        } else {
+            Preconditions.checkArgument(opNames == null);
+        }
+
+        Preconditions.checkFlagsArgument(filter,
+                FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
+                        | FILTER_BY_OP_NAMES);
+        Preconditions.checkArgumentNonnegative(beginTimeMillis);
+        Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
+        Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
     }
 
     @Override
     public void getHistoricalOps(int uid, String packageName, String attributionTag,
             List<String> opNames, int dataType, int filter, long beginTimeMillis,
             long endTimeMillis, int flags, RemoteCallback callback) {
-        mAppOpsService.getHistoricalOps(uid, packageName, attributionTag, opNames,
-                dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
+        PackageManager pm = mContext.getPackageManager();
+
+        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+                beginTimeMillis, endTimeMillis, flags);
+        Objects.requireNonNull(callback, "callback cannot be null");
+        ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
+        boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
+        if (!isSelfRequest) {
+            boolean isCallerInstrumented =
+                    ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
+            boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
+            boolean isCallerPermissionController;
+            try {
+                isCallerPermissionController = pm.getPackageUidAsUser(
+                        mContext.getPackageManager().getPermissionControllerPackageName(), 0,
+                        UserHandle.getUserId(Binder.getCallingUid()))
+                        == Binder.getCallingUid();
+            } catch (PackageManager.NameNotFoundException doesNotHappen) {
+                return;
+            }
+
+            boolean doesCallerHavePermission = mContext.checkPermission(
+                    android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
+                    Binder.getCallingPid(), Binder.getCallingUid())
+                    == PackageManager.PERMISSION_GRANTED;
+
+            if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
+                    && !doesCallerHavePermission) {
+                mHandler.post(() -> callback.sendResult(new Bundle()));
+                return;
+            }
+
+            mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                    Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+        }
+
+        final String[] opNamesArray = (opNames != null)
+                ? opNames.toArray(new String[opNames.size()]) : null;
+
+        Set<String> attributionChainExemptPackages = null;
+        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+            attributionChainExemptPackages =
+                    PermissionManager.getIndicatorExemptedPackages(mContext);
+        }
+
+        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+                ? attributionChainExemptPackages.toArray(
+                        new String[attributionChainExemptPackages.size()]) : null;
+
+        // Must not hold the appops lock
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+                callback).recycleOnUse());
     }
 
     @Override
     public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
             List<String> opNames, int dataType, int filter, long beginTimeMillis,
             long endTimeMillis, int flags, RemoteCallback callback) {
-        mAppOpsService.getHistoricalOpsFromDiskRaw(uid, packageName, attributionTag,
-                opNames, dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
+        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+                beginTimeMillis, endTimeMillis, flags);
+        Objects.requireNonNull(callback, "callback cannot be null");
+
+        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+                Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+
+        final String[] opNamesArray = (opNames != null)
+                ? opNames.toArray(new String[opNames.size()]) : null;
+
+        Set<String> attributionChainExemptPackages = null;
+        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+            attributionChainExemptPackages =
+                    PermissionManager.getIndicatorExemptedPackages(mContext);
+        }
+
+        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+                ? attributionChainExemptPackages.toArray(
+                new String[attributionChainExemptPackages.size()]) : null;
+
+        // Must not hold the appops lock
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+                callback).recycleOnUse());
     }
 
     @Override
     public void reloadNonHistoricalState() {
-        mAppOpsService.reloadNonHistoricalState();
+        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+                Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
+        writeState();
+        readState();
     }
 
     @Override
     public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
-        return mAppOpsService.getUidOps(uid, ops);
+        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
+            if (resOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                    null, uidState.uid, resOps);
+            res.add(resPackage);
+            return res;
+        }
+    }
+
+    private void pruneOpLocked(Op op, int uid, String packageName) {
+        op.removeAttributionsWithNoTime();
+
+        if (op.mAttributions.isEmpty()) {
+            Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
+            if (ops != null) {
+                ops.remove(op.op);
+                op.setMode(AppOpsManager.opToDefaultMode(op.op));
+                if (ops.size() <= 0) {
+                    UidState uidState = ops.uidState;
+                    ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+                    if (pkgOps != null) {
+                        pkgOps.remove(ops.packageName);
+                        mAppOpsCheckingService.removePackage(ops.packageName,
+                                UserHandle.getUserId(uidState.uid));
+                        if (pkgOps.isEmpty()) {
+                            uidState.pkgOps = null;
+                        }
+                        if (uidState.isDefault()) {
+                            uidState.clear();
+                            mUidStates.remove(uid);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
+        if (callingPid == Process.myPid()) {
+            return;
+        }
+        final int callingUser = UserHandle.getUserId(callingUid);
+        synchronized (this) {
+            if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
+                if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
+                    // Profile owners are allowed to change modes but only for apps
+                    // within their user.
+                    return;
+                }
+            }
+        }
+        mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
     }
 
     @Override
     public void setUidMode(int code, int uid, int mode) {
-        mAppOpsService.setUidMode(code, uid, mode, null);
+        setUidMode(code, uid, mode, null);
+    }
+
+    private void setUidMode(int code, int uid, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback) {
+        if (DEBUG) {
+            Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
+                    + " by uid " + Binder.getCallingUid());
+        }
+
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+        verifyIncomingOp(code);
+        code = AppOpsManager.opToSwitch(code);
+
+        if (permissionPolicyCallback == null) {
+            updatePermissionRevokedCompat(uid, code, mode);
+        }
+
+        int previousMode;
+        synchronized (this) {
+            final int defaultMode = AppOpsManager.opToDefaultMode(code);
+
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState == null) {
+                if (mode == defaultMode) {
+                    return;
+                }
+                uidState = new UidState(uid);
+                mUidStates.put(uid, uidState);
+            }
+            if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+                previousMode = uidState.getUidMode(code);
+            } else {
+                // doesn't look right but is legacy behavior.
+                previousMode = MODE_DEFAULT;
+            }
+
+            if (!uidState.setUidMode(code, mode)) {
+                return;
+            }
+            uidState.evalForegroundOps();
+            if (mode != MODE_ERRORED && mode != previousMode) {
+                updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+            }
+        }
+
+        notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
+        notifyOpChangedSync(code, uid, null, mode, previousMode);
+    }
+
+    /**
+     * Notify that an op changed for all packages in an uid.
+     *
+     * @param code The op that changed
+     * @param uid The uid the op was changed for
+     * @param onlyForeground Only notify watchers that watch for foreground changes
+     */
+    private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+            @Nullable IAppOpsCallback callbackToIgnore) {
+        ModeCallback listenerToIgnore = callbackToIgnore != null
+                ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
+        mAppOpsCheckingService.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
+                listenerToIgnore);
+    }
+
+    private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager == null) {
+            // This can only happen during early boot. At this time the permission state and appop
+            // state are in sync
+            return;
+        }
+
+        String[] packageNames = packageManager.getPackagesForUid(uid);
+        if (ArrayUtils.isEmpty(packageNames)) {
+            return;
+        }
+        String packageName = packageNames[0];
+
+        int[] ops = mSwitchedOps.get(switchCode);
+        for (int code : ops) {
+            String permissionName = AppOpsManager.opToPermission(code);
+            if (permissionName == null) {
+                continue;
+            }
+
+            if (packageManager.checkPermission(permissionName, packageName)
+                    != PackageManager.PERMISSION_GRANTED) {
+                continue;
+            }
+
+            PermissionInfo permissionInfo;
+            try {
+                permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                e.printStackTrace();
+                continue;
+            }
+
+            if (!permissionInfo.isRuntime()) {
+                continue;
+            }
+
+            boolean supportsRuntimePermissions = getPackageManagerInternal()
+                    .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
+
+            UserHandle user = UserHandle.getUserHandleForUid(uid);
+            boolean isRevokedCompat;
+            if (permissionInfo.backgroundPermission != null) {
+                if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+
+                    if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
+                        Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+                                + " permission state, this is discouraged and you should revoke the"
+                                + " runtime permission instead: uid=" + uid + ", switchCode="
+                                + switchCode + ", mode=" + mode + ", permission="
+                                + permissionInfo.backgroundPermission);
+                    }
+
+                    final long identity = Binder.clearCallingIdentity();
+                    try {
+                        packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
+                                packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                                isBackgroundRevokedCompat
+                                        ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                }
+
+                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
+                        && mode != AppOpsManager.MODE_FOREGROUND;
+            } else {
+                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+            }
+
+            if (isRevokedCompat && supportsRuntimePermissions) {
+                Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+                        + " permission state, this is discouraged and you should revoke the"
+                        + " runtime permission instead: uid=" + uid + ", switchCode="
+                        + switchCode + ", mode=" + mode + ", permission=" + permissionName);
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                packageManager.updatePermissionFlags(permissionName, packageName,
+                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
+                                ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
+            int previousMode) {
+        final StorageManagerInternal storageManagerInternal =
+                LocalServices.getService(StorageManagerInternal.class);
+        if (storageManagerInternal != null) {
+            storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
+        }
     }
 
     /**
@@ -407,12 +1891,309 @@
      */
     @Override
     public void setMode(int code, int uid, @NonNull String packageName, int mode) {
-        mAppOpsService.setMode(code, uid, packageName, mode, null);
+        setMode(code, uid, packageName, mode, null);
+    }
+
+    void setMode(int code, int uid, @NonNull String packageName, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback) {
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return;
+        }
+
+        ArraySet<OnOpModeChangedListener> repCbs = null;
+        code = AppOpsManager.opToSwitch(code);
+
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, null);
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Cannot setMode", e);
+            return;
+        }
+
+        int previousMode = MODE_DEFAULT;
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, false);
+            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
+            if (op != null) {
+                if (op.getMode() != mode) {
+                    previousMode = op.getMode();
+                    op.setMode(mode);
+
+                    if (uidState != null) {
+                        uidState.evalForegroundOps();
+                    }
+                    ArraySet<OnOpModeChangedListener> cbs =
+                            mAppOpsCheckingService.getOpModeChangedListeners(code);
+                    if (cbs != null) {
+                        if (repCbs == null) {
+                            repCbs = new ArraySet<>();
+                        }
+                        repCbs.addAll(cbs);
+                    }
+                    cbs = mAppOpsCheckingService.getPackageModeChangedListeners(packageName);
+                    if (cbs != null) {
+                        if (repCbs == null) {
+                            repCbs = new ArraySet<>();
+                        }
+                        repCbs.addAll(cbs);
+                    }
+                    if (repCbs != null && permissionPolicyCallback != null) {
+                        repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
+                    }
+                    if (mode == AppOpsManager.opToDefaultMode(op.op)) {
+                        // If going into the default mode, prune this op
+                        // if there is nothing else interesting in it.
+                        pruneOpLocked(op, uid, packageName);
+                    }
+                    scheduleFastWriteLocked();
+                    if (mode != MODE_ERRORED) {
+                        updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+                    }
+                }
+            }
+        }
+        if (repCbs != null) {
+            mHandler.sendMessage(PooledLambda.obtainMessage(
+                    AppOpsService::notifyOpChanged,
+                    this, repCbs, code, uid, packageName));
+        }
+
+        notifyOpChangedSync(code, uid, packageName, mode, previousMode);
+    }
+
+    private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
+            int uid, String packageName) {
+        for (int i = 0; i < callbacks.size(); i++) {
+            final OnOpModeChangedListener callback = callbacks.valueAt(i);
+            notifyOpChanged(callback, code, uid, packageName);
+        }
+    }
+
+    private void notifyOpChanged(OnOpModeChangedListener callback, int code,
+            int uid, String packageName) {
+        mAppOpsCheckingService.notifyOpChanged(callback, code, uid, packageName);
+    }
+
+    private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
+            int op, int uid, String packageName, int previousMode) {
+        boolean duplicate = false;
+        if (reports == null) {
+            reports = new ArrayList<>();
+        } else {
+            final int reportCount = reports.size();
+            for (int j = 0; j < reportCount; j++) {
+                ChangeRec report = reports.get(j);
+                if (report.op == op && report.pkg.equals(packageName)) {
+                    duplicate = true;
+                    break;
+                }
+            }
+        }
+        if (!duplicate) {
+            reports.add(new ChangeRec(op, uid, packageName, previousMode));
+        }
+
+        return reports;
+    }
+
+    private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
+            HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
+            int op, int uid, String packageName, int previousMode,
+            ArraySet<OnOpModeChangedListener> cbs) {
+        if (cbs == null) {
+            return callbacks;
+        }
+        if (callbacks == null) {
+            callbacks = new HashMap<>();
+        }
+        final int N = cbs.size();
+        for (int i=0; i<N; i++) {
+            OnOpModeChangedListener cb = cbs.valueAt(i);
+            ArrayList<ChangeRec> reports = callbacks.get(cb);
+            ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
+            if (changed != reports) {
+                callbacks.put(cb, changed);
+            }
+        }
+        return callbacks;
+    }
+
+    static final class ChangeRec {
+        final int op;
+        final int uid;
+        final String pkg;
+        final int previous_mode;
+
+        ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
+            op = _op;
+            uid = _uid;
+            pkg = _pkg;
+            previous_mode = _previous_mode;
+        }
     }
 
     @Override
     public void resetAllModes(int reqUserId, String reqPackageName) {
-        mAppOpsService.resetAllModes(reqUserId, reqPackageName);
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
+                true, true, "resetAllModes", null);
+
+        int reqUid = -1;
+        if (reqPackageName != null) {
+            try {
+                reqUid = AppGlobals.getPackageManager().getPackageUid(
+                        reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
+            } catch (RemoteException e) {
+                /* ignore - local call */
+            }
+        }
+
+        enforceManageAppOpsModes(callingPid, callingUid, reqUid);
+
+        HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
+        ArrayList<ChangeRec> allChanges = new ArrayList<>();
+        synchronized (this) {
+            boolean changed = false;
+            for (int i = mUidStates.size() - 1; i >= 0; i--) {
+                UidState uidState = mUidStates.valueAt(i);
+
+                SparseIntArray opModes = uidState.getNonDefaultUidModes();
+                if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
+                    final int uidOpCount = opModes.size();
+                    for (int j = uidOpCount - 1; j >= 0; j--) {
+                        final int code = opModes.keyAt(j);
+                        if (AppOpsManager.opAllowsReset(code)) {
+                            int previousMode = opModes.valueAt(j);
+                            uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
+                            for (String packageName : getPackagesForUid(uidState.uid)) {
+                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
+                                        previousMode,
+                                        mAppOpsCheckingService.getOpModeChangedListeners(code));
+                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
+                                        previousMode, mAppOpsCheckingService
+                                                .getPackageModeChangedListeners(packageName));
+
+                                allChanges = addChange(allChanges, code, uidState.uid,
+                                        packageName, previousMode);
+                            }
+                        }
+                    }
+                }
+
+                if (uidState.pkgOps == null) {
+                    continue;
+                }
+
+                if (reqUserId != UserHandle.USER_ALL
+                        && reqUserId != UserHandle.getUserId(uidState.uid)) {
+                    // Skip any ops for a different user
+                    continue;
+                }
+
+                Map<String, Ops> packages = uidState.pkgOps;
+                Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+                boolean uidChanged = false;
+                while (it.hasNext()) {
+                    Map.Entry<String, Ops> ent = it.next();
+                    String packageName = ent.getKey();
+                    if (reqPackageName != null && !reqPackageName.equals(packageName)) {
+                        // Skip any ops for a different package
+                        continue;
+                    }
+                    Ops pkgOps = ent.getValue();
+                    for (int j=pkgOps.size()-1; j>=0; j--) {
+                        Op curOp = pkgOps.valueAt(j);
+                        if (shouldDeferResetOpToDpm(curOp.op)) {
+                            deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
+                            continue;
+                        }
+                        if (AppOpsManager.opAllowsReset(curOp.op)
+                                && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
+                            int previousMode = curOp.getMode();
+                            curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
+                            changed = true;
+                            uidChanged = true;
+                            final int uid = curOp.uidState.uid;
+                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+                                    previousMode,
+                                    mAppOpsCheckingService.getOpModeChangedListeners(curOp.op));
+                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+                                    previousMode, mAppOpsCheckingService
+                                            .getPackageModeChangedListeners(packageName));
+
+                            allChanges = addChange(allChanges, curOp.op, uid, packageName,
+                                    previousMode);
+                            curOp.removeAttributionsWithNoTime();
+                            if (curOp.mAttributions.isEmpty()) {
+                                pkgOps.removeAt(j);
+                            }
+                        }
+                    }
+                    if (pkgOps.size() == 0) {
+                        it.remove();
+                        mAppOpsCheckingService.removePackage(packageName,
+                                UserHandle.getUserId(uidState.uid));
+                    }
+                }
+                if (uidState.isDefault()) {
+                    uidState.clear();
+                    mUidStates.remove(uidState.uid);
+                }
+                if (uidChanged) {
+                    uidState.evalForegroundOps();
+                }
+            }
+
+            if (changed) {
+                scheduleFastWriteLocked();
+            }
+        }
+        if (callbacks != null) {
+            for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
+                    : callbacks.entrySet()) {
+                OnOpModeChangedListener cb = ent.getKey();
+                ArrayList<ChangeRec> reports = ent.getValue();
+                for (int i=0; i<reports.size(); i++) {
+                    ChangeRec rep = reports.get(i);
+                    mHandler.sendMessage(PooledLambda.obtainMessage(
+                            AppOpsService::notifyOpChanged,
+                            this, cb, rep.op, rep.uid, rep.pkg));
+                }
+            }
+        }
+
+        int numChanges = allChanges.size();
+        for (int i = 0; i < numChanges; i++) {
+            ChangeRec change = allChanges.get(i);
+            notifyOpChangedSync(change.op, change.uid, change.pkg,
+                    AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
+        }
+    }
+
+    private boolean shouldDeferResetOpToDpm(int op) {
+        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
+        return dpmi != null && dpmi.supportsResetOp(op);
+    }
+
+    /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
+    private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
+        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
+        dpmi.resetOp(op, packageName, userId);
+    }
+
+    private void evalAllForegroundOpsLocked() {
+        for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
+            final UidState uidState = mUidStates.valueAt(uidi);
+            if (uidState.foregroundOps != null) {
+                uidState.evalForegroundOps();
+            }
+        }
     }
 
     @Override
@@ -423,17 +2204,66 @@
     @Override
     public void startWatchingModeWithFlags(int op, String packageName, int flags,
             IAppOpsCallback callback) {
-        mAppOpsService.startWatchingModeWithFlags(op, packageName, flags, callback);
+        int watchedUid = -1;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        // TODO: should have a privileged permission to protect this.
+        // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
+        // the USAGE_STATS permission since this can provide information about when an
+        // app is in the foreground?
+        Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
+                AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
+        if (callback == null) {
+            return;
+        }
+        final boolean mayWatchPackageName = packageName != null
+                && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
+        synchronized (this) {
+            int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
+
+            int notifiedOps;
+            if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
+                if (op == OP_NONE) {
+                    notifiedOps = ALL_OPS;
+                } else {
+                    notifiedOps = op;
+                }
+            } else {
+                notifiedOps = switchOp;
+            }
+
+            ModeCallback cb = mModeWatchers.get(callback.asBinder());
+            if (cb == null) {
+                cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
+                        callingPid);
+                mModeWatchers.put(callback.asBinder(), cb);
+            }
+            if (switchOp != AppOpsManager.OP_NONE) {
+                mAppOpsCheckingService.startWatchingOpModeChanged(cb, switchOp);
+            }
+            if (mayWatchPackageName) {
+                mAppOpsCheckingService.startWatchingPackageModeChanged(cb, packageName);
+            }
+            evalAllForegroundOpsLocked();
+        }
     }
 
     @Override
     public void stopWatchingMode(IAppOpsCallback callback) {
-        mAppOpsService.stopWatchingMode(callback);
+        if (callback == null) {
+            return;
+        }
+        synchronized (this) {
+            ModeCallback cb = mModeWatchers.remove(callback.asBinder());
+            if (cb != null) {
+                cb.unlinkToDeath();
+                mAppOpsCheckingService.removeListener(cb);
+            }
+
+            evalAllForegroundOpsLocked();
+        }
     }
 
-    /**
-     * @return the current {@link CheckOpsDelegate}.
-     */
     public CheckOpsDelegate getAppOpsServiceDelegate() {
         synchronized (AppOpsService.this) {
             final CheckOpsDelegateDispatcher dispatcher = mCheckOpsDelegateDispatcher;
@@ -441,9 +2271,6 @@
         }
     }
 
-    /**
-     * Sets the appops {@link CheckOpsDelegate}
-     */
     public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) {
         synchronized (AppOpsService.this) {
             final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher;
@@ -467,7 +2294,58 @@
 
     private int checkOperationImpl(int code, int uid, String packageName,
             @Nullable String attributionTag, boolean raw) {
-        return mAppOpsService.checkOperation(code, uid, packageName, attributionTag, raw);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return AppOpsManager.opToDefaultMode(code);
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+        return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
+    }
+
+    /**
+     * Get the mode of an app-op.
+     *
+     * @param code The code of the op
+     * @param uid The uid of the package the op belongs to
+     * @param packageName The package the op belongs to
+     * @param raw If the raw state of eval-ed state should be checked.
+     *
+     * @return The mode of the op
+     */
+    private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, boolean raw) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, null);
+        } catch (SecurityException e) {
+            Slog.e(TAG, "checkOperation", e);
+            return AppOpsManager.opToDefaultMode(code);
+        }
+
+        if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+        synchronized (this) {
+            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
+                return AppOpsManager.MODE_IGNORED;
+            }
+            code = AppOpsManager.opToSwitch(code);
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState != null
+                    && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+                final int rawMode = uidState.getUidMode(code);
+                return raw ? rawMode : uidState.evalMode(code, rawMode);
+            }
+            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
+            if (op == null) {
+                return AppOpsManager.opToDefaultMode(code);
+            }
+            return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
+        }
     }
 
     @Override
@@ -487,8 +2365,7 @@
     @Override
     public void setAudioRestriction(int code, int usage, int uid, int mode,
             String[] exceptionPackages) {
-        mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
-                Binder.getCallingUid(), uid);
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
 
@@ -496,35 +2373,58 @@
                 code, usage, uid, mode, exceptionPackages);
 
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, code,
-                UID_ANY));
+                AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
     }
 
 
     @Override
     public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
-        mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
-                Binder.getCallingUid(), -1);
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1);
 
         mAudioRestrictionManager.setCameraAudioRestriction(mode);
 
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
+                AppOpsService::notifyWatchersOfChange, this,
                 AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
+                AppOpsService::notifyWatchersOfChange, this,
                 AppOpsManager.OP_VIBRATE, UID_ANY));
     }
 
     @Override
     public int checkPackage(int uid, String packageName) {
-        return mAppOpsService.checkPackage(uid, packageName);
+        Objects.requireNonNull(packageName);
+        try {
+            verifyAndGetBypass(uid, packageName, null);
+            // When the caller is the system, it's possible that the packageName is the special
+            // one (e.g., "root") which isn't actually existed.
+            if (resolveUid(packageName) == uid
+                    || (isPackageExisted(packageName)
+                            && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
+                return AppOpsManager.MODE_ALLOWED;
+            }
+            return AppOpsManager.MODE_ERRORED;
+        } catch (SecurityException ignored) {
+            return AppOpsManager.MODE_ERRORED;
+        }
     }
 
     private boolean isPackageExisted(String packageName) {
         return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
     }
 
+    /**
+     * This method will check with PackageManager to determine if the package provided should
+     * be visible to the {@link Binder#getCallingUid()}.
+     *
+     * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
+     */
+    private boolean filterAppAccessUnlocked(String packageName, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        return LocalServices.getService(PackageManagerInternal.class)
+                .filterAppAccess(packageName, callingUid, userId);
+    }
+
     @Override
     public SyncNotedAppOp noteProxyOperation(int code, AttributionSource attributionSource,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
@@ -570,20 +2470,13 @@
             final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
                     : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
 
-            final int proxyReturn = mAppOpsService.noteOperationUnchecked(code, proxyUid,
+            final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
                     resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
-                    proxyFlags);
-            if (proxyReturn != AppOpsManager.MODE_ALLOWED) {
-                return new SyncNotedAppOp(proxyReturn, code, proxiedAttributionTag,
+                    proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage);
+            if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
+                return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
                         proxiedPackageName);
             }
-            if (shouldCollectAsyncNotedOp) {
-                boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
-                        resolveProxyPackageName, proxyAttributionTag, null);
-                collectAsyncNotedOp(proxyUid, resolveProxyPackageName, code,
-                        isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
-                        message, shouldCollectMessage);
-            }
         }
 
         String resolveProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -595,32 +2488,9 @@
 
         final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
                 : AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
-        final int result = mAppOpsService.noteOperationUnchecked(code, proxiedUid,
-                resolveProxiedPackageName, proxiedAttributionTag, proxyUid, resolveProxyPackageName,
-                proxyAttributionTag, proxiedFlags);
-
-        boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
-                resolveProxiedPackageName, proxiedAttributionTag, resolveProxyPackageName);
-        if (shouldCollectAsyncNotedOp && result == AppOpsManager.MODE_ALLOWED) {
-            collectAsyncNotedOp(proxiedUid, resolveProxiedPackageName, code,
-                    isProxiedAttributionTagValid ? proxiedAttributionTag : null, proxiedFlags,
-                    message, shouldCollectMessage);
-        }
-
-
-        return new SyncNotedAppOp(result, code,
-                isProxiedAttributionTagValid ? proxiedAttributionTag : null,
-                resolveProxiedPackageName);
-    }
-
-    private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
-        if (attributionSource.getUid() != Binder.getCallingUid()
-                && attributionSource.isTrusted(mContext)) {
-            return true;
-        }
-        return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), null)
-                == PackageManager.PERMISSION_GRANTED;
+        return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
+                proxiedAttributionTag, proxyUid, resolveProxyPackageName, proxyAttributionTag,
+                proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
     }
 
     @Override
@@ -634,58 +2504,258 @@
     private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
             @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp,
             @Nullable String message, boolean shouldCollectMessage) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
 
-        int result = mAppOpsService.noteOperation(code, uid, packageName,
-                attributionTag, message);
-
         String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+                    packageName);
+        }
+        return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+                Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
+                shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+    }
 
-        boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
-                    resolvedPackageName, attributionTag, null);
-
-        if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
-            collectAsyncNotedOp(uid, resolvedPackageName, code,
-                    isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
-                    message, shouldCollectMessage);
+    private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+            @Nullable String proxyAttributionTag, @OpFlags int flags,
+            boolean shouldCollectAsyncNotedOp, @Nullable String message,
+            boolean shouldCollectMessage) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+            boolean wasNull = attributionTag == null;
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "noteOperation", e);
+            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+                    packageName);
         }
 
-        return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
-                resolvedPackageName);
+        synchronized (this) {
+            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+            if (ops == null) {
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        AppOpsManager.MODE_IGNORED);
+                if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+                        + " package " + packageName + "flags: " +
+                        AppOpsManager.flagsToString(flags));
+                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+                        packageName);
+            }
+            final Op op = getOpLocked(ops, code, uid, true);
+            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+            if (attributedOp.isRunning()) {
+                Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+                        + code + " startTime of in progress event="
+                        + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
+            }
+
+            final int switchCode = AppOpsManager.opToSwitch(code);
+            final UidState uidState = ops.uidState;
+            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
+                attributedOp.rejected(uidState.getState(), flags);
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        AppOpsManager.MODE_IGNORED);
+                return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+                        packageName);
+            }
+            // If there is a non-default per UID policy (we set UID op mode only if
+            // non-default) it takes over, otherwise use the per package policy.
+            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+                if (uidMode != AppOpsManager.MODE_ALLOWED) {
+                    if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+                            + switchCode + " (" + code + ") uid " + uid + " package "
+                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    attributedOp.rejected(uidState.getState(), flags);
+                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                            uidMode);
+                    return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
+                }
+            } else {
+                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+                        : op;
+                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+                if (mode != AppOpsManager.MODE_ALLOWED) {
+                    if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
+                            + switchCode + " (" + code + ") uid " + uid + " package "
+                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    attributedOp.rejected(uidState.getState(), flags);
+                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                            mode);
+                    return new SyncNotedAppOp(mode, code, attributionTag, packageName);
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "noteOperation: allowing code " + code + " uid " + uid + " package "
+                                + packageName + (attributionTag == null ? ""
+                                : "." + attributionTag) + " flags: "
+                                + AppOpsManager.flagsToString(flags));
+            }
+            scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                    AppOpsManager.MODE_ALLOWED);
+            attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
+                    uidState.getState(),
+                    flags);
+
+            if (shouldCollectAsyncNotedOp) {
+                collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
+                        shouldCollectMessage);
+            }
+
+            return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
+                    packageName);
+        }
     }
 
     // TODO moltmann: Allow watching for attribution ops
     @Override
     public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
-        mAppOpsService.startWatchingActive(ops, callback);
+        int watchedUid = Process.INVALID_UID;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                != PackageManager.PERMISSION_GRANTED) {
+            watchedUid = callingUid;
+        }
+        if (ops != null) {
+            Preconditions.checkArrayElementsInRange(ops, 0,
+                    AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
+        }
+        if (callback == null) {
+            return;
+        }
+        synchronized (this) {
+            SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
+            if (callbacks == null) {
+                callbacks = new SparseArray<>();
+                mActiveWatchers.put(callback.asBinder(), callbacks);
+            }
+            final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
+                    callingUid, callingPid);
+            for (int op : ops) {
+                callbacks.put(op, activeCallback);
+            }
+        }
     }
 
     @Override
     public void stopWatchingActive(IAppOpsActiveCallback callback) {
-        mAppOpsService.stopWatchingActive(callback);
+        if (callback == null) {
+            return;
+        }
+        synchronized (this) {
+            final SparseArray<ActiveCallback> activeCallbacks =
+                    mActiveWatchers.remove(callback.asBinder());
+            if (activeCallbacks == null) {
+                return;
+            }
+            final int callbackCount = activeCallbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                activeCallbacks.valueAt(i).destroy();
+            }
+        }
     }
 
     @Override
     public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
-        mAppOpsService.startWatchingStarted(ops, callback);
+        int watchedUid = Process.INVALID_UID;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                != PackageManager.PERMISSION_GRANTED) {
+            watchedUid = callingUid;
+        }
+
+        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+                "Invalid op code in: " + Arrays.toString(ops));
+        Objects.requireNonNull(callback, "Callback cannot be null");
+
+        synchronized (this) {
+            SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
+            if (callbacks == null) {
+                callbacks = new SparseArray<>();
+                mStartedWatchers.put(callback.asBinder(), callbacks);
+            }
+
+            final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
+                    callingUid, callingPid);
+            for (int op : ops) {
+                callbacks.put(op, startedCallback);
+            }
+        }
     }
 
     @Override
     public void stopWatchingStarted(IAppOpsStartedCallback callback) {
-        mAppOpsService.stopWatchingStarted(callback);
+        Objects.requireNonNull(callback, "Callback cannot be null");
+
+        synchronized (this) {
+            final SparseArray<StartedCallback> startedCallbacks =
+                    mStartedWatchers.remove(callback.asBinder());
+            if (startedCallbacks == null) {
+                return;
+            }
+
+            final int callbackCount = startedCallbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                startedCallbacks.valueAt(i).destroy();
+            }
+        }
     }
 
     @Override
     public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
-        mAppOpsService.startWatchingNoted(ops, callback);
+        int watchedUid = Process.INVALID_UID;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                != PackageManager.PERMISSION_GRANTED) {
+            watchedUid = callingUid;
+        }
+        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+                "Invalid op code in: " + Arrays.toString(ops));
+        Objects.requireNonNull(callback, "Callback cannot be null");
+        synchronized (this) {
+            SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
+            if (callbacks == null) {
+                callbacks = new SparseArray<>();
+                mNotedWatchers.put(callback.asBinder(), callbacks);
+            }
+            final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
+                    callingUid, callingPid);
+            for (int op : ops) {
+                callbacks.put(op, notedCallback);
+            }
+        }
     }
 
     @Override
     public void stopWatchingNoted(IAppOpsNotedCallback callback) {
-        mAppOpsService.stopWatchingNoted(callback);
+        Objects.requireNonNull(callback, "Callback cannot be null");
+        synchronized (this) {
+            final SparseArray<NotedCallback> notedCallbacks =
+                    mNotedWatchers.remove(callback.asBinder());
+            if (notedCallbacks == null) {
+                return;
+            }
+            final int callbackCount = notedCallbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                notedCallbacks.valueAt(i).destroy();
+            }
+        }
     }
 
     /**
@@ -772,7 +2842,7 @@
         int uid = Binder.getCallingUid();
         Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
 
-        mAppOpsService.verifyPackage(uid, packageName);
+        verifyAndGetBypass(uid, packageName, null);
 
         synchronized (this) {
             RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -802,7 +2872,7 @@
         int uid = Binder.getCallingUid();
         Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
 
-        mAppOpsService.verifyPackage(uid, packageName);
+        verifyAndGetBypass(uid, packageName, null);
 
         synchronized (this) {
             RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -821,7 +2891,7 @@
 
         int uid = Binder.getCallingUid();
 
-        mAppOpsService.verifyPackage(uid, packageName);
+        verifyAndGetBypass(uid, packageName, null);
 
         synchronized (this) {
             return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
@@ -844,49 +2914,54 @@
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
             boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
             int attributionChainId) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
 
-        int result = mAppOpsService.startOperation(clientId, code, uid, packageName,
-                attributionTag, startIfModeDefault, message,
-                attributionFlags, attributionChainId);
-
         String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-
-        boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
-                resolvedPackageName, attributionTag, null);
-
-        if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
-            collectAsyncNotedOp(uid, resolvedPackageName, code,
-                    isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
-                    message, shouldCollectMessage);
+        if (resolvedPackageName == null) {
+            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+                    packageName);
         }
 
-        return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
-                resolvedPackageName);
+        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
+        // purposes and not as a check, also make sure that the caller is allowed to access
+        // the data gated by OP_RECORD_AUDIO.
+        //
+        // TODO: Revert this change before Android 12.
+        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
+            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+            if (result != AppOpsManager.MODE_ALLOWED) {
+                return new SyncNotedAppOp(result, code, attributionTag, packageName);
+            }
+        }
+        return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
+                Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
+                shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
+                attributionChainId, /*dryRun*/ false);
     }
 
     @Override
-    public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
+    public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
             @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
             boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
             @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
-        return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code,
-                attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
-                proxiedAttributionFlags, attributionChainId);
+        return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource,
+                startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
+                attributionChainId);
     }
 
-    private SyncNotedAppOp startProxyOperationImpl(IBinder clientId, int code,
+    private SyncNotedAppOp startProxyOperationImpl(@NonNull IBinder clientId, int code,
             @NonNull AttributionSource attributionSource,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
             boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
             int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
             int attributionChainId) {
-
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
@@ -934,68 +3009,147 @@
 
         if (!skipProxyOperation) {
             // Test if the proxied operation will succeed before starting the proxy operation
-            final int testProxiedOp = mAppOpsService.startOperationUnchecked(clientId, code,
+            final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
                     proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
+                    shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                     proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
-
-            boolean isTestProxiedAttributionTagValid =
-                    mAppOpsService.isAttributionTagValid(proxiedUid, resolvedProxiedPackageName,
-                            proxiedAttributionTag, resolvedProxyPackageName);
-
-            if (!shouldStartForMode(testProxiedOp, startIfModeDefault)) {
-                return new SyncNotedAppOp(testProxiedOp, code,
-                        isTestProxiedAttributionTagValid ? proxiedAttributionTag : null,
-                        resolvedProxiedPackageName);
+            if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
+                return testProxiedOp;
             }
 
             final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
                     : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
 
-            final int proxyAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxyUid,
+            final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
-                    proxyFlags, startIfModeDefault, proxyAttributionFlags, attributionChainId,
+                    proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
+                    shouldCollectMessage, proxyAttributionFlags, attributionChainId,
                     /*dryRun*/ false);
-
-            boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
-                    resolvedProxyPackageName, proxyAttributionTag, null);
-
-            if (!shouldStartForMode(proxyAppOp, startIfModeDefault)) {
-                return new SyncNotedAppOp(proxyAppOp, code,
-                        isProxyAttributionTagValid ? proxyAttributionTag : null,
-                        resolvedProxyPackageName);
-            }
-
-            if (shouldCollectAsyncNotedOp) {
-                collectAsyncNotedOp(proxyUid, resolvedProxyPackageName, code,
-                        isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
-                        message, shouldCollectMessage);
+            if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
+                return proxyAppOp;
             }
         }
 
-        final int proxiedAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxiedUid,
-                resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
-                resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
-                proxiedAttributionFlags, attributionChainId,/*dryRun*/ false);
-
-        boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
-                resolvedProxiedPackageName, proxiedAttributionTag, resolvedProxyPackageName);
-
-        if (shouldCollectAsyncNotedOp && proxiedAppOp == MODE_ALLOWED) {
-            collectAsyncNotedOp(proxyUid, resolvedProxiedPackageName, code,
-                    isProxiedAttributionTagValid ? proxiedAttributionTag : null,
-                    proxiedAttributionFlags, message, shouldCollectMessage);
-        }
-
-        return new SyncNotedAppOp(proxiedAppOp, code,
-                isProxiedAttributionTagValid ? proxiedAttributionTag : null,
-                resolvedProxiedPackageName);
+        return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
+                proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
+                proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
+                /*dryRun*/ false);
     }
 
     private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
         return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
     }
 
+    private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
+            @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
+            String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
+            boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
+            boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+            int attributionChainId, boolean dryRun) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "startOperation", e);
+            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+                    packageName);
+        }
+
+        boolean isRestricted = false;
+        int startType = START_TYPE_FAILED;
+        synchronized (this) {
+            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+            if (ops == null) {
+                if (!dryRun) {
+                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                            flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+                            attributionChainId);
+                }
+                if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+                        + " package " + packageName + " flags: "
+                        + AppOpsManager.flagsToString(flags));
+                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+                        packageName);
+            }
+            final Op op = getOpLocked(ops, code, uid, true);
+            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+            final UidState uidState = ops.uidState;
+            isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
+                    false);
+            final int switchCode = AppOpsManager.opToSwitch(code);
+            // If there is a non-default per UID policy (we set UID op mode only if
+            // non-default) it takes over, otherwise use the per package policy.
+            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+                if (!shouldStartForMode(uidMode, startIfModeDefault)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    if (!dryRun) {
+                        attributedOp.rejected(uidState.getState(), flags);
+                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                                flags, uidMode, startType, attributionFlags, attributionChainId);
+                    }
+                    return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
+                }
+            } else {
+                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+                        : op;
+                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+                if (mode != AppOpsManager.MODE_ALLOWED
+                        && (!startIfModeDefault || mode != MODE_DEFAULT)) {
+                    if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+                            + switchCode + " (" + code + ") uid " + uid + " package "
+                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    if (!dryRun) {
+                        attributedOp.rejected(uidState.getState(), flags);
+                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                                flags, mode, startType, attributionFlags, attributionChainId);
+                    }
+                    return new SyncNotedAppOp(mode, code, attributionTag, packageName);
+                }
+            }
+            if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+                    + " package " + packageName + " restricted: " + isRestricted
+                    + " flags: " + AppOpsManager.flagsToString(flags));
+            if (!dryRun) {
+                try {
+                    if (isRestricted) {
+                        attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
+                                proxyAttributionTag, uidState.getState(), flags,
+                                attributionFlags, attributionChainId);
+                    } else {
+                        attributedOp.started(clientId, proxyUid, proxyPackageName,
+                                proxyAttributionTag, uidState.getState(), flags,
+                                attributionFlags, attributionChainId);
+                        startType = START_TYPE_STARTED;
+                    }
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+                        attributionChainId);
+            }
+        }
+
+        if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
+            collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
+                    message, shouldCollectMessage);
+        }
+
+        return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
+                packageName);
+    }
+
     @Override
     public void finishOperation(IBinder clientId, int code, int uid, String packageName,
             String attributionTag) {
@@ -1005,11 +3159,22 @@
 
     private void finishOperationImpl(IBinder clientId, int code, int uid, String packageName,
             String attributionTag) {
-        mAppOpsService.finishOperation(clientId, code, uid, packageName, attributionTag);
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return;
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return;
+        }
+
+        finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
     }
 
     @Override
-    public void finishProxyOperation(IBinder clientId, int code,
+    public void finishProxyOperation(@NonNull IBinder clientId, int code,
             @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
         mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource,
                 skipProxyOperation);
@@ -1041,8 +3206,8 @@
         }
 
         if (!skipProxyOperation) {
-            mAppOpsService.finishOperationUnchecked(clientId, code, proxyUid,
-                    resolvedProxyPackageName, proxyAttributionTag);
+            finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
+                    proxyAttributionTag);
         }
 
         String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -1051,12 +3216,209 @@
             return null;
         }
 
-        mAppOpsService.finishOperationUnchecked(clientId, code, proxiedUid,
-                resolvedProxiedPackageName, proxiedAttributionTag);
+        finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
+                proxiedAttributionTag);
 
         return null;
     }
 
+    private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag);
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Cannot finishOperation", e);
+            return;
+        }
+
+        synchronized (this) {
+            Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
+                    pvr.bypass, /* edit */ true);
+            if (op == null) {
+                Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
+                return;
+            }
+            final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+            if (attributedOp == null) {
+                Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
+                return;
+            }
+
+            if (attributedOp.isRunning() || attributedOp.isPaused()) {
+                attributedOp.finished(clientId);
+            } else {
+                Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
+            }
+        }
+    }
+
+    void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
+            String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
+            int attributionFlags, int attributionChainId) {
+        ArraySet<ActiveCallback> dispatchedCallbacks = null;
+        final int callbackListCount = mActiveWatchers.size();
+        for (int i = 0; i < callbackListCount; i++) {
+            final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
+            ActiveCallback callback = callbacks.get(code);
+            if (callback != null) {
+                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+                    continue;
+                }
+                if (dispatchedCallbacks == null) {
+                    dispatchedCallbacks = new ArraySet<>();
+                }
+                dispatchedCallbacks.add(callback);
+            }
+        }
+        if (dispatchedCallbacks == null) {
+            return;
+        }
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsService::notifyOpActiveChanged,
+                this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
+                attributionFlags, attributionChainId));
+    }
+
+    private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
+            int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
+            boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
+        // There are features watching for mode changes such as window manager
+        // and location manager which are in our process. The callbacks in these
+        // features may require permissions our remote caller does not have.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final ActiveCallback callback = callbacks.valueAt(i);
+                try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
+                    callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
+                            active, attributionFlags, attributionChainId);
+                } catch (RemoteException e) {
+                    /* do nothing */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
+            String attributionTag, @OpFlags int flags, @Mode int result,
+            @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        ArraySet<StartedCallback> dispatchedCallbacks = null;
+        final int callbackListCount = mStartedWatchers.size();
+        for (int i = 0; i < callbackListCount; i++) {
+            final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
+
+            StartedCallback callback = callbacks.get(code);
+            if (callback != null) {
+                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+                    continue;
+                }
+
+                if (dispatchedCallbacks == null) {
+                    dispatchedCallbacks = new ArraySet<>();
+                }
+                dispatchedCallbacks.add(callback);
+            }
+        }
+
+        if (dispatchedCallbacks == null) {
+            return;
+        }
+
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsService::notifyOpStarted,
+                this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
+                result, startedType, attributionFlags, attributionChainId));
+    }
+
+    private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
+            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+            @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final StartedCallback callback = callbacks.valueAt(i);
+                try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
+                    callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
+                            result, startedType, attributionFlags, attributionChainId);
+                } catch (RemoteException e) {
+                    /* do nothing */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
+            String attributionTag, @OpFlags int flags, @Mode int result) {
+        ArraySet<NotedCallback> dispatchedCallbacks = null;
+        final int callbackListCount = mNotedWatchers.size();
+        for (int i = 0; i < callbackListCount; i++) {
+            final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
+            final NotedCallback callback = callbacks.get(code);
+            if (callback != null) {
+                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+                    continue;
+                }
+                if (dispatchedCallbacks == null) {
+                    dispatchedCallbacks = new ArraySet<>();
+                }
+                dispatchedCallbacks.add(callback);
+            }
+        }
+        if (dispatchedCallbacks == null) {
+            return;
+        }
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsService::notifyOpChecked,
+                this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
+                result));
+    }
+
+    private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
+            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+            @Mode int result) {
+        // There are features watching for checks in our process. The callbacks in
+        // these features may require permissions our remote caller does not have.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final NotedCallback callback = callbacks.valueAt(i);
+                try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
+                    callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
+                            result);
+                } catch (RemoteException e) {
+                    /* do nothing */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     @Override
     public int permissionToOpCode(String permission) {
         if (permission == null) {
@@ -1114,6 +3476,13 @@
                 Binder.getCallingPid(), Binder.getCallingUid(), null);
     }
 
+    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
+        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+        // as watcher should not use this to signal if the value is changed.
+        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
+    }
+
     private void verifyIncomingOp(int op) {
         if (op >= 0 && op < AppOpsManager._NUM_OP) {
             // Enforce manage appops permission if it's a restricted read op.
@@ -1154,6 +3523,35 @@
                 || resolveUid(resolvedPackage) != Process.INVALID_UID;
     }
 
+    private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
+        if (attributionSource.getUid() != Binder.getCallingUid()
+                && attributionSource.isTrusted(mContext)) {
+            return true;
+        }
+        return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
+        UidState uidState = mUidStates.get(uid);
+        if (uidState == null) {
+            if (!edit) {
+                return null;
+            }
+            uidState = new UidState(uid);
+            mUidStates.put(uid, uidState);
+        }
+
+        return uidState;
+    }
+
+    private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
+        synchronized (this) {
+            getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
+        }
+    }
+
     /**
      * @return {@link PackageManagerInternal}
      */
@@ -1165,6 +3563,801 @@
         return mPackageManagerInternal;
     }
 
+    /**
+     * Create a restriction description matching the properties of the package.
+     *
+     * @param pkg The package to create the restriction description for
+     *
+     * @return The restriction matching the package
+     */
+    private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
+        return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
+                mContext.checkPermission(android.Manifest.permission
+                        .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
+                == PackageManager.PERMISSION_GRANTED);
+    }
+
+    /**
+     * @see #verifyAndGetBypass(int, String, String, String)
+     */
+    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+            @Nullable String attributionTag) {
+        return verifyAndGetBypass(uid, packageName, attributionTag, null);
+    }
+
+    /**
+     * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
+     * description} for the package, along with a boolean indicating whether the attribution tag is
+     * valid.
+     *
+     * @param uid The uid the package belongs to
+     * @param packageName The package the might belong to the uid
+     * @param attributionTag attribution tag or {@code null} if no need to verify
+     * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
+     *
+     * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
+     *         attribution tag is valid
+     */
+    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+            @Nullable String attributionTag, @Nullable String proxyPackageName) {
+        if (uid == Process.ROOT_UID) {
+            // For backwards compatibility, don't check package name for root UID.
+            return new PackageVerificationResult(null,
+                    /* isAttributionTagValid */ true);
+        }
+        if (Process.isSdkSandboxUid(uid)) {
+            // SDK sandbox processes run in their own UID range, but their associated
+            // UID for checks should always be the UID of the package implementing SDK sandbox
+            // service.
+            // TODO: We will need to modify the callers of this function instead, so
+            // modifications and checks against the app ops state are done with the
+            // correct UID.
+            try {
+                final PackageManager pm = mContext.getPackageManager();
+                final String supplementalPackageName = pm.getSdkSandboxPackageName();
+                if (Objects.equals(packageName, supplementalPackageName)) {
+                    uid = pm.getPackageUidAsUser(supplementalPackageName,
+                            PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Shouldn't happen for the supplemental package
+                e.printStackTrace();
+            }
+        }
+
+
+        // Do not check if uid/packageName/attributionTag is already known.
+        synchronized (this) {
+            UidState uidState = mUidStates.get(uid);
+            if (uidState != null && uidState.pkgOps != null) {
+                Ops ops = uidState.pkgOps.get(packageName);
+
+                if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
+                        attributionTag)) && ops.bypass != null) {
+                    return new PackageVerificationResult(ops.bypass,
+                            ops.validAttributionTags.contains(attributionTag));
+                }
+            }
+        }
+
+        int callingUid = Binder.getCallingUid();
+
+        // Allow any attribution tag for resolvable uids
+        int pkgUid;
+        if (Objects.equals(packageName, "com.android.shell")) {
+            // Special case for the shell which is a package but should be able
+            // to bypass app attribution tag restrictions.
+            pkgUid = Process.SHELL_UID;
+        } else {
+            pkgUid = resolveUid(packageName);
+        }
+        if (pkgUid != Process.INVALID_UID) {
+            if (pkgUid != UserHandle.getAppId(uid)) {
+                Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+                        + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+                String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+                throw new SecurityException("Specified package \"" + packageName + "\" under uid "
+                        +  UserHandle.getAppId(uid) + otherUidMessage);
+            }
+            return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
+                    /* isAttributionTagValid */ true);
+        }
+
+        int userId = UserHandle.getUserId(uid);
+        RestrictionBypass bypass = null;
+        boolean isAttributionTagValid = false;
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+            AndroidPackage pkg = pmInt.getPackage(packageName);
+            if (pkg != null) {
+                isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
+                pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
+                bypass = getBypassforPackage(pkg);
+            }
+            if (!isAttributionTagValid) {
+                AndroidPackage proxyPkg = proxyPackageName != null
+                        ? pmInt.getPackage(proxyPackageName) : null;
+                // Re-check in proxy.
+                isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
+                String msg;
+                if (pkg != null && isAttributionTagValid) {
+                    msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
+                            + " package " + proxyPackageName + ", this is not advised";
+                } else if (pkg != null) {
+                    msg = "attributionTag " + attributionTag + " not declared in manifest of "
+                            + packageName;
+                } else {
+                    msg = "package " + packageName + " not found, can't check for "
+                            + "attributionTag " + attributionTag;
+                }
+
+                try {
+                    if (!mPlatformCompat.isChangeEnabledByPackageName(
+                            SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
+                            userId) || !mPlatformCompat.isChangeEnabledByUid(
+                                    SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
+                            callingUid)) {
+                        // Do not override tags if overriding is not enabled for this package
+                        isAttributionTagValid = true;
+                    }
+                    Slog.e(TAG, msg);
+                } catch (RemoteException neverHappens) {
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        if (pkgUid != uid) {
+            Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+                    + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+            String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+            throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
+                    + otherUidMessage);
+        }
+
+        return new PackageVerificationResult(bypass, isAttributionTagValid);
+    }
+
+    private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
+            @Nullable String attributionTag) {
+        if (pkg == null) {
+            return false;
+        } else if (attributionTag == null) {
+            return true;
+        }
+        if (pkg.getAttributions() != null) {
+            int numAttributions = pkg.getAttributions().size();
+            for (int i = 0; i < numAttributions; i++) {
+                if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Get (and potentially create) ops.
+     *
+     * @param uid The uid the package belongs to
+     * @param packageName The name of the package
+     * @param attributionTag attribution tag
+     * @param isAttributionTagValid whether the given attribution tag is valid
+     * @param bypass When to bypass certain op restrictions (can be null if edit == false)
+     * @param edit If an ops does not exist, create the ops?
+
+     * @return The ops
+     */
+    private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
+            boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
+        UidState uidState = getUidStateLocked(uid, edit);
+        if (uidState == null) {
+            return null;
+        }
+
+        if (uidState.pkgOps == null) {
+            if (!edit) {
+                return null;
+            }
+            uidState.pkgOps = new ArrayMap<>();
+        }
+
+        Ops ops = uidState.pkgOps.get(packageName);
+        if (ops == null) {
+            if (!edit) {
+                return null;
+            }
+            ops = new Ops(packageName, uidState);
+            uidState.pkgOps.put(packageName, ops);
+        }
+
+        if (edit) {
+            if (bypass != null) {
+                ops.bypass = bypass;
+            }
+
+            if (attributionTag != null) {
+                ops.knownAttributionTags.add(attributionTag);
+                if (isAttributionTagValid) {
+                    ops.validAttributionTags.add(attributionTag);
+                } else {
+                    ops.validAttributionTags.remove(attributionTag);
+                }
+            }
+        }
+
+        return ops;
+    }
+
+    @Override
+    public void scheduleWriteLocked() {
+        if (!mWriteScheduled) {
+            mWriteScheduled = true;
+            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+        }
+    }
+
+    @Override
+    public void scheduleFastWriteLocked() {
+        if (!mFastWriteScheduled) {
+            mWriteScheduled = true;
+            mFastWriteScheduled = true;
+            mHandler.removeCallbacks(mWriteRunner);
+            mHandler.postDelayed(mWriteRunner, 10*1000);
+        }
+    }
+
+    /**
+     * Get the state of an op for a uid.
+     *
+     * @param code The code of the op
+     * @param uid The uid the of the package
+     * @param packageName The package name for which to get the state for
+     * @param attributionTag The attribution tag
+     * @param isAttributionTagValid Whether the given attribution tag is valid
+     * @param bypass When to bypass certain op restrictions (can be null if edit == false)
+     * @param edit Iff {@code true} create the {@link Op} object if not yet created
+     *
+     * @return The {@link Op state} of the op
+     */
+    private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, boolean isAttributionTagValid,
+            @Nullable RestrictionBypass bypass, boolean edit) {
+        Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
+                edit);
+        if (ops == null) {
+            return null;
+        }
+        return getOpLocked(ops, code, uid, edit);
+    }
+
+    private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
+        Op op = ops.get(code);
+        if (op == null) {
+            if (!edit) {
+                return null;
+            }
+            op = new Op(ops.uidState, ops.packageName, code, uid);
+            ops.put(code, op);
+        }
+        if (edit) {
+            scheduleWriteLocked();
+        }
+        return op;
+    }
+
+    private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
+        if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
+            return false;
+        }
+        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+        return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
+    }
+
+    private boolean isOpRestrictedLocked(int uid, int code, String packageName,
+            String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
+        int restrictionSetCount = mOpGlobalRestrictions.size();
+
+        for (int i = 0; i < restrictionSetCount; i++) {
+            ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
+            if (restrictionState.hasRestriction(code)) {
+                return true;
+            }
+        }
+
+        int userHandle = UserHandle.getUserId(uid);
+        restrictionSetCount = mOpUserRestrictions.size();
+
+        for (int i = 0; i < restrictionSetCount; i++) {
+            // For each client, check that the given op is not restricted, or that the given
+            // package is exempt from the restriction.
+            ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
+            if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
+                    isCheckOp)) {
+                RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
+                if (opBypass != null) {
+                    // If we are the system, bypass user restrictions for certain codes
+                    synchronized (this) {
+                        if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
+                            return false;
+                        }
+                        if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
+                            return false;
+                        }
+                        if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
+                                && appBypass.isRecordAudioRestrictionExcept) {
+                            return false;
+                        }
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void readState() {
+        synchronized (mFile) {
+            synchronized (this) {
+                FileInputStream stream;
+                try {
+                    stream = mFile.openRead();
+                } catch (FileNotFoundException e) {
+                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+                    return;
+                }
+                boolean success = false;
+                mUidStates.clear();
+                mAppOpsCheckingService.clearAllModes();
+                try {
+                    TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+                    int type;
+                    while ((type = parser.next()) != XmlPullParser.START_TAG
+                            && type != XmlPullParser.END_DOCUMENT) {
+                        ;
+                    }
+
+                    if (type != XmlPullParser.START_TAG) {
+                        throw new IllegalStateException("no start tag found");
+                    }
+
+                    mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
+
+                    int outerDepth = parser.getDepth();
+                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                            continue;
+                        }
+
+                        String tagName = parser.getName();
+                        if (tagName.equals("pkg")) {
+                            readPackage(parser);
+                        } else if (tagName.equals("uid")) {
+                            readUidOps(parser);
+                        } else {
+                            Slog.w(TAG, "Unknown element under <app-ops>: "
+                                    + parser.getName());
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    }
+                    success = true;
+                } catch (IllegalStateException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (NullPointerException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (NumberFormatException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (XmlPullParserException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (IndexOutOfBoundsException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } finally {
+                    if (!success) {
+                        mUidStates.clear();
+                        mAppOpsCheckingService.clearAllModes();
+                    }
+                    try {
+                        stream.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    @GuardedBy("this")
+    void upgradeRunAnyInBackgroundLocked() {
+        for (int i = 0; i < mUidStates.size(); i++) {
+            final UidState uidState = mUidStates.valueAt(i);
+            if (uidState == null) {
+                continue;
+            }
+            SparseIntArray opModes = uidState.getNonDefaultUidModes();
+            if (opModes != null) {
+                final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
+                if (idx >= 0) {
+                    uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+                            opModes.valueAt(idx));
+                }
+            }
+            if (uidState.pkgOps == null) {
+                continue;
+            }
+            boolean changed = false;
+            for (int j = 0; j < uidState.pkgOps.size(); j++) {
+                Ops ops = uidState.pkgOps.valueAt(j);
+                if (ops != null) {
+                    final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
+                    if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
+                        final Op copy = new Op(op.uidState, op.packageName,
+                                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
+                        copy.setMode(op.getMode());
+                        ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
+                        changed = true;
+                    }
+                }
+            }
+            if (changed) {
+                uidState.evalForegroundOps();
+            }
+        }
+    }
+
+    /**
+     * The interpretation of the default mode - MODE_DEFAULT - for OP_SCHEDULE_EXACT_ALARM is
+     * changing. Simultaneously, we want to change this op's mode from MODE_DEFAULT to MODE_ALLOWED
+     * for already installed apps. For newer apps, it will stay as MODE_DEFAULT.
+     */
+    @VisibleForTesting
+    @GuardedBy("this")
+    void upgradeScheduleExactAlarmLocked() {
+        final PermissionManagerServiceInternal pmsi = LocalServices.getService(
+                PermissionManagerServiceInternal.class);
+        final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+        final PackageManagerInternal pmi = getPackageManagerInternal();
+
+        final String[] packagesDeclaringPermission = pmsi.getAppOpPermissionPackages(
+                AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM));
+        final int[] userIds = umi.getUserIds();
+
+        for (final String pkg : packagesDeclaringPermission) {
+            for (int userId : userIds) {
+                final int uid = pmi.getPackageUid(pkg, 0, userId);
+
+                UidState uidState = mUidStates.get(uid);
+                if (uidState == null) {
+                    uidState = new UidState(uid);
+                    mUidStates.put(uid, uidState);
+                }
+                final int oldMode = uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM);
+                if (oldMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) {
+                    uidState.setUidMode(OP_SCHEDULE_EXACT_ALARM, MODE_ALLOWED);
+                }
+            }
+            // This appop is meant to be controlled at a uid level. So we leave package modes as
+            // they are.
+        }
+    }
+
+    private void upgradeLocked(int oldVersion) {
+        if (oldVersion >= CURRENT_VERSION) {
+            return;
+        }
+        Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
+        switch (oldVersion) {
+            case NO_VERSION:
+                upgradeRunAnyInBackgroundLocked();
+                // fall through
+            case 1:
+                upgradeScheduleExactAlarmLocked();
+                // fall through
+            case 2:
+                // for future upgrades
+        }
+        scheduleFastWriteLocked();
+    }
+
+    private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
+            XmlPullParserException, IOException {
+        final int uid = parser.getAttributeInt(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                final int code = parser.getAttributeInt(null, "n");
+                final int mode = parser.getAttributeInt(null, "m");
+                setUidMode(code, uid, mode);
+            } else {
+                Slog.w(TAG, "Unknown element under <uid-ops>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readPackage(TypedXmlPullParser parser)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        String pkgName = parser.getAttributeValue(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("uid")) {
+                readUid(parser, pkgName);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readUid(TypedXmlPullParser parser, String pkgName)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int uid = parser.getAttributeInt(null, "n");
+        final UidState uidState = getUidStateLocked(uid, true);
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                readOp(parser, uidState, pkgName);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+        uidState.evalForegroundOps();
+    }
+
+    private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
+            @Nullable String attribution)
+            throws NumberFormatException, IOException, XmlPullParserException {
+        final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
+
+        final long key = parser.getAttributeLong(null, "n");
+        final int uidState = extractUidStateFromKey(key);
+        final int opFlags = extractFlagsFromKey(key);
+
+        final long accessTime = parser.getAttributeLong(null, "t", 0);
+        final long rejectTime = parser.getAttributeLong(null, "r", 0);
+        final long accessDuration = parser.getAttributeLong(null, "d", -1);
+        final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
+        final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
+        final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
+
+        if (accessTime > 0) {
+            attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
+                    proxyAttributionTag, uidState, opFlags);
+        }
+        if (rejectTime > 0) {
+            attributedOp.rejected(rejectTime, uidState, opFlags);
+        }
+    }
+
+    private void readOp(TypedXmlPullParser parser,
+            @NonNull UidState uidState, @NonNull String pkgName)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int opCode = parser.getAttributeInt(null, "n");
+        Op op = new Op(uidState, pkgName, opCode, uidState.uid);
+
+        final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
+        op.setMode(mode);
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals("st")) {
+                readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
+            } else {
+                Slog.w(TAG, "Unknown element under <op>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+
+        if (uidState.pkgOps == null) {
+            uidState.pkgOps = new ArrayMap<>();
+        }
+        Ops ops = uidState.pkgOps.get(pkgName);
+        if (ops == null) {
+            ops = new Ops(pkgName, uidState);
+            uidState.pkgOps.put(pkgName, ops);
+        }
+        ops.put(op.op, op);
+    }
+
+    void writeState() {
+        synchronized (mFile) {
+            FileOutputStream stream;
+            try {
+                stream = mFile.startWrite();
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to write state: " + e);
+                return;
+            }
+
+            List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
+
+            try {
+                TypedXmlSerializer out = Xml.resolveSerializer(stream);
+                out.startDocument(null, true);
+                out.startTag(null, "app-ops");
+                out.attributeInt(null, "v", CURRENT_VERSION);
+
+                SparseArray<SparseIntArray> uidStatesClone;
+                synchronized (this) {
+                    uidStatesClone = new SparseArray<>(mUidStates.size());
+
+                    final int uidStateCount = mUidStates.size();
+                    for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+                        UidState uidState = mUidStates.valueAt(uidStateNum);
+                        int uid = mUidStates.keyAt(uidStateNum);
+
+                        SparseIntArray opModes = uidState.getNonDefaultUidModes();
+                        if (opModes != null && opModes.size() > 0) {
+                            uidStatesClone.put(uid, opModes);
+                        }
+                    }
+                }
+
+                final int uidStateCount = uidStatesClone.size();
+                for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+                    SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
+                    if (opModes != null && opModes.size() > 0) {
+                        out.startTag(null, "uid");
+                        out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
+                        final int opCount = opModes.size();
+                        for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
+                            final int op = opModes.keyAt(opCountNum);
+                            final int mode = opModes.valueAt(opCountNum);
+                            out.startTag(null, "op");
+                            out.attributeInt(null, "n", op);
+                            out.attributeInt(null, "m", mode);
+                            out.endTag(null, "op");
+                        }
+                        out.endTag(null, "uid");
+                    }
+                }
+
+                if (allOps != null) {
+                    String lastPkg = null;
+                    for (int i=0; i<allOps.size(); i++) {
+                        AppOpsManager.PackageOps pkg = allOps.get(i);
+                        if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
+                            if (lastPkg != null) {
+                                out.endTag(null, "pkg");
+                            }
+                            lastPkg = pkg.getPackageName();
+                            if (lastPkg != null) {
+                                out.startTag(null, "pkg");
+                                out.attribute(null, "n", lastPkg);
+                            }
+                        }
+                        out.startTag(null, "uid");
+                        out.attributeInt(null, "n", pkg.getUid());
+                        List<AppOpsManager.OpEntry> ops = pkg.getOps();
+                        for (int j=0; j<ops.size(); j++) {
+                            AppOpsManager.OpEntry op = ops.get(j);
+                            out.startTag(null, "op");
+                            out.attributeInt(null, "n", op.getOp());
+                            if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
+                                out.attributeInt(null, "m", op.getMode());
+                            }
+
+                            for (String attributionTag : op.getAttributedOpEntries().keySet()) {
+                                final AttributedOpEntry attribution =
+                                        op.getAttributedOpEntries().get(attributionTag);
+
+                                final ArraySet<Long> keys = attribution.collectKeys();
+
+                                final int keyCount = keys.size();
+                                for (int k = 0; k < keyCount; k++) {
+                                    final long key = keys.valueAt(k);
+
+                                    final int uidState = AppOpsManager.extractUidStateFromKey(key);
+                                    final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+                                    final long accessTime = attribution.getLastAccessTime(uidState,
+                                            uidState, flags);
+                                    final long rejectTime = attribution.getLastRejectTime(uidState,
+                                            uidState, flags);
+                                    final long accessDuration = attribution.getLastDuration(
+                                            uidState, uidState, flags);
+                                    // Proxy information for rejections is not backed up
+                                    final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
+                                            uidState, uidState, flags);
+
+                                    if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
+                                            && proxy == null) {
+                                        continue;
+                                    }
+
+                                    String proxyPkg = null;
+                                    String proxyAttributionTag = null;
+                                    int proxyUid = Process.INVALID_UID;
+                                    if (proxy != null) {
+                                        proxyPkg = proxy.getPackageName();
+                                        proxyAttributionTag = proxy.getAttributionTag();
+                                        proxyUid = proxy.getUid();
+                                    }
+
+                                    out.startTag(null, "st");
+                                    if (attributionTag != null) {
+                                        out.attribute(null, "id", attributionTag);
+                                    }
+                                    out.attributeLong(null, "n", key);
+                                    if (accessTime > 0) {
+                                        out.attributeLong(null, "t", accessTime);
+                                    }
+                                    if (rejectTime > 0) {
+                                        out.attributeLong(null, "r", rejectTime);
+                                    }
+                                    if (accessDuration > 0) {
+                                        out.attributeLong(null, "d", accessDuration);
+                                    }
+                                    if (proxyPkg != null) {
+                                        out.attribute(null, "pp", proxyPkg);
+                                    }
+                                    if (proxyAttributionTag != null) {
+                                        out.attribute(null, "pc", proxyAttributionTag);
+                                    }
+                                    if (proxyUid >= 0) {
+                                        out.attributeInt(null, "pu", proxyUid);
+                                    }
+                                    out.endTag(null, "st");
+                                }
+                            }
+
+                            out.endTag(null, "op");
+                        }
+                        out.endTag(null, "uid");
+                    }
+                    if (lastPkg != null) {
+                        out.endTag(null, "pkg");
+                    }
+                }
+
+                out.endTag(null, "app-ops");
+                out.endDocument();
+                mFile.finishWrite(stream);
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to write state, restoring backup.", e);
+                mFile.failWrite(stream);
+            }
+        }
+        mHistoricalRegistry.writeAndClearDiscreteHistory();
+    }
+
     static class Shell extends ShellCommand {
         final IAppOpsService mInterface;
         final AppOpsService mInternal;
@@ -1178,6 +4371,7 @@
         int mode;
         int packageUid;
         int nonpackageUid;
+        final static Binder sBinder = new Binder();
         IBinder mToken;
         boolean targetsUid;
 
@@ -1198,7 +4392,7 @@
             dumpCommandHelp(pw);
         }
 
-        static int strOpToOp(String op, PrintWriter err) {
+        static private int strOpToOp(String op, PrintWriter err) {
             try {
                 return AppOpsManager.strOpToOp(op);
             } catch (IllegalArgumentException e) {
@@ -1395,24 +4589,6 @@
         pw.println("              not specified, the current user is assumed.");
     }
 
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        mAppOpsService.dump(fd, pw, args);
-
-        pw.println();
-        if (mCheckOpsDelegateDispatcher.mPolicy != null
-                && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
-            AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
-            policy.dumpTags(pw);
-        } else {
-            pw.println("  AppOps policy not set.");
-        }
-
-        if (mAudioRestrictionManager.hasActiveRestrictions()) {
-            pw.println();
-            mAudioRestrictionManager.dump(pw);
-        }
-    }
     static int onShellCommand(Shell shell, String cmd) {
         if (cmd == null) {
             return shell.handleDefaultCommands(cmd);
@@ -1616,12 +4792,14 @@
                     return 0;
                 }
                 case "write-settings": {
-                    shell.mInternal.mAppOpsService
-                            .enforceManageAppOpsModes(Binder.getCallingPid(),
+                    shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
                             Binder.getCallingUid(), -1);
                     final long token = Binder.clearCallingIdentity();
                     try {
-                        shell.mInternal.mAppOpsService.writeState();
+                        synchronized (shell.mInternal) {
+                            shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
+                        }
+                        shell.mInternal.writeState();
                         pw.println("Current settings written.");
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -1629,12 +4807,11 @@
                     return 0;
                 }
                 case "read-settings": {
-                    shell.mInternal.mAppOpsService
-                            .enforceManageAppOpsModes(Binder.getCallingPid(),
-                                    Binder.getCallingUid(), -1);
+                    shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
+                            Binder.getCallingUid(), -1);
                     final long token = Binder.clearCallingIdentity();
                     try {
-                        shell.mInternal.mAppOpsService.readState();
+                        shell.mInternal.readState();
                         pw.println("Last settings read.");
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -1680,70 +4857,877 @@
         return -1;
     }
 
+    private void dumpHelp(PrintWriter pw) {
+        pw.println("AppOps service (appops) dump options:");
+        pw.println("  -h");
+        pw.println("    Print this help text.");
+        pw.println("  --op [OP]");
+        pw.println("    Limit output to data associated with the given app op code.");
+        pw.println("  --mode [MODE]");
+        pw.println("    Limit output to data associated with the given app op mode.");
+        pw.println("  --package [PACKAGE]");
+        pw.println("    Limit output to data associated with the given package name.");
+        pw.println("  --attributionTag [attributionTag]");
+        pw.println("    Limit output to data associated with the given attribution tag.");
+        pw.println("  --include-discrete [n]");
+        pw.println("    Include discrete ops limited to n per dimension. Use zero for no limit.");
+        pw.println("  --watchers");
+        pw.println("    Only output the watcher sections.");
+        pw.println("  --history");
+        pw.println("    Only output history.");
+        pw.println("  --uid-state-changes");
+        pw.println("    Include logs about uid state changes.");
+    }
+
+    private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
+            @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
+            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
+        final int numAttributions = op.mAttributions.size();
+        for (int i = 0; i < numAttributions; i++) {
+            if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
+                    op.mAttributions.keyAt(i), filterAttributionTag)) {
+                continue;
+            }
+
+            pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
+            dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
+                    prefix + "  ");
+            pw.print(prefix + "]\n");
+        }
+    }
+
+    private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
+            @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
+            @NonNull Date date, @NonNull String prefix) {
+
+        final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
+                attributionTag).getAttributedOpEntries().get(attributionTag);
+
+        final ArraySet<Long> keys = entry.collectKeys();
+
+        final int keyCount = keys.size();
+        for (int k = 0; k < keyCount; k++) {
+            final long key = keys.valueAt(k);
+
+            final int uidState = AppOpsManager.extractUidStateFromKey(key);
+            final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+            final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
+            final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
+            final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
+            final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
+
+            String proxyPkg = null;
+            String proxyAttributionTag = null;
+            int proxyUid = Process.INVALID_UID;
+            if (proxy != null) {
+                proxyPkg = proxy.getPackageName();
+                proxyAttributionTag = proxy.getAttributionTag();
+                proxyUid = proxy.getUid();
+            }
+
+            if (accessTime > 0) {
+                pw.print(prefix);
+                pw.print("Access: ");
+                pw.print(AppOpsManager.keyToString(key));
+                pw.print(" ");
+                date.setTime(accessTime);
+                pw.print(sdf.format(date));
+                pw.print(" (");
+                TimeUtils.formatDuration(accessTime - now, pw);
+                pw.print(")");
+                if (accessDuration > 0) {
+                    pw.print(" duration=");
+                    TimeUtils.formatDuration(accessDuration, pw);
+                }
+                if (proxyUid >= 0) {
+                    pw.print(" proxy[");
+                    pw.print("uid=");
+                    pw.print(proxyUid);
+                    pw.print(", pkg=");
+                    pw.print(proxyPkg);
+                    pw.print(", attributionTag=");
+                    pw.print(proxyAttributionTag);
+                    pw.print("]");
+                }
+                pw.println();
+            }
+
+            if (rejectTime > 0) {
+                pw.print(prefix);
+                pw.print("Reject: ");
+                pw.print(AppOpsManager.keyToString(key));
+                date.setTime(rejectTime);
+                pw.print(sdf.format(date));
+                pw.print(" (");
+                TimeUtils.formatDuration(rejectTime - now, pw);
+                pw.print(")");
+                if (proxyUid >= 0) {
+                    pw.print(" proxy[");
+                    pw.print("uid=");
+                    pw.print(proxyUid);
+                    pw.print(", pkg=");
+                    pw.print(proxyPkg);
+                    pw.print(", attributionTag=");
+                    pw.print(proxyAttributionTag);
+                    pw.print("]");
+                }
+                pw.println();
+            }
+        }
+
+        final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+        if (attributedOp.isRunning()) {
+            long earliestElapsedTime = Long.MAX_VALUE;
+            long maxNumStarts = 0;
+            int numInProgressEvents = attributedOp.mInProgressEvents.size();
+            for (int i = 0; i < numInProgressEvents; i++) {
+                AttributedOp.InProgressStartOpEvent event =
+                        attributedOp.mInProgressEvents.valueAt(i);
+
+                earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
+                maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
+            }
+
+            pw.print(prefix + "Running start at: ");
+            TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
+            pw.println();
+
+            if (maxNumStarts > 1) {
+                pw.print(prefix + "startNesting=");
+                pw.println(maxNumStarts);
+            }
+        }
+    }
+
+    @NeverCompile // Avoid size overhead of debugging code.
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+
+        int dumpOp = OP_NONE;
+        String dumpPackage = null;
+        String dumpAttributionTag = null;
+        int dumpUid = Process.INVALID_UID;
+        int dumpMode = -1;
+        boolean dumpWatchers = false;
+        // TODO ntmyren: Remove the dumpHistory and dumpFilter
+        boolean dumpHistory = false;
+        boolean includeDiscreteOps = false;
+        boolean dumpUidStateChangeLogs = false;
+        int nDiscreteOps = 10;
+        @HistoricalOpsRequestFilter int dumpFilter = 0;
+        boolean dumpAll = false;
+
+        if (args != null) {
+            for (int i = 0; i < args.length; i++) {
+                String arg = args[i];
+                if ("-h".equals(arg)) {
+                    dumpHelp(pw);
+                    return;
+                } else if ("-a".equals(arg)) {
+                    // dump all data
+                    dumpAll = true;
+                } else if ("--op".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --op option");
+                        return;
+                    }
+                    dumpOp = Shell.strOpToOp(args[i], pw);
+                    dumpFilter |= FILTER_BY_OP_NAMES;
+                    if (dumpOp < 0) {
+                        return;
+                    }
+                } else if ("--package".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --package option");
+                        return;
+                    }
+                    dumpPackage = args[i];
+                    dumpFilter |= FILTER_BY_PACKAGE_NAME;
+                    try {
+                        dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
+                                PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
+                                0);
+                    } catch (RemoteException e) {
+                    }
+                    if (dumpUid < 0) {
+                        pw.println("Unknown package: " + dumpPackage);
+                        return;
+                    }
+                    dumpUid = UserHandle.getAppId(dumpUid);
+                    dumpFilter |= FILTER_BY_UID;
+                } else if ("--attributionTag".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --attributionTag option");
+                        return;
+                    }
+                    dumpAttributionTag = args[i];
+                    dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
+                } else if ("--mode".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --mode option");
+                        return;
+                    }
+                    dumpMode = Shell.strModeToMode(args[i], pw);
+                    if (dumpMode < 0) {
+                        return;
+                    }
+                } else if ("--watchers".equals(arg)) {
+                    dumpWatchers = true;
+                } else if ("--include-discrete".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --include-discrete option");
+                        return;
+                    }
+                    try {
+                        nDiscreteOps = Integer.valueOf(args[i]);
+                    } catch (NumberFormatException e) {
+                        pw.println("Wrong parameter: " + args[i]);
+                        return;
+                    }
+                    includeDiscreteOps = true;
+                } else if ("--history".equals(arg)) {
+                    dumpHistory = true;
+                } else if (arg.length() > 0 && arg.charAt(0) == '-') {
+                    pw.println("Unknown option: " + arg);
+                    return;
+                } else if ("--uid-state-changes".equals(arg)) {
+                    dumpUidStateChangeLogs = true;
+                } else {
+                    pw.println("Unknown command: " + arg);
+                    return;
+                }
+            }
+        }
+
+        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+        final Date date = new Date();
+        synchronized (this) {
+            pw.println("Current AppOps Service state:");
+            if (!dumpHistory && !dumpWatchers) {
+                mConstants.dump(pw);
+            }
+            pw.println();
+            final long now = System.currentTimeMillis();
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            final long nowUptime = SystemClock.uptimeMillis();
+            boolean needSep = false;
+            if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
+                    && !dumpHistory) {
+                pw.println("  Profile owners:");
+                for (int poi = 0; poi < mProfileOwners.size(); poi++) {
+                    pw.print("    User #");
+                    pw.print(mProfileOwners.keyAt(poi));
+                    pw.print(": ");
+                    UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
+                    pw.println();
+                }
+                pw.println();
+            }
+
+            if (!dumpHistory) {
+                needSep |= mAppOpsCheckingService.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
+            }
+
+            if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
+                boolean printedHeader = false;
+                for (int i = 0; i < mModeWatchers.size(); i++) {
+                    final ModeCallback cb = mModeWatchers.valueAt(i);
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
+                        continue;
+                    }
+                    needSep = true;
+                    if (!printedHeader) {
+                        pw.println("  All op mode watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
+                    pw.print(": "); pw.println(cb);
+                }
+            }
+            if (mActiveWatchers.size() > 0 && dumpMode < 0) {
+                needSep = true;
+                boolean printedHeader = false;
+                for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
+                    final SparseArray<ActiveCallback> activeWatchers =
+                            mActiveWatchers.valueAt(watcherNum);
+                    if (activeWatchers.size() <= 0) {
+                        continue;
+                    }
+                    final ActiveCallback cb = activeWatchers.valueAt(0);
+                    if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                        continue;
+                    }
+                    if (!printedHeader) {
+                        pw.println("  All op active watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(
+                            mActiveWatchers.keyAt(watcherNum))));
+                    pw.println(" ->");
+                    pw.print("        [");
+                    final int opCount = activeWatchers.size();
+                    for (int opNum = 0; opNum < opCount; opNum++) {
+                        if (opNum > 0) {
+                            pw.print(' ');
+                        }
+                        pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
+                        if (opNum < opCount - 1) {
+                            pw.print(',');
+                        }
+                    }
+                    pw.println("]");
+                    pw.print("        ");
+                    pw.println(cb);
+                }
+            }
+            if (mStartedWatchers.size() > 0 && dumpMode < 0) {
+                needSep = true;
+                boolean printedHeader = false;
+
+                final int watchersSize = mStartedWatchers.size();
+                for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
+                    final SparseArray<StartedCallback> startedWatchers =
+                            mStartedWatchers.valueAt(watcherNum);
+                    if (startedWatchers.size() <= 0) {
+                        continue;
+                    }
+
+                    final StartedCallback cb = startedWatchers.valueAt(0);
+                    if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                        continue;
+                    }
+
+                    if (!printedHeader) {
+                        pw.println("  All op started watchers:");
+                        printedHeader = true;
+                    }
+
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(
+                            mStartedWatchers.keyAt(watcherNum))));
+                    pw.println(" ->");
+
+                    pw.print("        [");
+                    final int opCount = startedWatchers.size();
+                    for (int opNum = 0; opNum < opCount; opNum++) {
+                        if (opNum > 0) {
+                            pw.print(' ');
+                        }
+
+                        pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
+                        if (opNum < opCount - 1) {
+                            pw.print(',');
+                        }
+                    }
+                    pw.println("]");
+
+                    pw.print("        ");
+                    pw.println(cb);
+                }
+            }
+            if (mNotedWatchers.size() > 0 && dumpMode < 0) {
+                needSep = true;
+                boolean printedHeader = false;
+                for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
+                    final SparseArray<NotedCallback> notedWatchers =
+                            mNotedWatchers.valueAt(watcherNum);
+                    if (notedWatchers.size() <= 0) {
+                        continue;
+                    }
+                    final NotedCallback cb = notedWatchers.valueAt(0);
+                    if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                        continue;
+                    }
+                    if (!printedHeader) {
+                        pw.println("  All op noted watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(
+                            mNotedWatchers.keyAt(watcherNum))));
+                    pw.println(" ->");
+                    pw.print("        [");
+                    final int opCount = notedWatchers.size();
+                    for (int opNum = 0; opNum < opCount; opNum++) {
+                        if (opNum > 0) {
+                            pw.print(' ');
+                        }
+                        pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
+                        if (opNum < opCount - 1) {
+                            pw.print(',');
+                        }
+                    }
+                    pw.println("]");
+                    pw.print("        ");
+                    pw.println(cb);
+                }
+            }
+            if (mAudioRestrictionManager.hasActiveRestrictions() && dumpOp < 0
+                    && dumpPackage != null && dumpMode < 0 && !dumpWatchers) {
+                needSep = mAudioRestrictionManager.dump(pw) || needSep;
+            }
+            if (needSep) {
+                pw.println();
+            }
+            for (int i=0; i<mUidStates.size(); i++) {
+                UidState uidState = mUidStates.valueAt(i);
+                final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+                final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+
+                if (dumpWatchers || dumpHistory) {
+                    continue;
+                }
+                if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
+                    boolean hasOp = dumpOp < 0 || (opModes != null
+                            && opModes.indexOfKey(dumpOp) >= 0);
+                    boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
+                    boolean hasMode = dumpMode < 0;
+                    if (!hasMode && opModes != null) {
+                        for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
+                            if (opModes.valueAt(opi) == dumpMode) {
+                                hasMode = true;
+                            }
+                        }
+                    }
+                    if (pkgOps != null) {
+                        for (int pkgi = 0;
+                                 (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
+                                 pkgi++) {
+                            Ops ops = pkgOps.valueAt(pkgi);
+                            if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
+                                hasOp = true;
+                            }
+                            if (!hasMode) {
+                                for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
+                                    if (ops.valueAt(opi).getMode() == dumpMode) {
+                                        hasMode = true;
+                                    }
+                                }
+                            }
+                            if (!hasPackage && dumpPackage.equals(ops.packageName)) {
+                                hasPackage = true;
+                            }
+                        }
+                    }
+                    if (uidState.foregroundOps != null && !hasOp) {
+                        if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
+                            hasOp = true;
+                        }
+                    }
+                    if (!hasOp || !hasPackage || !hasMode) {
+                        continue;
+                    }
+                }
+
+                pw.print("  Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
+                uidState.dump(pw, nowElapsed);
+                if (uidState.foregroundOps != null && (dumpMode < 0
+                        || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
+                    pw.println("    foregroundOps:");
+                    for (int j = 0; j < uidState.foregroundOps.size(); j++) {
+                        if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
+                            continue;
+                        }
+                        pw.print("      ");
+                        pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
+                        pw.print(": ");
+                        pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
+                    }
+                    pw.print("    hasForegroundWatchers=");
+                    pw.println(uidState.hasForegroundWatchers);
+                }
+                needSep = true;
+
+                if (opModes != null) {
+                    final int opModeCount = opModes.size();
+                    for (int j = 0; j < opModeCount; j++) {
+                        final int code = opModes.keyAt(j);
+                        final int mode = opModes.valueAt(j);
+                        if (dumpOp >= 0 && dumpOp != code) {
+                            continue;
+                        }
+                        if (dumpMode >= 0 && dumpMode != mode) {
+                            continue;
+                        }
+                        pw.print("      "); pw.print(AppOpsManager.opToName(code));
+                        pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
+                    }
+                }
+
+                if (pkgOps == null) {
+                    continue;
+                }
+
+                for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
+                    final Ops ops = pkgOps.valueAt(pkgi);
+                    if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
+                        continue;
+                    }
+                    boolean printedPackage = false;
+                    for (int j=0; j<ops.size(); j++) {
+                        final Op op = ops.valueAt(j);
+                        final int opCode = op.op;
+                        if (dumpOp >= 0 && dumpOp != opCode) {
+                            continue;
+                        }
+                        if (dumpMode >= 0 && dumpMode != op.getMode()) {
+                            continue;
+                        }
+                        if (!printedPackage) {
+                            pw.print("    Package "); pw.print(ops.packageName); pw.println(":");
+                            printedPackage = true;
+                        }
+                        pw.print("      "); pw.print(AppOpsManager.opToName(opCode));
+                        pw.print(" ("); pw.print(AppOpsManager.modeToName(op.getMode()));
+                        final int switchOp = AppOpsManager.opToSwitch(opCode);
+                        if (switchOp != opCode) {
+                            pw.print(" / switch ");
+                            pw.print(AppOpsManager.opToName(switchOp));
+                            final Op switchObj = ops.get(switchOp);
+                            int mode = switchObj == null
+                                    ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
+                            pw.print("="); pw.print(AppOpsManager.modeToName(mode));
+                        }
+                        pw.println("): ");
+                        dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
+                                sdf, date, "        ");
+                    }
+                }
+            }
+            if (needSep) {
+                pw.println();
+            }
+
+            boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
+            mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
+
+            if (!dumpHistory && !dumpWatchers) {
+                pw.println();
+                if (mCheckOpsDelegateDispatcher.mPolicy != null
+                        && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
+                    AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
+                    policy.dumpTags(pw);
+                } else {
+                    pw.println("  AppOps policy not set.");
+                }
+            }
+
+            if (dumpAll || dumpUidStateChangeLogs) {
+                pw.println();
+                pw.println("Uid State Changes Event Log:");
+                getUidStateTracker().dumpEvents(pw);
+            }
+        }
+
+        // Must not hold the appops lock
+        if (dumpHistory && !dumpWatchers) {
+            mHistoricalRegistry.dump("  ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
+                    dumpFilter);
+        }
+        if (includeDiscreteOps) {
+            pw.println("Discrete accesses: ");
+            mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
+                    dumpFilter, dumpOp, sdf, date, "  ", nDiscreteOps);
+        }
+    }
+
     @Override
     public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
-        mAppOpsService.setUserRestrictions(restrictions, token, userHandle);
+        checkSystemUid("setUserRestrictions");
+        Objects.requireNonNull(restrictions);
+        Objects.requireNonNull(token);
+        for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
+            String restriction = AppOpsManager.opToRestriction(i);
+            if (restriction != null) {
+                setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
+                        userHandle, null);
+            }
+        }
     }
 
     @Override
     public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
             PackageTagsList excludedPackageTags) {
-        mAppOpsService.setUserRestriction(code, restricted, token, userHandle,
-                excludedPackageTags);
+        if (Binder.getCallingPid() != Process.myPid()) {
+            mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
+                    Binder.getCallingPid(), Binder.getCallingUid(), null);
+        }
+        if (userHandle != UserHandle.getCallingUserId()) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission
+                    .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
+                && mContext.checkCallingOrSelfPermission(Manifest.permission
+                    .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
+                        + " INTERACT_ACROSS_USERS to interact cross user ");
+            }
+        }
+        verifyIncomingOp(code);
+        Objects.requireNonNull(token);
+        setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
+    }
+
+    private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
+            int userHandle, PackageTagsList excludedPackageTags) {
+        synchronized (AppOpsService.this) {
+            ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
+
+            if (restrictionState == null) {
+                try {
+                    restrictionState = new ClientUserRestrictionState(token);
+                } catch (RemoteException e) {
+                    return;
+                }
+                mOpUserRestrictions.put(token, restrictionState);
+            }
+
+            if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
+                    userHandle)) {
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsService::updateStartedOpModeForUser, this, code, restricted,
+                        userHandle));
+            }
+
+            if (restrictionState.isDefault()) {
+                mOpUserRestrictions.remove(token);
+                restrictionState.destroy();
+            }
+        }
+    }
+
+    private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
+        synchronized (AppOpsService.this) {
+            int numUids = mUidStates.size();
+            for (int uidNum = 0; uidNum < numUids; uidNum++) {
+                int uid = mUidStates.keyAt(uidNum);
+                if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
+                    continue;
+                }
+                updateStartedOpModeForUidLocked(code, restricted, uid);
+            }
+        }
+    }
+
+    private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
+        UidState uidState = mUidStates.get(uid);
+        if (uidState == null || uidState.pkgOps == null) {
+            return;
+        }
+
+        int numPkgOps = uidState.pkgOps.size();
+        for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
+            Ops ops = uidState.pkgOps.valueAt(pkgNum);
+            Op op = ops != null ? ops.get(code) : null;
+            if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
+                continue;
+            }
+            int numAttrTags = op.mAttributions.size();
+            for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
+                AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
+                if (restricted && attrOp.isRunning()) {
+                    attrOp.pause();
+                } else if (attrOp.isPaused()) {
+                    attrOp.resume();
+                }
+            }
+        }
+    }
+
+    private void notifyWatchersOfChange(int code, int uid) {
+        final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
+        synchronized (this) {
+            modeChangedListenerSet = mAppOpsCheckingService.getOpModeChangedListeners(code);
+            if (modeChangedListenerSet == null) {
+                return;
+            }
+        }
+
+        notifyOpChanged(modeChangedListenerSet,  code, uid, null);
     }
 
     @Override
     public void removeUser(int userHandle) throws RemoteException {
-        mAppOpsService.removeUser(userHandle);
+        checkSystemUid("removeUser");
+        synchronized (AppOpsService.this) {
+            final int tokenCount = mOpUserRestrictions.size();
+            for (int i = tokenCount - 1; i >= 0; i--) {
+                ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
+                opRestrictions.removeUser(userHandle);
+            }
+            removeUidsForUserLocked(userHandle);
+        }
     }
 
     @Override
     public boolean isOperationActive(int code, int uid, String packageName) {
-        return mAppOpsService.isOperationActive(code, uid, packageName);
+        if (Binder.getCallingUid() != uid) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return false;
+            }
+        }
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return false;
+        }
+
+        final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return false;
+        }
+        // TODO moltmann: Allow to check for attribution op activeness
+        synchronized (AppOpsService.this) {
+            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
+            if (pkgOps == null) {
+                return false;
+            }
+
+            Op op = pkgOps.get(code);
+            if (op == null) {
+                return false;
+            }
+
+            return op.isRunning();
+        }
     }
 
     @Override
     public boolean isProxying(int op, @NonNull String proxyPackageName,
             @NonNull String proxyAttributionTag, int proxiedUid,
             @NonNull String proxiedPackageName) {
-        return mAppOpsService.isProxying(op, proxyPackageName, proxyAttributionTag,
-                proxiedUid, proxiedPackageName);
+        Objects.requireNonNull(proxyPackageName);
+        Objects.requireNonNull(proxiedPackageName);
+        final long callingUid = Binder.getCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
+                    proxiedPackageName, new int[] {op});
+            if (packageOps == null || packageOps.isEmpty()) {
+                return false;
+            }
+            final List<OpEntry> opEntries = packageOps.get(0).getOps();
+            if (opEntries.isEmpty()) {
+                return false;
+            }
+            final OpEntry opEntry = opEntries.get(0);
+            if (!opEntry.isRunning()) {
+                return false;
+            }
+            final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
+                    OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
+            return proxyInfo != null && callingUid == proxyInfo.getUid()
+                    && proxyPackageName.equals(proxyInfo.getPackageName())
+                    && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
     public void resetPackageOpsNoHistory(@NonNull String packageName) {
-        mAppOpsService.resetPackageOpsNoHistory(packageName);
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "resetPackageOpsNoHistory");
+        synchronized (AppOpsService.this) {
+            final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
+                    UserHandle.getCallingUserId());
+            if (uid == Process.INVALID_UID) {
+                return;
+            }
+            UidState uidState = mUidStates.get(uid);
+            if (uidState == null || uidState.pkgOps == null) {
+                return;
+            }
+            Ops removedOps = uidState.pkgOps.remove(packageName);
+            mAppOpsCheckingService.removePackage(packageName, UserHandle.getUserId(uid));
+            if (removedOps != null) {
+                scheduleFastWriteLocked();
+            }
+        }
     }
 
     @Override
     public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
             long baseSnapshotInterval, int compressionStep) {
-        mAppOpsService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "setHistoryParameters");
+        // Must not hold the appops lock
+        mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
     }
 
     @Override
     public void offsetHistory(long offsetMillis) {
-        mAppOpsService.offsetHistory(offsetMillis);
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "offsetHistory");
+        // Must not hold the appops lock
+        mHistoricalRegistry.offsetHistory(offsetMillis);
+        mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
     }
 
     @Override
     public void addHistoricalOps(HistoricalOps ops) {
-        mAppOpsService.addHistoricalOps(ops);
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "addHistoricalOps");
+        // Must not hold the appops lock
+        mHistoricalRegistry.addHistoricalOps(ops);
     }
 
     @Override
     public void resetHistoryParameters() {
-        mAppOpsService.resetHistoryParameters();
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "resetHistoryParameters");
+        // Must not hold the appops lock
+        mHistoricalRegistry.resetHistoryParameters();
     }
 
     @Override
     public void clearHistory() {
-        mAppOpsService.clearHistory();
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "clearHistory");
+        // Must not hold the appops lock
+        mHistoricalRegistry.clearAllHistory();
     }
 
     @Override
     public void rebootHistory(long offlineDurationMillis) {
-        mAppOpsService.rebootHistory(offlineDurationMillis);
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "rebootHistory");
+
+        Preconditions.checkArgument(offlineDurationMillis >= 0);
+
+        // Must not hold the appops lock
+        mHistoricalRegistry.shutdown();
+
+        if (offlineDurationMillis > 0) {
+            SystemClock.sleep(offlineDurationMillis);
+        }
+
+        mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
+        mHistoricalRegistry.systemReady(mContext.getContentResolver());
+        mHistoricalRegistry.persistPendingHistory();
     }
 
     /**
@@ -1998,6 +5982,24 @@
         return false;
     }
 
+    @GuardedBy("this")
+    private void removeUidsForUserLocked(int userHandle) {
+        for (int i = mUidStates.size() - 1; i >= 0; --i) {
+            final int uid = mUidStates.keyAt(i);
+            if (UserHandle.getUserId(uid) == userHandle) {
+                mUidStates.valueAt(i).clear();
+                mUidStates.removeAt(i);
+            }
+        }
+    }
+
+    private void checkSystemUid(String function) {
+        int uid = Binder.getCallingUid();
+        if (uid != Process.SYSTEM_UID) {
+            throw new SecurityException(function + " must by called by the system");
+        }
+    }
+
     private static int resolveUid(String packageName)  {
         if (packageName == null) {
             return Process.INVALID_UID;
@@ -2018,43 +6020,184 @@
         return Process.INVALID_UID;
     }
 
+    private static String[] getPackagesForUid(int uid) {
+        String[] packageNames = null;
+
+        // Very early during boot the package manager is not yet or not yet fully started. At this
+        // time there are no packages yet.
+        if (AppGlobals.getPackageManager() != null) {
+            try {
+                packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
+            } catch (RemoteException e) {
+                /* ignore - local call */
+            }
+        }
+        if (packageNames == null) {
+            return EmptyArray.STRING;
+        }
+        return packageNames;
+    }
+
+    private final class ClientUserRestrictionState implements DeathRecipient {
+        private final IBinder token;
+
+        ClientUserRestrictionState(IBinder token)
+                throws RemoteException {
+            token.linkToDeath(this, 0);
+            this.token = token;
+        }
+
+        public boolean setRestriction(int code, boolean restricted,
+                PackageTagsList excludedPackageTags, int userId) {
+            return mAppOpsRestrictions.setUserRestriction(token, userId, code,
+                    restricted, excludedPackageTags);
+        }
+
+        public boolean hasRestriction(int code, String packageName, String attributionTag,
+                int userId, boolean isCheckOp) {
+            return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName,
+                    attributionTag, isCheckOp);
+        }
+
+        public void removeUser(int userId) {
+            mAppOpsRestrictions.clearUserRestrictions(token, userId);
+        }
+
+        public boolean isDefault() {
+            return !mAppOpsRestrictions.hasUserRestrictions(token);
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (AppOpsService.this) {
+                mAppOpsRestrictions.clearUserRestrictions(token);
+                mOpUserRestrictions.remove(token);
+                destroy();
+            }
+        }
+
+        public void destroy() {
+            token.unlinkToDeath(this, 0);
+        }
+    }
+
+    private final class ClientGlobalRestrictionState implements DeathRecipient {
+        final IBinder mToken;
+
+        ClientGlobalRestrictionState(IBinder token)
+                throws RemoteException {
+            token.linkToDeath(this, 0);
+            this.mToken = token;
+        }
+
+        boolean setRestriction(int code, boolean restricted) {
+            return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
+        }
+
+        boolean hasRestriction(int code) {
+            return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
+        }
+
+        boolean isDefault() {
+            return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
+        }
+
+        @Override
+        public void binderDied() {
+            mAppOpsRestrictions.clearGlobalRestrictions(mToken);
+            mOpGlobalRestrictions.remove(mToken);
+            destroy();
+        }
+
+        void destroy() {
+            mToken.unlinkToDeath(this, 0);
+        }
+    }
+
     private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal {
         @Override public void setDeviceAndProfileOwners(SparseIntArray owners) {
-            AppOpsService.this.mAppOpsService.setDeviceAndProfileOwners(owners);
+            synchronized (AppOpsService.this) {
+                mProfileOwners = owners;
+            }
         }
 
         @Override
         public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames,
                 boolean visible) {
-            AppOpsService.this.mAppOpsService
-                    .updateAppWidgetVisibility(uidPackageNames, visible);
+            AppOpsService.this.updateAppWidgetVisibility(uidPackageNames, visible);
         }
 
         @Override
         public void setUidModeFromPermissionPolicy(int code, int uid, int mode,
                 @Nullable IAppOpsCallback callback) {
-            AppOpsService.this.mAppOpsService.setUidMode(code, uid, mode, callback);
+            setUidMode(code, uid, mode, callback);
         }
 
         @Override
         public void setModeFromPermissionPolicy(int code, int uid, @NonNull String packageName,
                 int mode, @Nullable IAppOpsCallback callback) {
-            AppOpsService.this.mAppOpsService
-                    .setMode(code, uid, packageName, mode, callback);
+            setMode(code, uid, packageName, mode, callback);
         }
 
 
         @Override
         public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
-            AppOpsService.this.mAppOpsService
-                    .setGlobalRestriction(code, restricted, token);
+            if (Binder.getCallingPid() != Process.myPid()) {
+                // TODO instead of this enforcement put in AppOpsManagerInternal
+                throw new SecurityException("Only the system can set global restrictions");
+            }
+
+            synchronized (AppOpsService.this) {
+                ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
+
+                if (restrictionState == null) {
+                    try {
+                        restrictionState = new ClientGlobalRestrictionState(token);
+                    } catch (RemoteException  e) {
+                        return;
+                    }
+                    mOpGlobalRestrictions.put(token, restrictionState);
+                }
+
+                if (restrictionState.setRestriction(code, restricted)) {
+                    mHandler.sendMessage(PooledLambda.obtainMessage(
+                            AppOpsService::notifyWatchersOfChange, AppOpsService.this, code,
+                            UID_ANY));
+                    mHandler.sendMessage(PooledLambda.obtainMessage(
+                            AppOpsService::updateStartedOpModeForUser, AppOpsService.this,
+                            code, restricted, UserHandle.USER_ALL));
+                }
+
+                if (restrictionState.isDefault()) {
+                    mOpGlobalRestrictions.remove(token);
+                    restrictionState.destroy();
+                }
+            }
         }
 
         @Override
         public int getOpRestrictionCount(int code, UserHandle user, String pkg,
                 String attributionTag) {
-            return AppOpsService.this.mAppOpsService
-                    .getOpRestrictionCount(code, user, pkg, attributionTag);
+            int number = 0;
+            synchronized (AppOpsService.this) {
+                int numRestrictions = mOpUserRestrictions.size();
+                for (int i = 0; i < numRestrictions; i++) {
+                    if (mOpUserRestrictions.valueAt(i)
+                            .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
+                                    false)) {
+                        number++;
+                    }
+                }
+
+                numRestrictions = mOpGlobalRestrictions.size();
+                for (int i = 0; i < numRestrictions; i++) {
+                    if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
+                        number++;
+                    }
+                }
+            }
+
+            return number;
         }
     }
 
@@ -2350,7 +6493,7 @@
                     attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
         }
 
-        public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
+        public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
                 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@@ -2380,7 +6523,7 @@
                     proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
         }
 
-        private SyncNotedAppOp startDelegateProxyOperationImpl(IBinder clientId, int code,
+        private SyncNotedAppOp startDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
                 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@@ -2414,7 +6557,7 @@
                     AppOpsService.this::finishOperationImpl);
         }
 
-        public void finishProxyOperation(IBinder clientId, int code,
+        public void finishProxyOperation(@NonNull IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
@@ -2432,7 +6575,7 @@
             }
         }
 
-        private Void finishDelegateProxyOperationImpl(IBinder clientId, int code,
+        private Void finishDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
             mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
                     skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
deleted file mode 100644
index c3d2717..0000000
--- a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
+++ /dev/null
@@ -1,4742 +0,0 @@
-/*
- * Copyright (C) 2012 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.appop;
-
-import static android.app.AppOpsManager.AttributedOpEntry;
-import static android.app.AppOpsManager.AttributionFlags;
-import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
-import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
-import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
-import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
-import static android.app.AppOpsManager.FILTER_BY_UID;
-import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
-import static android.app.AppOpsManager.HistoricalOps;
-import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
-import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
-import static android.app.AppOpsManager.MODE_FOREGROUND;
-import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.Mode;
-import static android.app.AppOpsManager.OP_CAMERA;
-import static android.app.AppOpsManager.OP_FLAGS_ALL;
-import static android.app.AppOpsManager.OP_FLAG_SELF;
-import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
-import static android.app.AppOpsManager.OP_NONE;
-import static android.app.AppOpsManager.OP_PLAY_AUDIO;
-import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
-import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
-import static android.app.AppOpsManager.OP_VIBRATE;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
-import static android.app.AppOpsManager.OpEntry;
-import static android.app.AppOpsManager.OpEventProxyInfo;
-import static android.app.AppOpsManager.OpFlags;
-import static android.app.AppOpsManager.RestrictionBypass;
-import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
-import static android.app.AppOpsManager._NUM_OP;
-import static android.app.AppOpsManager.extractFlagsFromKey;
-import static android.app.AppOpsManager.extractUidStateFromKey;
-import static android.app.AppOpsManager.modeToName;
-import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
-import static android.app.AppOpsManager.opRestrictsRead;
-import static android.app.AppOpsManager.opToName;
-import static android.content.Intent.ACTION_PACKAGE_REMOVED;
-import static android.content.Intent.EXTRA_REPLACING;
-
-import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
-import android.app.AppGlobals;
-import android.app.AppOpsManager;
-import android.app.admin.DevicePolicyManagerInternal;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.PermissionInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.PackageTagsList;
-import android.os.Process;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.storage.StorageManagerInternal;
-import android.permission.PermissionManager;
-import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.KeyValueListParser;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-import android.util.TimeUtils;
-import android.util.Xml;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IAppOpsActiveCallback;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsNotedCallback;
-import com.android.internal.app.IAppOpsStartedCallback;
-import com.android.internal.compat.IPlatformCompat;
-import com.android.internal.os.Clock;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DumpUtils;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.LocalServices;
-import com.android.server.LockGuard;
-import com.android.server.SystemServerInitThreadPool;
-import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedAttribution;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import libcore.util.EmptyArray;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-class AppOpsServiceImpl implements AppOpsServiceInterface {
-    static final String TAG = "AppOps";
-    static final boolean DEBUG = false;
-
-    /**
-     * Sentinel integer version to denote that there was no appops.xml found on boot.
-     * This will happen when a device boots with no existing userdata.
-     */
-    private static final int NO_FILE_VERSION = -2;
-
-    /**
-     * Sentinel integer version to denote that there was no version in the appops.xml found on boot.
-     * This means the file is coming from a build before versioning was added.
-     */
-    private static final int NO_VERSION = -1;
-
-    /**
-     * Increment by one every time and add the corresponding upgrade logic in
-     * {@link #upgradeLocked(int)} below. The first version was 1.
-     */
-    @VisibleForTesting
-    static final int CURRENT_VERSION = 2;
-
-    /**
-     * This stores the version of appops.xml seen at boot. If this is smaller than
-     * {@link #CURRENT_VERSION}, then we will run {@link #upgradeLocked(int)} on startup.
-     */
-    private int mVersionAtBoot = NO_FILE_VERSION;
-
-    // Write at most every 30 minutes.
-    static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000;
-
-    // Constant meaning that any UID should be matched when dispatching callbacks
-    private static final int UID_ANY = -2;
-
-    private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
-            OP_PLAY_AUDIO,
-            OP_RECORD_AUDIO,
-            OP_CAMERA,
-            OP_VIBRATE,
-    };
-    private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
-
-    final Context mContext;
-    final AtomicFile mFile;
-    final Handler mHandler;
-
-    /**
-     * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
-     * objects
-     */
-    @GuardedBy("this")
-    final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
-            new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
-
-    /**
-     * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
-     * new objects
-     */
-    @GuardedBy("this")
-    final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
-            new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
-                    MAX_UNUSED_POOLED_OBJECTS);
-    @Nullable
-    private final DevicePolicyManagerInternal dpmi =
-            LocalServices.getService(DevicePolicyManagerInternal.class);
-
-    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
-            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
-
-    boolean mWriteScheduled;
-    boolean mFastWriteScheduled;
-    final Runnable mWriteRunner = new Runnable() {
-        public void run() {
-            synchronized (AppOpsServiceImpl.this) {
-                mWriteScheduled = false;
-                mFastWriteScheduled = false;
-                AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
-                    @Override
-                    protected Void doInBackground(Void... params) {
-                        writeState();
-                        return null;
-                    }
-                };
-                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-            }
-        }
-    };
-
-    @GuardedBy("this")
-    @VisibleForTesting
-    final SparseArray<UidState> mUidStates = new SparseArray<>();
-
-    volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
-
-    /*
-     * These are app op restrictions imposed per user from various parties.
-     */
-    private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
-            new ArrayMap<>();
-
-    /*
-     * These are app op restrictions imposed globally from various parties within the system.
-     */
-    private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
-            new ArrayMap<>();
-
-    SparseIntArray mProfileOwners;
-
-    /**
-     * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
-     * changed
-     */
-    private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
-
-    /**
-     * Package Manager internal. Access via {@link #getPackageManagerInternal()}
-     */
-    private @Nullable PackageManagerInternal mPackageManagerInternal;
-
-    /**
-     * Interface for app-op modes.
-     */
-    @VisibleForTesting
-    AppOpsCheckingServiceInterface mAppOpsServiceInterface;
-
-    /**
-     * Interface for app-op restrictions.
-     */
-    @VisibleForTesting
-    AppOpsRestrictions mAppOpsRestrictions;
-
-    private AppOpsUidStateTracker mUidStateTracker;
-
-    /**
-     * Hands the definition of foreground and uid states
-     */
-    @GuardedBy("this")
-    public AppOpsUidStateTracker getUidStateTracker() {
-        if (mUidStateTracker == null) {
-            mUidStateTracker = new AppOpsUidStateTrackerImpl(
-                    LocalServices.getService(ActivityManagerInternal.class),
-                    mHandler,
-                    r -> {
-                        synchronized (AppOpsServiceImpl.this) {
-                            r.run();
-                        }
-                    },
-                    Clock.SYSTEM_CLOCK, mConstants);
-
-            mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
-                    this::onUidStateChanged);
-        }
-        return mUidStateTracker;
-    }
-
-    /**
-     * All times are in milliseconds. These constants are kept synchronized with the system
-     * global Settings. Any access to this class or its fields should be done while
-     * holding the AppOpsService lock.
-     */
-    final class Constants extends ContentObserver {
-
-        /**
-         * How long we want for a drop in uid state from top to settle before applying it.
-         *
-         * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
-         */
-        public long TOP_STATE_SETTLE_TIME;
-
-        /**
-         * How long we want for a drop in uid state from foreground to settle before applying it.
-         *
-         * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
-         */
-        public long FG_SERVICE_STATE_SETTLE_TIME;
-
-        /**
-         * How long we want for a drop in uid state from background to settle before applying it.
-         *
-         * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
-         */
-        public long BG_STATE_SETTLE_TIME;
-
-        private final KeyValueListParser mParser = new KeyValueListParser(',');
-        private ContentResolver mResolver;
-
-        Constants(Handler handler) {
-            super(handler);
-            updateConstants();
-        }
-
-        public void startMonitoring(ContentResolver resolver) {
-            mResolver = resolver;
-            mResolver.registerContentObserver(
-                    Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
-                    false, this);
-            updateConstants();
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            updateConstants();
-        }
-
-        private void updateConstants() {
-            String value = mResolver != null ? Settings.Global.getString(mResolver,
-                    Settings.Global.APP_OPS_CONSTANTS) : "";
-
-            synchronized (AppOpsServiceImpl.this) {
-                try {
-                    mParser.setString(value);
-                } catch (IllegalArgumentException e) {
-                    // Failed to parse the settings string, log this and move on
-                    // with defaults.
-                    Slog.e(TAG, "Bad app ops settings", e);
-                }
-                TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
-                FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
-                BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
-            }
-        }
-
-        void dump(PrintWriter pw) {
-            pw.println("  Settings:");
-
-            pw.print("    ");
-            pw.print(KEY_TOP_STATE_SETTLE_TIME);
-            pw.print("=");
-            TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
-            pw.println();
-            pw.print("    ");
-            pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME);
-            pw.print("=");
-            TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
-            pw.println();
-            pw.print("    ");
-            pw.print(KEY_BG_STATE_SETTLE_TIME);
-            pw.print("=");
-            TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
-            pw.println();
-        }
-    }
-
-    @VisibleForTesting
-    final Constants mConstants;
-
-    @VisibleForTesting
-    final class UidState {
-        public final int uid;
-
-        public ArrayMap<String, Ops> pkgOps;
-
-        // true indicates there is an interested observer, false there isn't but it has such an op
-        //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
-        public SparseBooleanArray foregroundOps;
-        public boolean hasForegroundWatchers;
-
-        public UidState(int uid) {
-            this.uid = uid;
-        }
-
-        public void clear() {
-            mAppOpsServiceInterface.removeUid(uid);
-            if (pkgOps != null) {
-                for (String packageName : pkgOps.keySet()) {
-                    mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
-                }
-            }
-            pkgOps = null;
-        }
-
-        public boolean isDefault() {
-            boolean areAllPackageModesDefault = true;
-            if (pkgOps != null) {
-                for (String packageName : pkgOps.keySet()) {
-                    if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
-                            UserHandle.getUserId(uid))) {
-                        areAllPackageModesDefault = false;
-                        break;
-                    }
-                }
-            }
-            return (pkgOps == null || pkgOps.isEmpty())
-                    && mAppOpsServiceInterface.areUidModesDefault(uid)
-                    && areAllPackageModesDefault;
-        }
-
-        // Functions for uid mode access and manipulation.
-        public SparseIntArray getNonDefaultUidModes() {
-            return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
-        }
-
-        public int getUidMode(int op) {
-            return mAppOpsServiceInterface.getUidMode(uid, op);
-        }
-
-        public boolean setUidMode(int op, int mode) {
-            return mAppOpsServiceInterface.setUidMode(uid, op, mode);
-        }
-
-        @SuppressWarnings("GuardedBy")
-        int evalMode(int op, int mode) {
-            return getUidStateTracker().evalMode(uid, op, mode);
-        }
-
-        public void evalForegroundOps() {
-            foregroundOps = null;
-            foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
-            if (pkgOps != null) {
-                for (int i = pkgOps.size() - 1; i >= 0; i--) {
-                    foregroundOps = mAppOpsServiceInterface
-                            .evalForegroundPackageOps(pkgOps.valueAt(i).packageName,
-                                    foregroundOps,
-                                    UserHandle.getUserId(uid));
-                }
-            }
-            hasForegroundWatchers = false;
-            if (foregroundOps != null) {
-                for (int i = 0; i < foregroundOps.size(); i++) {
-                    if (foregroundOps.valueAt(i)) {
-                        hasForegroundWatchers = true;
-                        break;
-                    }
-                }
-            }
-        }
-
-        @SuppressWarnings("GuardedBy")
-        public int getState() {
-            return getUidStateTracker().getUidState(uid);
-        }
-
-        @SuppressWarnings("GuardedBy")
-        public void dump(PrintWriter pw, long nowElapsed) {
-            getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
-        }
-    }
-
-    static final class Ops extends SparseArray<Op> {
-        final String packageName;
-        final UidState uidState;
-
-        /**
-         * The restriction properties of the package. If {@code null} it could not have been read
-         * yet and has to be refreshed.
-         */
-        @Nullable RestrictionBypass bypass;
-
-        /** Lazily populated cache of attributionTags of this package */
-        final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
-
-        /**
-         * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
-         * than or equal to {@link #knownAttributionTags}.
-         */
-        final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
-
-        Ops(String _packageName, UidState _uidState) {
-            packageName = _packageName;
-            uidState = _uidState;
-        }
-    }
-
-    /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
-    private static final class PackageVerificationResult {
-
-        final RestrictionBypass bypass;
-        final boolean isAttributionTagValid;
-
-        PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
-            this.bypass = bypass;
-            this.isAttributionTagValid = isAttributionTagValid;
-        }
-    }
-
-    final class Op {
-        int op;
-        int uid;
-        final UidState uidState;
-        final @NonNull String packageName;
-
-        /** attributionTag -> AttributedOp */
-        final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
-
-        Op(UidState uidState, String packageName, int op, int uid) {
-            this.op = op;
-            this.uid = uid;
-            this.uidState = uidState;
-            this.packageName = packageName;
-        }
-
-        @Mode int getMode() {
-            return mAppOpsServiceInterface.getPackageMode(packageName, this.op,
-                    UserHandle.getUserId(this.uid));
-        }
-
-        void setMode(@Mode int mode) {
-            mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode,
-                    UserHandle.getUserId(this.uid));
-        }
-
-        void removeAttributionsWithNoTime() {
-            for (int i = mAttributions.size() - 1; i >= 0; i--) {
-                if (!mAttributions.valueAt(i).hasAnyTime()) {
-                    mAttributions.removeAt(i);
-                }
-            }
-        }
-
-        private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
-                @Nullable String attributionTag) {
-            AttributedOp attributedOp;
-
-            attributedOp = mAttributions.get(attributionTag);
-            if (attributedOp == null) {
-                attributedOp = new AttributedOp(AppOpsServiceImpl.this, attributionTag,
-                        parent);
-                mAttributions.put(attributionTag, attributedOp);
-            }
-
-            return attributedOp;
-        }
-
-        @NonNull
-        OpEntry createEntryLocked() {
-            final int numAttributions = mAttributions.size();
-
-            final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
-                    new ArrayMap<>(numAttributions);
-            for (int i = 0; i < numAttributions; i++) {
-                attributionEntries.put(mAttributions.keyAt(i),
-                        mAttributions.valueAt(i).createAttributedOpEntryLocked());
-            }
-
-            return new OpEntry(op, getMode(), attributionEntries);
-        }
-
-        @NonNull
-        OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
-            final int numAttributions = mAttributions.size();
-
-            final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
-            for (int i = 0; i < numAttributions; i++) {
-                if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
-                    attributionEntries.put(mAttributions.keyAt(i),
-                            mAttributions.valueAt(i).createAttributedOpEntryLocked());
-                    break;
-                }
-            }
-
-            return new OpEntry(op, getMode(), attributionEntries);
-        }
-
-        boolean isRunning() {
-            final int numAttributions = mAttributions.size();
-            for (int i = 0; i < numAttributions; i++) {
-                if (mAttributions.valueAt(i).isRunning()) {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-    }
-
-    final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
-    final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
-    final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
-    final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
-
-    final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient  {
-        /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
-        public static final int ALL_OPS = -2;
-
-        // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
-        // Otherwise we can just use the IBinder object.
-        private final IAppOpsCallback mCallback;
-
-        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
-                int callingUid, int callingPid) {
-            super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
-            this.mCallback = callback;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("ModeCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, getWatchingUid());
-            sb.append(" flags=0x");
-            sb.append(Integer.toHexString(getFlags()));
-            switch (getWatchedOpCode()) {
-                case OP_NONE:
-                    break;
-                case ALL_OPS:
-                    sb.append(" op=(all)");
-                    break;
-                default:
-                    sb.append(" op=");
-                    sb.append(opToName(getWatchedOpCode()));
-                    break;
-            }
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, getCallingUid());
-            sb.append(" pid=");
-            sb.append(getCallingPid());
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void unlinkToDeath() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingMode(mCallback);
-        }
-
-        @Override
-        public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
-            mCallback.opChanged(op, uid, packageName);
-        }
-    }
-
-    final class ActiveCallback implements DeathRecipient {
-        final IAppOpsActiveCallback mCallback;
-        final int mWatchingUid;
-        final int mCallingUid;
-        final int mCallingPid;
-
-        ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
-                int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("ActiveCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
-            sb.append(" pid=");
-            sb.append(mCallingPid);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void destroy() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingActive(mCallback);
-        }
-    }
-
-    final class StartedCallback implements DeathRecipient {
-        final IAppOpsStartedCallback mCallback;
-        final int mWatchingUid;
-        final int mCallingUid;
-        final int mCallingPid;
-
-        StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
-                int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("StartedCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
-            sb.append(" pid=");
-            sb.append(mCallingPid);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void destroy() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingStarted(mCallback);
-        }
-    }
-
-    final class NotedCallback implements DeathRecipient {
-        final IAppOpsNotedCallback mCallback;
-        final int mWatchingUid;
-        final int mCallingUid;
-        final int mCallingPid;
-
-        NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
-                int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("NotedCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
-            sb.append(" pid=");
-            sb.append(mCallingPid);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void destroy() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingNoted(mCallback);
-        }
-    }
-
-    /**
-     * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
-     */
-    static void onClientDeath(@NonNull AttributedOp attributedOp,
-            @NonNull IBinder clientId) {
-        attributedOp.onClientDeath(clientId);
-    }
-
-    AppOpsServiceImpl(File storagePath, Handler handler, Context context) {
-        mContext = context;
-
-        for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
-            int switchCode = AppOpsManager.opToSwitch(switchedCode);
-            mSwitchedOps.put(switchCode,
-                    ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
-        }
-        mAppOpsServiceInterface = new AppOpsCheckingServiceTracingDecorator(
-                new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps));
-        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
-                mAppOpsServiceInterface);
-
-        LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
-        mFile = new AtomicFile(storagePath, "appops");
-
-        mHandler = handler;
-        mConstants = new Constants(mHandler);
-        readState();
-    }
-
-    /**
-     * Handler for work when packages are removed or updated
-     */
-    private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            String pkgName = intent.getData().getEncodedSchemeSpecificPart();
-            int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
-
-            if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
-                synchronized (AppOpsServiceImpl.this) {
-                    UidState uidState = mUidStates.get(uid);
-                    if (uidState == null || uidState.pkgOps == null) {
-                        return;
-                    }
-                    mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
-                    Ops removedOps = uidState.pkgOps.remove(pkgName);
-                    if (removedOps != null) {
-                        scheduleFastWriteLocked();
-                    }
-                }
-            } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
-                AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
-                if (pkg == null) {
-                    return;
-                }
-
-                ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
-                ArraySet<String> attributionTags = new ArraySet<>();
-                attributionTags.add(null);
-                if (pkg.getAttributions() != null) {
-                    int numAttributions = pkg.getAttributions().size();
-                    for (int attributionNum = 0; attributionNum < numAttributions;
-                            attributionNum++) {
-                        ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
-                        attributionTags.add(attribution.getTag());
-
-                        int numInheritFrom = attribution.getInheritFrom().size();
-                        for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
-                                inheritFromNum++) {
-                            dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
-                                    attribution.getTag());
-                        }
-                    }
-                }
-
-                synchronized (AppOpsServiceImpl.this) {
-                    UidState uidState = mUidStates.get(uid);
-                    if (uidState == null || uidState.pkgOps == null) {
-                        return;
-                    }
-
-                    Ops ops = uidState.pkgOps.get(pkgName);
-                    if (ops == null) {
-                        return;
-                    }
-
-                    // Reset cached package properties to re-initialize when needed
-                    ops.bypass = null;
-                    ops.knownAttributionTags.clear();
-
-                    // Merge data collected for removed attributions into their successor
-                    // attributions
-                    int numOps = ops.size();
-                    for (int opNum = 0; opNum < numOps; opNum++) {
-                        Op op = ops.valueAt(opNum);
-
-                        int numAttributions = op.mAttributions.size();
-                        for (int attributionNum = numAttributions - 1; attributionNum >= 0;
-                                attributionNum--) {
-                            String attributionTag = op.mAttributions.keyAt(attributionNum);
-
-                            if (attributionTags.contains(attributionTag)) {
-                                // attribution still exist after upgrade
-                                continue;
-                            }
-
-                            String newAttributionTag = dstAttributionTags.get(attributionTag);
-
-                            AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
-                                    newAttributionTag);
-                            newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
-                            op.mAttributions.removeAt(attributionNum);
-
-                            scheduleFastWriteLocked();
-                        }
-                    }
-                }
-            }
-        }
-    };
-
-    @Override
-    public void systemReady() {
-        synchronized (this) {
-            upgradeLocked(mVersionAtBoot);
-        }
-
-        mConstants.startMonitoring(mContext.getContentResolver());
-        mHistoricalRegistry.systemReady(mContext.getContentResolver());
-
-        IntentFilter packageUpdateFilter = new IntentFilter();
-        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        packageUpdateFilter.addDataScheme("package");
-
-        mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
-                packageUpdateFilter, null, null);
-
-        synchronized (this) {
-            for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
-                int uid = mUidStates.keyAt(uidNum);
-                UidState uidState = mUidStates.valueAt(uidNum);
-
-                String[] pkgsInUid = getPackagesForUid(uidState.uid);
-                if (ArrayUtils.isEmpty(pkgsInUid)) {
-                    uidState.clear();
-                    mUidStates.removeAt(uidNum);
-                    scheduleFastWriteLocked();
-                    continue;
-                }
-
-                ArrayMap<String, Ops> pkgs = uidState.pkgOps;
-                if (pkgs == null) {
-                    continue;
-                }
-
-                int numPkgs = pkgs.size();
-                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
-                    String pkg = pkgs.keyAt(pkgNum);
-
-                    String action;
-                    if (!ArrayUtils.contains(pkgsInUid, pkg)) {
-                        action = Intent.ACTION_PACKAGE_REMOVED;
-                    } else {
-                        action = Intent.ACTION_PACKAGE_REPLACED;
-                    }
-
-                    SystemServerInitThreadPool.submit(
-                            () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
-                                    .setData(Uri.fromParts("package", pkg, null))
-                                    .putExtra(Intent.EXTRA_UID, uid)),
-                            "Update app-ops uidState in case package " + pkg + " changed");
-                }
-            }
-        }
-
-        final IntentFilter packageSuspendFilter = new IntentFilter();
-        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
-        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
-        mContext.registerReceiverAsUser(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
-                final String[] changedPkgs = intent.getStringArrayExtra(
-                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
-                    ArraySet<OnOpModeChangedListener> onModeChangedListeners;
-                    synchronized (AppOpsServiceImpl.this) {
-                        onModeChangedListeners =
-                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
-                        if (onModeChangedListeners == null) {
-                            continue;
-                        }
-                    }
-                    for (int i = 0; i < changedUids.length; i++) {
-                        final int changedUid = changedUids[i];
-                        final String changedPkg = changedPkgs[i];
-                        // We trust packagemanager to insert matching uid and packageNames in the
-                        // extras
-                        notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
-                    }
-                }
-            }
-        }, UserHandle.ALL, packageSuspendFilter, null, null);
-    }
-
-    @Override
-    public void packageRemoved(int uid, String packageName) {
-        synchronized (this) {
-            UidState uidState = mUidStates.get(uid);
-            if (uidState == null) {
-                return;
-            }
-
-            Ops removedOps = null;
-
-            // Remove any package state if such.
-            if (uidState.pkgOps != null) {
-                removedOps = uidState.pkgOps.remove(packageName);
-                mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
-            }
-
-            // If we just nuked the last package state check if the UID is valid.
-            if (removedOps != null && uidState.pkgOps.isEmpty()
-                    && getPackagesForUid(uid).length <= 0) {
-                uidState.clear();
-                mUidStates.remove(uid);
-            }
-
-            if (removedOps != null) {
-                scheduleFastWriteLocked();
-
-                final int numOps = removedOps.size();
-                for (int opNum = 0; opNum < numOps; opNum++) {
-                    final Op op = removedOps.valueAt(opNum);
-
-                    final int numAttributions = op.mAttributions.size();
-                    for (int attributionNum = 0; attributionNum < numAttributions;
-                            attributionNum++) {
-                        AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
-
-                        while (attributedOp.isRunning()) {
-                            attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
-                        }
-                        while (attributedOp.isPaused()) {
-                            attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
-                        }
-                    }
-                }
-            }
-        }
-
-        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
-                mHistoricalRegistry, uid, packageName));
-    }
-
-    @Override
-    public void uidRemoved(int uid) {
-        synchronized (this) {
-            if (mUidStates.indexOfKey(uid) >= 0) {
-                mUidStates.get(uid).clear();
-                mUidStates.remove(uid);
-                scheduleFastWriteLocked();
-            }
-        }
-    }
-
-    // The callback method from ForegroundPolicyInterface
-    private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
-        synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, true);
-
-            if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
-                for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
-                    if (!uidState.foregroundOps.valueAt(fgi)) {
-                        continue;
-                    }
-                    final int code = uidState.foregroundOps.keyAt(fgi);
-
-                    if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
-                            && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
-                        mHandler.sendMessage(PooledLambda.obtainMessage(
-                                AppOpsServiceImpl::notifyOpChangedForAllPkgsInUid,
-                                this, code, uidState.uid, true, null));
-                    } else if (uidState.pkgOps != null) {
-                        final ArraySet<OnOpModeChangedListener> listenerSet =
-                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
-                        if (listenerSet != null) {
-                            for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
-                                final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
-                                if ((listener.getFlags()
-                                        & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
-                                        || !listener.isWatchingUid(uidState.uid)) {
-                                    continue;
-                                }
-                                for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
-                                    final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
-                                    if (op == null) {
-                                        continue;
-                                    }
-                                    if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
-                                        mHandler.sendMessage(PooledLambda.obtainMessage(
-                                                AppOpsServiceImpl::notifyOpChanged,
-                                                this, listenerSet.valueAt(cbi), code, uidState.uid,
-                                                uidState.pkgOps.keyAt(pkgi)));
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (uidState != null && uidState.pkgOps != null) {
-                int numPkgs = uidState.pkgOps.size();
-                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
-                    Ops ops = uidState.pkgOps.valueAt(pkgNum);
-
-                    int numOps = ops.size();
-                    for (int opNum = 0; opNum < numOps; opNum++) {
-                        Op op = ops.valueAt(opNum);
-
-                        int numAttributions = op.mAttributions.size();
-                        for (int attributionNum = 0; attributionNum < numAttributions;
-                                attributionNum++) {
-                            AttributedOp attributedOp = op.mAttributions.valueAt(
-                                    attributionNum);
-
-                            attributedOp.onUidStateChanged(state);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Notify the proc state or capability has changed for a certain UID.
-     */
-    @Override
-    public void updateUidProcState(int uid, int procState,
-            @ActivityManager.ProcessCapability int capability) {
-        synchronized (this) {
-            getUidStateTracker().updateUidProcState(uid, procState, capability);
-            if (!mUidStates.contains(uid)) {
-                UidState uidState = new UidState(uid);
-                mUidStates.put(uid, uidState);
-                onUidStateChanged(uid,
-                        AppOpsUidStateTracker.processStateToUidState(procState), false);
-            }
-        }
-    }
-
-    @Override
-    public void shutdown() {
-        Slog.w(TAG, "Writing app ops before shutdown...");
-        boolean doWrite = false;
-        synchronized (this) {
-            if (mWriteScheduled) {
-                mWriteScheduled = false;
-                mFastWriteScheduled = false;
-                mHandler.removeCallbacks(mWriteRunner);
-                doWrite = true;
-            }
-        }
-        if (doWrite) {
-            writeState();
-        }
-
-        mHistoricalRegistry.shutdown();
-    }
-
-    private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
-        ArrayList<AppOpsManager.OpEntry> resOps = null;
-        if (ops == null) {
-            resOps = new ArrayList<>();
-            for (int j = 0; j < pkgOps.size(); j++) {
-                Op curOp = pkgOps.valueAt(j);
-                resOps.add(getOpEntryForResult(curOp));
-            }
-        } else {
-            for (int j = 0; j < ops.length; j++) {
-                Op curOp = pkgOps.get(ops[j]);
-                if (curOp != null) {
-                    if (resOps == null) {
-                        resOps = new ArrayList<>();
-                    }
-                    resOps.add(getOpEntryForResult(curOp));
-                }
-            }
-        }
-        return resOps;
-    }
-
-    @Nullable
-    private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
-            @Nullable int[] ops) {
-        final SparseIntArray opModes = uidState.getNonDefaultUidModes();
-        if (opModes == null) {
-            return null;
-        }
-
-        int opModeCount = opModes.size();
-        if (opModeCount == 0) {
-            return null;
-        }
-        ArrayList<AppOpsManager.OpEntry> resOps = null;
-        if (ops == null) {
-            resOps = new ArrayList<>();
-            for (int i = 0; i < opModeCount; i++) {
-                int code = opModes.keyAt(i);
-                resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
-            }
-        } else {
-            for (int j = 0; j < ops.length; j++) {
-                int code = ops[j];
-                if (opModes.indexOfKey(code) >= 0) {
-                    if (resOps == null) {
-                        resOps = new ArrayList<>();
-                    }
-                    resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
-                }
-            }
-        }
-        return resOps;
-    }
-
-    private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
-        return op.createEntryLocked();
-    }
-
-    @Override
-    public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
-        final int callingUid = Binder.getCallingUid();
-        final boolean hasAllPackageAccess = mContext.checkPermission(
-                Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
-                Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
-        ArrayList<AppOpsManager.PackageOps> res = null;
-        synchronized (this) {
-            final int uidStateCount = mUidStates.size();
-            for (int i = 0; i < uidStateCount; i++) {
-                UidState uidState = mUidStates.valueAt(i);
-                if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
-                    continue;
-                }
-                ArrayMap<String, Ops> packages = uidState.pkgOps;
-                final int packageCount = packages.size();
-                for (int j = 0; j < packageCount; j++) {
-                    Ops pkgOps = packages.valueAt(j);
-                    ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
-                    if (resOps != null) {
-                        if (res == null) {
-                            res = new ArrayList<>();
-                        }
-                        AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                                pkgOps.packageName, pkgOps.uidState.uid, resOps);
-                        // Caller can always see their packages and with a permission all.
-                        if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
-                            res.add(resPackage);
-                        }
-                    }
-                }
-            }
-        }
-        return res;
-    }
-
-    @Override
-    public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
-            int[] ops) {
-        enforceGetAppOpsStatsPermissionIfNeeded(uid, packageName);
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return Collections.emptyList();
-        }
-        synchronized (this) {
-            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
-                    /* edit */ false);
-            if (pkgOps == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
-            if (resOps == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<>();
-            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                    pkgOps.packageName, pkgOps.uidState.uid, resOps);
-            res.add(resPackage);
-            return res;
-        }
-    }
-
-    private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
-        final int callingUid = Binder.getCallingUid();
-        // We get to access everything
-        if (callingUid == Process.myPid()) {
-            return;
-        }
-        // Apps can access their own data
-        if (uid == callingUid && packageName != null
-                && checkPackage(uid, packageName) == MODE_ALLOWED) {
-            return;
-        }
-        // Otherwise, you need a permission...
-        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                Binder.getCallingPid(), callingUid, null);
-    }
-
-    /**
-     * Verify that historical appop request arguments are valid.
-     */
-    private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
-            String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
-            long endTimeMillis, int flags) {
-        if ((filter & FILTER_BY_UID) != 0) {
-            Preconditions.checkArgument(uid != Process.INVALID_UID);
-        } else {
-            Preconditions.checkArgument(uid == Process.INVALID_UID);
-        }
-
-        if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
-            Objects.requireNonNull(packageName);
-        } else {
-            Preconditions.checkArgument(packageName == null);
-        }
-
-        if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
-            Preconditions.checkArgument(attributionTag == null);
-        }
-
-        if ((filter & FILTER_BY_OP_NAMES) != 0) {
-            Objects.requireNonNull(opNames);
-        } else {
-            Preconditions.checkArgument(opNames == null);
-        }
-
-        Preconditions.checkFlagsArgument(filter,
-                FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
-                        | FILTER_BY_OP_NAMES);
-        Preconditions.checkArgumentNonnegative(beginTimeMillis);
-        Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
-        Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
-    }
-
-    @Override
-    public void getHistoricalOps(int uid, String packageName, String attributionTag,
-            List<String> opNames, int dataType, int filter, long beginTimeMillis,
-            long endTimeMillis, int flags, RemoteCallback callback) {
-        PackageManager pm = mContext.getPackageManager();
-
-        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
-                beginTimeMillis, endTimeMillis, flags);
-        Objects.requireNonNull(callback, "callback cannot be null");
-        ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
-        boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
-        if (!isSelfRequest) {
-            boolean isCallerInstrumented =
-                    ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
-            boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
-            boolean isCallerPermissionController;
-            try {
-                isCallerPermissionController = pm.getPackageUidAsUser(
-                        mContext.getPackageManager().getPermissionControllerPackageName(), 0,
-                        UserHandle.getUserId(Binder.getCallingUid()))
-                        == Binder.getCallingUid();
-            } catch (PackageManager.NameNotFoundException doesNotHappen) {
-                return;
-            }
-
-            boolean doesCallerHavePermission = mContext.checkPermission(
-                    android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
-                    Binder.getCallingPid(), Binder.getCallingUid())
-                    == PackageManager.PERMISSION_GRANTED;
-
-            if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
-                    && !doesCallerHavePermission) {
-                mHandler.post(() -> callback.sendResult(new Bundle()));
-                return;
-            }
-
-            mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                    Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
-        }
-
-        final String[] opNamesArray = (opNames != null)
-                ? opNames.toArray(new String[opNames.size()]) : null;
-
-        Set<String> attributionChainExemptPackages = null;
-        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
-            attributionChainExemptPackages =
-                    PermissionManager.getIndicatorExemptedPackages(mContext);
-        }
-
-        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
-                ? attributionChainExemptPackages.toArray(
-                new String[attributionChainExemptPackages.size()]) : null;
-
-        // Must not hold the appops lock
-        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
-                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
-                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
-                callback).recycleOnUse());
-    }
-
-    @Override
-    public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
-            List<String> opNames, int dataType, int filter, long beginTimeMillis,
-            long endTimeMillis, int flags, RemoteCallback callback) {
-        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
-                beginTimeMillis, endTimeMillis, flags);
-        Objects.requireNonNull(callback, "callback cannot be null");
-
-        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
-                Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
-
-        final String[] opNamesArray = (opNames != null)
-                ? opNames.toArray(new String[opNames.size()]) : null;
-
-        Set<String> attributionChainExemptPackages = null;
-        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
-            attributionChainExemptPackages =
-                    PermissionManager.getIndicatorExemptedPackages(mContext);
-        }
-
-        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
-                ? attributionChainExemptPackages.toArray(
-                new String[attributionChainExemptPackages.size()]) : null;
-
-        // Must not hold the appops lock
-        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
-                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
-                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
-                callback).recycleOnUse());
-    }
-
-    @Override
-    public void reloadNonHistoricalState() {
-        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
-                Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
-        writeState();
-        readState();
-    }
-
-    @Override
-    public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
-        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
-        synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, false);
-            if (uidState == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
-            if (resOps == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
-            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                    null, uidState.uid, resOps);
-            res.add(resPackage);
-            return res;
-        }
-    }
-
-    private void pruneOpLocked(Op op, int uid, String packageName) {
-        op.removeAttributionsWithNoTime();
-
-        if (op.mAttributions.isEmpty()) {
-            Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
-            if (ops != null) {
-                ops.remove(op.op);
-                op.setMode(AppOpsManager.opToDefaultMode(op.op));
-                if (ops.size() <= 0) {
-                    UidState uidState = ops.uidState;
-                    ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
-                    if (pkgOps != null) {
-                        pkgOps.remove(ops.packageName);
-                        mAppOpsServiceInterface.removePackage(ops.packageName,
-                                UserHandle.getUserId(uidState.uid));
-                        if (pkgOps.isEmpty()) {
-                            uidState.pkgOps = null;
-                        }
-                        if (uidState.isDefault()) {
-                            uidState.clear();
-                            mUidStates.remove(uid);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    public void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
-        if (callingPid == Process.myPid()) {
-            return;
-        }
-        final int callingUser = UserHandle.getUserId(callingUid);
-        synchronized (this) {
-            if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
-                if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
-                    // Profile owners are allowed to change modes but only for apps
-                    // within their user.
-                    return;
-                }
-            }
-        }
-        mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
-    }
-
-    @Override
-    public void setUidMode(int code, int uid, int mode,
-            @Nullable IAppOpsCallback permissionPolicyCallback) {
-        if (DEBUG) {
-            Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
-                    + " by uid " + Binder.getCallingUid());
-        }
-
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
-        verifyIncomingOp(code);
-        code = AppOpsManager.opToSwitch(code);
-
-        if (permissionPolicyCallback == null) {
-            updatePermissionRevokedCompat(uid, code, mode);
-        }
-
-        int previousMode;
-        synchronized (this) {
-            final int defaultMode = AppOpsManager.opToDefaultMode(code);
-
-            UidState uidState = getUidStateLocked(uid, false);
-            if (uidState == null) {
-                if (mode == defaultMode) {
-                    return;
-                }
-                uidState = new UidState(uid);
-                mUidStates.put(uid, uidState);
-            }
-            if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
-                previousMode = uidState.getUidMode(code);
-            } else {
-                // doesn't look right but is legacy behavior.
-                previousMode = MODE_DEFAULT;
-            }
-
-            if (!uidState.setUidMode(code, mode)) {
-                return;
-            }
-            uidState.evalForegroundOps();
-            if (mode != MODE_ERRORED && mode != previousMode) {
-                updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
-            }
-        }
-
-        notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
-        notifyOpChangedSync(code, uid, null, mode, previousMode);
-    }
-
-    /**
-     * Notify that an op changed for all packages in an uid.
-     *
-     * @param code           The op that changed
-     * @param uid            The uid the op was changed for
-     * @param onlyForeground Only notify watchers that watch for foreground changes
-     */
-    private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
-            @Nullable IAppOpsCallback callbackToIgnore) {
-        ModeCallback listenerToIgnore = callbackToIgnore != null
-                ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
-        mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
-                listenerToIgnore);
-    }
-
-    private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
-        PackageManager packageManager = mContext.getPackageManager();
-        if (packageManager == null) {
-            // This can only happen during early boot. At this time the permission state and appop
-            // state are in sync
-            return;
-        }
-
-        String[] packageNames = packageManager.getPackagesForUid(uid);
-        if (ArrayUtils.isEmpty(packageNames)) {
-            return;
-        }
-        String packageName = packageNames[0];
-
-        int[] ops = mSwitchedOps.get(switchCode);
-        for (int code : ops) {
-            String permissionName = AppOpsManager.opToPermission(code);
-            if (permissionName == null) {
-                continue;
-            }
-
-            if (packageManager.checkPermission(permissionName, packageName)
-                    != PackageManager.PERMISSION_GRANTED) {
-                continue;
-            }
-
-            PermissionInfo permissionInfo;
-            try {
-                permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
-            } catch (PackageManager.NameNotFoundException e) {
-                e.printStackTrace();
-                continue;
-            }
-
-            if (!permissionInfo.isRuntime()) {
-                continue;
-            }
-
-            boolean supportsRuntimePermissions = getPackageManagerInternal()
-                    .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
-
-            UserHandle user = UserHandle.getUserHandleForUid(uid);
-            boolean isRevokedCompat;
-            if (permissionInfo.backgroundPermission != null) {
-                if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
-                        == PackageManager.PERMISSION_GRANTED) {
-                    boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
-
-                    if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
-                        Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
-                                + " permission state, this is discouraged and you should revoke the"
-                                + " runtime permission instead: uid=" + uid + ", switchCode="
-                                + switchCode + ", mode=" + mode + ", permission="
-                                + permissionInfo.backgroundPermission);
-                    }
-
-                    final long identity = Binder.clearCallingIdentity();
-                    try {
-                        packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
-                                packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
-                                isBackgroundRevokedCompat
-                                        ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
-                    } finally {
-                        Binder.restoreCallingIdentity(identity);
-                    }
-                }
-
-                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
-                        && mode != AppOpsManager.MODE_FOREGROUND;
-            } else {
-                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
-            }
-
-            if (isRevokedCompat && supportsRuntimePermissions) {
-                Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
-                        + " permission state, this is discouraged and you should revoke the"
-                        + " runtime permission instead: uid=" + uid + ", switchCode="
-                        + switchCode + ", mode=" + mode + ", permission=" + permissionName);
-            }
-
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                packageManager.updatePermissionFlags(permissionName, packageName,
-                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
-                                ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-    }
-
-    private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
-            int previousMode) {
-        final StorageManagerInternal storageManagerInternal =
-                LocalServices.getService(StorageManagerInternal.class);
-        if (storageManagerInternal != null) {
-            storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
-        }
-    }
-
-    @Override
-    public void setMode(int code, int uid, @NonNull String packageName, int mode,
-            @Nullable IAppOpsCallback permissionPolicyCallback) {
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return;
-        }
-
-        ArraySet<OnOpModeChangedListener> repCbs = null;
-        code = AppOpsManager.opToSwitch(code);
-
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, null);
-        } catch (SecurityException e) {
-            Slog.e(TAG, "Cannot setMode", e);
-            return;
-        }
-
-        int previousMode = MODE_DEFAULT;
-        synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, false);
-            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
-            if (op != null) {
-                if (op.getMode() != mode) {
-                    previousMode = op.getMode();
-                    op.setMode(mode);
-
-                    if (uidState != null) {
-                        uidState.evalForegroundOps();
-                    }
-                    ArraySet<OnOpModeChangedListener> cbs =
-                            mAppOpsServiceInterface.getOpModeChangedListeners(code);
-                    if (cbs != null) {
-                        if (repCbs == null) {
-                            repCbs = new ArraySet<>();
-                        }
-                        repCbs.addAll(cbs);
-                    }
-                    cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
-                    if (cbs != null) {
-                        if (repCbs == null) {
-                            repCbs = new ArraySet<>();
-                        }
-                        repCbs.addAll(cbs);
-                    }
-                    if (repCbs != null && permissionPolicyCallback != null) {
-                        repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
-                    }
-                    if (mode == AppOpsManager.opToDefaultMode(op.op)) {
-                        // If going into the default mode, prune this op
-                        // if there is nothing else interesting in it.
-                        pruneOpLocked(op, uid, packageName);
-                    }
-                    scheduleFastWriteLocked();
-                    if (mode != MODE_ERRORED) {
-                        updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
-                    }
-                }
-            }
-        }
-        if (repCbs != null) {
-            mHandler.sendMessage(PooledLambda.obtainMessage(
-                    AppOpsServiceImpl::notifyOpChanged,
-                    this, repCbs, code, uid, packageName));
-        }
-
-        notifyOpChangedSync(code, uid, packageName, mode, previousMode);
-    }
-
-    private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
-            int uid, String packageName) {
-        for (int i = 0; i < callbacks.size(); i++) {
-            final OnOpModeChangedListener callback = callbacks.valueAt(i);
-            notifyOpChanged(callback, code, uid, packageName);
-        }
-    }
-
-    private void notifyOpChanged(OnOpModeChangedListener callback, int code,
-            int uid, String packageName) {
-        mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
-    }
-
-    private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
-            int op, int uid, String packageName, int previousMode) {
-        boolean duplicate = false;
-        if (reports == null) {
-            reports = new ArrayList<>();
-        } else {
-            final int reportCount = reports.size();
-            for (int j = 0; j < reportCount; j++) {
-                ChangeRec report = reports.get(j);
-                if (report.op == op && report.pkg.equals(packageName)) {
-                    duplicate = true;
-                    break;
-                }
-            }
-        }
-        if (!duplicate) {
-            reports.add(new ChangeRec(op, uid, packageName, previousMode));
-        }
-
-        return reports;
-    }
-
-    private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
-            HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
-            int op, int uid, String packageName, int previousMode,
-            ArraySet<OnOpModeChangedListener> cbs) {
-        if (cbs == null) {
-            return callbacks;
-        }
-        if (callbacks == null) {
-            callbacks = new HashMap<>();
-        }
-        final int N = cbs.size();
-        for (int i=0; i<N; i++) {
-            OnOpModeChangedListener cb = cbs.valueAt(i);
-            ArrayList<ChangeRec> reports = callbacks.get(cb);
-            ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
-            if (changed != reports) {
-                callbacks.put(cb, changed);
-            }
-        }
-        return callbacks;
-    }
-
-    static final class ChangeRec {
-        final int op;
-        final int uid;
-        final String pkg;
-        final int previous_mode;
-
-        ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
-            op = _op;
-            uid = _uid;
-            pkg = _pkg;
-            previous_mode = _previous_mode;
-        }
-    }
-
-    @Override
-    public void resetAllModes(int reqUserId, String reqPackageName) {
-        final int callingPid = Binder.getCallingPid();
-        final int callingUid = Binder.getCallingUid();
-        reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
-                true, true, "resetAllModes", null);
-
-        int reqUid = -1;
-        if (reqPackageName != null) {
-            try {
-                reqUid = AppGlobals.getPackageManager().getPackageUid(
-                        reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
-            } catch (RemoteException e) {
-                /* ignore - local call */
-            }
-        }
-
-        enforceManageAppOpsModes(callingPid, callingUid, reqUid);
-
-        HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
-        ArrayList<ChangeRec> allChanges = new ArrayList<>();
-        synchronized (this) {
-            boolean changed = false;
-            for (int i = mUidStates.size() - 1; i >= 0; i--) {
-                UidState uidState = mUidStates.valueAt(i);
-
-                SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
-                    final int uidOpCount = opModes.size();
-                    for (int j = uidOpCount - 1; j >= 0; j--) {
-                        final int code = opModes.keyAt(j);
-                        if (AppOpsManager.opAllowsReset(code)) {
-                            int previousMode = opModes.valueAt(j);
-                            uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
-                            for (String packageName : getPackagesForUid(uidState.uid)) {
-                                callbacks = addCallbacks(callbacks, code, uidState.uid,
-                                        packageName, previousMode,
-                                        mAppOpsServiceInterface.getOpModeChangedListeners(code));
-                                callbacks = addCallbacks(callbacks, code, uidState.uid,
-                                        packageName, previousMode, mAppOpsServiceInterface
-                                                .getPackageModeChangedListeners(packageName));
-
-                                allChanges = addChange(allChanges, code, uidState.uid,
-                                        packageName, previousMode);
-                            }
-                        }
-                    }
-                }
-
-                if (uidState.pkgOps == null) {
-                    continue;
-                }
-
-                if (reqUserId != UserHandle.USER_ALL
-                        && reqUserId != UserHandle.getUserId(uidState.uid)) {
-                    // Skip any ops for a different user
-                    continue;
-                }
-
-                Map<String, Ops> packages = uidState.pkgOps;
-                Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
-                boolean uidChanged = false;
-                while (it.hasNext()) {
-                    Map.Entry<String, Ops> ent = it.next();
-                    String packageName = ent.getKey();
-                    if (reqPackageName != null && !reqPackageName.equals(packageName)) {
-                        // Skip any ops for a different package
-                        continue;
-                    }
-                    Ops pkgOps = ent.getValue();
-                    for (int j=pkgOps.size()-1; j>=0; j--) {
-                        Op curOp = pkgOps.valueAt(j);
-                        if (shouldDeferResetOpToDpm(curOp.op)) {
-                            deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
-                            continue;
-                        }
-                        if (AppOpsManager.opAllowsReset(curOp.op)
-                                && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
-                            int previousMode = curOp.getMode();
-                            curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
-                            changed = true;
-                            uidChanged = true;
-                            final int uid = curOp.uidState.uid;
-                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    previousMode,
-                                    mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
-                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    previousMode, mAppOpsServiceInterface
-                                            .getPackageModeChangedListeners(packageName));
-
-                            allChanges = addChange(allChanges, curOp.op, uid, packageName,
-                                    previousMode);
-                            curOp.removeAttributionsWithNoTime();
-                            if (curOp.mAttributions.isEmpty()) {
-                                pkgOps.removeAt(j);
-                            }
-                        }
-                    }
-                    if (pkgOps.size() == 0) {
-                        it.remove();
-                        mAppOpsServiceInterface.removePackage(packageName,
-                                UserHandle.getUserId(uidState.uid));
-                    }
-                }
-                if (uidState.isDefault()) {
-                    uidState.clear();
-                    mUidStates.remove(uidState.uid);
-                }
-                if (uidChanged) {
-                    uidState.evalForegroundOps();
-                }
-            }
-
-            if (changed) {
-                scheduleFastWriteLocked();
-            }
-        }
-        if (callbacks != null) {
-            for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
-                    : callbacks.entrySet()) {
-                OnOpModeChangedListener cb = ent.getKey();
-                ArrayList<ChangeRec> reports = ent.getValue();
-                for (int i=0; i<reports.size(); i++) {
-                    ChangeRec rep = reports.get(i);
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppOpsServiceImpl::notifyOpChanged,
-                            this, cb, rep.op, rep.uid, rep.pkg));
-                }
-            }
-        }
-
-        int numChanges = allChanges.size();
-        for (int i = 0; i < numChanges; i++) {
-            ChangeRec change = allChanges.get(i);
-            notifyOpChangedSync(change.op, change.uid, change.pkg,
-                    AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
-        }
-    }
-
-    private boolean shouldDeferResetOpToDpm(int op) {
-        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
-        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
-        return dpmi != null && dpmi.supportsResetOp(op);
-    }
-
-    /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
-    private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
-        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
-        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
-        dpmi.resetOp(op, packageName, userId);
-    }
-
-    private void evalAllForegroundOpsLocked() {
-        for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
-            final UidState uidState = mUidStates.valueAt(uidi);
-            if (uidState.foregroundOps != null) {
-                uidState.evalForegroundOps();
-            }
-        }
-    }
-
-    @Override
-    public void startWatchingModeWithFlags(int op, String packageName, int flags,
-            IAppOpsCallback callback) {
-        int watchedUid = -1;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        // TODO: should have a privileged permission to protect this.
-        // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
-        // the USAGE_STATS permission since this can provide information about when an
-        // app is in the foreground?
-        Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
-                AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
-        if (callback == null) {
-            return;
-        }
-        final boolean mayWatchPackageName = packageName != null
-                && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
-        synchronized (this) {
-            int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
-
-            int notifiedOps;
-            if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
-                if (op == OP_NONE) {
-                    notifiedOps = ALL_OPS;
-                } else {
-                    notifiedOps = op;
-                }
-            } else {
-                notifiedOps = switchOp;
-            }
-
-            ModeCallback cb = mModeWatchers.get(callback.asBinder());
-            if (cb == null) {
-                cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
-                        callingPid);
-                mModeWatchers.put(callback.asBinder(), cb);
-            }
-            if (switchOp != AppOpsManager.OP_NONE) {
-                mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
-            }
-            if (mayWatchPackageName) {
-                mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
-            }
-            evalAllForegroundOpsLocked();
-        }
-    }
-
-    @Override
-    public void stopWatchingMode(IAppOpsCallback callback) {
-        if (callback == null) {
-            return;
-        }
-        synchronized (this) {
-            ModeCallback cb = mModeWatchers.remove(callback.asBinder());
-            if (cb != null) {
-                cb.unlinkToDeath();
-                mAppOpsServiceInterface.removeListener(cb);
-            }
-
-            evalAllForegroundOpsLocked();
-        }
-    }
-
-    @Override
-    public int checkOperation(int code, int uid, String packageName,
-            @Nullable String attributionTag, boolean raw) {
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return AppOpsManager.opToDefaultMode(code);
-        }
-
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return AppOpsManager.MODE_IGNORED;
-        }
-        return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
-    }
-
-    /**
-     * Get the mode of an app-op.
-     *
-     * @param code        The code of the op
-     * @param uid         The uid of the package the op belongs to
-     * @param packageName The package the op belongs to
-     * @param raw         If the raw state of eval-ed state should be checked.
-     * @return The mode of the op
-     */
-    private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, boolean raw) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, null);
-        } catch (SecurityException e) {
-            Slog.e(TAG, "checkOperation", e);
-            return AppOpsManager.opToDefaultMode(code);
-        }
-
-        if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
-            return AppOpsManager.MODE_IGNORED;
-        }
-        synchronized (this) {
-            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
-                return AppOpsManager.MODE_IGNORED;
-            }
-            code = AppOpsManager.opToSwitch(code);
-            UidState uidState = getUidStateLocked(uid, false);
-            if (uidState != null
-                    && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
-                final int rawMode = uidState.getUidMode(code);
-                return raw ? rawMode : uidState.evalMode(code, rawMode);
-            }
-            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
-            if (op == null) {
-                return AppOpsManager.opToDefaultMode(code);
-            }
-            return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
-        }
-    }
-
-    @Override
-    public int checkPackage(int uid, String packageName) {
-        Objects.requireNonNull(packageName);
-        try {
-            verifyAndGetBypass(uid, packageName, null);
-            // When the caller is the system, it's possible that the packageName is the special
-            // one (e.g., "root") which isn't actually existed.
-            if (resolveUid(packageName) == uid
-                    || (isPackageExisted(packageName)
-                            && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
-                return AppOpsManager.MODE_ALLOWED;
-            }
-            return AppOpsManager.MODE_ERRORED;
-        } catch (SecurityException ignored) {
-            return AppOpsManager.MODE_ERRORED;
-        }
-    }
-
-    private boolean isPackageExisted(String packageName) {
-        return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
-    }
-
-    /**
-     * This method will check with PackageManager to determine if the package provided should
-     * be visible to the {@link Binder#getCallingUid()}.
-     *
-     * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
-     */
-    private boolean filterAppAccessUnlocked(String packageName, int userId) {
-        final int callingUid = Binder.getCallingUid();
-        return LocalServices.getService(PackageManagerInternal.class)
-                .filterAppAccess(packageName, callingUid, userId);
-    }
-
-    @Override
-    public int noteOperation(int code, int uid, @Nullable String packageName,
-            @Nullable String attributionTag, @Nullable String message) {
-        verifyIncomingUid(uid);
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return AppOpsManager.MODE_ERRORED;
-        }
-
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return AppOpsManager.MODE_IGNORED;
-        }
-        return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
-                Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
-    }
-
-    @Override
-    public int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
-            @Nullable String proxyAttributionTag, @OpFlags int flags) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
-            if (!pvr.isAttributionTagValid) {
-                attributionTag = null;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "noteOperation", e);
-            return AppOpsManager.MODE_ERRORED;
-        }
-
-        synchronized (this) {
-            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
-                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
-            if (ops == null) {
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        AppOpsManager.MODE_IGNORED);
-                if (DEBUG) {
-                    Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
-                            + " package " + packageName + "flags: "
-                            + AppOpsManager.flagsToString(flags));
-                }
-                return AppOpsManager.MODE_ERRORED;
-            }
-            final Op op = getOpLocked(ops, code, uid, true);
-            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
-            if (attributedOp.isRunning()) {
-                Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
-                        + code + " startTime of in progress event="
-                        + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
-            }
-
-            final int switchCode = AppOpsManager.opToSwitch(code);
-            final UidState uidState = ops.uidState;
-            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
-                attributedOp.rejected(uidState.getState(), flags);
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        AppOpsManager.MODE_IGNORED);
-                return AppOpsManager.MODE_IGNORED;
-            }
-            // If there is a non-default per UID policy (we set UID op mode only if
-            // non-default) it takes over, otherwise use the per package policy.
-            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
-                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
-                if (uidMode != AppOpsManager.MODE_ALLOWED) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
-                                + switchCode + " (" + code + ") uid " + uid + " package "
-                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    }
-                    attributedOp.rejected(uidState.getState(), flags);
-                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                            uidMode);
-                    return uidMode;
-                }
-            } else {
-                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
-                        : op;
-                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
-                if (mode != AppOpsManager.MODE_ALLOWED) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
-                                + switchCode + " (" + code + ") uid " + uid + " package "
-                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    }
-                    attributedOp.rejected(uidState.getState(), flags);
-                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                            mode);
-                    return mode;
-                }
-            }
-            if (DEBUG) {
-                Slog.d(TAG,
-                        "noteOperation: allowing code " + code + " uid " + uid + " package "
-                                + packageName + (attributionTag == null ? ""
-                                : "." + attributionTag) + " flags: "
-                                + AppOpsManager.flagsToString(flags));
-            }
-            scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                    AppOpsManager.MODE_ALLOWED);
-            attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
-                    uidState.getState(),
-                    flags);
-
-            return AppOpsManager.MODE_ALLOWED;
-        }
-    }
-
-    @Override
-    public boolean isAttributionTagValid(int uid, @NonNull String packageName,
-            @Nullable String attributionTag,
-            @Nullable String proxyPackageName) {
-        try {
-            return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName)
-                    .isAttributionTagValid;
-        } catch (SecurityException ignored) {
-            // We don't want to throw, this exception will be handled in the (c/n/s)Operation calls
-            // when they need the bypass object.
-            return false;
-        }
-    }
-
-    // TODO moltmann: Allow watching for attribution ops
-    @Override
-    public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
-        int watchedUid = Process.INVALID_UID;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                != PackageManager.PERMISSION_GRANTED) {
-            watchedUid = callingUid;
-        }
-        if (ops != null) {
-            Preconditions.checkArrayElementsInRange(ops, 0,
-                    AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
-        }
-        if (callback == null) {
-            return;
-        }
-        synchronized (this) {
-            SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
-            if (callbacks == null) {
-                callbacks = new SparseArray<>();
-                mActiveWatchers.put(callback.asBinder(), callbacks);
-            }
-            final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
-                    callingUid, callingPid);
-            for (int op : ops) {
-                callbacks.put(op, activeCallback);
-            }
-        }
-    }
-
-    @Override
-    public void stopWatchingActive(IAppOpsActiveCallback callback) {
-        if (callback == null) {
-            return;
-        }
-        synchronized (this) {
-            final SparseArray<ActiveCallback> activeCallbacks =
-                    mActiveWatchers.remove(callback.asBinder());
-            if (activeCallbacks == null) {
-                return;
-            }
-            final int callbackCount = activeCallbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                activeCallbacks.valueAt(i).destroy();
-            }
-        }
-    }
-
-    @Override
-    public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
-        int watchedUid = Process.INVALID_UID;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                != PackageManager.PERMISSION_GRANTED) {
-            watchedUid = callingUid;
-        }
-
-        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
-        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
-                "Invalid op code in: " + Arrays.toString(ops));
-        Objects.requireNonNull(callback, "Callback cannot be null");
-
-        synchronized (this) {
-            SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
-            if (callbacks == null) {
-                callbacks = new SparseArray<>();
-                mStartedWatchers.put(callback.asBinder(), callbacks);
-            }
-
-            final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
-                    callingUid, callingPid);
-            for (int op : ops) {
-                callbacks.put(op, startedCallback);
-            }
-        }
-    }
-
-    @Override
-    public void stopWatchingStarted(IAppOpsStartedCallback callback) {
-        Objects.requireNonNull(callback, "Callback cannot be null");
-
-        synchronized (this) {
-            final SparseArray<StartedCallback> startedCallbacks =
-                    mStartedWatchers.remove(callback.asBinder());
-            if (startedCallbacks == null) {
-                return;
-            }
-
-            final int callbackCount = startedCallbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                startedCallbacks.valueAt(i).destroy();
-            }
-        }
-    }
-
-    @Override
-    public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
-        int watchedUid = Process.INVALID_UID;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                != PackageManager.PERMISSION_GRANTED) {
-            watchedUid = callingUid;
-        }
-        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
-        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
-                "Invalid op code in: " + Arrays.toString(ops));
-        Objects.requireNonNull(callback, "Callback cannot be null");
-        synchronized (this) {
-            SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
-            if (callbacks == null) {
-                callbacks = new SparseArray<>();
-                mNotedWatchers.put(callback.asBinder(), callbacks);
-            }
-            final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
-                    callingUid, callingPid);
-            for (int op : ops) {
-                callbacks.put(op, notedCallback);
-            }
-        }
-    }
-
-    @Override
-    public void stopWatchingNoted(IAppOpsNotedCallback callback) {
-        Objects.requireNonNull(callback, "Callback cannot be null");
-        synchronized (this) {
-            final SparseArray<NotedCallback> notedCallbacks =
-                    mNotedWatchers.remove(callback.asBinder());
-            if (notedCallbacks == null) {
-                return;
-            }
-            final int callbackCount = notedCallbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                notedCallbacks.valueAt(i).destroy();
-            }
-        }
-    }
-
-    @Override
-    public int startOperation(@NonNull IBinder clientId, int code, int uid,
-            @Nullable String packageName, @Nullable String attributionTag,
-            boolean startIfModeDefault, @NonNull String message,
-            @AttributionFlags int attributionFlags, int attributionChainId) {
-        verifyIncomingUid(uid);
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return AppOpsManager.MODE_ERRORED;
-        }
-
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return AppOpsManager.MODE_IGNORED;
-        }
-
-        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
-        // purposes and not as a check, also make sure that the caller is allowed to access
-        // the data gated by OP_RECORD_AUDIO.
-        //
-        // TODO: Revert this change before Android 12.
-        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
-            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName, null, false);
-            if (result != AppOpsManager.MODE_ALLOWED) {
-                return result;
-            }
-        }
-        return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
-                Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
-                attributionFlags, attributionChainId, /*dryRun*/ false);
-    }
-
-    private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
-        return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
-    }
-
-    @Override
-    public int startOperationUnchecked(IBinder clientId, int code, int uid,
-            @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
-            String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
-            boolean startIfModeDefault, @AttributionFlags int attributionFlags,
-            int attributionChainId, boolean dryRun) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
-            if (!pvr.isAttributionTagValid) {
-                attributionTag = null;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "startOperation", e);
-            return AppOpsManager.MODE_ERRORED;
-        }
-
-        boolean isRestricted;
-        int startType = START_TYPE_FAILED;
-        synchronized (this) {
-            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
-                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
-            if (ops == null) {
-                if (!dryRun) {
-                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                            flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
-                            attributionChainId);
-                }
-                if (DEBUG) {
-                    Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
-                            + " package " + packageName + " flags: "
-                            + AppOpsManager.flagsToString(flags));
-                }
-                return AppOpsManager.MODE_ERRORED;
-            }
-            final Op op = getOpLocked(ops, code, uid, true);
-            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
-            final UidState uidState = ops.uidState;
-            isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
-                    false);
-            final int switchCode = AppOpsManager.opToSwitch(code);
-            // If there is a non-default per UID policy (we set UID op mode only if
-            // non-default) it takes over, otherwise use the per package policy.
-            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
-                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
-                if (!shouldStartForMode(uidMode, startIfModeDefault)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
-                                + switchCode + " (" + code + ") uid " + uid + " package "
-                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    }
-                    if (!dryRun) {
-                        attributedOp.rejected(uidState.getState(), flags);
-                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                                flags, uidMode, startType, attributionFlags, attributionChainId);
-                    }
-                    return uidMode;
-                }
-            } else {
-                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
-                        : op;
-                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
-                if (!shouldStartForMode(mode, startIfModeDefault)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "startOperation: reject #" + mode + " for code "
-                                + switchCode + " (" + code + ") uid " + uid + " package "
-                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    }
-                    if (!dryRun) {
-                        attributedOp.rejected(uidState.getState(), flags);
-                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                                flags, mode, startType, attributionFlags, attributionChainId);
-                    }
-                    return mode;
-                }
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
-                        + " package " + packageName + " restricted: " + isRestricted
-                        + " flags: " + AppOpsManager.flagsToString(flags));
-            }
-            if (!dryRun) {
-                try {
-                    if (isRestricted) {
-                        attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
-                                proxyAttributionTag, uidState.getState(), flags,
-                                attributionFlags, attributionChainId);
-                    } else {
-                        attributedOp.started(clientId, proxyUid, proxyPackageName,
-                                proxyAttributionTag, uidState.getState(), flags,
-                                attributionFlags, attributionChainId);
-                        startType = START_TYPE_STARTED;
-                    }
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
-                        attributionChainId);
-            }
-        }
-
-        // Possible bug? The raw mode could have been MODE_DEFAULT to reach here.
-        return isRestricted ? MODE_IGNORED : MODE_ALLOWED;
-    }
-
-    @Override
-    public void finishOperation(IBinder clientId, int code, int uid, String packageName,
-            String attributionTag) {
-        verifyIncomingUid(uid);
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return;
-        }
-
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return;
-        }
-
-        finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
-    }
-
-    @Override
-    public void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
-            String attributionTag) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, attributionTag);
-            if (!pvr.isAttributionTagValid) {
-                attributionTag = null;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "Cannot finishOperation", e);
-            return;
-        }
-
-        synchronized (this) {
-            Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
-                    pvr.bypass, /* edit */ true);
-            if (op == null) {
-                Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
-                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
-                return;
-            }
-            final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
-            if (attributedOp == null) {
-                Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
-                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
-                return;
-            }
-
-            if (attributedOp.isRunning() || attributedOp.isPaused()) {
-                attributedOp.finished(clientId);
-            } else {
-                Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
-                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
-            }
-        }
-    }
-
-    void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, boolean active,
-            @AttributionFlags int attributionFlags, int attributionChainId) {
-        ArraySet<ActiveCallback> dispatchedCallbacks = null;
-        final int callbackListCount = mActiveWatchers.size();
-        for (int i = 0; i < callbackListCount; i++) {
-            final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
-            ActiveCallback callback = callbacks.get(code);
-            if (callback != null) {
-                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-                    continue;
-                }
-                if (dispatchedCallbacks == null) {
-                    dispatchedCallbacks = new ArraySet<>();
-                }
-                dispatchedCallbacks.add(callback);
-            }
-        }
-        if (dispatchedCallbacks == null) {
-            return;
-        }
-        mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsServiceImpl::notifyOpActiveChanged,
-                this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
-                attributionFlags, attributionChainId));
-    }
-
-    private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
-            int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
-            boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
-        // There are features watching for mode changes such as window manager
-        // and location manager which are in our process. The callbacks in these
-        // features may require permissions our remote caller does not have.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int callbackCount = callbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                final ActiveCallback callback = callbacks.valueAt(i);
-                try {
-                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
-                        continue;
-                    }
-                    callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
-                            active, attributionFlags, attributionChainId);
-                } catch (RemoteException e) {
-                    /* do nothing */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
-            String attributionTag, @OpFlags int flags, @Mode int result,
-            @AppOpsManager.OnOpStartedListener.StartedType int startedType,
-            @AttributionFlags int attributionFlags, int attributionChainId) {
-        ArraySet<StartedCallback> dispatchedCallbacks = null;
-        final int callbackListCount = mStartedWatchers.size();
-        for (int i = 0; i < callbackListCount; i++) {
-            final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
-
-            StartedCallback callback = callbacks.get(code);
-            if (callback != null) {
-                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-                    continue;
-                }
-
-                if (dispatchedCallbacks == null) {
-                    dispatchedCallbacks = new ArraySet<>();
-                }
-                dispatchedCallbacks.add(callback);
-            }
-        }
-
-        if (dispatchedCallbacks == null) {
-            return;
-        }
-
-        mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsServiceImpl::notifyOpStarted,
-                this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
-                result, startedType, attributionFlags, attributionChainId));
-    }
-
-    private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
-            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
-            @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
-            @AttributionFlags int attributionFlags, int attributionChainId) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int callbackCount = callbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                final StartedCallback callback = callbacks.valueAt(i);
-                try {
-                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
-                        continue;
-                    }
-                    callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
-                            result, startedType, attributionFlags, attributionChainId);
-                } catch (RemoteException e) {
-                    /* do nothing */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
-            String attributionTag, @OpFlags int flags, @Mode int result) {
-        ArraySet<NotedCallback> dispatchedCallbacks = null;
-        final int callbackListCount = mNotedWatchers.size();
-        for (int i = 0; i < callbackListCount; i++) {
-            final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
-            final NotedCallback callback = callbacks.get(code);
-            if (callback != null) {
-                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-                    continue;
-                }
-                if (dispatchedCallbacks == null) {
-                    dispatchedCallbacks = new ArraySet<>();
-                }
-                dispatchedCallbacks.add(callback);
-            }
-        }
-        if (dispatchedCallbacks == null) {
-            return;
-        }
-        mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsServiceImpl::notifyOpChecked,
-                this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
-                result));
-    }
-
-    private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
-            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
-            @Mode int result) {
-        // There are features watching for checks in our process. The callbacks in
-        // these features may require permissions our remote caller does not have.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int callbackCount = callbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                final NotedCallback callback = callbacks.valueAt(i);
-                try {
-                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
-                        continue;
-                    }
-                    callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
-                            result);
-                } catch (RemoteException e) {
-                    /* do nothing */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    private void verifyIncomingUid(int uid) {
-        if (uid == Binder.getCallingUid()) {
-            return;
-        }
-        if (Binder.getCallingPid() == Process.myPid()) {
-            return;
-        }
-        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
-    }
-
-    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
-        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
-        // as watcher should not use this to signal if the value is changed.
-        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
-                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
-    }
-
-    private void verifyIncomingOp(int op) {
-        if (op >= 0 && op < AppOpsManager._NUM_OP) {
-            // Enforce manage appops permission if it's a restricted read op.
-            if (opRestrictsRead(op)) {
-                mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
-                        Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
-            }
-            return;
-        }
-        throw new IllegalArgumentException("Bad operation #" + op);
-    }
-
-    private boolean isIncomingPackageValid(@Nullable String packageName, @UserIdInt int userId) {
-        final int callingUid = Binder.getCallingUid();
-        // Handle the special UIDs that don't have actual packages (audioserver, cameraserver, etc).
-        if (packageName == null || isSpecialPackage(callingUid, packageName)) {
-            return true;
-        }
-
-        // If the package doesn't exist, #verifyAndGetBypass would throw a SecurityException in
-        // the end. Although that exception would be caught and return, we could make it return
-        // early.
-        if (!isPackageExisted(packageName)) {
-            return false;
-        }
-
-        if (getPackageManagerInternal().filterAppAccess(packageName, callingUid, userId)) {
-            Slog.w(TAG, packageName + " not found from " + callingUid);
-            return false;
-        }
-
-        return true;
-    }
-
-    private boolean isSpecialPackage(int callingUid, @Nullable String packageName) {
-        final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName);
-        return callingUid == Process.SYSTEM_UID
-                || resolveUid(resolvedPackage) != Process.INVALID_UID;
-    }
-
-    private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
-        UidState uidState = mUidStates.get(uid);
-        if (uidState == null) {
-            if (!edit) {
-                return null;
-            }
-            uidState = new UidState(uid);
-            mUidStates.put(uid, uidState);
-        }
-
-        return uidState;
-    }
-
-    @Override
-    public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
-        synchronized (this) {
-            getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
-        }
-    }
-
-    /**
-     * @return {@link PackageManagerInternal}
-     */
-    private @NonNull PackageManagerInternal getPackageManagerInternal() {
-        if (mPackageManagerInternal == null) {
-            mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
-        }
-
-        return mPackageManagerInternal;
-    }
-
-    @Override
-    public void verifyPackage(int uid, String packageName) {
-        verifyAndGetBypass(uid, packageName, null);
-    }
-
-    /**
-     * Create a restriction description matching the properties of the package.
-     *
-     * @param pkg The package to create the restriction description for
-     * @return The restriction matching the package
-     */
-    private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
-        return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
-                mContext.checkPermission(android.Manifest.permission
-                        .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
-                        == PackageManager.PERMISSION_GRANTED);
-    }
-
-    /**
-     * @see #verifyAndGetBypass(int, String, String, String)
-     */
-    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
-            @Nullable String attributionTag) {
-        return verifyAndGetBypass(uid, packageName, attributionTag, null);
-    }
-
-    /**
-     * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
-     * description} for the package, along with a boolean indicating whether the attribution tag is
-     * valid.
-     *
-     * @param uid              The uid the package belongs to
-     * @param packageName      The package the might belong to the uid
-     * @param attributionTag   attribution tag or {@code null} if no need to verify
-     * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
-     * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
-     * attribution tag is valid
-     */
-    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
-            @Nullable String attributionTag, @Nullable String proxyPackageName) {
-        if (uid == Process.ROOT_UID) {
-            // For backwards compatibility, don't check package name for root UID.
-            return new PackageVerificationResult(null,
-                    /* isAttributionTagValid */ true);
-        }
-        if (Process.isSdkSandboxUid(uid)) {
-            // SDK sandbox processes run in their own UID range, but their associated
-            // UID for checks should always be the UID of the package implementing SDK sandbox
-            // service.
-            // TODO: We will need to modify the callers of this function instead, so
-            // modifications and checks against the app ops state are done with the
-            // correct UID.
-            try {
-                final PackageManager pm = mContext.getPackageManager();
-                final String supplementalPackageName = pm.getSdkSandboxPackageName();
-                if (Objects.equals(packageName, supplementalPackageName)) {
-                    uid = pm.getPackageUidAsUser(supplementalPackageName,
-                            PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
-                }
-            } catch (PackageManager.NameNotFoundException e) {
-                // Shouldn't happen for the supplemental package
-                e.printStackTrace();
-            }
-        }
-
-
-        // Do not check if uid/packageName/attributionTag is already known.
-        synchronized (this) {
-            UidState uidState = mUidStates.get(uid);
-            if (uidState != null && uidState.pkgOps != null) {
-                Ops ops = uidState.pkgOps.get(packageName);
-
-                if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
-                        attributionTag)) && ops.bypass != null) {
-                    return new PackageVerificationResult(ops.bypass,
-                            ops.validAttributionTags.contains(attributionTag));
-                }
-            }
-        }
-
-        int callingUid = Binder.getCallingUid();
-
-        // Allow any attribution tag for resolvable uids
-        int pkgUid;
-        if (Objects.equals(packageName, "com.android.shell")) {
-            // Special case for the shell which is a package but should be able
-            // to bypass app attribution tag restrictions.
-            pkgUid = Process.SHELL_UID;
-        } else {
-            pkgUid = resolveUid(packageName);
-        }
-        if (pkgUid != Process.INVALID_UID) {
-            if (pkgUid != UserHandle.getAppId(uid)) {
-                Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
-                        + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
-                String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
-                throw new SecurityException("Specified package \"" + packageName + "\" under uid "
-                        + UserHandle.getAppId(uid) + otherUidMessage);
-            }
-            return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
-                    /* isAttributionTagValid */ true);
-        }
-
-        int userId = UserHandle.getUserId(uid);
-        RestrictionBypass bypass = null;
-        boolean isAttributionTagValid = false;
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
-            AndroidPackage pkg = pmInt.getPackage(packageName);
-            if (pkg != null) {
-                isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
-                pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
-                bypass = getBypassforPackage(pkg);
-            }
-            if (!isAttributionTagValid) {
-                AndroidPackage proxyPkg = proxyPackageName != null
-                        ? pmInt.getPackage(proxyPackageName) : null;
-                // Re-check in proxy.
-                isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
-                String msg;
-                if (pkg != null && isAttributionTagValid) {
-                    msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
-                            + " package " + proxyPackageName + ", this is not advised";
-                } else if (pkg != null) {
-                    msg = "attributionTag " + attributionTag + " not declared in manifest of "
-                            + packageName;
-                } else {
-                    msg = "package " + packageName + " not found, can't check for "
-                            + "attributionTag " + attributionTag;
-                }
-
-                try {
-                    if (!mPlatformCompat.isChangeEnabledByPackageName(
-                            SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
-                            userId) || !mPlatformCompat.isChangeEnabledByUid(
-                            SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
-                            callingUid)) {
-                        // Do not override tags if overriding is not enabled for this package
-                        isAttributionTagValid = true;
-                    }
-                    Slog.e(TAG, msg);
-                } catch (RemoteException neverHappens) {
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-
-        if (pkgUid != uid) {
-            Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
-                    + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
-            String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
-            throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
-                    + otherUidMessage);
-        }
-
-        return new PackageVerificationResult(bypass, isAttributionTagValid);
-    }
-
-    private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
-            @Nullable String attributionTag) {
-        if (pkg == null) {
-            return false;
-        } else if (attributionTag == null) {
-            return true;
-        }
-        if (pkg.getAttributions() != null) {
-            int numAttributions = pkg.getAttributions().size();
-            for (int i = 0; i < numAttributions; i++) {
-                if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Get (and potentially create) ops.
-     *
-     * @param uid                   The uid the package belongs to
-     * @param packageName           The name of the package
-     * @param attributionTag        attribution tag
-     * @param isAttributionTagValid whether the given attribution tag is valid
-     * @param bypass                When to bypass certain op restrictions (can be null if edit
-        *                              == false)
-     * @param edit                  If an ops does not exist, create the ops?
-     * @return The ops
-     */
-    private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
-            boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
-        UidState uidState = getUidStateLocked(uid, edit);
-        if (uidState == null) {
-            return null;
-        }
-
-        if (uidState.pkgOps == null) {
-            if (!edit) {
-                return null;
-            }
-            uidState.pkgOps = new ArrayMap<>();
-        }
-
-        Ops ops = uidState.pkgOps.get(packageName);
-        if (ops == null) {
-            if (!edit) {
-                return null;
-            }
-            ops = new Ops(packageName, uidState);
-            uidState.pkgOps.put(packageName, ops);
-        }
-
-        if (edit) {
-            if (bypass != null) {
-                ops.bypass = bypass;
-            }
-
-            if (attributionTag != null) {
-                ops.knownAttributionTags.add(attributionTag);
-                if (isAttributionTagValid) {
-                    ops.validAttributionTags.add(attributionTag);
-                } else {
-                    ops.validAttributionTags.remove(attributionTag);
-                }
-            }
-        }
-
-        return ops;
-    }
-
-    @Override
-    public void scheduleWriteLocked() {
-        if (!mWriteScheduled) {
-            mWriteScheduled = true;
-            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
-        }
-    }
-
-    @Override
-    public void scheduleFastWriteLocked() {
-        if (!mFastWriteScheduled) {
-            mWriteScheduled = true;
-            mFastWriteScheduled = true;
-            mHandler.removeCallbacks(mWriteRunner);
-            mHandler.postDelayed(mWriteRunner, 10 * 1000);
-        }
-    }
-
-    /**
-     * Get the state of an op for a uid.
-     *
-     * @param code                  The code of the op
-     * @param uid                   The uid the of the package
-     * @param packageName           The package name for which to get the state for
-     * @param attributionTag        The attribution tag
-     * @param isAttributionTagValid Whether the given attribution tag is valid
-     * @param bypass                When to bypass certain op restrictions (can be null if edit
-     *                              == false)
-     * @param edit                  Iff {@code true} create the {@link Op} object if not yet created
-     * @return The {@link Op state} of the op
-     */
-    private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, boolean isAttributionTagValid,
-            @Nullable RestrictionBypass bypass, boolean edit) {
-        Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
-                edit);
-        if (ops == null) {
-            return null;
-        }
-        return getOpLocked(ops, code, uid, edit);
-    }
-
-    private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
-        Op op = ops.get(code);
-        if (op == null) {
-            if (!edit) {
-                return null;
-            }
-            op = new Op(ops.uidState, ops.packageName, code, uid);
-            ops.put(code, op);
-        }
-        if (edit) {
-            scheduleWriteLocked();
-        }
-        return op;
-    }
-
-    private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
-        if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
-            return false;
-        }
-        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
-        return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
-    }
-
-    private boolean isOpRestrictedLocked(int uid, int code, String packageName,
-            String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
-        int restrictionSetCount = mOpGlobalRestrictions.size();
-
-        for (int i = 0; i < restrictionSetCount; i++) {
-            ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
-            if (restrictionState.hasRestriction(code)) {
-                return true;
-            }
-        }
-
-        int userHandle = UserHandle.getUserId(uid);
-        restrictionSetCount = mOpUserRestrictions.size();
-
-        for (int i = 0; i < restrictionSetCount; i++) {
-            // For each client, check that the given op is not restricted, or that the given
-            // package is exempt from the restriction.
-            ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
-            if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
-                    isCheckOp)) {
-                RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
-                if (opBypass != null) {
-                    // If we are the system, bypass user restrictions for certain codes
-                    synchronized (this) {
-                        if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
-                            return false;
-                        }
-                        if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
-                            return false;
-                        }
-                        if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
-                                && appBypass.isRecordAudioRestrictionExcept) {
-                            return false;
-                        }
-                    }
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public void readState() {
-        synchronized (mFile) {
-            synchronized (this) {
-                FileInputStream stream;
-                try {
-                    stream = mFile.openRead();
-                } catch (FileNotFoundException e) {
-                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
-                    return;
-                }
-                boolean success = false;
-                mUidStates.clear();
-                mAppOpsServiceInterface.clearAllModes();
-                try {
-                    TypedXmlPullParser parser = Xml.resolvePullParser(stream);
-                    int type;
-                    while ((type = parser.next()) != XmlPullParser.START_TAG
-                            && type != XmlPullParser.END_DOCUMENT) {
-                        // Parse next until we reach the start or end
-                    }
-
-                    if (type != XmlPullParser.START_TAG) {
-                        throw new IllegalStateException("no start tag found");
-                    }
-
-                    mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
-
-                    int outerDepth = parser.getDepth();
-                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                            continue;
-                        }
-
-                        String tagName = parser.getName();
-                        if (tagName.equals("pkg")) {
-                            readPackage(parser);
-                        } else if (tagName.equals("uid")) {
-                            readUidOps(parser);
-                        } else {
-                            Slog.w(TAG, "Unknown element under <app-ops>: "
-                                    + parser.getName());
-                            XmlUtils.skipCurrentTag(parser);
-                        }
-                    }
-                    success = true;
-                } catch (IllegalStateException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (NullPointerException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (NumberFormatException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (XmlPullParserException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (IOException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (IndexOutOfBoundsException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } finally {
-                    if (!success) {
-                        mUidStates.clear();
-                        mAppOpsServiceInterface.clearAllModes();
-                    }
-                    try {
-                        stream.close();
-                    } catch (IOException e) {
-                    }
-                }
-            }
-        }
-    }
-
-    @VisibleForTesting
-    @GuardedBy("this")
-    void upgradeRunAnyInBackgroundLocked() {
-        for (int i = 0; i < mUidStates.size(); i++) {
-            final UidState uidState = mUidStates.valueAt(i);
-            if (uidState == null) {
-                continue;
-            }
-            SparseIntArray opModes = uidState.getNonDefaultUidModes();
-            if (opModes != null) {
-                final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
-                if (idx >= 0) {
-                    uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
-                            opModes.valueAt(idx));
-                }
-            }
-            if (uidState.pkgOps == null) {
-                continue;
-            }
-            boolean changed = false;
-            for (int j = 0; j < uidState.pkgOps.size(); j++) {
-                Ops ops = uidState.pkgOps.valueAt(j);
-                if (ops != null) {
-                    final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
-                    if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
-                        final Op copy = new Op(op.uidState, op.packageName,
-                                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
-                        copy.setMode(op.getMode());
-                        ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
-                        changed = true;
-                    }
-                }
-            }
-            if (changed) {
-                uidState.evalForegroundOps();
-            }
-        }
-    }
-
-    /**
-     * The interpretation of the default mode - MODE_DEFAULT - for OP_SCHEDULE_EXACT_ALARM is
-     * changing. Simultaneously, we want to change this op's mode from MODE_DEFAULT to MODE_ALLOWED
-     * for already installed apps. For newer apps, it will stay as MODE_DEFAULT.
-     */
-    @VisibleForTesting
-    @GuardedBy("this")
-    void upgradeScheduleExactAlarmLocked() {
-        final PermissionManagerServiceInternal pmsi = LocalServices.getService(
-                PermissionManagerServiceInternal.class);
-        final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
-        final PackageManagerInternal pmi = getPackageManagerInternal();
-
-        final String[] packagesDeclaringPermission = pmsi.getAppOpPermissionPackages(
-                AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM));
-        final int[] userIds = umi.getUserIds();
-
-        for (final String pkg : packagesDeclaringPermission) {
-            for (int userId : userIds) {
-                final int uid = pmi.getPackageUid(pkg, 0, userId);
-
-                UidState uidState = mUidStates.get(uid);
-                if (uidState == null) {
-                    uidState = new UidState(uid);
-                    mUidStates.put(uid, uidState);
-                }
-                final int oldMode = uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM);
-                if (oldMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) {
-                    uidState.setUidMode(OP_SCHEDULE_EXACT_ALARM, MODE_ALLOWED);
-                }
-            }
-            // This appop is meant to be controlled at a uid level. So we leave package modes as
-            // they are.
-        }
-    }
-
-    @GuardedBy("this")
-    private void upgradeLocked(int oldVersion) {
-        if (oldVersion == NO_FILE_VERSION || oldVersion >= CURRENT_VERSION) {
-            return;
-        }
-        Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
-        switch (oldVersion) {
-            case NO_VERSION:
-                upgradeRunAnyInBackgroundLocked();
-                // fall through
-            case 1:
-                upgradeScheduleExactAlarmLocked();
-                // fall through
-            case 2:
-                // for future upgrades
-        }
-        scheduleFastWriteLocked();
-    }
-
-    private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
-            XmlPullParserException, IOException {
-        final int uid = parser.getAttributeInt(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                final int code = parser.getAttributeInt(null, "n");
-                final int mode = parser.getAttributeInt(null, "m");
-                setUidMode(code, uid, mode, null);
-            } else {
-                Slog.w(TAG, "Unknown element under <uid-ops>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    private void readPackage(TypedXmlPullParser parser)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        String pkgName = parser.getAttributeValue(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("uid")) {
-                readUid(parser, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    private void readUid(TypedXmlPullParser parser, String pkgName)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        int uid = parser.getAttributeInt(null, "n");
-        final UidState uidState = getUidStateLocked(uid, true);
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                readOp(parser, uidState, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-        uidState.evalForegroundOps();
-    }
-
-    private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
-            @Nullable String attribution)
-            throws NumberFormatException, IOException, XmlPullParserException {
-        final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
-
-        final long key = parser.getAttributeLong(null, "n");
-        final int uidState = extractUidStateFromKey(key);
-        final int opFlags = extractFlagsFromKey(key);
-
-        final long accessTime = parser.getAttributeLong(null, "t", 0);
-        final long rejectTime = parser.getAttributeLong(null, "r", 0);
-        final long accessDuration = parser.getAttributeLong(null, "d", -1);
-        final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
-        final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
-        final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
-
-        if (accessTime > 0) {
-            attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
-                    proxyAttributionTag, uidState, opFlags);
-        }
-        if (rejectTime > 0) {
-            attributedOp.rejected(rejectTime, uidState, opFlags);
-        }
-    }
-
-    private void readOp(TypedXmlPullParser parser,
-            @NonNull UidState uidState, @NonNull String pkgName)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        int opCode = parser.getAttributeInt(null, "n");
-        Op op = new Op(uidState, pkgName, opCode, uidState.uid);
-
-        final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
-        op.setMode(mode);
-
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            String tagName = parser.getName();
-            if (tagName.equals("st")) {
-                readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
-            } else {
-                Slog.w(TAG, "Unknown element under <op>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-
-        if (uidState.pkgOps == null) {
-            uidState.pkgOps = new ArrayMap<>();
-        }
-        Ops ops = uidState.pkgOps.get(pkgName);
-        if (ops == null) {
-            ops = new Ops(pkgName, uidState);
-            uidState.pkgOps.put(pkgName, ops);
-        }
-        ops.put(op.op, op);
-    }
-
-    @Override
-    public void writeState() {
-        synchronized (mFile) {
-            FileOutputStream stream;
-            try {
-                stream = mFile.startWrite();
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to write state: " + e);
-                return;
-            }
-
-            List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
-
-            try {
-                TypedXmlSerializer out = Xml.resolveSerializer(stream);
-                out.startDocument(null, true);
-                out.startTag(null, "app-ops");
-                out.attributeInt(null, "v", CURRENT_VERSION);
-
-                SparseArray<SparseIntArray> uidStatesClone;
-                synchronized (this) {
-                    uidStatesClone = new SparseArray<>(mUidStates.size());
-
-                    final int uidStateCount = mUidStates.size();
-                    for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
-                        UidState uidState = mUidStates.valueAt(uidStateNum);
-                        int uid = mUidStates.keyAt(uidStateNum);
-
-                        SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                        if (opModes != null && opModes.size() > 0) {
-                            uidStatesClone.put(uid, opModes);
-                        }
-                    }
-                }
-
-                final int uidStateCount = uidStatesClone.size();
-                for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
-                    SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
-                    if (opModes != null && opModes.size() > 0) {
-                        out.startTag(null, "uid");
-                        out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
-                        final int opCount = opModes.size();
-                        for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
-                            final int op = opModes.keyAt(opCountNum);
-                            final int mode = opModes.valueAt(opCountNum);
-                            out.startTag(null, "op");
-                            out.attributeInt(null, "n", op);
-                            out.attributeInt(null, "m", mode);
-                            out.endTag(null, "op");
-                        }
-                        out.endTag(null, "uid");
-                    }
-                }
-
-                if (allOps != null) {
-                    String lastPkg = null;
-                    for (int i = 0; i < allOps.size(); i++) {
-                        AppOpsManager.PackageOps pkg = allOps.get(i);
-                        if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
-                            if (lastPkg != null) {
-                                out.endTag(null, "pkg");
-                            }
-                            lastPkg = pkg.getPackageName();
-                            if (lastPkg != null) {
-                                out.startTag(null, "pkg");
-                                out.attribute(null, "n", lastPkg);
-                            }
-                        }
-                        out.startTag(null, "uid");
-                        out.attributeInt(null, "n", pkg.getUid());
-                        List<AppOpsManager.OpEntry> ops = pkg.getOps();
-                        for (int j = 0; j < ops.size(); j++) {
-                            AppOpsManager.OpEntry op = ops.get(j);
-                            out.startTag(null, "op");
-                            out.attributeInt(null, "n", op.getOp());
-                            if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
-                                out.attributeInt(null, "m", op.getMode());
-                            }
-
-                            for (String attributionTag : op.getAttributedOpEntries().keySet()) {
-                                final AttributedOpEntry attribution =
-                                        op.getAttributedOpEntries().get(attributionTag);
-
-                                final ArraySet<Long> keys = attribution.collectKeys();
-
-                                final int keyCount = keys.size();
-                                for (int k = 0; k < keyCount; k++) {
-                                    final long key = keys.valueAt(k);
-
-                                    final int uidState = AppOpsManager.extractUidStateFromKey(key);
-                                    final int flags = AppOpsManager.extractFlagsFromKey(key);
-
-                                    final long accessTime = attribution.getLastAccessTime(uidState,
-                                            uidState, flags);
-                                    final long rejectTime = attribution.getLastRejectTime(uidState,
-                                            uidState, flags);
-                                    final long accessDuration = attribution.getLastDuration(
-                                            uidState, uidState, flags);
-                                    // Proxy information for rejections is not backed up
-                                    final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
-                                            uidState, uidState, flags);
-
-                                    if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
-                                            && proxy == null) {
-                                        continue;
-                                    }
-
-                                    String proxyPkg = null;
-                                    String proxyAttributionTag = null;
-                                    int proxyUid = Process.INVALID_UID;
-                                    if (proxy != null) {
-                                        proxyPkg = proxy.getPackageName();
-                                        proxyAttributionTag = proxy.getAttributionTag();
-                                        proxyUid = proxy.getUid();
-                                    }
-
-                                    out.startTag(null, "st");
-                                    if (attributionTag != null) {
-                                        out.attribute(null, "id", attributionTag);
-                                    }
-                                    out.attributeLong(null, "n", key);
-                                    if (accessTime > 0) {
-                                        out.attributeLong(null, "t", accessTime);
-                                    }
-                                    if (rejectTime > 0) {
-                                        out.attributeLong(null, "r", rejectTime);
-                                    }
-                                    if (accessDuration > 0) {
-                                        out.attributeLong(null, "d", accessDuration);
-                                    }
-                                    if (proxyPkg != null) {
-                                        out.attribute(null, "pp", proxyPkg);
-                                    }
-                                    if (proxyAttributionTag != null) {
-                                        out.attribute(null, "pc", proxyAttributionTag);
-                                    }
-                                    if (proxyUid >= 0) {
-                                        out.attributeInt(null, "pu", proxyUid);
-                                    }
-                                    out.endTag(null, "st");
-                                }
-                            }
-
-                            out.endTag(null, "op");
-                        }
-                        out.endTag(null, "uid");
-                    }
-                    if (lastPkg != null) {
-                        out.endTag(null, "pkg");
-                    }
-                }
-
-                out.endTag(null, "app-ops");
-                out.endDocument();
-                mFile.finishWrite(stream);
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to write state, restoring backup.", e);
-                mFile.failWrite(stream);
-            }
-        }
-        mHistoricalRegistry.writeAndClearDiscreteHistory();
-    }
-
-    private void dumpHelp(PrintWriter pw) {
-        pw.println("AppOps service (appops) dump options:");
-        pw.println("  -h");
-        pw.println("    Print this help text.");
-        pw.println("  --op [OP]");
-        pw.println("    Limit output to data associated with the given app op code.");
-        pw.println("  --mode [MODE]");
-        pw.println("    Limit output to data associated with the given app op mode.");
-        pw.println("  --package [PACKAGE]");
-        pw.println("    Limit output to data associated with the given package name.");
-        pw.println("  --attributionTag [attributionTag]");
-        pw.println("    Limit output to data associated with the given attribution tag.");
-        pw.println("  --include-discrete [n]");
-        pw.println("    Include discrete ops limited to n per dimension. Use zero for no limit.");
-        pw.println("  --watchers");
-        pw.println("    Only output the watcher sections.");
-        pw.println("  --history");
-        pw.println("    Only output history.");
-        pw.println("  --uid-state-changes");
-        pw.println("    Include logs about uid state changes.");
-    }
-
-    private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
-            @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
-            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
-        final int numAttributions = op.mAttributions.size();
-        for (int i = 0; i < numAttributions; i++) {
-            if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
-                    op.mAttributions.keyAt(i), filterAttributionTag)) {
-                continue;
-            }
-
-            pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
-            dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
-                    prefix + "  ");
-            pw.print(prefix + "]\n");
-        }
-    }
-
-    private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
-            @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
-            @NonNull Date date, @NonNull String prefix) {
-
-        final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
-                attributionTag).getAttributedOpEntries().get(attributionTag);
-
-        final ArraySet<Long> keys = entry.collectKeys();
-
-        final int keyCount = keys.size();
-        for (int k = 0; k < keyCount; k++) {
-            final long key = keys.valueAt(k);
-
-            final int uidState = AppOpsManager.extractUidStateFromKey(key);
-            final int flags = AppOpsManager.extractFlagsFromKey(key);
-
-            final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
-            final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
-            final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
-            final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
-
-            String proxyPkg = null;
-            String proxyAttributionTag = null;
-            int proxyUid = Process.INVALID_UID;
-            if (proxy != null) {
-                proxyPkg = proxy.getPackageName();
-                proxyAttributionTag = proxy.getAttributionTag();
-                proxyUid = proxy.getUid();
-            }
-
-            if (accessTime > 0) {
-                pw.print(prefix);
-                pw.print("Access: ");
-                pw.print(AppOpsManager.keyToString(key));
-                pw.print(" ");
-                date.setTime(accessTime);
-                pw.print(sdf.format(date));
-                pw.print(" (");
-                TimeUtils.formatDuration(accessTime - now, pw);
-                pw.print(")");
-                if (accessDuration > 0) {
-                    pw.print(" duration=");
-                    TimeUtils.formatDuration(accessDuration, pw);
-                }
-                if (proxyUid >= 0) {
-                    pw.print(" proxy[");
-                    pw.print("uid=");
-                    pw.print(proxyUid);
-                    pw.print(", pkg=");
-                    pw.print(proxyPkg);
-                    pw.print(", attributionTag=");
-                    pw.print(proxyAttributionTag);
-                    pw.print("]");
-                }
-                pw.println();
-            }
-
-            if (rejectTime > 0) {
-                pw.print(prefix);
-                pw.print("Reject: ");
-                pw.print(AppOpsManager.keyToString(key));
-                date.setTime(rejectTime);
-                pw.print(sdf.format(date));
-                pw.print(" (");
-                TimeUtils.formatDuration(rejectTime - now, pw);
-                pw.print(")");
-                if (proxyUid >= 0) {
-                    pw.print(" proxy[");
-                    pw.print("uid=");
-                    pw.print(proxyUid);
-                    pw.print(", pkg=");
-                    pw.print(proxyPkg);
-                    pw.print(", attributionTag=");
-                    pw.print(proxyAttributionTag);
-                    pw.print("]");
-                }
-                pw.println();
-            }
-        }
-
-        final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
-        if (attributedOp.isRunning()) {
-            long earliestElapsedTime = Long.MAX_VALUE;
-            long maxNumStarts = 0;
-            int numInProgressEvents = attributedOp.mInProgressEvents.size();
-            for (int i = 0; i < numInProgressEvents; i++) {
-                AttributedOp.InProgressStartOpEvent event =
-                        attributedOp.mInProgressEvents.valueAt(i);
-
-                earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
-                maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
-            }
-
-            pw.print(prefix + "Running start at: ");
-            TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
-            pw.println();
-
-            if (maxNumStarts > 1) {
-                pw.print(prefix + "startNesting=");
-                pw.println(maxNumStarts);
-            }
-        }
-    }
-
-    @NeverCompile // Avoid size overhead of debugging code.
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
-
-        int dumpOp = OP_NONE;
-        String dumpPackage = null;
-        String dumpAttributionTag = null;
-        int dumpUid = Process.INVALID_UID;
-        int dumpMode = -1;
-        boolean dumpWatchers = false;
-        // TODO ntmyren: Remove the dumpHistory and dumpFilter
-        boolean dumpHistory = false;
-        boolean includeDiscreteOps = false;
-        boolean dumpUidStateChangeLogs = false;
-        int nDiscreteOps = 10;
-        @HistoricalOpsRequestFilter int dumpFilter = 0;
-        boolean dumpAll = false;
-
-        if (args != null) {
-            for (int i = 0; i < args.length; i++) {
-                String arg = args[i];
-                if ("-h".equals(arg)) {
-                    dumpHelp(pw);
-                    return;
-                } else if ("-a".equals(arg)) {
-                    // dump all data
-                    dumpAll = true;
-                } else if ("--op".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --op option");
-                        return;
-                    }
-                    dumpOp = AppOpsService.Shell.strOpToOp(args[i], pw);
-                    dumpFilter |= FILTER_BY_OP_NAMES;
-                    if (dumpOp < 0) {
-                        return;
-                    }
-                } else if ("--package".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --package option");
-                        return;
-                    }
-                    dumpPackage = args[i];
-                    dumpFilter |= FILTER_BY_PACKAGE_NAME;
-                    try {
-                        dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
-                                PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
-                                0);
-                    } catch (RemoteException e) {
-                    }
-                    if (dumpUid < 0) {
-                        pw.println("Unknown package: " + dumpPackage);
-                        return;
-                    }
-                    dumpUid = UserHandle.getAppId(dumpUid);
-                    dumpFilter |= FILTER_BY_UID;
-                } else if ("--attributionTag".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --attributionTag option");
-                        return;
-                    }
-                    dumpAttributionTag = args[i];
-                    dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
-                } else if ("--mode".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --mode option");
-                        return;
-                    }
-                    dumpMode = AppOpsService.Shell.strModeToMode(args[i], pw);
-                    if (dumpMode < 0) {
-                        return;
-                    }
-                } else if ("--watchers".equals(arg)) {
-                    dumpWatchers = true;
-                } else if ("--include-discrete".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --include-discrete option");
-                        return;
-                    }
-                    try {
-                        nDiscreteOps = Integer.valueOf(args[i]);
-                    } catch (NumberFormatException e) {
-                        pw.println("Wrong parameter: " + args[i]);
-                        return;
-                    }
-                    includeDiscreteOps = true;
-                } else if ("--history".equals(arg)) {
-                    dumpHistory = true;
-                } else if (arg.length() > 0 && arg.charAt(0) == '-') {
-                    pw.println("Unknown option: " + arg);
-                    return;
-                } else if ("--uid-state-changes".equals(arg)) {
-                    dumpUidStateChangeLogs = true;
-                } else {
-                    pw.println("Unknown command: " + arg);
-                    return;
-                }
-            }
-        }
-
-        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-        final Date date = new Date();
-        synchronized (this) {
-            pw.println("Current AppOps Service state:");
-            if (!dumpHistory && !dumpWatchers) {
-                mConstants.dump(pw);
-            }
-            pw.println();
-            final long now = System.currentTimeMillis();
-            final long nowElapsed = SystemClock.elapsedRealtime();
-            boolean needSep = false;
-            if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
-                    && !dumpHistory) {
-                pw.println("  Profile owners:");
-                for (int poi = 0; poi < mProfileOwners.size(); poi++) {
-                    pw.print("    User #");
-                    pw.print(mProfileOwners.keyAt(poi));
-                    pw.print(": ");
-                    UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
-                    pw.println();
-                }
-                pw.println();
-            }
-
-            if (!dumpHistory) {
-                needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
-            }
-
-            if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
-                boolean printedHeader = false;
-                for (int i = 0; i < mModeWatchers.size(); i++) {
-                    final ModeCallback cb = mModeWatchers.valueAt(i);
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
-                        continue;
-                    }
-                    needSep = true;
-                    if (!printedHeader) {
-                        pw.println("  All op mode watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
-                    pw.print(": ");
-                    pw.println(cb);
-                }
-            }
-            if (mActiveWatchers.size() > 0 && dumpMode < 0) {
-                needSep = true;
-                boolean printedHeader = false;
-                for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
-                    final SparseArray<ActiveCallback> activeWatchers =
-                            mActiveWatchers.valueAt(watcherNum);
-                    if (activeWatchers.size() <= 0) {
-                        continue;
-                    }
-                    final ActiveCallback cb = activeWatchers.valueAt(0);
-                    if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
-                        continue;
-                    }
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                        continue;
-                    }
-                    if (!printedHeader) {
-                        pw.println("  All op active watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(
-                            mActiveWatchers.keyAt(watcherNum))));
-                    pw.println(" ->");
-                    pw.print("        [");
-                    final int opCount = activeWatchers.size();
-                    for (int opNum = 0; opNum < opCount; opNum++) {
-                        if (opNum > 0) {
-                            pw.print(' ');
-                        }
-                        pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
-                        if (opNum < opCount - 1) {
-                            pw.print(',');
-                        }
-                    }
-                    pw.println("]");
-                    pw.print("        ");
-                    pw.println(cb);
-                }
-            }
-            if (mStartedWatchers.size() > 0 && dumpMode < 0) {
-                needSep = true;
-                boolean printedHeader = false;
-
-                final int watchersSize = mStartedWatchers.size();
-                for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
-                    final SparseArray<StartedCallback> startedWatchers =
-                            mStartedWatchers.valueAt(watcherNum);
-                    if (startedWatchers.size() <= 0) {
-                        continue;
-                    }
-
-                    final StartedCallback cb = startedWatchers.valueAt(0);
-                    if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
-                        continue;
-                    }
-
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                        continue;
-                    }
-
-                    if (!printedHeader) {
-                        pw.println("  All op started watchers:");
-                        printedHeader = true;
-                    }
-
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(
-                            mStartedWatchers.keyAt(watcherNum))));
-                    pw.println(" ->");
-
-                    pw.print("        [");
-                    final int opCount = startedWatchers.size();
-                    for (int opNum = 0; opNum < opCount; opNum++) {
-                        if (opNum > 0) {
-                            pw.print(' ');
-                        }
-
-                        pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
-                        if (opNum < opCount - 1) {
-                            pw.print(',');
-                        }
-                    }
-                    pw.println("]");
-
-                    pw.print("        ");
-                    pw.println(cb);
-                }
-            }
-            if (mNotedWatchers.size() > 0 && dumpMode < 0) {
-                needSep = true;
-                boolean printedHeader = false;
-                for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
-                    final SparseArray<NotedCallback> notedWatchers =
-                            mNotedWatchers.valueAt(watcherNum);
-                    if (notedWatchers.size() <= 0) {
-                        continue;
-                    }
-                    final NotedCallback cb = notedWatchers.valueAt(0);
-                    if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
-                        continue;
-                    }
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                        continue;
-                    }
-                    if (!printedHeader) {
-                        pw.println("  All op noted watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(
-                            mNotedWatchers.keyAt(watcherNum))));
-                    pw.println(" ->");
-                    pw.print("        [");
-                    final int opCount = notedWatchers.size();
-                    for (int opNum = 0; opNum < opCount; opNum++) {
-                        if (opNum > 0) {
-                            pw.print(' ');
-                        }
-                        pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
-                        if (opNum < opCount - 1) {
-                            pw.print(',');
-                        }
-                    }
-                    pw.println("]");
-                    pw.print("        ");
-                    pw.println(cb);
-                }
-            }
-            if (needSep) {
-                pw.println();
-            }
-            for (int i = 0; i < mUidStates.size(); i++) {
-                UidState uidState = mUidStates.valueAt(i);
-                final SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
-
-                if (dumpWatchers || dumpHistory) {
-                    continue;
-                }
-                if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
-                    boolean hasOp = dumpOp < 0 || (opModes != null
-                            && opModes.indexOfKey(dumpOp) >= 0);
-                    boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
-                    boolean hasMode = dumpMode < 0;
-                    if (!hasMode && opModes != null) {
-                        for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
-                            if (opModes.valueAt(opi) == dumpMode) {
-                                hasMode = true;
-                            }
-                        }
-                    }
-                    if (pkgOps != null) {
-                        for (int pkgi = 0;
-                                (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
-                                pkgi++) {
-                            Ops ops = pkgOps.valueAt(pkgi);
-                            if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
-                                hasOp = true;
-                            }
-                            if (!hasMode) {
-                                for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
-                                    if (ops.valueAt(opi).getMode() == dumpMode) {
-                                        hasMode = true;
-                                    }
-                                }
-                            }
-                            if (!hasPackage && dumpPackage.equals(ops.packageName)) {
-                                hasPackage = true;
-                            }
-                        }
-                    }
-                    if (uidState.foregroundOps != null && !hasOp) {
-                        if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
-                            hasOp = true;
-                        }
-                    }
-                    if (!hasOp || !hasPackage || !hasMode) {
-                        continue;
-                    }
-                }
-
-                pw.print("  Uid ");
-                UserHandle.formatUid(pw, uidState.uid);
-                pw.println(":");
-                uidState.dump(pw, nowElapsed);
-                if (uidState.foregroundOps != null && (dumpMode < 0
-                        || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
-                    pw.println("    foregroundOps:");
-                    for (int j = 0; j < uidState.foregroundOps.size(); j++) {
-                        if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
-                            continue;
-                        }
-                        pw.print("      ");
-                        pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
-                        pw.print(": ");
-                        pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
-                    }
-                    pw.print("    hasForegroundWatchers=");
-                    pw.println(uidState.hasForegroundWatchers);
-                }
-                needSep = true;
-
-                if (opModes != null) {
-                    final int opModeCount = opModes.size();
-                    for (int j = 0; j < opModeCount; j++) {
-                        final int code = opModes.keyAt(j);
-                        final int mode = opModes.valueAt(j);
-                        if (dumpOp >= 0 && dumpOp != code) {
-                            continue;
-                        }
-                        if (dumpMode >= 0 && dumpMode != mode) {
-                            continue;
-                        }
-                        pw.print("      ");
-                        pw.print(AppOpsManager.opToName(code));
-                        pw.print(": mode=");
-                        pw.println(AppOpsManager.modeToName(mode));
-                    }
-                }
-
-                if (pkgOps == null) {
-                    continue;
-                }
-
-                for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
-                    final Ops ops = pkgOps.valueAt(pkgi);
-                    if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
-                        continue;
-                    }
-                    boolean printedPackage = false;
-                    for (int j = 0; j < ops.size(); j++) {
-                        final Op op = ops.valueAt(j);
-                        final int opCode = op.op;
-                        if (dumpOp >= 0 && dumpOp != opCode) {
-                            continue;
-                        }
-                        if (dumpMode >= 0 && dumpMode != op.getMode()) {
-                            continue;
-                        }
-                        if (!printedPackage) {
-                            pw.print("    Package ");
-                            pw.print(ops.packageName);
-                            pw.println(":");
-                            printedPackage = true;
-                        }
-                        pw.print("      ");
-                        pw.print(AppOpsManager.opToName(opCode));
-                        pw.print(" (");
-                        pw.print(AppOpsManager.modeToName(op.getMode()));
-                        final int switchOp = AppOpsManager.opToSwitch(opCode);
-                        if (switchOp != opCode) {
-                            pw.print(" / switch ");
-                            pw.print(AppOpsManager.opToName(switchOp));
-                            final Op switchObj = ops.get(switchOp);
-                            int mode = switchObj == null
-                                    ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
-                            pw.print("=");
-                            pw.print(AppOpsManager.modeToName(mode));
-                        }
-                        pw.println("): ");
-                        dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
-                                sdf, date, "        ");
-                    }
-                }
-            }
-            if (needSep) {
-                pw.println();
-            }
-
-            boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
-            mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
-
-            if (dumpAll || dumpUidStateChangeLogs) {
-                pw.println();
-                pw.println("Uid State Changes Event Log:");
-                getUidStateTracker().dumpEvents(pw);
-            }
-        }
-
-        // Must not hold the appops lock
-        if (dumpHistory && !dumpWatchers) {
-            mHistoricalRegistry.dump("  ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
-                    dumpFilter);
-        }
-        if (includeDiscreteOps) {
-            pw.println("Discrete accesses: ");
-            mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
-                    dumpFilter, dumpOp, sdf, date, "  ", nDiscreteOps);
-        }
-    }
-
-    @Override
-    public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
-        checkSystemUid("setUserRestrictions");
-        Objects.requireNonNull(restrictions);
-        Objects.requireNonNull(token);
-        for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
-            String restriction = AppOpsManager.opToRestriction(i);
-            if (restriction != null) {
-                setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
-                        userHandle, null);
-            }
-        }
-    }
-
-    @Override
-    public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
-            PackageTagsList excludedPackageTags) {
-        if (Binder.getCallingPid() != Process.myPid()) {
-            mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
-                    Binder.getCallingPid(), Binder.getCallingUid(), null);
-        }
-        if (userHandle != UserHandle.getCallingUserId()) {
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission
-                    .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
-                    && mContext.checkCallingOrSelfPermission(Manifest.permission
-                    .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
-                        + " INTERACT_ACROSS_USERS to interact cross user ");
-            }
-        }
-        verifyIncomingOp(code);
-        Objects.requireNonNull(token);
-        setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
-    }
-
-    private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
-            int userHandle, PackageTagsList excludedPackageTags) {
-        synchronized (AppOpsServiceImpl.this) {
-            ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
-
-            if (restrictionState == null) {
-                try {
-                    restrictionState = new ClientUserRestrictionState(token);
-                } catch (RemoteException e) {
-                    return;
-                }
-                mOpUserRestrictions.put(token, restrictionState);
-            }
-
-            if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
-                    userHandle)) {
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
-                        restricted, userHandle));
-            }
-
-            if (restrictionState.isDefault()) {
-                mOpUserRestrictions.remove(token);
-                restrictionState.destroy();
-            }
-        }
-    }
-
-    @Override
-    public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
-        if (Binder.getCallingPid() != Process.myPid()) {
-            throw new SecurityException("Only the system can set global restrictions");
-        }
-
-        synchronized (this) {
-            ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
-
-            if (restrictionState == null) {
-                try {
-                    restrictionState = new ClientGlobalRestrictionState(token);
-                } catch (RemoteException e) {
-                    return;
-                }
-                mOpGlobalRestrictions.put(token, restrictionState);
-            }
-
-            if (restrictionState.setRestriction(code, restricted)) {
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
-                        restricted, UserHandle.USER_ALL));
-            }
-
-            if (restrictionState.isDefault()) {
-                mOpGlobalRestrictions.remove(token);
-                restrictionState.destroy();
-            }
-        }
-    }
-
-    @Override
-    public int getOpRestrictionCount(int code, UserHandle user, String pkg,
-            String attributionTag) {
-        int number = 0;
-        synchronized (this) {
-            int numRestrictions = mOpUserRestrictions.size();
-            for (int i = 0; i < numRestrictions; i++) {
-                if (mOpUserRestrictions.valueAt(i)
-                        .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
-                                false)) {
-                    number++;
-                }
-            }
-
-            numRestrictions = mOpGlobalRestrictions.size();
-            for (int i = 0; i < numRestrictions; i++) {
-                if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
-                    number++;
-                }
-            }
-        }
-
-        return number;
-    }
-
-    private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
-        synchronized (AppOpsServiceImpl.this) {
-            int numUids = mUidStates.size();
-            for (int uidNum = 0; uidNum < numUids; uidNum++) {
-                int uid = mUidStates.keyAt(uidNum);
-                if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
-                    continue;
-                }
-                updateStartedOpModeForUidLocked(code, restricted, uid);
-            }
-        }
-    }
-
-    private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
-        UidState uidState = mUidStates.get(uid);
-        if (uidState == null || uidState.pkgOps == null) {
-            return;
-        }
-
-        int numPkgOps = uidState.pkgOps.size();
-        for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
-            Ops ops = uidState.pkgOps.valueAt(pkgNum);
-            Op op = ops != null ? ops.get(code) : null;
-            if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
-                continue;
-            }
-            int numAttrTags = op.mAttributions.size();
-            for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
-                AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
-                if (restricted && attrOp.isRunning()) {
-                    attrOp.pause();
-                } else if (attrOp.isPaused()) {
-                    attrOp.resume();
-                }
-            }
-        }
-    }
-
-    @Override
-    public void notifyWatchersOfChange(int code, int uid) {
-        final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
-        synchronized (this) {
-            modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
-            if (modeChangedListenerSet == null) {
-                return;
-            }
-        }
-
-        notifyOpChanged(modeChangedListenerSet, code, uid, null);
-    }
-
-    @Override
-    public void removeUser(int userHandle) throws RemoteException {
-        checkSystemUid("removeUser");
-        synchronized (AppOpsServiceImpl.this) {
-            final int tokenCount = mOpUserRestrictions.size();
-            for (int i = tokenCount - 1; i >= 0; i--) {
-                ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
-                opRestrictions.removeUser(userHandle);
-            }
-            removeUidsForUserLocked(userHandle);
-        }
-    }
-
-    @Override
-    public boolean isOperationActive(int code, int uid, String packageName) {
-        if (Binder.getCallingUid() != uid) {
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                    != PackageManager.PERMISSION_GRANTED) {
-                return false;
-            }
-        }
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return false;
-        }
-
-        final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return false;
-        }
-        // TODO moltmann: Allow to check for attribution op activeness
-        synchronized (AppOpsServiceImpl.this) {
-            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
-            if (pkgOps == null) {
-                return false;
-            }
-
-            Op op = pkgOps.get(code);
-            if (op == null) {
-                return false;
-            }
-
-            return op.isRunning();
-        }
-    }
-
-    @Override
-    public boolean isProxying(int op, @NonNull String proxyPackageName,
-            @NonNull String proxyAttributionTag, int proxiedUid,
-            @NonNull String proxiedPackageName) {
-        Objects.requireNonNull(proxyPackageName);
-        Objects.requireNonNull(proxiedPackageName);
-        final long callingUid = Binder.getCallingUid();
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
-                    proxiedPackageName, new int[]{op});
-            if (packageOps == null || packageOps.isEmpty()) {
-                return false;
-            }
-            final List<OpEntry> opEntries = packageOps.get(0).getOps();
-            if (opEntries.isEmpty()) {
-                return false;
-            }
-            final OpEntry opEntry = opEntries.get(0);
-            if (!opEntry.isRunning()) {
-                return false;
-            }
-            final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
-                    OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
-            return proxyInfo != null && callingUid == proxyInfo.getUid()
-                    && proxyPackageName.equals(proxyInfo.getPackageName())
-                    && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    @Override
-    public void resetPackageOpsNoHistory(@NonNull String packageName) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "resetPackageOpsNoHistory");
-        synchronized (AppOpsServiceImpl.this) {
-            final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
-                    UserHandle.getCallingUserId());
-            if (uid == Process.INVALID_UID) {
-                return;
-            }
-            UidState uidState = mUidStates.get(uid);
-            if (uidState == null || uidState.pkgOps == null) {
-                return;
-            }
-            Ops removedOps = uidState.pkgOps.remove(packageName);
-            mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
-            if (removedOps != null) {
-                scheduleFastWriteLocked();
-            }
-        }
-    }
-
-    @Override
-    public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
-            long baseSnapshotInterval, int compressionStep) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "setHistoryParameters");
-        // Must not hold the appops lock
-        mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
-    }
-
-    @Override
-    public void offsetHistory(long offsetMillis) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "offsetHistory");
-        // Must not hold the appops lock
-        mHistoricalRegistry.offsetHistory(offsetMillis);
-        mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
-    }
-
-    @Override
-    public void addHistoricalOps(HistoricalOps ops) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "addHistoricalOps");
-        // Must not hold the appops lock
-        mHistoricalRegistry.addHistoricalOps(ops);
-    }
-
-    @Override
-    public void resetHistoryParameters() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "resetHistoryParameters");
-        // Must not hold the appops lock
-        mHistoricalRegistry.resetHistoryParameters();
-    }
-
-    @Override
-    public void clearHistory() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "clearHistory");
-        // Must not hold the appops lock
-        mHistoricalRegistry.clearAllHistory();
-    }
-
-    @Override
-    public void rebootHistory(long offlineDurationMillis) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "rebootHistory");
-
-        Preconditions.checkArgument(offlineDurationMillis >= 0);
-
-        // Must not hold the appops lock
-        mHistoricalRegistry.shutdown();
-
-        if (offlineDurationMillis > 0) {
-            SystemClock.sleep(offlineDurationMillis);
-        }
-
-        mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
-        mHistoricalRegistry.systemReady(mContext.getContentResolver());
-        mHistoricalRegistry.persistPendingHistory();
-    }
-
-    @GuardedBy("this")
-    private void removeUidsForUserLocked(int userHandle) {
-        for (int i = mUidStates.size() - 1; i >= 0; --i) {
-            final int uid = mUidStates.keyAt(i);
-            if (UserHandle.getUserId(uid) == userHandle) {
-                mUidStates.valueAt(i).clear();
-                mUidStates.removeAt(i);
-            }
-        }
-    }
-
-    private void checkSystemUid(String function) {
-        int uid = Binder.getCallingUid();
-        if (uid != Process.SYSTEM_UID) {
-            throw new SecurityException(function + " must by called by the system");
-        }
-    }
-
-    private static int resolveUid(String packageName) {
-        if (packageName == null) {
-            return Process.INVALID_UID;
-        }
-        switch (packageName) {
-            case "root":
-                return Process.ROOT_UID;
-            case "shell":
-            case "dumpstate":
-                return Process.SHELL_UID;
-            case "media":
-                return Process.MEDIA_UID;
-            case "audioserver":
-                return Process.AUDIOSERVER_UID;
-            case "cameraserver":
-                return Process.CAMERASERVER_UID;
-        }
-        return Process.INVALID_UID;
-    }
-
-    private static String[] getPackagesForUid(int uid) {
-        String[] packageNames = null;
-
-        // Very early during boot the package manager is not yet or not yet fully started. At this
-        // time there are no packages yet.
-        if (AppGlobals.getPackageManager() != null) {
-            try {
-                packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
-            } catch (RemoteException e) {
-                /* ignore - local call */
-            }
-        }
-        if (packageNames == null) {
-            return EmptyArray.STRING;
-        }
-        return packageNames;
-    }
-
-    private final class ClientUserRestrictionState implements DeathRecipient {
-        private final IBinder mToken;
-
-        ClientUserRestrictionState(IBinder token)
-                throws RemoteException {
-            token.linkToDeath(this, 0);
-            this.mToken = token;
-        }
-
-        public boolean setRestriction(int code, boolean restricted,
-                PackageTagsList excludedPackageTags, int userId) {
-            return mAppOpsRestrictions.setUserRestriction(mToken, userId, code,
-                    restricted, excludedPackageTags);
-        }
-
-        public boolean hasRestriction(int code, String packageName, String attributionTag,
-                int userId, boolean isCheckOp) {
-            return mAppOpsRestrictions.getUserRestriction(mToken, userId, code, packageName,
-                    attributionTag, isCheckOp);
-        }
-
-        public void removeUser(int userId) {
-            mAppOpsRestrictions.clearUserRestrictions(mToken, userId);
-        }
-
-        public boolean isDefault() {
-            return !mAppOpsRestrictions.hasUserRestrictions(mToken);
-        }
-
-        @Override
-        public void binderDied() {
-            synchronized (AppOpsServiceImpl.this) {
-                mAppOpsRestrictions.clearUserRestrictions(mToken);
-                mOpUserRestrictions.remove(mToken);
-                destroy();
-            }
-        }
-
-        public void destroy() {
-            mToken.unlinkToDeath(this, 0);
-        }
-    }
-
-    private final class ClientGlobalRestrictionState implements DeathRecipient {
-        final IBinder mToken;
-
-        ClientGlobalRestrictionState(IBinder token)
-                throws RemoteException {
-            token.linkToDeath(this, 0);
-            this.mToken = token;
-        }
-
-        boolean setRestriction(int code, boolean restricted) {
-            return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
-        }
-
-        boolean hasRestriction(int code) {
-            return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
-        }
-
-        boolean isDefault() {
-            return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
-        }
-
-        @Override
-        public void binderDied() {
-            mAppOpsRestrictions.clearGlobalRestrictions(mToken);
-            mOpGlobalRestrictions.remove(mToken);
-            destroy();
-        }
-
-        void destroy() {
-            mToken.unlinkToDeath(this, 0);
-        }
-    }
-
-    @Override
-    public void setDeviceAndProfileOwners(SparseIntArray owners) {
-        synchronized (this) {
-            mProfileOwners = owners;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
deleted file mode 100644
index 8420fcb..0000000
--- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
- * Copyright (C) 2022 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.appop;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.content.AttributionSource;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.PackageTagsList;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-
-import com.android.internal.app.IAppOpsActiveCallback;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsNotedCallback;
-import com.android.internal.app.IAppOpsStartedCallback;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-
-/**
- *
- */
-public interface AppOpsServiceInterface extends PersistenceScheduler {
-
-    /**
-     *
-     */
-    void systemReady();
-
-    /**
-     *
-     */
-    void shutdown();
-
-    /**
-     *
-     * @param uid
-     * @param packageName
-     */
-    void verifyPackage(int uid, String packageName);
-
-    /**
-     *
-     * @param op
-     * @param packageName
-     * @param flags
-     * @param callback
-     */
-    void startWatchingModeWithFlags(int op, String packageName, int flags,
-            IAppOpsCallback callback);
-
-    /**
-     *
-     * @param callback
-     */
-    void stopWatchingMode(IAppOpsCallback callback);
-
-    /**
-     *
-     * @param ops
-     * @param callback
-     */
-    void startWatchingActive(int[] ops, IAppOpsActiveCallback callback);
-
-    /**
-     *
-     * @param callback
-     */
-    void stopWatchingActive(IAppOpsActiveCallback callback);
-
-    /**
-     *
-     * @param ops
-     * @param callback
-     */
-    void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback);
-
-    /**
-     *
-     * @param callback
-     */
-    void stopWatchingStarted(IAppOpsStartedCallback callback);
-
-    /**
-     *
-     * @param ops
-     * @param callback
-     */
-    void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback);
-
-    /**
-     *
-     * @param callback
-     */
-    void stopWatchingNoted(IAppOpsNotedCallback callback);
-
-    /**
-     * @param clientId
-     * @param code
-     * @param uid
-     * @param packageName
-     * @param attributionTag
-     * @param startIfModeDefault
-     * @param message
-     * @param attributionFlags
-     * @param attributionChainId
-     * @return
-     */
-    int startOperation(@NonNull IBinder clientId, int code, int uid,
-            @Nullable String packageName, @Nullable String attributionTag,
-            boolean startIfModeDefault, @NonNull String message,
-            @AppOpsManager.AttributionFlags int attributionFlags,
-            int attributionChainId);
-
-
-    int startOperationUnchecked(IBinder clientId, int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
-            @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags,
-            boolean startIfModeDefault, @AppOpsManager.AttributionFlags int attributionFlags,
-            int attributionChainId, boolean dryRun);
-
-    /**
-     *
-     * @param clientId
-     * @param code
-     * @param uid
-     * @param packageName
-     * @param attributionTag
-     */
-    void finishOperation(IBinder clientId, int code, int uid, String packageName,
-            String attributionTag);
-
-    /**
-     *
-     * @param clientId
-     * @param code
-     * @param uid
-     * @param packageName
-     * @param attributionTag
-     */
-    void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
-            String attributionTag);
-
-    /**
-     *
-     * @param uidPackageNames
-     * @param visible
-     */
-    void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible);
-
-    /**
-     *
-     */
-    void readState();
-
-    /**
-     *
-     */
-    void writeState();
-
-    /**
-     *
-     * @param uid
-     * @param packageName
-     */
-    void packageRemoved(int uid, String packageName);
-
-    /**
-     *
-     * @param uid
-     */
-    void uidRemoved(int uid);
-
-    /**
-     *
-     * @param uid
-     * @param procState
-     * @param capability
-     */
-    void updateUidProcState(int uid, int procState,
-            @ActivityManager.ProcessCapability int capability);
-
-    /**
-     *
-     * @param ops
-     * @return
-     */
-    List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops);
-
-    /**
-     *
-     * @param uid
-     * @param packageName
-     * @param ops
-     * @return
-     */
-    List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
-            int[] ops);
-
-    /**
-     *
-     * @param uid
-     * @param packageName
-     * @param attributionTag
-     * @param opNames
-     * @param dataType
-     * @param filter
-     * @param beginTimeMillis
-     * @param endTimeMillis
-     * @param flags
-     * @param callback
-     */
-    void getHistoricalOps(int uid, String packageName, String attributionTag,
-            List<String> opNames, int dataType, int filter, long beginTimeMillis,
-            long endTimeMillis, int flags, RemoteCallback callback);
-
-    /**
-     *
-     * @param uid
-     * @param packageName
-     * @param attributionTag
-     * @param opNames
-     * @param dataType
-     * @param filter
-     * @param beginTimeMillis
-     * @param endTimeMillis
-     * @param flags
-     * @param callback
-     */
-    void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
-            List<String> opNames, int dataType, int filter, long beginTimeMillis,
-            long endTimeMillis, int flags, RemoteCallback callback);
-
-    /**
-     *
-     */
-    void reloadNonHistoricalState();
-
-    /**
-     *
-     * @param uid
-     * @param ops
-     * @return
-     */
-    List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops);
-
-    /**
-     *
-     * @param owners
-     */
-    void setDeviceAndProfileOwners(SparseIntArray owners);
-
-    // used in audio restriction calls, might just copy the logic to avoid having this call.
-    /**
-     *
-     * @param callingPid
-     * @param callingUid
-     * @param targetUid
-     */
-    void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid);
-
-    /**
-     *
-     * @param code
-     * @param uid
-     * @param mode
-     * @param permissionPolicyCallback
-     */
-    void setUidMode(int code, int uid, int mode,
-            @Nullable IAppOpsCallback permissionPolicyCallback);
-
-    /**
-     *
-     * @param code
-     * @param uid
-     * @param packageName
-     * @param mode
-     * @param permissionPolicyCallback
-     */
-    void setMode(int code, int uid, @NonNull String packageName, int mode,
-            @Nullable IAppOpsCallback permissionPolicyCallback);
-
-    /**
-     *
-     * @param reqUserId
-     * @param reqPackageName
-     */
-    void resetAllModes(int reqUserId, String reqPackageName);
-
-    /**
-     *
-     * @param code
-     * @param uid
-     * @param packageName
-     * @param attributionTag
-     * @param raw
-     * @return
-     */
-    int checkOperation(int code, int uid, String packageName,
-            @Nullable String attributionTag, boolean raw);
-
-    /**
-     *
-     * @param uid
-     * @param packageName
-     * @return
-     */
-    int checkPackage(int uid, String packageName);
-
-    /**
-     *
-     * @param code
-     * @param uid
-     * @param packageName
-     * @param attributionTag
-     * @param message
-     * @return
-     */
-    int noteOperation(int code, int uid, @Nullable String packageName,
-            @Nullable String attributionTag, @Nullable String message);
-
-    /**
-     *
-     * @param code
-     * @param uid
-     * @param packageName
-     * @param attributionTag
-     * @param proxyUid
-     * @param proxyPackageName
-     * @param proxyAttributionTag
-     * @param flags
-     * @return
-     */
-    @AppOpsManager.Mode
-    int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
-            @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags);
-
-    boolean isAttributionTagValid(int uid, @NonNull String packageName,
-            @Nullable String attributionTag, @Nullable String proxyPackageName);
-
-    /**
-     *
-     * @param fd
-     * @param pw
-     * @param args
-     */
-    @NeverCompile
-        // Avoid size overhead of debugging code.
-    void dump(FileDescriptor fd, PrintWriter pw, String[] args);
-
-    /**
-     *
-     * @param restrictions
-     * @param token
-     * @param userHandle
-     */
-    void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle);
-
-    /**
-     *
-     * @param code
-     * @param restricted
-     * @param token
-     * @param userHandle
-     * @param excludedPackageTags
-     */
-    void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
-            PackageTagsList excludedPackageTags);
-
-    /**
-     *
-     * @param code
-     * @param restricted
-     * @param token
-     */
-    void setGlobalRestriction(int code, boolean restricted, IBinder token);
-
-    /**
-     *
-     * @param code
-     * @param user
-     * @param pkg
-     * @param attributionTag
-     * @return
-     */
-    int getOpRestrictionCount(int code, UserHandle user, String pkg,
-            String attributionTag);
-
-    /**
-     *
-     * @param code
-     * @param uid
-     */
-    // added to interface for audio restriction stuff
-    void notifyWatchersOfChange(int code, int uid);
-
-    /**
-     *
-     * @param userHandle
-     * @throws RemoteException
-     */
-    void removeUser(int userHandle) throws RemoteException;
-
-    /**
-     *
-     * @param code
-     * @param uid
-     * @param packageName
-     * @return
-     */
-    boolean isOperationActive(int code, int uid, String packageName);
-
-    /**
-     *
-     * @param op
-     * @param proxyPackageName
-     * @param proxyAttributionTag
-     * @param proxiedUid
-     * @param proxiedPackageName
-     * @return
-     */
-    // TODO this one might not need to be in the interface
-    boolean isProxying(int op, @NonNull String proxyPackageName,
-            @NonNull String proxyAttributionTag, int proxiedUid,
-            @NonNull String proxiedPackageName);
-
-    /**
-     *
-     * @param packageName
-     */
-    void resetPackageOpsNoHistory(@NonNull String packageName);
-
-    /**
-     *
-     * @param mode
-     * @param baseSnapshotInterval
-     * @param compressionStep
-     */
-    void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
-            long baseSnapshotInterval, int compressionStep);
-
-    /**
-     *
-     * @param offsetMillis
-     */
-    void offsetHistory(long offsetMillis);
-
-    /**
-     *
-     * @param ops
-     */
-    void addHistoricalOps(AppOpsManager.HistoricalOps ops);
-
-    /**
-     *
-     */
-    void resetHistoryParameters();
-
-    /**
-     *
-     */
-    void clearHistory();
-
-    /**
-     *
-     * @param offlineDurationMillis
-     */
-    void rebootHistory(long offlineDurationMillis);
-}
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index c1434e4..5114bd5 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -59,7 +59,7 @@
     private final DelayableExecutor mExecutor;
     private final Clock mClock;
     private ActivityManagerInternal mActivityManagerInternal;
-    private AppOpsServiceImpl.Constants mConstants;
+    private AppOpsService.Constants mConstants;
 
     private SparseIntArray mUidStates = new SparseIntArray();
     private SparseIntArray mPendingUidStates = new SparseIntArray();
@@ -85,7 +85,7 @@
 
     AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
             Handler handler, Executor lockingExecutor, Clock clock,
-            AppOpsServiceImpl.Constants constants) {
+            AppOpsService.Constants constants) {
 
         this(activityManagerInternal, new DelayableExecutor() {
             @Override
@@ -102,7 +102,7 @@
 
     @VisibleForTesting
     AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
-            DelayableExecutor executor, Clock clock, AppOpsServiceImpl.Constants constants,
+            DelayableExecutor executor, Clock clock, AppOpsService.Constants constants,
             Thread executorThread) {
         mActivityManagerInternal = activityManagerInternal;
         mExecutor = executor;
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 7970269..dcc36bc 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -40,9 +40,9 @@
 import java.util.NoSuchElementException;
 
 final class AttributedOp {
-    private final @NonNull AppOpsServiceImpl mAppOpsService;
+    private final @NonNull AppOpsService mAppOpsService;
     public final @Nullable String tag;
-    public final @NonNull AppOpsServiceImpl.Op parent;
+    public final @NonNull AppOpsService.Op parent;
 
     /**
      * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
@@ -80,8 +80,8 @@
     // @GuardedBy("mAppOpsService")
     @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
 
-    AttributedOp(@NonNull AppOpsServiceImpl appOpsService, @Nullable String tag,
-                 @NonNull AppOpsServiceImpl.Op parent) {
+    AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
+            @NonNull AppOpsService.Op parent) {
         mAppOpsService = appOpsService;
         this.tag = tag;
         this.parent = parent;
@@ -131,8 +131,8 @@
 
         AppOpsManager.OpEventProxyInfo proxyInfo = null;
         if (proxyUid != Process.INVALID_UID) {
-            proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid,
-                    proxyPackageName, proxyAttributionTag);
+            proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
+                    proxyAttributionTag);
         }
 
         AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -238,7 +238,7 @@
         if (event == null) {
             event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
                     SystemClock.elapsedRealtime(), clientId, tag,
-                    PooledLambda.obtainRunnable(AppOpsServiceImpl::onClientDeath, this, clientId),
+                    PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
                     proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
                     attributionFlags, attributionChainId);
             events.put(clientId, event);
@@ -251,9 +251,9 @@
         event.mNumUnfinishedStarts++;
 
         if (isStarted) {
-            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
-                    parent.uid, parent.packageName, tag, uidState, flags, startTime,
-                    attributionFlags, attributionChainId);
+            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
+                    parent.packageName, tag, uidState, flags, startTime, attributionFlags,
+                    attributionChainId);
         }
     }
 
@@ -309,8 +309,8 @@
             mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
                     finishedEvent);
 
-            mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op,
-                    parent.uid, parent.packageName, tag, event.getUidState(),
+            mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
+                    parent.packageName, tag, event.getUidState(),
                     event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
                     event.getAttributionFlags(), event.getAttributionChainId());
 
@@ -334,13 +334,13 @@
     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
     private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
         if (!isPaused()) {
-            Slog.wtf(AppOpsServiceImpl.TAG, "No ops running or paused");
+            Slog.wtf(AppOpsService.TAG, "No ops running or paused");
             return;
         }
 
         int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
         if (indexOfToken < 0) {
-            Slog.wtf(AppOpsServiceImpl.TAG, "No op running or paused for the client");
+            Slog.wtf(AppOpsService.TAG, "No op running or paused for the client");
             return;
         } else if (isPausing) {
             // already paused
@@ -416,9 +416,9 @@
             mInProgressEvents.put(event.getClientId(), event);
             event.setStartElapsedTime(SystemClock.elapsedRealtime());
             event.setStartTime(startTime);
-            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
-                    parent.uid, parent.packageName, tag, event.getUidState(), event.getFlags(),
-                    startTime, event.getAttributionFlags(), event.getAttributionChainId());
+            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
+                    parent.packageName, tag, event.getUidState(), event.getFlags(), startTime,
+                    event.getAttributionFlags(), event.getAttributionChainId());
             if (shouldSendActive) {
                 mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
                         parent.packageName, tag, true, event.getAttributionFlags(),
@@ -503,8 +503,8 @@
                         newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1;
                     }
                 } catch (RemoteException e) {
-                    if (AppOpsServiceImpl.DEBUG) {
-                        Slog.e(AppOpsServiceImpl.TAG,
+                    if (AppOpsService.DEBUG) {
+                        Slog.e(AppOpsService.TAG,
                                 "Cannot switch to new uidState " + newState);
                     }
                 }
@@ -555,8 +555,8 @@
             ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents =
                     opToAdd.isRunning()
                             ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
-            Slog.w(AppOpsServiceImpl.TAG, "Ignoring " + ignoredEvents.size()
-                    + " app-ops, running: " + opToAdd.isRunning());
+            Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
+                    + opToAdd.isRunning());
 
             int numInProgressEvents = ignoredEvents.size();
             for (int i = 0; i < numInProgressEvents; i++) {
@@ -668,22 +668,16 @@
         /**
          * Create a new {@link InProgressStartOpEvent}.
          *
-         * @param startTime          The time {@link AppOpCheckingServiceInterface#startOperation}
-         *                          was called
-         * @param startElapsedTime   The elapsed time whe
-         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
-         * @param clientId           The client id of the caller of
-         *                          {@link AppOpCheckingServiceInterface#startOperation}
+         * @param startTime          The time {@link #startOperation} was called
+         * @param startElapsedTime   The elapsed time when {@link #startOperation} was called
+         * @param clientId           The client id of the caller of {@link #startOperation}
          * @param attributionTag     The attribution tag for the operation.
          * @param onDeath            The code to execute on client death
-         * @param uidState           The uidstate of the app
-         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
-         *                          for
+         * @param uidState           The uidstate of the app {@link #startOperation} was called for
          * @param attributionFlags   the attribution flags for this operation.
          * @param attributionChainId the unique id of the attribution chain this op is a part of.
-         * @param proxy              The proxy information, if
-         *                          {@link AppOpCheckingServiceInterface#startProxyOperation} was
-         *                          called
+         * @param proxy              The proxy information, if {@link #startProxyOperation} was
+         *                           called
          * @param flags              The trusted/nontrusted/self flags.
          * @throws RemoteException If the client is dying
          */
@@ -724,21 +718,15 @@
         /**
          * Reinit existing object with new state.
          *
-         * @param startTime          The time {@link AppOpCheckingServiceInterface#startOperation}
-         *                          was called
-         * @param startElapsedTime   The elapsed time when
-         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
-         * @param clientId           The client id of the caller of
-         *                          {@link AppOpCheckingServiceInterface#startOperation}
+         * @param startTime          The time {@link #startOperation} was called
+         * @param startElapsedTime   The elapsed time when {@link #startOperation} was called
+         * @param clientId           The client id of the caller of {@link #startOperation}
          * @param attributionTag     The attribution tag for this operation.
          * @param onDeath            The code to execute on client death
-         * @param uidState           The uidstate of the app
-         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
-         *                          for
+         * @param uidState           The uidstate of the app {@link #startOperation} was called for
          * @param flags              The flags relating to the proxy
-         * @param proxy              The proxy information, if
-         *                          {@link AppOpCheckingServiceInterface#startProxyOperation was
-         *                          called
+         * @param proxy              The proxy information, if {@link #startProxyOperation}
+         *                           was called
          * @param attributionFlags   the attribution flags for this operation.
          * @param attributionChainId the unique id of the attribution chain this op is a part of.
          * @param proxyPool          The pool to release
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6fbd7b6..9b433cf 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -17,6 +17,7 @@
 package com.android.server.audio;
 
 import static android.Manifest.permission.REMOTE_AUDIO_PLAYBACK;
+import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
 import static android.media.AudioManager.RINGER_MODE_NORMAL;
 import static android.media.AudioManager.RINGER_MODE_SILENT;
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
@@ -45,6 +46,7 @@
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.IUidObserver;
 import android.app.NotificationManager;
 import android.app.role.OnRoleHoldersChangedListener;
@@ -178,6 +180,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.EventLogTags;
@@ -4380,7 +4383,7 @@
         }
     }
 
-    private void sendBroadcastToAll(Intent intent) {
+    private void sendBroadcastToAll(Intent intent, Bundle options) {
         if (!mSystemServer.isPrivileged()) {
             return;
         }
@@ -4388,7 +4391,8 @@
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         final long ident = Binder.clearCallingIdentity();
         try {
-            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    null /* receiverPermission */, options);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6539,7 +6543,7 @@
             Intent broadcast = new Intent(AudioManager.VIBRATE_SETTING_CHANGED_ACTION);
             broadcast.putExtra(AudioManager.EXTRA_VIBRATE_TYPE, vibrateType);
             broadcast.putExtra(AudioManager.EXTRA_VIBRATE_SETTING, getVibrateSetting(vibrateType));
-            sendBroadcastToAll(broadcast);
+            sendBroadcastToAll(broadcast, null /* options */);
         }
     }
 
@@ -7578,7 +7582,9 @@
             }
         };
         private final Intent mVolumeChanged;
+        private final Bundle mVolumeChangedOptions;
         private final Intent mStreamDevicesChanged;
+        private final Bundle mStreamDevicesChangedOptions;
 
         private VolumeStreamState(String settingName, int streamType) {
             mVolumeIndexSettingName = settingName;
@@ -7600,8 +7606,21 @@
             readSettings();
             mVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
             mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
+            final BroadcastOptions volumeChangedOptions = BroadcastOptions.makeBasic();
+            // This allows us to discard older broadcasts still waiting to be delivered
+            // which have the same namespace (VOLUME_CHANGED_ACTION) and key (mStreamType).
+            volumeChangedOptions.setDeliveryGroupPolicy(DELIVERY_GROUP_POLICY_MOST_RECENT);
+            volumeChangedOptions.setDeliveryGroupMatchingKey(
+                    AudioManager.VOLUME_CHANGED_ACTION, String.valueOf(mStreamType));
+            mVolumeChangedOptions = volumeChangedOptions.toBundle();
+
             mStreamDevicesChanged = new Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
             mStreamDevicesChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
+            final BroadcastOptions streamDevicesChangedOptions = BroadcastOptions.makeBasic();
+            streamDevicesChangedOptions.setDeliveryGroupPolicy(DELIVERY_GROUP_POLICY_MOST_RECENT);
+            streamDevicesChangedOptions.setDeliveryGroupMatchingKey(
+                    AudioManager.STREAM_DEVICES_CHANGED_ACTION, String.valueOf(mStreamType));
+            mStreamDevicesChangedOptions = streamDevicesChangedOptions.toBundle();
         }
 
         /**
@@ -7650,11 +7669,14 @@
             }
             // send STREAM_DEVICES_CHANGED_ACTION on the message handler so it is scheduled after
             // the postObserveDevicesForStreams is handled
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = mStreamDevicesChanged;
+            args.arg2 = mStreamDevicesChangedOptions;
             sendMsg(mAudioHandler,
                     MSG_STREAM_DEVICES_CHANGED,
                     SENDMSG_QUEUE, prevDevices /*arg1*/, devices /*arg2*/,
                     // ok to send reference to this object, it is final
-                    mStreamDevicesChanged /*obj*/, 0 /*delay*/);
+                    args /*obj*/, 0 /*delay*/);
             return mObservedDeviceSet;
         }
 
@@ -7879,7 +7901,7 @@
                     mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
                     mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
                             mStreamVolumeAlias[mStreamType]);
-                    sendBroadcastToAll(mVolumeChanged);
+                    sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions);
                 }
             }
             return changed;
@@ -8007,7 +8029,7 @@
                 Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
                 intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
                 intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state);
-                sendBroadcastToAll(intent);
+                sendBroadcastToAll(intent, null /* options */);
             }
             return changed;
         }
@@ -8484,9 +8506,14 @@
                     break;
 
                 case MSG_STREAM_DEVICES_CHANGED:
-                    sendBroadcastToAll(((Intent) msg.obj)
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final Intent intent = (Intent) args.arg1;
+                    final Bundle options = (Bundle) args.arg2;
+                    args.recycle();
+                    sendBroadcastToAll(intent
                             .putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, msg.arg1)
-                            .putExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, msg.arg2));
+                            .putExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, msg.arg2),
+                            options);
                     break;
 
                 case MSG_UPDATE_VOLUME_STATES_FOR_DEVICE:
@@ -9976,6 +10003,7 @@
     static final int LOG_NB_EVENTS_VOLUME = 40;
     static final int LOG_NB_EVENTS_DYN_POLICY = 10;
     static final int LOG_NB_EVENTS_SPATIAL = 30;
+    static final int LOG_NB_EVENTS_SOUND_DOSE = 30;
 
     static final EventLogger
             sLifecycleLogger = new EventLogger(LOG_NB_EVENTS_LIFECYCLE,
@@ -11392,6 +11420,11 @@
             public void onStop() {
                 unregisterAudioPolicyAsync(mPolicyCallback);
             }
+
+            @Override
+            public void onCapturedContentResize(int width, int height) {
+                // Ignore resize of the captured content.
+            }
         };
         UnregisterOnStopCallback mProjectionCallback;
 
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index b920517..d30bec7 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -476,4 +476,53 @@
             }
         }
     }
+
+    static final class SoundDoseEvent extends EventLogger.Event {
+        static final int MOMENTARY_EXPOSURE = 0;
+        static final int DOSE_UPDATE = 1;
+        static final int DOSE_REPEAT_5X = 2;
+        static final int DOSE_ACCUMULATION_START = 3;
+        final int mEventType;
+        final float mFloatValue;
+        final long mLongValue;
+
+        private SoundDoseEvent(int event, float f, long l) {
+            mEventType = event;
+            mFloatValue = f;
+            mLongValue = l;
+        }
+
+        static SoundDoseEvent getMomentaryExposureEvent(float mel) {
+            return new SoundDoseEvent(MOMENTARY_EXPOSURE, mel, 0 /*ignored*/);
+        }
+
+        static SoundDoseEvent getDoseUpdateEvent(float csd, long totalDuration) {
+            return new SoundDoseEvent(DOSE_UPDATE, csd, totalDuration);
+        }
+
+        static SoundDoseEvent getDoseRepeat5xEvent() {
+            return new SoundDoseEvent(DOSE_REPEAT_5X, 0 /*ignored*/, 0 /*ignored*/);
+        }
+
+        static SoundDoseEvent getDoseAccumulationStartEvent() {
+            return new SoundDoseEvent(DOSE_ACCUMULATION_START, 0 /*ignored*/, 0 /*ignored*/);
+        }
+
+        @Override
+        public String eventToString() {
+            switch (mEventType) {
+                case MOMENTARY_EXPOSURE:
+                    return String.format("momentary exposure MEL=%.2f", mFloatValue);
+                case DOSE_UPDATE:
+                    return String.format(java.util.Locale.US,
+                            "dose update CSD=%.1f%% total duration=%d",
+                            mFloatValue * 100.0f, mLongValue);
+                case DOSE_REPEAT_5X:
+                    return "CSD reached 500%";
+                case DOSE_ACCUMULATION_START:
+                    return "CSD accumulating: RS2 entered";
+            }
+            return new StringBuilder("FIXME invalid event type:").append(mEventType).toString();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 1d25e31..c176f29 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -192,15 +192,7 @@
             synchronized (mDevicesForAttrCache) {
                 res = mDevicesForAttrCache.get(key);
                 if (res == null) {
-                    // result from AudioSystem guaranteed non-null, but could be invalid
-                    // if there is a failure to talk to APM
                     res = AudioSystem.getDevicesForAttributes(attributes, forVolume);
-                    if (res.size() > 1 && res.get(0) != null
-                            && res.get(0).getInternalType() == AudioSystem.DEVICE_NONE) {
-                        Log.e(TAG, "unable to get devices for " + attributes);
-                        // return now, do not put invalid value in cache
-                        return res;
-                    }
                     mDevicesForAttrCache.put(key, res);
                     if (DEBUG_CACHE) {
                         Log.d(TAG, mMethodNames[METHOD_GETDEVICESFORATTRIBUTES]
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 0d0de8a..5fe9ada 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -43,6 +43,8 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.audio.AudioService.AudioHandler;
 import com.android.server.audio.AudioService.ISafeHearingVolumeController;
+import com.android.server.audio.AudioServiceEvents.SoundDoseEvent;
+import com.android.server.utils.EventLogger;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -94,6 +96,9 @@
 
     private static final float CUSTOM_RS2_VALUE = 90;
 
+    private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
+            "CSD updates");
+
     private int mMcc = 0;
 
     final Object mSafeMediaVolumeStateLock = new Object();
@@ -147,17 +152,21 @@
         public void onMomentaryExposure(float currentMel, int deviceId) {
             Log.w(TAG, "DeviceId " + deviceId + " triggered momentary exposure with value: "
                     + currentMel);
+            mLogger.enqueue(SoundDoseEvent.getMomentaryExposureEvent(currentMel));
         }
 
         public void onNewCsdValue(float currentCsd, SoundDoseRecord[] records) {
             Log.i(TAG, "onNewCsdValue: " + currentCsd);
             mCurrentCsd = currentCsd;
             mDoseRecords.addAll(Arrays.asList(records));
+            long totalDuration = 0;
             for (SoundDoseRecord record : records) {
                 Log.i(TAG, "  new record: csd=" + record.value
                         + " averageMel=" + record.averageMel + " timestamp=" + record.timestamp
                         + " duration=" + record.duration);
+                totalDuration += record.duration;
             }
+            mLogger.enqueue(SoundDoseEvent.getDoseUpdateEvent(currentCsd, totalDuration));
         }
     };
 
@@ -400,6 +409,9 @@
         pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
         pw.print("  mMcc="); pw.println(mMcc);
         pw.print("  mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
+        pw.println();
+        mLogger.dump(pw);
+        pw.println();
     }
 
     /*package*/void reset() {
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
index 6a01042..b66120d 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
@@ -86,7 +86,8 @@
 
     @Override
     public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
-            boolean withAudio, ITunerCallback callback) throws RemoteException {
+            boolean withAudio, ITunerCallback callback, int targetSdkVersion)
+            throws RemoteException {
         if (isDebugEnabled()) {
             Slogf.d(TAG, "Opening module %d", moduleId);
         }
@@ -94,7 +95,7 @@
         if (callback == null) {
             throw new IllegalArgumentException("Callback must not be null");
         }
-        return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback);
+        return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback, targetSdkVersion);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index 408fba1..8a1ba19 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -92,7 +92,8 @@
 
     @Override
     public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
-            boolean withAudio, ITunerCallback callback) throws RemoteException {
+            boolean withAudio, ITunerCallback callback, int targetSdkVersion)
+            throws RemoteException {
         if (isDebugEnabled()) {
             Slog.d(TAG, "Opening module " + moduleId);
         }
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 03acf72..772cd41 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -199,7 +199,8 @@
      */
     @Nullable
     public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
-            boolean withAudio, ITunerCallback callback) throws RemoteException {
+            boolean withAudio, ITunerCallback callback, int targetSdkVersion)
+            throws RemoteException {
         if (DEBUG) {
             Slogf.d(TAG, "Open AIDL radio session");
         }
@@ -222,7 +223,7 @@
             }
         }
 
-        TunerSession tunerSession = radioModule.openSession(callback);
+        TunerSession tunerSession = radioModule.openSession(callback, targetSdkVersion);
         if (legacyConfig != null) {
             tunerSession.setConfiguration(legacyConfig);
         }
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index d90f9c4..3c0fda8 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -33,6 +33,8 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.RadioTuner;
+import android.os.Build;
 import android.os.ParcelableException;
 import android.os.ServiceSpecificException;
 import android.util.ArrayMap;
@@ -62,6 +64,11 @@
         throw new UnsupportedOperationException("ConversionUtils class is noninstantiable");
     }
 
+    static boolean isAtLeastU(int targetSdkVersion) {
+        // TODO(b/261770108): Use version code for U.
+        return targetSdkVersion >= Build.VERSION_CODES.CUR_DEVELOPMENT;
+    }
+
     static RuntimeException throwOnError(RuntimeException halException, String action) {
         if (!(halException instanceof ServiceSpecificException)) {
             return new ParcelableException(new RuntimeException(
@@ -89,6 +96,27 @@
         }
     }
 
+    @RadioTuner.TunerResultType
+    static int halResultToTunerResult(int result) {
+        switch (result) {
+            case Result.OK:
+                return RadioTuner.TUNER_RESULT_OK;
+            case Result.INTERNAL_ERROR:
+                return RadioTuner.TUNER_RESULT_INTERNAL_ERROR;
+            case Result.INVALID_ARGUMENTS:
+                return RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS;
+            case Result.INVALID_STATE:
+                return RadioTuner.TUNER_RESULT_INVALID_STATE;
+            case Result.NOT_SUPPORTED:
+                return RadioTuner.TUNER_RESULT_NOT_SUPPORTED;
+            case Result.TIMEOUT:
+                return RadioTuner.TUNER_RESULT_TIMEOUT;
+            case Result.UNKNOWN_ERROR:
+            default:
+                return RadioTuner.TUNER_RESULT_UNKNOWN_ERROR;
+        }
+    }
+
     static VendorKeyValue[] vendorInfoToHalVendorKeyValues(@Nullable Map<String, String> info) {
         if (info == null) {
             return new VendorKeyValue[]{};
@@ -143,6 +171,7 @@
             case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
             case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
             case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT:
                 return ProgramSelector.PROGRAM_TYPE_DAB;
             case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
             case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
@@ -471,6 +500,60 @@
         return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
     }
 
+    private static boolean isNewIdentifierInU(ProgramSelector.Identifier id) {
+        return id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
+    }
+
+    static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel,
+            int targetSdkVersion) {
+        if (isAtLeastU(targetSdkVersion)) {
+            return true;
+        }
+        if (sel.getPrimaryId().getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
+            return false;
+        }
+        ProgramSelector.Identifier[] secondaryIds = sel.getSecondaryIds();
+        for (int i = 0; i < secondaryIds.length; i++) {
+            if (isNewIdentifierInU(secondaryIds[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info,
+            int targetSdkVersion) {
+        if (isAtLeastU(targetSdkVersion)) {
+            return true;
+        }
+        if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), targetSdkVersion)) {
+            return false;
+        }
+        if (isNewIdentifierInU(info.getLogicallyTunedTo())
+                || isNewIdentifierInU(info.getPhysicallyTunedTo())) {
+            return false;
+        }
+        Iterator<ProgramSelector.Identifier> relatedContentIt = info.getRelatedContent().iterator();
+        while (relatedContentIt.hasNext()) {
+            if (isNewIdentifierInU(relatedContentIt.next())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk,
+            int targetSdkVersion) {
+        if (isAtLeastU(targetSdkVersion)) {
+            return chunk;
+        }
+        Set<RadioManager.ProgramInfo> modified = chunk.getModified();
+        modified.removeIf(info -> !programInfoMeetsSdkVersionRequirement(info, targetSdkVersion));
+        Set<ProgramSelector.Identifier> removed = chunk.getRemoved();
+        removed.removeIf(id -> isNewIdentifierInU(id));
+        return new ProgramList.Chunk(chunk.isPurge(), chunk.isComplete(), modified, removed);
+    }
+
     public static android.hardware.radio.Announcement announcementFromHalAnnouncement(
             Announcement hwAnnouncement) {
         return new android.hardware.radio.Announcement(
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index e956a9c..6193f23 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -100,7 +100,16 @@
                 synchronized (mLock) {
                     android.hardware.radio.ProgramSelector csel =
                             ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
-                    fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
+                    int tunerResult = ConversionUtils.halResultToTunerResult(result);
+                    fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+                        if (csel != null && !ConversionUtils
+                                .programSelectorMeetsSdkVersionRequirement(csel, sdkVersion)) {
+                            Slogf.e(TAG, "onTuneFailed: cannot send program selector "
+                                    + "requiring higher target SDK version");
+                            return;
+                        }
+                        cb.onTuneFailed(tunerResult, csel);
+                    });
                 }
             });
         }
@@ -112,7 +121,13 @@
                     mCurrentProgramInfo =
                             ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
                     RadioManager.ProgramInfo currentProgramInfo = mCurrentProgramInfo;
-                    fanoutAidlCallbackLocked(cb -> {
+                    fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+                        if (!ConversionUtils.programInfoMeetsSdkVersionRequirement(
+                                currentProgramInfo, sdkVersion)) {
+                            Slogf.e(TAG, "onCurrentProgramInfoChanged: cannot send "
+                                    + "program info requiring higher target SDK version");
+                            return;
+                        }
                         cb.onCurrentProgramInfoChanged(currentProgramInfo);
                     });
                 }
@@ -139,7 +154,7 @@
             fireLater(() -> {
                 synchronized (mLock) {
                     mAntennaConnected = connected;
-                    fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
+                    fanoutAidlCallbackLocked((cb, sdkVersion) -> cb.onAntennaState(connected));
                 }
             });
         }
@@ -147,8 +162,11 @@
         @Override
         public void onConfigFlagUpdated(int flag, boolean value) {
             fireLater(() -> {
-                // TODO(b/243853343): implement config flag update method in
-                //  android.hardware.radio.ITunerCallback
+                synchronized (mLock) {
+                    fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+                        cb.onConfigFlagUpdated(flag, value);
+                    });
+                }
             });
         }
 
@@ -158,7 +176,9 @@
                 synchronized (mLock) {
                     Map<String, String> cparam =
                             ConversionUtils.vendorInfoFromHalVendorKeyValues(parameters);
-                    fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam));
+                    fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+                        cb.onParametersUpdated(cparam);
+                    });
                 }
             });
         }
@@ -222,14 +242,14 @@
         mService.setTunerCallback(mHalTunerCallback);
     }
 
-    TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
+    TunerSession openSession(android.hardware.radio.ITunerCallback userCb, int targetSdkVersion)
             throws RemoteException {
         mLogger.logRadioEvent("Open TunerSession");
         TunerSession tunerSession;
         Boolean antennaConnected;
         RadioManager.ProgramInfo currentProgramInfo;
         synchronized (mLock) {
-            tunerSession = new TunerSession(this, mService, userCb);
+            tunerSession = new TunerSession(this, mService, userCb, targetSdkVersion);
             mAidlTunerSessions.add(tunerSession);
             antennaConnected = mAntennaConnected;
             currentProgramInfo = mCurrentProgramInfo;
@@ -382,7 +402,8 @@
     }
 
     interface AidlCallbackRunnable {
-        void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
+        void run(android.hardware.radio.ITunerCallback callback, int targetSdkVersion)
+                throws RemoteException;
     }
 
     // Invokes runnable with each TunerSession currently open.
@@ -399,7 +420,8 @@
         List<TunerSession> deadSessions = null;
         for (int i = 0; i < mAidlTunerSessions.size(); i++) {
             try {
-                runnable.run(mAidlTunerSessions.valueAt(i).mCallback);
+                runnable.run(mAidlTunerSessions.valueAt(i).mCallback,
+                        mAidlTunerSessions.valueAt(i).getTargetSdkVersion());
             } catch (DeadObjectException ex) {
                 // The other side died without calling close(), so just purge it from our records.
                 Slogf.e(TAG, "Removing dead TunerSession");
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
index 1ce4044..d700ed0 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -21,7 +21,6 @@
 import android.hardware.broadcastradio.ConfigFlag;
 import android.hardware.broadcastradio.IBroadcastRadio;
 import android.hardware.radio.ITuner;
-import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.ProgramList;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
@@ -47,6 +46,7 @@
     private final RadioLogger mLogger;
     private final RadioModule mModule;
     final android.hardware.radio.ITunerCallback mCallback;
+    private final int mTargetSdkVersion;
     private final IBroadcastRadio mService;
 
     @GuardedBy("mLock")
@@ -61,10 +61,11 @@
     private RadioManager.BandConfig mPlaceHolderConfig;
 
     TunerSession(RadioModule radioModule, IBroadcastRadio service,
-            android.hardware.radio.ITunerCallback callback) {
+            android.hardware.radio.ITunerCallback callback, int targetSdkVersion) {
         mModule = Objects.requireNonNull(radioModule, "radioModule cannot be null");
         mService = Objects.requireNonNull(service, "service cannot be null");
         mCallback = Objects.requireNonNull(callback, "callback cannot be null");
+        mTargetSdkVersion = targetSdkVersion;
         mLogger = new RadioLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
     }
 
@@ -129,7 +130,7 @@
             mPlaceHolderConfig = Objects.requireNonNull(config, "config cannot be null");
         }
         Slogf.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL AIDL");
-        mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
+        mModule.fanoutAidlCallback((cb, sdkVersion) -> cb.onConfigurationChanged(config));
     }
 
     @Override
@@ -178,8 +179,8 @@
     }
 
     @Override
-    public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
-        mLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
+    public void seek(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+        mLogger.logRadioEvent("Seek with direction %s, skipSubChannel? %s",
                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
         if (!RadioServiceUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot scan on AIDL HAL client from non-current user");
@@ -232,8 +233,7 @@
 
     @Override
     public void cancelAnnouncement() {
-        // TODO(b/244485175): deperacte cancelAnnouncement
-        Slogf.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in AIDL");
+        Slogf.w(TAG, "Announcements control doesn't involve cancelling at the HAL level in AIDL");
     }
 
     @Override
@@ -244,12 +244,14 @@
 
     @Override
     public boolean startBackgroundScan() {
-        Slogf.i(TAG, "Explicit background scan trigger is not supported with HAL AIDL");
+        Slogf.w(TAG, "Explicit background scan trigger is not supported with HAL AIDL");
         if (!RadioServiceUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot start background scan on AIDL HAL client from non-current user");
             return false;
         }
-        mModule.fanoutAidlCallback(ITunerCallback::onBackgroundScanComplete);
+        mModule.fanoutAidlCallback((cb, sdkVersion) -> {
+            cb.onBackgroundScanComplete();
+        });
         return true;
     }
 
@@ -277,6 +279,10 @@
         mModule.onTunerSessionProgramListFilterChanged(this);
     }
 
+    int getTargetSdkVersion() {
+        return mTargetSdkVersion;
+    }
+
     ProgramList.Filter getProgramListFilter() {
         synchronized (mLock) {
             return mProgramInfoCache == null ? null : mProgramInfoCache.getFilter();
@@ -312,7 +318,14 @@
         }
         for (int i = 0; i < chunks.size(); i++) {
             try {
-                mCallback.onProgramListUpdated(chunks.get(i));
+                if (!ConversionUtils.isAtLeastU(getTargetSdkVersion())) {
+                    ProgramList.Chunk downgradedChunk =
+                            ConversionUtils.convertChunkToTargetSdkVersion(chunks.get(i),
+                                    getTargetSdkVersion());
+                    mCallback.onProgramListUpdated(downgradedChunk);
+                } else {
+                    mCallback.onProgramListUpdated(chunks.get(i));
+                }
             } catch (RemoteException ex) {
                 Slogf.w(TAG, ex, "mCallback.onProgramListUpdated() failed");
             }
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
index ed8a37a..8e5f6b5 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
@@ -189,9 +189,9 @@
     }
 
     @Override
-    public void scan(boolean directionDown, boolean skipSubChannel) {
+    public void seek(boolean directionDown, boolean skipSubChannel) {
         if (!RadioServiceUserController.isCurrentOrSystemUser()) {
-            Slogf.w(TAG, "Cannot scan on HAL 1.x client from non-current user");
+            Slogf.w(TAG, "Cannot seek on HAL 1.x client from non-current user");
             return;
         }
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
index 0cc3833..aa43b75 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
@@ -174,8 +174,13 @@
     }
 
     @Override
+    public void onConfigFlagUpdated(int flag, boolean value) {
+        Slog.w(TAG, "Not applicable for HAL 1.x");
+    }
+
+    @Override
     public void onParametersUpdated(Map<String, String> parameters) {
-        Slog.e(TAG, "Not applicable for HAL 1.x");
+        Slog.w(TAG, "Not applicable for HAL 1.x");
     }
 
     @Override
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 984bf51..1e31f20 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -169,7 +169,7 @@
     }
 
     public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
-        boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
+            boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
         Slog.v(TAG, "Open HIDL 2.0 session with module id " + moduleId);
         if (!RadioServiceUserController.isCurrentOrSystemUser()) {
             Slogf.e(TAG, "Cannot open tuner on HAL 2.0 client for non-current user");
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index 3daf1db..98a450f 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -36,6 +36,7 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.RadioTuner;
 import android.os.ParcelableException;
 import android.util.Slog;
 
@@ -81,6 +82,27 @@
         }
     }
 
+    @RadioTuner.TunerResultType
+    static int halResultToTunerResult(int result) {
+        switch (result) {
+            case Result.OK:
+                return RadioTuner.TUNER_RESULT_OK;
+            case Result.INTERNAL_ERROR:
+                return RadioTuner.TUNER_RESULT_INTERNAL_ERROR;
+            case Result.INVALID_ARGUMENTS:
+                return RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS;
+            case Result.INVALID_STATE:
+                return RadioTuner.TUNER_RESULT_INVALID_STATE;
+            case Result.NOT_SUPPORTED:
+                return RadioTuner.TUNER_RESULT_NOT_SUPPORTED;
+            case Result.TIMEOUT:
+                return RadioTuner.TUNER_RESULT_TIMEOUT;
+            case Result.UNKNOWN_ERROR:
+            default:
+                return RadioTuner.TUNER_RESULT_UNKNOWN_ERROR;
+        }
+    }
+
     static @NonNull ArrayList<VendorKeyValue>
     vendorInfoToHal(@Nullable Map<String, String> info) {
         if (info == null) return new ArrayList<>();
@@ -130,6 +152,7 @@
             case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
             case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
             case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT:
                 return ProgramSelector.PROGRAM_TYPE_DAB;
             case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
             case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 0ea5f0f..59a8154 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -87,8 +87,9 @@
             fireLater(() -> {
                 android.hardware.radio.ProgramSelector csel =
                         Convert.programSelectorFromHal(programSelector);
+                int tunerResult = Convert.halResultToTunerResult(result);
                 synchronized (mLock) {
-                    fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
+                    fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(tunerResult, csel));
                 }
             });
         }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 7afee27..204b964 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -171,8 +171,8 @@
     }
 
     @Override
-    public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
-        mEventLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
+    public void seek(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+        mEventLogger.logRadioEvent("Seek with direction %s, skipSubChannel? %s",
                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
         if (!RadioServiceUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot scan on HAL 2.0 client from non-current user");
@@ -214,7 +214,7 @@
 
     @Override
     public void cancelAnnouncement() {
-        Slog.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
+        Slog.w(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
     }
 
     @Override
@@ -225,7 +225,7 @@
 
     @Override
     public boolean startBackgroundScan() {
-        Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
+        Slog.w(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
         if (!RadioServiceUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG,
                     "Cannot start background scan on HAL 2.0 client from non-current user");
diff --git a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
index ab3b250..dce1c96 100644
--- a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
+++ b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
@@ -29,6 +29,7 @@
 import java.io.EOFException;
 import java.io.FileDescriptor;
 import java.io.InterruptedIOException;
+import java.net.ProtocolException;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -39,12 +40,16 @@
 // write contents of the host system's clipboard.
 class EmulatorClipboardMonitor implements Consumer<ClipData> {
     private static final String TAG = "EmulatorClipboardMonitor";
+
     private static final String PIPE_NAME = "pipe:clipboard";
     private static final int HOST_PORT = 5000;
-    private final Thread mHostMonitorThread;
+
     private static final boolean LOG_CLIBOARD_ACCESS =
             SystemProperties.getBoolean("ro.boot.qemu.log_clipboard_access", false);
+    private static final int MAX_CLIPBOARD_BYTES = 128 << 20;
+
     private FileDescriptor mPipe = null;
+    private final Thread mHostMonitorThread;
 
     private static byte[] createOpenHandshake() {
         // String.getBytes doesn't include the null terminator,
@@ -97,8 +102,8 @@
         return fd;
     }
 
-    private static byte[] receiveMessage(final FileDescriptor fd) throws ErrnoException,
-            InterruptedIOException, EOFException {
+    private byte[] receiveMessage(final FileDescriptor fd) throws ErrnoException,
+            InterruptedIOException, EOFException, ProtocolException {
         final byte[] lengthBits = new byte[4];
         readFully(fd, lengthBits, 0, lengthBits.length);
 
@@ -106,6 +111,10 @@
         bb.order(ByteOrder.LITTLE_ENDIAN);
         final int msgLen = bb.getInt();
 
+        if (msgLen <= 0 || msgLen > MAX_CLIPBOARD_BYTES) {
+            throw new ProtocolException("Clipboard message length: " + msgLen + " out of bounds.");
+        }
+
         final byte[] msg = new byte[msgLen];
         readFully(fd, msg, 0, msg.length);
 
@@ -150,7 +159,8 @@
                     }
                     setAndroidClipboard.accept(clip);
                 } catch (ErrnoException | EOFException | InterruptedIOException
-                         | InterruptedException e) {
+                         | InterruptedException | ProtocolException | OutOfMemoryError e) {
+                    Slog.w(TAG, "Failure to read from host clipboard", e);
                     setPipeFD(null);
 
                     try {
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 218be9d..09bec5e 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -36,7 +36,6 @@
         void onVirtualDisplayRemoved(int displayId);
     }
 
-
     /** Interface to listen to the changes on the list of app UIDs running on any virtual device. */
     public interface AppsOnVirtualDeviceListener {
         /** Notifies that running apps on any virtual device has changed */
@@ -72,6 +71,25 @@
     public abstract boolean isValidVirtualDevice(IVirtualDevice virtualDevice);
 
     /**
+     * Gets the owner uid for a deviceId.
+     *
+     * @param deviceId which device we're asking about
+     * @return the uid of the app which created and owns the VirtualDevice with the given deviceId,
+     * or {@link android.os.Process#INVALID_UID} if no such device exists.
+     */
+    public abstract int getDeviceOwnerUid(int deviceId);
+
+    /**
+     * Finds VirtualDevices where an app is running.
+     *
+     * @param uid - the app's uid
+     * @return a set of id's of VirtualDevices where the app with the given uid is running.
+     * *Note* this only checks VirtualDevices, and does not include information about whether
+     * the app is running on the default device or not.
+     */
+    public abstract @NonNull Set<Integer> getDeviceIdsForUid(int uid);
+
+    /**
      * Notifies that a virtual display is created.
      *
      * @param displayId The display id of the created virtual display.
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 4fcde97..19dbee7 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -646,7 +646,10 @@
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                 .setTransportInfo(new VpnTransportInfo(
-                        VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
+                        VpnManager.TYPE_VPN_NONE,
+                        null /* sessionId */,
+                        false /* bypassable */,
+                        false /* longLivedTcpConnectionsExpensive */))
                 .build();
 
         loadAlwaysOnPackage();
@@ -711,7 +714,10 @@
         mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
                 .setUids(null)
                 .setTransportInfo(new VpnTransportInfo(
-                        VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
+                        VpnManager.TYPE_VPN_NONE,
+                        null /* sessionId */,
+                        false /* bypassable */,
+                        false /* longLivedTcpConnectionsExpensive */))
                 .build();
     }
 
@@ -1570,7 +1576,8 @@
                 mConfig.allowedApplications, mConfig.disallowedApplications));
 
         capsBuilder.setTransportInfo(
-                new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass));
+                new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass,
+                        false /* longLivedTcpConnectionsExpensive */));
 
         // Only apps targeting Q and above can explicitly declare themselves as metered.
         // These VPNs are assumed metered unless they state otherwise.
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index c1e9526..98a0af7 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -36,16 +36,19 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.config.AutoBrightness;
+import com.android.server.display.config.BlockingZoneConfig;
 import com.android.server.display.config.BrightnessThresholds;
 import com.android.server.display.config.BrightnessThrottlingMap;
 import com.android.server.display.config.BrightnessThrottlingPoint;
 import com.android.server.display.config.Density;
+import com.android.server.display.config.DisplayBrightnessPoint;
 import com.android.server.display.config.DisplayConfiguration;
 import com.android.server.display.config.DisplayQuirks;
 import com.android.server.display.config.HbmTiming;
 import com.android.server.display.config.HighBrightnessMode;
 import com.android.server.display.config.NitsMap;
 import com.android.server.display.config.Point;
+import com.android.server.display.config.RefreshRateConfigs;
 import com.android.server.display.config.RefreshRateRange;
 import com.android.server.display.config.SdrHdrRatioMap;
 import com.android.server.display.config.SdrHdrRatioPoint;
@@ -130,6 +133,35 @@
  *        </brightnessThrottlingMap>
  *      </thermalThrottling>
  *
+ *      <refreshRate>
+ *        <lowerBlockingZoneConfigs>
+ *          <defaultRefreshRate>75</defaultRefreshRate>
+ *          <blockingZoneThreshold>
+ *            <displayBrightnessPoint>
+ *              <lux>50</lux>
+ *              <nits>45.3</nits>
+ *            </displayBrightnessPoint>
+ *            <displayBrightnessPoint>
+ *              <lux>60</lux>
+ *              <nits>55.2</nits>
+ *            </displayBrightnessPoint>
+ *          </blockingZoneThreshold>
+ *        </lowerBlockingZoneConfigs>
+ *        <higherBlockingZoneConfigs>
+ *          <defaultRefreshRate>90</defaultRefreshRate>
+ *          <blockingZoneThreshold>
+ *            <displayBrightnessPoint>
+ *              <lux>500</lux>
+ *              <nits>245.3</nits>
+ *            </displayBrightnessPoint>
+ *            <displayBrightnessPoint>
+ *              <lux>600</lux>
+ *              <nits>232.3</nits>
+ *            </displayBrightnessPoint>
+ *          </blockingZoneThreshold>
+ *        </higherBlockingZoneConfigs>
+ *      </refreshRate>
+ *
  *      <highBrightnessMode enabled="true">
  *        <transitionPoint>0.62</transitionPoint>
  *        <minimumLux>10000</minimumLux>
@@ -358,6 +390,9 @@
     private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d";
     private static final String NO_SUFFIX_FORMAT = "%d";
     private static final long STABLE_FLAG = 1L << 62;
+    private static final int DEFAULT_LOW_REFRESH_RATE = 60;
+    private static final int DEFAULT_HIGH_REFRESH_RATE = 0;
+    private static final int[] DEFAULT_BRIGHTNESS_THRESHOLDS = new int[]{};
 
     private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
     private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
@@ -512,6 +547,49 @@
     // This stores the raw value loaded from the config file - true if not written.
     private boolean mDdcAutoBrightnessAvailable = true;
 
+    /**
+     * The default peak refresh rate for a given device. This value prevents the framework from
+     * using higher refresh rates, even if display modes with higher refresh rates are available
+     * from hardware composer. Only has an effect if the value is non-zero.
+     */
+    private int mDefaultHighRefreshRate = DEFAULT_HIGH_REFRESH_RATE;
+
+    /**
+     * The default refresh rate for a given device. This value sets the higher default
+     * refresh rate. If the hardware composer on the device supports display modes with
+     * a higher refresh rate than the default value specified here, the framework may use those
+     * higher refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
+     * setFrameRate(). We have historically allowed fallback to mDefaultHighRefreshRate if
+     * mDefaultLowRefreshRate is set to 0, but this is not supported anymore.
+     */
+    private int mDefaultLowRefreshRate = DEFAULT_LOW_REFRESH_RATE;
+
+    /**
+     * The display uses different gamma curves for different refresh rates. It's hard for panel
+     * vendors to tune the curves to have exact same brightness for different refresh rate. So
+     * brightness flickers could be observed at switch time. The issue is worse at the gamma lower
+     * end. In addition, human eyes are more sensitive to the flicker at darker environment. To
+     * prevent flicker, we only support higher refresh rates if the display brightness is above a
+     * threshold. For example, no higher refresh rate if display brightness <= disp0 && ambient
+     * brightness <= amb0 || display brightness <= disp1 && ambient brightness <= amb1
+     */
+    private int[] mLowDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+    private int[] mLowAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+
+    /**
+     * The display uses different gamma curves for different refresh rates. It's hard for panel
+     * vendors to tune the curves to have exact same brightness for different refresh rate. So
+     * brightness flickers could be observed at switch time. The issue can be observed on the screen
+     * with even full white content at the high brightness. To prevent flickering, we support fixed
+     * refresh rates if the display and ambient brightness are equal to or above the provided
+     * thresholds. You can define multiple threshold levels as higher brightness environments may
+     * have lower display brightness requirements for the flickering is visible. For example, fixed
+     * refresh rate if display brightness >= disp0 && ambient brightness >= amb0 || display
+     * brightness >= disp1 && ambient brightness >= amb1
+     */
+    private int[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+    private int[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+
     // Brightness Throttling data may be updated via the DeviceConfig. Here we store the original
     // data, which comes from the ddc, and the current one, which may be the DeviceConfig
     // overwritten value.
@@ -1195,15 +1273,15 @@
     /**
      * @return Default peak refresh rate of the associated display
      */
-    public int getDefaultPeakRefreshRate() {
-        return mContext.getResources().getInteger(R.integer.config_defaultPeakRefreshRate);
+    public int getDefaultHighRefreshRate() {
+        return mDefaultHighRefreshRate;
     }
 
     /**
      * @return Default refresh rate of the associated display
      */
-    public int getDefaultRefreshRate() {
-        return mContext.getResources().getInteger(R.integer.config_defaultRefreshRate);
+    public int getDefaultLowRefreshRate() {
+        return mDefaultLowRefreshRate;
     }
 
     /**
@@ -1212,8 +1290,7 @@
      * allowed
      */
     public int[] getLowDisplayBrightnessThresholds() {
-        return mContext.getResources().getIntArray(
-                R.array.config_brightnessThresholdsOfPeakRefreshRate);
+        return mLowDisplayBrightnessThresholds;
     }
 
     /**
@@ -1222,8 +1299,7 @@
      * allowed
      */
     public int[] getLowAmbientBrightnessThresholds() {
-        return mContext.getResources().getIntArray(
-                R.array.config_ambientThresholdsOfPeakRefreshRate);
+        return mLowAmbientBrightnessThresholds;
     }
 
     /**
@@ -1232,8 +1308,7 @@
      * allowed
      */
     public int[] getHighDisplayBrightnessThresholds() {
-        return mContext.getResources().getIntArray(
-                R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
+        return mHighDisplayBrightnessThresholds;
     }
 
     /**
@@ -1242,8 +1317,7 @@
      * allowed
      */
     public int[] getHighAmbientBrightnessThresholds() {
-        return mContext.getResources().getIntArray(
-                R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+        return mHighAmbientBrightnessThresholds;
     }
 
     @Override
@@ -1335,6 +1409,17 @@
                 + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
                 + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
                 + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+                + "\n"
+                + ", mDefaultRefreshRate= " + mDefaultLowRefreshRate
+                + ", mDefaultPeakRefreshRate= " + mDefaultHighRefreshRate
+                + ", mLowDisplayBrightnessThresholds= "
+                + Arrays.toString(mLowDisplayBrightnessThresholds)
+                + ", mLowAmbientBrightnessThresholds= "
+                + Arrays.toString(mLowAmbientBrightnessThresholds)
+                + ", mHighDisplayBrightnessThresholds= "
+                + Arrays.toString(mHighDisplayBrightnessThresholds)
+                + ", mHighAmbientBrightnessThresholds= "
+                + Arrays.toString(mHighAmbientBrightnessThresholds)
                 + "}";
     }
 
@@ -1392,6 +1477,7 @@
                 loadAmbientHorizonFromDdc(config);
                 loadBrightnessChangeThresholds(config);
                 loadAutoBrightnessConfigValues(config);
+                loadRefreshRateSetting(config);
             } else {
                 Slog.w(TAG, "DisplayDeviceConfig file is null");
             }
@@ -1414,6 +1500,7 @@
         setProxSensorUnspecified();
         loadAutoBrightnessConfigsFromConfigXml();
         loadAutoBrightnessAvailableFromConfigXml();
+        loadRefreshRateSetting(null);
         mLoadedFrom = "<config.xml>";
     }
 
@@ -1624,6 +1711,143 @@
         }
     }
 
+    private void loadRefreshRateSetting(DisplayConfiguration config) {
+        final RefreshRateConfigs refreshRateConfigs =
+                (config == null) ? null : config.getRefreshRate();
+        BlockingZoneConfig lowerBlockingZoneConfig =
+                (refreshRateConfigs == null) ? null
+                        : refreshRateConfigs.getLowerBlockingZoneConfigs();
+        BlockingZoneConfig higherBlockingZoneConfig =
+                (refreshRateConfigs == null) ? null
+                        : refreshRateConfigs.getHigherBlockingZoneConfigs();
+        loadLowerRefreshRateBlockingZones(lowerBlockingZoneConfig);
+        loadHigherRefreshRateBlockingZones(higherBlockingZoneConfig);
+    }
+
+
+    /**
+     * Loads the refresh rate configurations pertaining to the upper blocking zones.
+     */
+    private void loadLowerRefreshRateBlockingZones(BlockingZoneConfig lowerBlockingZoneConfig) {
+        loadLowerBlockingZoneDefaultRefreshRate(lowerBlockingZoneConfig);
+        loadLowerBrightnessThresholds(lowerBlockingZoneConfig);
+    }
+
+    /**
+     * Loads the refresh rate configurations pertaining to the upper blocking zones.
+     */
+    private void loadHigherRefreshRateBlockingZones(BlockingZoneConfig upperBlockingZoneConfig) {
+        loadHigherBlockingZoneDefaultRefreshRate(upperBlockingZoneConfig);
+        loadHigherBrightnessThresholds(upperBlockingZoneConfig);
+    }
+
+    /**
+     * Loads the default peak refresh rate. Internally, this takes care of loading
+     * the value from the display config, and if not present, falls back to config.xml.
+     */
+    private void loadHigherBlockingZoneDefaultRefreshRate(
+                BlockingZoneConfig upperBlockingZoneConfig) {
+        if (upperBlockingZoneConfig == null) {
+            mDefaultHighRefreshRate = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_defaultPeakRefreshRate);
+        } else {
+            mDefaultHighRefreshRate =
+                upperBlockingZoneConfig.getDefaultRefreshRate().intValue();
+        }
+    }
+
+    /**
+     * Loads the default refresh rate. Internally, this takes care of loading
+     * the value from the display config, and if not present, falls back to config.xml.
+     */
+    private void loadLowerBlockingZoneDefaultRefreshRate(
+                BlockingZoneConfig lowerBlockingZoneConfig) {
+        if (lowerBlockingZoneConfig == null) {
+            mDefaultLowRefreshRate = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_defaultRefreshRate);
+        } else {
+            mDefaultLowRefreshRate =
+                lowerBlockingZoneConfig.getDefaultRefreshRate().intValue();
+        }
+    }
+
+    /**
+     * Loads the lower brightness thresholds for refresh rate switching. Internally, this takes care
+     * of loading the value from the display config, and if not present, falls back to config.xml.
+     */
+    private void loadLowerBrightnessThresholds(BlockingZoneConfig lowerBlockingZoneConfig) {
+        if (lowerBlockingZoneConfig == null) {
+            mLowDisplayBrightnessThresholds = mContext.getResources().getIntArray(
+                R.array.config_brightnessThresholdsOfPeakRefreshRate);
+            mLowAmbientBrightnessThresholds = mContext.getResources().getIntArray(
+                R.array.config_ambientThresholdsOfPeakRefreshRate);
+            if (mLowDisplayBrightnessThresholds == null || mLowAmbientBrightnessThresholds == null
+                    || mLowDisplayBrightnessThresholds.length
+                    != mLowAmbientBrightnessThresholds.length) {
+                throw new RuntimeException("display low brightness threshold array and ambient "
+                    + "brightness threshold array have different length: "
+                    + "mLowDisplayBrightnessThresholds="
+                    + Arrays.toString(mLowDisplayBrightnessThresholds)
+                    + ", mLowAmbientBrightnessThresholds="
+                    + Arrays.toString(mLowAmbientBrightnessThresholds));
+            }
+        } else {
+            List<DisplayBrightnessPoint> lowerThresholdDisplayBrightnessPoints =
+                    lowerBlockingZoneConfig.getBlockingZoneThreshold().getDisplayBrightnessPoint();
+            int size = lowerThresholdDisplayBrightnessPoints.size();
+            mLowDisplayBrightnessThresholds = new int[size];
+            mLowAmbientBrightnessThresholds = new int[size];
+            for (int i = 0; i < size; i++) {
+                // We are explicitly casting this value to an integer to be able to reuse the
+                // existing DisplayBrightnessPoint type. It is fine to do this because the round off
+                // will have the negligible and unnoticeable impact on the loaded thresholds.
+                mLowDisplayBrightnessThresholds[i] = (int) lowerThresholdDisplayBrightnessPoints
+                    .get(i).getNits().floatValue();
+                mLowAmbientBrightnessThresholds[i] = lowerThresholdDisplayBrightnessPoints
+                    .get(i).getLux().intValue();
+            }
+        }
+    }
+
+    /**
+     * Loads the higher brightness thresholds for refresh rate switching. Internally, this takes
+     * care of loading the value from the display config, and if not present, falls back to
+     * config.xml.
+     */
+    private void loadHigherBrightnessThresholds(BlockingZoneConfig blockingZoneConfig) {
+        if (blockingZoneConfig == null) {
+            mHighDisplayBrightnessThresholds = mContext.getResources().getIntArray(
+                R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
+            mHighAmbientBrightnessThresholds = mContext.getResources().getIntArray(
+                R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+            if (mHighAmbientBrightnessThresholds == null || mHighDisplayBrightnessThresholds == null
+                    || mHighAmbientBrightnessThresholds.length
+                    != mHighDisplayBrightnessThresholds.length) {
+                throw new RuntimeException("display high brightness threshold array and ambient "
+                    + "brightness threshold array have different length: "
+                    + "mHighDisplayBrightnessThresholds="
+                    + Arrays.toString(mHighDisplayBrightnessThresholds)
+                    + ", mHighAmbientBrightnessThresholds="
+                    + Arrays.toString(mHighAmbientBrightnessThresholds));
+            }
+        } else {
+            List<DisplayBrightnessPoint> higherThresholdDisplayBrightnessPoints =
+                    blockingZoneConfig.getBlockingZoneThreshold().getDisplayBrightnessPoint();
+            int size = higherThresholdDisplayBrightnessPoints.size();
+            mHighDisplayBrightnessThresholds = new int[size];
+            mHighAmbientBrightnessThresholds = new int[size];
+            for (int i = 0; i < size; i++) {
+                // We are explicitly casting this value to an integer to be able to reuse the
+                // existing DisplayBrightnessPoint type. It is fine to do this because the round off
+                // will have the negligible and unnoticeable impact on the loaded thresholds.
+                mHighDisplayBrightnessThresholds[i] = (int) higherThresholdDisplayBrightnessPoints
+                    .get(i).getNits().floatValue();
+                mHighAmbientBrightnessThresholds[i] = higherThresholdDisplayBrightnessPoints
+                    .get(i).getLux().intValue();
+            }
+        }
+    }
+
     private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
         final AutoBrightness autoBrightness = config.getAutoBrightness();
         loadAutoBrightnessBrighteningLightDebounce(autoBrightness);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ae84e96..329e3ca 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1414,12 +1414,20 @@
         // LogicalDisplayMapper aware of the link between the new display and its associated virtual
         // device before triggering DISPLAY_DEVICE_EVENT_ADDED.
         if ((flags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) != 0) {
-            try {
-                final int virtualDeviceId = virtualDevice.getDeviceId();
-                mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(
-                        device, virtualDeviceId);
-            } catch (RemoteException e) {
-                e.rethrowFromSystemServer();
+            if (virtualDevice != null) {
+                try {
+                    final int virtualDeviceId = virtualDevice.getDeviceId();
+                    mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(
+                            device, virtualDeviceId);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            } else {
+                Slog.i(
+                        TAG,
+                        "Display created with VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP set, but no"
+                            + " virtual device. The display will not be added to a device display"
+                            + " group.");
             }
         }
 
@@ -1844,15 +1852,7 @@
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth);
         mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
-            // If there is a display specific mode, don't override that
-            final Point deviceUserPreferredResolution =
-                    mPersistentDataStore.getUserPreferredResolution(device);
-            final float deviceRefreshRate =
-                    mPersistentDataStore.getUserPreferredRefreshRate(device);
-            if (!isValidResolution(deviceUserPreferredResolution)
-                    && !isValidRefreshRate(deviceRefreshRate)) {
-                device.setUserPreferredDisplayModeLocked(mode);
-            }
+            device.setUserPreferredDisplayModeLocked(mode);
         });
     }
 
@@ -3715,44 +3715,21 @@
         @Override
         public Set<DisplayInfo> getPossibleDisplayInfo(int displayId) {
             synchronized (mSyncRoot) {
-                // Retrieve the group associated with this display id.
-                final int displayGroupId =
-                        mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(displayId);
-                if (displayGroupId == Display.INVALID_DISPLAY_GROUP) {
-                    Slog.w(TAG,
-                            "Can't get possible display info since display group for " + displayId
-                                    + " does not exist");
-                    return new ArraySet<>();
-                }
-
-                // Assume any display in this group can be swapped out for the given display id.
                 Set<DisplayInfo> possibleInfo = new ArraySet<>();
-                final DisplayGroup group = mLogicalDisplayMapper.getDisplayGroupLocked(
-                        displayGroupId);
-                for (int i = 0; i < group.getSizeLocked(); i++) {
-                    final int id = group.getIdLocked(i);
-                    final LogicalDisplay logical = mLogicalDisplayMapper.getDisplayLocked(id);
-                    if (logical == null) {
-                        Slog.w(TAG,
-                                "Can't get possible display info since logical display for "
-                                        + "display id " + id + " does not exist, as part of group "
-                                        + displayGroupId);
-                    } else {
-                        possibleInfo.add(logical.getDisplayInfoLocked());
-                    }
-                }
-
-                // For the supported device states, retrieve the DisplayInfos for the logical
-                // display layout.
+                // For each of supported device states, retrieve the display layout of that state,
+                // and return all of the DisplayInfos (one per state) for the given display id.
                 if (mDeviceStateManager == null) {
                     Slog.w(TAG, "Can't get supported states since DeviceStateManager not ready");
-                } else {
-                    final int[] supportedStates =
-                            mDeviceStateManager.getSupportedStateIdentifiers();
-                    for (int state : supportedStates) {
-                        possibleInfo.addAll(
-                                mLogicalDisplayMapper.getDisplayInfoForStateLocked(state, displayId,
-                                        displayGroupId));
+                    return possibleInfo;
+                }
+                final int[] supportedStates =
+                        mDeviceStateManager.getSupportedStateIdentifiers();
+                DisplayInfo displayInfo;
+                for (int state : supportedStates) {
+                    displayInfo = mLogicalDisplayMapper.getDisplayInfoForStateLocked(state,
+                            displayId);
+                    if (displayInfo != null) {
+                        possibleInfo.add(displayInfo);
                     }
                 }
                 return possibleInfo;
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 405a2b9..5dba015 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -1358,7 +1358,7 @@
             mDefaultRefreshRate =
                     (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
                         R.integer.config_defaultRefreshRate)
-                        : (float) displayDeviceConfig.getDefaultRefreshRate();
+                        : (float) displayDeviceConfig.getDefaultLowRefreshRate();
         }
 
         public void observe() {
@@ -1445,7 +1445,7 @@
                 defaultPeakRefreshRate =
                         (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
                                 R.integer.config_defaultPeakRefreshRate)
-                                : (float) displayDeviceConfig.getDefaultPeakRefreshRate();
+                                : (float) displayDeviceConfig.getDefaultHighRefreshRate();
             }
             mDefaultPeakRefreshRate = defaultPeakRefreshRate;
         }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9d47892..75415cd 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -453,6 +453,8 @@
     // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
     private float mTemporaryAutoBrightnessAdjustment;
 
+    private boolean mUseAutoBrightness;
+
     private boolean mIsRbcActive;
 
     // Whether there's a callback to tell listeners the display has changed scheduled to run. When
@@ -683,6 +685,7 @@
     @Override
     public void onSwitchUser(@UserIdInt int newUserId) {
         handleSettingsChange(true /* userSwitch */);
+        handleBrightnessModeChange();
         if (mBrightnessTracker != null) {
             mBrightnessTracker.onSwitchUser(newUserId);
         }
@@ -930,6 +933,10 @@
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
                 false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
+                false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        handleBrightnessModeChange();
     }
 
     private void setUpAutoBrightness(Resources resources, Handler handler) {
@@ -1335,11 +1342,11 @@
 
         final boolean autoBrightnessEnabledInDoze =
                 mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
-        final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+        final boolean autoBrightnessEnabled = mUseAutoBrightness
                 && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
                 && Float.isNaN(brightnessState)
                 && mAutomaticBrightnessController != null;
-        final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+        final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
                 && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
         final int autoBrightnessState = autoBrightnessEnabled
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
@@ -1691,7 +1698,7 @@
                 || brightnessAdjustmentFlags != 0) {
             float lastBrightness = mLastBrightnessEvent.getBrightness();
             mTempBrightnessEvent.setInitialBrightness(lastBrightness);
-            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
+            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
             mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
             BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
             // Adjustment flags (and user-set flag) only get added after the equality checks since
@@ -2341,6 +2348,18 @@
         sendUpdatePowerState();
     }
 
+    private void handleBrightnessModeChange() {
+        final int screenBrightnessModeSetting = Settings.System.getIntForUser(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+        mHandler.post(() -> {
+            mUseAutoBrightness = screenBrightnessModeSetting
+                    == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+            updatePowerState();
+        });
+    }
+
     private float getAutoBrightnessAdjustmentSetting() {
         final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
@@ -2425,7 +2444,7 @@
     private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
             boolean hadUserDataPoint) {
         final float brightnessInNits = convertToNits(brightness);
-        if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+        if (mUseAutoBrightness && brightnessInNits >= 0.0f
                 && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
             // We only want to track changes on devices that can actually map the display backlight
             // values into a physical brightness unit since the value provided by the API is in
@@ -2897,7 +2916,11 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            handleSettingsChange(false /* userSwitch */);
+            if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
+                handleBrightnessModeChange();
+            } else {
+                handleSettingsChange(false /* userSwitch */);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 346b340..84b6da8 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -396,6 +396,8 @@
     // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
     private float mTemporaryAutoBrightnessAdjustment;
 
+    private boolean mUseAutoBrightness;
+
     private boolean mIsRbcActive;
 
     // Animators.
@@ -600,6 +602,7 @@
     @Override
     public void onSwitchUser(@UserIdInt int newUserId) {
         handleSettingsChange(true /* userSwitch */);
+        handleBrightnessModeChange();
         if (mBrightnessTracker != null) {
             mBrightnessTracker.onSwitchUser(newUserId);
         }
@@ -842,6 +845,10 @@
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
                 false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
+                false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        handleBrightnessModeChange();
     }
 
     private void setUpAutoBrightness(Resources resources, Handler handler) {
@@ -1174,11 +1181,11 @@
         final boolean autoBrightnessEnabledInDoze =
                 mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()
                         && Display.isDozeState(state);
-        final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+        final boolean autoBrightnessEnabled = mUseAutoBrightness
                 && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
                 && Float.isNaN(brightnessState)
                 && mAutomaticBrightnessController != null;
-        final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+        final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
                 && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
         final int autoBrightnessState = autoBrightnessEnabled
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
@@ -1510,7 +1517,7 @@
                 || brightnessAdjustmentFlags != 0) {
             float lastBrightness = mLastBrightnessEvent.getBrightness();
             mTempBrightnessEvent.setInitialBrightness(lastBrightness);
-            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
+            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
             mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
             BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
             // Adjustment flags (and user-set flag) only get added after the equality checks since
@@ -2045,6 +2052,18 @@
         sendUpdatePowerState();
     }
 
+    private void handleBrightnessModeChange() {
+        final int screenBrightnessModeSetting = Settings.System.getIntForUser(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+        mHandler.post(() -> {
+            mUseAutoBrightness = screenBrightnessModeSetting
+                    == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+            updatePowerState();
+        });
+    }
+
     private float getAutoBrightnessAdjustmentSetting() {
         final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
@@ -2131,7 +2150,7 @@
     private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
             boolean hadUserDataPoint) {
         final float brightnessInNits = convertToNits(brightness);
-        if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+        if (mUseAutoBrightness && brightnessInNits >= 0.0f
                 && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
             // We only want to track changes on devices that can actually map the display backlight
             // values into a physical brightness unit since the value provided by the API is in
@@ -2499,7 +2518,11 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            handleSettingsChange(false /* userSwitch */);
+            if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
+                handleBrightnessModeChange();
+            } else {
+                handleSettingsChange(false /* userSwitch */);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 80f47a1..d7983ae 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -19,6 +19,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.Handler;
@@ -29,7 +30,6 @@
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -45,7 +45,6 @@
 
 import java.io.PrintWriter;
 import java.util.Arrays;
-import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -324,58 +323,44 @@
     }
 
     /**
-     * Returns the set of {@link DisplayInfo} for this device state, only fetching the info that is
-     * part of the same display group as the provided display id. The DisplayInfo represent the
-     * logical display layouts possible for the given device state.
+     * Returns the {@link DisplayInfo} for this device state, indicated by the given display id. The
+     * DisplayInfo represents the attributes of the indicated display in the layout associated with
+     * this state. This is used to get display information for various displays in various states;
+     * e.g. to help apps preload resources for the possible display states.
      *
      * @param deviceState the state to query possible layouts for
-     * @param displayId   the display id to apply to all displays within the group
-     * @param groupId     the display group to filter display info for. Must be the same group as
-     *                    the display with the provided display id.
+     * @param displayId   the display id to retrieve
+     * @return {@code null} if no corresponding {@link DisplayInfo} could be found, or the
+     * {@link DisplayInfo} with a matching display id.
      */
-    public Set<DisplayInfo> getDisplayInfoForStateLocked(int deviceState, int displayId,
-            int groupId) {
-        Set<DisplayInfo> displayInfos = new ArraySet<>();
+    @Nullable
+    public DisplayInfo getDisplayInfoForStateLocked(int deviceState, int displayId) {
+        // Retrieve the layout for this particular state.
         final Layout layout = mDeviceStateToLayoutMap.get(deviceState);
-        final int layoutSize = layout.size();
-        for (int i = 0; i < layoutSize; i++) {
-            Layout.Display displayLayout = layout.getAt(i);
-            if (displayLayout == null) {
-                continue;
-            }
-
-            // If the underlying display-device we want to use for this display
-            // doesn't exist, then skip it. This can happen at startup as display-devices
-            // trickle in one at a time. When the new display finally shows up, the layout is
-            // recalculated so that the display is properly added to the current layout.
-            final DisplayAddress address = displayLayout.getAddress();
-            final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
-            if (device == null) {
-                Slog.w(TAG, "The display device (" + address + "), is not available"
-                        + " for the display state " + deviceState);
-                continue;
-            }
-
-            // Find or create the LogicalDisplay to map the DisplayDevice to.
-            final int logicalDisplayId = displayLayout.getLogicalDisplayId();
-            final LogicalDisplay logicalDisplay = getDisplayLocked(logicalDisplayId);
-            if (logicalDisplay == null) {
-                Slog.w(TAG, "The logical display (" + address + "), is not available"
-                        + " for the display state " + deviceState);
-                continue;
-            }
-            final DisplayInfo temp = logicalDisplay.getDisplayInfoLocked();
-            DisplayInfo displayInfo = new DisplayInfo(temp);
-            if (displayInfo.displayGroupId != groupId) {
-                // Ignore any displays not in the provided group.
-                continue;
-            }
-            // A display in the same group can be swapped out at any point, so set the display id
-            // for all results to the provided display id.
-            displayInfo.displayId = displayId;
-            displayInfos.add(displayInfo);
+        if (layout == null) {
+            return null;
         }
-        return displayInfos;
+        // Retrieve the details of the given display within this layout.
+        Layout.Display display = layout.getById(displayId);
+        if (display == null) {
+            return null;
+        }
+        // Retrieve the display info for the display that matches the display id.
+        final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(display.getAddress());
+        if (device == null) {
+            Slog.w(TAG, "The display device (" + display.getAddress() + "), is not available"
+                    + " for the display state " + mDeviceState);
+            return null;
+        }
+        LogicalDisplay logicalDisplay = getDisplayLocked(device, /* includeDisabled= */ true);
+        if (logicalDisplay == null) {
+            Slog.w(TAG, "The logical display associated with address (" + display.getAddress()
+                    + "), is not available for the display state " + mDeviceState);
+            return null;
+        }
+        DisplayInfo displayInfo = new DisplayInfo(logicalDisplay.getDisplayInfoLocked());
+        displayInfo.displayId = displayId;
+        return displayInfo;
     }
 
     public void dumpLocked(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index a118b2f..7c647cf 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -599,6 +599,15 @@
                 handleMediaProjectionStoppedLocked(mAppToken);
             }
         }
+
+        @Override
+        public void onCapturedContentResize(int width, int height) {
+            // Do nothing when we tell the client that the content is resized - it is up to them
+            // to decide to update the VirtualDisplay and Surface.
+            // We could only update the VirtualDisplay size, anyway (which the client wouldn't
+            // expect), and there will still be letterboxing on the output content since the
+            // Surface and VirtualDisplay would then have different aspect ratios.
+        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/incident/IncidentCompanionService.java b/services/core/java/com/android/server/incident/IncidentCompanionService.java
index b8e7d49..87fe785 100644
--- a/services/core/java/com/android/server/incident/IncidentCompanionService.java
+++ b/services/core/java/com/android/server/incident/IncidentCompanionService.java
@@ -34,6 +34,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 
 import com.android.internal.util.DumpUtils;
@@ -127,21 +128,21 @@
             try {
                 final Context context = getContext();
 
-                // Get the current admin user. Only they can do incident reports.
-                final int currentAdminUser = getCurrentUserIfAdmin();
-                if (currentAdminUser == UserHandle.USER_NULL) {
+                final int primaryUser = getAndValidateUser(context);
+                if (primaryUser == UserHandle.USER_NULL) {
                     return;
                 }
 
                 final Intent intent = new Intent(Intent.ACTION_INCIDENT_REPORT_READY);
                 intent.setComponent(new ComponentName(pkg, cls));
 
-                Log.d(TAG, "sendReportReadyBroadcast sending currentUser=" + currentAdminUser
-                        + " userHandle=" + UserHandle.of(currentAdminUser)
+                Log.d(TAG, "sendReportReadyBroadcast sending primaryUser=" + primaryUser
+                        + " userHandle=" + UserHandle.getUserHandleForUid(primaryUser)
                         + " intent=" + intent);
 
+                // Send it to the primary user.  Only they can do incident reports.
                 context.sendBroadcastAsUserMultiplePermissions(intent,
-                        UserHandle.of(currentAdminUser),
+                        UserHandle.getUserHandleForUid(primaryUser),
                         DUMP_AND_USAGE_STATS_PERMISSIONS);
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -413,10 +414,10 @@
     }
 
     /**
-     * Check whether the current user is an admin user, and return the user id if they are.
+     * Check whether the current user is the primary user, and return the user id if they are.
      * Returns UserHandle.USER_NULL if not valid.
      */
-    public static int getCurrentUserIfAdmin() {
+    public static int getAndValidateUser(Context context) {
         // Current user
         UserInfo currentUser;
         try {
@@ -426,21 +427,28 @@
             throw new RuntimeException(ex);
         }
 
+        // Primary user
+        final UserManager um = UserManager.get(context);
+        final UserInfo primaryUser = um.getPrimaryUser();
+
         // Check that we're using the right user.
         if (currentUser == null) {
             Log.w(TAG, "No current user.  Nobody to approve the report."
                     + " The report will be denied.");
             return UserHandle.USER_NULL;
         }
-
-        if (!currentUser.isAdmin()) {
-            Log.w(TAG, "Only an admin user running in foreground can approve "
-                    + "bugreports, but the current foreground user is not an admin user. "
-                    + "The report will be denied.");
+        if (primaryUser == null) {
+            Log.w(TAG, "No primary user.  Nobody to approve the report."
+                    + " The report will be denied.");
+            return UserHandle.USER_NULL;
+        }
+        if (primaryUser.id != currentUser.id) {
+            Log.w(TAG, "Only the primary user can approve bugreports, but they are not"
+                    + " the current user. The report will be denied.");
             return UserHandle.USER_NULL;
         }
 
-        return currentUser.id;
+        return primaryUser.id;
     }
 }
 
diff --git a/services/core/java/com/android/server/incident/PendingReports.java b/services/core/java/com/android/server/incident/PendingReports.java
index 6285bc3..f39bebf 100644
--- a/services/core/java/com/android/server/incident/PendingReports.java
+++ b/services/core/java/com/android/server/incident/PendingReports.java
@@ -16,7 +16,6 @@
 
 package com.android.server.incident;
 
-import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.content.ComponentName;
@@ -273,19 +272,15 @@
             return;
         }
 
-        // Find the current user of the device and check if they are an admin.
-        final int currentAdminUser = getCurrentUserIfAdmin();
-
-        // Deny the report if the current admin user is null
-        // or not the user who requested the report.
-        if (currentAdminUser == UserHandle.USER_NULL
-                || currentAdminUser != UserHandle.getUserId(callingUid)) {
+        // Find the primary user of this device.
+        final int primaryUser = getAndValidateUser();
+        if (primaryUser == UserHandle.USER_NULL) {
             denyReportBeforeAddingRec(listener, callingPackage);
             return;
         }
 
         // Find the approver app (hint: it's PermissionController).
-        final ComponentName receiver = getApproverComponent(currentAdminUser);
+        final ComponentName receiver = getApproverComponent(primaryUser);
         if (receiver == null) {
             // We couldn't find an approver... so deny the request here and now, before we
             // do anything else.
@@ -303,26 +298,26 @@
         try {
             listener.asBinder().linkToDeath(() -> {
                 Log.i(TAG, "Got death notification listener=" + listener);
-                cancelReportImpl(listener, receiver, currentAdminUser);
+                cancelReportImpl(listener, receiver, primaryUser);
             }, 0);
         } catch (RemoteException ex) {
             Log.e(TAG, "Remote died while trying to register death listener: " + rec.getUri());
             // First, remove from our list.
-            cancelReportImpl(listener, receiver, currentAdminUser);
+            cancelReportImpl(listener, receiver, primaryUser);
         }
 
         // Go tell Permission controller to start asking the user.
-        sendBroadcast(receiver, currentAdminUser);
+        sendBroadcast(receiver, primaryUser);
     }
 
     /**
      * Cancel a pending report request (because of an explicit call to cancel)
      */
     private void cancelReportImpl(IIncidentAuthListener listener) {
-        final int currentAdminUser = getCurrentUserIfAdmin();
-        final ComponentName receiver = getApproverComponent(currentAdminUser);
-        if (currentAdminUser != UserHandle.USER_NULL && receiver != null) {
-            cancelReportImpl(listener, receiver, currentAdminUser);
+        final int primaryUser = getAndValidateUser();
+        final ComponentName receiver = getApproverComponent(primaryUser);
+        if (primaryUser != UserHandle.USER_NULL && receiver != null) {
+            cancelReportImpl(listener, receiver, primaryUser);
         }
     }
 
@@ -331,13 +326,13 @@
      * by the calling app, or because of a binder death).
      */
     private void cancelReportImpl(IIncidentAuthListener listener, ComponentName receiver,
-            @UserIdInt int user) {
+            int primaryUser) {
         // First, remove from our list.
         synchronized (mLock) {
             removePendingReportRecLocked(listener);
         }
         // Second, call back to PermissionController to say it's canceled.
-        sendBroadcast(receiver, user);
+        sendBroadcast(receiver, primaryUser);
     }
 
     /**
@@ -347,21 +342,21 @@
      * cleanup cases to keep the apps' list in sync with ours.
      */
     private void sendBroadcast() {
-        final int currentAdminUser = getCurrentUserIfAdmin();
-        if (currentAdminUser == UserHandle.USER_NULL) {
+        final int primaryUser = getAndValidateUser();
+        if (primaryUser == UserHandle.USER_NULL) {
             return;
         }
-        final ComponentName receiver = getApproverComponent(currentAdminUser);
+        final ComponentName receiver = getApproverComponent(primaryUser);
         if (receiver == null) {
             return;
         }
-        sendBroadcast(receiver, currentAdminUser);
+        sendBroadcast(receiver, primaryUser);
     }
 
     /**
      * Send the confirmation broadcast.
      */
-    private void sendBroadcast(ComponentName receiver, int currentUser) {
+    private void sendBroadcast(ComponentName receiver, int primaryUser) {
         final Intent intent = new Intent(Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED);
         intent.setComponent(receiver);
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -369,8 +364,8 @@
         final BroadcastOptions options = BroadcastOptions.makeBasic();
         options.setBackgroundActivityStartsAllowed(true);
 
-        // Send it to the current user.
-        mContext.sendBroadcastAsUser(intent, UserHandle.of(currentUser),
+        // Send it to the primary user.
+        mContext.sendBroadcastAsUser(intent, UserHandle.getUserHandleForUid(primaryUser),
                 android.Manifest.permission.APPROVE_INCIDENT_REPORTS, options.toBundle());
     }
 
@@ -425,11 +420,11 @@
     }
 
     /**
-     * Check whether the current user is an admin user, and return the user id if they are.
+     * Check whether the current user is the primary user, and return the user id if they are.
      * Returns UserHandle.USER_NULL if not valid.
      */
-    private int getCurrentUserIfAdmin() {
-        return IncidentCompanionService.getCurrentUserIfAdmin();
+    private int getAndValidateUser() {
+        return IncidentCompanionService.getAndValidateUser(mContext);
     }
 
     /**
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 298098a..01a564d 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -171,4 +171,19 @@
      * {@see Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT}
      */
     public abstract void decrementKeyboardBacklight(int deviceId);
+
+    /**
+     * Add a runtime association between the input port and device type. Input ports are expected to
+     * be unique.
+     * @param inputPort The port of the input device.
+     * @param type The type of the device. E.g. "touchNavigation".
+     */
+    public abstract void setTypeAssociation(@NonNull String inputPort, @NonNull String type);
+
+    /**
+     * Removes a runtime association between the input device and type.
+     *
+     * @param inputPort The port of the input device.
+     */
+    public abstract void unsetTypeAssociation(@NonNull String inputPort);
 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 0da04a2..1809b18 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -248,6 +248,13 @@
     @GuardedBy("mAssociationsLock")
     private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
 
+    // Stores input ports associated with device types. For example, adding an association
+    // {"123", "touchNavigation"} here would mean that a touch device appearing at port "123" would
+    // enumerate as a "touch navigation" device rather than the default "touchpad as a mouse
+    // pointer" device.
+    @GuardedBy("mAssociationsLock")
+    private final Map<String, String> mDeviceTypeAssociations = new ArrayMap<>();
+
     // Guards per-display input properties and properties relating to the mouse pointer.
     // Threads can wait on this lock to be notified the next time the display on which the mouse
     // pointer is shown has changed.
@@ -1193,7 +1200,7 @@
     @Override // Binder call
     public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @NonNull InputMethodSubtype imeSubtype) {
+            @Nullable InputMethodSubtype imeSubtype) {
         return mKeyboardLayoutManager.getKeyboardLayoutForInputDevice(identifier, userId,
                 imeInfo, imeSubtype);
     }
@@ -1202,16 +1209,16 @@
     @Override // Binder call
     public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @NonNull InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) {
+            @Nullable InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) {
         super.setKeyboardLayoutForInputDevice_enforcePermission();
         mKeyboardLayoutManager.setKeyboardLayoutForInputDevice(identifier, userId, imeInfo,
                 imeSubtype, keyboardLayoutDescriptor);
     }
 
     @Override // Binder call
-    public String[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+    public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @NonNull InputMethodSubtype imeSubtype) {
+            @Nullable InputMethodSubtype imeSubtype) {
         return mKeyboardLayoutManager.getKeyboardLayoutListForInputDevice(identifier, userId,
                 imeInfo, imeSubtype);
     }
@@ -1905,6 +1912,23 @@
         mNative.changeUniqueIdAssociation();
     }
 
+    void setTypeAssociationInternal(@NonNull String inputPort, @NonNull String type) {
+        Objects.requireNonNull(inputPort);
+        Objects.requireNonNull(type);
+        synchronized (mAssociationsLock) {
+            mDeviceTypeAssociations.put(inputPort, type);
+        }
+        mNative.changeTypeAssociation();
+    }
+
+    void unsetTypeAssociationInternal(@NonNull String inputPort) {
+        Objects.requireNonNull(inputPort);
+        synchronized (mAssociationsLock) {
+            mDeviceTypeAssociations.remove(inputPort);
+        }
+        mNative.changeTypeAssociation();
+    }
+
     @Override // Binder call
     public InputSensorInfo[] getSensorList(int deviceId) {
         return mNative.getSensorList(deviceId);
@@ -2221,6 +2245,13 @@
                     pw.println("  uniqueId: " + v);
                 });
             }
+            if (!mDeviceTypeAssociations.isEmpty()) {
+                pw.println("Type Associations:");
+                mDeviceTypeAssociations.forEach((k, v) -> {
+                    pw.print("  port: " + k);
+                    pw.println("  type: " + v);
+                });
+            }
         }
     }
 
@@ -2630,6 +2661,18 @@
         return flatten(associations);
     }
 
+    // Native callback
+    @SuppressWarnings("unused")
+    @VisibleForTesting
+    String[] getDeviceTypeAssociations() {
+        final Map<String, String> associations;
+        synchronized (mAssociationsLock) {
+            associations = new HashMap<>(mDeviceTypeAssociations);
+        }
+
+        return flatten(associations);
+    }
+
     /**
      * Gets if an input device could dispatch to the given display".
      * @param deviceId The input device id.
@@ -3263,6 +3306,16 @@
         public void decrementKeyboardBacklight(int deviceId) {
             mKeyboardBacklightController.decrementKeyboardBacklight(deviceId);
         }
+
+        @Override
+        public void setTypeAssociation(@NonNull String inputPort, @NonNull String type) {
+            setTypeAssociationInternal(inputPort, type);
+        }
+
+        @Override
+        public void unsetTypeAssociation(@NonNull String inputPort) {
+            unsetTypeAssociationInternal(inputPort);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 1bb14aa..d76da83 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -17,6 +17,7 @@
 package com.android.server.input;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -550,7 +551,7 @@
 
     public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @NonNull InputMethodSubtype imeSubtype) {
+            @Nullable InputMethodSubtype imeSubtype) {
         // TODO(b/259530132): Implement the new keyboard layout API: Returning non-IME specific
         //  layout for now.
         return getCurrentKeyboardLayoutForInputDevice(identifier);
@@ -558,23 +559,18 @@
 
     public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @NonNull InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) {
+            @Nullable InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) {
         // TODO(b/259530132): Implement the new keyboard layout API: setting non-IME specific
         //  layout for now.
         setCurrentKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
     }
 
-    public String[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+    public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @NonNull InputMethodSubtype imeSubtype) {
+            @Nullable InputMethodSubtype imeSubtype) {
         // TODO(b/259530132): Implement the new keyboard layout API: Returning list of all
         //  layouts for now.
-        KeyboardLayout[] allLayouts = getKeyboardLayouts();
-        String[] allLayoutDesc = new String[allLayouts.length];
-        for (int i = 0; i < allLayouts.length; i++) {
-            allLayoutDesc[i] = allLayouts[i].getDescriptor();
-        }
-        return allLayoutDesc;
+        return getKeyboardLayouts();
     }
 
     public void switchKeyboardLayout(int deviceId, int direction) {
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 8781c6e..184bc0e 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -186,6 +186,8 @@
 
     void changeUniqueIdAssociation();
 
+    void changeTypeAssociation();
+
     void notifyPointerDisplayIdChanged();
 
     void setDisplayEligibilityForPointerCapture(int displayId, boolean enabled);
@@ -400,6 +402,9 @@
         public native void changeUniqueIdAssociation();
 
         @Override
+        public native void changeTypeAssociation();
+
+        @Override
         public native void notifyPointerDisplayIdChanged();
 
         @Override
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 3ce51c3..b4c9596 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -24,6 +24,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.location.LocationManager.BLOCK_PENDING_INTENT_SYSTEM_API_USAGE;
 import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.GPS_HARDWARE_PROVIDER;
 import static android.location.LocationManager.GPS_PROVIDER;
 import static android.location.LocationManager.NETWORK_PROVIDER;
 import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS;
@@ -95,6 +96,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
@@ -319,6 +321,9 @@
 
         for (LocationProviderManager manager : mProviderManagers) {
             if (providerName.equals(manager.getName())) {
+                if (!manager.isVisibleToCaller()) {
+                    return null;
+                }
                 return manager;
             }
         }
@@ -341,8 +346,9 @@
         }
     }
 
-    private void addLocationProviderManager(LocationProviderManager manager,
-            @Nullable AbstractLocationProvider realProvider) {
+    @VisibleForTesting
+    void addLocationProviderManager(
+            LocationProviderManager manager, @Nullable AbstractLocationProvider realProvider) {
         synchronized (mProviderManagers) {
             Preconditions.checkState(getLocationProviderManager(manager.getName()) == null);
 
@@ -453,6 +459,20 @@
             }
             if (gnssProvider == null) {
                 gnssProvider = mGnssManagerService.getGnssLocationProvider();
+            } else {
+                // If we have a GNSS provider override, add the hardware provider as a standalone
+                // option for use by apps with the correct permission. Note the GNSS HAL can only
+                // support a single client, so mGnssManagerService.getGnssLocationProvider() can
+                // only be installed with a single provider.
+                LocationProviderManager gnssHardwareManager =
+                        new LocationProviderManager(
+                                mContext,
+                                mInjector,
+                                GPS_HARDWARE_PROVIDER,
+                                mPassiveManager,
+                                Collections.singletonList(Manifest.permission.LOCATION_HARDWARE));
+                addLocationProviderManager(
+                        gnssHardwareManager, mGnssManagerService.getGnssLocationProvider());
             }
 
             LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
@@ -629,7 +649,9 @@
     public List<String> getAllProviders() {
         ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
         for (LocationProviderManager manager : mProviderManagers) {
-            providers.add(manager.getName());
+            if (manager.isVisibleToCaller()) {
+                providers.add(manager.getName());
+            }
         }
         return providers;
     }
@@ -644,15 +666,18 @@
         synchronized (mLock) {
             ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
             for (LocationProviderManager manager : mProviderManagers) {
-                String name = manager.getName();
-                if (enabledOnly && !manager.isEnabled(UserHandle.getCallingUserId())) {
-                    continue;
+                if (manager.isVisibleToCaller()) {
+                    String name = manager.getName();
+                    if (enabledOnly && !manager.isEnabled(UserHandle.getCallingUserId())) {
+                        continue;
+                    }
+                    if (criteria != null
+                            && !LocationProvider.propertiesMeetCriteria(
+                                    name, manager.getProperties(), criteria)) {
+                        continue;
+                    }
+                    providers.add(name);
                 }
-                if (criteria != null && !LocationProvider.propertiesMeetCriteria(name,
-                        manager.getProperties(), criteria)) {
-                    continue;
-                }
-                providers.add(name);
             }
             return providers;
         }
@@ -1059,7 +1084,9 @@
     public void addProviderRequestListener(IProviderRequestListener listener) {
         mContext.enforceCallingOrSelfPermission(INTERACT_ACROSS_USERS, null);
         for (LocationProviderManager manager : mProviderManagers) {
-            manager.addProviderRequestListener(listener);
+            if (manager.isVisibleToCaller()) {
+                manager.addProviderRequestListener(listener);
+            }
         }
     }
 
@@ -1649,7 +1676,7 @@
                 if (provider != null && !provider.equals(manager.getName())) {
                     continue;
                 }
-                if (identity.equals(manager.getProviderIdentity())) {
+                if (identity.equals(manager.getProviderIdentity()) && manager.isVisibleToCaller()) {
                     return true;
                 }
             }
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 7063cb8..ffdb531 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -19,6 +19,7 @@
 import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
 import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
 import static android.app.compat.CompatChanges.isChangeEnabled;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.location.LocationManager.DELIVER_HISTORICAL_LOCATIONS;
 import static android.location.LocationManager.GPS_PROVIDER;
 import static android.location.LocationManager.KEY_FLUSH_COMPLETE;
@@ -48,6 +49,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.AlarmManager.OnAlarmListener;
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
@@ -124,6 +126,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -1362,6 +1365,10 @@
     @GuardedBy("mMultiplexerLock")
     private final ArrayList<ProviderEnabledListener> mEnabledListeners;
 
+    // Extra permissions required to use this provider (on top of the usual location permissions).
+    // Not guarded because it's read only.
+    private final Collection<String> mRequiredPermissions;
+
     private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
 
     protected final LocationManagerInternal mLocationManagerInternal;
@@ -1435,12 +1442,34 @@
 
     public LocationProviderManager(Context context, Injector injector,
             String name, @Nullable PassiveLocationProviderManager passiveManager) {
+        this(context, injector, name, passiveManager, Collections.emptyList());
+    }
+
+    /**
+     * Creates a manager for a location provider (the two have a 1:1 correspondence).
+     *
+     * @param context Context in which the manager is running.
+     * @param injector Injector to retrieve system components (useful to override in testing)
+     * @param name Name of this provider (used in LocationManager APIs by client apps).
+     * @param passiveManager The "passive" manager (special case provider that returns locations
+     *     from all other providers).
+     * @param requiredPermissions Required permissions for accessing this provider. All of the given
+     *     permissions are required to access the provider. If a caller doesn't hold the correct
+     *     permission, the provider will be invisible to it.
+     */
+    public LocationProviderManager(
+            Context context,
+            Injector injector,
+            String name,
+            @Nullable PassiveLocationProviderManager passiveManager,
+            Collection<String> requiredPermissions) {
         mContext = context;
         mName = Objects.requireNonNull(name);
         mPassiveManager = passiveManager;
         mState = STATE_STOPPED;
         mEnabled = new SparseBooleanArray(2);
         mLastLocations = new SparseArray<>(2);
+        mRequiredPermissions = requiredPermissions;
 
         mEnabledListeners = new ArrayList<>();
         mProviderRequestListeners = new CopyOnWriteArrayList<>();
@@ -1559,6 +1588,24 @@
         }
     }
 
+    /**
+     * Returns true if this provider is visible to the current caller (whether called from a binder
+     * thread or not). If a provider isn't visible, then all APIs return the same data they would if
+     * the provider didn't exist (i.e. the caller can't see or use the provider).
+     *
+     * <p>This method doesn't require any permissions, but uses permissions to determine which
+     * subset of providers are visible.
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public boolean isVisibleToCaller() {
+        for (String permission : mRequiredPermissions) {
+            if (mContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     public void addEnabledListener(ProviderEnabledListener listener) {
         synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 2dc1e1c..5f39a52 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -240,8 +240,7 @@
     protected final UserManager mUserManager;
     private final IStorageManager mStorageManager;
     private final IActivityManager mActivityManager;
-    @VisibleForTesting
-    protected final SyntheticPasswordManager mSpManager;
+    private final SyntheticPasswordManager mSpManager;
 
     private final java.security.KeyStore mJavaKeyStore;
     private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
@@ -946,7 +945,7 @@
                     if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
                         Slogf.i(TAG, "Creating locksettings state for user %d now that boot "
                                 + "is complete", userId);
-                        initializeSyntheticPasswordLocked(userId);
+                        initializeSyntheticPassword(userId);
                     }
                 }
             }
@@ -985,7 +984,7 @@
         long protectorId = getCurrentLskfBasedProtectorId(userId);
         if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
             Slogf.i(TAG, "Migrating unsecured user %d to SP-based credential", userId);
-            initializeSyntheticPasswordLocked(userId);
+            initializeSyntheticPassword(userId);
         } else {
             Slogf.i(TAG, "Existing unsecured user %d has a synthetic password; re-encrypting CE " +
                     "key with it", userId);
@@ -1027,11 +1026,18 @@
     }
 
     private void enforceFrpResolved() {
+        final int mainUserId = mInjector.getUserManagerInternal().getMainUserId();
+        if (mainUserId < 0) {
+            Slog.i(TAG, "No Main user on device; skip enforceFrpResolved");
+            return;
+        }
         final ContentResolver cr = mContext.getContentResolver();
+
         final boolean inSetupWizard = Settings.Secure.getIntForUser(cr,
-                Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_SYSTEM) == 0;
-        final boolean secureFrp = Settings.Secure.getIntForUser(cr,
-                Settings.Secure.SECURE_FRP_MODE, 0, UserHandle.USER_SYSTEM) == 1;
+                Settings.Secure.USER_SETUP_COMPLETE, 0, mainUserId) == 0;
+        final boolean secureFrp = Settings.Global.getInt(cr,
+                Settings.Global.SECURE_FRP_MODE, 0) == 1;
+
         if (inSetupWizard && secureFrp) {
             throw new SecurityException("Cannot change credential in SUW while factory reset"
                     + " protection is not resolved yet");
@@ -1645,13 +1651,7 @@
         Objects.requireNonNull(savedCredential);
         if (DEBUG) Slog.d(TAG, "setLockCredentialInternal: user=" + userId);
         synchronized (mSpManager) {
-            if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
-                if (!savedCredential.isNone()) {
-                    throw new IllegalStateException("Saved credential given, but user has no SP");
-                }
-                // TODO(b/232452368): this case is only needed by unit tests now; remove it.
-                initializeSyntheticPasswordLocked(userId);
-            } else if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
+            if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
                 // get credential from keystore when profile has unified lock
                 try {
                     //TODO: remove as part of b/80170828
@@ -2315,9 +2315,7 @@
                 return;
             }
             removeStateForReusedUserIdIfNecessary(userId, userSerialNumber);
-            synchronized (mSpManager) {
-                initializeSyntheticPasswordLocked(userId);
-            }
+            initializeSyntheticPassword(userId);
         }
     }
 
@@ -2643,21 +2641,22 @@
      * until the time when Weaver is guaranteed to be available), or when upgrading from Android 13
      * or earlier where users with no LSKF didn't necessarily have an SP.
      */
-    @GuardedBy("mSpManager")
     @VisibleForTesting
-    SyntheticPassword initializeSyntheticPasswordLocked(int userId) {
-        Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
-        Preconditions.checkState(getCurrentLskfBasedProtectorId(userId) ==
-                SyntheticPasswordManager.NULL_PROTECTOR_ID,
-                "Cannot reinitialize SP");
+    SyntheticPassword initializeSyntheticPassword(int userId) {
+        synchronized (mSpManager) {
+            Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
+            Preconditions.checkState(getCurrentLskfBasedProtectorId(userId) ==
+                    SyntheticPasswordManager.NULL_PROTECTOR_ID,
+                    "Cannot reinitialize SP");
 
-        final SyntheticPassword sp = mSpManager.newSyntheticPassword(userId);
-        final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
-                LockscreenCredential.createNone(), sp, userId);
-        setCurrentLskfBasedProtectorId(protectorId, userId);
-        setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
-        onSyntheticPasswordKnown(userId, sp);
-        return sp;
+            final SyntheticPassword sp = mSpManager.newSyntheticPassword(userId);
+            final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
+                    LockscreenCredential.createNone(), sp, userId);
+            setCurrentLskfBasedProtectorId(protectorId, userId);
+            setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
+            onSyntheticPasswordKnown(userId, sp);
+            return sp;
+        }
     }
 
     @VisibleForTesting
@@ -2673,13 +2672,6 @@
         setLong(LSKF_LAST_CHANGED_TIME_KEY, System.currentTimeMillis(), userId);
     }
 
-    @VisibleForTesting
-    boolean isSyntheticPasswordBasedCredential(int userId) {
-        synchronized (mSpManager) {
-            return isSyntheticPasswordBasedCredentialLocked(userId);
-        }
-    }
-
     private boolean isSyntheticPasswordBasedCredentialLocked(int userId) {
         if (userId == USER_FRP) {
             final int type = mStorage.readPersistentDataBlock().type;
@@ -2918,19 +2910,14 @@
             @NonNull EscrowTokenStateChangeCallback callback) {
         if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId + ", type=" + type);
         synchronized (mSpManager) {
-            // If the user has no LSKF, then the token can be activated immediately, after creating
-            // the user's SP if it doesn't already exist.  Otherwise, the token can't be activated
-            // until the SP is unlocked by another protector (normally the LSKF-based one).
+            // If the user has no LSKF, then the token can be activated immediately.  Otherwise, the
+            // token can't be activated until the SP is unlocked by another protector (normally the
+            // LSKF-based one).
             SyntheticPassword sp = null;
             if (!isUserSecure(userId)) {
                 long protectorId = getCurrentLskfBasedProtectorId(userId);
-                if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
-                    // TODO(b/232452368): this case is only needed by unit tests now; remove it.
-                    sp = initializeSyntheticPasswordLocked(userId);
-                } else {
-                    sp = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
-                            LockscreenCredential.createNone(), userId, null).syntheticPassword;
-                }
+                sp = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
+                        LockscreenCredential.createNone(), userId, null).syntheticPassword;
             }
             disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
             if (!mSpManager.hasEscrowData(userId)) {
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index ed8d852..50e1fca 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -315,7 +315,7 @@
         @Override // Binder call
         public boolean isValidMediaProjection(IMediaProjection projection) {
             return MediaProjectionManagerService.this.isValidMediaProjection(
-                    projection.asBinder());
+                    projection == null ? null : projection.asBinder());
         }
 
         @Override // Binder call
@@ -348,7 +348,26 @@
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
+        }
 
+        @Override // Binder call
+        public void notifyActiveProjectionCapturedContentResized(int width, int height) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
+                        + "on captured content resize");
+            }
+            if (!isValidMediaProjection(mProjectionGrant)) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (mProjectionGrant != null && mCallbackDelegate != null) {
+                    mCallbackDelegate.dispatchResize(mProjectionGrant, width, height);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override //Binder call
@@ -659,9 +678,11 @@
 
     private static class CallbackDelegate {
         private Map<IBinder, IMediaProjectionCallback> mClientCallbacks;
+        // Map from the IBinder token representing the callback, to the callback instance.
+        // Represents the callbacks registered on the client's MediaProjectionManager.
         private Map<IBinder, IMediaProjectionWatcherCallback> mWatcherCallbacks;
         private Handler mHandler;
-        private Object mLock = new Object();
+        private final Object mLock = new Object();
 
         public CallbackDelegate() {
             mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
@@ -715,6 +736,8 @@
             }
             synchronized (mLock) {
                 for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
+                    // Notify every callback the client has registered for a particular
+                    // MediaProjection instance.
                     mHandler.post(new ClientStopCallback(callback));
                 }
 
@@ -724,6 +747,33 @@
                 }
             }
         }
+
+        public void dispatchResize(MediaProjection projection, int width, int height) {
+            if (projection == null) {
+                Slog.e(TAG, "Tried to dispatch stop notification for a null media projection."
+                        + " Ignoring!");
+                return;
+            }
+            synchronized (mLock) {
+                // TODO(b/249827847) Currently the service assumes there is only one projection
+                //  at once - need to find the callback for the given projection, when there are
+                //  multiple sessions.
+                for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
+                    mHandler.post(() -> {
+                        try {
+                            // Notify every callback the client has registered for a particular
+                            // MediaProjection instance.
+                            callback.onCapturedContentResize(width, height);
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to notify media projection has resized to " + width
+                                    + " x " + height, e);
+                        }
+                    });
+                }
+                // Do not need to notify watcher callback about resize, since watcher callback
+                // is for passing along if recording is still ongoing or not.
+            }
+        }
     }
 
     private static final class WatcherStartCallback implements Runnable {
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 2fdc4cd..58428ca 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -33,8 +33,8 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.telephony.TelephonyManager;
-import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Slog;
 
@@ -189,10 +189,10 @@
     }
 
     /**
-     * Validates that the current user is an admin user or, when bugreport is requested remotely
-     * that the current user is an affiliated user.
+     * Validates that the current user is the primary user or when bugreport is requested remotely
+     * and current user is affiliated user.
      *
-     * @throws IllegalArgumentException if the current user is not an admin user
+     * @throws IllegalArgumentException if the current user is not the primary user
      */
     private void ensureUserCanTakeBugReport(int bugreportMode) {
         UserInfo currentUser = null;
@@ -202,17 +202,20 @@
             // Impossible to get RemoteException for an in-process call.
         }
 
+        UserInfo primaryUser = UserManager.get(mContext).getPrimaryUser();
         if (currentUser == null) {
-            logAndThrow("There is no current user, so no bugreport can be requested.");
+            logAndThrow("No current user. Only primary user is allowed to take bugreports.");
         }
-
-        if (!currentUser.isAdmin()) {
+        if (primaryUser == null) {
+            logAndThrow("No primary user. Only primary user is allowed to take bugreports.");
+        }
+        if (primaryUser.id != currentUser.id) {
             if (bugreportMode == BugreportParams.BUGREPORT_MODE_REMOTE
                     && isCurrentUserAffiliated(currentUser.id)) {
                 return;
             }
-            logAndThrow(TextUtils.formatSimple("Current user %s is not an admin user."
-                    + " Only admin users are allowed to take bugreport.", currentUser.id));
+            logAndThrow("Current user not primary user. Only primary user"
+                    + " is allowed to take bugreports.");
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index df95f86..d4c4c69 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -17,19 +17,46 @@
 package com.android.server.pm;
 
 import android.annotation.NonNull;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.IBackgroundInstallControlService;
 import android.content.pm.IPackageManager;
+import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
-import android.os.IBinder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Slog;
 import android.util.SparseArrayMap;
+import android.util.SparseSetArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
 import com.android.server.SystemService;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.TreeSet;
 
 /**
  * @hide
@@ -37,14 +64,30 @@
 public class BackgroundInstallControlService extends SystemService {
     private static final String TAG = "BackgroundInstallControlService";
 
+    private static final String DISK_FILE_NAME = "states";
+    private static final String DISK_DIR_NAME = "bic";
+
+    private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10;
+
+    private static final int MSG_USAGE_EVENT_RECEIVED = 0;
+    private static final int MSG_PACKAGE_ADDED = 1;
+    private static final int MSG_PACKAGE_REMOVED = 2;
+
     private final Context mContext;
     private final BinderService mBinderService;
     private final IPackageManager mIPackageManager;
+    private final PackageManagerInternal mPackageManagerInternal;
+    private final UsageStatsManagerInternal mUsageStatsManagerInternal;
+    private final PermissionManagerServiceInternal mPermissionManager;
+    private final Handler mHandler;
+    private final File mDiskFile;
 
-    // User ID -> package name -> time diff
-    // The time diff between the last foreground activity installer and
-    // the "onPackageAdded" function call.
-    private final SparseArrayMap<String, Long> mBackgroundInstalledPackages =
+
+    private SparseSetArray<String> mBackgroundInstalledPackages = null;
+
+    // User ID -> package name -> set of foreground time frame
+    private final SparseArrayMap<String,
+            TreeSet<ForegroundTimeFrame>> mInstallerForegroundTimeFrames =
             new SparseArrayMap<>();
 
     public BackgroundInstallControlService(@NonNull Context context) {
@@ -56,49 +99,385 @@
         super(injector.getContext());
         mContext = injector.getContext();
         mIPackageManager = injector.getIPackageManager();
+        mPackageManagerInternal = injector.getPackageManagerInternal();
+        mPermissionManager = injector.getPermissionManager();
+        mHandler = new EventHandler(injector.getLooper(), this);
+        mDiskFile = injector.getDiskFile();
+        mUsageStatsManagerInternal = injector.getUsageStatsManagerInternal();
+        mUsageStatsManagerInternal.registerListener(
+                (userId, event) ->
+                        mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED,
+                                userId,
+                                0,
+                                event).sendToTarget()
+        );
         mBinderService = new BinderService(this);
     }
 
     private static final class BinderService extends IBackgroundInstallControlService.Stub {
         final BackgroundInstallControlService mService;
 
-        BinderService(BackgroundInstallControlService service)  {
+        BinderService(BackgroundInstallControlService service) {
             mService = service;
         }
 
         @Override
         public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
                 @PackageManager.PackageInfoFlagsBits long flags, int userId) {
-            ParceledListSlice<PackageInfo> packages;
-            try {
-                packages = mService.mIPackageManager.getInstalledPackages(flags, userId);
-            } catch (RemoteException e) {
-                throw new IllegalStateException("Package manager not available", e);
-            }
-
-            // TODO(b/244216300): to enable the test the usage by BinaryTransparencyService,
-            // we currently comment out the actual implementation.
-            // The fake implementation is just to filter out the first app of the list.
-            // for (int i = 0, size = packages.getList().size(); i < size; i++) {
-            //     String packageName = packages.getList().get(i).packageName;
-            //     if (!mBackgroundInstalledPackages.contains(userId, packageName) {
-            //         packages.getList().remove(i);
-            //     }
-            // }
-            if (packages.getList().size() > 0) {
-                packages.getList().remove(0);
-            }
-            return packages;
+            return mService.getBackgroundInstalledPackages(flags, userId);
         }
     }
 
-    /**
-     * Called when the system service should publish a binder service using
-     * {@link #publishBinderService(String, IBinder).}
-     */
+    @VisibleForTesting
+    ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
+            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
+        ParceledListSlice<PackageInfo> packages;
+        try {
+            packages = mIPackageManager.getInstalledPackages(flags, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        initBackgroundInstalledPackages();
+
+        ListIterator<PackageInfo> iter = packages.getList().listIterator();
+        while (iter.hasNext()) {
+            String packageName = iter.next().packageName;
+            if (!mBackgroundInstalledPackages.contains(userId, packageName)) {
+                iter.remove();
+            }
+        }
+
+        return packages;
+    }
+
+    private static class EventHandler extends Handler {
+        private final BackgroundInstallControlService mService;
+
+        EventHandler(Looper looper, BackgroundInstallControlService service) {
+            super(looper);
+            mService = service;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_USAGE_EVENT_RECEIVED: {
+                    mService.handleUsageEvent((UsageEvents.Event) msg.obj, msg.arg1 /* userId */);
+                    break;
+                }
+                case MSG_PACKAGE_ADDED: {
+                    mService.handlePackageAdd((String) msg.obj, msg.arg1 /* userId */);
+                    break;
+                }
+                case MSG_PACKAGE_REMOVED: {
+                    mService.handlePackageRemove((String) msg.obj, msg.arg1 /* userId */);
+                    break;
+                }
+                default:
+                    Slog.w(TAG, "Unknown message: " + msg.what);
+            }
+        }
+    }
+
+    void handlePackageAdd(String packageName, int userId) {
+        InstallSourceInfo installSourceInfo = null;
+        try {
+            installSourceInfo = mIPackageManager.getInstallSourceInfo(packageName);
+        } catch (RemoteException e) {
+            // Failed to talk to PackageManagerService Should never happen!
+            throw e.rethrowFromSystemServer();
+        }
+        String installerPackageName =
+                installSourceInfo == null ? null : installSourceInfo.getInstallingPackageName();
+        if (installerPackageName == null) {
+            Slog.w(TAG, "fails to get installerPackageName for " + packageName);
+            return;
+        }
+
+        ApplicationInfo appInfo = null;
+        try {
+            appInfo = mIPackageManager.getApplicationInfo(packageName,
+                    0, userId);
+        } catch (RemoteException e) {
+            // Failed to talk to PackageManagerService Should never happen!
+            throw e.rethrowFromSystemServer();
+        }
+
+        if (appInfo == null) {
+            Slog.w(TAG, "fails to get appInfo for " + packageName);
+            return;
+        }
+
+        // convert up-time to current time.
+        final long installTimestamp = System.currentTimeMillis()
+                - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
+
+        if (wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
+            return;
+        }
+
+        initBackgroundInstalledPackages();
+        mBackgroundInstalledPackages.add(userId, packageName);
+        writeBackgroundInstalledPackagesToDisk();
+    }
+
+    private boolean wasForegroundInstallation(String installerPackageName,
+            int userId, long installTimestamp) {
+        TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
+                mInstallerForegroundTimeFrames.get(userId, installerPackageName);
+
+        // The installer never run in foreground.
+        if (foregroundTimeFrames == null) {
+            return false;
+        }
+
+        for (var foregroundTimeFrame : foregroundTimeFrames) {
+            // the foreground time frame starts later than the installation.
+            // so the installation is outside the foreground time frame.
+            if (foregroundTimeFrame.startTimeStampMillis > installTimestamp) {
+                continue;
+            }
+
+            // the foreground time frame is not over yet.
+            // the installation is inside the foreground time frame.
+            if (!foregroundTimeFrame.isDone()) {
+                return true;
+            }
+
+            // the foreground time frame ends later than the installation.
+            // the installation is inside the foreground time frame.
+            if (installTimestamp <= foregroundTimeFrame.endTimeStampMillis) {
+                return true;
+            }
+        }
+
+        // the installation is not inside any of foreground time frames.
+        // so it is not a foreground installation.
+        return false;
+    }
+
+    void handlePackageRemove(String packageName, int userId) {
+        initBackgroundInstalledPackages();
+        mBackgroundInstalledPackages.remove(userId, packageName);
+        writeBackgroundInstalledPackagesToDisk();
+    }
+
+    void handleUsageEvent(UsageEvents.Event event, int userId) {
+        if (event.mEventType != UsageEvents.Event.ACTIVITY_RESUMED
+                && event.mEventType != UsageEvents.Event.ACTIVITY_PAUSED
+                && event.mEventType != UsageEvents.Event.ACTIVITY_STOPPED) {
+            return;
+        }
+
+        if (!isInstaller(event.mPackage, userId)) {
+            return;
+        }
+
+        if (!mInstallerForegroundTimeFrames.contains(userId, event.mPackage)) {
+            mInstallerForegroundTimeFrames.add(userId, event.mPackage, new TreeSet<>());
+        }
+
+        TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
+                mInstallerForegroundTimeFrames.get(userId, event.mPackage);
+
+        if ((foregroundTimeFrames.size() == 0) || foregroundTimeFrames.last().isDone()) {
+            // ignore the other events if there is no open ForegroundTimeFrame.
+            if (event.mEventType != UsageEvents.Event.ACTIVITY_RESUMED) {
+                return;
+            }
+            foregroundTimeFrames.add(new ForegroundTimeFrame(event.mTimeStamp));
+        }
+
+        foregroundTimeFrames.last().addEvent(event);
+
+        if (foregroundTimeFrames.size() > MAX_FOREGROUND_TIME_FRAMES_SIZE) {
+            foregroundTimeFrames.pollFirst();
+        }
+    }
+
+    @VisibleForTesting
+    void writeBackgroundInstalledPackagesToDisk() {
+        AtomicFile atomicFile = new AtomicFile(mDiskFile);
+        FileOutputStream fileOutputStream;
+        try {
+            fileOutputStream = atomicFile.startWrite();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to start write to states protobuf.", e);
+            return;
+        }
+
+        try {
+            ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
+            for (int i = 0; i < mBackgroundInstalledPackages.size(); i++) {
+                int userId = mBackgroundInstalledPackages.keyAt(i);
+                for (String packageName : mBackgroundInstalledPackages.get(userId)) {
+                    long token = protoOutputStream.start(
+                            BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                    protoOutputStream.write(
+                            BackgroundInstalledPackageProto.PACKAGE_NAME, packageName);
+                    protoOutputStream.write(
+                            BackgroundInstalledPackageProto.USER_ID, userId + 1);
+                    protoOutputStream.end(token);
+                }
+            }
+            protoOutputStream.flush();
+            atomicFile.finishWrite(fileOutputStream);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to finish write to states protobuf.", e);
+            atomicFile.failWrite(fileOutputStream);
+        }
+    }
+
+    @VisibleForTesting
+    void initBackgroundInstalledPackages() {
+        if (mBackgroundInstalledPackages != null) {
+            return;
+        }
+
+        mBackgroundInstalledPackages = new SparseSetArray<>();
+
+        if (!mDiskFile.exists()) {
+            return;
+        }
+
+        AtomicFile atomicFile = new AtomicFile(mDiskFile);
+        try (FileInputStream fileInputStream = atomicFile.openRead()) {
+            ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
+
+            while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                if (protoInputStream.getFieldNumber()
+                        != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
+                    continue;
+                }
+                long token = protoInputStream.start(
+                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                String packageName = null;
+                int userId = UserHandle.USER_NULL;
+                while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    switch (protoInputStream.getFieldNumber()) {
+                        case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
+                            packageName = protoInputStream.readString(
+                                    BackgroundInstalledPackageProto.PACKAGE_NAME);
+                            break;
+                        case (int) BackgroundInstalledPackageProto.USER_ID:
+                            userId = protoInputStream.readInt(
+                                    BackgroundInstalledPackageProto.USER_ID) - 1;
+                            break;
+                        default:
+                            Slog.w(TAG, "Undefined field in proto: "
+                                    + protoInputStream.getFieldNumber());
+                    }
+                }
+                protoInputStream.end(token);
+                if (packageName != null && userId != UserHandle.USER_NULL) {
+                    mBackgroundInstalledPackages.add(userId, packageName);
+                } else {
+                    Slog.w(TAG, "Fails to get packageName or UserId from proto file");
+                }
+            }
+        } catch (IOException e) {
+            Slog.w(TAG, "Error reading state from the disk", e);
+        }
+    }
+
+    @VisibleForTesting
+    SparseSetArray<String> getBackgroundInstalledPackages() {
+        return mBackgroundInstalledPackages;
+    }
+
+    @VisibleForTesting
+    SparseArrayMap<String, TreeSet<ForegroundTimeFrame>> getInstallerForegroundTimeFrames() {
+        return mInstallerForegroundTimeFrames;
+    }
+
+    private boolean isInstaller(String pkgName, int userId) {
+        if (mInstallerForegroundTimeFrames.contains(userId, pkgName)) {
+            return true;
+        }
+        return mPermissionManager.checkPermission(pkgName,
+                android.Manifest.permission.INSTALL_PACKAGES,
+                userId) == PackageManager.PERMISSION_GRANTED;
+    }
+
     @Override
     public void onStart() {
-        publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
+        onStart(/* isForTesting= */ false);
+    }
+
+    @VisibleForTesting
+    void onStart(boolean isForTesting) {
+        if (!isForTesting) {
+            publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
+        }
+
+        mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() {
+            @Override
+            public void onPackageAdded(String packageName, int uid) {
+                final int userId = UserHandle.getUserId(uid);
+                mHandler.obtainMessage(MSG_PACKAGE_ADDED,
+                        userId, 0, packageName).sendToTarget();
+            }
+
+            @Override
+            public void onPackageRemoved(String packageName, int uid) {
+                final int userId = UserHandle.getUserId(uid);
+                mHandler.obtainMessage(MSG_PACKAGE_REMOVED,
+                        userId, 0, packageName).sendToTarget();
+            }
+        });
+    }
+
+    // The foreground time frame (ForegroundTimeFrame) represents the period
+    // when a package's activities continuously occupy the foreground.
+    // Each ForegroundTimeFrame starts with an ACTIVITY_RESUMED event,
+    // and then ends with an ACTIVITY_PAUSED or ACTIVITY_STOPPED event.
+    // The startTimeStampMillis stores the timestamp of the ACTIVITY_RESUMED event.
+    // The endTimeStampMillis stores the timestamp of the ACTIVITY_PAUSED or ACTIVITY_STOPPED event
+    // that wraps up the ForegroundTimeFrame.
+    // The activities are designed to handle the edge case in which a package's one activity
+    // seamlessly replace another activity of the same package. Thus, we count these activities
+    // together as a ForegroundTimeFrame. For this scenario, only when all the activities terminate
+    // shall consider the completion of the ForegroundTimeFrame.
+    static final class ForegroundTimeFrame implements Comparable<ForegroundTimeFrame> {
+        public final long startTimeStampMillis;
+        public long endTimeStampMillis;
+        public final Set<Integer> activities;
+
+        public int compareTo(ForegroundTimeFrame o) {
+            int comp = Long.compare(startTimeStampMillis, o.startTimeStampMillis);
+            if (comp != 0) return comp;
+
+            return Integer.compare(hashCode(), o.hashCode());
+        }
+
+        ForegroundTimeFrame(long startTimeStampMillis) {
+            this.startTimeStampMillis = startTimeStampMillis;
+            endTimeStampMillis = 0;
+            activities = new ArraySet<>();
+        }
+
+        public boolean isDone() {
+            return endTimeStampMillis != 0;
+        }
+
+        public void addEvent(UsageEvents.Event event) {
+            switch (event.mEventType) {
+                case UsageEvents.Event.ACTIVITY_RESUMED:
+                    activities.add(event.mInstanceId);
+                    break;
+                case UsageEvents.Event.ACTIVITY_PAUSED:
+                case UsageEvents.Event.ACTIVITY_STOPPED:
+                    if (activities.contains(event.mInstanceId)) {
+                        activities.remove(event.mInstanceId);
+                        if (activities.size() == 0) {
+                            endTimeStampMillis = event.mTimeStamp;
+                        }
+                    }
+                    break;
+                default:
+            }
+        }
     }
 
     /**
@@ -108,6 +487,16 @@
         Context getContext();
 
         IPackageManager getIPackageManager();
+
+        PackageManagerInternal getPackageManagerInternal();
+
+        UsageStatsManagerInternal getUsageStatsManagerInternal();
+
+        PermissionManagerServiceInternal getPermissionManager();
+
+        Looper getLooper();
+
+        File getDiskFile();
     }
 
     private static final class InjectorImpl implements Injector {
@@ -126,5 +515,36 @@
         public IPackageManager getIPackageManager() {
             return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
         }
+
+        @Override
+        public PackageManagerInternal getPackageManagerInternal() {
+            return LocalServices.getService(PackageManagerInternal.class);
+        }
+
+        @Override
+        public UsageStatsManagerInternal getUsageStatsManagerInternal() {
+            return LocalServices.getService(UsageStatsManagerInternal.class);
+        }
+
+        @Override
+        public PermissionManagerServiceInternal getPermissionManager() {
+            return LocalServices.getService(PermissionManagerServiceInternal.class);
+        }
+
+        @Override
+        public Looper getLooper() {
+            ServiceThread serviceThread = new ServiceThread(TAG,
+                    android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+            serviceThread.start();
+            return serviceThread.getLooper();
+
+        }
+
+        @Override
+        public File getDiskFile() {
+            File dir = new File(Environment.getDataSystemDirectory(), DISK_DIR_NAME);
+            File file = new File(dir, DISK_FILE_NAME);
+            return file;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/CloneProfileResolver.java b/services/core/java/com/android/server/pm/CloneProfileResolver.java
index d3113e1..73036f1 100644
--- a/services/core/java/com/android/server/pm/CloneProfileResolver.java
+++ b/services/core/java/com/android/server/pm/CloneProfileResolver.java
@@ -18,6 +18,8 @@
 
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.provider.DeviceConfig;
 
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.resolution.ComponentResolverApi;
@@ -32,6 +34,30 @@
  */
 public class CloneProfileResolver extends CrossProfileResolver {
 
+    /**
+     * Feature flag to allow/restrict intent redirection from/to clone profile.
+     * Default value is false,this is to ensure that framework is not impacted by intent redirection
+     * till we are ready to launch.
+     * From Android U onwards, this would be set to true and eventually removed.
+     * @hide
+     */
+    private static final String FLAG_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE =
+            "allow_intent_redirection_for_clone_profile";
+
+    /**
+     * Returns true if intent redirection for clone profile feature flag is set
+     * @return value of flag allow_intent_redirection_for_clone_profile
+     */
+    public static boolean isIntentRedirectionForCloneProfileAllowed() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_APP_CLONING,
+                    FLAG_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE, false /* defaultValue */);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public CloneProfileResolver(ComponentResolverApi componentResolver,
             UserManagerService userManagerService) {
         super(componentResolver, userManagerService);
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 5b8ee2b..60621a0 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -338,6 +338,9 @@
     @NonNull
     ArrayMap<String, ? extends PackageStateInternal> getPackageStates();
 
+    @NonNull
+    ArrayMap<String, ? extends PackageStateInternal> getDisabledSystemPackageStates();
+
     @Nullable
     String getRenamedPackage(@NonNull String packageName);
 
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 45b633f..b8fba51 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -185,6 +185,10 @@
             return mSettings.getPackagesLocked().untrackedStorage();
         }
 
+        public ArrayMap<String, ? extends PackageStateInternal> getDisabledSystemPackages() {
+            return mSettings.getDisabledSystemPackagesLocked().untrackedStorage();
+        }
+
         public Settings(@NonNull com.android.server.pm.Settings settings) {
             mSettings = settings;
         }
@@ -3459,6 +3463,12 @@
         return mSettings.getPackages();
     }
 
+    @NonNull
+    @Override
+    public ArrayMap<String, ? extends PackageStateInternal> getDisabledSystemPackageStates() {
+        return mSettings.getDisabledSystemPackages();
+    }
+
     @Nullable
     @Override
     public String getRenamedPackage(@NonNull String packageName) {
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilterHelper.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilterHelper.java
new file mode 100644
index 0000000..e682586
--- /dev/null
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilterHelper.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 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.pm;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
+import android.util.ArraySet;
+
+/**
+ * Helper class to manage {@link com.android.server.pm.CrossProfileIntentFilter}s.
+ */
+public class CrossProfileIntentFilterHelper {
+    private Context mContext;
+    private UserManagerInternal mUserManagerInternal;
+    private Settings mSettings;
+    private UserManagerService mUserManagerService;
+    private PackageManagerTracedLock mLock;
+
+    public CrossProfileIntentFilterHelper(Settings settings, UserManagerService userManagerService,
+            PackageManagerTracedLock lock, UserManagerInternal userManagerInternal,
+            Context context) {
+        mSettings = settings;
+        mUserManagerService = userManagerService;
+        mLock = lock;
+        mContext = context;
+        mUserManagerInternal = userManagerInternal;
+    }
+
+    /**
+     * For users that have
+     * {@link android.content.pm.UserProperties#getUpdateCrossProfileIntentFiltersOnOTA} set, this
+     * task will update default {@link com.android.server.pm.CrossProfileIntentFilter} between that
+     * user and its parent. This will only update CrossProfileIntentFilters set by system package.
+     * The new default are configured in {@link UserTypeDetails}.
+     */
+    public void updateDefaultCrossProfileIntentFilter() {
+        for (UserInfo userInfo : mUserManagerInternal.getUsers(false)) {
+
+            UserProperties currentUserProperties = mUserManagerInternal
+                    .getUserProperties(userInfo.id);
+
+            if (currentUserProperties.getUpdateCrossProfileIntentFiltersOnOTA()) {
+                int parentUserId = mUserManagerInternal.getProfileParentId(userInfo.id);
+                if (parentUserId != userInfo.id) {
+                    clearCrossProfileIntentFilters(userInfo.id,
+                            mContext.getOpPackageName(), parentUserId);
+                    clearCrossProfileIntentFilters(parentUserId,
+                            mContext.getOpPackageName(),  userInfo.id);
+
+                    mUserManagerInternal.setDefaultCrossProfileIntentFilters(parentUserId,
+                            userInfo.id);
+                }
+            }
+        }
+    }
+
+    /**
+     * Clear {@link CrossProfileIntentFilter}s configured on source user by ownerPackage
+     * targeting the targetUserId. If targetUserId is null then it will clear
+     * {@link CrossProfileIntentFilter} for any target user.
+     * @param sourceUserId source user for whom CrossProfileIntentFilter would be configured
+     * @param ownerPackage package who would have configured CrossProfileIntentFilter
+     * @param targetUserId user id for which CrossProfileIntentFilter will be removed.
+     *                     This can be null in which case it will clear for any target user.
+     */
+    public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage,
+            Integer targetUserId) {
+        synchronized (mLock) {
+            CrossProfileIntentResolver resolver = mSettings
+                    .editCrossProfileIntentResolverLPw(sourceUserId);
+            ArraySet<CrossProfileIntentFilter> set =
+                    new ArraySet<>(resolver.filterSet());
+            for (CrossProfileIntentFilter filter : set) {
+                //Only remove if calling user is allowed based on access control of
+                // {@link CrossProfileIntentFilter}
+                if (filter.getOwnerPackage().equals(ownerPackage)
+                        && (targetUserId == null || filter.mTargetUserId == targetUserId)
+                        && mUserManagerService.isCrossProfileIntentFilterAccessible(sourceUserId,
+                        filter.mTargetUserId, /* addCrossProfileIntentFilter */ false)) {
+                    resolver.removeFilter(filter);
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
index 5d97cb7..1e0822d 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
@@ -32,7 +32,6 @@
 import android.content.pm.UserInfo;
 import android.os.Process;
 import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -250,8 +249,7 @@
          * SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE is enabled
          */
         if (sourceUserInfo.isCloneProfile() || targetUserInfo.isCloneProfile()) {
-            if (FeatureFlagUtils.isEnabled(mContext,
-                    FeatureFlagUtils.SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE)) {
+            if (CloneProfileResolver.isIntentRedirectionForCloneProfileAllowed()) {
                 return new CloneProfileResolver(computer.getComponentResolver(),
                         mUserManager);
             } else {
diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
index 247ac90..7cb096d 100644
--- a/services/core/java/com/android/server/pm/GentleUpdateHelper.java
+++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
@@ -16,7 +16,12 @@
 
 package com.android.server.pm;
 
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+
 import android.annotation.WorkerThread;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
 import android.app.ActivityThread;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
@@ -28,6 +33,9 @@
 import android.content.pm.PackageInstaller.InstallConstraintsResult;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
 import android.util.Slog;
 
 import java.util.ArrayDeque;
@@ -73,12 +81,24 @@
         public final List<String> packageNames;
         public final InstallConstraints constraints;
         public final CompletableFuture<InstallConstraintsResult> future;
+        private final long mFinishTime;
+
+        /**
+         * Note {@code timeoutMillis} will be clamped to 0 ~ one week to avoid overflow.
+         */
         PendingInstallConstraintsCheck(List<String> packageNames,
                 InstallConstraints constraints,
-                CompletableFuture<InstallConstraintsResult> future) {
+                CompletableFuture<InstallConstraintsResult> future,
+                long timeoutMillis) {
             this.packageNames = packageNames;
             this.constraints = constraints;
             this.future = future;
+
+            timeoutMillis = Math.max(0, Math.min(DateUtils.WEEK_IN_MILLIS, timeoutMillis));
+            mFinishTime = SystemClock.elapsedRealtime() + timeoutMillis;
+        }
+        public boolean isTimedOut() {
+            return SystemClock.elapsedRealtime() >= mFinishTime;
         }
     }
 
@@ -95,15 +115,31 @@
         mAppStateHelper = appStateHelper;
     }
 
+    void systemReady() {
+        var am = mContext.getSystemService(ActivityManager.class);
+        // Monitor top-visible apps
+        am.addOnUidImportanceListener(this::onUidImportance, IMPORTANCE_FOREGROUND);
+        // Monitor foreground apps
+        am.addOnUidImportanceListener(this::onUidImportance, IMPORTANCE_FOREGROUND_SERVICE);
+    }
+
     /**
      * Checks if install constraints are satisfied for the given packages.
      */
     CompletableFuture<InstallConstraintsResult> checkInstallConstraints(
-            List<String> packageNames, InstallConstraints constraints) {
+            List<String> packageNames, InstallConstraints constraints,
+            long timeoutMillis) {
         var future = new CompletableFuture<InstallConstraintsResult>();
         mHandler.post(() -> {
+            long clampedTimeoutMillis = timeoutMillis;
+            if (constraints.isRequireDeviceIdle()) {
+                // Device-idle-constraint is required. Clamp the timeout to ensure
+                // timeout-check happens after device-idle-check.
+                clampedTimeoutMillis = Math.max(timeoutMillis, PENDING_CHECK_MILLIS);
+            }
+
             var pendingCheck = new PendingInstallConstraintsCheck(
-                    packageNames, constraints, future);
+                    packageNames, constraints, future, clampedTimeoutMillis);
             if (constraints.isRequireDeviceIdle()) {
                 mPendingChecks.add(pendingCheck);
                 // JobScheduler doesn't provide queries about whether the device is idle.
@@ -113,8 +149,17 @@
                 scheduleIdleJob();
                 mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
                         PENDING_CHECK_MILLIS);
-            } else {
-                processPendingCheck(pendingCheck, false);
+            } else if (!processPendingCheck(pendingCheck, false)) {
+                // Not resolved. Schedule a job for re-check
+                mPendingChecks.add(pendingCheck);
+                scheduleIdleJob();
+            }
+
+            if (!future.isDone()) {
+                // Ensure the pending check is resolved after timeout, no matter constraints
+                // satisfied or not.
+                mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
+                        clampedTimeoutMillis);
             }
         });
         return future;
@@ -143,29 +188,77 @@
     }
 
     @WorkerThread
-    private void processPendingCheck(PendingInstallConstraintsCheck pendingCheck, boolean isIdle) {
+    private boolean areConstraintsSatisfied(List<String> packageNames,
+            InstallConstraints constraints, boolean isIdle) {
+        return (!constraints.isRequireDeviceIdle() || isIdle)
+                && (!constraints.isRequireAppNotForeground()
+                || !mAppStateHelper.hasForegroundApp(packageNames))
+                && (!constraints.isRequireAppNotInteracting()
+                || !mAppStateHelper.hasInteractingApp(packageNames))
+                && (!constraints.isRequireAppNotTopVisible()
+                || !mAppStateHelper.hasTopVisibleApp(packageNames))
+                && (!constraints.isRequireNotInCall()
+                || !mAppStateHelper.isInCall());
+    }
+
+    @WorkerThread
+    private boolean processPendingCheck(
+            PendingInstallConstraintsCheck pendingCheck, boolean isIdle) {
         var future = pendingCheck.future;
         if (future.isDone()) {
-            return;
+            return true;
         }
         var constraints = pendingCheck.constraints;
         var packageNames = mAppStateHelper.getDependencyPackages(pendingCheck.packageNames);
-        var constraintsSatisfied = (!constraints.isRequireDeviceIdle() || isIdle)
-                && (!constraints.isRequireAppNotForeground()
-                        || !mAppStateHelper.hasForegroundApp(packageNames))
-                && (!constraints.isRequireAppNotInteracting()
-                        || !mAppStateHelper.hasInteractingApp(packageNames))
-                && (!constraints.isRequireAppNotTopVisible()
-                        || !mAppStateHelper.hasTopVisibleApp(packageNames))
-                && (!constraints.isRequireNotInCall()
-                        || !mAppStateHelper.isInCall());
-        future.complete(new InstallConstraintsResult((constraintsSatisfied)));
+        var satisfied = areConstraintsSatisfied(packageNames, constraints, isIdle);
+        if (satisfied || pendingCheck.isTimedOut()) {
+            future.complete(new InstallConstraintsResult((satisfied)));
+            return true;
+        }
+        return false;
     }
 
     @WorkerThread
     private void processPendingChecksInIdle() {
-        while (!mPendingChecks.isEmpty()) {
-            processPendingCheck(mPendingChecks.remove(), true);
+        int size = mPendingChecks.size();
+        for (int i = 0; i < size; ++i) {
+            var pendingCheck = mPendingChecks.remove();
+            if (!processPendingCheck(pendingCheck, true)) {
+                // Not resolved. Put it back in the queue.
+                mPendingChecks.add(pendingCheck);
+            }
+        }
+        if (!mPendingChecks.isEmpty()) {
+            // Schedule a job for remaining pending checks
+            scheduleIdleJob();
+        }
+    }
+
+    @WorkerThread
+    private void onUidImportance(String packageName,
+            @RunningAppProcessInfo.Importance int importance) {
+        int size = mPendingChecks.size();
+        for (int i = 0; i < size; ++i) {
+            var pendingCheck = mPendingChecks.remove();
+            var dependencyPackages =
+                    mAppStateHelper.getDependencyPackages(pendingCheck.packageNames);
+            if (!dependencyPackages.contains(packageName)
+                    || !processPendingCheck(pendingCheck, false)) {
+                mPendingChecks.add(pendingCheck);
+            }
+        }
+        if (!mPendingChecks.isEmpty()) {
+            // Schedule a job for remaining pending checks
+            scheduleIdleJob();
+        }
+    }
+
+    private void onUidImportance(int uid, @RunningAppProcessInfo.Importance int importance) {
+        var pm = ActivityThread.getPackageManager();
+        try {
+            var packageName = pm.getNameForUid(uid);
+            mHandler.post(() -> onUidImportance(packageName, importance));
+        } catch (RemoteException ignore) {
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 12b5ab8..823bc71 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -39,6 +39,8 @@
 import android.os.Environment;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.system.ErrnoException;
+import android.system.Os;
 import android.util.ArrayMap;
 import android.util.EventLog;
 import android.util.Slog;
@@ -55,6 +57,7 @@
 import com.android.server.utils.WatchedArrayMap;
 
 import java.io.File;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
@@ -227,6 +230,24 @@
     }
 
     /**
+     * Fix up the previously-installed app directory mode - they can't be readable by non-system
+     * users to prevent them from listing the dir to discover installed package names.
+     */
+    void fixInstalledAppDirMode() {
+        try (var files = Files.newDirectoryStream(mPm.getAppInstallDir().toPath())) {
+            files.forEach(dir -> {
+                try {
+                    Os.chmod(dir.toString(), 0771);
+                } catch (ErrnoException e) {
+                    Slog.w(TAG, "Failed to fix an installed app dir mode", e);
+                }
+            });
+        } catch (Exception e) {
+            Slog.w(TAG, "Failed to walk the app install directory to fix the modes", e);
+        }
+    }
+
+    /**
      * Install apps/updates from data dir and fix system apps that are affected.
      */
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
@@ -234,6 +255,11 @@
             long startTime) {
         EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                 SystemClock.uptimeMillis());
+
+        if ((mScanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) == SCAN_FIRST_BOOT_OR_UPGRADE) {
+            fixInstalledAppDirMode();
+        }
+
         scanDirTracedLI(mPm.getAppInstallDir(), 0,
                 mScanFlags | SCAN_REQUIRE_KNOWN, packageParser, mExecutorService);
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 33fe4c5..911cfbd 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1720,7 +1720,7 @@
         final boolean onIncremental = mPm.mIncrementalManager != null
                 && isIncrementalPath(beforeCodeFile.getAbsolutePath());
         try {
-            makeDirRecursive(afterCodeFile.getParentFile(), 0775);
+            makeDirRecursive(afterCodeFile.getParentFile(), 0771);
             if (onIncremental) {
                 // Just link files here. The stage dir will be removed when the installation
                 // session is completed.
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 4803c5e..9c60795 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -45,6 +45,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.InstallConstraints;
+import android.content.pm.PackageInstaller.InstallConstraintsResult;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageItemInfo;
@@ -91,7 +92,6 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.ImageUtils;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.IoThread;
@@ -123,6 +123,7 @@
 import java.util.Random;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.concurrent.CompletableFuture;
 import java.util.function.IntPredicate;
 import java.util.function.Supplier;
 
@@ -153,6 +154,8 @@
     private static final long MAX_HISTORICAL_SESSIONS = 1048576;
     /** Destroy sessions older than this on storage free request */
     private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS;
+    /** Maximum time to wait for install constraints to be satisfied */
+    private static final long MAX_INSTALL_CONSTRAINTS_TIMEOUT_MILLIS = DateUtils.WEEK_IN_MILLIS;
 
     /** Threshold of historical sessions size */
     private static final int HISTORICAL_SESSIONS_THRESHOLD = 500;
@@ -300,6 +303,7 @@
     public void systemReady() {
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mStagingManager.systemReady();
+        mGentleUpdateHelper.systemReady();
 
         synchronized (mSessions) {
             readSessionsLocked();
@@ -1248,12 +1252,11 @@
         }
     }
 
-    @Override
-    public void checkInstallConstraints(String installerPackageName, List<String> packageNames,
-            InstallConstraints constraints, RemoteCallback callback) {
-        Preconditions.checkArgument(packageNames != null);
-        Preconditions.checkArgument(constraints != null);
-        Preconditions.checkArgument(callback != null);
+    private CompletableFuture<InstallConstraintsResult> checkInstallConstraintsInternal(
+            String installerPackageName, List<String> packageNames,
+            InstallConstraints constraints, long timeoutMillis) {
+        Objects.requireNonNull(packageNames);
+        Objects.requireNonNull(constraints);
 
         final var snapshot = mPm.snapshotComputer();
         final int callingUid = Binder.getCallingUid();
@@ -1267,7 +1270,16 @@
             }
         }
 
-        var future = mGentleUpdateHelper.checkInstallConstraints(packageNames, constraints);
+        return mGentleUpdateHelper.checkInstallConstraints(
+                packageNames, constraints, timeoutMillis);
+    }
+
+    @Override
+    public void checkInstallConstraints(String installerPackageName, List<String> packageNames,
+            InstallConstraints constraints, RemoteCallback callback) {
+        Objects.requireNonNull(callback);
+        var future = checkInstallConstraintsInternal(
+                installerPackageName, packageNames, constraints, /*timeoutMillis=*/0);
         future.thenAccept(result -> {
             var b = new Bundle();
             b.putParcelable("result", result);
@@ -1276,6 +1288,27 @@
     }
 
     @Override
+    public void waitForInstallConstraints(String installerPackageName, List<String> packageNames,
+            InstallConstraints constraints, IntentSender callback, long timeoutMillis) {
+        Objects.requireNonNull(callback);
+        if (timeoutMillis < 0 || timeoutMillis > MAX_INSTALL_CONSTRAINTS_TIMEOUT_MILLIS) {
+            throw new IllegalArgumentException("Invalid timeoutMillis=" + timeoutMillis);
+        }
+        var future = checkInstallConstraintsInternal(
+                installerPackageName, packageNames, constraints, timeoutMillis);
+        future.thenAccept(result -> {
+            final var intent = new Intent();
+            intent.putExtra(Intent.EXTRA_PACKAGES, packageNames.toArray(new String[0]));
+            intent.putExtra(PackageInstaller.EXTRA_INSTALL_CONSTRAINTS, constraints);
+            intent.putExtra(PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT, result);
+            try {
+                callback.sendIntent(mContext, 0, intent, null, null);
+            } catch (SendIntentException ignore) {
+            }
+        });
+    }
+
+    @Override
     public void registerCallback(IPackageInstallerCallback callback, int userId) {
         final Computer snapshot = mPm.snapshotComputer();
         snapshot.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3983acf..9aaf685 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -125,7 +125,7 @@
 import android.os.incremental.PerUidReadTimeouts;
 import android.os.incremental.StorageHealthCheckParams;
 import android.os.storage.StorageManager;
-import android.provider.Settings.Secure;
+import android.provider.Settings.Global;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.system.ErrnoException;
 import android.system.Int64Ref;
@@ -1842,7 +1842,8 @@
             assertNoWriteFileTransfersOpenLocked();
 
             final boolean isSecureFrpEnabled =
-                    (Secure.getInt(mContext.getContentResolver(), Secure.SECURE_FRP_MODE, 0) == 1);
+                    Global.getInt(mContext.getContentResolver(), Global.SECURE_FRP_MODE, 0) == 1;
+
             if (isSecureFrpEnabled
                     && !isSecureFrpInstallAllowed(mContext, Binder.getCallingUid())) {
                 throw new SecurityException("Can't install packages while in secure FRP");
@@ -2785,6 +2786,7 @@
         // Default to require only if existing base apk has fs-verity signature.
         mVerityFoundForApks = PackageManagerServiceUtils.isApkVerityEnabled()
                 && params.mode == SessionParams.MODE_INHERIT_EXISTING
+                && VerityUtils.hasFsverity(pkgInfo.applicationInfo.getBaseCodePath())
                 && (new File(VerityUtils.getFsveritySignatureFilePath(
                         pkgInfo.applicationInfo.getBaseCodePath()))).exists();
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index d163d3d..935c4dd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -149,6 +149,16 @@
         @NonNull
         Map<String, PackageState> getPackageStates();
 
+        /**
+         * Returns a map of all disabled system {@link PackageState PackageStates} on the device.
+         *
+         * @return Mapping of package name to disabled system {@link PackageState}.
+         *
+         * @hide Pending API
+         */
+        @NonNull
+        Map<String, PackageState> getDisabledSystemPackageStates();
+
         @Override
         void close();
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4b7cefa..759ec67 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1539,7 +1539,10 @@
                 (i, pm) -> new BackgroundDexOptService(i.getContext(), i.getDexManager(), pm),
                 (i, pm) -> IBackupManager.Stub.asInterface(ServiceManager.getService(
                         Context.BACKUP_SERVICE)),
-                (i, pm) -> new SharedLibrariesImpl(pm, i));
+                (i, pm) -> new SharedLibrariesImpl(pm, i),
+                (i, pm) -> new CrossProfileIntentFilterHelper(i.getSettings(),
+                        i.getUserManagerService(), i.getLock(), i.getUserManagerInternal(),
+                        context));
 
         if (Build.VERSION.SDK_INT <= 0) {
             Slog.w(TAG, "**** ro.build.version.sdk not set!");
@@ -2031,13 +2034,21 @@
             final WatchedArrayMap<String, PackageSetting> packageSettings =
                 mSettings.getPackagesLocked();
 
-            // Save the names of pre-existing packages prior to scanning, so we can determine
-            // which system packages are completely new due to an upgrade.
             if (isDeviceUpgrading()) {
+                // Save the names of pre-existing packages prior to scanning, so we can determine
+                // which system packages are completely new due to an upgrade.
                 mExistingPackages = new ArraySet<>(packageSettings.size());
                 for (PackageSetting ps : packageSettings.values()) {
                     mExistingPackages.add(ps.getPackageName());
                 }
+
+                // Triggering {@link com.android.server.pm.crossprofile.
+                // CrossProfileIntentFilterHelper.updateDefaultCrossProfileIntentFilter} to update
+                // {@link  CrossProfileIntentFilter}s between eligible users and their parent
+                t.traceBegin("cross profile intent filter update");
+                mInjector.getCrossProfileIntentFilterHelper()
+                        .updateDefaultCrossProfileIntentFilter();
+                t.traceEnd();
             }
 
             mCacheDir = PackageManagerServiceUtils.preparePackageParserCache(
@@ -3257,6 +3268,7 @@
         return isPackageDeviceAdmin(packageName, UserHandle.USER_ALL);
     }
 
+    // TODO(b/261957226): centralise this logic in DPM
     boolean isPackageDeviceAdmin(String packageName, int userId) {
         final IDevicePolicyManager dpm = getDevicePolicyManager();
         try {
@@ -3283,6 +3295,9 @@
                     if (dpm.packageHasActiveAdmins(packageName, users[i])) {
                         return true;
                     }
+                    if (isDeviceManagementRoleHolder(packageName, users[i])) {
+                        return true;
+                    }
                 }
             }
         } catch (RemoteException e) {
@@ -3290,6 +3305,24 @@
         return false;
     }
 
+    private boolean isDeviceManagementRoleHolder(String packageName, int userId) {
+        return Objects.equals(packageName, getDevicePolicyManagementRoleHolderPackageName(userId));
+    }
+
+    @Nullable
+    private String getDevicePolicyManagementRoleHolderPackageName(int userId) {
+        return Binder.withCleanCallingIdentity(() -> {
+            RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+            List<String> roleHolders =
+                    roleManager.getRoleHoldersAsUser(
+                            RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, UserHandle.of(userId));
+            if (roleHolders.isEmpty()) {
+                return null;
+            }
+            return roleHolders.get(0);
+        });
+    }
+
     /** Returns the device policy manager interface. */
     private IDevicePolicyManager getDevicePolicyManager() {
         if (mDevicePolicyManager == null) {
@@ -3450,6 +3483,7 @@
         scheduleWritePackageRestrictions(sourceUserId);
     }
 
+
     // Enforcing that callingUid is owning pkg on userId
     private void enforceOwnerRights(@NonNull Computer snapshot, String pkg, int callingUid) {
         // The system owns everything.
@@ -4640,21 +4674,9 @@
             enforceOwnerRights(snapshot, ownerPackage, callingUid);
             PackageManagerServiceUtils.enforceShellRestriction(mInjector.getUserManagerInternal(),
                     UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
-            synchronized (mLock) {
-                CrossProfileIntentResolver resolver =
-                        mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
-                ArraySet<CrossProfileIntentFilter> set =
-                        new ArraySet<>(resolver.filterSet());
-                for (CrossProfileIntentFilter filter : set) {
-                    //Only remove if calling user is allowed based on access control of
-                    // {@link CrossProfileIntentFilter}
-                    if (filter.getOwnerPackage().equals(ownerPackage)
-                            && mUserManager.isCrossProfileIntentFilterAccessible(sourceUserId,
-                            filter.mTargetUserId, /* addCrossProfileIntentFilter */ false)) {
-                        resolver.removeFilter(filter);
-                    }
-                }
-            }
+            PackageManagerService.this.mInjector.getCrossProfileIntentFilterHelper()
+                            .clearCrossProfileIntentFilters(sourceUserId, ownerPackage,
+                                    null);
             scheduleWritePackageRestrictions(sourceUserId);
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 396994b..76e6e45f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -139,6 +139,7 @@
     private final Singleton<BackgroundDexOptService> mBackgroundDexOptService;
     private final Singleton<IBackupManager> mIBackupManager;
     private final Singleton<SharedLibrariesImpl> mSharedLibrariesProducer;
+    private final Singleton<CrossProfileIntentFilterHelper> mCrossProfileIntentFilterHelperProducer;
 
     PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock,
             Installer installer, Object installLock, PackageAbiHelper abiHelper,
@@ -176,7 +177,8 @@
             ServiceProducer getSystemServiceProducer,
             Producer<BackgroundDexOptService> backgroundDexOptService,
             Producer<IBackupManager> iBackupManager,
-            Producer<SharedLibrariesImpl> sharedLibrariesProducer) {
+            Producer<SharedLibrariesImpl> sharedLibrariesProducer,
+            Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer) {
         mContext = context;
         mLock = lock;
         mInstaller = installer;
@@ -228,6 +230,8 @@
         mBackgroundDexOptService = new Singleton<>(backgroundDexOptService);
         mIBackupManager = new Singleton<>(iBackupManager);
         mSharedLibrariesProducer = new Singleton<>(sharedLibrariesProducer);
+        mCrossProfileIntentFilterHelperProducer = new Singleton<>(
+                crossProfileIntentFilterHelperProducer);
     }
 
     /**
@@ -262,6 +266,14 @@
         return mLock;
     }
 
+    /**
+     * {@link CrossProfileIntentFilterHelper} which manages {@link CrossProfileIntentFilter}
+     * @return CrossProfileIntentFilterHelper
+     */
+    public CrossProfileIntentFilterHelper getCrossProfileIntentFilterHelper() {
+        return mCrossProfileIntentFilterHelperProducer.get(this, mPackageManager);
+    }
+
     public Installer getInstaller() {
         return mInstaller;
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e1efc61..2138c20 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -99,7 +99,6 @@
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -116,6 +115,7 @@
 import com.android.server.art.ArtManagerLocal;
 import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+import com.android.server.pm.permission.PermissionAllowlist;
 import com.android.server.pm.verify.domain.DomainVerificationShell;
 
 import dalvik.system.DexFile;
@@ -2684,26 +2684,7 @@
             getErrPrintWriter().println("Error: no package specified.");
             return 1;
         }
-
-        ArraySet<String> privAppPermissions = null;
-        if (isVendorApp(pkg)) {
-            privAppPermissions = SystemConfig.getInstance().getVendorPrivAppPermissions(pkg);
-        } else if (isProductApp(pkg)) {
-            privAppPermissions = SystemConfig.getInstance().getProductPrivAppPermissions(pkg);
-        } else if (isSystemExtApp(pkg)) {
-            privAppPermissions = SystemConfig.getInstance()
-                    .getSystemExtPrivAppPermissions(pkg);
-        } else if (isApexApp(pkg)) {
-            final String apexName = ApexManager.getInstance().getApexModuleNameForPackageName(
-                    getApexPackageNameContainingPackage(pkg));
-            privAppPermissions = SystemConfig.getInstance()
-                    .getApexPrivAppPermissions(apexName, pkg);
-        } else {
-            privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg);
-        }
-
-        getOutPrintWriter().println(privAppPermissions == null
-                ? "{}" : privAppPermissions.toString());
+        getOutPrintWriter().println(getPrivAppPermissionsString(pkg, true));
         return 0;
     }
 
@@ -2713,29 +2694,54 @@
             getErrPrintWriter().println("Error: no package specified.");
             return 1;
         }
-
-        ArraySet<String> privAppPermissions = null;
-        if (isVendorApp(pkg)) {
-            privAppPermissions = SystemConfig.getInstance().getVendorPrivAppDenyPermissions(pkg);
-        } else if (isProductApp(pkg)) {
-            privAppPermissions = SystemConfig.getInstance().getProductPrivAppDenyPermissions(pkg);
-        } else if (isSystemExtApp(pkg)) {
-            privAppPermissions = SystemConfig.getInstance()
-                    .getSystemExtPrivAppDenyPermissions(pkg);
-        } else if (isApexApp(pkg)) {
-            final String apexName = ApexManager.getInstance().getApexModuleNameForPackageName(
-                    getApexPackageNameContainingPackage(pkg));
-            privAppPermissions = SystemConfig.getInstance()
-                    .getApexPrivAppDenyPermissions(apexName, pkg);
-        } else {
-            privAppPermissions = SystemConfig.getInstance().getPrivAppDenyPermissions(pkg);
-        }
-
-        getOutPrintWriter().println(privAppPermissions == null
-                ? "{}" : privAppPermissions.toString());
+        getOutPrintWriter().println(getPrivAppPermissionsString(pkg, false));
         return 0;
     }
 
+    @NonNull
+    private String getPrivAppPermissionsString(@NonNull String packageName, boolean allowed) {
+        final PermissionAllowlist permissionAllowlist =
+                SystemConfig.getInstance().getPermissionAllowlist();
+        final ArrayMap<String, ArrayMap<String, Boolean>> privAppPermissions;
+        if (isVendorApp(packageName)) {
+            privAppPermissions = permissionAllowlist.getVendorPrivilegedAppAllowlist();
+        } else if (isProductApp(packageName)) {
+            privAppPermissions = permissionAllowlist.getProductPrivilegedAppAllowlist();
+        } else if (isSystemExtApp(packageName)) {
+            privAppPermissions = permissionAllowlist.getSystemExtPrivilegedAppAllowlist();
+        } else if (isApexApp(packageName)) {
+            final String moduleName = ApexManager.getInstance().getApexModuleNameForPackageName(
+                    getApexPackageNameContainingPackage(packageName));
+            privAppPermissions = permissionAllowlist.getApexPrivilegedAppAllowlists()
+                    .get(moduleName);
+        } else {
+            privAppPermissions = permissionAllowlist.getPrivilegedAppAllowlist();
+        }
+        final ArrayMap<String, Boolean> permissions = privAppPermissions != null
+                ? privAppPermissions.get(packageName) : null;
+        if (permissions == null) {
+            return "{}";
+        }
+        final StringBuilder result = new StringBuilder("{");
+        boolean isFirstPermission = true;
+        final int permissionsSize = permissions.size();
+        for (int i = 0; i < permissionsSize; i++) {
+            boolean permissionAllowed = permissions.valueAt(i);
+            if (permissionAllowed != allowed) {
+                continue;
+            }
+            if (isFirstPermission) {
+                isFirstPermission = false;
+            } else {
+                result.append(", ");
+            }
+            String permissionName = permissions.keyAt(i);
+            result.append(permissionName);
+        }
+        result.append("}");
+        return result.toString();
+    }
+
     private int runGetOemPermissions() {
         final String pkg = getNextArg();
         if (pkg == null) {
@@ -2743,7 +2749,7 @@
             return 1;
         }
         final Map<String, Boolean> oemPermissions = SystemConfig.getInstance()
-                .getOemPermissions(pkg);
+                .getPermissionAllowlist().getOemAppAllowlist().get(pkg);
         if (oemPermissions == null || oemPermissions.isEmpty()) {
             getOutPrintWriter().println("{}");
         } else {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 89b74f4..7fec0eb00f 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -369,8 +369,6 @@
 
     // Current settings file.
     private final File mSettingsFilename;
-    // Compressed current settings file.
-    private final File mCompressedSettingsFilename;
     // Previous settings file.
     // Removed when the current settings file successfully stored.
     private final File mPreviousSettingsFilename;
@@ -641,7 +639,6 @@
         mRuntimePermissionsPersistence = null;
         mPermissionDataProvider = null;
         mSettingsFilename = null;
-        mCompressedSettingsFilename = null;
         mPreviousSettingsFilename = null;
         mPackageListFilename = null;
         mStoppedPackagesFilename = null;
@@ -713,7 +710,6 @@
                 |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                 -1, -1);
         mSettingsFilename = new File(mSystemDir, "packages.xml");
-        mCompressedSettingsFilename = new File(mSystemDir, "packages.compressed");
         mPreviousSettingsFilename = new File(mSystemDir, "packages-backup.xml");
         mPackageListFilename = new File(mSystemDir, "packages.list");
         FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
@@ -755,7 +751,6 @@
         mLock = null;
         mRuntimePermissionsPersistence = r.mRuntimePermissionsPersistence;
         mSettingsFilename = null;
-        mCompressedSettingsFilename = null;
         mPreviousSettingsFilename = null;
         mPackageListFilename = null;
         mStoppedPackagesFilename = null;
@@ -819,6 +814,10 @@
         return mPackages;
     }
 
+    WatchedArrayMap<String, PackageSetting> getDisabledSystemPackagesLocked() {
+        return mDisabledSysPackages;
+    }
+
     KeySetManagerService getKeySetManagerService() {
         return mKeySetManagerService;
     }
@@ -2593,8 +2592,6 @@
                 Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
             }
         }
-        // Compressed settings are not valid anymore.
-        mCompressedSettingsFilename.delete();
 
         mPastSignatures.clear();
 
@@ -2684,30 +2681,10 @@
             mPreviousSettingsFilename.delete();
 
             FileUtils.setPermissions(mSettingsFilename.toString(),
-                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+                    FileUtils.S_IRUSR|FileUtils.S_IWUSR
+                    |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
                     -1, -1);
 
-            final FileInputStream fis = new FileInputStream(mSettingsFilename);
-            final AtomicFile compressed = new AtomicFile(mCompressedSettingsFilename);
-            final FileOutputStream fos = compressed.startWrite();
-
-            BackgroundThread.getHandler().post(() -> {
-                try {
-                    if (!nativeCompressLz4(fis.getFD().getInt$(), fos.getFD().getInt$())) {
-                        throw new IOException("Failed to compress");
-                    }
-                    compressed.finishWrite(fos);
-                    FileUtils.setPermissions(mCompressedSettingsFilename.toString(),
-                            FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP
-                                    | FileUtils.S_IWGRP, -1, -1);
-                } catch (IOException e) {
-                    Slog.e(PackageManagerService.TAG, "Failed to write compressed settings file: "
-                            + mCompressedSettingsFilename, e);
-                    compressed.delete();
-                }
-                IoUtils.closeQuietly(fis);
-            });
-
             writeKernelMappingLPr();
             writePackageListLPr();
             writeAllUsersPackageRestrictionsLPr(sync);
@@ -2730,8 +2707,6 @@
         //Debug.stopMethodTracing();
     }
 
-    private native boolean nativeCompressLz4(int inputFd, int outputFd);
-
     private void writeKernelRemoveUserLPr(int userId) {
         if (mKernelMappingFilename == null) return;
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0a650c9..251da84 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -6986,10 +6986,8 @@
         @UserAssignmentResult
         public int assignUserToDisplayOnStart(@UserIdInt int userId,
                 @UserIdInt int profileGroupId, @UserStartMode int userStartMode, int displayId) {
-            // TODO(245939659): change UserVisibilityMediator to take @UserStartMode
-            boolean foreground = userStartMode == UserManagerInternal.USER_START_MODE_FOREGROUND;
             return mUserVisibilityMediator.assignUserToDisplayOnStart(userId, profileGroupId,
-                    foreground, displayId);
+                    userStartMode, displayId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 8fb5773..2bb72b8 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -132,7 +132,8 @@
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
                         .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_WITH_PARENT)
                         .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
-                        .setUseParentsContacts(true));
+                        .setUseParentsContacts(true)
+                        .setUpdateCrossProfileIntentFiltersOnOTA(true));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 92e8f55..40d87bc 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -23,7 +23,11 @@
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
 import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
+import static com.android.server.pm.UserManagerInternal.userStartModeToString;
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
@@ -43,6 +47,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.am.EventLogTags;
 import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
+import com.android.server.pm.UserManagerInternal.UserStartMode;
 import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
 import com.android.server.utils.Slogf;
 
@@ -142,7 +147,8 @@
      * See {@link UserManagerInternal#assignUserToDisplayOnStart(int, int, int, int)}.
      */
     public @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
-            @UserIdInt int unResolvedProfileGroupId, boolean foreground, int displayId) {
+            @UserIdInt int unResolvedProfileGroupId, @UserStartMode int userStartMode,
+            int displayId) {
         Preconditions.checkArgument(!isSpecialUserId(userId), "user id cannot be generic: %d",
                 userId);
         // This method needs to perform 4 actions:
@@ -161,14 +167,16 @@
                 ? userId
                 : unResolvedProfileGroupId;
         if (DBG) {
-            Slogf.d(TAG, "assignUserToDisplayOnStart(%d, %d, %b, %d): actualProfileGroupId=%d",
-                    userId, unResolvedProfileGroupId, foreground, displayId, profileGroupId);
+            Slogf.d(TAG, "assignUserToDisplayOnStart(%d, %d, %s, %d): actualProfileGroupId=%d",
+                    userId, unResolvedProfileGroupId, userStartModeToString(userStartMode),
+                    displayId, profileGroupId);
         }
 
         int result;
         IntArray visibleUsersBefore, visibleUsersAfter;
         synchronized (mLock) {
-            result = getUserVisibilityOnStartLocked(userId, profileGroupId, foreground, displayId);
+            result = getUserVisibilityOnStartLocked(userId, profileGroupId, userStartMode,
+                    displayId);
             if (DBG) {
                 Slogf.d(TAG, "result of getUserVisibilityOnStartLocked(%s)",
                         userAssignmentResultToString(result));
@@ -185,7 +193,7 @@
             visibleUsersBefore = getVisibleUsers();
 
             // Set current user / profiles state
-            if (foreground) {
+            if (userStartMode == USER_START_MODE_FOREGROUND) {
                 mCurrentUserId = userId;
             }
             if (DBG) {
@@ -228,8 +236,23 @@
 
     @GuardedBy("mLock")
     @UserAssignmentResult
-    private int getUserVisibilityOnStartLocked(@UserIdInt int userId,
-            @UserIdInt int profileGroupId, boolean foreground, int displayId) {
+    private int getUserVisibilityOnStartLocked(@UserIdInt int userId, @UserIdInt int profileGroupId,
+            @UserStartMode int userStartMode, int displayId) {
+
+        // Check for invalid combinations first
+        if (userStartMode == USER_START_MODE_BACKGROUND && displayId != DEFAULT_DISPLAY) {
+            Slogf.wtf(TAG, "cannot start user (%d) as BACKGROUND_USER on secondary display (%d) "
+                    + "(it should be BACKGROUND_USER_VISIBLE", userId, displayId);
+            return USER_ASSIGNMENT_RESULT_FAILURE;
+        }
+        if (userStartMode == USER_START_MODE_BACKGROUND_VISIBLE
+                && displayId == DEFAULT_DISPLAY && !isProfile(userId, profileGroupId)) {
+            Slogf.wtf(TAG, "cannot start full user (%d) visible on default display", userId);
+            return USER_ASSIGNMENT_RESULT_FAILURE;
+        }
+
+        boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;
+
         if (displayId != DEFAULT_DISPLAY) {
             if (foreground) {
                 Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: cannot start "
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index f8e1547..4e0a11d 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -104,6 +104,9 @@
         @Nullable
         private Map<String, PackageState> mCachedUnmodifiablePackageStates;
 
+        @Nullable
+        private Map<String, PackageState> mCachedUnmodifiableDisabledSystemPackageStates;
+
         private UnfilteredSnapshotImpl(@NonNull PackageDataSnapshot snapshot) {
             super(snapshot);
         }
@@ -126,10 +129,24 @@
             return mCachedUnmodifiablePackageStates;
         }
 
+        @SuppressWarnings("RedundantSuppression")
+        @NonNull
+        @Override
+        public Map<String, PackageState> getDisabledSystemPackageStates() {
+            checkClosed();
+
+            if (mCachedUnmodifiableDisabledSystemPackageStates == null) {
+                mCachedUnmodifiableDisabledSystemPackageStates =
+                        Collections.unmodifiableMap(mSnapshot.getDisabledSystemPackageStates());
+            }
+            return mCachedUnmodifiableDisabledSystemPackageStates;
+        }
+
         @Override
         public void close() {
             super.close();
             mCachedUnmodifiablePackageStates = null;
+            mCachedUnmodifiableDisabledSystemPackageStates = null;
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
new file mode 100644
index 0000000..3efac81
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 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.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+
+/**
+ * Data class for OEM and privileged app permission allowlist state.
+ */
+public final class PermissionAllowlist {
+    @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mOemAppAllowlist = new ArrayMap<>();
+    @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mPrivilegedAppAllowlist =
+            new ArrayMap<>();
+    @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mVendorPrivilegedAppAllowlist =
+            new ArrayMap<>();
+    @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mProductPrivilegedAppAllowlist =
+            new ArrayMap<>();
+    @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mSystemExtPrivilegedAppAllowlist =
+            new ArrayMap<>();
+    @NonNull
+    private final ArrayMap<String, ArrayMap<String, ArrayMap<String, Boolean>>>
+            mApexPrivilegedAppAllowlists = new ArrayMap<>();
+
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getOemAppAllowlist() {
+        return mOemAppAllowlist;
+    }
+
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getPrivilegedAppAllowlist() {
+        return mPrivilegedAppAllowlist;
+    }
+
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getVendorPrivilegedAppAllowlist() {
+        return mVendorPrivilegedAppAllowlist;
+    }
+
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getProductPrivilegedAppAllowlist() {
+        return mProductPrivilegedAppAllowlist;
+    }
+
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getSystemExtPrivilegedAppAllowlist() {
+        return mSystemExtPrivilegedAppAllowlist;
+    }
+
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, ArrayMap<String, Boolean>>>
+            getApexPrivilegedAppAllowlists() {
+        return mApexPrivilegedAppAllowlists;
+    }
+
+    @Nullable
+    public Boolean getOemAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mOemAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
+
+    @Nullable
+    public Boolean getPrivilegedAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mPrivilegedAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
+
+    @Nullable
+    public Boolean getVendorPrivilegedAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mVendorPrivilegedAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
+
+    @Nullable
+    public Boolean getProductPrivilegedAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mProductPrivilegedAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
+
+    @Nullable
+    public Boolean getSystemExtPrivilegedAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mSystemExtPrivilegedAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
+
+    @Nullable
+    public Boolean getApexPrivilegedAppAllowlistState(@NonNull String moduleName,
+            @NonNull String packageName, @NonNull String permissionName) {
+        ArrayMap<String, ArrayMap<String, Boolean>> allowlist =
+                mApexPrivilegedAppAllowlists.get(moduleName);
+        if (allowlist == null) {
+            return null;
+        }
+        ArrayMap<String, Boolean> permissions = allowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 2a2bcab..f9c0deb 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -3314,13 +3314,10 @@
         final String permissionName = permission.getName();
         final String containingApexPackageName =
                 mApexManager.getActiveApexPackageNameContainingPackage(packageName);
-        if (isInSystemConfigPrivAppPermissions(pkg, permissionName,
-                containingApexPackageName)) {
-            return true;
-        }
-        if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName,
-                containingApexPackageName)) {
-            return false;
+        final Boolean allowlistState = getPrivilegedPermissionAllowlistState(pkg, permissionName,
+                containingApexPackageName);
+        if (allowlistState != null) {
+            return allowlistState;
         }
         // Updated system apps do not need to be allowlisted
         if (packageSetting.getTransientState().isUpdatedSystemApp()) {
@@ -3356,61 +3353,43 @@
         return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
     }
 
-    private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
-            @NonNull String permission, String containingApexPackageName) {
-        final SystemConfig systemConfig = SystemConfig.getInstance();
-        final Set<String> permissions;
+    @Nullable
+    private Boolean getPrivilegedPermissionAllowlistState(@NonNull AndroidPackage pkg,
+            @NonNull String permissionName, String containingApexPackageName) {
+        final PermissionAllowlist permissionAllowlist =
+                SystemConfig.getInstance().getPermissionAllowlist();
+        final String packageName = pkg.getPackageName();
         if (pkg.isVendor()) {
-            permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
+            return permissionAllowlist.getVendorPrivilegedAppAllowlistState(packageName,
+                    permissionName);
         } else if (pkg.isProduct()) {
-            permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
+            return permissionAllowlist.getProductPrivilegedAppAllowlistState(packageName,
+                    permissionName);
         } else if (pkg.isSystemExt()) {
-            permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
+            return permissionAllowlist.getSystemExtPrivilegedAppAllowlistState(packageName,
+                    permissionName);
         } else if (containingApexPackageName != null) {
-            final String apexName = mApexManager.getApexModuleNameForPackageName(
-                    containingApexPackageName);
-            final Set<String> privAppPermissions = systemConfig.getPrivAppPermissions(
-                    pkg.getPackageName());
-            final Set<String> apexPermissions = systemConfig.getApexPrivAppPermissions(
-                    apexName, pkg.getPackageName());
-            if (privAppPermissions != null) {
+            final Boolean nonApexAllowlistState =
+                    permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName);
+            if (nonApexAllowlistState != null) {
                 // TODO(andreionea): Remove check as soon as all apk-in-apex
                 // permission allowlists are migrated.
                 Slog.w(TAG, "Package " + pkg.getPackageName() + " is an APK in APEX,"
                         + " but has permission allowlist on the system image. Please bundle the"
                         + " allowlist in the " + containingApexPackageName + " APEX instead.");
-                if (apexPermissions != null) {
-                    permissions = new ArraySet<>(privAppPermissions);
-                    permissions.addAll(apexPermissions);
-                } else {
-                    permissions = privAppPermissions;
-                }
-            } else {
-                permissions = apexPermissions;
             }
+            final String moduleName = mApexManager.getApexModuleNameForPackageName(
+                    containingApexPackageName);
+            final Boolean apexAllowlistState =
+                    permissionAllowlist.getApexPrivilegedAppAllowlistState(moduleName, packageName,
+                            permissionName);
+            if (apexAllowlistState != null) {
+                return apexAllowlistState;
+            }
+            return nonApexAllowlistState;
         } else {
-            permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
+            return permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName);
         }
-        return CollectionUtils.contains(permissions, permission);
-    }
-
-    private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
-            @NonNull String permission, String containingApexPackageName) {
-        final SystemConfig systemConfig = SystemConfig.getInstance();
-        final Set<String> permissions;
-        if (pkg.isVendor()) {
-            permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
-        } else if (pkg.isProduct()) {
-            permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
-        } else if (pkg.isSystemExt()) {
-            permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
-        } else if (containingApexPackageName != null) {
-            permissions = systemConfig.getApexPrivAppDenyPermissions(containingApexPackageName,
-                    pkg.getPackageName());
-        } else {
-            permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
-        }
-        return CollectionUtils.contains(permissions, permission);
     }
 
     private boolean shouldGrantPermissionBySignature(@NonNull AndroidPackage pkg,
@@ -3612,8 +3591,8 @@
             return false;
         }
         // all oem permissions must explicitly be granted or denied
-        final Boolean granted =
-                SystemConfig.getInstance().getOemPermissions(pkg.getPackageName()).get(permission);
+        final Boolean granted = SystemConfig.getInstance().getPermissionAllowlist()
+                .getOemAppAllowlistState(pkg.getPackageName(), permission);
         if (granted == null) {
             throw new IllegalStateException("OEM permission " + permission
                     + " requested by package " + pkg.getPackageName()
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index 54ece73..983b7f4 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -283,7 +283,7 @@
                 mItems.add(mAirplaneModeOn);
             } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
                 if (Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
+                        Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserAdmin()) {
                     mItems.add(new BugReportAction());
                 }
             } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
@@ -535,9 +535,9 @@
         }
     }
 
-    private boolean isCurrentUserOwner() {
+    private boolean isCurrentUserAdmin() {
         UserInfo currentUser = getCurrentUser();
-        return currentUser == null || currentUser.isPrimary();
+        return currentUser == null || currentUser.isAdmin();
     }
 
     private void addUsersToMenu(ArrayList<Action> items) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7919e19..b5d4afe 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2900,6 +2900,11 @@
                     }
                 }
                 break;
+            case KeyEvent.KEYCODE_RECENT_APPS:
+                if (down && repeatCount == 0) {
+                    showRecentApps(false /* triggeredFromAltTab */);
+                }
+                return key_consumed;
             case KeyEvent.KEYCODE_APP_SWITCH:
                 if (!keyguardOn) {
                     if (down && repeatCount == 0) {
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 1fe82f4..09a7b29 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.hardware.display.DisplayManagerInternal;
 import android.media.AudioManager;
 import android.media.Ringtone;
@@ -67,6 +66,7 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.PrintWriter;
+import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -140,9 +140,8 @@
     private final NotifierHandler mHandler;
     private final Executor mBackgroundExecutor;
     private final Intent mScreenOnIntent;
-    private final Bundle mScreenOnOptions;
     private final Intent mScreenOffIntent;
-    private final Bundle mScreenOffOptions;
+    private final Bundle mScreenOnOffOptions;
 
     // True if the device should suspend when the screen is off due to proximity.
     private final boolean mSuspendWhenScreenOffDueToProximityConfig;
@@ -204,14 +203,11 @@
         mScreenOnIntent.addFlags(
                 Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
                 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
-        mScreenOnOptions = BroadcastOptions.makeRemovingMatchingFilter(
-                new IntentFilter(Intent.ACTION_SCREEN_OFF)).toBundle();
         mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
         mScreenOffIntent.addFlags(
                 Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
                 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
-        mScreenOffOptions = BroadcastOptions.makeRemovingMatchingFilter(
-                new IntentFilter(Intent.ACTION_SCREEN_ON)).toBundle();
+        mScreenOnOffOptions = createScreenOnOffBroadcastOptions();
 
         mSuspendWhenScreenOffDueToProximityConfig = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity);
@@ -229,6 +225,25 @@
     }
 
     /**
+     * Create the {@link BroadcastOptions} bundle that will be used with sending the
+     * {@link Intent#ACTION_SCREEN_ON} and {@link Intent#ACTION_SCREEN_OFF} broadcasts.
+     */
+    private Bundle createScreenOnOffBroadcastOptions() {
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        // This allows the broadcasting system to discard any older broadcasts
+        // waiting to be delivered to a process.
+        options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+        // Set namespace and key to identify which older broadcasts can be discarded.
+        // We could use any strings here but namespace needs to be unlikely to be reused with in
+        // the system_server process, as that could result in potentially discarding some
+        // non-screen on/off related broadcast.
+        options.setDeliveryGroupMatchingKey(
+                UUID.randomUUID().toString(),
+                Intent.ACTION_SCREEN_ON);
+        return options.toBundle();
+    }
+
+    /**
      * Called when a wake lock is acquired.
      */
     public void onWakeLockAcquired(int flags, String tag, String packageName,
@@ -798,7 +813,7 @@
         if (mActivityManagerInternal.isSystemReady()) {
             final boolean ordered = !mActivityManagerInternal.isModernQueueEnabled();
             mActivityManagerInternal.broadcastIntent(mScreenOnIntent, mWakeUpBroadcastDone,
-                    null, ordered, UserHandle.USER_ALL, null, null, mScreenOnOptions);
+                    null, ordered, UserHandle.USER_ALL, null, null, mScreenOnOffOptions);
         } else {
             EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1);
             sendNextBroadcast();
@@ -823,7 +838,7 @@
         if (mActivityManagerInternal.isSystemReady()) {
             final boolean ordered = !mActivityManagerInternal.isModernQueueEnabled();
             mActivityManagerInternal.broadcastIntent(mScreenOffIntent, mGoToSleepBroadcastDone,
-                    null, ordered, UserHandle.USER_ALL, null, null, mScreenOffOptions);
+                    null, ordered, UserHandle.USER_ALL, null, null, mScreenOnOffOptions);
         } else {
             EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);
             sendNextBroadcast();
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 1c4e143..522c6c8 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -407,16 +407,14 @@
         return mDisplayPowerRequest.policy;
     }
 
-    boolean updateLocked(float screenBrightnessOverride, boolean autoBrightness,
-            boolean useProximitySensor, boolean boostScreenBrightness, int dozeScreenState,
-            float dozeScreenBrightness, boolean overrideDrawWakeLock,
-            PowerSaveState powerSaverState, boolean quiescent, boolean dozeAfterScreenOff,
-            boolean bootCompleted, boolean screenBrightnessBoostInProgress,
-            boolean waitForNegativeProximity) {
+    boolean updateLocked(float screenBrightnessOverride, boolean useProximitySensor,
+            boolean boostScreenBrightness, int dozeScreenState, float dozeScreenBrightness,
+            boolean overrideDrawWakeLock, PowerSaveState powerSaverState, boolean quiescent,
+            boolean dozeAfterScreenOff, boolean bootCompleted,
+            boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity) {
         mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
                 bootCompleted, screenBrightnessBoostInProgress);
         mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
-        mDisplayPowerRequest.useAutoBrightness = autoBrightness;
         mDisplayPowerRequest.useProximitySensor = useProximitySensor;
         mDisplayPowerRequest.boostScreenBrightness = boostScreenBrightness;
 
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 6e3c827..b5ddc06 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -581,10 +581,6 @@
     private boolean mIsFaceDown = false;
     private long mLastFlipTime = 0L;
 
-    // The screen brightness mode.
-    // One of the Settings.System.SCREEN_BRIGHTNESS_MODE_* constants.
-    private int mScreenBrightnessModeSetting;
-
     // The screen brightness setting override from the window manager
     // to allow the current foreground activity to override the brightness.
     private float mScreenBrightnessOverrideFromWindowManager =
@@ -1457,10 +1453,6 @@
             mSystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, retailDemoValue);
         }
 
-        mScreenBrightnessModeSetting = Settings.System.getIntForUser(resolver,
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
-
         mDirty |= DIRTY_SETTINGS;
     }
 
@@ -3432,23 +3424,18 @@
                 final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
                 final int groupId = powerGroup.getGroupId();
 
-                // Determine appropriate screen brightness and auto-brightness adjustments.
-                final boolean autoBrightness;
+                // Determine appropriate screen brightness.
                 final float screenBrightnessOverride;
                 if (!mBootCompleted) {
                     // Keep the brightness steady during boot. This requires the
                     // bootloader brightness and the default brightness to be identical.
-                    autoBrightness = false;
                     screenBrightnessOverride = mScreenBrightnessDefault;
                 } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
-                    autoBrightness = false;
                     screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager;
                 } else {
-                    autoBrightness = (mScreenBrightnessModeSetting
-                            == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
                     screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
                 }
-                boolean ready = powerGroup.updateLocked(screenBrightnessOverride, autoBrightness,
+                boolean ready = powerGroup.updateLocked(screenBrightnessOverride,
                         shouldUseProximitySensorLocked(), shouldBoostScreenBrightness(),
                         mDozeScreenStateOverrideFromDreamManager,
                         mDozeScreenBrightnessOverrideFromDreamManagerFloat,
@@ -3469,7 +3456,6 @@
                             powerGroup.getUserActivitySummaryLocked())
                             + ", mBootCompleted=" + mBootCompleted
                             + ", screenBrightnessOverride=" + screenBrightnessOverride
-                            + ", useAutoBrightness=" + autoBrightness
                             + ", mScreenBrightnessBoostInProgress="
                             + mScreenBrightnessBoostInProgress
                             + ", sQuiescent=" + sQuiescent);
@@ -4488,7 +4474,6 @@
                     + mMaximumScreenOffTimeoutFromDeviceAdmin + " (enforced="
                     + isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")");
             pw.println("  mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting);
-            pw.println("  mScreenBrightnessModeSetting=" + mScreenBrightnessModeSetting);
             pw.println("  mScreenBrightnessOverrideFromWindowManager="
                     + mScreenBrightnessOverrideFromWindowManager);
             pw.println("  mUserActivityTimeoutOverrideFromWindowManager="
@@ -4866,9 +4851,6 @@
             proto.end(stayOnWhilePluggedInToken);
 
             proto.write(
-                    PowerServiceSettingsAndConfigurationDumpProto.SCREEN_BRIGHTNESS_MODE_SETTING,
-                    mScreenBrightnessModeSetting);
-            proto.write(
                     PowerServiceSettingsAndConfigurationDumpProto
                             .SCREEN_BRIGHTNESS_OVERRIDE_FROM_WINDOW_MANAGER,
                     mScreenBrightnessOverrideFromWindowManager);
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 2a88473..855fcaf 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -763,8 +763,6 @@
     @NonNull
     private final BatteryStatsHistory mHistory;
 
-    private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
-
     int mStartCount;
 
     /**
@@ -11242,17 +11240,11 @@
         return mHistory.getHistoryUsedSize();
     }
 
-    @Override
-    public boolean startIteratingHistoryLocked() {
-        mBatteryStatsHistoryIterator = createBatteryStatsHistoryIterator();
-        return true;
-    }
-
     /**
      * Creates an iterator for battery stats history.
      */
-    @VisibleForTesting
-    public BatteryStatsHistoryIterator createBatteryStatsHistoryIterator() {
+    @Override
+    public BatteryStatsHistoryIterator iterateBatteryStatsHistory() {
         return mHistory.iterate();
     }
 
@@ -11277,17 +11269,6 @@
     }
 
     @Override
-    public boolean getNextHistoryLocked(HistoryItem out) {
-        return mBatteryStatsHistoryIterator.next(out);
-    }
-
-    @Override
-    public void finishIteratingHistoryLocked() {
-        mBatteryStatsHistoryIterator.close();
-        mBatteryStatsHistoryIterator = null;
-    }
-
-    @Override
     public int getStartCount() {
         return mStartCount;
     }
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index ab35dc8..6d39177 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -223,7 +223,7 @@
     }
 
     class SensorPrivacyServiceImpl extends ISensorPrivacyManager.Stub implements
-            AppOpsManager.OnOpNotedListener, AppOpsManager.OnOpStartedListener,
+            AppOpsManager.OnOpNotedInternalListener, AppOpsManager.OnOpStartedListener,
             IBinder.DeathRecipient, UserManagerInternal.UserRestrictionsListener {
 
         private final SensorPrivacyHandler mHandler;
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index c379abc..6c616e0 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -2408,7 +2408,20 @@
                         metrics.gpuTotalUsageKb,
                         metrics.gpuPrivateAllocationsKb,
                         metrics.dmaBufTotalExportedKb,
-                        metrics.shmemKb));
+                        metrics.shmemKb,
+                        metrics.totalKb,
+                        metrics.freeKb,
+                        metrics.availableKb,
+                        metrics.activeKb,
+                        metrics.inactiveKb,
+                        metrics.activeAnonKb,
+                        metrics.inactiveAnonKb,
+                        metrics.activeFileKb,
+                        metrics.inactiveFileKb,
+                        metrics.swapTotalKb,
+                        metrics.swapFreeKb,
+                        metrics.cmaTotalKb,
+                        metrics.cmaFreeKb));
         return StatsManager.PULL_SUCCESS;
     }
 
diff --git a/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java b/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java
index 9ebf59e..2ab4fdf 100644
--- a/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java
+++ b/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java
@@ -83,6 +83,19 @@
         result.pageTablesKb = (int) mInfos[Debug.MEMINFO_PAGE_TABLES];
         result.kernelStackKb = (int) mInfos[Debug.MEMINFO_KERNEL_STACK];
         result.shmemKb = (int) mInfos[Debug.MEMINFO_SHMEM];
+        result.totalKb = (int) mInfos[Debug.MEMINFO_TOTAL];
+        result.freeKb = (int) mInfos[Debug.MEMINFO_FREE];
+        result.availableKb = (int) mInfos[Debug.MEMINFO_AVAILABLE];
+        result.activeKb = (int) mInfos[Debug.MEMINFO_ACTIVE];
+        result.inactiveKb = (int) mInfos[Debug.MEMINFO_INACTIVE];
+        result.activeAnonKb = (int) mInfos[Debug.MEMINFO_ACTIVE_ANON];
+        result.inactiveAnonKb = (int) mInfos[Debug.MEMINFO_INACTIVE_ANON];
+        result.activeFileKb = (int) mInfos[Debug.MEMINFO_ACTIVE_FILE];
+        result.inactiveFileKb = (int) mInfos[Debug.MEMINFO_INACTIVE_FILE];
+        result.swapTotalKb = (int) mInfos[Debug.MEMINFO_SWAP_TOTAL];
+        result.swapFreeKb = (int) mInfos[Debug.MEMINFO_SWAP_FREE];
+        result.cmaTotalKb = (int) mInfos[Debug.MEMINFO_CMA_TOTAL];
+        result.cmaFreeKb = (int) mInfos[Debug.MEMINFO_CMA_FREE];
         result.totalIonKb = totalIonKb;
         result.gpuTotalUsageKb = gpuTotalUsageKb;
         result.gpuPrivateAllocationsKb = gpuPrivateAllocationsKb;
@@ -97,6 +110,19 @@
         public int pageTablesKb;
         public int kernelStackKb;
         public int shmemKb;
+        public int totalKb;
+        public int freeKb;
+        public int availableKb;
+        public int activeKb;
+        public int inactiveKb;
+        public int activeAnonKb;
+        public int inactiveAnonKb;
+        public int activeFileKb;
+        public int inactiveFileKb;
+        public int swapTotalKb;
+        public int swapFreeKb;
+        public int cmaTotalKb;
+        public int cmaFreeKb;
         public int totalIonKb;
         public int gpuTotalUsageKb;
         public int gpuPrivateAllocationsKb;
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 05df22f..3be16a1 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -26,6 +26,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
+import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
 import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
 import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
 import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;
@@ -36,6 +37,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.net.ConnectivityDiagnosticsManager;
+import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
 import android.net.ConnectivityManager;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
@@ -50,6 +53,7 @@
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkProvider;
+import android.net.NetworkRequest;
 import android.net.NetworkScore;
 import android.net.RouteInfo;
 import android.net.TelephonyNetworkSpecifier;
@@ -546,6 +550,39 @@
         }
     }
 
+    /**
+     * Sent when there is a suspected data stall on a network
+     *
+     * <p>Only relevant in the Connected state.
+     *
+     * @param arg1 The "all" token; this signal is always honored.
+     * @param obj @NonNull An EventDataStallSuspectedInfo instance with relevant data.
+     */
+    private static final int EVENT_DATA_STALL_SUSPECTED = 13;
+
+    private static class EventDataStallSuspectedInfo implements EventInfo {
+        @NonNull public final Network network;
+
+        EventDataStallSuspectedInfo(@NonNull Network network) {
+            this.network = network;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(network);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof EventDataStallSuspectedInfo)) {
+                return false;
+            }
+
+            final EventDataStallSuspectedInfo rhs = (EventDataStallSuspectedInfo) other;
+            return Objects.equals(network, rhs.network);
+        }
+    }
+
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     @NonNull
     final DisconnectedState mDisconnectedState = new DisconnectedState();
@@ -578,10 +615,13 @@
     @NonNull
     private final VcnUnderlyingNetworkControllerCallback mUnderlyingNetworkControllerCallback;
 
+    @NonNull private final VcnConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback;
+
     private final boolean mIsMobileDataEnabled;
 
     @NonNull private final IpSecManager mIpSecManager;
     @NonNull private final ConnectivityManager mConnectivityManager;
+    @NonNull private final ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager;
 
     @Nullable private IpSecTunnelInterface mTunnelIface = null;
 
@@ -748,6 +788,20 @@
                         mUnderlyingNetworkControllerCallback);
         mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
         mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
+        mConnectivityDiagnosticsManager =
+                mVcnContext.getContext().getSystemService(ConnectivityDiagnosticsManager.class);
+
+        mConnectivityDiagnosticsCallback = new VcnConnectivityDiagnosticsCallback();
+
+        if (mConnectionConfig.hasGatewayOption(
+                VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY)) {
+            final NetworkRequest diagRequest =
+                    new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build();
+            mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
+                    diagRequest,
+                    new HandlerExecutor(new Handler(vcnContext.getLooper())),
+                    mConnectivityDiagnosticsCallback);
+        }
 
         addState(mDisconnectedState);
         addState(mDisconnectingState);
@@ -810,6 +864,9 @@
         mUnderlyingNetworkController.teardown();
 
         mGatewayStatusCallback.onQuit();
+
+        mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
+                mConnectivityDiagnosticsCallback);
     }
 
     /**
@@ -828,6 +885,20 @@
         sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
     }
 
+    private class VcnConnectivityDiagnosticsCallback extends ConnectivityDiagnosticsCallback {
+        @Override
+        public void onDataStallSuspected(ConnectivityDiagnosticsManager.DataStallReport report) {
+            mVcnContext.ensureRunningOnLooperThread();
+
+            final Network network = report.getNetwork();
+            logInfo("Data stall suspected on " + network);
+            sendMessageAndAcquireWakeLock(
+                    EVENT_DATA_STALL_SUSPECTED,
+                    TOKEN_ALL,
+                    new EventDataStallSuspectedInfo(network));
+        }
+    }
+
     private class VcnUnderlyingNetworkControllerCallback
             implements UnderlyingNetworkControllerCallback {
         @Override
@@ -1367,7 +1438,8 @@
                 case EVENT_SUBSCRIPTIONS_CHANGED: // Fallthrough
                 case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: // Fallthrough
                 case EVENT_MIGRATION_COMPLETED: // Fallthrough
-                case EVENT_IKE_CONNECTION_INFO_CHANGED:
+                case EVENT_IKE_CONNECTION_INFO_CHANGED: // Fallthrough
+                case EVENT_DATA_STALL_SUSPECTED:
                     logUnexpectedEvent(msg.what);
                     break;
                 default:
@@ -1925,6 +1997,11 @@
                     mIkeConnectionInfo =
                             ((EventIkeConnectionInfoChangedInfo) msg.obj).ikeConnectionInfo;
                     break;
+                case EVENT_DATA_STALL_SUSPECTED:
+                    final Network networkWithDataStall =
+                            ((EventDataStallSuspectedInfo) msg.obj).network;
+                    handleDataStallSuspected(networkWithDataStall);
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
@@ -1985,6 +2062,15 @@
             }
         }
 
+        private void handleDataStallSuspected(Network networkWithDataStall) {
+            if (mUnderlying != null
+                    && mNetworkAgent != null
+                    && mNetworkAgent.getNetwork().equals(networkWithDataStall)) {
+                logInfo("Perform Mobility update to recover from suspected data stall");
+                mIkeSession.setNetwork(mUnderlying.network);
+            }
+        }
+
         protected void setupInterfaceAndNetworkAgent(
                 int token,
                 @NonNull IpSecTunnelInterface tunnelIface,
@@ -2424,6 +2510,11 @@
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
+    ConnectivityDiagnosticsCallback getConnectivityDiagnosticsCallback() {
+        return mConnectivityDiagnosticsCallback;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
     UnderlyingNetworkRecord getUnderlyingNetwork() {
         return mUnderlying;
     }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4480d52..37450ac 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -603,6 +603,13 @@
      * for display.
      */
     void generateCrop(WallpaperData wallpaper) {
+        TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+        t.traceBegin("WPMS.generateCrop");
+        generateCropInternal(wallpaper);
+        t.traceEnd();
+    }
+
+    private void generateCropInternal(WallpaperData wallpaper) {
         boolean success = false;
 
         // Only generate crop for default display.
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index af430f9..2b49a81 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -17,7 +17,15 @@
 package com.android.server.wm;
 
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.app.Activity.FULLSCREEN_MODE_REQUEST_ENTER;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ActivityTaskManager.INVALID_WINDOWING_MODE;
+import static android.app.FullscreenRequestHandler.REMOTE_CALLBACK_RESULT_KEY;
+import static android.app.FullscreenRequestHandler.RESULT_APPROVED;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_DEFAULT_FREEFORM;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_IN_FREEFORM;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_TOP_FOCUSED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -27,6 +35,7 @@
 import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
@@ -52,6 +61,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
+import android.app.FullscreenRequestHandler;
 import android.app.IActivityClientController;
 import android.app.ICompatCameraControlCallback;
 import android.app.IRequestFinishCallback;
@@ -71,6 +81,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
@@ -85,6 +96,7 @@
 
 import com.android.internal.app.AssistUtils;
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
@@ -675,29 +687,32 @@
 
     @Override
     public int getLaunchedFromUid(IBinder token) {
-        if (!canGetLaunchedFrom()) {
-            return INVALID_UID;
-        }
+        final int uid = Binder.getCallingUid();
+        final boolean isInternalCaller = isInternalCallerGetLaunchedFrom(uid);
         synchronized (mGlobalLock) {
             final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-            return r != null ? r.launchedFromUid : INVALID_UID;
+            if (r != null && (isInternalCaller || r.mShareIdentity || r.launchedFromUid == uid)) {
+                return r.launchedFromUid;
+            }
         }
+        return INVALID_UID;
     }
 
     @Override
     public String getLaunchedFromPackage(IBinder token) {
-        if (!canGetLaunchedFrom()) {
-            return null;
-        }
+        final int uid = Binder.getCallingUid();
+        final boolean isInternalCaller = isInternalCallerGetLaunchedFrom(uid);
         synchronized (mGlobalLock) {
             final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-            return r != null ? r.launchedFromPackage : null;
+            if (r != null && (isInternalCaller || r.mShareIdentity || r.launchedFromUid == uid)) {
+                return r.launchedFromPackage;
+            }
         }
+        return null;
     }
 
-    /** Whether the caller can get the package or uid that launched its activity. */
-    private boolean canGetLaunchedFrom() {
-        final int uid = Binder.getCallingUid();
+    /** Whether the call to one of the getLaunchedFrom APIs is performed by an internal caller. */
+    private boolean isInternalCallerGetLaunchedFrom(int uid) {
         if (UserHandle.getAppId(uid) == SYSTEM_UID) {
             return true;
         }
@@ -1056,6 +1071,133 @@
         }
     }
 
+    private @FullscreenRequestHandler.RequestResult int validateMultiwindowFullscreenRequestLocked(
+            Task topFocusedRootTask, int fullscreenRequest, ActivityRecord requesterActivity) {
+        // If the mode is not by default freeform, the freeform will be a user-driven event.
+        if (topFocusedRootTask.getParent().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+            return RESULT_FAILED_NOT_DEFAULT_FREEFORM;
+        }
+        // If this is not coming from the currently top-most activity, reject the request.
+        if (requesterActivity != topFocusedRootTask.getTopMostActivity()) {
+            return RESULT_FAILED_NOT_TOP_FOCUSED;
+        }
+        if (fullscreenRequest == FULLSCREEN_MODE_REQUEST_ENTER) {
+            if (topFocusedRootTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+                return RESULT_FAILED_NOT_IN_FREEFORM;
+            }
+        } else {
+            if (topFocusedRootTask.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+                return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
+            }
+            if (topFocusedRootTask.mMultiWindowRestoreWindowingMode == INVALID_WINDOWING_MODE) {
+                return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
+            }
+        }
+        return RESULT_APPROVED;
+    }
+
+    @Override
+    public void requestMultiwindowFullscreen(IBinder callingActivity, int fullscreenRequest,
+            IRemoteCallback callback) {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                requestMultiwindowFullscreenLocked(callingActivity, fullscreenRequest, callback);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void requestMultiwindowFullscreenLocked(IBinder callingActivity, int fullscreenRequest,
+            IRemoteCallback callback) {
+        final ActivityRecord r = ActivityRecord.forTokenLocked(callingActivity);
+        if (r == null) {
+            return;
+        }
+
+        // If the shell transition is not enabled, just execute and done.
+        final TransitionController controller = r.mTransitionController;
+        if (!controller.isShellTransitionsEnabled()) {
+            final @FullscreenRequestHandler.RequestResult int validateResult;
+            final Task topFocusedRootTask;
+            topFocusedRootTask = mService.getTopDisplayFocusedRootTask();
+            validateResult = validateMultiwindowFullscreenRequestLocked(topFocusedRootTask,
+                    fullscreenRequest, r);
+            reportMultiwindowFullscreenRequestValidatingResult(callback, validateResult);
+            if (validateResult == RESULT_APPROVED) {
+                executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask);
+            }
+            return;
+        }
+        // Initiate the transition.
+        final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */, controller,
+                mService.mWindowManager.mSyncEngine);
+        if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                    "Creating Pending Multiwindow Fullscreen Request: %s", transition);
+            mService.mWindowManager.mSyncEngine.queueSyncSet(
+                    () -> r.mTransitionController.moveToCollecting(transition),
+                    () -> {
+                        executeFullscreenRequestTransition(fullscreenRequest, callback, r,
+                                transition, true /* queued */);
+                    });
+        } else {
+            executeFullscreenRequestTransition(fullscreenRequest, callback, r, transition,
+                    false /* queued */);
+        }
+    }
+
+    private void executeFullscreenRequestTransition(int fullscreenRequest, IRemoteCallback callback,
+            ActivityRecord r, Transition transition, boolean queued) {
+        final @FullscreenRequestHandler.RequestResult int validateResult;
+        final Task topFocusedRootTask;
+        topFocusedRootTask = mService.getTopDisplayFocusedRootTask();
+        validateResult = validateMultiwindowFullscreenRequestLocked(topFocusedRootTask,
+                fullscreenRequest, r);
+        reportMultiwindowFullscreenRequestValidatingResult(callback, validateResult);
+        if (validateResult != RESULT_APPROVED) {
+            if (queued) {
+                transition.abort();
+            }
+            return;
+        }
+        r.mTransitionController.requestStartTransition(transition, topFocusedRootTask,
+                null /* remoteTransition */, null /* displayChange */);
+        executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask);
+        transition.setReady(topFocusedRootTask, true);
+    }
+
+    private static void reportMultiwindowFullscreenRequestValidatingResult(IRemoteCallback callback,
+            @FullscreenRequestHandler.RequestResult int result) {
+        if (callback == null) {
+            return;
+        }
+        Bundle res = new Bundle();
+        res.putInt(REMOTE_CALLBACK_RESULT_KEY, result);
+        try {
+            callback.sendResult(res);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "client throws an exception back to the server, ignore it");
+        }
+    }
+
+    private static void executeMultiWindowFullscreenRequest(int fullscreenRequest, Task requester) {
+        final int targetWindowingMode;
+        if (fullscreenRequest == FULLSCREEN_MODE_REQUEST_ENTER) {
+            requester.mMultiWindowRestoreWindowingMode =
+                    requester.getRequestedOverrideWindowingMode();
+            targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
+        } else {
+            targetWindowingMode = requester.mMultiWindowRestoreWindowingMode;
+            requester.mMultiWindowRestoreWindowingMode = INVALID_WINDOWING_MODE;
+        }
+        requester.setWindowingMode(targetWindowingMode);
+        if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+            requester.setBounds(null);
+        }
+    }
+
     @Override
     public void startLockTaskModeByToken(IBinder token) {
         synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f725d1a..efd0ffa 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -670,7 +670,7 @@
     private boolean mCurrentLaunchCanTurnScreenOn = true;
 
     /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
-    private boolean mLastSurfaceShowing = true;
+    private boolean mLastSurfaceShowing;
 
     /**
      * The activity is opaque and fills the entire space of this task.
@@ -873,6 +873,7 @@
     boolean mEnteringAnimation;
     boolean mOverrideTaskTransition;
     boolean mDismissKeyguard;
+    boolean mShareIdentity;
 
     /** True if the activity has reported stopped; False if the activity becomes visible. */
     boolean mAppStopped;
@@ -1233,8 +1234,8 @@
                 pw.println(prefix + "supportsEnterPipOnTaskSwitch: "
                         + supportsEnterPipOnTaskSwitch);
             }
-            if (info.getMaxAspectRatio() != 0) {
-                pw.println(prefix + "maxAspectRatio=" + info.getMaxAspectRatio());
+            if (getMaxAspectRatio() != 0) {
+                pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio());
             }
             final float minAspectRatio = getMinAspectRatio();
             if (minAspectRatio != 0) {
@@ -1574,6 +1575,7 @@
                 newParent.setResumedActivity(this, "onParentChanged");
                 mImeInsetsFrozenUntilStartInput = false;
             }
+            mLetterboxUiController.onActivityParentChanged(newParent);
         }
 
         if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -1997,6 +1999,7 @@
 
             mOverrideTaskTransition = options.getOverrideTaskTransition();
             mDismissKeyguard = options.getDismissKeyguard();
+            mShareIdentity = options.getShareIdentity();
         }
 
         ColorDisplayService.ColorDisplayServiceInternal cds = LocalServices.getService(
@@ -5470,7 +5473,8 @@
         // no animation but there will still be a transition set.
         // We still need to delay hiding the surface such that it
         // can be synchronized with showing the next surface in the transition.
-        if (!isVisible() && !delayed && !displayContent.mAppTransition.isTransitionSet()) {
+        if (!usingShellTransitions && !isVisible() && !delayed
+                && !displayContent.mAppTransition.isTransitionSet()) {
             SurfaceControl.openTransaction();
             try {
                 forAllWindows(win -> {
@@ -7400,6 +7404,11 @@
     }
 
     @Override
+    boolean showSurfaceOnCreation() {
+        return false;
+    }
+
+    @Override
     void prepareSurfaces() {
         final boolean show = isVisible() || isAnimating(PARENTS,
                 ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
@@ -7638,6 +7647,15 @@
     @Configuration.Orientation
     @Override
     int getRequestedConfigurationOrientation(boolean forDisplay) {
+        if (mLetterboxUiController.hasInheritedOrientation()) {
+            final RootDisplayArea root = getRootDisplayArea();
+            if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) {
+                return ActivityInfo.reverseOrientation(
+                        mLetterboxUiController.getInheritedOrientation());
+            } else {
+                return mLetterboxUiController.getInheritedOrientation();
+            }
+        }
         if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) {
             // We use Task here because we want to be consistent with what happens in
             // multi-window mode where other tasks orientations are ignored.
@@ -7765,6 +7783,9 @@
 
     @Nullable
     CompatDisplayInsets getCompatDisplayInsets() {
+        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+            return mLetterboxUiController.getInheritedCompatDisplayInsets();
+        }
         return mCompatDisplayInsets;
     }
 
@@ -7847,6 +7868,10 @@
 
     // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
     private void updateCompatDisplayInsets() {
+        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+            mCompatDisplayInsets =  mLetterboxUiController.getInheritedCompatDisplayInsets();
+            return;
+        }
         if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) {
             // The override configuration is set only once in size compatibility mode.
             return;
@@ -7908,6 +7933,9 @@
 
     @Override
     float getCompatScale() {
+        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+            return mLetterboxUiController.getInheritedSizeCompatScale();
+        }
         return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
     }
 
@@ -8017,6 +8045,16 @@
     }
 
     /**
+     * @return The orientation to use to understand if reachability is enabled.
+     */
+    @ActivityInfo.ScreenOrientation
+    int getOrientationForReachability() {
+        return mLetterboxUiController.hasInheritedLetterboxBehavior()
+                ? mLetterboxUiController.getInheritedOrientation()
+                : getRequestedConfigurationOrientation();
+    }
+
+    /**
      * Returns whether activity bounds are letterboxed.
      *
      * <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link
@@ -8056,6 +8094,10 @@
         if (!ignoreVisibility && !mVisibleRequested) {
             return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
         }
+        // TODO(b/256564921): Investigate if we need new metrics for translucent activities
+        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+            return mLetterboxUiController.getInheritedAppCompatState();
+        }
         if (mInSizeCompatModeForBounds) {
             return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
         }
@@ -8526,6 +8568,11 @@
     }
 
     private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) {
+        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+            // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity
+            // is letterboxed.
+            return false;
+        }
         final int appWidth = appBounds.width();
         final int appHeight = appBounds.height();
         final int containerAppWidth = containerBounds.width();
@@ -8546,10 +8593,11 @@
 
         // The rest of the condition is that only one side is smaller than the container, but it
         // still needs to exclude the cases where the size is limited by the fixed aspect ratio.
-        if (info.getMaxAspectRatio() > 0) {
+        final float maxAspectRatio = getMaxAspectRatio();
+        if (maxAspectRatio > 0) {
             final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
                     / Math.min(appWidth, appHeight);
-            if (aspectRatio >= info.getMaxAspectRatio()) {
+            if (aspectRatio >= maxAspectRatio) {
                 // The current size has reached the max aspect ratio.
                 return false;
             }
@@ -8771,7 +8819,7 @@
     // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
     private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
             Rect containingBounds, float desiredAspectRatio) {
-        final float maxAspectRatio = info.getMaxAspectRatio();
+        final float maxAspectRatio = getMaxAspectRatio();
         final Task rootTask = getRootTask();
         final float minAspectRatio = getMinAspectRatio();
         final TaskFragment organizedTf = getOrganizedTaskFragment();
@@ -8878,6 +8926,9 @@
      * Returns the min aspect ratio of this activity.
      */
     float getMinAspectRatio() {
+        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+            return mLetterboxUiController.getInheritedMinAspectRatio();
+        }
         if (info.applicationInfo == null) {
             return info.getMinAspectRatio();
         }
@@ -8922,11 +8973,18 @@
                 && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
     }
 
+    float getMaxAspectRatio() {
+        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+            return mLetterboxUiController.getInheritedMaxAspectRatio();
+        }
+        return info.getMaxAspectRatio();
+    }
+
     /**
      * Returns true if the activity has maximum or minimum aspect ratio.
      */
     private boolean hasFixedAspectRatio() {
-        return info.getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
+        return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2f4d154..2762c45 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1823,7 +1823,7 @@
         if (mPreferredTaskDisplayArea != null) {
             final DisplayContent displayContent = mRootWindowContainer.getDisplayContentOrCreate(
                     mPreferredTaskDisplayArea.getDisplayId());
-            if (displayContent != null && displayContent.mDwpcHelper.hasController()) {
+            if (displayContent != null) {
                 final int targetWindowingMode = (targetTask != null)
                         ? targetTask.getWindowingMode() : displayContent.getWindowingMode();
                 final int launchingFromDisplayId =
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 8a247cf..33c90a0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -395,7 +395,7 @@
 
         final DisplayContent displayContent =
                 mRootWindowContainer.getDisplayContentOrCreate(displayId);
-        if (displayContent != null && displayContent.mDwpcHelper.hasController()) {
+        if (displayContent != null) {
             final ArrayList<ActivityInfo> activities = new ArrayList<>();
             if (activityInfo != null) {
                 activities.add(activityInfo);
@@ -405,10 +405,8 @@
                     activities.add(r.info);
                 });
             }
-            if (!displayContent.mDwpcHelper.canContainActivities(activities,
-                    displayContent.getWindowingMode())) {
-                return false;
-            }
+            return displayContent.mDwpcHelper.canContainActivities(activities,
+                        displayContent.getWindowingMode());
         }
 
         return true;
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 6e23ed9..8d5d0d5 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.content.Context.MEDIA_PROJECTION_SERVICE;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
@@ -27,8 +28,10 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.media.projection.MediaProjectionManager;
+import android.media.projection.IMediaProjectionManager;
 import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.provider.DeviceConfig;
 import android.view.ContentRecordingSession;
 import android.view.Display;
@@ -83,13 +86,7 @@
     private int mLastOrientation = ORIENTATION_UNDEFINED;
 
     ContentRecorder(@NonNull DisplayContent displayContent) {
-        this(displayContent, () -> {
-            MediaProjectionManager mpm = displayContent.mWmService.mContext.getSystemService(
-                    MediaProjectionManager.class);
-            if (mpm != null) {
-                mpm.stopActiveProjection();
-            }
-        });
+        this(displayContent, new RemoteMediaProjectionManagerWrapper());
     }
 
     @VisibleForTesting
@@ -445,6 +442,9 @@
                 .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */)
                 .apply();
         mLastRecordedBounds = new Rect(recordedContentBounds);
+        // Request to notify the client about the resize.
+        mMediaProjectionManager.notifyActiveProjectionCapturedContentResized(
+                mLastRecordedBounds.width(), mLastRecordedBounds.height());
     }
 
     /**
@@ -503,6 +503,56 @@
 
     @VisibleForTesting interface MediaProjectionManagerWrapper {
         void stopActiveProjection();
+        void notifyActiveProjectionCapturedContentResized(int width, int height);
+    }
+
+    private static final class RemoteMediaProjectionManagerWrapper implements
+            MediaProjectionManagerWrapper {
+        @Nullable private IMediaProjectionManager mIMediaProjectionManager = null;
+
+        @Override
+        public void stopActiveProjection() {
+            fetchMediaProjectionManager();
+            if (mIMediaProjectionManager == null) {
+                return;
+            }
+            try {
+                mIMediaProjectionManager.stopActiveProjection();
+            } catch (RemoteException e) {
+                ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+                        "Unable to tell MediaProjectionManagerService to stop the active "
+                                + "projection: %s",
+                        e);
+            }
+        }
+
+        @Override
+        public void notifyActiveProjectionCapturedContentResized(int width, int height) {
+            fetchMediaProjectionManager();
+            if (mIMediaProjectionManager == null) {
+                return;
+            }
+            try {
+                mIMediaProjectionManager.notifyActiveProjectionCapturedContentResized(width,
+                        height);
+            } catch (RemoteException e) {
+                ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+                        "Unable to tell MediaProjectionManagerService about resizing the active "
+                                + "projection: %s",
+                        e);
+            }
+        }
+
+        private void fetchMediaProjectionManager() {
+            if (mIMediaProjectionManager != null) {
+                return;
+            }
+            IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
+            if (b == null) {
+                return;
+            }
+            mIMediaProjectionManager = IMediaProjectionManager.Stub.asInterface(b);
+        }
     }
 
     private boolean isRecordingContentTask() {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 69fd00c..72ed108 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -22,12 +22,14 @@
 import android.content.pm.ActivityInfo;
 import android.os.UserHandle;
 import android.util.ArraySet;
+import android.util.Slog;
 import android.window.DisplayWindowPolicyController;
 
 import java.io.PrintWriter;
 import java.util.List;
 
 class DisplayWindowPolicyControllerHelper {
+    private static final String TAG = "DisplayWindowPolicyControllerHelper";
 
     private final DisplayContent mDisplayContent;
 
@@ -69,6 +71,17 @@
     public boolean canContainActivities(@NonNull List<ActivityInfo> activities,
             @WindowConfiguration.WindowingMode int windowingMode) {
         if (mDisplayWindowPolicyController == null) {
+            for (int i = activities.size() - 1; i >= 0; i--) {
+                final ActivityInfo aInfo = activities.get(i);
+                if (aInfo.requiredDisplayCategory != null) {
+                    Slog.e(TAG,
+                            String.format("Activity with requiredDisplayCategory='%s' cannot be"
+                                            + " displayed on display %d because that display does"
+                                            + " not have a matching category",
+                                    aInfo.requiredDisplayCategory, mDisplayContent.mDisplayId));
+                    return false;
+                }
+            }
             return true;
         }
         return mDisplayWindowPolicyController.canContainActivities(activities, windowingMode);
@@ -81,6 +94,14 @@
             @WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
             boolean isNewTask) {
         if (mDisplayWindowPolicyController == null) {
+            if (activityInfo.requiredDisplayCategory != null) {
+                Slog.e(TAG,
+                        String.format("Activity with requiredDisplayCategory='%s' cannot be"
+                                + " launched on display %d because that display does"
+                                + " not have a matching category",
+                                activityInfo.requiredDisplayCategory, mDisplayContent.mDisplayId));
+                return false;
+            }
             return true;
         }
         return mDisplayWindowPolicyController.canActivityBeLaunched(activityInfo, windowingMode,
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index c19353c..127a7bf 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Color;
+import android.provider.DeviceConfig;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -103,6 +104,10 @@
 
     final Context mContext;
 
+    // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
+    @NonNull
+    private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+
     // Aspect ratio of letterbox for fixed orientation, values <=
     // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored.
     private float mFixedOrientationLetterboxAspectRatio;
@@ -165,9 +170,12 @@
     // Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.
     private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled;
 
-    // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
-    @NonNull
-    private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+    // Whether letterboxing strategy is enabled for translucent activities. If {@value false}
+    // all the feature is disabled
+    private boolean mTranslucentLetterboxingEnabled;
+
+    // Allows to enable letterboxing strategy for translucent activities ignoring flags.
+    private boolean mTranslucentLetterboxingOverrideEnabled;
 
     LetterboxConfiguration(Context systemUiContext) {
         this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
@@ -206,6 +214,8 @@
                 R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
         mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
+        mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean(
+                R.bool.config_letterboxIsEnabledForTranslucentActivities);
         mLetterboxConfigurationPersister = letterboxConfigurationPersister;
         mLetterboxConfigurationPersister.start();
     }
@@ -817,6 +827,32 @@
                 R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
     }
 
+    boolean isTranslucentLetterboxingEnabled() {
+        return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled
+                && isTranslucentLetterboxingAllowed());
+    }
+
+    void setTranslucentLetterboxingEnabled(boolean translucentLetterboxingEnabled) {
+        mTranslucentLetterboxingEnabled = translucentLetterboxingEnabled;
+    }
+
+    void setTranslucentLetterboxingOverrideEnabled(
+            boolean translucentLetterboxingOverrideEnabled) {
+        mTranslucentLetterboxingOverrideEnabled = translucentLetterboxingOverrideEnabled;
+        setTranslucentLetterboxingEnabled(translucentLetterboxingOverrideEnabled);
+    }
+
+    /**
+     * Resets whether we use the constraints override strategy for letterboxing when dealing
+     * with translucent activities {@link R.bool.config_letterboxIsEnabledForTranslucentActivities}.
+     */
+    void resetTranslucentLetterboxingEnabled() {
+        final boolean newValue = mContext.getResources().getBoolean(
+                R.bool.config_letterboxIsEnabledForTranslucentActivities);
+        setTranslucentLetterboxingEnabled(newValue);
+        setTranslucentLetterboxingOverrideEnabled(false);
+    }
+
     /** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */
     private void updatePositionForHorizontalReachability(
             Function<Integer, Integer> newHorizonalPositionFun) {
@@ -839,4 +875,9 @@
                 nextVerticalPosition);
     }
 
+    // TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener
+    static boolean isTranslucentLetterboxingAllowed() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                "enable_translucent_activity_letterbox", false);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index bcea6f4..a53a5fc 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -27,6 +28,7 @@
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
@@ -82,13 +84,44 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
 
+    private static final float UNDEFINED_ASPECT_RATIO = 0f;
+
     private final Point mTmpPoint = new Point();
 
     private final LetterboxConfiguration mLetterboxConfiguration;
+
     private final ActivityRecord mActivityRecord;
 
+    /*
+     * WindowContainerListener responsible to make translucent activities inherit
+     * constraints from the first opaque activity beneath them. It's null for not
+     * translucent activities.
+     */
+    @Nullable
+    private WindowContainerListener mLetterboxConfigListener;
+
     private boolean mShowWallpaperForLetterboxBackground;
 
+    // In case of transparent activities we might need to access the aspectRatio of the
+    // first opaque activity beneath.
+    private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
+    private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
+
+    @Configuration.Orientation
+    private int mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
+
+    // The app compat state for the opaque activity if any
+    private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
+
+    // If true it means that the opaque activity beneath a translucent one is in SizeCompatMode.
+    private boolean mIsInheritedInSizeCompatMode;
+
+    // This is the SizeCompatScale of the opaque activity beneath a translucent one
+    private float mInheritedSizeCompatScale;
+
+    // The CompatDisplayInsets of the opaque activity beneath the translucent one.
+    private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets;
+
     @Nullable
     private Letterbox mLetterbox;
 
@@ -220,7 +253,9 @@
                     : mActivityRecord.inMultiWindowMode()
                             ? mActivityRecord.getTask().getBounds()
                             : mActivityRecord.getRootTask().getParent().getBounds();
-            mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint);
+            final Rect innerFrame = hasInheritedLetterboxBehavior()
+                    ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame();
+            mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
         } else if (mLetterbox != null) {
             mLetterbox.hide();
         }
@@ -305,7 +340,9 @@
     }
 
     private void handleHorizontalDoubleTap(int x) {
-        if (!isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) {
+        // TODO(b/260857308): Investigate if enabling reachability for translucent activity
+        if (hasInheritedLetterboxBehavior() || !isHorizontalReachabilityEnabled()
+                || mActivityRecord.isInTransition()) {
             return;
         }
 
@@ -341,7 +378,9 @@
     }
 
     private void handleVerticalDoubleTap(int y) {
-        if (!isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) {
+        // TODO(b/260857308): Investigate if enabling reachability for translucent activity
+        if (hasInheritedLetterboxBehavior() || !isVerticalReachabilityEnabled()
+                || mActivityRecord.isInTransition()) {
             return;
         }
 
@@ -390,7 +429,7 @@
                 && parentConfiguration.windowConfiguration.getWindowingMode()
                         == WINDOWING_MODE_FULLSCREEN
                 && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE
-                && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT);
+                && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT);
     }
 
     private boolean isHorizontalReachabilityEnabled() {
@@ -412,7 +451,7 @@
                 && parentConfiguration.windowConfiguration.getWindowingMode()
                         == WINDOWING_MODE_FULLSCREEN
                 && (parentConfiguration.orientation == ORIENTATION_PORTRAIT
-                && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_LANDSCAPE);
+                && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE);
     }
 
     private boolean isVerticalReachabilityEnabled() {
@@ -576,9 +615,7 @@
         // Rounded corners should be displayed above the taskbar.
         bounds.bottom =
                 Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top);
-        if (mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f) {
-            bounds.scale(1.0f / mActivityRecord.getCompatScale());
-        }
+        scaleIfNeeded(bounds);
     }
 
     private int getInsetsStateCornerRadius(
@@ -788,4 +825,144 @@
                 w.mAttrs.insetsFlags.appearance
         );
     }
+
+    /**
+     * Handles translucent activities letterboxing inheriting constraints from the
+     * first opaque activity beneath.
+     * @param parent The parent container.
+     */
+    void onActivityParentChanged(WindowContainer<?> parent) {
+        if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
+            return;
+        }
+        if (mLetterboxConfigListener != null) {
+            mLetterboxConfigListener.onRemoved();
+            clearInheritedConfig();
+        }
+        // In case mActivityRecord.getCompatDisplayInsets() is not null we don't apply the
+        // opaque activity constraints because we're expecting the activity is already letterboxed.
+        if (mActivityRecord.getTask() == null || mActivityRecord.getCompatDisplayInsets() != null
+                || mActivityRecord.fillsParent()) {
+            return;
+        }
+        final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
+                ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */,
+                true /* traverseTopToBottom */);
+        if (firstOpaqueActivityBeneath == null
+                || mActivityRecord.launchedFromUid != firstOpaqueActivityBeneath.getUid()) {
+            // We skip letterboxing if the translucent activity doesn't have any opaque
+            // activities beneath of if it's launched from a different user (e.g. notification)
+            return;
+        }
+        inheritConfiguration(firstOpaqueActivityBeneath);
+        mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation(
+                mActivityRecord, firstOpaqueActivityBeneath,
+                (opaqueConfig, transparentConfig) -> {
+                    final Configuration mutatedConfiguration = new Configuration();
+                    final Rect parentBounds = parent.getWindowConfiguration().getBounds();
+                    final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds();
+                    final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds();
+                    // We cannot use letterboxBounds directly here because the position relies on
+                    // letterboxing. Using letterboxBounds directly, would produce a double offset.
+                    bounds.set(parentBounds.left, parentBounds.top,
+                            parentBounds.left + letterboxBounds.width(),
+                            parentBounds.top + letterboxBounds.height());
+                    // We need to initialize appBounds to avoid NPE. The actual value will
+                    // be set ahead when resolving the Configuration for the activity.
+                    mutatedConfiguration.windowConfiguration.setAppBounds(new Rect());
+                    return mutatedConfiguration;
+                });
+    }
+
+    /**
+     * @return {@code true} if the current activity is translucent with an opaque activity
+     * beneath. In this case it will inherit bounds, orientation and aspect ratios from
+     * the first opaque activity beneath.
+     */
+    boolean hasInheritedLetterboxBehavior() {
+        return mLetterboxConfigListener != null && !mActivityRecord.matchParentBounds();
+    }
+
+    /**
+     * @return {@code true} if the current activity is translucent with an opaque activity
+     * beneath and needs to inherit its orientation.
+     */
+    boolean hasInheritedOrientation() {
+        // To force a different orientation, the transparent one needs to have an explicit one
+        // otherwise the existing one is fine and the actual orientation will depend on the
+        // bounds.
+        // To avoid wrong behaviour, we're not forcing orientation for activities with not
+        // fixed orientation (e.g. permission dialogs).
+        return hasInheritedLetterboxBehavior()
+                && mActivityRecord.mOrientation != SCREEN_ORIENTATION_UNSPECIFIED;
+    }
+
+    float getInheritedMinAspectRatio() {
+        return mInheritedMinAspectRatio;
+    }
+
+    float getInheritedMaxAspectRatio() {
+        return mInheritedMaxAspectRatio;
+    }
+
+    int getInheritedAppCompatState() {
+        return mInheritedAppCompatState;
+    }
+
+    float getInheritedSizeCompatScale() {
+        return mInheritedSizeCompatScale;
+    }
+
+    @Configuration.Orientation
+    int getInheritedOrientation() {
+        return mInheritedOrientation;
+    }
+
+    public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() {
+        return mInheritedCompatDisplayInsets;
+    }
+
+    private void inheritConfiguration(ActivityRecord firstOpaque) {
+        // To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities
+        // which are not already providing one (e.g. permission dialogs) and presumably also
+        // not resizable.
+        if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) {
+            mInheritedMinAspectRatio = firstOpaque.getMinAspectRatio();
+        }
+        if (mActivityRecord.getMaxAspectRatio() != UNDEFINED_ASPECT_RATIO) {
+            mInheritedMaxAspectRatio = firstOpaque.getMaxAspectRatio();
+        }
+        mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation();
+        mInheritedAppCompatState = firstOpaque.getAppCompatState();
+        mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode();
+        mInheritedSizeCompatScale = firstOpaque.getCompatScale();
+        mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets();
+    }
+
+    private void clearInheritedConfig() {
+        mLetterboxConfigListener = null;
+        mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
+        mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
+        mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
+        mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
+        mIsInheritedInSizeCompatMode = false;
+        mInheritedSizeCompatScale = 1f;
+        mInheritedCompatDisplayInsets = null;
+    }
+
+    private void scaleIfNeeded(Rect bounds) {
+        if (boundsNeedToScale()) {
+            bounds.scale(1.0f / mActivityRecord.getCompatScale());
+        }
+    }
+
+    private boolean boundsNeedToScale() {
+        if (hasInheritedLetterboxBehavior()) {
+            return mIsInheritedInSizeCompatMode
+                    && mInheritedSizeCompatScale < 1.0f;
+        } else {
+            return mActivityRecord.inSizeCompatMode()
+                    && mActivityRecord.getCompatScale() < 1.0f;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 03e3e93..07e3b83 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.isStartResultSuccessful;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ActivityTaskManager.INVALID_WINDOWING_MODE;
 import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED;
 import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
@@ -443,6 +444,8 @@
     @Surface.Rotation
     private int mRotation;
 
+    int mMultiWindowRestoreWindowingMode = INVALID_WINDOWING_MODE;
+
     /**
      * Last requested orientation reported to DisplayContent. This is different from {@link
      * #mOrientation} in the sense that this takes activities' requested orientation into
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1d17cd4..64574a7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -642,7 +642,6 @@
         if (showSurfaceOnCreation()) {
             getSyncTransaction().show(mSurfaceControl);
         }
-        onSurfaceShown(getSyncTransaction());
         updateSurfacePositionNonOrganized();
         if (mLastMagnificationSpec != null) {
             applyMagnificationSpec(getSyncTransaction(), mLastMagnificationSpec);
@@ -697,13 +696,6 @@
         scheduleAnimation();
     }
 
-    /**
-     * Called when the surface is shown for the first time.
-     */
-    void onSurfaceShown(Transaction t) {
-        // do nothing
-    }
-
     // Temp. holders for a chain of containers we are currently processing.
     private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList<>();
     private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList<>();
@@ -3989,27 +3981,54 @@
         unregisterConfigurationChangeListener(listener);
     }
 
+    static void overrideConfigurationPropagation(WindowContainer<?> receiver,
+            WindowContainer<?> supplier) {
+        overrideConfigurationPropagation(receiver, supplier, null /* configurationMerger */);
+    }
+
     /**
      * Forces the receiver container to always use the configuration of the supplier container as
      * its requested override configuration. It allows to propagate configuration without changing
      * the relationship between child and parent.
+     *
+     * @param receiver            The {@link WindowContainer<?>} which will receive the {@link
+     *                            Configuration} result of the merging operation.
+     * @param supplier            The {@link WindowContainer<?>} which provides the initial {@link
+     *                            Configuration}.
+     * @param configurationMerger A {@link ConfigurationMerger} which combines the {@link
+     *                            Configuration} of the receiver and the supplier.
      */
-    static void overrideConfigurationPropagation(WindowContainer<?> receiver,
-            WindowContainer<?> supplier) {
+    static WindowContainerListener overrideConfigurationPropagation(WindowContainer<?> receiver,
+            WindowContainer<?> supplier, @Nullable ConfigurationMerger configurationMerger) {
         final ConfigurationContainerListener listener = new ConfigurationContainerListener() {
             @Override
             public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) {
-                receiver.onRequestedOverrideConfigurationChanged(supplier.getConfiguration());
+                final Configuration mergedConfiguration =
+                        configurationMerger != null
+                                ? configurationMerger.merge(mergedOverrideConfig,
+                                receiver.getConfiguration())
+                                : supplier.getConfiguration();
+                receiver.onRequestedOverrideConfigurationChanged(mergedConfiguration);
             }
         };
         supplier.registerConfigurationChangeListener(listener);
-        receiver.registerWindowContainerListener(new WindowContainerListener() {
+        final WindowContainerListener wcListener = new WindowContainerListener() {
             @Override
             public void onRemoved() {
                 receiver.unregisterWindowContainerListener(this);
                 supplier.unregisterConfigurationChangeListener(listener);
             }
-        });
+        };
+        receiver.registerWindowContainerListener(wcListener);
+        return wcListener;
+    }
+
+    /**
+     * Abstraction for functions merging two {@link Configuration} objects into one.
+     */
+    @FunctionalInterface
+    interface ConfigurationMerger {
+        Configuration merge(Configuration first, Configuration second);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f6f825f..be1e7e6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -168,7 +168,6 @@
 import android.app.IAssistDataReceiver;
 import android.app.WindowConfiguration;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -178,7 +177,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.TestUtilityService;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
@@ -3718,19 +3716,9 @@
                 return;
             }
 
-            try {
-                // TODO(b/221898546): remove the following and convert to jni
-                IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlingerAIDL");
-                if (surfaceFlinger != null) {
-                    ProtoLog.i(WM_ERROR, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
-                    Parcel data = Parcel.obtain();
-                    data.writeInterfaceToken("android.gui.ISurfaceComposer");
-                    surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
-                            data, null, 0);
-                    data.recycle();
-                }
-            } catch (RemoteException ex) {
-                ProtoLog.e(WM_ERROR, "Boot completed: SurfaceFlinger is dead!");
+            if (!SurfaceControl.bootFinished()) {
+                ProtoLog.w(WM_ERROR, "performEnableScreen: bootFinished() failed.");
+                return;
             }
 
             EventLogTags.writeWmBootAnimationDone(SystemClock.uptimeMillis());
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 46a30fb..060784d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -978,6 +978,29 @@
         return 0;
     }
 
+    private int runSetTranslucentLetterboxingEnabled(PrintWriter pw) {
+        String arg = getNextArg();
+        final boolean enabled;
+        switch (arg) {
+            case "true":
+            case "1":
+                enabled = true;
+                break;
+            case "false":
+            case "0":
+                enabled = false;
+                break;
+            default:
+                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+                return -1;
+        }
+
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(enabled);
+        }
+        return 0;
+    }
+
     private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
         if (peekNextArg() == null) {
             getErrPrintWriter().println("Error: No arguments provided.");
@@ -1033,6 +1056,9 @@
                 case "--isSplitScreenAspectRatioForUnresizableAppsEnabled":
                     runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(pw);
                     break;
+                case "--isTranslucentLetterboxingEnabled":
+                    runSetTranslucentLetterboxingEnabled(pw);
+                    break;
                 default:
                     getErrPrintWriter().println(
                             "Error: Unrecognized letterbox style option: " + arg);
@@ -1096,6 +1122,9 @@
                         mLetterboxConfiguration
                                 .getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
                         break;
+                    case "isTranslucentLetterboxingEnabled":
+                        mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
+                        break;
                     default:
                         getErrPrintWriter().println(
                                 "Error: Unrecognized letterbox style option: " + arg);
@@ -1196,6 +1225,7 @@
             mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
             mLetterboxConfiguration.resetIsEducationEnabled();
             mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+            mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
         }
     }
 
@@ -1232,7 +1262,6 @@
             pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
                     + mLetterboxConfiguration
                             .getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
-
             pw.println("Background type: "
                     + LetterboxConfiguration.letterboxBackgroundTypeToString(
                             mLetterboxConfiguration.getLetterboxBackgroundType()));
@@ -1242,6 +1271,12 @@
                     + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius());
             pw.println("    Wallpaper dark scrim alpha: "
                     + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha());
+
+            if (mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
+                pw.println("Letterboxing for translucent activities: enabled");
+            } else {
+                pw.println("Letterboxing for translucent activities: disabled");
+            }
         }
         return 0;
     }
@@ -1434,12 +1469,16 @@
         pw.println("      --isSplitScreenAspectRatioForUnresizableAppsEnabled [true|1|false|0]");
         pw.println("        Whether using split screen aspect ratio as a default aspect ratio for");
         pw.println("        unresizable apps.");
+        pw.println("      --isTranslucentLetterboxingEnabled [true|1|false|0]");
+        pw.println("        Whether letterboxing for translucent activities is enabled.");
+
         pw.println("  reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
         pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
         pw.println("      |horizontalPositionMultiplier|verticalPositionMultiplier");
         pw.println("      |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled");
-        pw.println("      isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
-        pw.println("      ||defaultPositionMultiplierForVerticalReachability]");
+        pw.println("      |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
+        pw.println("      |isTranslucentLetterboxingEnabled");
+        pw.println("      |defaultPositionMultiplierForVerticalReachability]");
         pw.println("    Resets overrides to default values for specified properties separated");
         pw.println("    by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
         pw.println("    If no arguments provided, all values will be reset.");
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 07819b9..e661688 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -71,7 +71,6 @@
         "com_android_server_PersistentDataBlockService.cpp",
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
-        "com_android_server_pm_Settings.cpp",
         "com_android_server_sensor_SensorService.cpp",
         "com_android_server_wm_TaskFpsCallbackController.cpp",
         "onload.cpp",
@@ -153,7 +152,6 @@
         "libpsi",
         "libdataloader",
         "libincfs",
-        "liblz4",
         "android.hardware.audio.common@2.0",
         "android.media.audio.common.types-V1-ndk",
         "android.hardware.broadcastradio@1.0",
@@ -234,26 +232,3 @@
         "com_android_server_app_GameManagerService.cpp",
     ],
 }
-
-// Settings JNI library for unit tests.
-cc_library_shared {
-    name: "libservices.core.settings.testonly",
-    defaults: ["libservices.core-libs"],
-
-    cpp_std: "c++2a",
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-unused-parameter",
-        "-Wthread-safety",
-    ],
-
-    srcs: [
-        "com_android_server_pm_Settings.cpp",
-        "onload_settings.cpp",
-    ],
-
-    header_libs: [
-        "bionic_libc_platform_headers",
-    ],
-}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 145e088..c36c571 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -120,6 +120,7 @@
     jmethodID getExcludedDeviceNames;
     jmethodID getInputPortAssociations;
     jmethodID getInputUniqueIdAssociations;
+    jmethodID getDeviceTypeAssociations;
     jmethodID getKeyRepeatTimeout;
     jmethodID getKeyRepeatDelay;
     jmethodID getHoverTapTimeout;
@@ -411,6 +412,8 @@
     void ensureSpriteControllerLocked();
     sp<SurfaceControl> getParentSurfaceForPointers(int displayId);
     static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
+    std::unordered_map<std::string, std::string> readMapFromInterleavedJavaArray(
+            jmethodID method, const char* methodName);
 
     static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); }
 };
@@ -583,21 +586,14 @@
         }
         env->DeleteLocalRef(portAssociations);
     }
-    outConfig->uniqueIdAssociations.clear();
-    jobjectArray uniqueIdAssociations = jobjectArray(
-            env->CallObjectMethod(mServiceObj, gServiceClassInfo.getInputUniqueIdAssociations));
-    if (!checkAndClearExceptionFromCallback(env, "getInputUniqueIdAssociations") &&
-        uniqueIdAssociations) {
-        jsize length = env->GetArrayLength(uniqueIdAssociations);
-        for (jsize i = 0; i < length / 2; i++) {
-            std::string inputDeviceUniqueId =
-                    getStringElementFromJavaArray(env, uniqueIdAssociations, 2 * i);
-            std::string displayUniqueId =
-                    getStringElementFromJavaArray(env, uniqueIdAssociations, 2 * i + 1);
-            outConfig->uniqueIdAssociations.insert({inputDeviceUniqueId, displayUniqueId});
-        }
-        env->DeleteLocalRef(uniqueIdAssociations);
-    }
+
+    outConfig->uniqueIdAssociations =
+            readMapFromInterleavedJavaArray(gServiceClassInfo.getInputUniqueIdAssociations,
+                                            "getInputUniqueIdAssociations");
+
+    outConfig->deviceTypeAssociations =
+            readMapFromInterleavedJavaArray(gServiceClassInfo.getDeviceTypeAssociations,
+                                            "getDeviceTypeAssociations");
 
     jint hoverTapTimeout = env->CallIntMethod(mServiceObj,
             gServiceClassInfo.getHoverTapTimeout);
@@ -647,6 +643,23 @@
     } // release lock
 }
 
+std::unordered_map<std::string, std::string> NativeInputManager::readMapFromInterleavedJavaArray(
+        jmethodID method, const char* methodName) {
+    JNIEnv* env = jniEnv();
+    jobjectArray javaArray = jobjectArray(env->CallObjectMethod(mServiceObj, method));
+    std::unordered_map<std::string, std::string> map;
+    if (!checkAndClearExceptionFromCallback(env, methodName) && javaArray) {
+        jsize length = env->GetArrayLength(javaArray);
+        for (jsize i = 0; i < length / 2; i++) {
+            std::string key = getStringElementFromJavaArray(env, javaArray, 2 * i);
+            std::string value = getStringElementFromJavaArray(env, javaArray, 2 * i + 1);
+            map.insert({key, value});
+        }
+    }
+    env->DeleteLocalRef(javaArray);
+    return map;
+}
+
 std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerController(
         int32_t /* deviceId */) {
     ATRACE_CALL();
@@ -2237,6 +2250,12 @@
             InputReaderConfiguration::CHANGE_DISPLAY_INFO);
 }
 
+static void nativeChangeTypeAssociation(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    im->getInputManager()->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_DEVICE_TYPE);
+}
+
 static void nativeSetMotionClassifierEnabled(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
@@ -2425,6 +2444,7 @@
         {"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
         {"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
         {"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
+        {"changeTypeAssociation", "()V", (void*)nativeChangeTypeAssociation},
         {"setDisplayEligibilityForPointerCapture", "(IZ)V",
          (void*)nativeSetDisplayEligibilityForPointerCapture},
         {"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
@@ -2546,6 +2566,9 @@
     GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociations, clazz,
                   "getInputUniqueIdAssociations", "()[Ljava/lang/String;");
 
+    GET_METHOD_ID(gServiceClassInfo.getDeviceTypeAssociations, clazz, "getDeviceTypeAssociations",
+                  "()[Ljava/lang/String;");
+
     GET_METHOD_ID(gServiceClassInfo.getKeyRepeatTimeout, clazz,
             "getKeyRepeatTimeout", "()I");
 
diff --git a/services/core/jni/com_android_server_pm_Settings.cpp b/services/core/jni/com_android_server_pm_Settings.cpp
deleted file mode 100644
index 9633a11..0000000
--- a/services/core/jni/com_android_server_pm_Settings.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2022 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.
- */
-
-#define ATRACE_TAG ATRACE_TAG_ADB
-#define LOG_TAG "Settings-jni"
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/no_destructor.h>
-#include <core_jni_helpers.h>
-#include <lz4frame.h>
-#include <nativehelper/JNIHelp.h>
-
-#include <vector>
-
-namespace android {
-
-namespace {
-
-struct LZ4FCContextDeleter {
-    void operator()(LZ4F_cctx* cctx) { LZ4F_freeCompressionContext(cctx); }
-};
-
-static constexpr int LZ4_BUFFER_SIZE = 64 * 1024;
-
-static bool writeToFile(std::vector<char>& outBuffer, int fdOut) {
-    if (!android::base::WriteFully(fdOut, outBuffer.data(), outBuffer.size())) {
-        PLOG(ERROR) << "Error to write to output file";
-        return false;
-    }
-    outBuffer.clear();
-    return true;
-}
-
-static bool compressAndWriteLz4(LZ4F_cctx* context, std::vector<char>& inBuffer,
-                                std::vector<char>& outBuffer, int fdOut) {
-    auto inSize = inBuffer.size();
-    if (inSize > 0) {
-        auto prvSize = outBuffer.size();
-        auto outSize = LZ4F_compressBound(inSize, nullptr);
-        outBuffer.resize(prvSize + outSize);
-        auto rc = LZ4F_compressUpdate(context, outBuffer.data() + prvSize, outSize, inBuffer.data(),
-                                      inSize, nullptr);
-        if (LZ4F_isError(rc)) {
-            LOG(ERROR) << "LZ4F_compressUpdate failed: " << LZ4F_getErrorName(rc);
-            return false;
-        }
-        outBuffer.resize(prvSize + rc);
-    }
-
-    if (outBuffer.size() > LZ4_BUFFER_SIZE) {
-        return writeToFile(outBuffer, fdOut);
-    }
-
-    return true;
-}
-
-static jboolean nativeCompressLz4(JNIEnv* env, jclass klass, jint fdIn, jint fdOut) {
-    LZ4F_cctx* cctx;
-    if (LZ4F_createCompressionContext(&cctx, LZ4F_VERSION) != 0) {
-        LOG(ERROR) << "Failed to initialize LZ4 compression context.";
-        return false;
-    }
-    std::unique_ptr<LZ4F_cctx, LZ4FCContextDeleter> context(cctx);
-
-    std::vector<char> inBuffer, outBuffer;
-    inBuffer.reserve(LZ4_BUFFER_SIZE);
-    outBuffer.reserve(2 * LZ4_BUFFER_SIZE);
-
-    LZ4F_preferences_t prefs;
-
-    memset(&prefs, 0, sizeof(prefs));
-
-    // Set compression parameters.
-    prefs.autoFlush = 0;
-    prefs.compressionLevel = 0;
-    prefs.frameInfo.blockMode = LZ4F_blockLinked;
-    prefs.frameInfo.blockSizeID = LZ4F_default;
-    prefs.frameInfo.blockChecksumFlag = LZ4F_noBlockChecksum;
-    prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
-    prefs.favorDecSpeed = 0;
-
-    struct stat sb;
-    if (fstat(fdIn, &sb) == -1) {
-        PLOG(ERROR) << "Failed to obtain input file size.";
-        return false;
-    }
-    prefs.frameInfo.contentSize = sb.st_size;
-
-    // Write header first.
-    outBuffer.resize(LZ4F_HEADER_SIZE_MAX);
-    auto rc = LZ4F_compressBegin(context.get(), outBuffer.data(), outBuffer.size(), &prefs);
-    if (LZ4F_isError(rc)) {
-        LOG(ERROR) << "LZ4F_compressBegin failed: " << LZ4F_getErrorName(rc);
-        return false;
-    }
-    outBuffer.resize(rc);
-
-    bool eof = false;
-    while (!eof) {
-        constexpr auto capacity = LZ4_BUFFER_SIZE;
-        inBuffer.resize(capacity);
-        auto read = TEMP_FAILURE_RETRY(::read(fdIn, inBuffer.data(), inBuffer.size()));
-        if (read < 0) {
-            PLOG(ERROR) << "Failed to read from input file.";
-            return false;
-        }
-
-        inBuffer.resize(read);
-
-        if (read == 0) {
-            eof = true;
-        }
-
-        if (!compressAndWriteLz4(context.get(), inBuffer, outBuffer, fdOut)) {
-            return false;
-        }
-    }
-
-    // Footer.
-    auto prvSize = outBuffer.size();
-    outBuffer.resize(outBuffer.capacity());
-    rc = LZ4F_compressEnd(context.get(), outBuffer.data() + prvSize, outBuffer.size() - prvSize,
-                          nullptr);
-    if (LZ4F_isError(rc)) {
-        LOG(ERROR) << "LZ4F_compressEnd failed: " << LZ4F_getErrorName(rc);
-        return false;
-    }
-    outBuffer.resize(prvSize + rc);
-
-    if (!writeToFile(outBuffer, fdOut)) {
-        return false;
-    }
-
-    return true;
-}
-
-static const JNINativeMethod method_table[] = {
-        {"nativeCompressLz4", "(II)Z", (void*)nativeCompressLz4},
-};
-
-} // namespace
-
-int register_android_server_com_android_server_pm_Settings(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, "com/android/server/pm/Settings", method_table,
-                                    NELEM(method_table));
-}
-
-} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 1845057..00f851f 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,7 +56,6 @@
 int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
 int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
 int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
-int register_android_server_com_android_server_pm_Settings(JNIEnv* env);
 int register_android_server_AdbDebuggingManager(JNIEnv* env);
 int register_android_server_FaceService(JNIEnv* env);
 int register_android_server_GpuService(JNIEnv* env);
@@ -115,7 +114,6 @@
     register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
     register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
     register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
-    register_android_server_com_android_server_pm_Settings(env);
     register_android_server_AdbDebuggingManager(env);
     register_android_server_FaceService(env);
     register_android_server_GpuService(env);
diff --git a/services/core/jni/onload_settings.cpp b/services/core/jni/onload_settings.cpp
deleted file mode 100644
index b21c34a..0000000
--- a/services/core/jni/onload_settings.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2022 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.
- */
-
-#include "jni.h"
-#include "utils/Log.h"
-
-namespace android {
-int register_android_server_com_android_server_pm_Settings(JNIEnv* env);
-};
-
-using namespace android;
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
-    JNIEnv* env = NULL;
-    jint result = -1;
-
-    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
-        ALOGE("GetEnv failed!");
-        return result;
-    }
-    ALOG_ASSERT(env, "Could not retrieve the env!");
-
-    register_android_server_com_android_server_pm_Settings(env);
-
-    return JNI_VERSION_1_4;
-}
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index b914051..f96c9293 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -44,9 +44,11 @@
                 </xs:element>
                 <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
                             maxOccurs="1"/>
-                <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" />
+                <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1"/>
                 <xs:element type="autoBrightness" name="autoBrightness" minOccurs="0"
-                            maxOccurs="1" />
+                            maxOccurs="1"/>
+                <xs:element type="refreshRateConfigs" name="refreshRate" minOccurs="0"
+                            maxOccurs="1"/>
                 <xs:element type="nonNegativeDecimal" name="screenBrightnessRampFastDecrease">
                     <xs:annotation name="final"/>
                 </xs:element>
@@ -324,7 +326,7 @@
                 <xs:annotation name="final"/>
             </xs:element>
         </xs:sequence>
-      </xs:complexType>
+    </xs:complexType>
 
     <!-- Thresholds for brightness changes. -->
     <xs:complexType name="thresholds">
@@ -452,4 +454,35 @@
             </xs:element>
         </xs:sequence>
     </xs:complexType>
+
+    <xs:complexType name="refreshRateConfigs">
+        <xs:element name="lowerBlockingZoneConfigs" type="blockingZoneConfig"
+                    minOccurs="0" maxOccurs="1">
+            <xs:annotation name="final"/>
+        </xs:element>
+        <xs:element name="higherBlockingZoneConfigs" type="blockingZoneConfig"
+                    minOccurs="0" maxOccurs="1">
+            <xs:annotation name="final"/>
+        </xs:element>
+    </xs:complexType>
+
+    <xs:complexType name="blockingZoneConfig">
+        <xs:element name="defaultRefreshRate" type="xs:nonNegativeInteger"
+                    minOccurs="1" maxOccurs="1">
+            <xs:annotation name="final"/>
+        </xs:element>
+        <xs:element name="blockingZoneThreshold" type="blockingZoneThreshold"
+                    minOccurs="1" maxOccurs="1">
+            <xs:annotation name="final"/>
+        </xs:element>
+    </xs:complexType>
+
+    <xs:complexType name="blockingZoneThreshold">
+        <xs:sequence>
+            <xs:element name="displayBrightnessPoint" type="displayBrightnessPoint"
+                        minOccurs="1" maxOccurs="unbounded">
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
 </xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index d89bd7c..6276eda 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -13,6 +13,19 @@
     method public void setEnabled(boolean);
   }
 
+  public class BlockingZoneConfig {
+    ctor public BlockingZoneConfig();
+    method public final com.android.server.display.config.BlockingZoneThreshold getBlockingZoneThreshold();
+    method public final java.math.BigInteger getDefaultRefreshRate();
+    method public final void setBlockingZoneThreshold(com.android.server.display.config.BlockingZoneThreshold);
+    method public final void setDefaultRefreshRate(java.math.BigInteger);
+  }
+
+  public class BlockingZoneThreshold {
+    ctor public BlockingZoneThreshold();
+    method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
+  }
+
   public class BrightnessThresholds {
     ctor public BrightnessThresholds();
     method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
@@ -76,6 +89,7 @@
     method public final com.android.server.display.config.SensorDetails getLightSensor();
     method public final com.android.server.display.config.SensorDetails getProxSensor();
     method public com.android.server.display.config.DisplayQuirks getQuirks();
+    method public com.android.server.display.config.RefreshRateConfigs getRefreshRate();
     method @NonNull public final java.math.BigDecimal getScreenBrightnessDefault();
     method @NonNull public final com.android.server.display.config.NitsMap getScreenBrightnessMap();
     method public final java.math.BigInteger getScreenBrightnessRampDecreaseMaxMillis();
@@ -97,6 +111,7 @@
     method public final void setLightSensor(com.android.server.display.config.SensorDetails);
     method public final void setProxSensor(com.android.server.display.config.SensorDetails);
     method public void setQuirks(com.android.server.display.config.DisplayQuirks);
+    method public void setRefreshRate(com.android.server.display.config.RefreshRateConfigs);
     method public final void setScreenBrightnessDefault(@NonNull java.math.BigDecimal);
     method public final void setScreenBrightnessMap(@NonNull com.android.server.display.config.NitsMap);
     method public final void setScreenBrightnessRampDecreaseMaxMillis(java.math.BigInteger);
@@ -160,6 +175,14 @@
     method public final void setValue(@NonNull java.math.BigDecimal);
   }
 
+  public class RefreshRateConfigs {
+    ctor public RefreshRateConfigs();
+    method public final com.android.server.display.config.BlockingZoneConfig getHigherBlockingZoneConfigs();
+    method public final com.android.server.display.config.BlockingZoneConfig getLowerBlockingZoneConfigs();
+    method public final void setHigherBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
+    method public final void setLowerBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
+  }
+
   public class RefreshRateRange {
     ctor public RefreshRateRange();
     method public final java.math.BigInteger getMaximum();
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 2f951ed..a92e4a1 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.credentials.ClearCredentialStateRequest;
@@ -30,9 +31,13 @@
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.ICredentialManager;
 import android.credentials.IGetCredentialCallback;
+import android.credentials.IListEnabledProvidersCallback;
+import android.credentials.ListEnabledProvidersResponse;
+import android.credentials.ISetEnabledProvidersCallback;
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.ICancellationSignal;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.credentials.BeginCreateCredentialRequest;
@@ -52,20 +57,23 @@
 /**
  * Entry point service for credential management.
  *
- * <p>This service provides the {@link ICredentialManager} implementation and keeps a list of
- * {@link CredentialManagerServiceImpl} per user; the real work is done by
- * {@link CredentialManagerServiceImpl} itself.
+ * <p>This service provides the {@link ICredentialManager} implementation and keeps a list of {@link
+ * CredentialManagerServiceImpl} per user; the real work is done by {@link
+ * CredentialManagerServiceImpl} itself.
  */
-public final class CredentialManagerService extends
-        AbstractMasterSystemService<CredentialManagerService, CredentialManagerServiceImpl> {
+public final class CredentialManagerService
+        extends AbstractMasterSystemService<
+                CredentialManagerService, CredentialManagerServiceImpl> {
 
     private static final String TAG = "CredManSysService";
 
     public CredentialManagerService(@NonNull Context context) {
-        super(context,
-                new SecureSettingsServiceNameResolver(context, Settings.Secure.CREDENTIAL_SERVICE,
-                        /*isMultipleMode=*/true),
-                null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
+        super(
+                context,
+                new SecureSettingsServiceNameResolver(
+                        context, Settings.Secure.CREDENTIAL_SERVICE, /* isMultipleMode= */ true),
+                null,
+                PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
     }
 
     @Override
@@ -74,12 +82,14 @@
     }
 
     @Override // from AbstractMasterSystemService
-    protected CredentialManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
-            boolean disabled) {
+    protected CredentialManagerServiceImpl newServiceLocked(
+            @UserIdInt int resolvedUserId, boolean disabled) {
         // This method should not be called for CredentialManagerService as it is configured to use
         // multiple services.
-        Slog.w(TAG, "Should not be here - CredentialManagerService is configured to use "
-                + "multiple services");
+        Slog.w(
+                TAG,
+                "Should not be here - CredentialManagerService is configured to use "
+                        + "multiple services");
         return null;
     }
 
@@ -89,8 +99,8 @@
     }
 
     @Override // from AbstractMasterSystemService
-    protected List<CredentialManagerServiceImpl> newServiceListLocked(int resolvedUserId,
-            boolean disabled, String[] serviceNames) {
+    protected List<CredentialManagerServiceImpl> newServiceListLocked(
+            int resolvedUserId, boolean disabled, String[] serviceNames) {
         if (serviceNames == null || serviceNames.length == 0) {
             Slog.i(TAG, "serviceNames sent in newServiceListLocked is null, or empty");
             return new ArrayList<>();
@@ -102,8 +112,8 @@
                 continue;
             }
             try {
-                serviceList.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
-                        serviceName));
+                serviceList.add(
+                        new CredentialManagerServiceImpl(this, mLock, resolvedUserId, serviceName));
             } catch (PackageManager.NameNotFoundException | SecurityException e) {
                 Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
             }
@@ -127,19 +137,20 @@
         }
     }
 
-    private List<ProviderSession> initiateProviderSessions(RequestSession session,
-            List<String> requestOptions) {
+    private List<ProviderSession> initiateProviderSessions(
+            RequestSession session, List<String> requestOptions) {
         List<ProviderSession> providerSessions = new ArrayList<>();
         // Invoke all services of a user to initiate a provider session
-        runForUser((service) -> {
-            if (service.isServiceCapable(requestOptions)) {
-                ProviderSession providerSession = service
-                        .initiateProviderSessionForRequest(session);
-                if (providerSession != null) {
-                    providerSessions.add(providerSession);
-                }
-            }
-        });
+        runForUser(
+                (service) -> {
+                    if (service.isServiceCapable(requestOptions)) {
+                        ProviderSession providerSession =
+                                service.initiateProviderSessionForRequest(session);
+                        if (providerSession != null) {
+                            providerSessions.add(providerSession);
+                        }
+                    }
+                });
         return providerSessions;
     }
 
@@ -154,25 +165,33 @@
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
 
             // New request session, scoped for this request only.
-            final GetRequestSession session = new GetRequestSession(getContext(),
-                    UserHandle.getCallingUserId(),
-                    callback,
-                    request,
-                    callingPackage);
+            final GetRequestSession session =
+                    new GetRequestSession(
+                            getContext(),
+                            UserHandle.getCallingUserId(),
+                            callback,
+                            request,
+                            callingPackage);
 
             // Initiate all provider sessions
             List<ProviderSession> providerSessions =
-                    initiateProviderSessions(session, request.getGetCredentialOptions()
-                            .stream().map(GetCredentialOption::getType)
-                            .collect(Collectors.toList()));
+                    initiateProviderSessions(
+                            session,
+                            request.getGetCredentialOptions().stream()
+                                    .map(GetCredentialOption::getType)
+                                    .collect(Collectors.toList()));
             // TODO : Return error when no providers available
 
             // Iterate over all provider sessions and invoke the request
-            providerSessions.forEach(providerGetSession -> {
-                providerGetSession.getRemoteCredentialService().onBeginGetCredentials(
-                        (BeginGetCredentialsRequest) providerGetSession.getProviderRequest(),
-                        /*callback=*/providerGetSession);
-            });
+            providerSessions.forEach(
+                    providerGetSession -> {
+                        providerGetSession
+                                .getRemoteCredentialService()
+                                .onBeginGetCredentials(
+                                        (BeginGetCredentialsRequest)
+                                                providerGetSession.getProviderRequest(),
+                                        /* callback= */ providerGetSession);
+                    });
             return cancelTransport;
         }
 
@@ -186,11 +205,13 @@
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
 
             // New request session, scoped for this request only.
-            final CreateRequestSession session = new CreateRequestSession(getContext(),
-                    UserHandle.getCallingUserId(),
-                    request,
-                    callback,
-                    callingPackage);
+            final CreateRequestSession session =
+                    new CreateRequestSession(
+                            getContext(),
+                            UserHandle.getCallingUserId(),
+                            request,
+                            callback,
+                            callingPackage);
 
             // Initiate all provider sessions
             List<ProviderSession> providerSessions =
@@ -198,16 +219,83 @@
             // TODO : Return error when no providers available
 
             // Iterate over all provider sessions and invoke the request
-            providerSessions.forEach(providerCreateSession -> {
-                providerCreateSession.getRemoteCredentialService().onCreateCredential(
-                        (BeginCreateCredentialRequest)
-                                providerCreateSession.getProviderRequest(),
-                        /*callback=*/providerCreateSession);
-            });
+            providerSessions.forEach(
+                    providerCreateSession -> {
+                        providerCreateSession
+                                .getRemoteCredentialService()
+                                .onCreateCredential(
+                                        (BeginCreateCredentialRequest)
+                                                providerCreateSession.getProviderRequest(),
+                                        /* callback= */ providerCreateSession);
+                    });
             return cancelTransport;
         }
 
         @Override
+        public ICancellationSignal listEnabledProviders(IListEnabledProvidersCallback callback) {
+            Log.i(TAG, "listEnabledProviders");
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+            List<String> enabledProviders = new ArrayList<>();
+            runForUser(
+                    (service) -> {
+                        enabledProviders.add(
+                                service.getServiceInfo().getComponentName().flattenToString());
+                    });
+
+            // Call the callback.
+            try {
+                callback.onResponse(ListEnabledProvidersResponse.create(enabledProviders));
+            } catch (RemoteException e) {
+                Log.i(TAG, "Issue with invoking response: " + e.getMessage());
+                // TODO: Propagate failure
+            }
+
+            return cancelTransport;
+        }
+
+        @Override
+        public void setEnabledProviders(
+                List<String> providers, int userId, ISetEnabledProvidersCallback callback) {
+            Log.i(TAG, "setEnabledProviders");
+
+            userId =
+                    ActivityManager.handleIncomingUser(
+                            Binder.getCallingPid(),
+                            Binder.getCallingUid(),
+                            userId,
+                            false,
+                            false,
+                            "setEnabledProviders",
+                            null);
+
+            String storedValue = String.join(":", providers);
+            if (!Settings.Secure.putStringForUser(
+                    getContext().getContentResolver(),
+                    Settings.Secure.CREDENTIAL_SERVICE,
+                    storedValue,
+                    userId)) {
+                Log.e(TAG, "Failed to store setting containing enabled providers");
+                try {
+                    callback.onError(
+                            "failed_setting_store",
+                            "Failed to store setting containing enabled providers");
+                } catch (RemoteException e) {
+                    Log.i(TAG, "Issue with invoking error response: " + e.getMessage());
+                    // TODO: Propagate failure
+                }
+            }
+
+            // Call the callback.
+            try {
+                callback.onResponse();
+            } catch (RemoteException e) {
+                Log.i(TAG, "Issue with invoking response: " + e.getMessage());
+                // TODO: Propagate failure
+            }
+        }
+
+        @Override
         public ICancellationSignal clearCredentialState(ClearCredentialStateRequest request,
                 IClearCredentialStateCallback callback, String callingPackage) {
             // TODO: implement.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 9af30ba..4634ff5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -163,6 +163,7 @@
             "preferential_network_service_config";
     private static final String TAG_PROTECTED_PACKAGES = "protected_packages";
     private static final String TAG_SUSPENDED_PACKAGES = "suspended-packages";
+    private static final String TAG_MTE_POLICY = "mte-policy";
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
     private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -222,6 +223,8 @@
     int numNetworkLoggingNotifications = 0;
     long lastNetworkLoggingNotificationTimeMs = 0; // Time in milliseconds since epoch
 
+    @DevicePolicyManager.MtePolicy int mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+
     ActiveAdmin parentAdmin;
     final boolean isParent;
 
@@ -620,6 +623,9 @@
             }
             out.endTag(null, TAG_PREFERENTIAL_NETWORK_SERVICE_CONFIGS);
         }
+        if (mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+            writeAttributeValueToXml(out, TAG_MTE_POLICY, mtePolicy);
+        }
     }
 
     private List<String> ssidsToStrings(Set<WifiSsid> ssids) {
@@ -906,6 +912,8 @@
                 if (!configs.isEmpty()) {
                     mPreferentialNetworkServiceConfigs = configs;
                 }
+            } else if (TAG_MTE_POLICY.equals(tag)) {
+                mtePolicy = parser.getAttributeInt(null, ATTR_VALUE);
             } else {
                 Slogf.w(LOG_TAG, "Unknown admin tag: %s", tag);
                 XmlUtils.skipCurrentTag(parser);
@@ -1338,5 +1346,8 @@
             }
             pw.decreaseIndent();
         }
+
+        pw.print("mtePolicy=");
+        pw.println(mtePolicy);
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e93809d..c42ddf8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5387,7 +5387,8 @@
                         }
                         if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
                             throw new UnsupportedOperationException(
-                                    "FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY only applies to FBE devices");
+                                    "FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY only applies to FBE"
+                                        + " devices");
                         }
                         mUserManager.evictCredentialEncryptionKey(callingUserId);
                     }
@@ -19240,4 +19241,55 @@
                 KEEP_PROFILES_RUNNING_FLAG,
                 DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
     }
+
+    @Override
+    public void setMtePolicy(int flags) {
+        final Set<Integer> allowedModes =
+                Set.of(
+                        DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
+                        DevicePolicyManager.MTE_DISABLED,
+                        DevicePolicyManager.MTE_ENABLED);
+        Preconditions.checkArgument(
+                allowedModes.contains(flags), "Provided mode is not one of the allowed values.");
+        final CallerIdentity caller = getCallerIdentity();
+        if (flags == DevicePolicyManager.MTE_DISABLED) {
+            Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
+        } else {
+            Preconditions.checkCallAuthorization(
+                    isDefaultDeviceOwner(caller)
+                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        }
+        synchronized (getLockObject()) {
+            ActiveAdmin admin =
+                    getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                            UserHandle.USER_SYSTEM);
+            if (admin != null) {
+                final String memtagProperty = "arm64.memtag.bootctl";
+                if (flags == DevicePolicyManager.MTE_ENABLED) {
+                    mInjector.systemPropertiesSet(memtagProperty, "memtag");
+                } else if (flags == DevicePolicyManager.MTE_DISABLED) {
+                    mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
+                }
+                admin.mtePolicy = flags;
+                saveSettingsLocked(caller.getUserId());
+            }
+        }
+    }
+
+    @Override
+    public int getMtePolicy() {
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        || isSystemUid(caller));
+        synchronized (getLockObject()) {
+            ActiveAdmin admin =
+                    getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                            UserHandle.USER_SYSTEM);
+            return admin != null
+                    ? admin.mtePolicy
+                    : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+        }
+    }
 }
diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java
index c3329795..deebfc7 100644
--- a/services/java/com/android/server/BootUserInitializer.java
+++ b/services/java/com/android/server/BootUserInitializer.java
@@ -102,6 +102,7 @@
         switchToInitialUser(initialUserId);
     }
 
+    /* TODO(b/261791491): STOPSHIP - SUW should be responsible for this. */
     private void provisionHeadlessSystemUser() {
         if (isDeviceProvisioned()) {
             Slogf.d(TAG, "provisionHeadlessSystemUser(): already provisioned");
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9632fea..c68eea8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -155,6 +155,7 @@
 import com.android.server.os.NativeTombstoneManagerService;
 import com.android.server.os.SchedulingPolicyService;
 import com.android.server.people.PeopleService;
+import com.android.server.permission.access.AccessCheckingService;
 import com.android.server.pm.ApexManager;
 import com.android.server.pm.ApexSystemServiceInfo;
 import com.android.server.pm.BackgroundInstallControlService;
@@ -1111,6 +1112,11 @@
         startMemtrackProxyService();
         t.traceEnd();
 
+        // Start AccessCheckingService which provides new implementation for permission and app op.
+        t.traceBegin("StartAccessCheckingService");
+        mSystemServiceManager.startService(AccessCheckingService.class);
+        t.traceEnd();
+
         // Activity manager runs the show.
         t.traceBegin("StartActivityManager");
         // TODO: Might need to move after migration to WM.
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index 7b96d42..f493b89 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -16,11 +16,23 @@
 
 package com.android.server.permission.access
 
+import android.content.Context
 import com.android.internal.annotations.Keep
-import com.android.server.permission.access.external.PackageState
+import com.android.server.LocalManagerRegistry
+import com.android.server.LocalServices
+import com.android.server.SystemConfig
+import com.android.server.SystemService
+import com.android.server.appop.AppOpsCheckingServiceInterface
+import com.android.server.permission.access.appop.AppOpService
+import com.android.server.permission.access.collection.IntSet
+import com.android.server.permission.access.permission.PermissionService
+import com.android.server.pm.PackageManagerLocal
+import com.android.server.pm.UserManagerService
+import com.android.server.pm.permission.PermissionManagerServiceInterface
+import com.android.server.pm.pkg.PackageState
 
 @Keep
-class AccessCheckingService {
+class AccessCheckingService(context: Context) : SystemService(context) {
     @Volatile
     private lateinit var state: AccessState
     private val stateLock = Any()
@@ -29,14 +41,40 @@
 
     private val persistence = AccessPersistence(policy)
 
-    fun init() {
+    private lateinit var appOpService: AppOpService
+    private lateinit var permissionService: PermissionService
+
+    private lateinit var packageManagerLocal: PackageManagerLocal
+    private lateinit var userManagerService: UserManagerService
+    private lateinit var systemConfig: SystemConfig
+
+    override fun onStart() {
+        appOpService = AppOpService(this)
+        permissionService = PermissionService(this)
+
+        LocalServices.addService(AppOpsCheckingServiceInterface::class.java, appOpService)
+        LocalServices.addService(PermissionManagerServiceInterface::class.java, permissionService)
+    }
+
+    fun initialize() {
+        packageManagerLocal =
+            LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
+        userManagerService = UserManagerService.getInstance()
+        systemConfig = SystemConfig.getInstance()
+
+        val userIds = IntSet(userManagerService.userIdsIncludingPreCreated)
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
+        val permissionAllowlist = systemConfig.permissionAllowlist
+
         val state = AccessState()
-        state.systemState.userIds.apply {
-            // TODO: Get and add all user IDs.
-            // TODO: Maybe get and add all packages?
-        }
+        policy.initialize(
+            state, userIds, packageStates, disabledSystemPackageStates, permissionAllowlist
+        )
         persistence.read(state)
         this.state = state
+
+        appOpService.initialize()
+        permissionService.initialize()
     }
 
     fun getDecision(subject: AccessUri, `object`: AccessUri): Int =
@@ -50,34 +88,73 @@
         }
     }
 
-    fun onUserAdded(userId: Int) {
+    internal fun onUserAdded(userId: Int) {
         mutateState {
             with(policy) { onUserAdded(userId) }
         }
     }
 
-    fun onUserRemoved(userId: Int) {
+    internal fun onUserRemoved(userId: Int) {
         mutateState {
             with(policy) { onUserRemoved(userId) }
         }
     }
 
-    fun onPackageAdded(packageState: PackageState) {
+    internal fun onStorageVolumeMounted(volumeUuid: String?, isSystemUpdated: Boolean) {
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
         mutateState {
-            with(policy) { onPackageAdded(packageState) }
+            with(policy) {
+                onStorageVolumeMounted(
+                    packageStates, disabledSystemPackageStates, volumeUuid, isSystemUpdated
+                )
+            }
         }
     }
 
-    fun onPackageRemoved(packageState: PackageState) {
+    internal fun onPackageAdded(packageName: String) {
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
         mutateState {
-            with(policy) { onPackageRemoved(packageState) }
+            with(policy) { onPackageAdded(packageStates, disabledSystemPackageStates, packageName) }
         }
     }
 
+    internal fun onPackageRemoved(packageName: String, appId: Int) {
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
+        mutateState {
+            with(policy) {
+                onPackageRemoved(packageStates, disabledSystemPackageStates, packageName, appId)
+            }
+        }
+    }
+
+    internal fun onPackageInstalled(packageName: String, userId: Int) {
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
+        mutateState {
+            with(policy) {
+                onPackageInstalled(packageStates, disabledSystemPackageStates, packageName, userId)
+            }
+        }
+    }
+
+    internal fun onPackageUninstalled(packageName: String, appId: Int, userId: Int) {
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
+        mutateState {
+            with(policy) {
+                onPackageUninstalled(
+                    packageStates, disabledSystemPackageStates, packageName, appId, userId
+                )
+            }
+        }
+    }
+
+    private val PackageManagerLocal.allPackageStates:
+        Pair<Map<String, PackageState>, Map<String, PackageState>>
+        get() = withUnfilteredSnapshot().use { it.packageStates to it.disabledSystemPackageStates }
+
     internal inline fun <T> getState(action: GetStateScope.() -> T): T =
         GetStateScope(state).action()
 
-    internal inline fun mutateState(action: MutateStateScope.() -> Unit) {
+    internal inline fun mutateState(crossinline action: MutateStateScope.() -> Unit) {
         synchronized(stateLock) {
             val oldState = state
             val newState = oldState.copy()
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index e9741c6..89316c2 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -22,11 +22,12 @@
 import com.android.server.permission.access.appop.PackageAppOpPolicy
 import com.android.server.permission.access.appop.UidAppOpPolicy
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.external.PackageState
 import com.android.server.permission.access.permission.UidPermissionPolicy
 import com.android.server.permission.access.util.forEachTag
 import com.android.server.permission.access.util.tag
 import com.android.server.permission.access.util.tagName
+import com.android.server.pm.permission.PermissionAllowlist
+import com.android.server.pm.pkg.PackageState
 
 class AccessPolicy private constructor(
     private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>>
@@ -53,6 +54,25 @@
         with(getSchemePolicy(subject, `object`)) { setDecision(subject, `object`, decision) }
     }
 
+    fun initialize(
+        state: AccessState,
+        userIds: IntSet,
+        packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
+        permissionAllowlist: PermissionAllowlist
+    ) {
+        state.systemState.apply {
+            this.userIds += userIds
+            this.packageStates = packageStates
+            this.disabledSystemPackageStates = disabledSystemPackageStates
+            packageStates.forEach { (_, packageState) ->
+                appIds.getOrPut(packageState.appId) { IndexedListSet() }
+                    .add(packageState.packageName)
+            }
+            this.permissionAllowlist = permissionAllowlist
+        }
+    }
+
     fun MutateStateScope.onUserAdded(userId: Int) {
         newState.systemState.userIds += userId
         newState.userStates[userId] = UserState()
@@ -69,18 +89,44 @@
         }
     }
 
-    fun MutateStateScope.onPackageAdded(packageState: PackageState) {
+    fun MutateStateScope.onStorageVolumeMounted(
+        packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
+        volumeUuid: String?,
+        isSystemUpdated: Boolean
+    ) {
+        newState.systemState.apply {
+            this.packageStates = packageStates
+            this.disabledSystemPackageStates = disabledSystemPackageStates
+        }
+        forEachSchemePolicy {
+            with(it) { onStorageVolumeMounted(volumeUuid, isSystemUpdated) }
+        }
+    }
+
+    fun MutateStateScope.onPackageAdded(
+        packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
+        packageName: String
+    ) {
+        val packageState = packageStates[packageName]
+        // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
+        checkNotNull(packageState) {
+            "Added package $packageName isn't found in packageStates in onPackageAdded()"
+        }
+        val appId = packageState.appId
         var isAppIdAdded = false
         newState.systemState.apply {
-            packageStates[packageState.packageName] = packageState
-            appIds.getOrPut(packageState.appId) {
+            this.packageStates = packageStates
+            this.disabledSystemPackageStates = disabledSystemPackageStates
+            appIds.getOrPut(appId) {
                 isAppIdAdded = true
                 IndexedListSet()
-            }.add(packageState.packageName)
+            }.add(packageName)
         }
         if (isAppIdAdded) {
             forEachSchemePolicy {
-                with(it) { onAppIdAdded(packageState.appId) }
+                with(it) { onAppIdAdded(appId) }
             }
         }
         forEachSchemePolicy {
@@ -88,30 +134,74 @@
         }
     }
 
-    fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
+    fun MutateStateScope.onPackageRemoved(
+        packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
+        packageName: String,
+        appId: Int
+    ) {
+        // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
+        check(packageName !in packageStates) {
+            "Removed package $packageName is still in packageStates in onPackageRemoved()"
+        }
         var isAppIdRemoved = false
         newState.systemState.apply {
-            packageStates -= packageState.packageName
-            appIds.apply appIds@{
-                this[packageState.appId]?.apply {
-                    this -= packageState.packageName
-                    if (isEmpty()) {
-                        this@appIds -= packageState.appId
-                        isAppIdRemoved = true
-                    }
+            this.packageStates = packageStates
+            this.disabledSystemPackageStates = disabledSystemPackageStates
+            appIds[appId]?.apply {
+                this -= packageName
+                if (isEmpty()) {
+                    appIds -= appId
+                    isAppIdRemoved = true
                 }
             }
         }
         forEachSchemePolicy {
-            with(it) { onPackageRemoved(packageState) }
+            with(it) { onPackageRemoved(packageName, appId) }
         }
         if (isAppIdRemoved) {
             forEachSchemePolicy {
-                with(it) { onAppIdRemoved(packageState.appId) }
+                with(it) { onAppIdRemoved(appId) }
             }
         }
     }
 
+    fun MutateStateScope.onPackageInstalled(
+        packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
+        packageName: String,
+        userId: Int
+    ) {
+        newState.systemState.apply {
+            this.packageStates = packageStates
+            this.disabledSystemPackageStates = disabledSystemPackageStates
+        }
+        val packageState = packageStates[packageName]
+        // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
+        checkNotNull(packageState) {
+            "Installed package $packageName isn't found in packageStates in onPackageInstalled()"
+        }
+        forEachSchemePolicy {
+            with(it) { onPackageInstalled(packageState, userId) }
+        }
+    }
+
+    fun MutateStateScope.onPackageUninstalled(
+        packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
+        packageName: String,
+        appId: Int,
+        userId: Int
+    ) {
+        newState.systemState.apply {
+            this.packageStates = packageStates
+            this.disabledSystemPackageStates = disabledSystemPackageStates
+        }
+        forEachSchemePolicy {
+            with(it) { onPackageUninstalled(packageName, appId, userId) }
+        }
+    }
+
     fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {
         forEachTag {
             when (tagName) {
@@ -182,10 +272,6 @@
 }
 
 abstract class SchemePolicy {
-    @Volatile
-    private var onDecisionChangedListeners = IndexedListSet<OnDecisionChangedListener>()
-    private val onDecisionChangedListenersLock = Any()
-
     abstract val subjectScheme: String
 
     abstract val objectScheme: String
@@ -198,30 +284,6 @@
         decision: Int
     )
 
-    fun addOnDecisionChangedListener(listener: OnDecisionChangedListener) {
-        synchronized(onDecisionChangedListenersLock) {
-            onDecisionChangedListeners = onDecisionChangedListeners + listener
-        }
-    }
-
-    fun removeOnDecisionChangedListener(listener: OnDecisionChangedListener) {
-        synchronized(onDecisionChangedListenersLock) {
-            onDecisionChangedListeners = onDecisionChangedListeners - listener
-        }
-    }
-
-    protected fun notifyOnDecisionChangedListeners(
-        subject: AccessUri,
-        `object`: AccessUri,
-        oldDecision: Int,
-        newDecision: Int
-    ) {
-        val listeners = onDecisionChangedListeners
-        listeners.forEachIndexed { _, it ->
-            it.onDecisionChanged(subject, `object`, oldDecision, newDecision)
-        }
-    }
-
     open fun MutateStateScope.onUserAdded(userId: Int) {}
 
     open fun MutateStateScope.onUserRemoved(userId: Int) {}
@@ -230,9 +292,18 @@
 
     open fun MutateStateScope.onAppIdRemoved(appId: Int) {}
 
+    open fun MutateStateScope.onStorageVolumeMounted(
+        volumeUuid: String?,
+        isSystemUpdated: Boolean
+    ) {}
+
     open fun MutateStateScope.onPackageAdded(packageState: PackageState) {}
 
-    open fun MutateStateScope.onPackageRemoved(packageState: PackageState) {}
+    open fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {}
+
+    open fun MutateStateScope.onPackageInstalled(packageState: PackageState, userId: Int) {}
+
+    open fun MutateStateScope.onPackageUninstalled(packageName: String, appId: Int, userId: Int) {}
 
     open fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {}
 
@@ -241,13 +312,4 @@
     open fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {}
 
     open fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {}
-
-    fun interface OnDecisionChangedListener {
-        fun onDecisionChanged(
-            subject: AccessUri,
-            `object`: AccessUri,
-            oldDecision: Int,
-            newDecision: Int
-        )
-    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index cf8f383..fae1ec3 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -18,73 +18,66 @@
 
 import android.content.pm.PermissionGroupInfo
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.data.Permission
-import com.android.server.permission.access.external.PackageState
+import com.android.server.permission.access.permission.Permission
+import com.android.server.pm.permission.PermissionAllowlist
+import com.android.server.pm.pkg.PackageState
 
 class AccessState private constructor(
     val systemState: SystemState,
     val userStates: IntMap<UserState>
 ) {
-    constructor() : this(SystemState(), IntMap())
+    constructor() : this(
+        SystemState(),
+        IntMap()
+    )
 
-    fun copy(): AccessState = AccessState(systemState.copy(), userStates.copy { it.copy() })
+    fun copy(): AccessState = AccessState(
+        systemState.copy(),
+        userStates.copy { it.copy() }
+    )
 }
 
 class SystemState private constructor(
     val userIds: IntSet,
-    val packageStates: IndexedMap<String, PackageState>,
-    val disabledSystemPackageStates: IndexedMap<String, PackageState>,
+    var packageStates: Map<String, PackageState>,
+    var disabledSystemPackageStates: Map<String, PackageState>,
     val appIds: IntMap<IndexedListSet<String>>,
     // A map of KnownPackagesInt to a set of known package names
     val knownPackages: IntMap<IndexedListSet<String>>,
     // A map of userId to packageName
     val deviceAndProfileOwners: IntMap<String>,
-    // A map of packageName to (A map of oem permission name to whether it's granted)
-    val oemPermissions: IndexedMap<String, IndexedMap<String, Boolean>>,
     val privilegedPermissionAllowlistSourcePackageNames: IndexedListSet<String>,
-    // A map of packageName to a set of vendor priv app permission names
-    val vendorPrivAppPermissions: Map<String, Set<String>>,
-    val productPrivAppPermissions: Map<String, Set<String>>,
-    val systemExtPrivAppPermissions: Map<String, Set<String>>,
-    val privAppPermissions: Map<String, Set<String>>,
-    val apexPrivAppPermissions: Map<String, Map<String, Set<String>>>,
-    val vendorPrivAppDenyPermissions: Map<String, Set<String>>,
-    val productPrivAppDenyPermissions: Map<String, Set<String>>,
-    val systemExtPrivAppDenyPermissions: Map<String, Set<String>>,
-    val apexPrivAppDenyPermissions: Map<String, Map<String, Set<String>>>,
-    val privAppDenyPermissions: Map<String, Set<String>>,
+    var permissionAllowlist: PermissionAllowlist,
     val implicitToSourcePermissions: Map<String, Set<String>>,
     val permissionGroups: IndexedMap<String, PermissionGroupInfo>,
     val permissionTrees: IndexedMap<String, Permission>,
     val permissions: IndexedMap<String, Permission>
 ) : WritableState() {
     constructor() : this(
-        IntSet(), IndexedMap(), IndexedMap(), IntMap(), IntMap(), IntMap(), IndexedMap(),
-        IndexedListSet(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(),
-        IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(),
-        IndexedMap(), IndexedMap(), IndexedMap()
+        IntSet(),
+        emptyMap(),
+        emptyMap(),
+        IntMap(),
+        IntMap(),
+        IntMap(),
+        IndexedListSet(),
+        PermissionAllowlist(),
+        IndexedMap(),
+        IndexedMap(),
+        IndexedMap(),
+        IndexedMap()
     )
 
     fun copy(): SystemState =
         SystemState(
             userIds.copy(),
-            packageStates.copy { it },
-            disabledSystemPackageStates.copy { it },
+            packageStates,
+            disabledSystemPackageStates,
             appIds.copy { it.copy() },
             knownPackages.copy { it.copy() },
             deviceAndProfileOwners.copy { it },
-            oemPermissions.copy { it.copy { it } },
             privilegedPermissionAllowlistSourcePackageNames.copy(),
-            vendorPrivAppPermissions,
-            productPrivAppPermissions,
-            systemExtPrivAppPermissions,
-            privAppPermissions,
-            apexPrivAppPermissions,
-            vendorPrivAppDenyPermissions,
-            productPrivAppDenyPermissions,
-            systemExtPrivAppDenyPermissions,
-            apexPrivAppDenyPermissions,
-            privAppDenyPermissions,
+            permissionAllowlist,
             implicitToSourcePermissions,
             permissionGroups.copy { it },
             permissionTrees.copy { it },
@@ -94,14 +87,21 @@
 
 class UserState private constructor(
     // A map of (appId to a map of (permissionName to permissionFlags))
-    val permissionFlags: IntMap<IndexedMap<String, Int>>,
+    val uidPermissionFlags: IntMap<IndexedMap<String, Int>>,
     val uidAppOpModes: IntMap<IndexedMap<String, Int>>,
     val packageAppOpModes: IndexedMap<String, IndexedMap<String, Int>>
 ) : WritableState() {
-    constructor() : this(IntMap(), IntMap(), IndexedMap())
+    constructor() : this(
+        IntMap(),
+        IntMap(),
+        IndexedMap()
+    )
 
-    fun copy(): UserState = UserState(permissionFlags.copy { it.copy { it } },
-        uidAppOpModes.copy { it.copy { it } }, packageAppOpModes.copy { it.copy { it } })
+    fun copy(): UserState = UserState(
+        uidPermissionFlags.copy { it.copy { it } },
+        uidAppOpModes.copy { it.copy { it } },
+        packageAppOpModes.copy { it.copy { it } }
+    )
 }
 
 object WriteMode {
diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt
index 7e98d2c..d1abc04 100644
--- a/services/permission/java/com/android/server/permission/access/AccessUri.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt
@@ -16,8 +16,7 @@
 
 package com.android.server.permission.access
 
-import com.android.server.permission.access.external.UserHandle
-import com.android.server.permission.access.external.UserHandleCompat
+import android.os.UserHandle
 
 sealed class AccessUri(
     val scheme: String
@@ -70,7 +69,7 @@
     val uid: Int
 ) : AccessUri(SCHEME) {
     val userId: Int
-        get() = UserHandleCompat.getUserId(uid)
+        get() = UserHandle.getUserId(uid)
 
     val appId: Int
         get() = UserHandle.getAppId(uid)
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
similarity index 94%
rename from services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt
rename to services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index a565feb..b8d6aa3 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -24,9 +24,13 @@
 import com.android.server.permission.access.AccessCheckingService
 import java.io.PrintWriter
 
-class AppOpsCheckingServiceCompatImpl(
-    private val accessCheckingService: AccessCheckingService
+class AppOpService(
+    private val service: AccessCheckingService
 ) : AppOpsCheckingServiceInterface {
+    fun initialize() {
+        TODO("Not yet implemented")
+    }
+
     override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
         TODO("Not yet implemented")
     }
@@ -139,6 +143,6 @@
     }
 
     companion object {
-        private val LOG_TAG = AppOpsCheckingServiceCompatImpl::class.java.simpleName
+        private val LOG_TAG = AppOpService::class.java.simpleName
     }
 }
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
index a1a5e2d..7f4e0f7 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
@@ -16,50 +16,17 @@
 
 package com.android.server.permission.access.appop
 
-import android.app.AppOpsManager
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
-import com.android.server.permission.access.AccessUri
 import com.android.server.permission.access.AppOpUri
-import com.android.server.permission.access.GetStateScope
-import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.SchemePolicy
 import com.android.server.permission.access.UserState
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
 
-abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() {
-    override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
-        `object` as AppOpUri
-        return getModes(subject)
-            .getWithDefault(`object`.appOpName, opToDefaultMode(`object`.appOpName))
-    }
-
-    override fun MutateStateScope.setDecision(
-        subject: AccessUri,
-        `object`: AccessUri,
-        decision: Int
-    ) {
-        `object` as AppOpUri
-        val modes = getOrCreateModes(subject)
-        val oldMode = modes.putWithDefault(`object`.appOpName, decision,
-            opToDefaultMode(`object`.appOpName))
-        if (modes.isEmpty()) {
-            removeModes(subject)
-        }
-        if (oldMode != decision) {
-            notifyOnDecisionChangedListeners(subject, `object`, oldMode, decision)
-        }
-    }
-
-    abstract fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>?
-
-    abstract fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int>
-
-    abstract fun MutateStateScope.removeModes(subject: AccessUri)
-
-    // TODO need to check that [AppOpsManager.getSystemAlertWindowDefault] works; likely no issue
-    //  since running in system process.
-    private fun opToDefaultMode(appOpName: String) = AppOpsManager.opToDefaultMode(appOpName)
+abstract class BaseAppOpPolicy(
+    private val persistence: BaseAppOpPersistence
+) : SchemePolicy() {
+    override val objectScheme: String
+        get() = AppOpUri.SCHEME
 
     override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
         with(persistence) { this@parseUserState.parseUserState(userId, userState) }
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 966489f..607e512 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -16,41 +16,101 @@
 
 package com.android.server.permission.access.appop
 
+import android.app.AppOpsManager
 import com.android.server.permission.access.AccessUri
 import com.android.server.permission.access.AppOpUri
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.PackageUri
-import com.android.server.permission.access.UserState
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.external.PackageState
 
 class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
+    @Volatile
+    private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
+    private val onAppOpModeChangedListenersLock = Any()
+
     override val subjectScheme: String
         get() = PackageUri.SCHEME
 
-    override val objectScheme: String
-        get() = AppOpUri.SCHEME
-
-    override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
+    override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
         subject as PackageUri
-        return state.userStates[subject.userId]?.packageAppOpModes?.get(subject.packageName)
+        `object` as AppOpUri
+        return getAppOpMode(subject.packageName, subject.userId, `object`.appOpName)
     }
 
-    override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
+    override fun MutateStateScope.setDecision(
+        subject: AccessUri,
+        `object`: AccessUri,
+        decision: Int
+    ) {
         subject as PackageUri
-        return newState.userStates.getOrPut(subject.userId) { UserState() }
-            .packageAppOpModes.getOrPut(subject.packageName) { IndexedMap() }
+        `object` as AppOpUri
+        setAppOpMode(subject.packageName, subject.userId, `object`.appOpName, decision)
     }
 
-    override fun MutateStateScope.removeModes(subject: AccessUri) {
-        subject as PackageUri
-        newState.userStates[subject.userId]?.packageAppOpModes?.remove(subject.packageName)
-    }
-
-    override fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
+    override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
         newState.userStates.forEachIndexed { _, _, userState ->
-            userState.packageAppOpModes -= packageState.packageName
+            userState.packageAppOpModes -= packageName
+            userState.requestWrite()
+            // Skip notifying the change listeners since the package no longer exists.
         }
     }
+
+    fun MutateStateScope.removeAppOpModes(packageName: String, userId: Int): Boolean =
+        newState.userStates[userId].packageAppOpModes.remove(packageName) != null
+
+    fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int =
+        state.userStates[userId].packageAppOpModes[packageName]
+            .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
+
+    fun MutateStateScope.setAppOpMode(
+        packageName: String,
+        userId: Int,
+        appOpName: String,
+        mode: Int
+    ): Boolean {
+        val userState = newState.userStates[userId]
+        val packageAppOpModes = userState.packageAppOpModes
+        var appOpModes = packageAppOpModes[packageName]
+        val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
+        val oldMode = appOpModes.getWithDefault(appOpName, defaultMode)
+        if (oldMode == mode) {
+            return false
+        }
+        if (appOpModes == null) {
+            appOpModes = IndexedMap()
+            packageAppOpModes[packageName] = appOpModes
+        }
+        appOpModes.putWithDefault(appOpName, mode, defaultMode)
+        if (appOpModes.isEmpty()) {
+            packageAppOpModes -= packageName
+        }
+        userState.requestWrite()
+        onAppOpModeChangedListeners.forEachIndexed { _, it ->
+            it.onAppOpModeChanged(packageName, userId, appOpName, oldMode, mode)
+        }
+        return true
+    }
+
+    fun addOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+        synchronized(onAppOpModeChangedListenersLock) {
+            onAppOpModeChangedListeners = onAppOpModeChangedListeners + listener
+        }
+    }
+
+    fun removeOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+        synchronized(onAppOpModeChangedListenersLock) {
+            onAppOpModeChangedListeners = onAppOpModeChangedListeners - listener
+        }
+    }
+
+    fun interface OnAppOpModeChangedListener {
+        fun onAppOpModeChanged(
+            packageName: String,
+            userId: Int,
+            appOpName: String,
+            oldMode: Int,
+            newMode: Int
+        )
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
index 862db8f..0b01038 100644
--- a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
@@ -16,40 +16,104 @@
 
 package com.android.server.permission.access.appop
 
+import android.app.AppOpsManager
 import com.android.server.permission.access.AccessUri
 import com.android.server.permission.access.AppOpUri
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.UidUri
-import com.android.server.permission.access.UserState
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
 
 class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) {
+    @Volatile
+    private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
+    private val onAppOpModeChangedListenersLock = Any()
+
     override val subjectScheme: String
         get() = UidUri.SCHEME
 
-    override val objectScheme: String
-        get() = AppOpUri.SCHEME
-
-    override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
+    override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
         subject as UidUri
-        return state.userStates[subject.userId]?.uidAppOpModes?.get(subject.appId)
+        `object` as AppOpUri
+        return getAppOpMode(subject.appId, subject.userId, `object`.appOpName)
     }
 
-    override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
+    override fun MutateStateScope.setDecision(
+        subject: AccessUri,
+        `object`: AccessUri,
+        decision: Int
+    ) {
         subject as UidUri
-        return newState.userStates.getOrPut(subject.userId) { UserState() }
-            .uidAppOpModes.getOrPut(subject.appId) { IndexedMap() }
-    }
-
-    override fun MutateStateScope.removeModes(subject: AccessUri) {
-        subject as UidUri
-        newState.userStates[subject.userId]?.uidAppOpModes?.remove(subject.appId)
+        `object` as AppOpUri
+        setAppOpMode(subject.appId, subject.userId, `object`.appOpName, decision)
     }
 
     override fun MutateStateScope.onAppIdRemoved(appId: Int) {
         newState.userStates.forEachIndexed { _, _, userState ->
             userState.uidAppOpModes -= appId
+            userState.requestWrite()
+            // Skip notifying the change listeners since the app ID no longer exists.
         }
     }
+
+    fun GetStateScope.getAppOpModes(appId: Int, userId: Int): IndexedMap<String, Int>? =
+        state.userStates[userId].uidAppOpModes[appId]
+
+    fun MutateStateScope.removeAppOpModes(appId: Int, userId: Int): Boolean =
+        newState.userStates[userId].uidAppOpModes.removeReturnOld(appId) != null
+
+    fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int =
+        state.userStates[userId].uidAppOpModes[appId]
+            .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
+
+    fun MutateStateScope.setAppOpMode(
+        appId: Int,
+        userId: Int,
+        appOpName: String,
+        mode: Int
+    ): Boolean {
+        val userState = newState.userStates[userId]
+        val uidAppOpModes = userState.uidAppOpModes
+        var appOpModes = uidAppOpModes[appId]
+        val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
+        val oldMode = appOpModes.getWithDefault(appOpName, defaultMode)
+        if (oldMode == mode) {
+            return false
+        }
+        if (appOpModes == null) {
+            appOpModes = IndexedMap()
+            uidAppOpModes[appId] = appOpModes
+        }
+        appOpModes.putWithDefault(appOpName, mode, defaultMode)
+        if (appOpModes.isEmpty()) {
+            uidAppOpModes -= appId
+        }
+        userState.requestWrite()
+        onAppOpModeChangedListeners.forEachIndexed { _, it ->
+            it.onAppOpModeChanged(appId, userId, appOpName, oldMode, mode)
+        }
+        return true
+    }
+
+    fun addOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+        synchronized(onAppOpModeChangedListenersLock) {
+            onAppOpModeChangedListeners = onAppOpModeChangedListeners + listener
+        }
+    }
+
+    fun removeOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+        synchronized(onAppOpModeChangedListenersLock) {
+            onAppOpModeChangedListeners = onAppOpModeChangedListeners - listener
+        }
+    }
+
+    fun interface OnAppOpModeChangedListener {
+        fun onAppOpModeChanged(
+            appId: Int,
+            userId: Int,
+            appOpName: String,
+            oldMode: Int,
+            newMode: Int
+        )
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
index 5ba435c..c4d07fe 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
@@ -19,8 +19,8 @@
 typealias IndexedList<T> = ArrayList<T>
 
 inline fun <T> IndexedList<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, this[index])) {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
             return false
         }
     }
@@ -28,8 +28,8 @@
 }
 
 inline fun <T> IndexedList<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, this[index])) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return true
         }
     }
@@ -45,6 +45,12 @@
     }
 }
 
+inline fun <T> IndexedList<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, this[index])
+    }
+}
+
 @Suppress("NOTHING_TO_INLINE")
 inline operator fun <T> IndexedList<T>.minus(element: T): IndexedList<T> =
     copy().apply { this -= element }
@@ -55,8 +61,8 @@
 }
 
 inline fun <T> IndexedList<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, this[index])) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return false
         }
     }
@@ -72,18 +78,24 @@
     add(element)
 }
 
-inline fun <T> IndexedList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, this[index])) {
+inline fun <T> IndexedList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, element ->
+        if (predicate(index, element)) {
             removeAt(index)
+            isChanged = true
         }
     }
+    return isChanged
 }
 
-inline fun <T> IndexedList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, this[index])) {
+inline fun <T> IndexedList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, element ->
+        if (!predicate(index, element)) {
             removeAt(index)
+            isChanged = true
         }
     }
+    return isChanged
 }
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
index ac552ff..c40f7ee 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
@@ -70,8 +70,8 @@
 }
 
 inline fun <T> IndexedListSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
             return false
         }
     }
@@ -79,8 +79,8 @@
 }
 
 inline fun <T> IndexedListSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return true
         }
     }
@@ -93,6 +93,12 @@
     }
 }
 
+inline fun <T> IndexedListSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, elementAt(index))
+    }
+}
+
 inline val <T> IndexedListSet<T>.lastIndex: Int
     get() = size - 1
 
@@ -106,8 +112,8 @@
 }
 
 inline fun <T> IndexedListSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return false
         }
     }
@@ -123,18 +129,24 @@
     add(element)
 }
 
-inline fun <T> IndexedListSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, elementAt(index))) {
+inline fun <T> IndexedListSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, element ->
+        if (predicate(index, element)) {
             removeAt(index)
+            isChanged = true
         }
     }
+    return isChanged
 }
 
-inline fun <T> IndexedListSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, elementAt(index))) {
+inline fun <T> IndexedListSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, element ->
+        if (!predicate(index, element)) {
             removeAt(index)
+            isChanged = true
         }
     }
+    return isChanged
 }
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
index 1251666..43f18e2 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
@@ -21,8 +21,8 @@
 typealias IndexedMap<K, V> = ArrayMap<K, V>
 
 inline fun <K, V> IndexedMap<K, V>.allIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, keyAt(index), valueAt(index))) {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
             return false
         }
     }
@@ -30,8 +30,8 @@
 }
 
 inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, keyAt(index), valueAt(index))) {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
             return true
         }
     }
@@ -46,8 +46,8 @@
     }
 
 inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? {
-    for (index in 0 until size) {
-        transform(index, keyAt(index), valueAt(index))?.let { return it }
+    forEachIndexed { index, key, value ->
+        transform(index, key, value)?.let { return it }
     }
     return null
 }
@@ -64,6 +64,12 @@
     }
 }
 
+inline fun <K, V> IndexedMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
 inline fun <K, V> IndexedMap<K, V>.forEachValueIndexed(action: (Int, V) -> Unit) {
     for (index in 0 until size) {
         action(index, valueAt(index))
@@ -90,6 +96,15 @@
     remove(key)
 }
 
+inline fun <K, V> IndexedMap<K, V>.noneIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
 @Suppress("NOTHING_TO_INLINE")
 inline fun <K, V> IndexedMap<K, V>.putWithDefault(key: K, value: V, defaultValue: V): V {
     val index = indexOfKey(key)
@@ -111,20 +126,26 @@
     }
 }
 
-inline fun <K, V> IndexedMap<K, V>.removeAllIndexed(predicate: (Int, K, V) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, keyAt(index), valueAt(index))) {
+inline fun <K, V> IndexedMap<K, V>.removeAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
             removeAt(index)
+            isChanged = true
         }
     }
+    return isChanged
 }
 
-inline fun <K, V> IndexedMap<K, V>.retainAllIndexed(predicate: (Int, K, V) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, keyAt(index), valueAt(index))) {
+inline fun <K, V> IndexedMap<K, V>.retainAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
             removeAt(index)
+            isChanged = true
         }
     }
+    return isChanged
 }
 
 @Suppress("NOTHING_TO_INLINE")
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
index 36d8ff0c..13fa31f 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
@@ -21,8 +21,8 @@
 typealias IndexedSet<T> = ArraySet<T>
 
 inline fun <T> IndexedSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
             return false
         }
     }
@@ -30,8 +30,8 @@
 }
 
 inline fun <T> IndexedSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return true
         }
     }
@@ -50,6 +50,12 @@
     }
 }
 
+inline fun <T> IndexedSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, elementAt(index))
+    }
+}
+
 inline val <T> IndexedSet<T>.lastIndex: Int
     get() = size - 1
 
@@ -63,8 +69,8 @@
 }
 
 inline fun <T> IndexedSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return false
         }
     }
@@ -80,20 +86,26 @@
     add(element)
 }
 
-inline fun <T> IndexedSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, elementAt(index))) {
+inline fun <T> IndexedSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, element ->
+        if (predicate(index, element)) {
             removeAt(index)
+            isChanged = true
         }
     }
+    return isChanged
 }
 
-inline fun <T> IndexedSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, elementAt(index))) {
+inline fun <T> IndexedSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, element ->
+        if (!predicate(index, element)) {
             removeAt(index)
+            isChanged = true
         }
     }
+    return isChanged
 }
 
 @Suppress("NOTHING_TO_INLINE")
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
index 9051c66..e905567 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
@@ -21,8 +21,8 @@
 typealias IntMap<T> = SparseArray<T>
 
 inline fun <T> IntMap<T>.allIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, keyAt(index), valueAt(index))) {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
             return false
         }
     }
@@ -30,8 +30,8 @@
 }
 
 inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, keyAt(index), valueAt(index))) {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
             return true
         }
     }
@@ -46,8 +46,8 @@
     }
 
 inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? {
-    for (index in 0 until size) {
-        transform(index, keyAt(index), valueAt(index))?.let { return it }
+    forEachIndexed { index, key, value ->
+        transform(index, key, value)?.let { return it }
     }
     return null
 }
@@ -64,6 +64,12 @@
     }
 }
 
+inline fun <T> IntMap<T>.forEachReversedIndexed(action: (Int, Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
 inline fun <T> IntMap<T>.forEachValueIndexed(action: (Int, T) -> Unit) {
     for (index in 0 until size) {
         action(index, valueAt(index))
@@ -91,8 +97,8 @@
 }
 
 inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, keyAt(index), valueAt(index))) {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
             return false
         }
     }
@@ -120,20 +126,38 @@
     }
 }
 
-inline fun <T> IntMap<T>.removeAllIndexed(predicate: (Int, Int, T) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, keyAt(index), valueAt(index))) {
-            removeAt(index)
-        }
+// SparseArray.removeReturnOld() is @hide, so a backup once we move to APIs.
+fun <T> IntMap<T>.removeReturnOld(key: Int): T? {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        val oldValue = valueAt(index)
+        removeAt(index)
+        oldValue
+    } else {
+        null
     }
 }
 
-inline fun <T> IntMap<T>.retainAllIndexed(predicate: (Int, Int, T) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, keyAt(index), valueAt(index))) {
+inline fun <T> IntMap<T>.removeAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
             removeAt(index)
+            isChanged = true
         }
     }
+    return isChanged
+}
+
+inline fun <T> IntMap<T>.retainAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
 }
 
 inline val <T> IntMap<T>.size: Int
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
index d6dfe9d..4717251 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
@@ -51,9 +51,11 @@
     fun copy(): IntSet = IntSet(array.clone())
 }
 
+fun IntSet(values: IntArray): IntSet = IntSet().apply{ this += values }
+
 inline fun IntSet.allIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
             return false
         }
     }
@@ -61,8 +63,8 @@
 }
 
 inline fun IntSet.anyIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return true
         }
     }
@@ -75,6 +77,12 @@
     }
 }
 
+inline fun IntSet.forEachReversedIndexed(action: (Int, Int) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, elementAt(index))
+    }
+}
+
 inline val IntSet.lastIndex: Int
     get() = size - 1
 
@@ -87,8 +95,8 @@
 }
 
 inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return false
         }
     }
@@ -103,18 +111,32 @@
     add(element)
 }
 
-inline fun IntSet.removeAllIndexed(predicate: (Int, Int) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, elementAt(index))) {
-            removeAt(index)
-        }
-    }
+operator fun IntSet.plusAssign(set: IntSet) {
+    set.forEachIndexed { _, it -> this += it }
 }
 
-inline fun IntSet.retainAllIndexed(predicate: (Int, Int) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, elementAt(index))) {
+operator fun IntSet.plusAssign(array: IntArray) {
+    array.forEach { this += it }
+}
+
+inline fun IntSet.removeAllIndexed(predicate: (Int, Int) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, element ->
+        if (predicate(index, element)) {
             removeAt(index)
+            isChanged = true
         }
     }
+    return isChanged
+}
+
+inline fun IntSet.retainAllIndexed(predicate: (Int, Int) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, element ->
+        if (!predicate(index, element)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
 }
diff --git a/services/permission/java/com/android/server/permission/access/collection/List.kt b/services/permission/java/com/android/server/permission/access/collection/List.kt
index dc28642..91f15bc 100644
--- a/services/permission/java/com/android/server/permission/access/collection/List.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/List.kt
@@ -17,8 +17,8 @@
 package com.android.server.permission.access.collection
 
 inline fun <T> List<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, this[index])) {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
             return false
         }
     }
@@ -26,8 +26,8 @@
 }
 
 inline fun <T> List<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, this[index])) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return true
         }
     }
@@ -40,27 +40,39 @@
     }
 }
 
+inline fun <T> List<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, this[index])
+    }
+}
+
 inline fun <T> List<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, this[index])) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return false
         }
     }
     return true
 }
 
-inline fun <T> MutableList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, this[index])) {
+inline fun <T> MutableList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, element ->
+        if (predicate(index, element)) {
             removeAt(index)
+            isChanged = true
         }
     }
+    return isChanged
 }
 
-inline fun <T> MutableList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean) {
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, this[index])) {
+inline fun <T> MutableList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, element ->
+        if (!predicate(index, element)) {
             removeAt(index)
+            isChanged = true
         }
     }
+    return isChanged
 }
diff --git a/services/permission/java/com/android/server/permission/access/data/Package.kt b/services/permission/java/com/android/server/permission/access/data/Package.kt
deleted file mode 100644
index d6f98ab..0000000
--- a/services/permission/java/com/android/server/permission/access/data/Package.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2021 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.permission.access.data
-
-import com.android.server.permission.access.external.AndroidPackage
-
-class Package(
-    private val androidPackage: AndroidPackage
-) {
-    val name: String
-        get() = androidPackage.packageName
-
-    val adoptPermissions: List<String>
-        get() = androidPackage.adoptPermissions
-
-    val appId: Int
-        get() = androidPackage.appId
-
-    val requestedPermissions: List<String>
-        get() = androidPackage.requestedPermissions
-
-    override fun equals(other: Any?): Boolean {
-        throw NotImplementedError()
-    }
-
-    override fun hashCode(): Int {
-        throw NotImplementedError()
-    }
-}
diff --git a/services/permission/java/com/android/server/permission/access/external/CompatibilityPermissionInfo.kt b/services/permission/java/com/android/server/permission/access/external/CompatibilityPermissionInfo.kt
deleted file mode 100644
index aadd8ba..0000000
--- a/services/permission/java/com/android/server/permission/access/external/CompatibilityPermissionInfo.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2022 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.permission.access.external
-
-class CompatibilityPermissionInfo {
-    companion object {
-        val COMPAT_PERMS = arrayOf(CompatibilityPermissionInfo())
-    }
-
-    val name: String
-        get() = throw NotImplementedError()
-
-    val sdkVersion: Int
-        get() = throw NotImplementedError()
-}
diff --git a/services/permission/java/com/android/server/permission/access/external/KnownPackages.kt b/services/permission/java/com/android/server/permission/access/external/KnownPackages.kt
deleted file mode 100644
index 1239450..0000000
--- a/services/permission/java/com/android/server/permission/access/external/KnownPackages.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2022 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.permission.access.external
-
-class KnownPackages {
-    companion object {
-        const val PACKAGE_SYSTEM = 0
-        const val PACKAGE_SETUP_WIZARD = 1
-        const val PACKAGE_INSTALLER = 2
-        const val PACKAGE_VERIFIER = 4
-        const val PACKAGE_SYSTEM_TEXT_CLASSIFIER = 6
-        const val PACKAGE_PERMISSION_CONTROLLER = 7
-        const val PACKAGE_CONFIGURATOR = 10
-        const val PACKAGE_INCIDENT_REPORT_APPROVER = 11
-        const val PACKAGE_APP_PREDICTOR = 12
-        const val PACKAGE_COMPANION = 15
-        const val PACKAGE_RETAIL_DEMO = 16
-        const val PACKAGE_RECENTS = 17
-    }
-}
diff --git a/services/permission/java/com/android/server/permission/access/external/PackageInfoUtils.kt b/services/permission/java/com/android/server/permission/access/external/PackageInfoUtils.kt
deleted file mode 100644
index 24b28bd..0000000
--- a/services/permission/java/com/android/server/permission/access/external/PackageInfoUtils.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2021 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.permission.access.external
-
-import android.content.pm.PermissionGroupInfo
-import android.content.pm.PermissionInfo
-
-object PackageInfoUtils {
-    fun generatePermissionInfo(parsedPermission: ParsedPermission, flags: Long): PermissionInfo {
-        throw NotImplementedError()
-    }
-
-    fun generatePermissionGroupInfo(
-        parsedPermissionGroup: ParsedPermissionGroup,
-        flags: Long
-    ): PermissionGroupInfo {
-        throw NotImplementedError()
-    }
-}
diff --git a/services/permission/java/com/android/server/permission/access/external/PackageState.kt b/services/permission/java/com/android/server/permission/access/external/PackageState.kt
deleted file mode 100644
index b81d794..0000000
--- a/services/permission/java/com/android/server/permission/access/external/PackageState.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2021 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.permission.access.external
-
-import android.util.SparseArray
-
-interface PackageState {
-    val androidPackage: AndroidPackage?
-    val appId: Int
-    val isSystem: Boolean
-    val isUpdatedSystemApp: Boolean
-    val packageName: String
-    val userStates: SparseArray<PackageUserState>
-    val hasSharedUser: Boolean
-    val sharedUserAppId: Int
-    val signingDetails: SigningDetails
-}
-
-interface AndroidPackage {
-    val packageName: String
-    val apexModuleName: String?
-    val appId: Int
-    val isPrivileged: Boolean
-    val isOem: Boolean
-    val isVendor: Boolean
-    val isProduct: Boolean
-    val isSystemExt: Boolean
-    val targetSdkVersion: Int
-    val adoptPermissions: List<String>
-    val permissions: List<ParsedPermission>
-    val permissionGroups: List<ParsedPermissionGroup>
-    val requestedPermissions: List<String>
-    val implicitPermissions: List<String>
-}
-
-interface ParsedPermission {
-    val name: String
-    val isTree: Boolean
-    val packageName: String
-    val isSignature: Boolean
-    val protectionLevel: Int
-}
-
-interface ParsedPermissionGroup {
-    val name: String
-    val packageName: String
-}
-
-interface PackageUserState {
-    val isInstantApp: Boolean
-}
diff --git a/services/permission/java/com/android/server/permission/access/external/SigningDetails.kt b/services/permission/java/com/android/server/permission/access/external/SigningDetails.kt
deleted file mode 100644
index 25917f9..0000000
--- a/services/permission/java/com/android/server/permission/access/external/SigningDetails.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2022 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.permission.access.external
-
-object SigningDetails {
-    fun hasCommonSignerWithCapability(otherDetails: SigningDetails, flags: Int): Boolean {
-        throw NotImplementedError()
-    }
-
-    fun hasAncestorOrSelf(oldDetails: SigningDetails): Boolean {
-        throw NotImplementedError()
-    }
-
-    fun checkCapability(oldDetails: SigningDetails, flags: Int): Boolean {
-        throw NotImplementedError()
-    }
-
-    fun hasAncestorOrSelfWithDigest(certDigests: Set<String>): Boolean {
-        throw NotImplementedError()
-    }
-
-    class CertCapabilities {
-        companion object {
-            /** grant SIGNATURE permissions to pkgs with this cert  */
-            var PERMISSION = 4
-        }
-    }
-}
diff --git a/services/permission/java/com/android/server/permission/access/external/UserHandle.kt b/services/permission/java/com/android/server/permission/access/external/UserHandle.kt
deleted file mode 100644
index e1072a7..0000000
--- a/services/permission/java/com/android/server/permission/access/external/UserHandle.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2021 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.permission.access.external
-
-interface UserHandle {
-    companion object {
-        fun getAppId(uid: Int): Int {
-            throw NotImplementedError()
-        }
-    }
-}
-
-object UserHandleCompat {
-    fun getUserId(uid: Int): Int {
-        throw NotImplementedError()
-    }
-}
diff --git a/services/permission/java/com/android/server/permission/access/data/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
similarity index 98%
rename from services/permission/java/com/android/server/permission/access/data/Permission.kt
rename to services/permission/java/com/android/server/permission/access/permission/Permission.kt
index 877f23b..efa5bf3 100644
--- a/services/permission/java/com/android/server/permission/access/data/Permission.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.permission.access.data
+package com.android.server.permission.access.permission
 
 import android.content.pm.PermissionInfo
 import com.android.server.permission.access.util.hasBits
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
index bb1a86c..6b2b1856 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -16,17 +16,468 @@
 
 package com.android.server.permission.access.permission
 
-object PermissionFlags {
-    const val INSTALL_GRANTED = 1 shl 0
-    const val INSTALL_REVOKED = 1 shl 1
-    const val PROTECTION_GRANTED = 1 shl 2
-    const val ROLE_GRANTED = 1 shl 3
-    // For permissions that are granted in other ways,
-    // ex: via an API or implicit permissions that inherit from granted install permissions
-    const val OTHER_GRANTED = 1 shl 4
-    // For the permissions that are implicit for the package
-    const val IMPLICIT = 1 shl 5
+import android.app.AppOpsManager
+import android.app.admin.DevicePolicyManager
+import android.content.pm.PackageManager
+import android.os.Build
+import android.permission.PermissionManager
+import com.android.server.permission.access.util.andInv
+import com.android.server.permission.access.util.hasAnyBit
+import com.android.server.permission.access.util.hasBits
 
-    const val MASK_GRANTED = INSTALL_GRANTED or PROTECTION_GRANTED or OTHER_GRANTED or ROLE_GRANTED
-    const val MASK_RUNTIME = OTHER_GRANTED or IMPLICIT
+/**
+ * A set of internal permission flags that's better than the set of `FLAG_PERMISSION_*` constants on
+ * [PackageManager].
+ *
+ * The old binary permission state is now tracked by multiple `*_GRANTED` and `*_REVOKED` flags, so
+ * that:
+ *
+ * - With [INSTALL_GRANTED] and [INSTALL_REVOKED], we can now get rid of the old per-package
+ *   `areInstallPermissionsFixed` attribute and correctly track it per-permission, finally fixing
+ *   edge cases during module rollbacks.
+ *
+ * - With [LEGACY_GRANTED] and [IMPLICIT_GRANTED], we can now ensure that legacy permissions and
+ *   implicit permissions split from non-runtime permissions are never revoked, without checking
+ *   split permissions and package state everywhere slowly and in slightly different ways.
+ *
+ * - With [RESTRICTION_REVOKED], we can now get rid of the error-prone logic about revoking and
+ *   potentially re-granting permissions upon restriction state changes.
+ *
+ * Permission grants due to protection level are now tracked by [PROTECTION_GRANTED], and permission
+ * grants due to [PackageManager.grantRuntimePermission] are now tracked by [RUNTIME_GRANTED].
+ *
+ * The [PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED] and
+ * [PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED] flags are now unified into [IMPLICIT], and
+ * they can be differentiated by the presence of [LEGACY_GRANTED].
+ *
+ * The rest of the permission flags have a 1:1 mapping to the old `FLAG_PERMISSION_*` constants, and
+ * don't have any effect on the binary permission state.
+ */
+object PermissionFlags {
+    /**
+     * Permission flag for a normal permission that is granted at package installation.
+     */
+    const val INSTALL_GRANTED = 1 shl 0
+
+    /**
+     * Permission flag for a normal permission that is revoked at package installation.
+     *
+     * Normally packages that have already been installed cannot be granted new normal permissions
+     * until its next installation (update), so this flag helps track that the normal permission was
+     * revoked upon its most recent installation.
+     */
+    const val INSTALL_REVOKED = 1 shl 1
+
+    /**
+     * Permission flag for a signature or internal permission that is granted based on the
+     * permission's protection level, including its protection and protection flags.
+     *
+     * For example, this flag may be set when the permission is a signature permission and the
+     * package is having a compatible signing certificate with the package defining the permission,
+     * or when the permission is a privileged permission and the package is a privileged app with
+     * its permission in the
+     * [privileged permission allowlist](https://source.android.com/docs/core/permissions/perms-allowlist).
+     */
+    const val PROTECTION_GRANTED = 1 shl 2
+
+    /**
+     * Permission flag for a role or runtime permission that is or was granted by a role.
+     *
+     * @see PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE
+     */
+    const val ROLE = 1 shl 3
+
+    /**
+     * Permission flag for a development, role or runtime permission that is granted via
+     * [PackageManager.grantRuntimePermission].
+     */
+    const val RUNTIME_GRANTED = 1 shl 4
+
+    /**
+     * Permission flag for a runtime permission whose state is set by the user.
+     *
+     * For example, this flag may be set when the permission is allowed by the user in the
+     * request permission dialog, or managed in the permission settings.
+     *
+     * @see PackageManager.FLAG_PERMISSION_USER_SET
+     */
+    const val USER_SET = 1 shl 5
+
+    /**
+     * Permission flag for a runtime permission whose state is (revoked and) fixed by the user.
+     *
+     * For example, this flag may be set when the permission is denied twice by the user in the
+     * request permission dialog.
+     *
+     * @see PackageManager.FLAG_PERMISSION_USER_FIXED
+     */
+    const val USER_FIXED = 1 shl 6
+
+    /**
+     * Permission flag for a runtime permission whose state is set and fixed by the device policy
+     * via [DevicePolicyManager.setPermissionGrantState].
+     *
+     * @see PackageManager.FLAG_PERMISSION_POLICY_FIXED
+     */
+    const val POLICY_FIXED = 1 shl 7
+
+    /**
+     * Permission flag for a runtime permission that is (pregranted and) fixed by the system.
+     *
+     * For example, this flag may be set in
+     * [com.android.server.pm.permission.DefaultPermissionGrantPolicy].
+     *
+     * @see PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+     */
+    const val SYSTEM_FIXED = 1 shl 8
+
+    /**
+     * Permission flag for a runtime permission that is or was pregranted by the system.
+     *
+     * For example, this flag may be set in
+     * [com.android.server.pm.permission.DefaultPermissionGrantPolicy].
+     *
+     * @see PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+     */
+    const val PREGRANT = 1 shl 9
+
+    /**
+     * Permission flag for a runtime permission that is granted because the package targets a legacy
+     * SDK version before [Build.VERSION_CODES.M] and doesn't support runtime permissions.
+     *
+     * As long as this flag is set, the permission should always be considered granted, although
+     * [APP_OP_REVOKED] may cause the app op for the runtime permission to be revoked. Once the
+     * package targets a higher SDK version so that it started supporting runtime permissions, this
+     * flag should be removed and the remaining flags should take effect.
+     *
+     * @see PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+     * @see PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
+     */
+    const val LEGACY_GRANTED = 1 shl 10
+
+    /**
+     * Permission flag for a runtime permission that is granted because the package targets a lower
+     * SDK version and the permission is implicit to it as a
+     * [split permission][PermissionManager.SplitPermissionInfo] from other non-runtime permissions.
+     *
+     * As long as this flag is set, the permission should always be considered granted, although
+     * [APP_OP_REVOKED] may cause the app op for the runtime permission to be revoked. Once the
+     * package targets a higher SDK version so that the permission is no longer implicit to it, this
+     * flag should be removed and the remaining flags should take effect.
+     *
+     * @see PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+     * @see PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
+     */
+    const val IMPLICIT_GRANTED = 1 shl 11
+
+    /**
+     * Permission flag for a runtime permission that is granted because the package targets a legacy
+     * SDK version before [Build.VERSION_CODES.M] and doesn't support runtime permissions, so that
+     * it needs to be reviewed by the user; or granted because the package targets a lower SDK
+     * version and the permission is implicit to it as a
+     * [split permission][PermissionManager.SplitPermissionInfo] from other non-runtime permissions,
+     * so that it needs to be revoked when it's no longer implicit.
+     *
+     * @see PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+     * @see PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+     */
+    const val IMPLICIT = 1 shl 12
+
+    /**
+     * Permission flag for a runtime permission that is user-sensitive when it's granted.
+     *
+     * This flag is informational and managed by PermissionController.
+     *
+     * @see PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+     */
+    const val USER_SENSITIVE_WHEN_GRANTED = 1 shl 13
+
+    /**
+     * Permission flag for a runtime permission that is user-sensitive when it's revoked.
+     *
+     * This flag is informational and managed by PermissionController.
+     *
+     * @see PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
+     */
+    const val USER_SENSITIVE_WHEN_REVOKED = 1 shl 14
+
+    /**
+     * Permission flag for a restricted runtime permission that is exempt by the package's
+     * installer.
+     *
+     * For example, this flag may be set when the installer applied the exemption as part of the
+     * [session parameters](https://developer.android.com/reference/android/content/pm/PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(java.util.Set%3Cjava.lang.String%3E)).
+     *
+     * The permission will be restricted when none of the exempt flags is set.
+     *
+     * @see PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT
+     */
+    const val INSTALLER_EXEMPT = 1 shl 15
+
+    /**
+     * Permission flag for a restricted runtime permission that is exempt by the system.
+     *
+     * For example, this flag may be set when the package is a system app.
+     *
+     * The permission will be restricted when none of the exempt flags is set.
+     *
+     * @see PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
+     */
+    const val SYSTEM_EXEMPT = 1 shl 16
+
+    /**
+     * Permission flag for a restricted runtime permission that is exempt due to system upgrade.
+     *
+     * For example, this flag may be set when the package was installed before the system was
+     * upgraded to [Build.VERSION_CODES.Q], when restricted permissions were introduced.
+     *
+     * The permission will be restricted when none of the exempt flags is set.
+     *
+     * @see PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
+     */
+    const val UPGRADE_EXEMPT = 1 shl 17
+
+    /**
+     * Permission flag for a restricted runtime permission that is revoked due to being hard
+     * restricted.
+     *
+     * @see PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
+     */
+    const val RESTRICTION_REVOKED = 1 shl 18
+
+    /**
+     * Permission flag for a restricted runtime permission that is soft restricted.
+     *
+     * @see PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
+     */
+    const val SOFT_RESTRICTED = 1 shl 19
+
+    /**
+     * Permission flag for a runtime permission whose app op is revoked.
+     *
+     * For example, this flag may be set when the runtime permission is legacy or implicit but still
+     * "revoked" by the user in permission settings, or when the app op mode for the runtime
+     * permission is set to revoked via [AppOpsManager.setUidMode].
+     *
+     * @see PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
+     */
+    const val APP_OP_REVOKED = 1 shl 20
+
+    /**
+     * Permission flag for a runtime permission that is granted as one-time.
+     *
+     * For example, this flag may be set when the user selected "Only this time" in the request
+     * permission dialog.
+     *
+     * This flag, along with other user decisions when it is set, should never be persisted, and
+     * should be removed once the permission is revoked.
+     *
+     * @see PackageManager.FLAG_PERMISSION_ONE_TIME
+     */
+    const val ONE_TIME = 1 shl 21
+
+    /**
+     * Permission flag for a runtime permission that was revoked due to app hibernation.
+     *
+     * This flag is informational and added by PermissionController, and should be removed once the
+     * permission is granted again.
+     *
+     * @see PackageManager.FLAG_PERMISSION_AUTO_REVOKED
+     */
+    const val HIBERNATION = 1 shl 22
+
+    /**
+     * Permission flag for a runtime permission that is selected by the user.
+     *
+     * For example, this flag may be set when one of the coarse/fine location accuracies is
+     * selected by the user.
+     *
+     * This flag is informational and managed by PermissionController.
+     *
+     * @see PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY
+     */
+    const val USER_SELECTED = 1 shl 23
+
+    /**
+     * Mask for all permission flags.
+     */
+    const val MASK_ALL = 0.inv()
+
+    /**
+     * Mask for all permission flags that may be applied to a runtime permission.
+     */
+    const val MASK_RUNTIME = ROLE or RUNTIME_GRANTED or USER_SET or USER_FIXED or POLICY_FIXED or
+        SYSTEM_FIXED or PREGRANT or LEGACY_GRANTED or IMPLICIT_GRANTED or IMPLICIT or
+        USER_SENSITIVE_WHEN_GRANTED or USER_SENSITIVE_WHEN_REVOKED or INSTALLER_EXEMPT or
+        SYSTEM_EXEMPT or UPGRADE_EXEMPT or RESTRICTION_REVOKED or SOFT_RESTRICTED or
+        APP_OP_REVOKED or ONE_TIME or HIBERNATION or USER_SELECTED
+
+    /**
+     * Mask for all API permission flags about permission restriction.
+     */
+    private const val API_MASK_RESTRICTION =
+        PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or
+            PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or
+            PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or
+            PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
+
+    /**
+     * Mask for all permission flags about permission restriction.
+     */
+    private const val MASK_RESTRICTION = INSTALLER_EXEMPT or SYSTEM_EXEMPT or
+        UPGRADE_EXEMPT or RESTRICTION_REVOKED or SOFT_RESTRICTED
+
+    fun isPermissionGranted(policyFlags: Int): Boolean {
+        if (policyFlags.hasBits(INSTALL_GRANTED)) {
+            return true
+        }
+        if (policyFlags.hasBits(INSTALL_REVOKED)) {
+            return false
+        }
+        if (policyFlags.hasBits(PROTECTION_GRANTED)) {
+            return true
+        }
+        if (policyFlags.hasBits(LEGACY_GRANTED) || policyFlags.hasBits(IMPLICIT_GRANTED)) {
+            return true
+        }
+        if (policyFlags.hasBits(RESTRICTION_REVOKED)) {
+            return false
+        }
+        return policyFlags.hasBits(RUNTIME_GRANTED)
+    }
+
+    fun isAppOpGranted(policyFlags: Int): Boolean =
+        isPermissionGranted(policyFlags) && !policyFlags.hasBits(APP_OP_REVOKED)
+
+    fun isReviewRequired(policyFlags: Int): Boolean =
+        policyFlags.hasBits(LEGACY_GRANTED) && policyFlags.hasBits(IMPLICIT)
+
+    fun toApiFlags(policyFlags: Int): Int {
+        var apiFlags = 0
+        if (policyFlags.hasBits(USER_SET)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SET
+        }
+        if (policyFlags.hasBits(USER_FIXED)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_FIXED
+        }
+        if (policyFlags.hasBits(POLICY_FIXED)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_POLICY_FIXED
+        }
+        if (policyFlags.hasBits(SYSTEM_FIXED)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+        }
+        if (policyFlags.hasBits(PREGRANT)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
+        }
+        if (policyFlags.hasBits(IMPLICIT)) {
+            apiFlags = apiFlags or if (policyFlags.hasBits(LEGACY_GRANTED)) {
+                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+            } else {
+                PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+            }
+        }
+        if (policyFlags.hasBits(USER_SENSITIVE_WHEN_GRANTED)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+        }
+        if (policyFlags.hasBits(USER_SENSITIVE_WHEN_REVOKED)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
+        }
+        if (policyFlags.hasBits(INSTALLER_EXEMPT)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT
+        }
+        if (policyFlags.hasBits(SYSTEM_EXEMPT)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
+        }
+        if (policyFlags.hasBits(UPGRADE_EXEMPT)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
+        }
+        if (policyFlags.hasBits(RESTRICTION_REVOKED) || policyFlags.hasBits(SOFT_RESTRICTED)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
+        }
+        if (policyFlags.hasBits(ROLE)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE
+        }
+        if (policyFlags.hasBits(APP_OP_REVOKED)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
+        }
+        if (policyFlags.hasBits(ONE_TIME)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_ONE_TIME
+        }
+        if (policyFlags.hasBits(HIBERNATION)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_AUTO_REVOKED
+        }
+        if (policyFlags.hasBits(USER_SELECTED)) {
+            apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY
+        }
+        return apiFlags
+    }
+
+    fun setRuntimePermissionGranted(policyFlags: Int, isGranted: Boolean): Int =
+        if (isGranted) policyFlags or RUNTIME_GRANTED else policyFlags andInv RUNTIME_GRANTED
+
+    fun updatePolicyFlags(policyFlags: Int, apiFlagMask: Int, apiFlagValues: Int): Int {
+        check(!apiFlagMask.hasAnyBit(API_MASK_RESTRICTION)) {
+            "Permission flags about permission restriction can only be directly mutated by the" +
+                " policy"
+        }
+        val oldApiFlags = toApiFlags(policyFlags)
+        val newApiFlags = (oldApiFlags andInv apiFlagMask) or (apiFlagValues and apiFlagMask)
+        return toPolicyFlags(policyFlags, newApiFlags)
+    }
+
+    private fun toPolicyFlags(oldPolicyFlags: Int, apiFlags: Int): Int {
+        var policyFlags = 0
+        policyFlags = policyFlags or (oldPolicyFlags and INSTALL_GRANTED)
+        policyFlags = policyFlags or (oldPolicyFlags and INSTALL_REVOKED)
+        policyFlags = policyFlags or (oldPolicyFlags and PROTECTION_GRANTED)
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE)) {
+            policyFlags = policyFlags or ROLE
+        }
+        policyFlags = policyFlags or (oldPolicyFlags and RUNTIME_GRANTED)
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SET)) {
+            policyFlags = policyFlags or USER_SET
+        }
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_FIXED)) {
+            policyFlags = policyFlags or USER_FIXED
+        }
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_POLICY_FIXED)) {
+            policyFlags = policyFlags or POLICY_FIXED
+        }
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)) {
+            policyFlags = policyFlags or SYSTEM_FIXED
+        }
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT)) {
+            policyFlags = policyFlags or PREGRANT
+        }
+        policyFlags = policyFlags or (oldPolicyFlags and LEGACY_GRANTED)
+        policyFlags = policyFlags or (oldPolicyFlags and IMPLICIT_GRANTED)
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) ||
+            apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)) {
+            policyFlags = policyFlags or IMPLICIT
+        }
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)) {
+            policyFlags = policyFlags or USER_SENSITIVE_WHEN_GRANTED
+        }
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED)) {
+            policyFlags = policyFlags or USER_SENSITIVE_WHEN_REVOKED
+        }
+        // FLAG_PERMISSION_APPLY_RESTRICTION can be either REVOKED_BY_RESTRICTION when the
+        // permission is hard restricted, or SOFT_RESTRICTED when the permission is soft restricted.
+        // However since we should never allow indirect mutation of restriction state, we can just
+        // get the flags about restriction from the old policy flags.
+        policyFlags = policyFlags or (oldPolicyFlags and MASK_RESTRICTION)
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)) {
+            policyFlags = policyFlags or APP_OP_REVOKED
+        }
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_ONE_TIME)) {
+            policyFlags = policyFlags or ONE_TIME
+        }
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)) {
+            policyFlags = policyFlags or HIBERNATION
+        }
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY)) {
+            policyFlags = policyFlags or USER_SELECTED
+        }
+        return policyFlags
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
similarity index 81%
rename from services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt
rename to services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index cc51866..8201736 100644
--- a/services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -33,8 +33,9 @@
 import com.android.server.permission.access.AccessCheckingService
 import com.android.server.permission.access.PermissionUri
 import com.android.server.permission.access.UidUri
-import com.android.server.permission.access.data.Permission
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.hasBits
+import com.android.server.pm.UserManagerService
 import com.android.server.pm.permission.LegacyPermission
 import com.android.server.pm.permission.LegacyPermissionSettings
 import com.android.server.pm.permission.LegacyPermissionState
@@ -46,17 +47,24 @@
 /**
  * Modern implementation of [PermissionManagerServiceInterface].
  */
-class ModernPermissionManagerServiceImpl(
+class PermissionService(
     private val service: AccessCheckingService
 ) : PermissionManagerServiceInterface {
     private val policy =
         service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as UidPermissionPolicy
 
-    private val packageManagerInternal =
-        LocalServices.getService(PackageManagerInternal::class.java)
+    private lateinit var packageManagerInternal: PackageManagerInternal
+    private lateinit var packageManagerLocal: PackageManagerLocal
+    private lateinit var userManagerService: UserManagerService
 
-    private val packageManagerLocal =
-        LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
+    private val mountedStorageVolumes = IndexedSet<String?>()
+
+    fun initialize() {
+        packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java)
+        packageManagerLocal =
+            LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
+        userManagerService = UserManagerService.getInstance()
+    }
 
     override fun getAllPermissionGroups(flags: Int): List<PermissionGroupInfo> {
         TODO("Not yet implemented")
@@ -224,7 +232,12 @@
     }
 
     override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
-        TODO("Not yet implemented")
+        // TODO: Implement permission checks.
+        val appId = 0
+        val flags = service.getState {
+            with(policy) { getPermissionFlags(appId, userId, permissionName) }
+        }
+        return PermissionFlags.toApiFlags(flags)
     }
 
     override fun isPermissionRevokedByPolicy(
@@ -236,7 +249,12 @@
     }
 
     override fun isPermissionsReviewRequired(packageName: String, userId: Int): Boolean {
-        TODO("Not yet implemented")
+        val packageState = packageManagerLocal.withUnfilteredSnapshot()
+            .use { it.packageStates[packageName] } ?: return false
+        val permissionFlags = service.getState {
+            with(policy) { getUidPermissionFlags(packageState.appId, userId) }
+        } ?: return false
+        return permissionFlags.anyIndexed { _, _, flags -> PermissionFlags.isReviewRequired(flags) }
     }
 
     override fun shouldShowRequestPermissionRationale(
@@ -351,6 +369,8 @@
     }
 
     override fun readLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) {
+        // Package settings has been read when this method is called.
+        service.initialize()
         TODO("Not yet implemented")
     }
 
@@ -375,15 +395,18 @@
     }
 
     override fun onUserCreated(userId: Int) {
-        TODO("Not yet implemented")
+        service.onUserAdded(userId)
     }
 
     override fun onUserRemoved(userId: Int) {
-        TODO("Not yet implemented")
+        service.onUserRemoved(userId)
     }
 
     override fun onStorageVolumeMounted(volumeUuid: String, fingerprintChanged: Boolean) {
-        TODO("Not yet implemented")
+        service.onStorageVolumeMounted(volumeUuid, fingerprintChanged)
+        synchronized(mountedStorageVolumes) {
+            mountedStorageVolumes += volumeUuid
+        }
     }
 
     override fun onPackageAdded(
@@ -391,7 +414,18 @@
         isInstantApp: Boolean,
         oldPackage: AndroidPackage?
     ) {
-        TODO("Not yet implemented")
+        synchronized(mountedStorageVolumes) {
+            if (androidPackage.volumeUuid !in mountedStorageVolumes) {
+                // Wait for the storage volume to be mounted and batch the state mutation there.
+                return
+            }
+        }
+        service.onPackageAdded(androidPackage.packageName)
+    }
+
+    override fun onPackageRemoved(androidPackage: AndroidPackage) {
+        // This may not be a full removal so ignored - we'll figure out full removal in
+        // onPackageUninstalled().
     }
 
     override fun onPackageInstalled(
@@ -400,21 +434,38 @@
         params: PermissionManagerServiceInternal.PackageInstalledParams,
         userId: Int
     ) {
-        TODO("Not yet implemented")
+        synchronized(mountedStorageVolumes) {
+            if (androidPackage.volumeUuid !in mountedStorageVolumes) {
+                // Wait for the storage volume to be mounted and batch the state mutation there.
+                return
+            }
+        }
+        val userIds = if (userId == UserHandle.USER_ALL) {
+            userManagerService.userIdsIncludingPreCreated
+        } else {
+            intArrayOf(userId)
+        }
+        userIds.forEach { service.onPackageInstalled(androidPackage.packageName, it) }
+        // TODO: Handle params.
     }
 
     override fun onPackageUninstalled(
         packageName: String,
         appId: Int,
         androidPackage: AndroidPackage?,
-        sharedUserPkgs: MutableList<AndroidPackage>,
+        sharedUserPkgs: List<AndroidPackage>,
         userId: Int
     ) {
-        TODO("Not yet implemented")
-    }
-
-    override fun onPackageRemoved(androidPackage: AndroidPackage) {
-        TODO("Not yet implemented")
+        val userIds = if (userId == UserHandle.USER_ALL) {
+            userManagerService.userIdsIncludingPreCreated
+        } else {
+            intArrayOf(userId)
+        }
+        userIds.forEach { service.onPackageUninstalled(packageName, appId, it) }
+        val packageState = packageManagerInternal.packageStates[packageName]
+        if (packageState == null) {
+            service.onPackageRemoved(packageName, appId)
+        }
     }
 
     /**
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt
index 3489061..061933a 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt
@@ -22,7 +22,6 @@
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.SystemState
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.data.Permission
 import com.android.server.permission.access.util.attribute
 import com.android.server.permission.access.util.attributeInt
 import com.android.server.permission.access.util.attributeIntHex
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index e081924..b2f52cc 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -20,9 +20,11 @@
 import android.content.pm.PackageManager
 import android.content.pm.PermissionGroupInfo
 import android.content.pm.PermissionInfo
+import android.content.pm.SigningDetails
 import android.os.Build
 import android.os.UserHandle
 import android.util.Log
+import com.android.internal.os.RoSystemProperties
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.AccessState
@@ -33,22 +35,23 @@
 import com.android.server.permission.access.SchemePolicy
 import com.android.server.permission.access.SystemState
 import com.android.server.permission.access.UidUri
-import com.android.server.permission.access.UserState
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.data.Permission
-import com.android.server.permission.access.external.AndroidPackage
-import com.android.server.permission.access.external.CompatibilityPermissionInfo
-import com.android.server.permission.access.external.KnownPackages
-import com.android.server.permission.access.external.PackageInfoUtils
-import com.android.server.permission.access.external.PackageState
-import com.android.server.permission.access.external.RoSystemProperties
-import com.android.server.permission.access.external.SigningDetails
-import com.android.server.permission.access.util.hasAnyBit
+import com.android.server.permission.access.util.andInv
 import com.android.server.permission.access.util.hasBits
+import com.android.server.pm.KnownPackages
+import com.android.server.pm.parsing.PackageInfoUtils
+import com.android.server.pm.permission.CompatibilityPermissionInfo
+import com.android.server.pm.pkg.AndroidPackage
+import com.android.server.pm.pkg.PackageState
 
 class UidPermissionPolicy : SchemePolicy() {
     private val persistence = UidPermissionPersistence()
 
+    @Volatile
+    private var onPermissionFlagsChangedListeners =
+        IndexedListSet<OnPermissionFlagsChangedListener>()
+    private val onPermissionFlagsChangedListenersLock = Any()
+
     override val subjectScheme: String
         get() = UidUri.SCHEME
 
@@ -58,8 +61,7 @@
     override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
         subject as UidUri
         `object` as PermissionUri
-        return state.userStates[subject.userId]?.permissionFlags?.get(subject.appId)
-            ?.get(`object`.permissionName) ?: 0
+        return getPermissionFlags(subject.appId, subject.userId, `object`.permissionName)
     }
 
     override fun MutateStateScope.setDecision(
@@ -69,26 +71,22 @@
     ) {
         subject as UidUri
         `object` as PermissionUri
-        val uidFlags = newState.userStates.getOrPut(subject.userId) { UserState() }
-            .permissionFlags.getOrPut(subject.appId) { IndexedMap() }
-        uidFlags[`object`.permissionName] = decision
+        setPermissionFlags(subject.appId, subject.userId, `object`.permissionName, decision)
     }
 
     override fun MutateStateScope.onUserAdded(userId: Int) {
-        newState.systemState.packageStates.forEachValueIndexed { _, packageState ->
-            evaluateAllPermissionStatesForPackageAndUser(packageState, null, userId)
+        newState.systemState.packageStates.forEach { (_, packageState) ->
+            evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
             grantImplicitPermissions(packageState, userId)
         }
     }
 
-    override fun MutateStateScope.onAppIdAdded(appId: Int) {
-        newState.userStates.forEachIndexed { _, _, userState ->
-            userState.permissionFlags.getOrPut(appId) { IndexedMap() }
-        }
-    }
-
     override fun MutateStateScope.onAppIdRemoved(appId: Int) {
-        newState.userStates.forEachIndexed { _, _, userState -> userState.permissionFlags -= appId }
+        newState.userStates.forEachValueIndexed { _, userState ->
+            userState.uidPermissionFlags -= appId
+            userState.requestWrite()
+            // Skip notifying the change listeners since the app ID no longer exists.
+        }
     }
 
     override fun MutateStateScope.onPackageAdded(packageState: PackageState) {
@@ -97,7 +95,7 @@
         addPermissionGroups(packageState)
         addPermissions(packageState, changedPermissionNames)
         // TODO: revokeStoragePermissionsIfScopeExpandedInternal()
-        trimPermissions(packageState.packageName)
+        trimPermissions(packageState.packageName, changedPermissionNames)
         changedPermissionNames.forEachIndexed { _, permissionName ->
             evaluatePermissionStateForAllPackages(permissionName, packageState)
         }
@@ -121,22 +119,23 @@
             if (!canAdoptPermissions(packageName, originalPackageName)) {
                 return@forEachIndexed
             }
-            newState.systemState.permissions.let { permissions ->
-                permissions.forEachIndexed permissions@ {
-                    permissionIndex, permissionName, oldPermission ->
-                    if (oldPermission.packageName != originalPackageName) {
-                        return@permissions
-                    }
-                    @Suppress("DEPRECATION")
-                    val newPermissionInfo = PermissionInfo().apply {
-                        name = oldPermission.permissionInfo.name
-                        this.packageName = packageName
-                        protectionLevel = oldPermission.permissionInfo.protectionLevel
-                    }
-                    val newPermission = Permission(newPermissionInfo, false, oldPermission.type, 0)
-                    changedPermissionNames += permissionName
-                    permissions.setValueAt(permissionIndex, newPermission)
+            val systemState = newState.systemState
+            val permissions = systemState.permissions
+            permissions.forEachIndexed permissions@ {
+                permissionIndex, permissionName, oldPermission ->
+                if (oldPermission.packageName != originalPackageName) {
+                    return@permissions
                 }
+                @Suppress("DEPRECATION")
+                val newPermissionInfo = PermissionInfo().apply {
+                    name = oldPermission.permissionInfo.name
+                    this.packageName = packageName
+                    protectionLevel = oldPermission.permissionInfo.protectionLevel
+                }
+                val newPermission = Permission(newPermissionInfo, false, oldPermission.type, 0)
+                permissions.setValueAt(permissionIndex, newPermission)
+                systemState.requestWrite()
+                changedPermissionNames += permissionName
             }
         }
     }
@@ -179,7 +178,7 @@
         packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup ->
             val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo(
                 parsedPermissionGroup, PackageManager.GET_META_DATA.toLong()
-            )
+            )!!
             // TODO: Clear permission state on group take-over?
             val permissionGroupName = newPermissionGroup.name
             val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName]
@@ -211,13 +210,14 @@
             // }
             val newPermissionInfo = PackageInfoUtils.generatePermissionInfo(
                 parsedPermission, PackageManager.GET_META_DATA.toLong()
-            )
+            )!!
             // TODO: newPermissionInfo.flags |= PermissionInfo.FLAG_INSTALLED
+            val systemState = newState.systemState
             val permissionName = newPermissionInfo.name
             val oldPermission = if (parsedPermission.isTree) {
-                newState.systemState.permissionTrees[permissionName]
+                systemState.permissionTrees[permissionName]
             } else {
-                newState.systemState.permissions[permissionName]
+                systemState.permissions[permissionName]
             }
             // Different from the old implementation, which may add an (incomplete) signature
             // permission inside another package's permission tree, we now consistently ignore such
@@ -247,19 +247,18 @@
                 if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) {
                     // It's a config permission and has no owner, take ownership now.
                     Permission(newPermissionInfo, true, Permission.TYPE_CONFIG, packageState.appId)
-                } else if (newState.systemState.packageStates[oldPackageName]?.isSystem != true) {
+                } else if (systemState.packageStates[oldPackageName]?.isSystem != true) {
                     Log.w(
                         LOG_TAG, "Overriding permission $permissionName with new declaration in" +
                             " system package $newPackageName: originally declared in another" +
                             " package $oldPackageName"
                     )
                     // Remove permission state on owner change.
-                    newState.userStates.forEachValueIndexed { _, userState ->
-                        userState.permissionFlags.forEachValueIndexed { _, permissionFlags ->
-                            permissionFlags -= newPermissionInfo.name
+                    systemState.userIds.forEachIndexed { _, userId ->
+                        systemState.appIds.forEachKeyIndexed { _, appId ->
+                            setPermissionFlags(appId, userId, permissionName, 0)
                         }
                     }
-                    // TODO: Notify re-evaluation of this permission.
                     Permission(
                         newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId
                     )
@@ -278,23 +277,28 @@
                 Permission(newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId)
             }
 
-            changedPermissionNames += permissionName
             if (parsedPermission.isTree) {
-                newState.systemState.permissionTrees[permissionName] = newPermission
+                systemState.permissionTrees[permissionName] = newPermission
             } else {
-                newState.systemState.permissions[permissionName] = newPermission
+                systemState.permissions[permissionName] = newPermission
             }
+            systemState.requestWrite()
+            changedPermissionNames += permissionName
         }
     }
 
-    private fun MutateStateScope.trimPermissions(packageName: String) {
-        val packageState = newState.systemState.packageStates[packageName]
+    private fun MutateStateScope.trimPermissions(
+        packageName: String,
+        changedPermissionNames: IndexedSet<String>
+    ) {
+        val systemState = newState.systemState
+        val packageState = systemState.packageStates[packageName]
         val androidPackage = packageState?.androidPackage
         if (packageState != null && androidPackage == null) {
             return
         }
 
-        newState.systemState.permissionTrees.removeAllIndexed {
+        val isPermissionTreeRemoved = systemState.permissionTrees.removeAllIndexed {
             _, permissionTreeName, permissionTree ->
             permissionTree.packageName == packageName && (
                 packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
@@ -302,26 +306,30 @@
                 }
             )
         }
+        if (isPermissionTreeRemoved) {
+            systemState.requestWrite()
+        }
 
-        newState.systemState.permissions.removeAllIndexed { i, permissionName, permission ->
+        systemState.permissions.removeAllIndexed { permissionIndex, permissionName, permission ->
             val updatedPermission = updatePermissionIfDynamic(permission)
-            newState.systemState.permissions.setValueAt(i, updatedPermission)
+            newState.systemState.permissions.setValueAt(permissionIndex, updatedPermission)
             if (updatedPermission.packageName == packageName && (
                 packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
                     !it.isTree && it.name == permissionName
                 }
             )) {
-                if (!isPermissionDeclaredByDisabledSystemPackage(permission)) {
-                    newState.userStates.forEachIndexed { _, userId, userState ->
-                        userState.permissionFlags.forEachKeyIndexed { _, appId ->
-                            setPermissionFlags(
-                                appId, permissionName, getPermissionFlags(
-                                    appId, permissionName, userId
-                                ) and PermissionFlags.INSTALL_REVOKED, userId
-                            )
-                        }
+                // Different from the old implementation where we keep the permission state if the
+                // permission is declared by a disabled system package (ag/15189282), we now
+                // shouldn't be notified when the updated system package is removed but the disabled
+                // system package isn't re-enabled yet, so we don't need to maintain that brittle
+                // special case either.
+                systemState.userIds.forEachIndexed { _, userId ->
+                    systemState.appIds.forEachKeyIndexed { _, appId ->
+                        setPermissionFlags(appId, userId, permissionName, 0)
                     }
                 }
+                changedPermissionNames += permissionName
+                systemState.requestWrite()
                 true
             } else {
                 false
@@ -329,16 +337,6 @@
         }
     }
 
-    private fun MutateStateScope.isPermissionDeclaredByDisabledSystemPackage(
-        permission: Permission
-    ): Boolean {
-        val disabledSystemPackage = newState.systemState
-            .disabledSystemPackageStates[permission.packageName]?.androidPackage ?: return false
-        return disabledSystemPackage.permissions.anyIndexed { _, it ->
-            it.name == permission.name && it.protectionLevel == permission.protectionLevel
-        }
-    }
-
     private fun MutateStateScope.updatePermissionIfDynamic(permission: Permission): Permission {
         if (!permission.isDynamic) {
             return permission
@@ -368,11 +366,14 @@
         permissionName: String,
         installedPackageState: PackageState?
     ) {
-        newState.systemState.userIds.forEachIndexed { _, userId ->
-            oldState.userStates[userId]?.permissionFlags?.forEachIndexed {
-                _, appId, permissionFlags ->
-                if (permissionName in permissionFlags) {
-                    evaluatePermissionState(appId, permissionName, installedPackageState, userId)
+        val systemState = newState.systemState
+        systemState.userIds.forEachIndexed { _, userId ->
+            systemState.appIds.forEachKeyIndexed { _, appId ->
+                val isPermissionRequested = anyPackageInAppId(appId) { packageState ->
+                    permissionName in packageState.androidPackage!!.requestedPermissions
+                }
+                if (isPermissionRequested) {
+                    evaluatePermissionState(appId, userId, permissionName, installedPackageState)
                 }
             }
         }
@@ -384,28 +385,28 @@
     ) {
         newState.systemState.userIds.forEachIndexed { _, userId ->
             evaluateAllPermissionStatesForPackageAndUser(
-                packageState, installedPackageState, userId
+                packageState, userId, installedPackageState
             )
         }
     }
 
     private fun MutateStateScope.evaluateAllPermissionStatesForPackageAndUser(
         packageState: PackageState,
-        installedPackageState: PackageState?,
-        userId: Int
+        userId: Int,
+        installedPackageState: PackageState?
     ) {
         packageState.androidPackage?.requestedPermissions?.forEachIndexed { _, permissionName ->
             evaluatePermissionState(
-                packageState.appId, permissionName, installedPackageState, userId
+                packageState.appId, userId, permissionName, installedPackageState
             )
         }
     }
 
     private fun MutateStateScope.evaluatePermissionState(
         appId: Int,
+        userId: Int,
         permissionName: String,
-        installedPackageState: PackageState?,
-        userId: Int
+        installedPackageState: PackageState?
     ) {
         val packageNames = newState.systemState.appIds[appId]
         val hasMissingPackage = packageNames.anyIndexed { _, packageName ->
@@ -416,7 +417,7 @@
             return
         }
         val permission = newState.systemState.permissions[permissionName] ?: return
-        val oldFlags = getPermissionFlags(appId, permissionName, userId)
+        val oldFlags = getPermissionFlags(appId, userId, permissionName)
         if (permission.isNormal) {
             val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED)
             if (!wasGranted) {
@@ -438,7 +439,7 @@
                 } else {
                     PermissionFlags.INSTALL_REVOKED
                 }
-                setPermissionFlags(appId, permissionName, newFlags, userId)
+                setPermissionFlags(appId, userId, permissionName, newFlags)
             }
         } else if (permission.isSignature || permission.isInternal) {
             val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED)
@@ -472,12 +473,12 @@
             // should only affect the other static flags, but not dynamic flags like development or
             // role. This may be useful in the case of an updated system app.
             if (permission.isDevelopment) {
-                newFlags = newFlags or (oldFlags and PermissionFlags.OTHER_GRANTED)
+                newFlags = newFlags or (oldFlags and PermissionFlags.RUNTIME_GRANTED)
             }
             if (permission.isRole) {
-                newFlags = newFlags or (oldFlags and PermissionFlags.ROLE_GRANTED)
+                newFlags = newFlags or (oldFlags and PermissionFlags.ROLE)
             }
-            setPermissionFlags(appId, permissionName, newFlags, userId)
+            setPermissionFlags(appId, userId, permissionName, newFlags)
         } else if (permission.isRuntime) {
             // TODO: add runtime permissions
         } else {
@@ -503,7 +504,7 @@
             }
             // Explicitly check against the old state to determine if this permission is new.
             val isNewPermission = getPermissionFlags(
-                appId, implicitPermissionName, userId, oldState
+                appId, userId, implicitPermissionName, oldState
             ) == 0
             if (!isNewPermission) {
                 return@implicitPermissions
@@ -516,9 +517,9 @@
                 checkNotNull(sourcePermission) {
                     "Unknown source permission $sourcePermissionName in split permissions"
                 }
-                val sourceFlags = getPermissionFlags(appId, sourcePermissionName, userId)
-                val isSourceGranted = sourceFlags.hasAnyBit(PermissionFlags.MASK_GRANTED)
-                val isNewGranted = newFlags.hasAnyBit(PermissionFlags.MASK_GRANTED)
+                val sourceFlags = getPermissionFlags(appId, userId, sourcePermissionName)
+                val isSourceGranted = PermissionFlags.isPermissionGranted(sourceFlags)
+                val isNewGranted = PermissionFlags.isPermissionGranted(newFlags)
                 val isGrantingNewFromRevoke = isSourceGranted && !isNewGranted
                 if (isSourceGranted == isNewGranted || isGrantingNewFromRevoke) {
                     if (isGrantingNewFromRevoke) {
@@ -526,32 +527,15 @@
                     }
                     newFlags = newFlags or (sourceFlags and PermissionFlags.MASK_RUNTIME)
                     if (!sourcePermission.isRuntime && isSourceGranted) {
-                        newFlags = newFlags or PermissionFlags.OTHER_GRANTED
+                        newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED
                     }
                 }
             }
             newFlags = newFlags or PermissionFlags.IMPLICIT
-            setPermissionFlags(appId, implicitPermissionName, newFlags, userId)
+            setPermissionFlags(appId, userId, implicitPermissionName, newFlags)
         }
     }
 
-    private fun MutateStateScope.getPermissionFlags(
-        appId: Int,
-        permissionName: String,
-        userId: Int,
-        state: AccessState = newState
-    ): Int = state.userStates[userId].permissionFlags[appId].getWithDefault(permissionName, 0)
-
-    private fun MutateStateScope.setPermissionFlags(
-        appId: Int,
-        permissionName: String,
-        flags: Int,
-        userId: Int
-    ) {
-        newState.userStates[userId].permissionFlags[appId]!!
-            .putWithDefault(permissionName, flags, 0)
-    }
-
     private fun isCompatibilityPermissionForPackage(
         androidPackage: AndroidPackage,
         permissionName: String
@@ -573,23 +557,24 @@
         packageState: PackageState,
         permission: Permission
     ): Boolean {
-        // check if the package is allow to use this signature permission.  A package is allowed to
-        // use a signature permission if:
-        //     - it has the same set of signing certificates as the source package
-        //     - or its signing certificate was rotated from the source package's certificate
-        //     - or its signing certificate is a previous signing certificate of the defining
-        //       package, and the defining package still trusts the old certificate for permissions
-        //     - or it shares a common signing certificate in its lineage with the defining package,
-        //       and the defining package still trusts the old certificate for permissions
-        //     - or it shares the above relationships with the system package
+        // Check if the package is allowed to use this signature permission.  A package is allowed
+        // to use a signature permission if:
+        // - it has the same set of signing certificates as the source package
+        // - or its signing certificate was rotated from the source package's certificate
+        // - or its signing certificate is a previous signing certificate of the defining
+        //     package, and the defining package still trusts the old certificate for permissions
+        // - or it shares a common signing certificate in its lineage with the defining package,
+        //     and the defining package still trusts the old certificate for permissions
+        // - or it shares the above relationships with the system package
+        val packageSigningDetails = packageState.androidPackage!!.signingDetails
         val sourceSigningDetails = newState.systemState
-            .packageStates[permission.packageName]?.signingDetails
+            .packageStates[permission.packageName]?.androidPackage?.signingDetails
         val platformSigningDetails = newState.systemState
-            .packageStates[PLATFORM_PACKAGE_NAME]!!.signingDetails
-        return sourceSigningDetails?.hasCommonSignerWithCapability(packageState.signingDetails,
+            .packageStates[PLATFORM_PACKAGE_NAME]!!.androidPackage!!.signingDetails
+        return sourceSigningDetails?.hasCommonSignerWithCapability(packageSigningDetails,
             SigningDetails.CertCapabilities.PERMISSION) == true ||
-            packageState.signingDetails.hasAncestorOrSelf(platformSigningDetails) ||
-            platformSigningDetails.checkCapability(packageState.signingDetails,
+            packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
+            platformSigningDetails.checkCapability(packageSigningDetails,
                     SigningDetails.CertCapabilities.PERMISSION)
     }
 
@@ -611,11 +596,9 @@
             newState.systemState.privilegedPermissionAllowlistSourcePackageNames) {
             return true
         }
-        if (isInSystemConfigPrivAppPermissions(androidPackage, permission.name)) {
-            return true
-        }
-        if (isInSystemConfigPrivAppDenyPermissions(androidPackage, permission.name)) {
-            return false
+        val allowlistState = getPrivilegedPermissionAllowlistState(androidPackage, permission.name)
+        if (allowlistState != null) {
+            return allowlistState
         }
         // Updated system apps do not need to be allowlisted
         if (packageState.isUpdatedSystemApp) {
@@ -625,60 +608,51 @@
         return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE
     }
 
-    private fun MutateStateScope.isInSystemConfigPrivAppPermissions(
+    /**
+     * Get the whether a privileged permission is explicitly allowed or denied for a package in the
+     * allowlist, or `null` if it's not in the allowlist.
+     */
+    private fun MutateStateScope.getPrivilegedPermissionAllowlistState(
         androidPackage: AndroidPackage,
         permissionName: String
-    ): Boolean {
-        val apexModuleName = androidPackage.apexModuleName
-        val systemState = newState.systemState
+    ): Boolean? {
+        val permissionAllowlist = newState.systemState.permissionAllowlist
+        // TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName. The below is only for
+        //  passing compilation but won't actually work.
+        //val apexModuleName = androidPackage.apexModuleName
+        val apexModuleName = androidPackage.packageName
         val packageName = androidPackage.packageName
-        val permissionNames = when {
-            androidPackage.isVendor -> systemState.vendorPrivAppPermissions[packageName]
-            androidPackage.isProduct -> systemState.productPrivAppPermissions[packageName]
-            androidPackage.isSystemExt -> systemState.systemExtPrivAppPermissions[packageName]
+        return when {
+            androidPackage.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState(
+                packageName, permissionName
+            )
+            androidPackage.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState(
+                packageName, permissionName
+            )
+            androidPackage.isSystemExt ->
+                permissionAllowlist.getSystemExtPrivilegedAppAllowlistState(
+                    packageName, permissionName
+                )
             apexModuleName != null -> {
-                val apexPrivAppPermissions = systemState.apexPrivAppPermissions[apexModuleName]
-                    ?.get(packageName)
-                val privAppPermissions = systemState.privAppPermissions[packageName]
-                when {
-                    apexPrivAppPermissions == null -> privAppPermissions
-                    privAppPermissions == null -> apexPrivAppPermissions
-                    else -> apexPrivAppPermissions + privAppPermissions
+                val nonApexAllowlistState = permissionAllowlist.getPrivilegedAppAllowlistState(
+                    packageName, permissionName
+                )
+                if (nonApexAllowlistState != null) {
+                    // TODO(andreionea): Remove check as soon as all apk-in-apex
+                    // permission allowlists are migrated.
+                    Log.w(
+                        LOG_TAG, "Package $packageName is an APK in APEX but has permission" +
+                            " allowlist on the system image, please bundle the allowlist in the" +
+                            " $apexModuleName APEX instead"
+                    )
                 }
+                val apexAllowlistState = permissionAllowlist.getApexPrivilegedAppAllowlistState(
+                    apexModuleName, packageName, permissionName
+                )
+                apexAllowlistState ?: nonApexAllowlistState
             }
-            else -> systemState.privAppPermissions[packageName]
+            else -> permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName)
         }
-        return permissionNames?.contains(permissionName) == true
-    }
-
-    private fun MutateStateScope.isInSystemConfigPrivAppDenyPermissions(
-        androidPackage: AndroidPackage,
-        permissionName: String
-    ): Boolean {
-        // Different from the previous implementation, which may incorrectly use the APEX package
-        // name, we now use the APEX module name to be consistent with the allowlist.
-        val apexModuleName = androidPackage.apexModuleName
-        val systemState = newState.systemState
-        val packageName = androidPackage.packageName
-        val permissionNames = when {
-            androidPackage.isVendor -> systemState.vendorPrivAppDenyPermissions[packageName]
-            androidPackage.isProduct -> systemState.productPrivAppDenyPermissions[packageName]
-            androidPackage.isSystemExt -> systemState.systemExtPrivAppDenyPermissions[packageName]
-            // Different from the previous implementation, which ignores the regular priv app
-            // denylist in this case, we now respect it as well to be consistent with the allowlist.
-            apexModuleName != null -> {
-                val apexPrivAppDenyPermissions = systemState
-                    .apexPrivAppDenyPermissions[apexModuleName]?.get(packageName)
-                val privAppDenyPermissions = systemState.privAppDenyPermissions[packageName]
-                when {
-                    apexPrivAppDenyPermissions == null -> privAppDenyPermissions
-                    privAppDenyPermissions == null -> apexPrivAppDenyPermissions
-                    else -> apexPrivAppDenyPermissions + privAppDenyPermissions
-                }
-            }
-            else -> systemState.privAppDenyPermissions[packageName]
-        }
-        return permissionNames?.contains(permissionName) == true
     }
 
     private fun MutateStateScope.anyPackageInAppId(
@@ -741,7 +715,7 @@
             return true
         }
         if (permission.isKnownSigner &&
-            packageState.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) {
+            androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) {
             // If the permission is to be granted to a known signer then check if any of this
             // app's signing certificates are in the trusted certificate digest Set.
             return true
@@ -819,13 +793,13 @@
             }
             permission.isOem -> {
                 if (androidPackage.isOem) {
-                    val isOemAllowlisted = newState.systemState
-                        .oemPermissions[packageName]?.get(permissionName)
-                    checkNotNull(isOemAllowlisted) {
+                    val allowlistState = newState.systemState.permissionAllowlist
+                        .getOemAppAllowlistState(packageName, permissionName)
+                    checkNotNull(allowlistState) {
                         "OEM permission $permissionName requested by package" +
                             " $packageName must be explicitly declared granted or not"
                     }
-                    return isOemAllowlisted
+                    return allowlistState
                 }
             }
         }
@@ -840,8 +814,11 @@
         return uid == ownerUid
     }
 
-    override fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
-        // TODO
+    override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
+        // TODO: STOPSHIP: Remove this check or at least turn into logging.
+        check(packageName !in newState.systemState.disabledSystemPackageStates) {
+            "Package $packageName reported as removed before disabled system package is enabled"
+        }
     }
 
     override fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {
@@ -858,6 +835,80 @@
     fun GetStateScope.getPermission(permissionName: String): Permission? =
         state.systemState.permissions[permissionName]
 
+    fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? =
+        state.userStates[userId]?.uidPermissionFlags?.get(appId)
+
+    fun GetStateScope.getPermissionFlags(
+        appId: Int,
+        userId: Int,
+        permissionName: String
+    ): Int = getPermissionFlags(state, appId, userId, permissionName)
+
+    private fun MutateStateScope.getPermissionFlags(
+        appId: Int,
+        userId: Int,
+        permissionName: String,
+        state: AccessState = newState
+    ): Int = getPermissionFlags(state, appId, userId, permissionName)
+
+    private fun getPermissionFlags(
+        state: AccessState,
+        appId: Int,
+        userId: Int,
+        permissionName: String
+    ): Int =
+        state.userStates[userId]?.uidPermissionFlags?.get(appId).getWithDefault(permissionName, 0)
+
+    fun MutateStateScope.setPermissionFlags(
+        appId: Int,
+        userId: Int,
+        permissionName: String,
+        flags: Int
+    ): Boolean =
+        updatePermissionFlags(appId, userId, permissionName, PermissionFlags.MASK_ALL, flags)
+
+    fun MutateStateScope.updatePermissionFlags(
+        appId: Int,
+        userId: Int,
+        permissionName: String,
+        flagMask: Int,
+        flagValues: Int
+    ): Boolean {
+        val userState = newState.userStates[userId]
+        val uidPermissionFlags = userState.uidPermissionFlags
+        var permissionFlags = uidPermissionFlags[appId]
+        val oldFlags = permissionFlags.getWithDefault(permissionName, 0)
+        val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
+        if (oldFlags == newFlags) {
+            return false
+        }
+        if (permissionFlags == null) {
+            permissionFlags = IndexedMap()
+            uidPermissionFlags[appId] = permissionFlags
+        }
+        permissionFlags.putWithDefault(permissionName, newFlags, 0)
+        if (permissionFlags.isEmpty()) {
+            uidPermissionFlags -= appId
+        }
+        userState.requestWrite()
+        onPermissionFlagsChangedListeners.forEachIndexed { _, it ->
+            it.onPermissionFlagsChanged(appId, userId, permissionName, oldFlags, newFlags)
+        }
+        return true
+    }
+
+    fun addOnPermissionFlagsChangedListener(listener: OnPermissionFlagsChangedListener) {
+        synchronized(onPermissionFlagsChangedListenersLock) {
+            onPermissionFlagsChangedListeners = onPermissionFlagsChangedListeners + listener
+        }
+    }
+
+    fun removeOnPermissionFlagsChangedListener(listener: OnPermissionFlagsChangedListener) {
+        synchronized(onPermissionFlagsChangedListenersLock) {
+            onPermissionFlagsChangedListeners = onPermissionFlagsChangedListeners - listener
+        }
+    }
+
     companion object {
         private val LOG_TAG = UidPermissionPolicy::class.java.simpleName
 
@@ -872,4 +923,14 @@
             Manifest.permission.READ_MEDIA_VIDEO,
         )
     }
+
+    fun interface OnPermissionFlagsChangedListener {
+        fun onPermissionFlagsChanged(
+            appId: Int,
+            userId: Int,
+            permissionName: String,
+            oldFlags: Int,
+            newFlags: Int
+        )
+    }
 }
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index cc26593..ebd6b64 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -94,7 +94,6 @@
         "libunwindstack",
         "libutils",
         "netd_aidl_interface-V5-cpp",
-        "libservices.core.settings.testonly",
     ],
 
     dxflags: ["--multi-dex"],
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/AppsFilterImplTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/BundleUtilsTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BundleUtilsTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/BundleUtilsTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BundleUtilsTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/CompatibilityModeTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/CompatibilityModeTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/InstallerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/InstallerTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/InstallerTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/InstallerTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetManagerServiceTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetManagerServiceTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetManagerServiceTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetStrings.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetStrings.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetStrings.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetStrings.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetUtils.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetUtils.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetUtils.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetUtils.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ModuleInfoProviderTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ModuleInfoProviderTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/ModuleInfoProviderTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ModuleInfoProviderTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageInstallerSessionTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageInstallerSessionTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerServiceTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
similarity index 99%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index b3f64b6..3727d66 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -111,10 +111,6 @@
     private static final String PACKAGE_NAME_3 = "com.android.app3";
     private static final int TEST_RESOURCE_ID = 2131231283;
 
-    static {
-        System.loadLibrary("services.core.settings.testonly");
-    }
-
     @Mock
     RuntimePermissionsPersistence mRuntimePermissionsPersistence;
     @Mock
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerTests.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageSignaturesTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageSignaturesTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageSignaturesTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageSignaturesTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageUserStateTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageVerificationStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageVerificationStateTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ParallelPackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/ParallelPackageParserTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PreferredComponentTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PreferredComponentTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PreferredComponentTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PreferredComponentTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/RestrictionsSetTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/RestrictionsSetTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/RestrictionsSetTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/RestrictionsSetTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanRequestBuilder.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanRequestBuilder.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanTests.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/UserDataPreparerTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/WatchedIntentHandlingTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/WatchedIntentHandlingTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/WatchedIntentHandlingTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/WatchedIntentHandlingTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParserLegacyCoreTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParsingDeferErrorTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParsingDeferErrorTest.kt
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/SystemPartitionParseTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/SystemPartitionParseTest.kt
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidHidlUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidHidlUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestBaseUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestBaseUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestRunnerSplitUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ApexSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ApexSharedLibraryUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ComGoogleAndroidMapsUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OptionalClassRunner.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OptionalClassRunner.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OptionalClassRunner.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OptionalClassRunner.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OrgApacheHttpLegacyUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageBackwardCompatibilityTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageSharedLibraryUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/OWNERS b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/OWNERS
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/OWNERS
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/OWNERS
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatchableTester.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/WatchableTester.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatchableTester.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/WatchableTester.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatcherTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/WatcherTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatcherTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/WatcherTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index b9615f6..d79c4d8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -50,6 +50,7 @@
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
+import android.app.ReceiverInfo;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
@@ -62,6 +63,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
+import android.content.res.CompatibilityInfo;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.DeadObjectException;
@@ -262,6 +264,7 @@
             return res;
         }).when(mAms).startProcessLocked(any(), any(), anyBoolean(), anyInt(),
                 any(), anyInt(), anyBoolean(), anyBoolean());
+
         doAnswer((invocation) -> {
             final String processName = invocation.getArgument(0);
             final int uid = invocation.getArgument(1);
@@ -295,12 +298,16 @@
         };
 
         if (mImpl == Impl.DEFAULT) {
-            mQueue = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG,
+            var q = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG,
                     constants, emptySkipPolicy, emptyHistory, false,
                     ProcessList.SCHED_GROUP_DEFAULT);
+            q.mReceiverBatch.mDeepReceiverCopy = true;
+            mQueue = q;
         } else if (mImpl == Impl.MODERN) {
-            mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
+            var q = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
                     constants, constants, emptySkipPolicy, emptyHistory);
+            q.mReceiverBatch.mDeepReceiverCopy = true;
+            mQueue = q;
         } else {
             throw new UnsupportedOperationException();
         }
@@ -398,6 +405,43 @@
                 UnaryOperator.identity());
     }
 
+    private void doRegisteredReceiver(ProcessRecord r, boolean wedge, boolean abort,
+            UnaryOperator<Bundle> extrasOperator, ReceiverInfo info) {
+        final Intent intent = info.intent;
+        final Bundle extras = info.extras;
+        final boolean ordered = info.ordered;
+        mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
+        if (!wedge && ordered) {
+            assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
+            assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
+                    mQueue.getPreferredSchedulingGroupLocked(r));
+            mHandlerThread.getThreadHandler().post(() -> {
+                synchronized (mAms) {
+                    mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
+                            null, extrasOperator.apply(extras), abort, false);
+                }
+            });
+        }
+    }
+
+    private void doManifestReceiver(ProcessRecord r, boolean wedge, boolean abort,
+            UnaryOperator<Bundle> extrasOperator, ReceiverInfo info) {
+        final Intent intent = info.intent;
+        final Bundle extras = info.extras;
+        mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
+        if (!wedge) {
+            assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
+            assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
+                    mQueue.getPreferredSchedulingGroupLocked(r));
+            mHandlerThread.getThreadHandler().post(() -> {
+                synchronized (mAms) {
+                    mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null,
+                            extrasOperator.apply(extras), abort, false);
+                }
+            });
+        }
+    }
+
     private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, String processName,
             ProcessBehavior behavior, UnaryOperator<Bundle> extrasOperator) throws Exception {
         final boolean wedge = (behavior == ProcessBehavior.WEDGE);
@@ -441,47 +485,22 @@
         if (dead) return r;
 
         doAnswer((invocation) -> {
-            Log.v(TAG, "Intercepting scheduleReceiver() for "
+            Log.v(TAG, "Intercepting scheduleReceiverList() for "
                     + Arrays.toString(invocation.getArguments()));
-            final Intent intent = invocation.getArgument(0);
-            final Bundle extras = invocation.getArgument(5);
-            mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
-            if (!wedge) {
-                assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
-                assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
-                        mQueue.getPreferredSchedulingGroupLocked(r));
-                mHandlerThread.getThreadHandler().post(() -> {
-                    synchronized (mAms) {
-                        mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null,
-                                extrasOperator.apply(extras), abort, false);
-                    }
-                });
+            final List<ReceiverInfo> data = invocation.getArgument(0);
+            for (int i = 0; i < data.size(); i++) {
+                ReceiverInfo info = data.get(i);
+                // The logic here mimics the logic in ActivityThread: elements of the list are
+                // forwarded to a handler for manifest receivers or to a handler for registered
+                // receivers.
+                if (info.registered) {
+                    doRegisteredReceiver(r, wedge, abort, extrasOperator, info);
+                } else {
+                    doManifestReceiver(r, wedge, abort, extrasOperator, info);
+                }
             }
             return null;
-        }).when(thread).scheduleReceiver(any(), any(), any(), anyInt(), any(), any(), anyBoolean(),
-                anyInt(), anyInt());
-
-        doAnswer((invocation) -> {
-            Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
-                    + Arrays.toString(invocation.getArguments()));
-            final Intent intent = invocation.getArgument(1);
-            final Bundle extras = invocation.getArgument(4);
-            final boolean ordered = invocation.getArgument(5);
-            mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
-            if (!wedge && ordered) {
-                assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
-                assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
-                        mQueue.getPreferredSchedulingGroupLocked(r));
-                mHandlerThread.getThreadHandler().post(() -> {
-                    synchronized (mAms) {
-                        mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
-                                null, extrasOperator.apply(extras), abort, false);
-                    }
-                });
-            }
-            return null;
-        }).when(thread).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(),
-                anyBoolean(), anyBoolean(), anyInt(), anyInt());
+        }).when(thread).scheduleReceiverList(any());
 
         return r;
     }
@@ -623,6 +642,123 @@
         };
     }
 
+    private static <T> boolean matchElement(T a, T b) {
+        return a == null || a.equals(b);
+    }
+    private static <T> boolean matchObject(ArgumentMatcher<T> m, T b) {
+        return m == null || m.matches(b);
+    }
+
+    /**
+     * Create an ArgumentMatcher for a manifest receiver.  The parameters are in the order of
+     * {@link IApplicationThread#scheduleReceiver} but the names correspond to the field names in
+     * {@link ReceiverInfo}.  For every parameter, a null means "don't care".
+     */
+    private ArgumentMatcher<ReceiverInfo> manifestReceiverMatcher(
+            ArgumentMatcher<Intent> intent,
+            ArgumentMatcher<ActivityInfo> activityInfo,
+            ArgumentMatcher<CompatibilityInfo> compatInfo,
+            Integer resultCode,
+            ArgumentMatcher<String> data,
+            ArgumentMatcher<Bundle> extras,
+            Boolean sync,
+            Integer sendingUser,
+            Integer processState) {
+        return (test) -> {
+            return test.registered == false
+                    && matchObject(intent, test.intent)
+                    && matchObject(activityInfo, test.activityInfo)
+                    && matchObject(compatInfo, test.compatInfo)
+                    && matchElement(resultCode, test.resultCode)
+                    && matchObject(data, test.data)
+                    && matchObject(extras, test.extras)
+                    && matchElement(sync, test.sync)
+                    && matchElement(sendingUser, test.sendingUser)
+                    && matchElement(processState, test.processState);
+        };
+    }
+
+
+    /**
+     * Create an argument suitable for the verify() mock methods, when the goal is to find a call
+     * containing a manifest receiver.
+     */
+    private List<ReceiverInfo> manifestReceiver(
+            ArgumentMatcher<Intent> intent,
+            ArgumentMatcher<ActivityInfo> activityInfo,
+            ArgumentMatcher<CompatibilityInfo> compatInfo,
+            Integer resultCode,
+            ArgumentMatcher<String> data,
+            ArgumentMatcher<Bundle> extras,
+            Boolean sync,
+            Integer sendingUser,
+            Integer processState) {
+        return argThat(receiverList(manifestReceiverMatcher(intent, activityInfo, compatInfo,
+                                resultCode, data, extras, sync, sendingUser, processState)));
+    }
+
+    /**
+     * Create an ArgumentMatcher for a registered receiver.  The parameters are in the order of
+     * {@link IApplicationThread#scheduleRegisteredReceiver} but the names correspond to the field
+     * names in {@link ReceiverInfo}.  For every parameter, a null means "don't care".
+     */
+    private ArgumentMatcher<ReceiverInfo> registeredReceiverMatcher(
+            ArgumentMatcher<IIntentReceiver> receiver,
+            ArgumentMatcher<Intent> intent,
+            Integer resultCode,
+            ArgumentMatcher<String> data,
+            ArgumentMatcher<Bundle> extras,
+            Boolean ordered,
+            Boolean sticky,
+            Integer sendingUser,
+            Integer processState) {
+        return (test) -> {
+            return test.registered == true
+                    && matchObject(receiver, test.receiver)
+                    && matchObject(intent, test.intent)
+                    && matchElement(resultCode, test.resultCode)
+                    && matchObject(data, test.data)
+                    && matchObject(extras, test.extras)
+                    && matchElement(ordered, test.ordered)
+                    && matchElement(sticky, test.sticky)
+                    && matchElement(sendingUser, test.sendingUser)
+                    && matchElement(processState, test.processState);
+        };
+    }
+
+    /**
+     * Create an argument suitable for the verify() mock methods, when the goal is to find a call
+     * containing a registered receiver.
+     */
+    private List<ReceiverInfo> registeredReceiver(
+            ArgumentMatcher<IIntentReceiver> receiver,
+            ArgumentMatcher<Intent> intent,
+            Integer resultCode,
+            ArgumentMatcher<String> data,
+            ArgumentMatcher<Bundle> extras,
+            Boolean ordered,
+            Boolean sticky,
+            Integer sendingUser,
+            Integer processState) {
+        return argThat(receiverList(registeredReceiverMatcher(receiver, intent, resultCode,
+                                data, extras, ordered, sticky, sendingUser, processState)));
+    }
+
+    /**
+     * Apply a matcher to every element in a ReceiverInfo list.
+     */
+    private ArgumentMatcher<List<ReceiverInfo>> receiverList(ArgumentMatcher<ReceiverInfo> a) {
+        return (test) -> {
+            for (int i = 0; i < test.size(); i++) {
+                ReceiverInfo r = test.get(i);
+                if (a.matches(r)) {
+                    return true;
+                }
+            }
+            return false;
+        };
+    }
+
     private ArgumentMatcher<Bundle> bundleEquals(Bundle bundle) {
         return (test) -> {
             // TODO: check values in addition to keys
@@ -654,26 +790,27 @@
     }
 
     private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent,
-            int userId) throws Exception {
-        verify(app.getThread(), mode).scheduleReceiver(
-                argThat(filterEqualsIgnoringComponent(intent)), any(), any(), anyInt(), any(),
-                any(), eq(false), eq(userId), anyInt());
-    }
-
-    private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent,
             ComponentName component) throws Exception {
         final Intent targetedIntent = new Intent(intent);
         targetedIntent.setComponent(component);
-        verify(app.getThread(), mode).scheduleReceiver(
-                argThat(filterEquals(targetedIntent)), any(), any(), anyInt(), any(),
-                any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt());
+        verify(app.getThread(), mode).scheduleReceiverList(
+            manifestReceiver(filterEquals(targetedIntent),
+                    null, null, null, null, null, null, UserHandle.USER_SYSTEM, null));
     }
 
-    private void verifyScheduleRegisteredReceiver(ProcessRecord app, Intent intent)
+    private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app,
+            Intent intent, int userId) throws Exception {
+        verify(app.getThread(), mode).scheduleReceiverList(
+            manifestReceiver(filterEqualsIgnoringComponent(intent),
+                    null, null, null, null, null, null, userId, null));
+    }
+
+    private void verifyScheduleRegisteredReceiver(ProcessRecord app,
+            Intent intent)
             throws Exception {
-        verify(app.getThread()).scheduleRegisteredReceiver(any(),
-                argThat(filterEqualsIgnoringComponent(intent)), anyInt(), any(), any(),
-                anyBoolean(), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+        verify(app.getThread()).scheduleReceiverList(
+            registeredReceiver(null, filterEqualsIgnoringComponent(intent),
+                    null, null, null, null, null, UserHandle.USER_SYSTEM, null));
     }
 
     static final int USER_GUEST = 11;
@@ -1237,23 +1374,24 @@
         final InOrder inOrder = inOrder(greenThread, blueThread, yellowThread, redThread);
         final Bundle expectedExtras = new Bundle();
         expectedExtras.putBoolean(PACKAGE_RED, true);
-        inOrder.verify(greenThread).scheduleReceiver(
-                argThat(filterEqualsIgnoringComponent(airplane)), any(), any(),
-                eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true),
-                eq(UserHandle.USER_SYSTEM), anyInt());
-        inOrder.verify(blueThread).scheduleReceiver(
-                argThat(filterEqualsIgnoringComponent(airplane)), any(), any(),
-                eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true),
-                eq(UserHandle.USER_SYSTEM), anyInt());
+        inOrder.verify(greenThread).scheduleReceiverList(manifestReceiver(
+                filterEqualsIgnoringComponent(airplane), null, null,
+                Activity.RESULT_OK, null, bundleEquals(expectedExtras), true,
+                UserHandle.USER_SYSTEM, null));
+        inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
+                filterEqualsIgnoringComponent(airplane), null, null,
+                Activity.RESULT_OK, null, bundleEquals(expectedExtras), true,
+                UserHandle.USER_SYSTEM, null));
         expectedExtras.putBoolean(PACKAGE_BLUE, true);
-        inOrder.verify(yellowThread).scheduleReceiver(
-                argThat(filterEqualsIgnoringComponent(airplane)), any(), any(),
-                eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true),
-                eq(UserHandle.USER_SYSTEM), anyInt());
+        inOrder.verify(yellowThread).scheduleReceiverList(manifestReceiver(
+                filterEqualsIgnoringComponent(airplane), null, null,
+                Activity.RESULT_OK, null, bundleEquals(expectedExtras), true,
+                UserHandle.USER_SYSTEM, null));
         expectedExtras.putBoolean(PACKAGE_YELLOW, true);
-        inOrder.verify(redThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(airplane)),
-                eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(false),
-                anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+        inOrder.verify(redThread).scheduleReceiverList(registeredReceiver(
+                null, filterEquals(airplane),
+                Activity.RESULT_OK, null, bundleEquals(expectedExtras), false,
+                null, UserHandle.USER_SYSTEM, null));
 
         // Finally, verify that we thawed the final receiver
         verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(callerApp),
@@ -1316,22 +1454,24 @@
         // have invoked or skipped the second receiver depending on the intent
         // flag policy; we always deliver to final receiver regardless of abort
         final InOrder inOrder = inOrder(greenThread, blueThread, redThread);
-        inOrder.verify(greenThread).scheduleReceiver(
-                argThat(filterEqualsIgnoringComponent(intent)), any(), any(),
-                eq(Activity.RESULT_OK), any(), any(), eq(true), eq(UserHandle.USER_SYSTEM),
-                anyInt());
+        inOrder.verify(greenThread).scheduleReceiverList(manifestReceiver(
+                filterEqualsIgnoringComponent(intent), null, null,
+                Activity.RESULT_OK, null, null, true, UserHandle.USER_SYSTEM,
+                null));
         if ((intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0) {
-            inOrder.verify(blueThread).scheduleReceiver(
-                    argThat(filterEqualsIgnoringComponent(intent)), any(), any(),
-                    eq(Activity.RESULT_OK), any(), any(), eq(true), eq(UserHandle.USER_SYSTEM),
-                    anyInt());
+            inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
+                    filterEqualsIgnoringComponent(intent), null, null,
+                    Activity.RESULT_OK, null, null, true, UserHandle.USER_SYSTEM,
+                    null));
         } else {
-            inOrder.verify(blueThread, never()).scheduleReceiver(any(), any(), any(), anyInt(),
-                    any(), any(), anyBoolean(), anyInt(), anyInt());
+            inOrder.verify(blueThread, never()).scheduleReceiverList(manifestReceiver(
+                    null, null, null, null,
+                    null, null, null, null, null));
         }
-        inOrder.verify(redThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(intent)),
-                eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)),
-                eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+        inOrder.verify(redThread).scheduleReceiverList(registeredReceiver(
+                null, filterEquals(intent),
+                Activity.RESULT_OK, null, bundleEquals(expectedExtras),
+                false, null, UserHandle.USER_SYSTEM, null));
     }
 
     /**
@@ -1351,9 +1491,10 @@
                 orderedResultTo, orderedExtras));
 
         waitForIdle();
-        verify(callerThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(airplane)),
-                eq(Activity.RESULT_OK), any(), argThat(bundleEquals(orderedExtras)), eq(false),
-                anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+        verify(callerThread).scheduleReceiverList(registeredReceiver(
+                null, filterEquals(airplane),
+                Activity.RESULT_OK, null, bundleEquals(orderedExtras), false,
+                null, UserHandle.USER_SYSTEM, null));
     }
 
     /**
@@ -1371,9 +1512,10 @@
                         makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)), resultTo));
 
         waitForIdle();
-        verify(callerThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(airplane)),
-                eq(Activity.RESULT_OK), any(), any(), eq(false),
-                anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+        verify(callerThread).scheduleReceiverList(registeredReceiver(
+                null, filterEquals(airplane),
+                Activity.RESULT_OK, null, null, false,
+                null, UserHandle.USER_SYSTEM, null));
     }
 
     /**
@@ -1536,28 +1678,28 @@
         final InOrder inOrder = inOrder(callerThread, blueThread);
 
         // First broadcast is canceled
-        inOrder.verify(callerThread).scheduleRegisteredReceiver(any(),
-                argThat(filterAndExtrasEquals(timezoneFirst)), eq(Activity.RESULT_CANCELED), any(),
-                any(), eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+        inOrder.verify(callerThread).scheduleReceiverList(registeredReceiver(null,
+                filterAndExtrasEquals(timezoneFirst), Activity.RESULT_CANCELED, null,
+                null, false, null, UserHandle.USER_SYSTEM, null));
 
         // We deliver second broadcast to app
         timezoneSecond.setClassName(PACKAGE_BLUE, CLASS_GREEN);
-        inOrder.verify(blueThread).scheduleReceiver(
-                argThat(filterAndExtrasEquals(timezoneSecond)),
-                any(), any(), anyInt(), any(), any(), eq(true), anyInt(), anyInt());
+        inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
+                filterAndExtrasEquals(timezoneSecond),
+                null, null, null, null, null, true, null, null));
 
         // Second broadcast is finished
         timezoneSecond.setComponent(null);
-        inOrder.verify(callerThread).scheduleRegisteredReceiver(any(),
-                argThat(filterAndExtrasEquals(timezoneSecond)), eq(Activity.RESULT_OK), any(),
-                any(), eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+        inOrder.verify(callerThread).scheduleReceiverList(registeredReceiver(null,
+                filterAndExtrasEquals(timezoneSecond), Activity.RESULT_OK, null,
+                null, false, null, UserHandle.USER_SYSTEM, null));
 
         // Since we "replaced" the first broadcast in its original position,
         // only now do we see the airplane broadcast
         airplane.setClassName(PACKAGE_BLUE, CLASS_RED);
-        inOrder.verify(blueThread).scheduleReceiver(
-                argThat(filterEquals(airplane)),
-                any(), any(), anyInt(), any(), any(), eq(false), anyInt(), anyInt());
+        inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
+                filterEquals(airplane),
+                null, null, null, null, null, false, null, null));
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
index be13bad..021d01c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -48,7 +48,7 @@
     StaticMockitoSession mSession;
 
     @Mock
-    AppOpsServiceImpl.Constants mConstants;
+    AppOpsService.Constants mConstants;
 
     @Mock
     Context mContext;
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 7d4bc6f..c0688d1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -22,8 +22,6 @@
 import static android.app.AppOpsManager.OP_READ_SMS;
 import static android.app.AppOpsManager.OP_WIFI_SCAN;
 import static android.app.AppOpsManager.OP_WRITE_SMS;
-import static android.app.AppOpsManager.resolvePackageName;
-import static android.os.Process.INVALID_UID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -41,7 +39,6 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 
-import android.app.AppOpsManager;
 import android.app.AppOpsManager.OpEntry;
 import android.app.AppOpsManager.PackageOps;
 import android.content.ContentResolver;
@@ -89,13 +86,13 @@
 
     private File mAppOpsFile;
     private Handler mHandler;
-    private AppOpsServiceImpl mAppOpsService;
+    private AppOpsService mAppOpsService;
     private int mMyUid;
     private long mTestStartMillis;
     private StaticMockitoSession mMockingSession;
 
     private void setupAppOpsService() {
-        mAppOpsService = new AppOpsServiceImpl(mAppOpsFile, mHandler, spy(sContext));
+        mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext));
         mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver());
 
         // Always approve all permission checks
@@ -164,20 +161,17 @@
 
     @Test
     public void testNoteOperationAndGetOpsForPackage() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
-        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
+        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
 
         // Note an op that's allowed.
-        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
-                resolvePackageName(mMyUid, sMyPackageName), null,
-                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
 
         // Note another op that's not allowed.
-        mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
-                resolvePackageName(mMyUid, sMyPackageName), null,
-                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+        mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
+                false);
         loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
         assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
@@ -191,20 +185,18 @@
     @Test
     public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
         // This op controls WIFI_SCAN
-        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED);
 
-        assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
-                resolvePackageName(mMyUid, sMyPackageName), null,
-                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ALLOWED);
+        assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
+                null, false).getOpMode()).isEqualTo(MODE_ALLOWED);
 
         assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
                 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
 
         // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
-        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED, null);
-        assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
-                resolvePackageName(mMyUid, sMyPackageName), null,
-                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ERRORED);
+        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED);
+        assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
+                null, false).getOpMode()).isEqualTo(MODE_ERRORED);
 
         assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
                 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
@@ -213,14 +205,11 @@
     // Tests the dumping and restoring of the in-memory state to/from XML.
     @Test
     public void testStatePersistence() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
-        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
-        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
-                resolvePackageName(mMyUid, sMyPackageName), null,
-                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
-        mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
-                resolvePackageName(mMyUid, sMyPackageName), null,
-                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
+        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
+                false);
         mAppOpsService.writeState();
 
         // Create a new app ops service which will initialize its state from XML.
@@ -235,10 +224,8 @@
     // Tests that ops are persisted during shutdown.
     @Test
     public void testShutdown() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
-        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
-                resolvePackageName(mMyUid, sMyPackageName), null,
-                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
         mAppOpsService.shutdown();
 
         // Create a new app ops service which will initialize its state from XML.
@@ -251,10 +238,8 @@
 
     @Test
     public void testGetOpsForPackage() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
-        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
-                resolvePackageName(mMyUid, sMyPackageName), null,
-                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
 
         // Query all ops
         List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
@@ -282,10 +267,8 @@
 
     @Test
     public void testPackageRemoved() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
-        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
-                resolvePackageName(mMyUid, sMyPackageName), null,
-                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
 
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
@@ -339,10 +322,8 @@
 
     @Test
     public void testUidRemoved() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
-        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
-                resolvePackageName(mMyUid, sMyPackageName), null,
-                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
 
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index 3efd5e7..98e895a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -76,7 +76,7 @@
     ActivityManagerInternal mAmi;
 
     @Mock
-    AppOpsServiceImpl.Constants mConstants;
+    AppOpsService.Constants mConstants;
 
     AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor();
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index 302fa0f..9eed6ad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -121,13 +121,13 @@
         }
     }
 
-    private void assertSameModes(SparseArray<AppOpsServiceImpl.UidState> uidStates,
+    private void assertSameModes(SparseArray<AppOpsService.UidState> uidStates,
             int op1, int op2) {
         int numberOfNonDefaultOps = 0;
         final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1);
         final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
         for (int i = 0; i < uidStates.size(); i++) {
-            final AppOpsServiceImpl.UidState uidState = uidStates.valueAt(i);
+            final AppOpsService.UidState uidState = uidStates.valueAt(i);
             SparseIntArray opModes = uidState.getNonDefaultUidModes();
             if (opModes != null) {
                 final int uidMode1 = opModes.get(op1, defaultModeOp1);
@@ -141,12 +141,12 @@
                 continue;
             }
             for (int j = 0; j < uidState.pkgOps.size(); j++) {
-                final AppOpsServiceImpl.Ops ops = uidState.pkgOps.valueAt(j);
+                final AppOpsService.Ops ops = uidState.pkgOps.valueAt(j);
                 if (ops == null) {
                     continue;
                 }
-                final AppOpsServiceImpl.Op _op1 = ops.get(op1);
-                final AppOpsServiceImpl.Op _op2 = ops.get(op2);
+                final AppOpsService.Op _op1 = ops.get(op1);
+                final AppOpsService.Op _op2 = ops.get(op2);
                 final int mode1 = (_op1 == null) ? defaultModeOp1 : _op1.getMode();
                 final int mode2 = (_op2 == null) ? defaultModeOp2 : _op2.getMode();
                 assertEquals(mode1, mode2);
@@ -199,7 +199,7 @@
     public void upgradeRunAnyInBackground() {
         extractAppOpsFile(APP_OPS_UNVERSIONED_ASSET_PATH);
 
-        AppOpsServiceImpl testService = new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext);
+        AppOpsService testService = new AppOpsService(sAppOpsFile, mHandler, mTestContext);
 
         testService.upgradeRunAnyInBackgroundLocked();
         assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
@@ -244,7 +244,7 @@
             return UserHandle.getUid(userId, appIds[index]);
         }).when(mPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
 
-        AppOpsServiceImpl testService = new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext);
+        AppOpsService testService = new AppOpsService(sAppOpsFile, mHandler, mTestContext);
 
         testService.upgradeScheduleExactAlarmLocked();
 
@@ -259,7 +259,7 @@
                 } else {
                     expectedMode = previousMode;
                 }
-                final AppOpsServiceImpl.UidState uidState = testService.mUidStates.get(uid);
+                final AppOpsService.UidState uidState = testService.mUidStates.get(uid);
                 assertEquals(expectedMode, uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM));
             }
         }
@@ -268,7 +268,7 @@
         int[] unrelatedUidsInFile = {10225, 10178};
 
         for (int uid : unrelatedUidsInFile) {
-            final AppOpsServiceImpl.UidState uidState = testService.mUidStates.get(uid);
+            final AppOpsService.UidState uidState = testService.mUidStates.get(uid);
             assertEquals(AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM),
                     uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM));
         }
@@ -278,8 +278,8 @@
     public void upgradeFromNoFile() {
         assertFalse(sAppOpsFile.exists());
 
-        AppOpsServiceImpl testService = spy(
-                new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext));
+        AppOpsService testService = spy(
+                new AppOpsService(sAppOpsFile, mHandler, mTestContext));
 
         doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
         doNothing().when(testService).upgradeScheduleExactAlarmLocked();
@@ -296,7 +296,7 @@
 
         AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile);
         assertTrue(parser.parse());
-        assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
+        assertEquals(AppOpsService.CURRENT_VERSION, parser.mVersion);
     }
 
     @Test
@@ -306,8 +306,8 @@
         assertTrue(parser.parse());
         assertEquals(AppOpsDataParser.NO_VERSION, parser.mVersion);
 
-        AppOpsServiceImpl testService = spy(
-                new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext));
+        AppOpsService testService = spy(
+                new AppOpsService(sAppOpsFile, mHandler, mTestContext));
 
         doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
         doNothing().when(testService).upgradeScheduleExactAlarmLocked();
@@ -320,7 +320,7 @@
 
         testService.writeState();
         assertTrue(parser.parse());
-        assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
+        assertEquals(AppOpsService.CURRENT_VERSION, parser.mVersion);
     }
 
     @Test
@@ -330,8 +330,8 @@
         assertTrue(parser.parse());
         assertEquals(1, parser.mVersion);
 
-        AppOpsServiceImpl testService = spy(
-                new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext));
+        AppOpsService testService = spy(
+                new AppOpsService(sAppOpsFile, mHandler, mTestContext));
 
         doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
         doNothing().when(testService).upgradeScheduleExactAlarmLocked();
@@ -344,7 +344,7 @@
 
         testService.writeState();
         assertTrue(parser.parse());
-        assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
+        assertEquals(AppOpsService.CURRENT_VERSION, parser.mVersion);
     }
 
     /**
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 5e5cbdc..4524759 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -169,6 +169,18 @@
                 .thenReturn(mockArray);
         when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray))
                 .thenReturn(mockArray);
+        when(mMockedResources.getIntArray(
+            com.android.internal.R.array.config_brightnessThresholdsOfPeakRefreshRate))
+            .thenReturn(new int[]{});
+        when(mMockedResources.getIntArray(
+            com.android.internal.R.array.config_ambientThresholdsOfPeakRefreshRate))
+            .thenReturn(new int[]{});
+        when(mMockedResources.getIntArray(
+            com.android.internal.R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+            .thenReturn(new int[]{});
+        when(mMockedResources.getIntArray(
+            com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+            .thenReturn(new int[]{});
     }
 
     @After
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index a9dc4af..480a4f3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -217,7 +217,7 @@
         assertEquals(0, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
         assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
-        assertEquals(0, assignmentInfo.numRunningTopEj);
+        assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged);
     }
 
     @Test
@@ -239,7 +239,7 @@
         assertEquals(0, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
         assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
-        assertEquals(0, assignmentInfo.numRunningTopEj);
+        assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged);
     }
 
     @Test
@@ -265,15 +265,14 @@
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
         assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
-        assertEquals(0, assignmentInfo.numRunningTopEj);
+        assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged);
     }
 
     @Test
-    public void testPrepareForAssignmentDetermination_onlyRunningTopEjs() {
+    public void testPrepareForAssignmentDetermination_onlyStartedWithImmediacyPrivilege() {
         for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
             JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
-            job.startedAsExpeditedJob = true;
-            job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+            job.startedWithImmediacyPrivilege = true;
             mJobConcurrencyManager.addRunningJobForTesting(job);
         }
 
@@ -294,7 +293,7 @@
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, stoppable.size());
         assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
-                assignmentInfo.numRunningTopEj);
+                assignmentInfo.numRunningImmediacyPrivileged);
     }
 
     @Test
@@ -500,6 +499,38 @@
     }
 
     @Test
+    public void testHasImmediacyPrivilege() {
+        JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
+        spyOn(job);
+        assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+        doReturn(false).when(job).shouldTreatAsExpeditedJob();
+        doReturn(false).when(job).shouldTreatAsUserInitiated();
+        job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+        assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+        doReturn(true).when(job).shouldTreatAsExpeditedJob();
+        doReturn(false).when(job).shouldTreatAsUserInitiated();
+        job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+        assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+        doReturn(false).when(job).shouldTreatAsExpeditedJob();
+        doReturn(true).when(job).shouldTreatAsUserInitiated();
+        job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+        assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+        doReturn(false).when(job).shouldTreatAsExpeditedJob();
+        doReturn(true).when(job).shouldTreatAsUserInitiated();
+        job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+        assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+        doReturn(true).when(job).shouldTreatAsExpeditedJob();
+        doReturn(false).when(job).shouldTreatAsUserInitiated();
+        job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+        assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+    }
+
+    @Test
     public void testIsPkgConcurrencyLimited_top() {
         final JobStatus topJob = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
         topJob.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 8e48490..0eeebca 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -68,6 +68,8 @@
 import com.android.server.SystemServiceManager;
 import com.android.server.job.controllers.ConnectivityController;
 import com.android.server.job.controllers.JobStatus;
+import com.android.server.job.controllers.QuotaController;
+import com.android.server.job.controllers.TareController;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.usage.AppStandbyInternal;
 
@@ -336,14 +338,29 @@
         when(jobUI.shouldTreatAsUserInitiated()).thenReturn(true);
         when(jobUIDT.shouldTreatAsUserInitiated()).thenReturn(true);
 
-        grantRunLongJobsPermission(true);
+        QuotaController quotaController = mService.getQuotaController();
+        spyOn(quotaController);
+        TareController tareController = mService.getTareController();
+        spyOn(tareController);
+        doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+                .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
+        doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+                .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
 
+        grantRunLongJobsPermission(true);
         assertEquals(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS,
                 mService.getMaxJobExecutionTimeMs(jobDT));
         assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_LIMIT_MS,
                 mService.getMaxJobExecutionTimeMs(jobUI));
         assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
                 mService.getMaxJobExecutionTimeMs(jobUIDT));
+        grantRunLongJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobDT));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUI));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUIDT));
     }
 
     /**
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 17822c6..d72ccf8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -2163,6 +2163,31 @@
     }
 
     @Test
+    public void testIsWithinQuotaLocked_UserInitiated() {
+        // Put app in a state where regular jobs are out of quota.
+        setDischarging();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25), false);
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount),
+                false);
+        JobStatus job = createJobStatus("testIsWithinQuotaLocked_UserInitiated", 1);
+        spyOn(job);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, jobCount);
+            assertFalse(mQuotaController
+                    .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+            doReturn(false).when(job).shouldTreatAsUserInitiated();
+            assertFalse(mQuotaController.isWithinQuotaLocked(job));
+            // User-initiated job should still be allowed.
+            doReturn(true).when(job).shouldTreatAsUserInitiated();
+            assertTrue(mQuotaController.isWithinQuotaLocked(job));
+        }
+    }
+
+    @Test
     public void testIsWithinQuotaLocked_WithQuotaBump_Duration() {
         setDischarging();
         int standbyBucket = WORKING_INDEX;
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
new file mode 100644
index 0000000..4d11296
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2022 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.location;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.location.ILocationListener;
+import android.location.LocationManagerInternal;
+import android.location.LocationRequest;
+import android.location.provider.ProviderRequest;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.TestInjector;
+import com.android.server.location.provider.AbstractLocationProvider;
+import com.android.server.location.provider.LocationProviderManager;
+import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import java.util.Collections;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LocationManagerServiceTest {
+    private static final String PROVIDER_WITH_PERMISSION = "provider_with_permission";
+    private static final String PROVIDER_WITHOUT_PERMISSION = "provider_without_permission";
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final String CALLER_PACKAGE = "caller_package";
+    private static final String MISSING_PERMISSION = "missing_permission";
+
+    private TestInjector mInjector;
+    private LocationManagerService mLocationManagerService;
+
+    @Spy private FakeAbstractLocationProvider mProviderWithPermission;
+    @Spy private FakeAbstractLocationProvider mProviderWithoutPermission;
+    @Mock private ILocationListener mLocationListener;
+    @Mock private IBinder mBinder;
+    @Mock private Context mContext;
+    @Mock private Resources mResources;
+    @Mock private PackageManager mPackageManager;
+    @Mock private AppOpsManager mAppOpsManager;
+    @Mock private PowerManager mPowerManager;
+    @Mock private PowerManager.WakeLock mWakeLock;
+    @Mock private LegacyPermissionManagerInternal mPermissionManagerInternal;
+
+    @Before
+    public void setUp() {
+        initMocks(this);
+
+        doReturn(mContext).when(mContext).createAttributionContext(any());
+        doReturn("android").when(mContext).getPackageName();
+        doReturn(mResources).when(mContext).getResources();
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
+        doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
+        doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
+        String[] packages = {CALLER_PACKAGE};
+        doReturn(InstrumentationRegistry.getInstrumentation().getContext().getContentResolver())
+                .when(mContext)
+                .getContentResolver();
+        doReturn(packages).when(mPackageManager).getPackagesForUid(anyInt());
+        doReturn(mBinder).when(mLocationListener).asBinder();
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext)
+                .checkCallingOrSelfPermission(MISSING_PERMISSION);
+
+        mInjector = new TestInjector(mContext);
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
+
+        LocalServices.addService(LegacyPermissionManagerInternal.class, mPermissionManagerInternal);
+
+        mLocationManagerService = new LocationManagerService(mContext, mInjector);
+
+        LocationProviderManager managerWithPermission =
+                new LocationProviderManager(
+                        mContext, mInjector, PROVIDER_WITH_PERMISSION, /* passiveManager= */ null);
+        mLocationManagerService.addLocationProviderManager(
+                managerWithPermission, mProviderWithPermission);
+        LocationProviderManager managerWithoutPermission =
+                new LocationProviderManager(
+                        mContext,
+                        mInjector,
+                        PROVIDER_WITHOUT_PERMISSION,
+                        /* passiveManager= */ null,
+                        Collections.singletonList(MISSING_PERMISSION));
+        mLocationManagerService.addLocationProviderManager(
+                managerWithoutPermission, mProviderWithoutPermission);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(LegacyPermissionManagerInternal.class);
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testRequestLocationUpdates() {
+        LocationRequest request = new LocationRequest.Builder(0).build();
+        mLocationManagerService.registerLocationListener(
+                PROVIDER_WITH_PERMISSION,
+                request,
+                mLocationListener,
+                CALLER_PACKAGE,
+                /* attributionTag= */ null,
+                "any_listener_id");
+        verify(mProviderWithPermission).onSetRequestPublic(any());
+    }
+
+    @Test
+    public void testRequestLocationUpdates_noPermission() {
+        LocationRequest request = new LocationRequest.Builder(0).build();
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        mLocationManagerService.registerLocationListener(
+                                PROVIDER_WITHOUT_PERMISSION,
+                                request,
+                                mLocationListener,
+                                CALLER_PACKAGE,
+                                /* attributionTag= */ null,
+                                "any_listener_id"));
+    }
+
+    @Test
+    public void testHasProvider() {
+        assertThat(mLocationManagerService.hasProvider(PROVIDER_WITH_PERMISSION)).isTrue();
+    }
+
+    @Test
+    public void testHasProvider_noPermission() {
+        assertThat(mLocationManagerService.hasProvider(PROVIDER_WITHOUT_PERMISSION)).isFalse();
+    }
+
+    @Test
+    public void testGetAllProviders() {
+        assertThat(mLocationManagerService.getAllProviders()).contains(PROVIDER_WITH_PERMISSION);
+        assertThat(mLocationManagerService.getAllProviders())
+                .doesNotContain(PROVIDER_WITHOUT_PERMISSION);
+    }
+
+    abstract static class FakeAbstractLocationProvider extends AbstractLocationProvider {
+        FakeAbstractLocationProvider() {
+            super(
+                    MoreExecutors.directExecutor(),
+                    /* identity= */ null,
+                    /* properties= */ null,
+                    /* extraAttributionTags= */ Collections.emptySet());
+            setAllowed(true);
+        }
+
+        @Override
+        protected void onSetRequest(ProviderRequest request) {
+            // Call a public version of this method so mockito can verify.
+            onSetRequestPublic(request);
+        }
+
+        public abstract void onSetRequestPublic(ProviderRequest request);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index aa28ad4..7dc1935 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -102,6 +102,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Random;
@@ -133,6 +134,7 @@
     private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1,
             "mypackage", "attribution", "listener");
     private static final WorkSource WORK_SOURCE = new WorkSource(IDENTITY.getUid());
+    private static final String MISSING_PERMISSION = "missing_permission";
 
     private Random mRandom;
 
@@ -173,6 +175,9 @@
         doReturn(mPackageManager).when(mContext).getPackageManager();
         doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
         doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext)
+                .checkCallingOrSelfPermission(MISSING_PERMISSION);
 
         mInjector = new TestInjector(mContext);
         mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
@@ -187,12 +192,18 @@
     }
 
     private void createManager(String name) {
+        createManager(name, Collections.emptyList());
+    }
+
+    private void createManager(String name, Collection<String> requiredPermissions) {
         mStateChangedListener = mock(LocationProviderManager.StateChangedListener.class);
 
         mProvider = new TestProvider(PROPERTIES, PROVIDER_IDENTITY);
         mProvider.setProviderAllowed(true);
 
-        mManager = new LocationProviderManager(mContext, mInjector, name, mPassive);
+        mManager =
+                new LocationProviderManager(
+                        mContext, mInjector, name, mPassive, requiredPermissions);
         mManager.startManager(mStateChangedListener);
         mManager.setRealProvider(mProvider);
     }
@@ -1317,6 +1328,17 @@
         assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse();
     }
 
+    @Test
+    public void testIsVisibleToCaller() {
+        assertThat(mManager.isVisibleToCaller()).isTrue();
+    }
+
+    @Test
+    public void testIsVisibleToCaller_noPermissions() {
+        createManager("any_name", Collections.singletonList(MISSING_PERMISSION));
+        assertThat(mManager.isVisibleToCaller()).isFalse();
+    }
+
     private ILocationListener createMockLocationListener() {
         return spy(new ILocationListener.Stub() {
             @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 27d0662..4f56271 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -193,6 +193,7 @@
         val incrementalManager: IncrementalManager = mock()
         val platformCompat: PlatformCompat = mock()
         val settings: Settings = mock()
+        val crossProfileIntentFilterHelper: CrossProfileIntentFilterHelper = mock()
         val resources: Resources = mock()
         val systemConfig: SystemConfig = mock()
         val apexManager: ApexManager = mock()
@@ -279,6 +280,8 @@
         whenever(mocks.injector.incrementalManager).thenReturn(mocks.incrementalManager)
         whenever(mocks.injector.compatibility).thenReturn(mocks.platformCompat)
         whenever(mocks.injector.settings).thenReturn(mocks.settings)
+        whenever(mocks.injector.crossProfileIntentFilterHelper)
+                .thenReturn(mocks.crossProfileIntentFilterHelper)
         whenever(mocks.injector.dexManager).thenReturn(mocks.dexManager)
         whenever(mocks.injector.systemConfig).thenReturn(mocks.systemConfig)
         whenever(mocks.injector.apexManager).thenReturn(mocks.apexManager)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
index 6d8910e..58cff94 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -107,8 +107,8 @@
                 onVisible(PROFILE_USER_ID));
         startForegroundUser(PARENT_USER_ID);
 
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
         expectUserIsVisible(PROFILE_USER_ID);
@@ -138,7 +138,8 @@
     public void testStartBgUser_onInvalidDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG, INVALID_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+                INVALID_DISPLAY);
 
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
@@ -151,7 +152,7 @@
     public void testStartBgUser_onSecondaryDisplay_displayAvailable() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
 
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
                 SECONDARY_DISPLAY_ID);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
@@ -176,7 +177,7 @@
         expectUserIsNotVisibleOnDisplay("before", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
         expectUserIsNotVisibleOnDisplay("before", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
 
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
                 SECONDARY_DISPLAY_ID);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
@@ -189,7 +190,7 @@
         AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(OTHER_USER_ID));
         startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
 
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
                 SECONDARY_DISPLAY_ID);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
@@ -205,7 +206,7 @@
         AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
         startUserInSecondaryDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
                 SECONDARY_DISPLAY_ID);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
@@ -228,8 +229,8 @@
         AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
         startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
index 1065392..3d64c29 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -106,8 +106,8 @@
                 onVisible(PROFILE_USER_ID));
         startForegroundUser(PARENT_USER_ID);
 
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
         expectUserIsVisible(PROFILE_USER_ID);
@@ -126,7 +126,7 @@
     public void testStartBgUser_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
                 SECONDARY_DISPLAY_ID);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 4487d13..74fd9ff 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -26,6 +26,9 @@
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
 import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
 import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
 import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
@@ -99,8 +102,9 @@
      */
     protected static final int OTHER_SECONDARY_DISPLAY_ID = 108;
 
-    protected static final boolean FG = true;
-    protected static final boolean BG = false;
+    protected static final int FG = USER_START_MODE_FOREGROUND;
+    protected static final int BG = USER_START_MODE_BACKGROUND;
+    protected static final int BG_VISIBLE = USER_START_MODE_BACKGROUND_VISIBLE;
 
     private Handler mHandler;
     protected AsyncUserVisibilityListener.Factory mListenerFactory;
@@ -154,8 +158,7 @@
     public final void testStartBgUser_onDefaultDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
-                DEFAULT_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG, DEFAULT_DISPLAY);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         expectUserIsNotVisibleAtAll(USER_ID);
@@ -166,6 +169,34 @@
     }
 
     @Test
+    public final void testStartBgUser_onDefaultDisplay_visible() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public final void testStartBgUser_onSecondaryDisplay_invisible() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+
+        listener.verify();
+    }
+
+    @Test
     public final void testStartBgSystemUser_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForEvents(
                 onInvisible(INITIAL_CURRENT_USER_ID),
@@ -228,8 +259,8 @@
             throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -244,8 +275,8 @@
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
         startBackgroundUser(PARENT_USER_ID);
 
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -261,6 +292,21 @@
     public final void testStartBgProfile_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public final void testStartBgProfile_onSecondaryDisplay_invisible() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
                 SECONDARY_DISPLAY_ID);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
@@ -384,8 +430,8 @@
         Log.d(TAG, "starting default profile (" + PROFILE_USER_ID + ") in background after starting"
                 + " its parent (" + PARENT_USER_ID + ") on foreground");
 
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
         if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
             throw new IllegalStateException("Failed to start profile user " + PROFILE_USER_ID
                     + ": mediator returned " + userAssignmentResultToString(result));
@@ -403,7 +449,7 @@
         Preconditions.checkArgument(displayId != INVALID_DISPLAY && displayId != DEFAULT_DISPLAY,
                 "must pass a secondary display, not %d", displayId);
         Log.d(TAG, "startUserInSecondaryDisplay(" + userId + ", " + displayId + ")");
-        int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG, displayId);
+        int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG_VISIBLE, displayId);
         if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
             throw new IllegalStateException("Failed to startuser " + userId
                     + " on background: mediator returned " + userAssignmentResultToString(result));
diff --git a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
index 83a597d..b0ad31d 100644
--- a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
@@ -179,8 +179,9 @@
         mCountryDetectorService.systemRunning();
         mCountryDetectorService.addCountryListener(countryListenerA);
         mCountryDetectorService.addCountryListener(countryListenerB);
-        expect.that(countryListenerA.isNotified()).isFalse();
-        expect.that(countryListenerB.isNotified()).isFalse();
+        //Immediate Callback Info support at ag/20470367
+        expect.that(countryListenerA.isNotified()).isTrue();
+        expect.that(countryListenerB.isNotified()).isTrue();
         mCountryDetectorService.notifyReceivers(country);
 
         expect.that(countryListenerA.isNotified()).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 98037d7..0dfe664 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -243,14 +243,6 @@
     }
 
     @Test
-    public void testStartUserOnSecondaryDisplay_defaultDisplay() {
-        assertThrows(IllegalArgumentException.class, () -> mUserController
-                .startUserOnSecondaryDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY));
-
-        verifyUserNeverAssignedToDisplay();
-    }
-
-    @Test
     public void testStartUserOnSecondaryDisplay() {
         boolean started = mUserController.startUserOnSecondaryDisplay(TEST_USER_ID, 42);
 
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
index 6630178..47fdcb6 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
@@ -64,12 +64,12 @@
         // Verify that we got called for the ops being noted
         final InOrder inOrder = inOrder(listener);
         inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
-                .times(1)).onOpNoted(eq(AppOpsManager.OP_FINE_LOCATION),
+                .times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
                 eq(Process.myUid()), eq(getContext().getPackageName()),
                 eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
                 eq(AppOpsManager.MODE_ALLOWED));
         inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
-                .times(1)).onOpNoted(eq(AppOpsManager.OP_CAMERA),
+                .times(1)).onOpNoted(eq(AppOpsManager.OPSTR_CAMERA),
                 eq(Process.myUid()), eq(getContext().getPackageName()),
                 eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
                 eq(AppOpsManager.MODE_ALLOWED));
@@ -94,7 +94,7 @@
 
         // Verify it's watched again
         verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
-                .times(2)).onOpNoted(eq(AppOpsManager.OP_FINE_LOCATION),
+                .times(2)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
                 eq(Process.myUid()), eq(getContext().getPackageName()),
                 eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
                 eq(AppOpsManager.MODE_ALLOWED));
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index d2f2af1..9c7c574 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.startsWith;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -143,4 +144,38 @@
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
     }
 
+    @Test
+    public void createNavigationTouchpad_hasDeviceId() {
+        final IBinder deviceToken = new Binder();
+        mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,
+                deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
+
+        int deviceId = mInputController.getInputDeviceId(deviceToken);
+        int[] deviceIds = InputManager.getInstance().getInputDeviceIds();
+
+        assertWithMessage("InputManager's deviceIds list should contain id of the device").that(
+            deviceIds).asList().contains(deviceId);
+    }
+
+    @Test
+    public void createNavigationTouchpad_setsTypeAssociation() {
+        final IBinder deviceToken = new Binder();
+        mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,
+                deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
+
+        verify(mInputManagerInternalMock).setTypeAssociation(
+                startsWith("virtualNavigationTouchpad:"), eq("touchNavigation"));
+    }
+
+    @Test
+    public void createAndUnregisterNavigationTouchpad_unsetsTypeAssociation() {
+        final IBinder deviceToken = new Binder();
+        mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,
+                deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
+
+        mInputController.unregisterInputDevice(deviceToken);
+
+        verify(mInputManagerInternalMock).unsetTypeAssociation(
+                startsWith("virtualNavigationTouchpad:"));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index ded8179..31e53d5 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.companion.virtual;
 
-import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID;
-import static android.companion.virtual.VirtualDeviceManager.INVALID_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
@@ -69,6 +69,7 @@
 import android.hardware.input.VirtualMouseConfig;
 import android.hardware.input.VirtualMouseRelativeEvent;
 import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualNavigationTouchpadConfig;
 import android.hardware.input.VirtualTouchEvent;
 import android.hardware.input.VirtualTouchscreenConfig;
 import android.net.MacAddress;
@@ -97,6 +98,8 @@
 import com.android.server.input.InputManagerInternal;
 import com.android.server.sensors.SensorManagerInternal;
 
+import com.google.android.collect.Sets;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -108,6 +111,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 
 @Presubmit
@@ -124,12 +128,13 @@
     private static final String GOOGLE_MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
     private static final String DEVICE_NAME = "device name";
     private static final int DISPLAY_ID = 2;
+    private static final int DISPLAY_ID_2 = 3;
+    private static final int DEVICE_OWNER_UID_1 = 50;
+    private static final int DEVICE_OWNER_UID_2 = 51;
     private static final int UID_1 = 0;
     private static final int UID_2 = 10;
     private static final int UID_3 = 10000;
     private static final int UID_4 = 10001;
-    private static final int ASSOCIATION_ID_1 = 1;
-    private static final int ASSOCIATION_ID_2 = 2;
     private static final int PRODUCT_ID = 10;
     private static final int VENDOR_ID = 5;
     private static final String UNIQUE_ID = "uniqueid";
@@ -171,6 +176,14 @@
                     .setWidthInPixels(WIDTH)
                     .setHeightInPixels(HEIGHT)
                     .build();
+    private static final VirtualNavigationTouchpadConfig NAVIGATION_TOUCHPAD_CONFIG =
+            new VirtualNavigationTouchpadConfig.Builder(
+                    /* touchpadHeight= */ HEIGHT, /* touchpadWidth= */ WIDTH)
+                    .setVendorId(VENDOR_ID)
+                    .setProductId(PRODUCT_ID)
+                    .setInputDeviceName(DEVICE_NAME)
+                    .setAssociatedDisplayId(DISPLAY_ID)
+                    .build();
 
     private Context mContext;
     private InputManagerMockHelper mInputManagerMockHelper;
@@ -301,34 +314,25 @@
                 mContext.getSystemService(WindowManager.class), threadVerifier);
         mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
 
-        mAssociationInfo = new AssociationInfo(1, 0, null,
+        mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null,
                 MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
 
         mVdms = new VirtualDeviceManagerService(mContext);
         mLocalService = mVdms.getLocalServiceInstance();
         mVdm = mVdms.new VirtualDeviceManagerImpl();
-
-        VirtualDeviceParams params = new VirtualDeviceParams
-                .Builder()
-                .setBlockedActivities(getBlockedActivities())
-                .build();
-        mDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), /* ownerUid= */ 0, VIRTUAL_DEVICE_ID,
-                mInputController, mSensorController, (int associationId) -> {},
-                mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
-        mVdms.addVirtualDevice(mDeviceImpl);
+        mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID, DEVICE_OWNER_UID_1);
     }
 
     @Test
     public void getDeviceIdForDisplayId_invalidDisplayId_returnsDefault() {
         assertThat(mVdm.getDeviceIdForDisplayId(Display.INVALID_DISPLAY))
-                .isEqualTo(DEFAULT_DEVICE_ID);
+                .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
     public void getDeviceIdForDisplayId_defaultDisplayId_returnsDefault() {
         assertThat(mVdm.getDeviceIdForDisplayId(Display.DEFAULT_DISPLAY))
-                .isEqualTo(DEFAULT_DEVICE_ID);
+                .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
@@ -336,7 +340,7 @@
         mDeviceImpl.mVirtualDisplayIds.remove(DISPLAY_ID);
 
         assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID))
-                .isEqualTo(DEFAULT_DEVICE_ID);
+                .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
@@ -349,13 +353,13 @@
 
     @Test
     public void getDevicePolicy_invalidDeviceId_returnsDefault() {
-        assertThat(mVdm.getDevicePolicy(INVALID_DEVICE_ID, POLICY_TYPE_SENSORS))
+        assertThat(mVdm.getDevicePolicy(DEVICE_ID_INVALID, POLICY_TYPE_SENSORS))
                 .isEqualTo(DEVICE_POLICY_DEFAULT);
     }
 
     @Test
     public void getDevicePolicy_defaultDeviceId_returnsDefault() {
-        assertThat(mVdm.getDevicePolicy(DEFAULT_DEVICE_ID, POLICY_TYPE_SENSORS))
+        assertThat(mVdm.getDevicePolicy(DEVICE_ID_DEFAULT, POLICY_TYPE_SENSORS))
                 .isEqualTo(DEVICE_POLICY_DEFAULT);
     }
 
@@ -380,7 +384,8 @@
                 .build();
         mDeviceImpl = new VirtualDeviceImpl(mContext,
                 mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
-                mInputController, mSensorController, (int associationId) -> {},
+                mInputController, mSensorController,
+                /* onDeviceCloseListener= */ (int deviceId) -> {},
                 mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
         mVdms.addVirtualDevice(mDeviceImpl);
 
@@ -389,6 +394,106 @@
     }
 
     @Test
+    public void getDeviceOwnerUid_oneDevice_returnsCorrectId() {
+        int ownerUid = mLocalService.getDeviceOwnerUid(mDeviceImpl.getDeviceId());
+        assertThat(ownerUid).isEqualTo(mDeviceImpl.getOwnerUid());
+    }
+
+    @Test
+    public void getDeviceOwnerUid_twoDevices_returnsCorrectId() {
+        int firstDeviceId = mDeviceImpl.getDeviceId();
+        int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
+
+        createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
+
+        int secondDeviceOwner = mLocalService.getDeviceOwnerUid(secondDeviceId);
+        assertThat(secondDeviceOwner).isEqualTo(DEVICE_OWNER_UID_2);
+
+        int firstDeviceOwner = mLocalService.getDeviceOwnerUid(firstDeviceId);
+        assertThat(firstDeviceOwner).isEqualTo(DEVICE_OWNER_UID_1);
+    }
+
+    @Test
+    public void getDeviceOwnerUid_nonExistentDevice_returnsInvalidUid() {
+        int nonExistentDeviceId = DEVICE_ID_DEFAULT;
+        int ownerUid = mLocalService.getDeviceOwnerUid(nonExistentDeviceId);
+        assertThat(ownerUid).isEqualTo(Process.INVALID_UID);
+    }
+
+    @Test
+    public void getDeviceIdsForUid_noRunningApps_returnsNull() {
+        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+        assertThat(deviceIds).isEmpty();
+    }
+
+    @Test
+    public void getDeviceIdsForUid_differentUidOnDevice_returnsNull() {
+        GenericWindowPolicyController gwpc =
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID);
+        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_2));
+
+        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+        assertThat(deviceIds).isEmpty();
+    }
+
+    @Test
+    public void getDeviceIdsForUid_oneUidOnDevice_returnsCorrectId() {
+        GenericWindowPolicyController gwpc =
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID);
+        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+
+        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+        assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
+    }
+
+    @Test
+    public void getDeviceIdsForUid_twoUidsOnDevice_returnsCorrectId() {
+        GenericWindowPolicyController gwpc =
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID);
+        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+
+        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+        assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
+    }
+
+    @Test
+    public void getDeviceIdsForUid_twoDevicesUidOnOne_returnsCorrectId() {
+        int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
+
+        VirtualDeviceImpl secondDevice = createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
+
+        GenericWindowPolicyController gwpc =
+                secondDevice.createWindowPolicyController(new ArrayList<>());
+        secondDevice.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_2);
+        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+
+        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+        assertThat(deviceIds).containsExactly(secondDevice.getDeviceId());
+    }
+
+    @Test
+    public void getDeviceIdsForUid_twoDevicesUidOnBoth_returnsCorrectId() {
+        int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
+
+        VirtualDeviceImpl secondDevice = createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
+        GenericWindowPolicyController gwpc1 =
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+        GenericWindowPolicyController gwpc2 =
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc1, DISPLAY_ID);
+        secondDevice.onVirtualDisplayCreatedLocked(gwpc2, DISPLAY_ID_2);
+        gwpc1.onRunningAppsChanged(Sets.newArraySet(UID_1));
+        gwpc2.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+
+        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+        assertThat(deviceIds).containsExactly(
+                mDeviceImpl.getDeviceId(), secondDevice.getDeviceId());
+    }
+
+    @Test
     public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
                 mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
@@ -423,7 +528,7 @@
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
 
-        mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uids);
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uids);
         TestableLooper.get(this).processAllMessages();
 
         verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(uids);
@@ -436,13 +541,13 @@
         mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
 
         // Notifies that the running apps on the first virtual device has changed.
-        mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1);
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uidsOnDevice1);
         TestableLooper.get(this).processAllMessages();
         verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(
                 new ArraySet<>(Arrays.asList(UID_1, UID_2)));
 
         // Notifies that the running apps on the second virtual device has changed.
-        mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_2, uidsOnDevice2);
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId() + 1, uidsOnDevice2);
         TestableLooper.get(this).processAllMessages();
         // The union of the apps running on both virtual devices are sent to the listeners.
         verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(
@@ -450,7 +555,7 @@
 
         // Notifies that the running apps on the first virtual device has changed again.
         uidsOnDevice1.remove(UID_2);
-        mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1);
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uidsOnDevice1);
         mLocalService.onAppsOnVirtualDeviceChanged();
         TestableLooper.get(this).processAllMessages();
         // The union of the apps running on both virtual devices are sent to the listeners.
@@ -459,7 +564,7 @@
 
         // Notifies that the running apps on the first virtual device has changed but with the same
         // set of UIDs.
-        mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1);
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uidsOnDevice1);
         mLocalService.onAppsOnVirtualDeviceChanged();
         TestableLooper.get(this).processAllMessages();
         // Listeners should not be notified.
@@ -604,8 +709,67 @@
                         .build();
         mDeviceImpl.createVirtualTouchscreen(positiveConfig, BINDER);
         assertWithMessage(
-                "Virtual touchscreen should create input device descriptor on successful creation"
-                        + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
+            "Virtual touchscreen should create input device descriptor on successful creation"
+                + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
+    }
+
+    @Test
+    public void createVirtualNavigationTouchpad_noDisplay_failsSecurityException() {
+        assertThrows(SecurityException.class,
+                () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
+                        BINDER));
+    }
+
+    @Test
+    public void createVirtualNavigationTouchpad_zeroDisplayDimension_failsWithException() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        assertThrows(IllegalArgumentException.class,
+                () -> {
+                final VirtualNavigationTouchpadConfig zeroConfig =
+                        new VirtualNavigationTouchpadConfig.Builder(
+                                /* touchpadHeight= */ 0, /* touchpadWidth= */ 0)
+                                .setVendorId(VENDOR_ID)
+                                .setProductId(PRODUCT_ID)
+                                .setInputDeviceName(DEVICE_NAME)
+                                .setAssociatedDisplayId(DISPLAY_ID)
+                                .build();
+                mDeviceImpl.createVirtualNavigationTouchpad(zeroConfig, BINDER);
+            });
+    }
+
+    @Test
+    public void createVirtualNavigationTouchpad_negativeDisplayDimension_failsWithException() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        assertThrows(IllegalArgumentException.class,
+                () -> {
+                    final VirtualNavigationTouchpadConfig zeroConfig =
+                            new VirtualNavigationTouchpadConfig.Builder(
+                                    /* touchpadHeight= */ -50, /* touchpadWidth= */ 50)
+                                    .setVendorId(VENDOR_ID)
+                                    .setProductId(PRODUCT_ID)
+                                    .setInputDeviceName(DEVICE_NAME)
+                                    .setAssociatedDisplayId(DISPLAY_ID)
+                                    .build();
+                mDeviceImpl.createVirtualNavigationTouchpad(zeroConfig, BINDER);
+            });
+    }
+
+    @Test
+    public void createVirtualNavigationTouchpad_positiveDisplayDimension_successful() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        VirtualNavigationTouchpadConfig positiveConfig =
+                new VirtualNavigationTouchpadConfig.Builder(
+                            /* touchpadHeight= */ 50, /* touchpadWidth= */ 50)
+                        .setVendorId(VENDOR_ID)
+                        .setProductId(PRODUCT_ID)
+                        .setInputDeviceName(DEVICE_NAME)
+                        .setAssociatedDisplayId(DISPLAY_ID)
+                        .build();
+        mDeviceImpl.createVirtualNavigationTouchpad(positiveConfig, BINDER);
+        assertWithMessage(
+            "Virtual navigation touchpad should create input device descriptor on successful "
+            + "creation"
+                + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
     }
 
     @Test
@@ -652,6 +816,16 @@
     }
 
     @Test
+    public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+        assertThrows(SecurityException.class,
+                () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
+                        BINDER));
+    }
+
+    @Test
     public void createVirtualSensor_noPermission_failsSecurityException() {
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
@@ -715,7 +889,18 @@
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
         assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
-                mInputController.getInputDeviceDescriptors()).isNotEmpty();
+            mInputController.getInputDeviceDescriptors()).isNotEmpty();
+        verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
+                eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
+    }
+
+    @Test
+    public void createVirtualNavigationTouchpad_hasDisplay_obtainFileDescriptor() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, BINDER);
+        assertWithMessage("Virtual navigation touchpad should register fd when the display matches")
+            .that(
+            mInputController.getInputDeviceDescriptors()).isNotEmpty();
         verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
                 eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
     }
@@ -1160,7 +1345,6 @@
                 /* targetDisplayCategory= */ null);
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
-
     }
 
     @Test
@@ -1185,4 +1369,18 @@
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
     }
+
+    private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid) {
+        VirtualDeviceParams params = new VirtualDeviceParams
+                .Builder()
+                .setBlockedActivities(getBlockedActivities())
+                .build();
+        VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
+                mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
+                mInputController, mSensorController,
+                /* onDeviceCloseListener= */ (int deviceId) -> {},
+                mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
+        mVdms.addVirtualDevice(virtualDeviceImpl);
+        return virtualDeviceImpl;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
index f6f1339..f473086 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.companion.virtual;
 
-import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID;
-import static android.companion.virtual.VirtualDeviceManager.INVALID_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -41,14 +41,14 @@
     public void build_invalidId_shouldThrowIllegalArgumentException() {
         assertThrows(
                 IllegalArgumentException.class,
-                () -> new VirtualDevice(INVALID_DEVICE_ID, VIRTUAL_DEVICE_NAME));
+                () -> new VirtualDevice(DEVICE_ID_INVALID, VIRTUAL_DEVICE_NAME));
     }
 
     @Test
     public void build_defaultId_shouldThrowIllegalArgumentException() {
         assertThrows(
                 IllegalArgumentException.class,
-                () -> new VirtualDevice(DEFAULT_DEVICE_ID, VIRTUAL_DEVICE_NAME));
+                () -> new VirtualDevice(DEVICE_ID_DEFAULT, VIRTUAL_DEVICE_NAME));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 88314ab..a55b196 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -7730,9 +7730,9 @@
 
         dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
 
-        assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
+        assertThat(dpm.isFinancedDevice()).isTrue();
         initializeDpms();
-        assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
+        assertThat(dpm.isFinancedDevice()).isTrue();
     }
 
     @Test
@@ -7743,19 +7743,12 @@
 
         dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
 
-        assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
+        assertThat(dpm.isFinancedDevice()).isTrue();
     }
 
     @Test
-    public void testGetDeviceOwnerType_throwsExceptionWhenThereIsNoDeviceOwner() {
-        assertThrows(IllegalStateException.class, () -> dpm.getDeviceOwnerType(admin1));
-    }
-
-    @Test
-    public void testGetDeviceOwnerType_throwsExceptionWhenNotAsDeviceOwnerAdmin() throws Exception {
-        setDeviceOwner();
-
-        assertThrows(IllegalStateException.class, () -> dpm.getDeviceOwnerType(admin2));
+    public void testIsFinancedDevice_throwsExceptionWhenThereIsNoDeviceOwner() {
+        assertThrows(SecurityException.class, () -> dpm.isFinancedDevice());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index fdb78b8..c1eafd9 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -145,6 +145,16 @@
         assertArrayEquals(new float[]{23, 24, 25},
                 mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
 
+        assertEquals(75, mDisplayDeviceConfig.getDefaultLowRefreshRate());
+        assertEquals(90, mDisplayDeviceConfig.getDefaultHighRefreshRate());
+        assertArrayEquals(new int[]{45, 55},
+                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds());
+        assertArrayEquals(new int[]{50, 60},
+                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds());
+        assertArrayEquals(new int[]{65, 75},
+                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds());
+        assertArrayEquals(new int[]{70, 80},
+                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds());
 
         // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
@@ -207,8 +217,8 @@
                 mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
         assertArrayEquals(new float[]{29, 30, 31},
                 mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
-        assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
+        assertEquals(mDisplayDeviceConfig.getDefaultLowRefreshRate(), DEFAULT_REFRESH_RATE);
+        assertEquals(mDisplayDeviceConfig.getDefaultHighRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
         assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(),
                 LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
         assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(),
@@ -416,6 +426,38 @@
                 +           "</brightnessThrottlingPoint>\n"
                 +       "</brightnessThrottlingMap>\n"
                 +   "</thermalThrottling>\n"
+                +   "<refreshRate>\n"
+                +       "<lowerBlockingZoneConfigs>\n"
+                +           "<defaultRefreshRate>75</defaultRefreshRate>\n"
+                +           "<blockingZoneThreshold>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>50</lux>\n"
+                // This number will be rounded to integer when read by the system
+                +                   "<nits>45.3</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>60</lux>\n"
+                // This number will be rounded to integer when read by the system
+                +                   "<nits>55.2</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +           "</blockingZoneThreshold>\n"
+                +       "</lowerBlockingZoneConfigs>\n"
+                +       "<higherBlockingZoneConfigs>\n"
+                +           "<defaultRefreshRate>90</defaultRefreshRate>\n"
+                +           "<blockingZoneThreshold>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>70</lux>\n"
+                // This number will be rounded to integer when read by the system
+                +                   "<nits>65.6</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>80</lux>\n"
+                // This number will be rounded to integer when read by the system
+                +                   "<nits>75</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +           "</blockingZoneThreshold>\n"
+                +       "</higherBlockingZoneConfigs>\n"
+                +   "</refreshRate>\n"
                 + "</displayConfiguration>\n";
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 865bc98..bd4058a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -2250,8 +2250,8 @@
 
         // Notify that the default display is updated, such that DisplayDeviceConfig has new values
         DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
-        when(displayDeviceConfig.getDefaultRefreshRate()).thenReturn(50);
-        when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(55);
+        when(displayDeviceConfig.getDefaultLowRefreshRate()).thenReturn(50);
+        when(displayDeviceConfig.getDefaultHighRefreshRate()).thenReturn(55);
         when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
         when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
         when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index c7caa43..c6a0b0f 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -18,10 +18,14 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.DEFAULT_DISPLAY_GROUP;
+import static android.view.Display.TYPE_INTERNAL;
+import static android.view.Display.TYPE_VIRTUAL;
 
+import static com.android.server.display.DeviceStateToLayoutMap.STATE_DEFAULT;
 import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
 import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
 import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
+import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
 
@@ -69,7 +73,6 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Arrays;
-import java.util.Set;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -155,8 +158,8 @@
 
     @Test
     public void testDisplayDeviceAddAndRemove_Internal() {
-        DisplayDevice device = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         // add
         LogicalDisplay displayAdded = add(device);
@@ -177,7 +180,7 @@
         testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_EXTERNAL);
         testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI);
         testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY);
-        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_VIRTUAL);
+        testDisplayDeviceAddAndRemove_NonInternal(TYPE_VIRTUAL);
         testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN);
 
         // Call the internal test again, just to verify that adding non-internal displays
@@ -187,9 +190,9 @@
 
     @Test
     public void testDisplayDeviceAdd_TwoInternalOneDefault() {
-        DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0);
-        DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800, 0);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         LogicalDisplay display1 = add(device1);
         assertEquals(info(display1).address, info(device1).address);
@@ -202,10 +205,10 @@
 
     @Test
     public void testDisplayDeviceAdd_TwoInternalBothDefault() {
-        DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         LogicalDisplay display1 = add(device1);
         assertEquals(info(display1).address, info(device1).address);
@@ -220,7 +223,7 @@
     @Test
     public void testDisplayDeviceAddAndRemove_OneExternalDefault() {
         DisplayDevice device = createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         // add
         LogicalDisplay displayAdded = add(device);
@@ -238,10 +241,10 @@
 
     @Test
     public void testDisplayDeviceAddAndRemove_SwitchDefault() {
-        DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         LogicalDisplay display1 = add(device1);
         assertEquals(info(display1).address, info(device1).address);
@@ -267,10 +270,10 @@
 
     @Test
     public void testGetDisplayIdsLocked() {
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+        add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
         add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0));
-        add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+        add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
 
         int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID,
                 /* includeDisabled= */ true);
@@ -280,71 +283,98 @@
     }
 
     @Test
-    public void testGetDisplayInfoForStateLocked_oneDisplayGroup_internalType() {
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 700, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+    public void testGetDisplayInfoForStateLocked_defaultLayout() {
+        final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
-        Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
-        assertThat(displayInfos.size()).isEqualTo(1);
-        for (DisplayInfo displayInfo : displayInfos) {
-            assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
-            assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
-            assertThat(displayInfo.logicalWidth).isEqualTo(600);
-            assertThat(displayInfo.logicalHeight).isEqualTo(800);
-        }
+        add(device1);
+        add(device2);
+
+        Layout layout1 = new Layout();
+        layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
+                /* isEnabled= */ true, mIdProducer);
+        layout1.createDisplayLocked(info(device2).address, /* isDefault= */ false,
+                /* isEnabled= */ true, mIdProducer);
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
+        assertThat(layout1.size()).isEqualTo(2);
+        final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
+
+        final DisplayInfo displayInfoDefault = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                STATE_DEFAULT, DEFAULT_DISPLAY);
+        assertThat(displayInfoDefault.displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayInfoDefault.logicalWidth).isEqualTo(width(device1));
+        assertThat(displayInfoDefault.logicalHeight).isEqualTo(height(device1));
+
+        final DisplayInfo displayInfoOther = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                STATE_DEFAULT, logicalId2);
+        assertThat(displayInfoOther).isNotNull();
+        assertThat(displayInfoOther.displayId).isEqualTo(logicalId2);
+        assertThat(displayInfoOther.logicalWidth).isEqualTo(width(device2));
+        assertThat(displayInfoOther.logicalHeight).isEqualTo(height(device2));
     }
 
     @Test
-    public void testGetDisplayInfoForStateLocked_oneDisplayGroup_differentTypes() {
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(Display.TYPE_EXTERNAL, 700, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+    public void testGetDisplayInfoForStateLocked_multipleLayouts() {
+        final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        final DisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 700, 800,
+                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
 
-        Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
-        assertThat(displayInfos.size()).isEqualTo(1);
-        for (DisplayInfo displayInfo : displayInfos) {
-            assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
-            assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
-            assertThat(displayInfo.logicalWidth).isEqualTo(600);
-            assertThat(displayInfo.logicalHeight).isEqualTo(800);
-        }
-    }
+        add(device1);
+        add(device2);
+        add(device3);
 
-    @Test
-    public void testGetDisplayInfoForStateLocked_multipleDisplayGroups_defaultGroup() {
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(Display.TYPE_VIRTUAL, 700, 800,
-                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP));
+        Layout layout1 = new Layout();
+        layout1.createDisplayLocked(info(device1).address,
+                /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
 
-        Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
-        assertThat(displayInfos.size()).isEqualTo(1);
-        for (DisplayInfo displayInfo : displayInfos) {
-            assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
-            assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
-            assertThat(displayInfo.logicalWidth).isEqualTo(600);
-            assertThat(displayInfo.logicalHeight).isEqualTo(800);
-        }
+        final int layoutState2 = 2;
+        Layout layout2 = new Layout();
+        layout2.createDisplayLocked(info(device2).address,
+                /* isDefault= */ false, /* isEnabled= */ true, mIdProducer);
+        // Device3 is the default display.
+        layout2.createDisplayLocked(info(device3).address,
+                /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+        when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
+        assertThat(layout2.size()).isEqualTo(2);
+        final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
+
+        // Default layout.
+        final DisplayInfo displayInfoLayout1Default =
+                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                        STATE_DEFAULT, DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout1Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout1Default.logicalWidth).isEqualTo(width(device1));
+        assertThat(displayInfoLayout1Default.logicalHeight).isEqualTo(height(device1));
+
+        // Second layout, where device3 is the default display.
+        final DisplayInfo displayInfoLayout2Default =
+                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                        layoutState2, DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout2Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout2Default.logicalWidth).isEqualTo(width(device3));
+        assertThat(displayInfoLayout2Default.logicalHeight).isEqualTo(height(device3));
+
+        final DisplayInfo displayInfoLayout2Other =
+                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                        layoutState2, logicalId2);
+        assertThat(displayInfoLayout2Other).isNotNull();
+        assertThat(displayInfoLayout2Other.displayId).isEqualTo(logicalId2);
+        assertThat(displayInfoLayout2Other.logicalWidth).isEqualTo(width(device2));
+        assertThat(displayInfoLayout2Other.logicalHeight).isEqualTo(height(device2));
     }
 
     @Test
     public void testSingleDisplayGroup() {
-        LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
-        LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+        LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+        LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
+        LogicalDisplay display3 = add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
 
         assertEquals(DEFAULT_DISPLAY_GROUP,
                 mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
@@ -356,12 +386,12 @@
 
     @Test
     public void testMultipleDisplayGroups() {
-        LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
+        LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+        LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
 
 
-        TestDisplayDevice device3 = createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800,
+        TestDisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 600, 800,
                 DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
         LogicalDisplay display3 = add(device3);
 
@@ -519,10 +549,10 @@
 
     @Test
     public void testDeviceStateLocked() {
-        DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         Layout layout = new Layout();
         layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
@@ -577,14 +607,11 @@
         DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress();
 
         TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, "one",
-                Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+                TYPE_INTERNAL, 600, 800, DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
         TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, "two",
-                Display.TYPE_INTERNAL, 200, 800,
-                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+                TYPE_INTERNAL, 200, 800, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
         TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, "three",
-                Display.TYPE_INTERNAL, 600, 900,
-                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+                TYPE_INTERNAL, 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
 
         Layout threeDevicesEnabledLayout = new Layout();
         threeDevicesEnabledLayout.createDisplayLocked(
@@ -603,7 +630,7 @@
                 /* isEnabled= */ true,
                 mIdProducer);
 
-        when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT))
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
                 .thenReturn(threeDevicesEnabledLayout);
 
         LogicalDisplay display1 = add(device1);
@@ -735,6 +762,14 @@
         return device.getDisplayDeviceInfoLocked();
     }
 
+    private int width(DisplayDevice device) {
+        return info(device).width;
+    }
+
+    private int height(DisplayDevice device) {
+        return info(device).height;
+    }
+
     private DisplayInfo info(LogicalDisplay display) {
         return display.getDisplayInfoLocked();
     }
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
index e390bcc..3326f80f 100644
--- a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.input
 
+
 import android.content.Context
 import android.content.ContextWrapper
 import android.hardware.display.DisplayViewport
@@ -25,6 +26,7 @@
 import android.view.Display
 import android.view.PointerIcon
 import androidx.test.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 import org.junit.Assert.assertFalse
@@ -287,6 +289,28 @@
         verify(native).setPointerAcceleration(eq(5f))
     }
 
+    @Test
+    fun setDeviceTypeAssociation_setsDeviceTypeAssociation() {
+        val inputPort = "inputPort"
+        val type = "type"
+
+        localService.setTypeAssociation(inputPort, type)
+
+        assertThat(service.getDeviceTypeAssociations()).asList().containsExactly(inputPort, type)
+            .inOrder()
+    }
+
+    @Test
+    fun setAndUnsetDeviceTypeAssociation_deviceTypeAssociationIsMissing() {
+        val inputPort = "inputPort"
+        val type = "type"
+
+        localService.setTypeAssociation(inputPort, type)
+        localService.unsetTypeAssociation(inputPort)
+
+        assertTrue(service.getDeviceTypeAssociations().isEmpty())
+    }
+
     private fun setVirtualMousePointerDisplayIdAndVerify(overrideDisplayId: Int) {
         val thread = Thread { localService.setVirtualMousePointerDisplayId(overrideDisplayId) }
         thread.start()
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 4668e5d..5ca695b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -85,7 +85,8 @@
     protected static final int SECONDARY_USER_ID = 20;
 
     private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
-            UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+            UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY
+                    | UserInfo.FLAG_MAIN);
     private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
             UserInfo.FLAG_INITIALIZED);
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 196226a..41e3a08 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -59,65 +59,65 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
+
     @Before
-    public void disableProcessCaches() {
+    public void setUp() {
         PropertyInvalidatedCache.disableForTestMode();
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+        mService.initializeSyntheticPassword(MANAGED_PROFILE_USER_ID);
     }
 
     @Test
-    public void testCreatePasswordPrimaryUser() throws RemoteException {
-        testCreateCredential(PRIMARY_USER_ID, newPassword("password"));
+    public void testSetPasswordPrimaryUser() throws RemoteException {
+        setAndVerifyCredential(PRIMARY_USER_ID, newPassword("password"));
     }
 
     @Test
-    public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException {
-        testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPassword("password"));
+    public void testSetPasswordFailsWithoutLockScreen() throws RemoteException {
+        testSetCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPassword("password"));
     }
 
     @Test
-    public void testCreatePatternPrimaryUser() throws RemoteException {
-        testCreateCredential(PRIMARY_USER_ID, newPattern("123456789"));
+    public void testSetPatternPrimaryUser() throws RemoteException {
+        setAndVerifyCredential(PRIMARY_USER_ID, newPattern("123456789"));
     }
 
     @Test
-    public void testCreatePatternFailsWithoutLockScreen() throws RemoteException {
-        testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPattern("123456789"));
+    public void testSetPatternFailsWithoutLockScreen() throws RemoteException {
+        testSetCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPattern("123456789"));
     }
 
     @Test
     public void testChangePasswordPrimaryUser() throws RemoteException {
-        testChangeCredentials(PRIMARY_USER_ID, newPattern("78963214"), newPassword("asdfghjk"));
+        testChangeCredential(PRIMARY_USER_ID, newPattern("78963214"), newPassword("asdfghjk"));
     }
 
     @Test
     public void testChangePatternPrimaryUser() throws RemoteException {
-        testChangeCredentials(PRIMARY_USER_ID, newPassword("!£$%^&*(())"), newPattern("1596321"));
+        testChangeCredential(PRIMARY_USER_ID, newPassword("!£$%^&*(())"), newPattern("1596321"));
     }
 
     @Test
     public void testChangePasswordFailPrimaryUser() throws RemoteException {
-        initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"));
-
+        setCredential(PRIMARY_USER_ID, newPassword("password"));
         assertFalse(mService.setLockCredential(newPassword("newpwd"), newPassword("badpwd"),
                     PRIMARY_USER_ID));
-        assertVerifyCredentials(PRIMARY_USER_ID, newPassword("password"));
+        assertVerifyCredential(PRIMARY_USER_ID, newPassword("password"));
     }
 
     @Test
     public void testClearPasswordPrimaryUser() throws RemoteException {
-        initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"));
-        assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"),
-                PRIMARY_USER_ID));
-        assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(PRIMARY_USER_ID));
-        assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+        setCredential(PRIMARY_USER_ID, newPassword("password"));
+        clearCredential(PRIMARY_USER_ID, newPassword("password"));
     }
 
     @Test
     public void testManagedProfileUnifiedChallenge() throws RemoteException {
+        mService.initializeSyntheticPassword(TURNED_OFF_PROFILE_USER_ID);
+
         final LockscreenCredential firstUnifiedPassword = newPassword("pwd-1");
         final LockscreenCredential secondUnifiedPassword = newPassword("pwd-2");
-        assertTrue(mService.setLockCredential(firstUnifiedPassword,
-                nonePassword(), PRIMARY_USER_ID));
+        setCredential(PRIMARY_USER_ID, firstUnifiedPassword);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -146,14 +146,12 @@
         assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
 
         // Change primary password and verify that profile SID remains
-        assertTrue(mService.setLockCredential(
-                secondUnifiedPassword, firstUnifiedPassword, PRIMARY_USER_ID));
+        setCredential(PRIMARY_USER_ID, secondUnifiedPassword, firstUnifiedPassword);
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
         assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
 
         // Clear unified challenge
-        assertTrue(mService.setLockCredential(nonePassword(),
-                secondUnifiedPassword, PRIMARY_USER_ID));
+        clearCredential(PRIMARY_USER_ID, secondUnifiedPassword);
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID));
@@ -163,12 +161,8 @@
     public void testManagedProfileSeparateChallenge() throws RemoteException {
         final LockscreenCredential primaryPassword = newPassword("primary");
         final LockscreenCredential profilePassword = newPassword("profile");
-        assertTrue(mService.setLockCredential(primaryPassword,
-                nonePassword(),
-                PRIMARY_USER_ID));
-        assertTrue(mService.setLockCredential(profilePassword,
-                nonePassword(),
-                MANAGED_PROFILE_USER_ID));
+        setCredential(PRIMARY_USER_ID, primaryPassword);
+        setCredential(MANAGED_PROFILE_USER_ID, profilePassword);
 
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -191,8 +185,7 @@
         assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
 
-        assertTrue(mService.setLockCredential(
-                newPassword("pwd"), primaryPassword, PRIMARY_USER_ID));
+        setCredential(PRIMARY_USER_ID, newPassword("pwd"), primaryPassword);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 profilePassword, MANAGED_PROFILE_USER_ID, 0 /* flags */)
                 .getResponseCode());
@@ -207,8 +200,7 @@
         assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(MANAGED_PROFILE_USER_ID));
 
         // Set a separate challenge on the profile
-        assertTrue(mService.setLockCredential(
-                newPassword("12345678"), nonePassword(), MANAGED_PROFILE_USER_ID));
+        setCredential(MANAGED_PROFILE_USER_ID, newPassword("12345678"));
         assertNotEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
         assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(MANAGED_PROFILE_USER_ID));
 
@@ -221,11 +213,7 @@
 
     @Test
     public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception {
-        assertTrue(mService.setLockCredential(
-                newPassword("password"),
-                nonePassword(),
-                PRIMARY_USER_ID));
-
+        setCredential(PRIMARY_USER_ID, newPassword("password"));
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, "password".getBytes(),
                         PRIMARY_USER_ID);
@@ -234,11 +222,7 @@
     @Test
     public void testSetLockCredential_forProfileWithSeparateChallenge_sendsCredentials()
             throws Exception {
-        assertTrue(mService.setLockCredential(
-                newPattern("12345"),
-                nonePassword(),
-                MANAGED_PROFILE_USER_ID));
-
+        setCredential(MANAGED_PROFILE_USER_ID, newPattern("12345"));
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_PATTERN, "12345".getBytes(),
                         MANAGED_PROFILE_USER_ID);
@@ -248,13 +232,8 @@
     public void testSetLockCredential_forProfileWithSeparateChallenge_updatesCredentials()
             throws Exception {
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, true, null);
-        initializeStorageWithCredential(MANAGED_PROFILE_USER_ID, newPattern("12345"));
-
-        assertTrue(mService.setLockCredential(
-                newPassword("newPassword"),
-                newPattern("12345"),
-                MANAGED_PROFILE_USER_ID));
-
+        setCredential(MANAGED_PROFILE_USER_ID, newPattern("12345"));
+        setCredential(MANAGED_PROFILE_USER_ID, newPassword("newPassword"), newPattern("12345"));
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, "newPassword".getBytes(),
                         MANAGED_PROFILE_USER_ID);
@@ -264,12 +243,7 @@
     public void testSetLockCredential_forProfileWithUnifiedChallenge_doesNotSendRandomCredential()
             throws Exception {
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
-
-        assertTrue(mService.setLockCredential(
-                newPattern("12345"),
-                nonePassword(),
-                PRIMARY_USER_ID));
-
+        setCredential(PRIMARY_USER_ID, newPattern("12345"));
         verify(mRecoverableKeyStoreManager, never())
                 .lockScreenSecretChanged(
                         eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID));
@@ -281,13 +255,9 @@
                     throws Exception {
         final LockscreenCredential oldCredential = newPassword("oldPassword");
         final LockscreenCredential newCredential = newPassword("newPassword");
-        initializeStorageWithCredential(PRIMARY_USER_ID, oldCredential);
+        setCredential(PRIMARY_USER_ID, oldCredential);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
-
-        assertTrue(mService.setLockCredential(
-                newCredential,
-                oldCredential,
-                PRIMARY_USER_ID));
+        setCredential(PRIMARY_USER_ID, newCredential, oldCredential);
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential.getCredential(),
@@ -301,13 +271,9 @@
     public void
             testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_removesBothCredentials()
                     throws Exception {
-        initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("oldPassword"));
+        setCredential(PRIMARY_USER_ID, newPassword("oldPassword"));
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
-
-        assertTrue(mService.setLockCredential(
-                nonePassword(),
-                newPassword("oldPassword"),
-                PRIMARY_USER_ID));
+        clearCredential(PRIMARY_USER_ID, newPassword("oldPassword"));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, PRIMARY_USER_ID);
@@ -316,11 +282,10 @@
     }
 
     @Test
-    public void testSetLockCredential_nullCredential_removeBiometrics() throws RemoteException {
-        initializeStorageWithCredential(PRIMARY_USER_ID, newPattern("123654"));
+    public void testClearLockCredential_removesBiometrics() throws RemoteException {
+        setCredential(PRIMARY_USER_ID, newPattern("123654"));
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
-
-        mService.setLockCredential(nonePassword(), newPattern("123654"), PRIMARY_USER_ID);
+        clearCredential(PRIMARY_USER_ID, newPattern("123654"));
 
         // Verify fingerprint is removed
         verify(mFingerprintManager).removeAll(eq(PRIMARY_USER_ID), any());
@@ -335,14 +300,9 @@
             throws Exception {
         final LockscreenCredential parentPassword = newPassword("parentPassword");
         final LockscreenCredential profilePassword = newPassword("profilePassword");
-        initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword);
+        setCredential(PRIMARY_USER_ID, parentPassword);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
-
-        assertTrue(mService.setLockCredential(
-                profilePassword,
-                nonePassword(),
-                MANAGED_PROFILE_USER_ID));
-
+        setCredential(MANAGED_PROFILE_USER_ID, profilePassword);
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, profilePassword.getCredential(),
                         MANAGED_PROFILE_USER_ID);
@@ -356,9 +316,8 @@
         final LockscreenCredential profilePassword = newPattern("12345");
         mService.setSeparateProfileChallengeEnabled(
                 MANAGED_PROFILE_USER_ID, true, profilePassword);
-        initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword);
-        // Create and verify separate profile credentials.
-        testCreateCredential(MANAGED_PROFILE_USER_ID, profilePassword);
+        setCredential(PRIMARY_USER_ID, parentPassword);
+        setAndVerifyCredential(MANAGED_PROFILE_USER_ID, profilePassword);
 
         mService.setSeparateProfileChallengeEnabled(
                 MANAGED_PROFILE_USER_ID, false, profilePassword);
@@ -372,7 +331,7 @@
     @Test
     public void testVerifyCredential_forPrimaryUser_sendsCredentials() throws Exception {
         final LockscreenCredential password = newPassword("password");
-        initializeStorageWithCredential(PRIMARY_USER_ID, password);
+        setCredential(PRIMARY_USER_ID, password);
         reset(mRecoverableKeyStoreManager);
 
         mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */);
@@ -386,10 +345,7 @@
     public void testVerifyCredential_forProfileWithSeparateChallenge_sendsCredentials()
             throws Exception {
         final LockscreenCredential pattern = newPattern("12345");
-        assertTrue(mService.setLockCredential(
-                pattern,
-                nonePassword(),
-                MANAGED_PROFILE_USER_ID));
+        setCredential(MANAGED_PROFILE_USER_ID, pattern);
         reset(mRecoverableKeyStoreManager);
 
         mService.verifyCredential(pattern, MANAGED_PROFILE_USER_ID, 0 /* flags */);
@@ -403,7 +359,7 @@
     public void verifyCredential_forPrimaryUserWithUnifiedChallengeProfile_sendsCredentialsForBoth()
                     throws Exception {
         final LockscreenCredential pattern = newPattern("12345");
-        initializeStorageWithCredential(PRIMARY_USER_ID, pattern);
+        setCredential(PRIMARY_USER_ID, pattern);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         reset(mRecoverableKeyStoreManager);
 
@@ -423,7 +379,7 @@
     }
 
     @Test
-    public void testCredentialChangeNotPossibleInSecureFrpModeDuringSuw() {
+    public void testSetCredentialNotPossibleInSecureFrpModeDuringSuw() {
         setUserSetupComplete(false);
         setSecureFrpMode(true);
         try {
@@ -433,21 +389,17 @@
     }
 
     @Test
-    public void testCredentialChangePossibleInSecureFrpModeAfterSuw() {
+    public void testSetCredentialPossibleInSecureFrpModeAfterSuw() throws RemoteException {
         setUserSetupComplete(true);
         setSecureFrpMode(true);
-        assertTrue(mService.setLockCredential(newPassword("1234"), nonePassword(),
-                PRIMARY_USER_ID));
+        setCredential(PRIMARY_USER_ID, newPassword("1234"));
     }
 
     @Test
     public void testPasswordHistoryDisabledByDefault() throws Exception {
         final int userId = PRIMARY_USER_ID;
         checkPasswordHistoryLength(userId, 0);
-        initializeStorageWithCredential(userId, nonePassword());
-        checkPasswordHistoryLength(userId, 0);
-
-        assertTrue(mService.setLockCredential(newPassword("1234"), nonePassword(), userId));
+        setCredential(userId, newPassword("1234"));
         checkPasswordHistoryLength(userId, 0);
     }
 
@@ -456,20 +408,18 @@
         final int userId = PRIMARY_USER_ID;
         when(mDevicePolicyManager.getPasswordHistoryLength(any(), eq(userId))).thenReturn(3);
         checkPasswordHistoryLength(userId, 0);
-        initializeStorageWithCredential(userId, nonePassword());
-        checkPasswordHistoryLength(userId, 0);
 
-        assertTrue(mService.setLockCredential(newPassword("pass1"), nonePassword(), userId));
+        setCredential(userId, newPassword("pass1"));
         checkPasswordHistoryLength(userId, 1);
 
-        assertTrue(mService.setLockCredential(newPassword("pass2"), newPassword("pass1"), userId));
+        setCredential(userId, newPassword("pass2"), newPassword("pass1"));
         checkPasswordHistoryLength(userId, 2);
 
-        assertTrue(mService.setLockCredential(newPassword("pass3"), newPassword("pass2"), userId));
+        setCredential(userId, newPassword("pass3"), newPassword("pass2"));
         checkPasswordHistoryLength(userId, 3);
 
         // maximum length should have been reached
-        assertTrue(mService.setLockCredential(newPassword("pass4"), newPassword("pass3"), userId));
+        setCredential(userId, newPassword("pass4"), newPassword("pass3"));
         checkPasswordHistoryLength(userId, 3);
     }
 
@@ -479,18 +429,11 @@
         assertEquals(expectedLen, hashes.length);
     }
 
-    private void testCreateCredential(int userId, LockscreenCredential credential)
-            throws RemoteException {
-        assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
-        assertVerifyCredentials(userId, credential);
-    }
-
-    private void testCreateCredentialFailsWithoutLockScreen(
+    private void testSetCredentialFailsWithoutLockScreen(
             int userId, LockscreenCredential credential) throws RemoteException {
         mService.mHasSecureLockScreen = false;
-
         try {
-            mService.setLockCredential(credential, null, userId);
+            mService.setLockCredential(credential, nonePassword(), userId);
             fail("An exception should have been thrown.");
         } catch (UnsupportedOperationException e) {
             // Success - the exception was expected.
@@ -499,14 +442,14 @@
         assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(userId));
     }
 
-    private void testChangeCredentials(int userId, LockscreenCredential newCredential,
+    private void testChangeCredential(int userId, LockscreenCredential newCredential,
             LockscreenCredential oldCredential) throws RemoteException {
-        initializeStorageWithCredential(userId, oldCredential);
-        assertTrue(mService.setLockCredential(newCredential, oldCredential, userId));
-        assertVerifyCredentials(userId, newCredential);
+        setCredential(userId, oldCredential);
+        setCredential(userId, newCredential, oldCredential);
+        assertVerifyCredential(userId, newCredential);
     }
 
-    private void assertVerifyCredentials(int userId, LockscreenCredential credential)
+    private void assertVerifyCredential(int userId, LockscreenCredential credential)
             throws RemoteException{
         VerifyCredentialResponse response = mService.verifyCredential(credential, userId,
                 0 /* flags */);
@@ -533,16 +476,29 @@
                 badCredential, userId, 0 /* flags */).getResponseCode());
     }
 
-    private void initializeStorageWithCredential(int userId, LockscreenCredential credential)
+    private void setAndVerifyCredential(int userId, LockscreenCredential newCredential)
             throws RemoteException {
-        assertEquals(0, mGateKeeperService.getSecureUserId(userId));
-        synchronized (mService.mSpManager) {
-            mService.initializeSyntheticPasswordLocked(userId);
-        }
-        if (credential.isNone()) {
+        setCredential(userId, newCredential);
+        assertVerifyCredential(userId, newCredential);
+    }
+
+    private void setCredential(int userId, LockscreenCredential newCredential)
+            throws RemoteException {
+        setCredential(userId, newCredential, nonePassword());
+    }
+
+    private void clearCredential(int userId, LockscreenCredential oldCredential)
+            throws RemoteException {
+        setCredential(userId, nonePassword(), oldCredential);
+    }
+
+    private void setCredential(int userId, LockscreenCredential newCredential,
+            LockscreenCredential oldCredential) throws RemoteException {
+        assertTrue(mService.setLockCredential(newCredential, oldCredential, userId));
+        assertEquals(newCredential.getType(), mService.getCredentialType(userId));
+        if (newCredential.isNone()) {
             assertEquals(0, mGateKeeperService.getSecureUserId(userId));
         } else {
-            assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
             assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
index fc0ca7e..10869da 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -42,14 +42,13 @@
 public class LockscreenFrpTest extends BaseLockSettingsServiceTests {
 
     @Before
-    public void setDeviceNotProvisioned() throws Exception {
+    public void setUp() throws Exception {
+        PropertyInvalidatedCache.disableForTestMode();
+
         // FRP credential can only be verified prior to provisioning
         setDeviceProvisioned(false);
-    }
 
-    @Before
-    public void disableProcessCaches() {
-        PropertyInvalidatedCache.disableForTestMode();
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index b00467c..57593cf 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -19,7 +19,6 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
-import static com.android.internal.widget.LockPatternUtils.CURRENT_LSKF_BASED_PROTECTOR_ID_KEY;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -59,9 +58,6 @@
 import org.mockito.ArgumentCaptor;
 
 import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-
 
 /**
  * atest FrameworksServicesTests:SyntheticPasswordTests
@@ -127,21 +123,11 @@
         return mGateKeeperService.getSecureUserId(SyntheticPasswordManager.fakeUserId(userId)) != 0;
     }
 
-    private boolean hasSyntheticPassword(int userId) throws RemoteException {
-        return mService.getLong(CURRENT_LSKF_BASED_PROTECTOR_ID_KEY, 0, userId) != 0;
-    }
-
-    private void initializeCredential(LockscreenCredential password, int userId)
+    private void initSpAndSetCredential(int userId, LockscreenCredential credential)
             throws RemoteException {
-        assertTrue(mService.setLockCredential(password, nonePassword(), userId));
-        assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(userId));
-        assertTrue(mService.isSyntheticPasswordBasedCredential(userId));
-    }
-
-    protected void initializeSyntheticPassword(int userId) {
-        synchronized (mService.mSpManager) {
-            mService.initializeSyntheticPasswordLocked(userId);
-        }
+        mService.initializeSyntheticPassword(userId);
+        assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
+        assertEquals(credential.getType(), mService.getCredentialType(userId));
     }
 
     // Tests that the FRP credential is updated when an LSKF-based protector is created for the user
@@ -149,7 +135,7 @@
     @Test
     public void testFrpCredentialSyncedIfDeviceProvisioned() throws RemoteException {
         setDeviceProvisioned(true);
-        initializeSyntheticPassword(PRIMARY_USER_ID);
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
         verify(mStorage.mPersistentDataBlockManager).setFrpCredentialHandle(any());
     }
 
@@ -159,7 +145,7 @@
     @Test
     public void testEmptyFrpCredentialNotSyncedIfDeviceNotProvisioned() throws RemoteException {
         setDeviceProvisioned(false);
-        initializeSyntheticPassword(PRIMARY_USER_ID);
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
         verify(mStorage.mPersistentDataBlockManager, never()).setFrpCredentialHandle(any());
     }
 
@@ -169,7 +155,7 @@
     @Test
     public void testNonEmptyFrpCredentialSyncedIfDeviceNotProvisioned() throws RemoteException {
         setDeviceProvisioned(false);
-        initializeSyntheticPassword(PRIMARY_USER_ID);
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
         verify(mStorage.mPersistentDataBlockManager, never()).setFrpCredentialHandle(any());
         mService.setLockCredential(newPassword("password"), nonePassword(), PRIMARY_USER_ID);
         verify(mStorage.mPersistentDataBlockManager).setFrpCredentialHandle(any());
@@ -180,7 +166,7 @@
         final LockscreenCredential password = newPassword("password");
         final LockscreenCredential newPassword = newPassword("newpassword");
 
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         mService.setLockCredential(newPassword, password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
@@ -193,7 +179,7 @@
         LockscreenCredential password = newPassword("password");
         LockscreenCredential badPassword = newPassword("badpassword");
 
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         verify(mActivityManager).unlockUser2(eq(PRIMARY_USER_ID), any());
@@ -207,7 +193,7 @@
         LockscreenCredential password = newPassword("password");
         LockscreenCredential badPassword = newPassword("newpassword");
 
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         // clear password
         mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
@@ -225,7 +211,7 @@
         LockscreenCredential password = newPassword("password");
         LockscreenCredential badPassword = newPassword("new");
 
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         mService.setLockCredential(badPassword, password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
@@ -242,7 +228,7 @@
     public void testVerifyPassesPrimaryUserAuthSecret() throws RemoteException {
         LockscreenCredential password = newPassword("password");
 
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         reset(mAuthSecretService);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
@@ -253,7 +239,7 @@
     public void testSecondaryUserDoesNotPassAuthSecret() throws RemoteException {
         LockscreenCredential password = newPassword("password");
 
-        initializeCredential(password, SECONDARY_USER_ID);
+        initSpAndSetCredential(SECONDARY_USER_ID, password);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, SECONDARY_USER_ID, 0 /* flags */).getResponseCode());
         verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
@@ -262,7 +248,7 @@
     @Test
     public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecret() throws RemoteException {
         LockscreenCredential password = newPassword("password");
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
 
         reset(mAuthSecretService);
@@ -275,7 +261,7 @@
         LockscreenCredential password = newPassword("password");
         LockscreenCredential pattern = newPattern("123654");
         byte[] token = "some-high-entropy-secure-token".getBytes();
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         // Disregard any reportPasswordChanged() invocations as part of credential setup.
         flushHandlerTasks();
         reset(mDevicePolicyManager);
@@ -310,7 +296,7 @@
         LockscreenCredential password = newPassword("password");
         LockscreenCredential pattern = newPattern("123654");
         byte[] token = "some-high-entropy-secure-token".getBytes();
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
 
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
@@ -336,7 +322,7 @@
         LockscreenCredential pattern = newPattern("123654");
         LockscreenCredential newPassword = newPassword("password");
         byte[] token = "some-high-entropy-secure-token".getBytes();
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
 
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
@@ -356,38 +342,20 @@
     }
 
     @Test
-    public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration()
-            throws RemoteException {
+    public void testEscrowTokenActivatedImmediatelyIfNoUserPassword() throws RemoteException {
         final byte[] token = "some-high-entropy-secure-token".getBytes();
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-        assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
-    }
-
-    @Test
-    public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration()
-            throws RemoteException {
-        final byte[] token = "some-high-entropy-secure-token".getBytes();
-        // By first setting a password and then clearing it, we enter the state where SP is
-        // initialized but the user currently has no password
-        initializeCredential(newPassword("password"), PRIMARY_USER_ID);
-        assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"),
-                PRIMARY_USER_ID));
-        assertTrue(mService.isSyntheticPasswordBasedCredential(PRIMARY_USER_ID));
-
-        long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
-        assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
-        assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-        assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
     }
 
     @Test
     public void testEscrowTokenActivatedLaterWithUserPassword() throws RemoteException {
         byte[] token = "some-high-entropy-secure-token".getBytes();
         LockscreenCredential password = newPassword("password");
-        mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
 
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         // Token not activated immediately since user password exists
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
@@ -407,6 +375,7 @@
         when(mUserManagerInternal.isUserManaged(PRIMARY_USER_ID)).thenReturn(false);
         when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
 
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
         try {
             mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
             fail("Escrow token should not be possible on unmanaged device");
@@ -421,7 +390,7 @@
 
         LockscreenCredential password = newPassword("password");
         LockscreenCredential pattern = newPattern("123654");
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
 
         long handle0 = mLocalService.addEscrowToken(token0, PRIMARY_USER_ID, null);
         long handle1 = mLocalService.addEscrowToken(token1, PRIMARY_USER_ID, null);
@@ -450,6 +419,7 @@
         byte[] token = "some-high-entropy-secure-token".getBytes();
 
         mService.mHasSecureLockScreen = false;
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
@@ -473,7 +443,7 @@
     @Test
     public void testGetHashFactorPrimaryUser() throws RemoteException {
         LockscreenCredential password = newPassword("password");
-        mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         byte[] hashFactor = mService.getHashFactor(password, PRIMARY_USER_ID);
         assertNotNull(hashFactor);
 
@@ -486,6 +456,9 @@
 
     @Test
     public void testGetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+        mService.initializeSyntheticPassword(MANAGED_PROFILE_USER_ID);
+
         LockscreenCredential pattern = newPattern("1236");
         mService.setLockCredential(pattern, nonePassword(), PRIMARY_USER_ID);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
@@ -494,6 +467,9 @@
 
     @Test
     public void testGetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+        mService.initializeSyntheticPassword(MANAGED_PROFILE_USER_ID);
+
         LockscreenCredential primaryPassword = newPassword("primary");
         LockscreenCredential profilePassword = newPassword("profile");
         mService.setLockCredential(primaryPassword, nonePassword(), PRIMARY_USER_ID);
@@ -594,7 +570,7 @@
 
         LockscreenCredential password = newPassword("testGsiDisablesAuthSecret-password");
 
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
@@ -604,7 +580,7 @@
     public void testUnlockUserWithToken() throws Exception {
         LockscreenCredential password = newPassword("password");
         byte[] token = "some-high-entropy-secure-token".getBytes();
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         // Disregard any reportPasswordChanged() invocations as part of credential setup.
         flushHandlerTasks();
         reset(mDevicePolicyManager);
@@ -625,7 +601,7 @@
     @Test
     public void testPasswordChange_NoOrphanedFilesLeft() throws Exception {
         LockscreenCredential password = newPassword("password");
-        initializeCredential(password, PRIMARY_USER_ID);
+        initSpAndSetCredential(PRIMARY_USER_ID, password);
         assertTrue(mService.setLockCredential(password, password, PRIMARY_USER_ID));
         assertNoOrphanedFilesLeft(PRIMARY_USER_ID);
     }
@@ -633,6 +609,7 @@
     @Test
     public void testAddingEscrowToken_NoOrphanedFilesLeft() throws Exception {
         final byte[] token = "some-high-entropy-secure-token".getBytes();
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
         for (int i = 0; i < 16; i++) {
             long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
             assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
index 51ddcef..2c9ba34 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
@@ -40,6 +40,7 @@
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -49,6 +50,11 @@
 @RunWith(AndroidJUnit4.class)
 public class WeakEscrowTokenTests extends BaseLockSettingsServiceTests{
 
+    @Before
+    public void setUp() {
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+    }
+
     @Test
     public void testWeakTokenActivatedImmediatelyIfNoUserPassword()
             throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
index 6c13a6f..966c047 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -36,7 +36,7 @@
         assertEquals(Sets.newHashSet(), mPasswordSlotManager.getUsedSlots());
         mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, frpWeaverSlot, 0,
                 new byte[1]);
-        initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
+        mService.initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
         assertEquals(Sets.newHashSet(1), mPasswordSlotManager.getUsedSlots());
     }
 
@@ -52,7 +52,7 @@
         assertEquals(Sets.newHashSet(), mPasswordSlotManager.getUsedSlots());
         mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, frpWeaverSlot, 0,
                 new byte[1]);
-        initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
+        mService.initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
         assertEquals(Sets.newHashSet(0), mPasswordSlotManager.getUsedSlots());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
new file mode 100644
index 0000000..54fa272
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -0,0 +1,840 @@
+/*
+ * Copyright (C) 2022 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.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
+import android.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstallSourceInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
+import android.os.FileUtils;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+import android.util.AtomicFile;
+import android.util.SparseSetArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.FieldSetter;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link com.android.server.pm.BackgroundInstallControlService}
+ */
+@Presubmit
+public final class BackgroundInstallControlServiceTest {
+    private static final String INSTALLER_NAME_1 = "installer1";
+    private static final String INSTALLER_NAME_2 = "installer2";
+    private static final String PACKAGE_NAME_1 = "package1";
+    private static final String PACKAGE_NAME_2 = "package2";
+    private static final String PACKAGE_NAME_3 = "package3";
+    private static final int USER_ID_1 = 1;
+    private static final int USER_ID_2 = 2;
+    private static final long USAGE_EVENT_TIMESTAMP_1 = 1000;
+    private static final long USAGE_EVENT_TIMESTAMP_2 = 2000;
+    private static final long USAGE_EVENT_TIMESTAMP_3 = 3000;
+    private static final long PACKAGE_ADD_TIMESTAMP_1 = 1500;
+
+    private BackgroundInstallControlService mBackgroundInstallControlService;
+    private PackageManagerInternal.PackageListObserver mPackageListObserver;
+    private UsageEventListener mUsageEventListener;
+    private TestLooper mTestLooper;
+    private Looper mLooper;
+    private File mFile;
+
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private IPackageManager mIPackageManager;
+    @Mock
+    private PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    private UsageStatsManagerInternal mUsageStatsManagerInternal;
+    @Mock
+    private PermissionManagerServiceInternal mPermissionManager;
+    @Captor
+    private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
+    @Captor
+    private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mTestLooper = new TestLooper();
+        mLooper = mTestLooper.getLooper();
+        mFile = new File(
+                InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(),
+                "test");
+        mBackgroundInstallControlService = new BackgroundInstallControlService(
+                new MockInjector(mContext));
+
+        verify(mUsageStatsManagerInternal).registerListener(mUsageEventListenerCaptor.capture());
+        mUsageEventListener = mUsageEventListenerCaptor.getValue();
+
+        mBackgroundInstallControlService.onStart(true);
+        verify(mPackageManagerInternal).getPackageList(mPackageListObserverCaptor.capture());
+        mPackageListObserver = mPackageListObserverCaptor.getValue();
+    }
+
+    @After
+    public void tearDown() {
+        FileUtils.deleteContentsAndDir(mFile);
+    }
+
+    @Test
+    public void testInitBackgroundInstalledPackages_empty() {
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        mBackgroundInstallControlService.initBackgroundInstalledPackages();
+        assertNotNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        assertEquals(0,
+                mBackgroundInstallControlService.getBackgroundInstalledPackages().size());
+    }
+
+    @Test
+    public void testInitBackgroundInstalledPackages_one() {
+        AtomicFile atomicFile = new AtomicFile(mFile);
+        FileOutputStream fileOutputStream;
+        try {
+            fileOutputStream = atomicFile.startWrite();
+        } catch (IOException e) {
+            fail("Failed to start write to states protobuf." + e);
+            return;
+        }
+
+        // Write test data to the file on the disk.
+        try {
+            ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
+            long token = protoOutputStream.start(
+                    BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+            protoOutputStream.write(
+                    BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
+            protoOutputStream.write(
+                    BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
+            protoOutputStream.end(token);
+            protoOutputStream.flush();
+            atomicFile.finishWrite(fileOutputStream);
+        } catch (Exception e) {
+            fail("Failed to finish write to states protobuf. " + e);
+            atomicFile.failWrite(fileOutputStream);
+        }
+
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        mBackgroundInstallControlService.initBackgroundInstalledPackages();
+        var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+        assertNotNull(packages);
+        assertEquals(1, packages.size());
+        assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+    }
+
+    @Test
+    public void testInitBackgroundInstalledPackages_two() {
+        AtomicFile atomicFile = new AtomicFile(mFile);
+        FileOutputStream fileOutputStream;
+        try {
+            fileOutputStream = atomicFile.startWrite();
+        } catch (IOException e) {
+            fail("Failed to start write to states protobuf." + e);
+            return;
+        }
+
+        // Write test data to the file on the disk.
+        try {
+            ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
+
+            long token = protoOutputStream.start(
+                    BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+            protoOutputStream.write(
+                    BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
+            protoOutputStream.write(
+                    BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
+            protoOutputStream.end(token);
+
+            token = protoOutputStream.start(
+                    BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+            protoOutputStream.write(
+                    BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2);
+            protoOutputStream.write(
+                    BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1);
+            protoOutputStream.end(token);
+
+            protoOutputStream.flush();
+            atomicFile.finishWrite(fileOutputStream);
+        } catch (Exception e) {
+            fail("Failed to finish write to states protobuf. " + e);
+            atomicFile.failWrite(fileOutputStream);
+        }
+
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        mBackgroundInstallControlService.initBackgroundInstalledPackages();
+        var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+        assertNotNull(packages);
+        assertEquals(2, packages.size());
+        assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+        assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+    }
+
+    @Test
+    public void testWriteBackgroundInstalledPackagesToDisk_empty() {
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        mBackgroundInstallControlService.initBackgroundInstalledPackages();
+        var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+        assertNotNull(packages);
+        mBackgroundInstallControlService.writeBackgroundInstalledPackagesToDisk();
+
+        // Read the file on the disk to verify
+        var packagesInDisk = new SparseSetArray<>();
+        AtomicFile atomicFile = new AtomicFile(mFile);
+        try (FileInputStream fileInputStream  = atomicFile.openRead()) {
+            ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
+
+            while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                if (protoInputStream.getFieldNumber()
+                        != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
+                    continue;
+                }
+                long token = protoInputStream.start(
+                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                String packageName = null;
+                int userId = UserHandle.USER_NULL;
+                while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    switch (protoInputStream.getFieldNumber()) {
+                        case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
+                            packageName = protoInputStream.readString(
+                                    BackgroundInstalledPackageProto.PACKAGE_NAME);
+                            break;
+                        case (int) BackgroundInstalledPackageProto.USER_ID:
+                            userId = protoInputStream.readInt(
+                                    BackgroundInstalledPackageProto.USER_ID) - 1;
+                            break;
+                        default:
+                            fail("Undefined field in proto: "
+                                    + protoInputStream.getFieldNumber());
+                    }
+                }
+                protoInputStream.end(token);
+                if (packageName != null && userId != UserHandle.USER_NULL) {
+                    packagesInDisk.add(userId, packageName);
+                } else {
+                    fail("Fails to get packageName or UserId from proto file");
+                }
+            }
+        } catch (IOException e) {
+            fail("Error reading state from the disk. " + e);
+        }
+
+        assertEquals(0, packagesInDisk.size());
+        assertEquals(packages.size(), packagesInDisk.size());
+    }
+
+    @Test
+    public void testWriteBackgroundInstalledPackagesToDisk_one() {
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        mBackgroundInstallControlService.initBackgroundInstalledPackages();
+        var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+        assertNotNull(packages);
+
+        packages.add(USER_ID_1, PACKAGE_NAME_1);
+        mBackgroundInstallControlService.writeBackgroundInstalledPackagesToDisk();
+
+        // Read the file on the disk to verify
+        var packagesInDisk = new SparseSetArray<>();
+        AtomicFile atomicFile = new AtomicFile(mFile);
+        try (FileInputStream fileInputStream  = atomicFile.openRead()) {
+            ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
+
+            while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                if (protoInputStream.getFieldNumber()
+                        != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
+                    continue;
+                }
+                long token = protoInputStream.start(
+                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                String packageName = null;
+                int userId = UserHandle.USER_NULL;
+                while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    switch (protoInputStream.getFieldNumber()) {
+                        case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
+                            packageName = protoInputStream.readString(
+                                    BackgroundInstalledPackageProto.PACKAGE_NAME);
+                            break;
+                        case (int) BackgroundInstalledPackageProto.USER_ID:
+                            userId = protoInputStream.readInt(
+                                    BackgroundInstalledPackageProto.USER_ID) - 1;
+                            break;
+                        default:
+                            fail("Undefined field in proto: "
+                                    + protoInputStream.getFieldNumber());
+                    }
+                }
+                protoInputStream.end(token);
+                if (packageName != null && userId != UserHandle.USER_NULL) {
+                    packagesInDisk.add(userId, packageName);
+                } else {
+                    fail("Fails to get packageName or UserId from proto file");
+                }
+            }
+        } catch (IOException e) {
+            fail("Error reading state from the disk. " + e);
+        }
+
+        assertEquals(1, packagesInDisk.size());
+        assertEquals(packages.size(), packagesInDisk.size());
+        assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+    }
+
+    @Test
+    public void testWriteBackgroundInstalledPackagesToDisk_two() {
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        mBackgroundInstallControlService.initBackgroundInstalledPackages();
+        var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+        assertNotNull(packages);
+
+        packages.add(USER_ID_1, PACKAGE_NAME_1);
+        packages.add(USER_ID_2, PACKAGE_NAME_2);
+        mBackgroundInstallControlService.writeBackgroundInstalledPackagesToDisk();
+
+        // Read the file on the disk to verify
+        var packagesInDisk = new SparseSetArray<>();
+        AtomicFile atomicFile = new AtomicFile(mFile);
+        try (FileInputStream fileInputStream  = atomicFile.openRead()) {
+            ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
+
+            while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                if (protoInputStream.getFieldNumber()
+                        != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
+                    continue;
+                }
+                long token = protoInputStream.start(
+                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                String packageName = null;
+                int userId = UserHandle.USER_NULL;
+                while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    switch (protoInputStream.getFieldNumber()) {
+                        case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
+                            packageName = protoInputStream.readString(
+                                    BackgroundInstalledPackageProto.PACKAGE_NAME);
+                            break;
+                        case (int) BackgroundInstalledPackageProto.USER_ID:
+                            userId = protoInputStream.readInt(
+                                    BackgroundInstalledPackageProto.USER_ID) - 1;
+                            break;
+                        default:
+                            fail("Undefined field in proto: "
+                                    + protoInputStream.getFieldNumber());
+                    }
+                }
+                protoInputStream.end(token);
+                if (packageName != null && userId != UserHandle.USER_NULL) {
+                    packagesInDisk.add(userId, packageName);
+                } else {
+                    fail("Fails to get packageName or UserId from proto file");
+                }
+            }
+        } catch (IOException e) {
+            fail("Error reading state from the disk. " + e);
+        }
+
+        assertEquals(2, packagesInDisk.size());
+        assertEquals(packages.size(), packagesInDisk.size());
+        assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+        assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+    }
+
+    @Test
+    public void testHandleUsageEvent_permissionDenied() {
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, 0);
+        mTestLooper.dispatchAll();
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+    }
+
+    @Test
+    public void testHandleUsageEvent_permissionGranted() {
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, 0);
+        mTestLooper.dispatchAll();
+        assertEquals(1,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+    }
+
+    @Test
+    public void testHandleUsageEvent_ignoredEvent() {
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt());
+        generateUsageEvent(UsageEvents.Event.USER_INTERACTION,
+                USER_ID_1, INSTALLER_NAME_1, 0);
+        mTestLooper.dispatchAll();
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+    }
+
+    @Test
+    public void testHandleUsageEvent_firstActivityResumedHalfTimeFrame() {
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+        mTestLooper.dispatchAll();
+
+        var installerForegroundTimeFrames =
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames();
+        assertEquals(1, installerForegroundTimeFrames.numMaps());
+        assertEquals(1, installerForegroundTimeFrames.numElementsForKey(USER_ID_1));
+
+        var foregroundTimeFrames = installerForegroundTimeFrames.get(USER_ID_1, INSTALLER_NAME_1);
+        assertEquals(1, foregroundTimeFrames.size());
+
+        var foregroundTimeFrame = foregroundTimeFrames.first();
+        assertEquals(USAGE_EVENT_TIMESTAMP_1, foregroundTimeFrame.startTimeStampMillis);
+        assertFalse(foregroundTimeFrame.isDone());
+    }
+
+    @Test
+    public void testHandleUsageEvent_firstActivityResumedOneTimeFrame() {
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        mTestLooper.dispatchAll();
+
+        var installerForegroundTimeFrames =
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames();
+        assertEquals(1, installerForegroundTimeFrames.numMaps());
+        assertEquals(1, installerForegroundTimeFrames.numElementsForKey(USER_ID_1));
+
+        var foregroundTimeFrames = installerForegroundTimeFrames.get(USER_ID_1, INSTALLER_NAME_1);
+        assertEquals(1, foregroundTimeFrames.size());
+
+        var foregroundTimeFrame = foregroundTimeFrames.first();
+        assertEquals(USAGE_EVENT_TIMESTAMP_1, foregroundTimeFrame.startTimeStampMillis);
+        assertTrue(foregroundTimeFrame.isDone());
+    }
+
+    @Test
+    public void testHandleUsageEvent_firstActivityResumedOneAndHalfTimeFrame() {
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+        mTestLooper.dispatchAll();
+
+        var installerForegroundTimeFrames =
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames();
+        assertEquals(1, installerForegroundTimeFrames.numMaps());
+        assertEquals(1, installerForegroundTimeFrames.numElementsForKey(USER_ID_1));
+
+        var foregroundTimeFrames = installerForegroundTimeFrames.get(USER_ID_1, INSTALLER_NAME_1);
+        assertEquals(2, foregroundTimeFrames.size());
+
+        var foregroundTimeFrame1 = foregroundTimeFrames.first();
+        assertEquals(USAGE_EVENT_TIMESTAMP_1, foregroundTimeFrame1.startTimeStampMillis);
+        assertTrue(foregroundTimeFrame1.isDone());
+
+        var foregroundTimeFrame2 = foregroundTimeFrames.last();
+        assertEquals(USAGE_EVENT_TIMESTAMP_3, foregroundTimeFrame2.startTimeStampMillis);
+        assertFalse(foregroundTimeFrame2.isDone());
+    }
+
+    @Test
+    public void testHandleUsageEvent_firstNoneActivityResumed() {
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt());
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+        mTestLooper.dispatchAll();
+
+        var installerForegroundTimeFrames =
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames();
+        assertEquals(1, installerForegroundTimeFrames.numMaps());
+        assertEquals(1, installerForegroundTimeFrames.numElementsForKey(USER_ID_1));
+
+        var foregroundTimeFrames = installerForegroundTimeFrames.get(USER_ID_1, INSTALLER_NAME_1);
+        assertEquals(0, foregroundTimeFrames.size());
+    }
+
+    @Test
+    public void testHandleUsageEvent_packageAddedNoUsageEvent() throws
+            RemoteException, NoSuchFieldException {
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+                /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
+                /* originatingPackageName = */ null,
+                /* installingPackageName = */ INSTALLER_NAME_1);
+        assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+        when(mIPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+        ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+        when(mIPackageManager.getApplicationInfo(
+                eq(PACKAGE_NAME_1),
+                eq(0L),
+                anyInt())
+        ).thenReturn(appInfo);
+
+        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(appInfo,
+                ApplicationInfo.class.getDeclaredField("createTimestamp"),
+                createTimestamp);
+
+        int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+        assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+        mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+        mTestLooper.dispatchAll();
+
+        var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+        assertNotNull(packages);
+        assertEquals(1, packages.size());
+        assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+    }
+
+    @Test
+    public void testHandleUsageEvent_packageAddedInsideTimeFrame() throws
+            RemoteException, NoSuchFieldException {
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+                /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
+                /* originatingPackageName = */ null,
+                /* installingPackageName = */ INSTALLER_NAME_1);
+        assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+        when(mIPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+        ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+        when(mIPackageManager.getApplicationInfo(
+                eq(PACKAGE_NAME_1),
+                eq(0L),
+                anyInt())
+        ).thenReturn(appInfo);
+
+        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(appInfo,
+                ApplicationInfo.class.getDeclaredField("createTimestamp"),
+                createTimestamp);
+
+        int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+        assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+        // The following 2 usage events generation is the only difference from the
+        // testHandleUsageEvent_packageAddedNoUsageEvent test.
+        // The 2 usage events make the package adding inside a time frame.
+        // So it's not a background install. Thus, it's null for the return of
+        // mBackgroundInstallControlService.getBackgroundInstalledPackages()
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+
+        mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+        mTestLooper.dispatchAll();
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+    }
+
+    @Test
+    public void testHandleUsageEvent_packageAddedOutsideTimeFrame1() throws
+            RemoteException, NoSuchFieldException {
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+                /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
+                /* originatingPackageName = */ null,
+                /* installingPackageName = */ INSTALLER_NAME_1);
+        assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+        when(mIPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+        ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+        when(mIPackageManager.getApplicationInfo(
+                eq(PACKAGE_NAME_1),
+                eq(0L),
+                anyInt())
+        ).thenReturn(appInfo);
+
+        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(appInfo,
+                ApplicationInfo.class.getDeclaredField("createTimestamp"),
+                createTimestamp);
+
+        int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+        assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+        // The following 2 usage events generation is the only difference from the
+        // testHandleUsageEvent_packageAddedNoUsageEvent test.
+        // The 2 usage events make the package adding outside a time frame.
+        // Compared to testHandleUsageEvent_packageAddedInsideTimeFrame,
+        // it's a background install. Thus, it's not null for the return of
+        // mBackgroundInstallControlService.getBackgroundInstalledPackages()
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+
+        mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+        mTestLooper.dispatchAll();
+
+        var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+        assertNotNull(packages);
+        assertEquals(1, packages.size());
+        assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+    }
+    @Test
+    public void testHandleUsageEvent_packageAddedOutsideTimeFrame2() throws
+            RemoteException, NoSuchFieldException {
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+                /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
+                /* originatingPackageName = */ null,
+                /* installingPackageName = */ INSTALLER_NAME_1);
+        assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+        when(mIPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+        ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+        when(mIPackageManager.getApplicationInfo(
+                eq(PACKAGE_NAME_1),
+                eq(0L),
+                anyInt())
+        ).thenReturn(appInfo);
+
+        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(appInfo,
+                ApplicationInfo.class.getDeclaredField("createTimestamp"),
+                createTimestamp);
+
+        int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+        assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+        // The following 2 usage events generation is the only difference from the
+        // testHandleUsageEvent_packageAddedNoUsageEvent test.
+        // These 2 usage events are triggered by INSTALLER_NAME_2.
+        // The 2 usage events make the package adding outside a time frame.
+        // Compared to testHandleUsageEvent_packageAddedInsideTimeFrame,
+        // it's a background install. Thus, it's not null for the return of
+        // mBackgroundInstallControlService.getBackgroundInstalledPackages()
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3);
+
+        mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+        mTestLooper.dispatchAll();
+
+        var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+        assertNotNull(packages);
+        assertEquals(1, packages.size());
+        assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+    }
+
+    @Test
+    public void testPackageRemoved() {
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        mBackgroundInstallControlService.initBackgroundInstalledPackages();
+        var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+        assertNotNull(packages);
+
+        packages.add(USER_ID_1, PACKAGE_NAME_1);
+        packages.add(USER_ID_2, PACKAGE_NAME_2);
+
+        assertEquals(2, packages.size());
+        assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+        assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+
+        int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+        assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+        mPackageListObserver.onPackageRemoved(PACKAGE_NAME_1, uid);
+        mTestLooper.dispatchAll();
+
+        assertEquals(1, packages.size());
+        assertFalse(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+        assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+    }
+
+    @Test
+    public void testGetBackgroundInstalledPackages() throws RemoteException {
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        mBackgroundInstallControlService.initBackgroundInstalledPackages();
+        var bgPackages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+        assertNotNull(bgPackages);
+
+        bgPackages.add(USER_ID_1, PACKAGE_NAME_1);
+        bgPackages.add(USER_ID_2, PACKAGE_NAME_2);
+
+        assertEquals(2, bgPackages.size());
+        assertTrue(bgPackages.contains(USER_ID_1, PACKAGE_NAME_1));
+        assertTrue(bgPackages.contains(USER_ID_2, PACKAGE_NAME_2));
+
+        List<PackageInfo> packages = new ArrayList<>();
+        var packageInfo1 = makePackageInfo(PACKAGE_NAME_1);
+        packages.add(packageInfo1);
+        var packageInfo2 = makePackageInfo(PACKAGE_NAME_2);
+        packages.add(packageInfo2);
+        var packageInfo3 = makePackageInfo(PACKAGE_NAME_3);
+        packages.add(packageInfo3);
+        doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages(
+                anyLong(), anyInt());
+
+        var resultPackages =
+                mBackgroundInstallControlService.getBackgroundInstalledPackages(0L, USER_ID_1);
+        assertEquals(1, resultPackages.getList().size());
+        assertTrue(resultPackages.getList().contains(packageInfo1));
+        assertFalse(resultPackages.getList().contains(packageInfo2));
+        assertFalse(resultPackages.getList().contains(packageInfo3));
+    }
+
+    /**
+     * Mock a usage event occurring.
+     *
+     * @param usageEventId id of a usage event
+     * @param userId user id of a usage event
+     * @param pkgName package name of a usage event
+     * @param timestamp timestamp of a usage event
+     */
+    private void generateUsageEvent(int usageEventId,
+            int userId,
+            String pkgName,
+            long timestamp) {
+        Event event = new Event(usageEventId, timestamp);
+        event.mPackage = pkgName;
+        mUsageEventListener.onUsageEvent(userId, event);
+    }
+
+    private PackageInfo makePackageInfo(String packageName) {
+        PackageInfo pkg = new PackageInfo();
+        pkg.packageName = packageName;
+        pkg.applicationInfo = new ApplicationInfo();
+        return pkg;
+    }
+
+    private class MockInjector implements BackgroundInstallControlService.Injector {
+        private final Context mContext;
+
+        MockInjector(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public Context getContext() {
+            return mContext;
+        }
+
+        @Override
+        public IPackageManager getIPackageManager() {
+            return mIPackageManager;
+        }
+
+        @Override
+        public PackageManagerInternal getPackageManagerInternal() {
+            return mPackageManagerInternal;
+        }
+
+        @Override
+        public UsageStatsManagerInternal getUsageStatsManagerInternal() {
+            return mUsageStatsManagerInternal;
+        }
+
+        @Override
+        public PermissionManagerServiceInternal getPermissionManager() {
+            return mPermissionManager;
+        }
+
+        @Override
+        public Looper getLooper() {
+            return mLooper;
+        }
+
+        @Override
+        public File getDiskFile() {
+            return mFile;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
index b034b0d..fe31b9c 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
@@ -257,7 +257,6 @@
                 .build();
 
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ false,
                 /* useProximitySensor= */ false,
                 /* boostScreenBrightness= */ false,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -273,7 +272,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(false);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(false);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(false);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -297,7 +295,6 @@
         mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_DOZE);
 
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -313,7 +310,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_ON);
@@ -336,7 +332,6 @@
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
 
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -352,7 +347,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -374,7 +368,6 @@
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -390,7 +383,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -412,7 +404,6 @@
         mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -428,7 +419,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -451,7 +441,6 @@
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
         mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_SCREEN_BRIGHT);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -467,7 +456,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -488,7 +476,6 @@
                 .build();
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -504,7 +491,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -526,7 +512,6 @@
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         mPowerGroup.setUserActivitySummaryLocked(USER_ACTIVITY_SCREEN_BRIGHT);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -542,7 +527,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -563,7 +547,6 @@
                 .build();
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -579,7 +562,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 22e44f8..71c8c1d 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -93,43 +93,44 @@
         }
 
         final BatteryStatsHistoryIterator iterator =
-                mBatteryStats.createBatteryStatsHistoryIterator();
+                mBatteryStats.iterateBatteryStatsHistory();
 
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        BatteryStats.HistoryItem item;
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_RESET, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 1_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 1_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 2_400_000, 80, 2_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 2_400_000, 80, 2_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE,
                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START,
                 "foo", APP_UID, 2_400_000, 80, 3_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE,
                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
                 "foo", APP_UID, 2_400_000, 80, 3_001_000);
 
-        assertThat(iterator.next(item)).isFalse();
+        assertThat(iterator.hasNext()).isFalse();
+        assertThat(iterator.next()).isNull();
     }
 
     // Test history that spans multiple buffers and uses more than 32k different strings.
@@ -163,21 +164,21 @@
         }
 
         final BatteryStatsHistoryIterator iterator =
-                mBatteryStats.createBatteryStatsHistoryIterator();
+                mBatteryStats.iterateBatteryStatsHistory();
 
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
-        assertThat(iterator.next(item)).isTrue();
+        BatteryStats.HistoryItem item;
+        assertThat(item = iterator.next()).isNotNull();
         assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_RESET);
         assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
         assertThat(item.eventTag).isNull();
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_UPDATE);
         assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
         assertThat(item.eventTag).isNull();
         assertThat(item.time).isEqualTo(1_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_UPDATE);
         assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
         assertThat(item.eventTag).isNull();
@@ -186,7 +187,7 @@
         for (int i = 0; i < eventCount; i++) {
             String name = "a" + (i % 10);
             do {
-                assertThat(iterator.next(item)).isTrue();
+                assertThat(item = iterator.next()).isNotNull();
                 // Skip a blank event inserted at the start of every buffer
             } while (item.cmd != BatteryStats.HistoryItem.CMD_UPDATE
                     || item.eventCode == BatteryStats.HistoryItem.EVENT_NONE);
@@ -196,7 +197,7 @@
             assertThat(item.eventTag.string).isEqualTo(name);
 
             do {
-                assertThat(iterator.next(item)).isTrue();
+                assertThat(item = iterator.next()).isNotNull();
             } while (item.cmd != BatteryStats.HistoryItem.CMD_UPDATE
                     || item.eventCode == BatteryStats.HistoryItem.EVENT_NONE);
 
@@ -205,7 +206,8 @@
             assertThat(item.eventTag.string).isEqualTo(name);
         }
 
-        assertThat(iterator.next(item)).isFalse();
+        assertThat(iterator.hasNext()).isFalse();
+        assertThat(iterator.next()).isNull();
     }
 
     private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode,
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 3f5d331..22a7e8d 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -309,10 +309,10 @@
         mHistory.recordMeasuredEnergyDetails(200, 200, details);
 
         BatteryStatsHistoryIterator iterator = mHistory.iterate();
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
-        assertThat(iterator.next(item)).isTrue(); // First item contains current time only
+        BatteryStats.HistoryItem item;
+        assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
 
         String dump = toString(item, /* checkin */ false);
         assertThat(dump).contains("+200ms");
@@ -344,9 +344,9 @@
 
         BatteryStatsHistoryIterator iterator = mHistory.iterate();
         BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
-        assertThat(iterator.next(item)).isTrue(); // First item contains current time only
+        assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
 
         String dump = toString(item, /* checkin */ false);
         assertThat(dump).contains("+200ms");
@@ -361,7 +361,7 @@
         assertThat(checkin).contains("XB,3,2,HIGH");
         assertThat(checkin).contains("XC,10123,100,200,300");
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
 
         dump = toString(item, /* checkin */ false);
         assertThat(dump).contains("+300ms");
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index 5b51868..773a2dc 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -83,6 +83,7 @@
  * Run: adb shell am instrument -e class BatteryStatsNoteTest -w \
  *      com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
  */
+@SuppressWarnings("GuardedBy")
 public class BatteryStatsNoteTest extends TestCase {
     private static final String TAG = BatteryStatsNoteTest.class.getSimpleName();
 
@@ -271,11 +272,11 @@
         clocks.realtime = clocks.uptime = 220;
         bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
 
-        final BatteryStatsHistoryIterator iterator =  bi.createBatteryStatsHistoryIterator();
+        final BatteryStatsHistoryIterator iterator =  bi.iterateBatteryStatsHistory();
 
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        BatteryStats.HistoryItem item;
 
-        while (iterator.next(item)) {
+        while ((item = iterator.next()) != null) {
             if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_START) break;
         }
         assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_START);
@@ -283,7 +284,7 @@
         assertThat(item.eventTag.string).isEqualTo(historyName);
         assertThat(item.eventTag.uid).isEqualTo(UID);
 
-        while (iterator.next(item)) {
+        while ((item = iterator.next()) != null) {
             if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH) break;
         }
         assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH);
@@ -327,11 +328,11 @@
         clocks.realtime = clocks.uptime = 220;
         bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
 
-        final BatteryStatsHistoryIterator iterator = bi.createBatteryStatsHistoryIterator();
+        final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
 
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        BatteryStats.HistoryItem item;
 
-        while (iterator.next(item)) {
+        while ((item = iterator.next()) != null) {
             if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_START) break;
         }
         assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_START);
@@ -339,7 +340,7 @@
         assertThat(item.eventTag.string).isEqualTo(historyName);
         assertThat(item.eventTag.uid).isEqualTo(UID);
 
-        while (iterator.next(item)) {
+        while ((item = iterator.next()) != null) {
             if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH) break;
         }
         assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH);
@@ -941,26 +942,27 @@
         clocks.realtime = clocks.uptime = 5000;
         bi.noteAlarmFinishLocked("foo", null, UID);
 
-        HistoryItem item = new HistoryItem();
-        assertTrue(bi.startIteratingHistoryLocked());
+        BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+        HistoryItem item;
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
         assertEquals("foo", item.eventTag.string);
         assertEquals(UID, item.eventTag.uid);
 
         // TODO(narayan): Figure out why this event is written to the history buffer. See
         // test below where it is being interspersed between multiple START events too.
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_NONE, item.eventCode);
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
         assertTrue(item.isDeltaData());
         assertEquals("foo", item.eventTag.string);
         assertEquals(UID, item.eventTag.uid);
 
-        assertFalse(bi.getNextHistoryLocked(item));
+        assertThat(iterator.hasNext()).isFalse();
+        assertThat(iterator.next()).isNull();
     }
 
     @SmallTest
@@ -980,28 +982,28 @@
         clocks.realtime = clocks.uptime = 5000;
         bi.noteAlarmFinishLocked("foo", ws, UID);
 
-        HistoryItem item = new HistoryItem();
-        assertTrue(bi.startIteratingHistoryLocked());
+        BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+        HistoryItem item;
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
         assertEquals("foo", item.eventTag.string);
         assertEquals(100, item.eventTag.uid);
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_NONE, item.eventCode);
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
         assertEquals("foo", item.eventTag.string);
         assertEquals(500, item.eventTag.uid);
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
         assertEquals("foo", item.eventTag.string);
         assertEquals(100, item.eventTag.uid);
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
         assertEquals("foo", item.eventTag.string);
         assertEquals(500, item.eventTag.uid);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 9d46e11..968609b 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -219,36 +219,37 @@
 
         final BatteryStatsHistoryIterator iterator =
                 unparceled.iterateBatteryStatsHistory();
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        BatteryStats.HistoryItem item;
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_RESET, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 1_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 1_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 2_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE,
                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START,
                 "foo", APP_UID, 3_600_000, 90, 3_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE,
                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
                 "foo", APP_UID, 3_600_000, 90, 3_001_000);
 
-        assertThat(iterator.next(item)).isFalse();
+        assertThat(iterator.hasNext()).isFalse();
+        assertThat(iterator.next()).isNull();
     }
 
     @Test
@@ -304,16 +305,16 @@
                 BatteryUsageStats.class);
 
         BatteryStatsHistoryIterator iterator = unparceled.iterateBatteryStatsHistory();
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        BatteryStats.HistoryItem item;
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_RESET);
 
         int expectedUid = 1;
-        while (iterator.next(item)) {
+        while ((item = iterator.next()) != null) {
             while (item.cmd != BatteryStats.HistoryItem.CMD_UPDATE
                     || item.eventCode == BatteryStats.HistoryItem.EVENT_NONE) {
-                assertThat(iterator.next(item)).isTrue();
+                assertThat(item = iterator.next()).isNotNull();
             }
             int uid = item.eventTag.uid;
             assertThat(uid).isEqualTo(expectedUid++);
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index 92c7871..16c213c 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -405,12 +405,11 @@
 
         mSysConfig.readApexPrivAppPermissions(parser, permissionFile, apexDir.toPath());
 
-        assertThat(mSysConfig.getApexPrivAppPermissions("com.android.my_module",
-                "com.android.apk_in_apex"))
-            .containsExactly("android.permission.FOO");
-        assertThat(mSysConfig.getApexPrivAppDenyPermissions("com.android.my_module",
-                "com.android.apk_in_apex"))
-            .containsExactly("android.permission.BAR");
+        ArrayMap<String, Boolean> permissions = mSysConfig.getPermissionAllowlist()
+                .getApexPrivilegedAppAllowlists().get("com.android.my_module")
+                .get("com.android.apk_in_apex");
+        assertThat(permissions)
+            .containsExactly("android.permission.FOO", true, "android.permission.BAR", false);
     }
 
     /**
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 107bbe1..593ee4a 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -45,6 +45,7 @@
     <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"/>
 
     <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
     <application android:debuggable="true"
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index d16e11b..43627f4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1637,6 +1637,8 @@
         final ActivityRecord target = new ActivityBuilder(mAtm).setAffinity(info.taskAffinity)
                 .build();
         final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, false);
+        spyOn(starter);
+        doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
         startActivityInner(starter, target, null /* source */, null /* options */,
                 null /* inTask */, null /* inTaskFragment */);
 
@@ -1661,6 +1663,8 @@
         final ActivityRecord target = new ActivityBuilder(mAtm).setRequiredDisplayCategory("auto")
                 .setAffinity(info.taskAffinity).build();
         final ActivityStarter starter = prepareStarter(0, false);
+        spyOn(starter);
+        doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
         startActivityInner(starter, target,  task.getBottomMostActivity(), null /* options */,
                 null /* inTask */, null /* inTaskFragment */);
 
@@ -1685,7 +1689,10 @@
         final ActivityRecord target = new ActivityBuilder(mAtm)
                 .setRequiredDisplayCategory(info.requiredDisplayCategory)
                 .setAffinity(info.taskAffinity).build();
+
         final ActivityStarter starter = prepareStarter(0, false);
+        spyOn(starter);
+        doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
         startActivityInner(starter, target,  task.getBottomMostActivity(), null /* options */,
                 null /* inTask */, null /* inTaskFragment */);
 
@@ -1706,6 +1713,8 @@
         inTask.inRecents = true;
 
         final ActivityStarter starter = prepareStarter(0, false);
+        spyOn(starter);
+        doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
         final ActivityRecord target = new ActivityBuilder(mAtm).build();
         startActivityInner(starter, target, null /* source */, null /* options */, inTask,
                 null /* inTaskFragment */);
@@ -1724,6 +1733,8 @@
         inTask.inRecents = true;
 
         final ActivityStarter starter = prepareStarter(0, false);
+        spyOn(starter);
+        doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
         final ActivityRecord target = new ActivityBuilder(mAtm).setRequiredDisplayCategory("auto")
                 .build();
         startActivityInner(starter, target, null /* source */, null /* options */, inTask,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 49fd1ab..92dd047 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -52,6 +52,8 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wm.ContentRecorder.MediaProjectionManagerWrapper;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -79,7 +81,7 @@
     private ContentRecordingSession mTaskSession;
     private static Point sSurfaceSize;
     private ContentRecorder mContentRecorder;
-    @Mock private ContentRecorder.MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
+    @Mock private MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
     private SurfaceControl mRecordedSurface;
     // Handle feature flag.
     private ConfigListener mConfigListener;
@@ -241,7 +243,7 @@
     }
 
     @Test
-    public void testOnTaskConfigurationChanged_resizesSurface() {
+    public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
 
@@ -256,6 +258,29 @@
     }
 
     @Test
+    public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
+        final int recordedWidth = 333;
+        final int recordedHeight = 999;
+        // WHEN a recording is ongoing.
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        // WHEN a configuration change arrives, and the recorded content is a different size.
+        mTask.setBounds(new Rect(0, 0, recordedWidth, recordedHeight));
+        mContentRecorder.onConfigurationChanged(mDefaultDisplay.getLastOrientation());
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        // THEN content in the captured DisplayArea is scaled to fit the surface size.
+        verify(mTransaction, atLeastOnce()).setMatrix(eq(mRecordedSurface), anyFloat(), eq(0f),
+                eq(0f),
+                anyFloat());
+        // THEN the resize callback is notified.
+        verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized(
+                recordedWidth, recordedHeight);
+    }
+
+    @Test
     public void testPauseRecording_pausesRecording() {
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
@@ -324,6 +349,9 @@
         int scaledWidth = Math.round((float) displayAreaBounds.width() / xScale);
         int xInset = (sSurfaceSize.x - scaledWidth) / 2;
         verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, xInset, 0);
+        // THEN the resize callback is notified.
+        verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized(
+                displayAreaBounds.width(), displayAreaBounds.height());
     }
 
     private static class RecordingTestToken extends Binder {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index ba68a25..2ce1d60 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -193,6 +193,30 @@
         assertEquals(result, START_ABORTED);
     }
 
+    @Test
+    public void testCanActivityBeLaunched_requiredDisplayCategory() {
+        ActivityStarter starter = new ActivityStarter(mock(ActivityStartController.class), mAtm,
+                mSupervisor, mock(ActivityStartInterceptor.class));
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(mSecondaryDisplay).build();
+        final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setTask(task).build();
+        final ActivityRecord disallowedRecord =
+                new ActivityBuilder(mAtm).setRequiredDisplayCategory("auto").build();
+
+        int result = starter.startActivityInner(
+                disallowedRecord,
+                sourceRecord,
+                /* voiceSession= */null,
+                /* voiceInteractor= */ null,
+                /* startFlags= */ 0,
+                /* options= */null,
+                /* inTask= */null,
+                /* inTaskFragment= */ null,
+                /* restrictedBgActivity= */false,
+                /* intentGrants= */null);
+
+        assertEquals(result, START_ABORTED);
+    }
+
     private class TestDisplayWindowPolicyController extends DisplayWindowPolicyController {
 
         public ComponentName DISALLOWED_ACTIVITY =
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 13ea99a..6877e4f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -166,6 +166,114 @@
     }
 
     @Test
+    public void testApplyStrategyToTranslucentActivities() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2000, 1000);
+        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.info.setMinAspectRatio(1.2f);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        // Translucent Activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+                .setMinAspectRatio(1.1f)
+                .setMaxAspectRatio(3f)
+                .build();
+        doReturn(false).when(translucentActivity).fillsParent();
+        mTask.addChild(translucentActivity);
+        // We check bounds
+        final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
+        final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
+        assertEquals(opaqueBounds, translucentRequestedBounds);
+        // We check orientation
+        final int translucentOrientation =
+                translucentActivity.getRequestedConfigurationOrientation();
+        assertEquals(ORIENTATION_PORTRAIT, translucentOrientation);
+        // We check aspect ratios
+        assertEquals(1.2f, translucentActivity.getMinAspectRatio(), 0.00001f);
+        assertEquals(1.5f, translucentActivity.getMaxAspectRatio(), 0.00001f);
+    }
+
+    @Test
+    public void testNotApplyStrategyToTranslucentActivitiesWithDifferentUid() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2000, 1000);
+        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.info.setMinAspectRatio(1.2f);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        // Translucent Activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+                .setMinAspectRatio(1.1f)
+                .setMaxAspectRatio(3f)
+                .build();
+        doReturn(false).when(translucentActivity).fillsParent();
+        mTask.addChild(translucentActivity);
+        // We check bounds
+        final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
+        final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
+        assertNotEquals(opaqueBounds, translucentRequestedBounds);
+    }
+
+    @Test
+    public void testApplyStrategyToMultipleTranslucentActivities() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2000, 1000);
+        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.info.setMinAspectRatio(1.2f);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        // Translucent Activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+                .setMinAspectRatio(1.1f)
+                .setMaxAspectRatio(3f)
+                .build();
+        doReturn(false).when(translucentActivity).fillsParent();
+        mTask.addChild(translucentActivity);
+        // We check bounds
+        final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
+        final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
+        assertEquals(opaqueBounds, translucentRequestedBounds);
+        // Launch another translucent activity
+        final ActivityRecord translucentActivity2 = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+                .build();
+        doReturn(false).when(translucentActivity2).fillsParent();
+        mTask.addChild(translucentActivity2);
+        // We check bounds
+        final Rect translucent2RequestedBounds = translucentActivity2.getRequestedOverrideBounds();
+        assertEquals(opaqueBounds, translucent2RequestedBounds);
+    }
+
+    @Test
+    public void testTranslucentActivitiesDontGoInSizeCompactMode() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2800, 1400);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+        // Rotate to put activity in size compat mode.
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+        assertTrue(mActivity.inSizeCompatMode());
+        // Rotate back
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_0);
+        assertFalse(mActivity.inSizeCompatMode());
+        // We launch a transparent activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .build();
+        doReturn(true).when(translucentActivity).fillsParent();
+        mTask.addChild(translucentActivity);
+        // It should not be in SCM
+        assertFalse(translucentActivity.inSizeCompatMode());
+        // We rotate again
+        rotateDisplay(translucentActivity.mDisplayContent, ROTATION_90);
+        assertFalse(translucentActivity.inSizeCompatMode());
+    }
+
+    @Test
     public void testRestartProcessIfVisible() {
         setUpDisplaySizeWithApp(1000, 2500);
         doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index 5e1fae0..7d9f29c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -20,6 +20,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 
 import android.platform.test.annotations.Presubmit;
 import android.view.SurfaceControl;
@@ -31,12 +34,15 @@
 import org.junit.Test;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @Presubmit
 public class SurfaceSyncGroupTest {
 
+    private final Executor mExecutor = Runnable::run;
+
     @Before
     public void setup() {
         SurfaceSyncGroup.setTransactionFactory(StubTransaction::new);
@@ -45,10 +51,11 @@
     @Test
     public void testSyncOne() throws InterruptedException {
         final CountDownLatch finishedLatch = new CountDownLatch(1);
-        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(transaction -> finishedLatch.countDown());
+        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+        syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
         SyncTarget syncTarget = new SyncTarget();
-        syncGroup.addToSync(syncTarget);
-        syncGroup.markSyncReady();
+        syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
+        syncGroup.onTransactionReady(null);
 
         syncTarget.onBufferReady();
 
@@ -59,15 +66,16 @@
     @Test
     public void testSyncMultiple() throws InterruptedException {
         final CountDownLatch finishedLatch = new CountDownLatch(1);
-        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(transaction -> finishedLatch.countDown());
+        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+        syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
         SyncTarget syncTarget3 = new SyncTarget();
 
-        syncGroup.addToSync(syncTarget1);
-        syncGroup.addToSync(syncTarget2);
-        syncGroup.addToSync(syncTarget3);
-        syncGroup.markSyncReady();
+        syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */);
+        syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */);
+        syncGroup.addToSync(syncTarget3, false /* parentSyncGroupMerge */);
+        syncGroup.onTransactionReady(null);
 
         syncTarget1.onBufferReady();
         assertNotEquals(0, finishedLatch.getCount());
@@ -83,35 +91,35 @@
 
     @Test
     public void testAddSyncWhenSyncComplete() {
-        final CountDownLatch finishedLatch = new CountDownLatch(1);
-        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(transaction -> finishedLatch.countDown());
+        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
 
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
 
-        assertTrue(syncGroup.addToSync(syncTarget1));
-        syncGroup.markSyncReady();
+        assertTrue(syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        syncGroup.onTransactionReady(null);
         // Adding to a sync that has been completed is also invalid since the sync id has been
         // cleared.
-        assertFalse(syncGroup.addToSync(syncTarget2));
+        assertFalse(syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
     }
 
     @Test
-    public void testMultiplesyncGroups() throws InterruptedException {
+    public void testMultipleSyncGroups() throws InterruptedException {
         final CountDownLatch finishedLatch1 = new CountDownLatch(1);
         final CountDownLatch finishedLatch2 = new CountDownLatch(1);
-        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(
-                transaction -> finishedLatch1.countDown());
-        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(
-                transaction -> finishedLatch2.countDown());
+        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+        syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+        syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
 
-        assertTrue(syncGroup1.addToSync(syncTarget1));
-        assertTrue(syncGroup2.addToSync(syncTarget2));
-        syncGroup1.markSyncReady();
-        syncGroup2.markSyncReady();
+        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        syncGroup1.onTransactionReady(null);
+        syncGroup2.onTransactionReady(null);
 
         syncTarget1.onBufferReady();
 
@@ -126,22 +134,23 @@
     }
 
     @Test
-    public void testMergeSync() throws InterruptedException {
+    public void testAddSyncGroup() throws InterruptedException {
         final CountDownLatch finishedLatch1 = new CountDownLatch(1);
         final CountDownLatch finishedLatch2 = new CountDownLatch(1);
-        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(
-                transaction -> finishedLatch1.countDown());
-        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(
-                transaction -> finishedLatch2.countDown());
+        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+        syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+        syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
 
-        assertTrue(syncGroup1.addToSync(syncTarget1));
-        assertTrue(syncGroup2.addToSync(syncTarget2));
-        syncGroup1.markSyncReady();
-        syncGroup2.merge(syncGroup1);
-        syncGroup2.markSyncReady();
+        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        syncGroup1.onTransactionReady(null);
+        syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
+        syncGroup2.onTransactionReady(null);
 
         // Finish syncTarget2 first to test that the syncGroup is not complete until the merged sync
         // is also done.
@@ -161,28 +170,29 @@
     }
 
     @Test
-    public void testMergeSyncAlreadyComplete() throws InterruptedException {
+    public void testAddSyncAlreadyComplete() throws InterruptedException {
         final CountDownLatch finishedLatch1 = new CountDownLatch(1);
         final CountDownLatch finishedLatch2 = new CountDownLatch(1);
-        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(
-                transaction -> finishedLatch1.countDown());
-        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(
-                transaction -> finishedLatch2.countDown());
+        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+        syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+        syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
 
-        assertTrue(syncGroup1.addToSync(syncTarget1));
-        assertTrue(syncGroup2.addToSync(syncTarget2));
-        syncGroup1.markSyncReady();
+        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        syncGroup1.onTransactionReady(null);
         syncTarget1.onBufferReady();
 
         // The first sync will still get a callback when it's sync requirements are done.
         finishedLatch1.await(5, TimeUnit.SECONDS);
         assertEquals(0, finishedLatch1.getCount());
 
-        syncGroup2.merge(syncGroup1);
-        syncGroup2.markSyncReady();
+        syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
+        syncGroup2.onTransactionReady(null);
         syncTarget2.onBufferReady();
 
         // Verify that the second sync will receive complete since the merged sync was already
@@ -191,18 +201,145 @@
         assertEquals(0, finishedLatch2.getCount());
     }
 
-    private static class SyncTarget implements SurfaceSyncGroup.SyncTarget {
-        private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
+    @Test
+    public void testAddSyncAlreadyInASync_NewSyncReadyFirst() throws InterruptedException {
+        final CountDownLatch finishedLatch1 = new CountDownLatch(1);
+        final CountDownLatch finishedLatch2 = new CountDownLatch(1);
+        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
 
-        @Override
-        public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
-                SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
-            mTransactionReadyCallback = transactionReadyCallback;
-        }
+        syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+        syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
+        SyncTarget syncTarget1 = new SyncTarget();
+        SyncTarget syncTarget2 = new SyncTarget();
+        SyncTarget syncTarget3 = new SyncTarget();
+
+        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup1.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+
+        // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
+        assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
+
+        syncGroup1.onTransactionReady(null);
+        syncGroup2.onTransactionReady(null);
+
+        // Make target1 and target3 ready, but not target2. SyncGroup2 should not be ready since
+        // SyncGroup2 also waits for all of SyncGroup1 to finish, which includes target2
+        syncTarget1.onBufferReady();
+        syncTarget3.onBufferReady();
+
+        // Neither SyncGroup will be ready.
+        finishedLatch1.await(1, TimeUnit.SECONDS);
+        finishedLatch2.await(1, TimeUnit.SECONDS);
+
+        assertEquals(1, finishedLatch1.getCount());
+        assertEquals(1, finishedLatch2.getCount());
+
+        syncTarget2.onBufferReady();
+
+        // Both sync groups should be ready after target2 completed.
+        finishedLatch1.await(5, TimeUnit.SECONDS);
+        finishedLatch2.await(5, TimeUnit.SECONDS);
+        assertEquals(0, finishedLatch1.getCount());
+        assertEquals(0, finishedLatch2.getCount());
+    }
+
+    @Test
+    public void testAddSyncAlreadyInASync_OldSyncFinishesFirst() throws InterruptedException {
+        final CountDownLatch finishedLatch1 = new CountDownLatch(1);
+        final CountDownLatch finishedLatch2 = new CountDownLatch(1);
+        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+        syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+        syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
+
+        SyncTarget syncTarget1 = new SyncTarget();
+        SyncTarget syncTarget2 = new SyncTarget();
+        SyncTarget syncTarget3 = new SyncTarget();
+
+        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup1.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        syncTarget2.onBufferReady();
+
+        // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
+        assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
+
+        syncGroup1.onTransactionReady(null);
+        syncGroup2.onTransactionReady(null);
+
+        syncTarget1.onBufferReady();
+
+        // Only SyncGroup1 will be ready, but SyncGroup2 still needs its own targets to be ready.
+        finishedLatch1.await(1, TimeUnit.SECONDS);
+        finishedLatch2.await(1, TimeUnit.SECONDS);
+
+        assertEquals(0, finishedLatch1.getCount());
+        assertEquals(1, finishedLatch2.getCount());
+
+        syncTarget3.onBufferReady();
+
+        // SyncGroup2 is finished after target3 completed.
+        finishedLatch2.await(1, TimeUnit.SECONDS);
+        assertEquals(0, finishedLatch2.getCount());
+    }
+
+    @Test
+    public void testParentSyncGroupMerge_true() {
+        // Temporarily set a new transaction factory so it will return the stub transaction for
+        // the sync group.
+        SurfaceControl.Transaction parentTransaction = spy(new StubTransaction());
+        SurfaceSyncGroup.setTransactionFactory(() -> parentTransaction);
+
+        final CountDownLatch finishedLatch = new CountDownLatch(1);
+        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+        syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
+
+        SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
+        SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
+
+        SyncTarget syncTarget = new SyncTarget();
+        assertTrue(syncGroup.addToSync(syncTarget, true /* parentSyncGroupMerge */));
+        syncTarget.onTransactionReady(null);
+
+        // When parentSyncGroupMerge is true, the transaction passed in merges the main SyncGroup
+        // transaction first because it knows the previous parentSyncGroup is older so it should
+        // be overwritten by anything newer.
+        verify(targetTransaction).merge(parentTransaction);
+        verify(parentTransaction).merge(targetTransaction);
+    }
+
+    @Test
+    public void testParentSyncGroupMerge_false() {
+        // Temporarily set a new transaction factory so it will return the stub transaction for
+        // the sync group.
+        SurfaceControl.Transaction parentTransaction = spy(new StubTransaction());
+        SurfaceSyncGroup.setTransactionFactory(() -> parentTransaction);
+
+        final CountDownLatch finishedLatch = new CountDownLatch(1);
+        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+        syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
+
+        SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
+        SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
+
+        SyncTarget syncTarget = new SyncTarget();
+        assertTrue(syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */));
+        syncTarget.onTransactionReady(null);
+
+        // When parentSyncGroupMerge is false, the transaction passed in should not merge
+        // the main SyncGroup since we don't need to change the transaction order
+        verify(targetTransaction, never()).merge(parentTransaction);
+        verify(parentTransaction).merge(targetTransaction);
+    }
+
+    private static class SyncTarget extends SurfaceSyncGroup {
         void onBufferReady() {
             SurfaceControl.Transaction t = new StubTransaction();
-            mTransactionReadyCallback.onTransactionReady(t);
+            onTransactionReady(t);
         }
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
index 2812264..84bd7160 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.media.permission.Identity;
+import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.SharedMemory;
@@ -74,12 +75,13 @@
 
     DspTrustedHotwordDetectorSession(
             @NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
-            @NonNull Object lock, @NonNull Context context,
+            @NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
             @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity,
             @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
-        super(remoteHotwordDetectionService, lock, context, callback, voiceInteractionServiceUid,
-                voiceInteractorIdentity, scheduledExecutorService, logging);
+        super(remoteHotwordDetectionService, lock, context, token, callback,
+                voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
+                logging);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -106,12 +108,14 @@
                     }
                     HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                             HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED,
+                            mVoiceInteractionServiceUid);
                     if (!mValidatingDspTrigger) {
                         Slog.i(TAG, "Ignoring #onDetected due to a process restart");
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK,
+                                mVoiceInteractionServiceUid);
                         return;
                     }
                     mValidatingDspTrigger = false;
@@ -122,7 +126,8 @@
                         Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
+                                mVoiceInteractionServiceUid);
                         externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
                         return;
                     }
@@ -157,12 +162,14 @@
                     }
                     HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                             HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED,
+                            mVoiceInteractionServiceUid);
                     if (!mValidatingDspTrigger) {
                         Slog.i(TAG, "Ignoring #onRejected due to a process restart");
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                                METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK);
+                                METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK,
+                                mVoiceInteractionServiceUid);
                         return;
                     }
                     mValidatingDspTrigger = false;
@@ -187,7 +194,8 @@
                         Slog.w(TAG, "Timed out on #detectFromDspSource");
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                                HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT);
+                                HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT,
+                                mVoiceInteractionServiceUid);
                         try {
                             externalCallback.onError(CALLBACK_DETECT_TIMEOUT);
                         } catch (RemoteException e) {
@@ -220,7 +228,8 @@
                 mCallback.onRejected(new HotwordRejectedResult.Builder().build());
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART);
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART,
+                        mVoiceInteractionServiceUid);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to call #rejected");
                 HotwordMetricsLogger.writeDetectorEvent(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 2a4d4a1..6a7a2f9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -49,11 +49,12 @@
 import android.provider.DeviceConfig;
 import android.service.voice.HotwordDetectionService;
 import android.service.voice.HotwordDetector;
-import android.service.voice.IHotwordDetectionService;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.ISandboxedDetectionService;
 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
 import android.speech.IRecognitionServiceManager;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.contentcapture.IContentCaptureManager;
 
 import com.android.internal.annotations.GuardedBy;
@@ -68,6 +69,7 @@
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 import java.util.function.Function;
 
 /**
@@ -113,18 +115,18 @@
     @GuardedBy("mLock")
     private boolean mDebugHotwordLogging = false;
 
+    /**
+     * For multiple detectors feature, we only support one AlwaysOnHotwordDetector and one
+     * SoftwareHotwordDetector at the same time. We use SparseArray with detector type as the key
+     * to record the detectors.
+     */
     @GuardedBy("mLock")
-    private final HotwordDetectorSession mHotwordDetectorSession;
+    private final SparseArray<HotwordDetectorSession> mHotwordDetectorSessions =
+            new SparseArray<>();
 
     HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
-            boolean bindInstantServiceAllowed, @Nullable PersistableBundle options,
-            @Nullable SharedMemory sharedMemory,
-            @NonNull IHotwordRecognitionStatusCallback callback, int detectorType) {
-        if (callback == null) {
-            Slog.w(TAG, "Callback is null while creating connection");
-            throw new IllegalArgumentException("Callback is null while creating connection");
-        }
+            boolean bindInstantServiceAllowed, int detectorType) {
         mLock = lock;
         mContext = context;
         mVoiceInteractionServiceUid = voiceInteractionServiceUid;
@@ -142,19 +144,6 @@
         mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
         mLastRestartInstant = Instant.now();
 
-        if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP) {
-            mHotwordDetectorSession = new DspTrustedHotwordDetectorSession(
-                    mRemoteHotwordDetectionService, mLock, mContext, callback,
-                    mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
-                    mScheduledExecutorService, mDebugHotwordLogging);
-        } else {
-            mHotwordDetectorSession = new SoftwareTrustedHotwordDetectorSession(
-                    mRemoteHotwordDetectionService, mLock, mContext, callback,
-                    mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
-                    mScheduledExecutorService, mDebugHotwordLogging);
-        }
-        mHotwordDetectorSession.initialize(options, sharedMemory);
-
         if (mReStartPeriodSeconds <= 0) {
             mCancellationTaskFuture = null;
         } else {
@@ -165,7 +154,8 @@
                 synchronized (mLock) {
                     restartProcessLocked();
                     HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
-                            HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE);
+                            HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE,
+                            mVoiceInteractionServiceUid);
                 }
             }, mReStartPeriodSeconds, mReStartPeriodSeconds, TimeUnit.SECONDS);
         }
@@ -200,7 +190,8 @@
             // conditions with audio reading in the service.
             restartProcessLocked();
             HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
-                    HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED);
+                    HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED,
+                    mVoiceInteractionServiceUid);
         }
     }
 
@@ -208,7 +199,10 @@
     void cancelLocked() {
         Slog.v(TAG, "cancelLocked");
         clearDebugHotwordLoggingTimeoutLocked();
-        mHotwordDetectorSession.destroyLocked();
+        runForEachHotwordDetectorSessionLocked((session) -> {
+            session.destroyLocked();
+        });
+        mHotwordDetectorSessions.clear();
         mDebugHotwordLogging = false;
         mRemoteHotwordDetectionService.unbind();
         LocalServices.getService(PermissionManagerServiceInternal.class)
@@ -218,7 +212,7 @@
         }
         mIdentity = null;
         if (mCancellationTaskFuture != null) {
-            mCancellationTaskFuture.cancel(/* may interrupt */ true);
+            mCancellationTaskFuture.cancel(/* mayInterruptIfRunning= */ true);
         }
         if (mAudioFlinger != null) {
             mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
@@ -226,57 +220,65 @@
     }
 
     @SuppressWarnings("GuardedBy")
-    void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) {
-        mHotwordDetectorSession.updateStateLocked(options, sharedMemory, mLastRestartInstant);
+    void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory,
+            @NonNull IBinder token) {
+        final HotwordDetectorSession session = getDetectorSessionByTokenLocked(token);
+        if (session == null) {
+            Slog.v(TAG, "Not found the detector by token");
+            return;
+        }
+        session.updateStateLocked(options, sharedMemory, mLastRestartInstant);
     }
 
     /**
      * This method is only used by SoftwareHotwordDetector.
      */
-    void startListeningFromMic(
+    void startListeningFromMicLocked(
             AudioFormat audioFormat,
             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
-            Slog.d(TAG, "startListeningFromMic");
+            Slog.d(TAG, "startListeningFromMicLocked");
         }
-        synchronized (mLock) {
-            if (!(mHotwordDetectorSession instanceof SoftwareTrustedHotwordDetectorSession)) {
-                Slog.d(TAG, "It is not a software detector");
-                return;
-            }
-            ((SoftwareTrustedHotwordDetectorSession) mHotwordDetectorSession)
-                    .startListeningFromMicLocked(audioFormat, callback);
+        // We only support one Dsp trusted hotword detector and one software hotword detector at
+        // the same time, so we can reuse original single software trusted hotword mechanism.
+        final SoftwareTrustedHotwordDetectorSession session =
+                getSoftwareTrustedHotwordDetectorSessionLocked();
+        if (session == null) {
+            return;
         }
+        session.startListeningFromMicLocked(audioFormat, callback);
     }
 
-    public void startListeningFromExternalSource(
+    public void startListeningFromExternalSourceLocked(
             ParcelFileDescriptor audioStream,
             AudioFormat audioFormat,
             @Nullable PersistableBundle options,
+            @NonNull IBinder token,
             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
-            Slog.d(TAG, "startListeningFromExternalSource");
+            Slog.d(TAG, "startListeningFromExternalSourceLocked");
         }
-        synchronized (mLock) {
-            mHotwordDetectorSession.startListeningFromExternalSourceLocked(audioStream, audioFormat,
-                    options, callback);
+        final HotwordDetectorSession session = getDetectorSessionByTokenLocked(token);
+        if (session == null) {
+            Slog.v(TAG, "Not found the detector by token");
+            return;
         }
+        session.startListeningFromExternalSourceLocked(audioStream, audioFormat, options, callback);
     }
 
     /**
      * This method is only used by SoftwareHotwordDetector.
      */
-    void stopListening() {
+    void stopListeningFromMicLocked() {
         if (DEBUG) {
-            Slog.d(TAG, "stopListening");
+            Slog.d(TAG, "stopListeningFromMicLocked");
         }
-        synchronized (mLock) {
-            if (!(mHotwordDetectorSession instanceof SoftwareTrustedHotwordDetectorSession)) {
-                Slog.d(TAG, "It is not a software detector");
-                return;
-            }
-            ((SoftwareTrustedHotwordDetectorSession) mHotwordDetectorSession).stopListeningLocked();
+        final SoftwareTrustedHotwordDetectorSession session =
+                getSoftwareTrustedHotwordDetectorSessionLocked();
+        if (session == null) {
+            return;
         }
+        session.stopListeningFromMicLocked();
     }
 
     void triggerHardwareRecognitionEventForTestLocked(
@@ -293,13 +295,16 @@
         if (DEBUG) {
             Slog.d(TAG, "detectFromDspSource");
         }
+        // We only support one Dsp trusted hotword detector and one software hotword detector at
+        // the same time, so we can reuse original single Dsp trusted hotword mechanism.
         synchronized (mLock) {
-            if (!(mHotwordDetectorSession instanceof DspTrustedHotwordDetectorSession)) {
-                Slog.d(TAG, "It is not a Dsp detector");
+            final DspTrustedHotwordDetectorSession session =
+                    getDspTrustedHotwordDetectorSessionLocked();
+            if (session == null || !session.isSameCallback(externalCallback)) {
+                Slog.v(TAG, "Not found the Dsp detector by callback");
                 return;
             }
-            ((DspTrustedHotwordDetectorSession) mHotwordDetectorSession).detectFromDspSourceLocked(
-                    recognitionEvent, externalCallback);
+            session.detectFromDspSourceLocked(recognitionEvent, externalCallback);
         }
     }
 
@@ -315,7 +320,9 @@
         Slog.v(TAG, "setDebugHotwordLoggingLocked: " + logging);
         clearDebugHotwordLoggingTimeoutLocked();
         mDebugHotwordLogging = logging;
-        mHotwordDetectorSession.setDebugHotwordLoggingLocked(logging);
+        runForEachHotwordDetectorSessionLocked((session) -> {
+            session.setDebugHotwordLoggingLocked(logging);
+        });
 
         if (logging) {
             // Reset mDebugHotwordLogging to false after one hour
@@ -323,7 +330,9 @@
                 Slog.v(TAG, "Timeout to reset mDebugHotwordLogging to false");
                 synchronized (mLock) {
                     mDebugHotwordLogging = false;
-                    mHotwordDetectorSession.setDebugHotwordLoggingLocked(false);
+                    runForEachHotwordDetectorSessionLocked((session) -> {
+                        session.setDebugHotwordLoggingLocked(false);
+                    });
                 }
             }, RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         }
@@ -331,7 +340,7 @@
 
     private void clearDebugHotwordLoggingTimeoutLocked() {
         if (mDebugHotwordLoggingTimeoutFuture != null) {
-            mDebugHotwordLoggingTimeoutFuture.cancel(/* mayInterruptIfRunning= */true);
+            mDebugHotwordLoggingTimeoutFuture.cancel(/* mayInterruptIfRunning= */ true);
             mDebugHotwordLoggingTimeoutFuture = null;
         }
     }
@@ -348,12 +357,14 @@
         mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
 
         Slog.v(TAG, "Started the new process, dispatching processRestarted to detector");
-        mHotwordDetectorSession.updateRemoteHotwordDetectionServiceLocked(
-                mRemoteHotwordDetectionService);
-        mHotwordDetectorSession.informRestartProcessLocked();
+        runForEachHotwordDetectorSessionLocked((session) -> {
+            session.updateRemoteHotwordDetectionServiceLocked(mRemoteHotwordDetectionService);
+            session.informRestartProcessLocked();
+        });
         if (DEBUG) {
             Slog.i(TAG, "processRestarted is dispatched done, unbinding from the old process");
         }
+
         oldConnection.ignoreConnectionStatusEvents();
         oldConnection.unbind();
         if (previousIdentity != null) {
@@ -364,11 +375,13 @@
     static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
         private final HotwordDetectionConnection mHotwordDetectionConnection;
         private final IHotwordRecognitionStatusCallback mExternalCallback;
+        private final int mVoiceInteractionServiceUid;
 
         SoundTriggerCallback(IHotwordRecognitionStatusCallback callback,
-                HotwordDetectionConnection connection) {
+                HotwordDetectionConnection connection, int uid) {
             mHotwordDetectionConnection = connection;
             mExternalCallback = callback;
+            mVoiceInteractionServiceUid = uid;
         }
 
         @Override
@@ -381,13 +394,15 @@
             if (useHotwordDetectionService) {
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
+                        mVoiceInteractionServiceUid);
                 mHotwordDetectionConnection.detectFromDspSource(
                         recognitionEvent, mExternalCallback);
             } else {
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
+                        mVoiceInteractionServiceUid);
                 mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
             }
         }
@@ -425,8 +440,10 @@
             pw.print(prefix); pw.print("mLastRestartInstant="); pw.println(mLastRestartInstant);
             pw.print(prefix); pw.print("mDetectorType=");
             pw.println(HotwordDetector.detectorTypeToString(mDetectorType));
-            pw.print(prefix); pw.println("HotwordDetectorSession");
-            mHotwordDetectorSession.dumpLocked(prefix, pw);
+            pw.print(prefix); pw.println("HotwordDetectorSession(s)");
+            runForEachHotwordDetectorSessionLocked((session) -> {
+                session.dumpLocked(prefix, pw);
+            });
         }
     }
 
@@ -444,7 +461,7 @@
         ServiceConnection createLocked() {
             ServiceConnection connection =
                     new ServiceConnection(mContext, mIntent, mBindingFlags, mUser,
-                            IHotwordDetectionService.Stub::asInterface,
+                            ISandboxedDetectionService.Stub::asInterface,
                             mRestartCount++ % MAX_ISOLATED_PROCESS_NUMBER);
             connection.connect();
 
@@ -456,7 +473,7 @@
         }
     }
 
-    class ServiceConnection extends ServiceConnector.Impl<IHotwordDetectionService> {
+    class ServiceConnection extends ServiceConnector.Impl<ISandboxedDetectionService> {
         private final Object mLock = new Object();
 
         private final Intent mIntent;
@@ -469,7 +486,7 @@
 
         ServiceConnection(@NonNull Context context,
                 @NonNull Intent intent, int bindingFlags, int userId,
-                @Nullable Function<IBinder, IHotwordDetectionService> binderAsInterface,
+                @Nullable Function<IBinder, ISandboxedDetectionService> binderAsInterface,
                 int instanceNumber) {
             super(context, intent, bindingFlags, userId, binderAsInterface);
             this.mIntent = intent;
@@ -478,7 +495,7 @@
         }
 
         @Override // from ServiceConnector.Impl
-        protected void onServiceConnectionStatusChanged(IHotwordDetectionService service,
+        protected void onServiceConnectionStatusChanged(ISandboxedDetectionService service,
                 boolean connected) {
             if (DEBUG) {
                 Slog.d(TAG, "onServiceConnectionStatusChanged connected = " + connected);
@@ -519,8 +536,10 @@
                 }
             }
             synchronized (HotwordDetectionConnection.this.mLock) {
-                mHotwordDetectorSession.reportErrorLocked(
-                        HotwordDetectorSession.HOTWORD_DETECTION_SERVICE_DIED);
+                runForEachHotwordDetectorSessionLocked((session) -> {
+                    session.reportErrorLocked(
+                            HotwordDetectorSession.HOTWORD_DETECTION_SERVICE_DIED);
+                });
             }
         }
 
@@ -565,6 +584,93 @@
         }
     }
 
+    @SuppressWarnings("GuardedBy")
+    void createDetectorLocked(
+            @Nullable PersistableBundle options,
+            @Nullable SharedMemory sharedMemory,
+            @NonNull IBinder token,
+            @NonNull IHotwordRecognitionStatusCallback callback,
+            int detectorType) {
+        // We only support one Dsp trusted hotword detector and one software hotword detector at
+        // the same time, remove existing one.
+        HotwordDetectorSession removeSession = mHotwordDetectorSessions.get(detectorType);
+        if (removeSession != null) {
+            removeSession.destroyLocked();
+            mHotwordDetectorSessions.remove(detectorType);
+        }
+        final HotwordDetectorSession session;
+        if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP) {
+            session = new DspTrustedHotwordDetectorSession(mRemoteHotwordDetectionService,
+                    mLock, mContext, token, callback, mVoiceInteractionServiceUid,
+                    mVoiceInteractorIdentity, mScheduledExecutorService, mDebugHotwordLogging);
+        } else {
+            session = new SoftwareTrustedHotwordDetectorSession(
+                    mRemoteHotwordDetectionService, mLock, mContext, token, callback,
+                    mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
+                    mScheduledExecutorService, mDebugHotwordLogging);
+        }
+        mHotwordDetectorSessions.put(detectorType, session);
+        session.initialize(options, sharedMemory);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    void destroyDetectorLocked(@NonNull IBinder token) {
+        final HotwordDetectorSession session = getDetectorSessionByTokenLocked(token);
+        if (session != null) {
+            session.destroyLocked();
+            final int index = mHotwordDetectorSessions.indexOfValue(session);
+            if (index < 0 || index > mHotwordDetectorSessions.size() - 1) {
+                return;
+            }
+            mHotwordDetectorSessions.removeAt(index);
+        }
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private HotwordDetectorSession getDetectorSessionByTokenLocked(IBinder token) {
+        if (token == null) {
+            return null;
+        }
+        for (int i = 0; i < mHotwordDetectorSessions.size(); i++) {
+            final HotwordDetectorSession session = mHotwordDetectorSessions.valueAt(i);
+            if (!session.isDestroyed() && session.isSameToken(token)) {
+                return session;
+            }
+        }
+        return null;
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private DspTrustedHotwordDetectorSession getDspTrustedHotwordDetectorSessionLocked() {
+        final HotwordDetectorSession session = mHotwordDetectorSessions.get(
+                HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
+        if (session == null || session.isDestroyed()) {
+            Slog.v(TAG, "Not found the Dsp detector");
+            return null;
+        }
+        return (DspTrustedHotwordDetectorSession) session;
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private SoftwareTrustedHotwordDetectorSession getSoftwareTrustedHotwordDetectorSessionLocked() {
+        final HotwordDetectorSession session = mHotwordDetectorSessions.get(
+                HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
+        if (session == null || session.isDestroyed()) {
+            Slog.v(TAG, "Not found the software detector");
+            return null;
+        }
+        return (SoftwareTrustedHotwordDetectorSession) session;
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private void runForEachHotwordDetectorSessionLocked(
+            @NonNull Consumer<HotwordDetectorSession> action) {
+        for (int i = 0; i < mHotwordDetectorSessions.size(); i++) {
+            HotwordDetectorSession session = mHotwordDetectorSessions.valueAt(i);
+            action.accept(session);
+        }
+    }
+
     private static void updateAudioFlinger(ServiceConnection connection, IBinder audioFlinger) {
         // TODO: Consider using a proxy that limits the exposed API surface.
         connection.run(service -> service.updateAudioFlinger(audioFlinger));
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
index f9f43c9..689423a 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
@@ -57,6 +57,7 @@
 import android.media.permission.PermissionUtil;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
@@ -182,16 +183,18 @@
     private boolean mDestroyed = false;
     @GuardedBy("mLock")
     boolean mPerformingExternalSourceHotwordDetection;
+    @NonNull final IBinder mToken;
 
     HotwordDetectorSession(
             @NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
-            @NonNull Object lock, @NonNull Context context,
+            @NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
             @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity,
             @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
         mRemoteHotwordDetectionService = remoteHotwordDetectionService;
         mLock = lock;
         mContext = context;
+        mToken = token;
         mCallback = callback;
         mVoiceInteractionServiceUid = voiceInteractionServiceUid;
         mVoiceInteractorIdentity = voiceInteractorIdentity;
@@ -238,7 +241,7 @@
                     try {
                         mCallback.onStatusReported(status);
                         HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
-                                initResultMetricsResult);
+                                initResultMetricsResult, mVoiceInteractionServiceUid);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to report initialization status: " + e);
                         HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
@@ -269,7 +272,7 @@
                 try {
                     mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
                     HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
-                            METRICS_INIT_UNKNOWN_TIMEOUT);
+                            METRICS_INIT_UNKNOWN_TIMEOUT, mVoiceInteractionServiceUid);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
                     HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
@@ -474,8 +477,8 @@
                                             callback.onError();
                                             return;
                                         }
-                                        callback.onDetected(newResult, null /* audioFormat */,
-                                                null /* audioStream */);
+                                        callback.onDetected(newResult, /* audioFormat= */ null,
+                                                /* audioStream= */ null);
                                         Slog.i(TAG, "Egressed "
                                                 + HotwordDetectedResult.getUsageSize(newResult)
                                                 + " bits from hotword trusted process");
@@ -542,6 +545,30 @@
      */
     abstract void informRestartProcessLocked();
 
+    boolean isSameCallback(@Nullable IHotwordRecognitionStatusCallback callback) {
+        synchronized (mLock) {
+            if (callback == null) {
+                return false;
+            }
+            return mCallback.asBinder().equals(callback.asBinder());
+        }
+    }
+
+    boolean isSameToken(@NonNull IBinder token) {
+        synchronized (mLock) {
+            if (token == null) {
+                return false;
+            }
+            return mToken == token;
+        }
+    }
+
+    boolean isDestroyed() {
+        synchronized (mLock) {
+            return mDestroyed;
+        }
+    }
+
     private static Pair<ParcelFileDescriptor, ParcelFileDescriptor> createPipe() {
         ParcelFileDescriptor[] fileDescriptors;
         try {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
index 940aed3..61c18be 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
@@ -64,28 +64,28 @@
     /**
      * Logs information related to hotword detection service init result.
      */
-    public static void writeServiceInitResultEvent(int detectorType, int result) {
+    public static void writeServiceInitResultEvent(int detectorType, int result, int uid) {
         int metricsDetectorType = getInitMetricsDetectorType(detectorType);
         FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED,
-                metricsDetectorType, result);
+                metricsDetectorType, result, uid);
     }
 
     /**
      * Logs information related to hotword detection service restarting.
      */
-    public static void writeServiceRestartEvent(int detectorType, int reason) {
+    public static void writeServiceRestartEvent(int detectorType, int reason, int uid) {
         int metricsDetectorType = getRestartMetricsDetectorType(detectorType);
         FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED,
-                metricsDetectorType, reason);
+                metricsDetectorType, reason, uid);
     }
 
     /**
      * Logs information related to keyphrase trigger.
      */
-    public static void writeKeyphraseTriggerEvent(int detectorType, int result) {
+    public static void writeKeyphraseTriggerEvent(int detectorType, int result, int uid) {
         int metricsDetectorType = getKeyphraseMetricsDetectorType(detectorType);
         FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED,
-                metricsDetectorType, result);
+                metricsDetectorType, result, uid);
     }
 
     /**
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
index 6930689..4eb997a 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.media.AudioFormat;
 import android.media.permission.Identity;
+import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.SharedMemory;
@@ -35,8 +36,8 @@
 import android.service.voice.HotwordDetector;
 import android.service.voice.HotwordRejectedResult;
 import android.service.voice.IDspHotwordDetectionCallback;
-import android.service.voice.IHotwordDetectionService;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.ISandboxedDetectionService;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -63,12 +64,13 @@
 
     SoftwareTrustedHotwordDetectorSession(
             @NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
-            @NonNull Object lock, @NonNull Context context,
+            @NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
             @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity,
             @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
-        super(remoteHotwordDetectionService, lock, context, callback, voiceInteractionServiceUid,
-                voiceInteractorIdentity, scheduledExecutorService, logging);
+        super(remoteHotwordDetectionService, lock, context, token, callback,
+                voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
+                logging);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -101,12 +103,14 @@
                 synchronized (mLock) {
                     HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                             HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
-                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED,
+                            mVoiceInteractionServiceUid);
                     if (!mPerformingSoftwareHotwordDetection) {
                         Slog.i(TAG, "Hotword detection has already completed");
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK,
+                                mVoiceInteractionServiceUid);
                         return;
                     }
                     mPerformingSoftwareHotwordDetection = false;
@@ -115,7 +119,8 @@
                     } catch (SecurityException e) {
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
+                                mVoiceInteractionServiceUid);
                         mSoftwareCallback.onError();
                         return;
                     }
@@ -144,7 +149,8 @@
                 }
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED,
+                        mVoiceInteractionServiceUid);
                 // onRejected isn't allowed here, and we are not expecting it.
             }
         };
@@ -163,9 +169,9 @@
     }
 
     @SuppressWarnings("GuardedBy")
-    void stopListeningLocked() {
+    void stopListeningFromMicLocked() {
         if (DEBUG) {
-            Slog.d(TAG, "stopListeningLocked");
+            Slog.d(TAG, "stopListeningFromMicLocked");
         }
         if (!mPerformingSoftwareHotwordDetection) {
             Slog.i(TAG, "Hotword detection is not running");
@@ -173,7 +179,7 @@
         }
         mPerformingSoftwareHotwordDetection = false;
 
-        mRemoteHotwordDetectionService.run(IHotwordDetectionService::stopDetection);
+        mRemoteHotwordDetectionService.run(ISandboxedDetectionService::stopDetection);
 
         closeExternalAudioStreamLocked("stopping requested");
     }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 7207e373..9a02188 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1245,14 +1245,15 @@
         @Override
         public void updateState(
                 @Nullable PersistableBundle options,
-                @Nullable SharedMemory sharedMemory) {
+                @Nullable SharedMemory sharedMemory,
+                @NonNull IBinder token) {
             super.updateState_enforcePermission();
 
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
 
                 Binder.withCleanCallingIdentity(
-                        () -> mImpl.updateStateLocked(options, sharedMemory));
+                        () -> mImpl.updateStateLocked(options, sharedMemory, token));
             }
         }
 
@@ -1262,6 +1263,7 @@
                 @NonNull Identity voiceInteractorIdentity,
                 @Nullable PersistableBundle options,
                 @Nullable SharedMemory sharedMemory,
+                @NonNull IBinder token,
                 IHotwordRecognitionStatusCallback callback,
                 int detectorType) {
             super.initAndVerifyDetector_enforcePermission();
@@ -1274,7 +1276,20 @@
 
                 Binder.withCleanCallingIdentity(
                         () -> mImpl.initAndVerifyDetectorLocked(voiceInteractorIdentity, options,
-                                sharedMemory, callback, detectorType));
+                                sharedMemory, token, callback, detectorType));
+            }
+        }
+
+        @Override
+        public void destroyDetector(@NonNull IBinder token) {
+            synchronized (this) {
+                if (mImpl == null) {
+                    Slog.w(TAG, "destroyDetector without running voice interaction service");
+                    return;
+                }
+
+                Binder.withCleanCallingIdentity(
+                        () -> mImpl.destroyDetectorLocked(token));
             }
         }
 
@@ -1326,6 +1341,7 @@
                 ParcelFileDescriptor audioStream,
                 AudioFormat audioFormat,
                 PersistableBundle options,
+                @NonNull IBinder token,
                 IMicrophoneHotwordDetectionVoiceInteractionCallback callback)
                 throws RemoteException {
             synchronized (this) {
@@ -1338,8 +1354,8 @@
                 }
                 final long caller = Binder.clearCallingIdentity();
                 try {
-                    mImpl.startListeningFromExternalSourceLocked(
-                            audioStream, audioFormat, options, callback);
+                    mImpl.startListeningFromExternalSourceLocked(audioStream, audioFormat, options,
+                            token, callback);
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 0a660b0..138362f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -56,7 +56,6 @@
 import android.os.ServiceManager;
 import android.os.SharedMemory;
 import android.os.UserHandle;
-import android.service.voice.HotwordDetector;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
@@ -113,7 +112,6 @@
 
     VoiceInteractionSessionConnection mActiveSession;
     int mDisabledShowContext;
-    int mDetectorType;
 
     final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -552,7 +550,8 @@
 
     public void updateStateLocked(
             @Nullable PersistableBundle options,
-            @Nullable SharedMemory sharedMemory) {
+            @Nullable SharedMemory sharedMemory,
+            @NonNull IBinder token) {
         Slog.v(TAG, "updateStateLocked");
 
         if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
@@ -565,7 +564,7 @@
             throw new IllegalStateException("Hotword detection connection not found");
         }
         synchronized (mHotwordDetectionConnection.mLock) {
-            mHotwordDetectionConnection.updateStateLocked(options, sharedMemory);
+            mHotwordDetectionConnection.updateStateLocked(options, sharedMemory, token);
         }
     }
 
@@ -573,6 +572,7 @@
             @NonNull Identity voiceInteractorIdentity,
             @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
+            @NonNull IBinder token,
             IHotwordRecognitionStatusCallback callback,
             int detectorType) {
         Slog.v(TAG, "initAndVerifyDetectorLocked");
@@ -624,16 +624,26 @@
             throw new IllegalStateException("Can't set sharedMemory to be read-only");
         }
 
-        mDetectorType = detectorType;
-
         logDetectorCreateEventIfNeeded(callback, detectorType, true,
                 voiceInteractionServiceUid);
         if (mHotwordDetectionConnection == null) {
             mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
                     mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
                     mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false,
-                    options, sharedMemory, callback, detectorType);
+                    detectorType);
         }
+        mHotwordDetectionConnection.createDetectorLocked(options, sharedMemory, token, callback,
+                detectorType);
+    }
+
+    public void destroyDetectorLocked(IBinder token) {
+        Slog.v(TAG, "destroyDetectorLocked");
+
+        if (mHotwordDetectionConnection == null) {
+            Slog.w(TAG, "destroy detector callback, but no hotword detection connection");
+            return;
+        }
+        mHotwordDetectionConnection.destroyDetectorLocked(token);
     }
 
     private void logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback,
@@ -642,19 +652,16 @@
             HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, isCreated,
                     voiceInteractionServiceUid);
         }
-
     }
 
     public void shutdownHotwordDetectionServiceLocked() {
         if (DEBUG) {
             Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
         }
-
         if (mHotwordDetectionConnection == null) {
             Slog.w(TAG, "shutdown, but no hotword detection connection");
             return;
         }
-
         mHotwordDetectionConnection.cancelLocked();
         mHotwordDetectionConnection = null;
     }
@@ -663,7 +670,7 @@
             AudioFormat audioFormat,
             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
-            Slog.d(TAG, "startListeningFromMic");
+            Slog.d(TAG, "startListeningFromMicLocked");
         }
 
         if (mHotwordDetectionConnection == null) {
@@ -671,16 +678,17 @@
             return;
         }
 
-        mHotwordDetectionConnection.startListeningFromMic(audioFormat, callback);
+        mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback);
     }
 
     public void startListeningFromExternalSourceLocked(
             ParcelFileDescriptor audioStream,
             AudioFormat audioFormat,
             @Nullable PersistableBundle options,
+            @NonNull IBinder token,
             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
-            Slog.d(TAG, "startListeningFromExternalSource");
+            Slog.d(TAG, "startListeningFromExternalSourceLocked");
         }
 
         if (mHotwordDetectionConnection == null) {
@@ -693,21 +701,21 @@
             throw new IllegalStateException("External source is null for hotword detector");
         }
 
-        mHotwordDetectionConnection
-                .startListeningFromExternalSource(audioStream, audioFormat, options, callback);
+        mHotwordDetectionConnection.startListeningFromExternalSourceLocked(audioStream, audioFormat,
+                options, token, callback);
     }
 
     public void stopListeningFromMicLocked() {
         if (DEBUG) {
-            Slog.d(TAG, "stopListeningFromMic");
+            Slog.d(TAG, "stopListeningFromMicLocked");
         }
 
         if (mHotwordDetectionConnection == null) {
-            Slog.w(TAG, "stopListeningFromMic() called but connection isn't established");
+            Slog.w(TAG, "stopListeningFromMicLocked() called but connection isn't established");
             return;
         }
 
-        mHotwordDetectionConnection.stopListening();
+        mHotwordDetectionConnection.stopListeningFromMicLocked();
     }
 
     public void triggerHardwareRecognitionEventForTestLocked(
@@ -730,7 +738,7 @@
             Slog.d(TAG, "createSoundTriggerCallbackLocked");
         }
         return new HotwordDetectionConnection.SoundTriggerCallback(callback,
-                mHotwordDetectionConnection);
+                mHotwordDetectionConnection, mInfo.getServiceInfo().applicationInfo.uid);
     }
 
     private static ServiceInfo getServiceInfoLocked(@NonNull ComponentName componentName,
@@ -809,8 +817,6 @@
             pw.println(Integer.toHexString(mDisabledShowContext));
         }
         pw.print("  mBound="); pw.print(mBound);  pw.print(" mService="); pw.println(mService);
-        pw.print("  mDetectorType=");
-        pw.println(HotwordDetector.detectorTypeToString(mDetectorType));
         if (mHotwordDetectionConnection != null) {
             pw.println("  Hotword detection connection:");
             mHotwordDetectionConnection.dump("    ", pw);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 22cd31a..c4744ef 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -52,6 +53,7 @@
 import com.android.telephony.Rlog;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
@@ -9957,10 +9959,13 @@
      * @param subId the subscription ID, normally obtained from {@link SubscriptionManager}.
      * @return A {@link PersistableBundle} containing the config for the given subId, or default
      *         values for an invalid subId.
+     *
+     * @deprecated Use {@link #getConfigForSubId(int, String...)} instead.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     @Nullable
+    @Deprecated
     public PersistableBundle getConfigForSubId(int subId) {
         try {
             ICarrierConfigLoader loader = getICarrierConfigLoader();
@@ -9979,6 +9984,59 @@
     }
 
     /**
+     * Gets the configuration values of the specified keys for a particular subscription.
+     *
+     * <p>If an invalid subId is used, the returned configuration will contain default values for
+     * the specified keys. If the value for the key can't be found, the returned configuration will
+     * filter the key out.
+     *
+     * <p>After using this method to get the configuration bundle,
+     * {@link #isConfigForIdentifiedCarrier(PersistableBundle)} should be called to confirm whether
+     * any carrier specific configuration has been applied.
+     *
+     * <p>Note that on success, the key/value for {@link #KEY_CARRIER_CONFIG_VERSION_STRING} and
+     * {@link #KEY_CARRIER_CONFIG_APPLIED_BOOL} are always in the returned bundle, no matter if they
+     * were explicitly requested.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}, or the calling app
+     * has carrier privileges on the specified subscription (see
+     * {@link TelephonyManager#hasCarrierPrivileges()}).
+     *
+     * @param subId The subscription ID on which the carrier config should be retrieved.
+     * @param keys  The carrier config keys to retrieve values.
+     * @return A {@link PersistableBundle} with key/value mapping for the specified configuration
+     * on success, or an empty (but never null) bundle on failure (for example, when the calling app
+     * has no permission).
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.READ_PHONE_STATE,
+            "carrier privileges",
+    })
+    @NonNull
+    public PersistableBundle getConfigForSubId(int subId, @NonNull String... keys) {
+        Objects.requireNonNull(keys, "Config keys should be non-null");
+        for (String key : keys) {
+            Objects.requireNonNull(key, "Config key should be non-null");
+        }
+
+        try {
+            ICarrierConfigLoader loader = getICarrierConfigLoader();
+            if (loader == null) {
+                Rlog.w(TAG, "Error getting config for subId " + subId
+                        + " ICarrierConfigLoader is null");
+                throw new IllegalStateException("Carrier config loader is not available.");
+            }
+            return loader.getConfigSubsetForSubIdWithFeature(subId, mContext.getOpPackageName(),
+                    mContext.getAttributionTag(), keys);
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "Error getting config for subId " + subId + ": " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+        return new PersistableBundle();
+    }
+
+    /**
      * Overrides the carrier config of the provided subscription ID with the provided values.
      *
      * Any further queries to carrier config from any process will return the overridden values
@@ -10052,15 +10110,52 @@
      * has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges()}).
      *
      * @see #getConfigForSubId
+     * @see #getConfig(String...)
+     * @deprecated use {@link #getConfig(String...)} instead.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     @Nullable
+    @Deprecated
     public PersistableBundle getConfig() {
         return getConfigForSubId(SubscriptionManager.getDefaultSubscriptionId());
     }
 
     /**
+     * Gets the configuration values of the specified config keys applied for the default
+     * subscription.
+     *
+     * <p>If the value for the key can't be found, the returned bundle will filter the key out.
+     *
+     * <p>After using this method to get the configuration bundle, {@link
+     * #isConfigForIdentifiedCarrier(PersistableBundle)} should be called to confirm whether any
+     * carrier specific configuration has been applied.
+     *
+     * <p>Note that on success, the key/value for {@link #KEY_CARRIER_CONFIG_VERSION_STRING} and
+     * {@link #KEY_CARRIER_CONFIG_APPLIED_BOOL} are always in the returned bundle, no matter if
+     * they were explicitly requested.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}, or the calling app
+     * has carrier privileges for the default subscription (see
+     * {@link TelephonyManager#hasCarrierPrivileges()}).
+     *
+     * @param keys The config keys to retrieve values
+     * @return A {@link PersistableBundle} with key/value mapping for the specified carrier
+     * configs on success, or an empty (but never null) bundle on failure.
+     * @see #getConfigForSubId(int, String...)
+     * @see SubscriptionManager#getDefaultSubscriptionId()
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.READ_PHONE_STATE,
+            "carrier privileges",
+    })
+    @NonNull
+    public PersistableBundle getConfig(@NonNull String... keys) {
+        return getConfigForSubId(SubscriptionManager.getDefaultSubscriptionId(), keys);
+    }
+
+    /**
      * Determines whether a configuration {@link PersistableBundle} obtained from
      * {@link #getConfig()} or {@link #getConfigForSubId(int)} corresponds to an identified carrier.
      *
@@ -10246,4 +10341,85 @@
             configs.putPersistableBundle(key, (PersistableBundle) value);
         }
     }
+
+    /**
+     * Listener interface to get a notification when the carrier configurations have changed.
+     *
+     * Use this listener to receive timely updates when the carrier configuration changes. System
+     * components should prefer this listener over {@link #ACTION_CARRIER_CONFIG_CHANGED}
+     * whenever possible.
+     *
+     * To register the listener, call
+     * {@link #registerCarrierConfigChangeListener(Executor, CarrierConfigChangeListener)}.
+     * To unregister, call
+     * {@link #unregisterCarrierConfigChangeListener(CarrierConfigChangeListener)}.
+     *
+     * Note that on registration, registrants will NOT receive a notification on last carrier config
+     * change. Only carrier configs change AFTER the registration will be sent to registrants. And
+     * unlike {@link #ACTION_CARRIER_CONFIG_CHANGED}, notification wouldn't send when the device is
+     * unlocked. Registrants only receive the notification when there has been real carrier config
+     * changes.
+     *
+     * @see #registerCarrierConfigChangeListener(Executor, CarrierConfigChangeListener)
+     * @see #unregisterCarrierConfigChangeListener(CarrierConfigChangeListener)
+     * @see #ACTION_CARRIER_CONFIG_CHANGED
+     * @see #getConfig(String...)
+     * @see #getConfigForSubId(int, String...)
+     */
+    public interface CarrierConfigChangeListener {
+        /**
+         * Called when carrier configurations have changed.
+         *
+         * @param logicalSlotIndex  The logical SIM slot index on which to monitor and get
+         *                          notification. It is guaranteed to be valid.
+         * @param subscriptionId    The subscription on the SIM slot. May be
+         *                          {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+         * @param carrierId         The optional carrier Id, may be
+         *                          {@link TelephonyManager#UNKNOWN_CARRIER_ID}.
+         *                          See {@link TelephonyManager#getSimCarrierId()}.
+         * @param specificCarrierId The optional fine-grained carrierId, may be {@link
+         *                          TelephonyManager#UNKNOWN_CARRIER_ID}. A specific carrierId may
+         *                          be different from the carrierId above in a MVNO scenario. See
+         *                          detail in {@link TelephonyManager#getSimSpecificCarrierId()}.
+         */
+        void onCarrierConfigChanged(int logicalSlotIndex, int subscriptionId, int carrierId,
+                int specificCarrierId);
+    }
+
+    /**
+     * Register a {@link CarrierConfigChangeListener} to get a notification when carrier
+     * configurations have changed.
+     *
+     * @param executor The executor on which the listener will be called.
+     * @param listener The CarrierConfigChangeListener called when carrier configs has changed.
+     */
+    public void registerCarrierConfigChangeListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull CarrierConfigChangeListener listener) {
+        Objects.requireNonNull(executor, "Executor should be non-null.");
+        Objects.requireNonNull(listener, "Listener should be non-null.");
+
+        TelephonyRegistryManager trm = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (trm == null) {
+            throw new IllegalStateException("Telephony registry service is null");
+        }
+        trm.addCarrierConfigChangedListener(executor, listener);
+    }
+
+    /**
+     * Unregister the {@link CarrierConfigChangeListener} to stop notification on carrier
+     * configurations change.
+     *
+     * @param listener The CarrierConfigChangeListener which was registered with method
+     * {@link #registerCarrierConfigChangeListener(Executor, CarrierConfigChangeListener)}.
+     */
+    public void unregisterCarrierConfigChangeListener(
+            @NonNull CarrierConfigChangeListener listener) {
+        Objects.requireNonNull(listener, "Listener should be non-null.");
+
+        TelephonyRegistryManager trm = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (trm == null) {
+            throw new IllegalStateException("Telephony registry service is null");
+        }
+        trm.removeCarrierConfigChangedListener(listener);
+    }
 }
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 1f301c1..d1f19ee 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -3125,6 +3125,43 @@
         }
     }
 
+    /**
+     * Set Storage Availability in SmsStorageMonitor
+     * @param storageAvailable storage availability to be set true or false
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @TestApi
+    public void setStorageMonitorMemoryStatusOverride(boolean storageAvailable) {
+        try {
+            ISms iccISms = getISmsServiceOrThrow();
+            if (iccISms != null) {
+                iccISms.setStorageMonitorMemoryStatusOverride(getSubscriptionId(),
+                                                                storageAvailable);
+            }
+        } catch (RemoteException ex) {
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Clear the memory status override set by
+     * {@link #setStorageMonitorMemoryStatusOverride(boolean)}
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @TestApi
+    public void clearStorageMonitorMemoryStatusOverride() {
+        try {
+            ISms iccISms = getISmsServiceOrThrow();
+            if (iccISms != null) {
+                iccISms.clearStorageMonitorMemoryStatusOverride(getSubscriptionId());
+            }
+        } catch (RemoteException ex) {
+            ex.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"SMS_CATEGORY_"},
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 9b566fb..8e8755d 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -58,6 +58,7 @@
 import android.provider.Telephony.SimInfo;
 import android.telephony.euicc.EuiccManager;
 import android.telephony.ims.ImsMmTelManager;
+import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
@@ -370,7 +371,7 @@
 
     /**
      * A content {@link Uri} used to receive updates on advanced calling user setting
-     * @see ImsMmTelManager#isAdvancedCallingSettingEnabled().
+     *
      * <p>
      * Use this {@link Uri} with a {@link ContentObserver} to be notified of changes to the
      * subscription advanced calling enabled
@@ -381,6 +382,9 @@
      * delivery of updates to the {@link Uri}.
      * To be notified of changes to a specific subId, append subId to the URI
      * {@link Uri#withAppendedPath(Uri, String)}.
+     *
+     * @see ImsMmTelManager#isAdvancedCallingSettingEnabled()
+     *
      * @hide
      */
     @NonNull
@@ -1165,7 +1169,7 @@
      *
      * An opportunistic subscription will default to data-centric.
      *
-     * {@see SubscriptionInfo#isOpportunistic}
+     * @see SubscriptionInfo#isOpportunistic
      */
     public static final int USAGE_SETTING_DEFAULT = 0;
 
@@ -1949,7 +1953,7 @@
      *
      * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
      *
-     * @see {@link TelephonyManager#getCardIdForDefaultEuicc()} for more information on the card ID.
+     * @see TelephonyManager#getCardIdForDefaultEuicc() for more information on the card ID.
      *
      * @hide
      */
@@ -1979,7 +1983,7 @@
      *
      * @param cardId the card ID of the eUICC.
      *
-     * @see {@link TelephonyManager#getCardIdForDefaultEuicc()} for more information on the card ID.
+     * @see TelephonyManager#getCardIdForDefaultEuicc() for more information on the card ID.
      *
      * @hide
      */
@@ -2103,10 +2107,15 @@
     }
 
     /**
-     * Remove SubscriptionInfo record from the SubscriptionInfo database
+     * Remove subscription info record from the subscription database.
+     *
      * @param uniqueId This is the unique identifier for the subscription within the specific
-     *                 subscription type.
-     * @param subscriptionType the {@link #SUBSCRIPTION_TYPE}
+     * subscription type.
+     * @param subscriptionType the type of subscription to be removed.
+     *
+     * @throws NullPointerException if {@code uniqueId} is {@code null}.
+     * @throws SecurityException if callers do not hold the required permission.
+     *
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@@ -2457,20 +2466,6 @@
         return getActiveSubscriptionInfo(getDefaultDataSubscriptionId());
     }
 
-    /** @hide */
-    public void clearSubscriptionInfo() {
-        try {
-            ISub iSub = TelephonyManager.getSubscriptionService();
-            if (iSub != null) {
-                iSub.clearSubInfo();
-            }
-        } catch (RemoteException ex) {
-            // ignore it
-        }
-
-        return;
-    }
-
     /**
      * Check if the supplied subscription ID is valid.
      *
@@ -2614,17 +2609,27 @@
     }
 
     /**
-     * Store properties associated with SubscriptionInfo in database
-     * @param subId Subscription Id of Subscription
-     * @param propKey Column name in database associated with SubscriptionInfo
-     * @param propValue Value to store in DB for particular subId & column name
+     * Set a field in the subscription database. Note not all fields are supported.
+     *
+     * @param subscriptionId Subscription Id of Subscription.
+     * @param columnName Column name in the database. Note not all fields are supported.
+     * @param value Value to store in the database.
+     *
+     * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
+     * exposed.
+     * @throws SecurityException if callers do not hold the required permission.
+     *
+     * @see android.provider.Telephony.SimInfo for all the columns.
+     *
      * @hide
      */
-    public static void setSubscriptionProperty(int subId, String propKey, String propValue) {
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public static void setSubscriptionProperty(int subscriptionId, @NonNull String columnName,
+            @NonNull String value) {
         try {
             ISub iSub = TelephonyManager.getSubscriptionService();
             if (iSub != null) {
-                iSub.setSubscriptionProperty(subId, propKey, propValue);
+                iSub.setSubscriptionProperty(subscriptionId, columnName, value);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -2653,118 +2658,149 @@
     }
 
     /**
-     * Return list of contacts uri corresponding to query result.
-     * @param subId Subscription Id of Subscription
-     * @param propKey Column name in SubscriptionInfo database
-     * @return list of contacts uri to be returned
+     * Get specific field in string format from the subscription info database.
+     *
+     * @param context The calling context.
+     * @param subscriptionId Subscription id of the subscription.
+     * @param columnName Column name in subscription database.
+     *
+     * @return Value in string format associated with {@code subscriptionId} and {@code columnName}
+     * from the database.
+     *
+     * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
+     * exposed.
+     *
+     * @see android.provider.Telephony.SimInfo for all the columns.
+     *
      * @hide
      */
-    private static List<Uri> getContactsFromSubscriptionProperty(int subId, String propKey,
-            Context context) {
-        String result = getSubscriptionProperty(subId, propKey, context);
-        if (result != null) {
-            try {
-                byte[] b = Base64.decode(result, Base64.DEFAULT);
-                ByteArrayInputStream bis = new ByteArrayInputStream(b);
-                ObjectInputStream ois = new ObjectInputStream(bis);
-                List<String> contacts = ArrayList.class.cast(ois.readObject());
-                List<Uri> uris = new ArrayList<>();
-                for (String contact : contacts) {
-                    uris.add(Uri.parse(contact));
-                }
-                return uris;
-            } catch (IOException e) {
-                logd("getContactsFromSubscriptionProperty IO exception");
-            } catch (ClassNotFoundException e) {
-                logd("getContactsFromSubscriptionProperty ClassNotFound exception");
-            }
-        }
-        return new ArrayList<>();
-    }
-
-    /**
-     * Store properties associated with SubscriptionInfo in database
-     * @param subId Subscription Id of Subscription
-     * @param propKey Column name in SubscriptionInfo database
-     * @return Value associated with subId and propKey column in database
-     * @hide
-     */
-    private static String getSubscriptionProperty(int subId, String propKey,
-            Context context) {
+    @NonNull
+    @RequiresPermission(anyOf = {
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            "carrier privileges",
+    })
+    private static String getStringSubscriptionProperty(@NonNull Context context,
+            int subscriptionId, @NonNull String columnName) {
         String resultValue = null;
         try {
             ISub iSub = TelephonyManager.getSubscriptionService();
             if (iSub != null) {
-                resultValue = iSub.getSubscriptionProperty(subId, propKey,
+                resultValue = iSub.getSubscriptionProperty(subscriptionId, columnName,
                         context.getOpPackageName(), context.getAttributionTag());
             }
         } catch (RemoteException ex) {
             // ignore it
         }
-        return resultValue;
+        return TextUtils.emptyIfNull(resultValue);
     }
 
     /**
-     * Returns boolean value corresponding to query result.
-     * @param subId Subscription Id of Subscription
-     * @param propKey Column name in SubscriptionInfo database
-     * @param defValue Default boolean value to be returned
-     * @return boolean result value to be returned
+     * Get specific field in {@code boolean} format from the subscription info database.
+     *
+     * @param subscriptionId Subscription id of the subscription.
+     * @param columnName Column name in subscription database.
+     * @param defaultValue Default value in case not found or error.
+     * @param context The calling context.
+     *
+     * @return Value in {@code boolean} format associated with {@code subscriptionId} and
+     * {@code columnName} from the database, or {@code defaultValue} if not found or error.
+     *
+     * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
+     * exposed.
+     *
+     * @see android.provider.Telephony.SimInfo for all the columns.
+     *
      * @hide
      */
-    public static boolean getBooleanSubscriptionProperty(int subId, String propKey,
-            boolean defValue, Context context) {
-        String result = getSubscriptionProperty(subId, propKey, context);
-        if (result != null) {
+    @RequiresPermission(anyOf = {
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            "carrier privileges",
+    })
+    public static boolean getBooleanSubscriptionProperty(int subscriptionId,
+            @NonNull String columnName, boolean defaultValue, @NonNull Context context) {
+        String result = getStringSubscriptionProperty(context, subscriptionId, columnName);
+        if (!result.isEmpty()) {
             try {
                 return Integer.parseInt(result) == 1;
             } catch (NumberFormatException err) {
                 logd("getBooleanSubscriptionProperty NumberFormat exception");
             }
         }
-        return defValue;
+        return defaultValue;
     }
 
     /**
-     * Returns integer value corresponding to query result.
-     * @param subId Subscription Id of Subscription
-     * @param propKey Column name in SubscriptionInfo database
-     * @param defValue Default integer value to be returned
-     * @return integer result value to be returned
+     * Get specific field in {@code integer} format from the subscription info database.
+     *
+     * @param subscriptionId Subscription id of the subscription.
+     * @param columnName Column name in subscription database.
+     * @param defaultValue Default value in case not found or error.
+     * @param context The calling context.
+     *
+     * @return Value in {@code integer} format associated with {@code subscriptionId} and
+     * {@code columnName} from the database, or {@code defaultValue} if not found or error.
+     *
+     * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
+     * exposed.
+     *
+     * @see android.provider.Telephony.SimInfo for all the columns.
+     *
      * @hide
      */
-    public static int getIntegerSubscriptionProperty(int subId, String propKey, int defValue,
-            Context context) {
-        String result = getSubscriptionProperty(subId, propKey, context);
-        if (result != null) {
+    @RequiresPermission(anyOf = {
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            "carrier privileges",
+    })
+    public static int getIntegerSubscriptionProperty(int subscriptionId, @NonNull String columnName,
+            int defaultValue, @NonNull Context context) {
+        String result = getStringSubscriptionProperty(context, subscriptionId, columnName);
+        if (!result.isEmpty()) {
             try {
                 return Integer.parseInt(result);
             } catch (NumberFormatException err) {
                 logd("getIntegerSubscriptionProperty NumberFormat exception");
             }
         }
-        return defValue;
+        return defaultValue;
     }
 
     /**
-     * Returns long value corresponding to query result.
-     * @param subId Subscription Id of Subscription
-     * @param propKey Column name in SubscriptionInfo database
-     * @param defValue Default long value to be returned
-     * @return long result value to be returned
+     * Get specific field in {@code long} format from the subscription info database.
+     *
+     * @param subscriptionId Subscription id of the subscription.
+     * @param columnName Column name in subscription database.
+     * @param defaultValue Default value in case not found or error.
+     * @param context The calling context.
+     *
+     * @return Value in {@code long} format associated with {@code subscriptionId} and
+     * {@code columnName} from the database, or {@code defaultValue} if not found or error.
+     *
+     * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
+     * exposed.
+     *
+     * @see android.provider.Telephony.SimInfo for all the columns.
+     *
      * @hide
      */
-    public static long getLongSubscriptionProperty(int subId, String propKey, long defValue,
-                                                     Context context) {
-        String result = getSubscriptionProperty(subId, propKey, context);
-        if (result != null) {
+    @RequiresPermission(anyOf = {
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            "carrier privileges",
+    })
+    public static long getLongSubscriptionProperty(int subscriptionId, @NonNull String columnName,
+            long defaultValue, @NonNull Context context) {
+        String result = getStringSubscriptionProperty(context, subscriptionId, columnName);
+        if (!result.isEmpty()) {
             try {
                 return Long.parseLong(result);
             } catch (NumberFormatException err) {
                 logd("getLongSubscriptionProperty NumberFormat exception");
             }
         }
-        return defValue;
+        return defaultValue;
     }
 
     /**
@@ -3002,7 +3038,6 @@
      *            considered unmetered.
      * @param networkTypes the network types this override applies to. If no
      *            network types are specified, override values will be ignored.
-     *            {@see TelephonyManager#getAllNetworkTypes()}
      * @param expirationDurationMillis the duration after which the requested override
      *            will be automatically cleared, or {@code 0} to leave in the
      *            requested state until explicitly cleared, or the next reboot,
@@ -3063,17 +3098,14 @@
      * </ul>
      *
      * @param subId the subscriber this override applies to.
-     * @param overrideCongested set if the subscription should be considered
-     *            congested.
-     * @param networkTypes the network types this override applies to. If no
-     *            network types are specified, override values will be ignored.
-     *            {@see TelephonyManager#getAllNetworkTypes()}
+     * @param overrideCongested set if the subscription should be considered congested.
+     * @param networkTypes the network types this override applies to. If no network types are
+     * specified, override values will be ignored.
      * @param expirationDurationMillis the duration after which the requested override
-     *            will be automatically cleared, or {@code 0} to leave in the
-     *            requested state until explicitly cleared, or the next reboot,
-     *            whichever happens first.
-     * @throws SecurityException if the caller doesn't meet the requirements
-     *            outlined above.
+     * will be automatically cleared, or {@code 0} to leave in the requested state until explicitly
+     * cleared, or the next reboot, whichever happens first.
+     *
+     * @throws SecurityException if the caller doesn't meet the requirements outlined above.
      */
     public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested,
             @NonNull @Annotation.NetworkType int[] networkTypes,
@@ -3089,10 +3121,11 @@
      *
      * Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
      * true). To check for permissions for non-embedded subscription as well,
-     * {@see android.telephony.TelephonyManager#hasCarrierPrivileges}.
      *
      * @param info The subscription to check.
      * @return whether the app is authorized to manage this subscription per its metadata.
+     *
+     * @see android.telephony.TelephonyManager#hasCarrierPrivileges
      */
     public boolean canManageSubscription(SubscriptionInfo info) {
         return canManageSubscription(info, mContext.getPackageName());
@@ -3105,11 +3138,13 @@
      *
      * Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
      * true). To check for permissions for non-embedded subscription as well,
-     * {@see android.telephony.TelephonyManager#hasCarrierPrivileges}.
      *
      * @param info The subscription to check.
      * @param packageName Package name of the app to check.
+     *
      * @return whether the app is authorized to manage this subscription per its access rules.
+     *
+     * @see android.telephony.TelephonyManager#hasCarrierPrivileges
      * @hide
      */
     @SystemApi
@@ -3423,21 +3458,20 @@
 
     /**
      * Remove a list of subscriptions from their subscription group.
-     * See {@link #createSubscriptionGroup(List)} for more details.
      *
      * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
-     * permission or had carrier privilege permission on the subscriptions:
-     * {@link TelephonyManager#hasCarrierPrivileges()} or
-     * {@link #canManageSubscription(SubscriptionInfo)}
-     *
-     * @throws SecurityException if the caller doesn't meet the requirements
-     *             outlined above.
-     * @throws IllegalArgumentException if the some subscriptions in the list doesn't belong
-     *             the specified group.
-     * @throws IllegalStateException if Telephony service is in bad state.
+     * permission or has carrier privilege permission on all of the subscriptions provided in
+     * {@code subIdList}.
      *
      * @param subIdList list of subId that need removing from their groups.
+     * @param groupUuid The UUID of the subscription group.
      *
+     * @throws SecurityException if the caller doesn't meet the requirements outlined above.
+     * @throws IllegalArgumentException if the some subscriptions in the list doesn't belong the
+     * specified group.
+     * @throws IllegalStateException if Telephony service is in bad state.
+     *
+     * @see #createSubscriptionGroup(List)
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -3445,7 +3479,7 @@
             @NonNull ParcelUuid groupUuid) {
         Preconditions.checkNotNull(subIdList, "subIdList can't be null.");
         Preconditions.checkNotNull(groupUuid, "groupUuid can't be null.");
-        String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+        String callingPackage = mContext != null ? mContext.getOpPackageName() : "<unknown>";
         if (VDBG) {
             logd("[removeSubscriptionsFromGroup]");
         }
@@ -3455,7 +3489,7 @@
         try {
             ISub iSub = TelephonyManager.getSubscriptionService();
             if (iSub != null) {
-                iSub.removeSubscriptionsFromGroup(subIdArray, groupUuid, pkgForDebug);
+                iSub.removeSubscriptionsFromGroup(subIdArray, groupUuid, callingPackage);
             } else {
                 if (!isSystemProcess()) {
                     throw new IllegalStateException("telephony service is null.");
@@ -3493,7 +3527,6 @@
      * @param groupUuid of which list of subInfo will be returned.
      * @return list of subscriptionInfo that belong to the same group, including the given
      * subscription itself. It will return an empty list if no subscription belongs to the group.
-     *
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
@@ -3533,7 +3566,8 @@
      * want to see their own hidden subscriptions.
      *
      * @param info the subscriptionInfo to check against.
-     * @return true if this subscription should be visible to the API caller.
+     *
+     * @return {@code true} if this subscription should be visible to the API caller.
      *
      * @hide
      */
@@ -3606,9 +3640,9 @@
      * <p>
      * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
      *
+     * @param subscriptionId Subscription to be enabled or disabled. It could be a eSIM or pSIM
+     * subscription.
      * @param enable whether user is turning it on or off.
-     * @param subscriptionId Subscription to be enabled or disabled.
-     *                       It could be a eSIM or pSIM subscription.
      *
      * @return whether the operation is successful.
      *
@@ -3641,8 +3675,6 @@
      * available from SubscriptionInfo.areUiccApplicationsEnabled() will be updated
      * immediately.)
      *
-     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
-     *
      * @param subscriptionId which subscription to operate on.
      * @param enabled whether uicc applications are enabled or disabled.
      * @hide
@@ -3675,8 +3707,6 @@
      * It provides whether a physical SIM card can be disabled without taking it out, which is done
      * via {@link #setSubscriptionEnabled(int, boolean)} API.
      *
-     * Requires Permission: READ_PRIVILEGED_PHONE_STATE.
-     *
      * @return whether can disable subscriptions on physical SIMs.
      *
      * @hide
@@ -3704,13 +3734,9 @@
     }
 
     /**
-     * Check if a subscription is active.
+     * Check if the subscription is currently active in any slot.
      *
-     * @param subscriptionId The subscription id to check.
-     *
-     * @return {@code true} if the subscription is active.
-     *
-     * @throws IllegalArgumentException if the provided slot index is invalid.
+     * @param subscriptionId The subscription id.
      *
      * @hide
      */
@@ -3730,15 +3756,14 @@
     }
 
     /**
-     * Set the device to device status sharing user preference for a subscription ID. The setting
+     * Set the device to device status sharing user preference for a subscription id. The setting
      * app uses this method to indicate with whom they wish to share device to device status
      * information.
      *
-     * @param subscriptionId the unique Subscription ID in database.
-     * @param sharing the status sharing preference.
+     * @param subscriptionId The subscription id.
+     * @param sharing The status sharing preference.
      *
-     * @throws IllegalArgumentException if the subscription does not exist, or the sharing
-     * preference is invalid.
+     * @throws SecurityException if the caller doesn't have permissions required.
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setDeviceToDeviceStatusSharingPreference(int subscriptionId,
@@ -3755,6 +3780,8 @@
      * Returns the user-chosen device to device status sharing preference
      * @param subscriptionId Subscription id of subscription
      * @return The device to device status sharing preference
+     *
+     * @throws SecurityException if the caller doesn't have permissions required.
      */
     public @DeviceToDeviceStatusSharingPreference int getDeviceToDeviceStatusSharingPreference(
             int subscriptionId) {
@@ -3766,15 +3793,14 @@
     }
 
     /**
-     * Set the list of contacts that allow device to device status sharing for a subscription ID.
+     * Set the list of contacts that allow device to device status sharing for a subscription id.
      * The setting app uses this method to indicate with whom they wish to share device to device
      * status information.
      *
-     * @param subscriptionId The unique Subscription ID in database.
+     * @param subscriptionId The subscription id.
      * @param contacts The list of contacts that allow device to device status sharing.
      *
-     * @throws IllegalArgumentException if the subscription does not exist, or contacts is
-     * {@code null}.
+     * @throws SecurityException if the caller doesn't have permissions required.
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setDeviceToDeviceStatusSharingContacts(int subscriptionId,
@@ -3790,38 +3816,46 @@
     }
 
     /**
-     * Returns the list of contacts that allow device to device status sharing.
-     * @param subscriptionId Subscription id of subscription
-     * @return The list of contacts that allow device to device status sharing
+     * Get the list of contacts that allow device to device status sharing.
+     *
+     * @param subscriptionId Subscription id.
+     *
+     * @return The list of contacts that allow device to device status sharing.
      */
-    public @NonNull List<Uri> getDeviceToDeviceStatusSharingContacts(
-            int subscriptionId) {
-        if (VDBG) {
-            logd("[getDeviceToDeviceStatusSharingContacts] + subId: " + subscriptionId);
+    public @NonNull List<Uri> getDeviceToDeviceStatusSharingContacts(int subscriptionId) {
+        String result = getStringSubscriptionProperty(mContext, subscriptionId,
+                D2D_STATUS_SHARING_SELECTED_CONTACTS);
+        if (result != null) {
+            try {
+                byte[] b = Base64.decode(result, Base64.DEFAULT);
+                ByteArrayInputStream bis = new ByteArrayInputStream(b);
+                ObjectInputStream ois = new ObjectInputStream(bis);
+                List<String> contacts = ArrayList.class.cast(ois.readObject());
+                List<Uri> uris = new ArrayList<>();
+                for (String contact : contacts) {
+                    uris.add(Uri.parse(contact));
+                }
+                return uris;
+            } catch (IOException e) {
+                logd("getDeviceToDeviceStatusSharingContacts IO exception");
+            } catch (ClassNotFoundException e) {
+                logd("getDeviceToDeviceStatusSharingContacts ClassNotFound exception");
+            }
         }
-        return getContactsFromSubscriptionProperty(subscriptionId,
-                D2D_STATUS_SHARING_SELECTED_CONTACTS, mContext);
+        return new ArrayList<>();
     }
 
     /**
-     * Get the active subscription id by logical SIM slot index.
-     *
-     * @param slotIndex The logical SIM slot index.
-     * @return The active subscription id.
-     *
-     * @throws IllegalArgumentException if the provided slot index is invalid.
-     *
+     * DO NOT USE.
+     * This API is designed for features that are not finished at this point. Do not call this API.
      * @hide
+     * TODO b/135547512: further clean up
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public int getEnabledSubscriptionId(int slotIndex) {
         int subId = INVALID_SUBSCRIPTION_ID;
 
-        if (!isValidSlotIndex(slotIndex)) {
-            throw new IllegalArgumentException("Invalid slot index " + slotIndex);
-        }
-
         try {
             ISub iSub = TelephonyManager.getSubscriptionService();
             if (iSub != null) {
@@ -3863,7 +3897,7 @@
     /**
      * Get active data subscription id. Active data subscription refers to the subscription
      * currently chosen to provide cellular internet connection to the user. This may be
-     * different from getDefaultDataSubscriptionId().
+     * different from {@link #getDefaultDataSubscriptionId()}.
      *
      * @return Active data subscription id if any is chosen, or {@link #INVALID_SUBSCRIPTION_ID} if
      * not.
@@ -4061,12 +4095,15 @@
      * security-related or other sensitive scenarios.
      *
      * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
-     *                       for the default one.
+     * for the default one.
      * @param source the source of the phone number, one of the PHONE_NUMBER_SOURCE_* constants.
+     *
      * @return the phone number, or an empty string if not available.
+     *
      * @throws IllegalArgumentException if {@code source} is invalid.
      * @throws IllegalStateException if the telephony process is not currently available.
      * @throws SecurityException if the caller doesn't have permissions required.
+     *
      * @see #PHONE_NUMBER_SOURCE_UICC
      * @see #PHONE_NUMBER_SOURCE_CARRIER
      * @see #PHONE_NUMBER_SOURCE_IMS
@@ -4123,8 +4160,10 @@
      * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
      *                       for the default one.
      * @return the phone number, or an empty string if not available.
+     *
      * @throws IllegalStateException if the telephony process is not currently available.
      * @throws SecurityException if the caller doesn't have permissions required.
+     *
      * @see #getPhoneNumber(int, int)
      */
     @RequiresPermission(anyOf = {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 851697a..5d49413 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2935,7 +2935,7 @@
     public static final int NETWORK_TYPE_HSPA = TelephonyProtoEnums.NETWORK_TYPE_HSPA; // = 10.
     /**
      * Current network is iDen
-     * @deprecated Legacy network type no longer being used.
+     * @deprecated Legacy network type no longer being used starting in Android U.
      */
     @Deprecated
     public static final int NETWORK_TYPE_IDEN = TelephonyProtoEnums.NETWORK_TYPE_IDEN; // = 11.
@@ -13960,7 +13960,7 @@
      * If used, will be converted to {@link #NETWORK_TYPE_BITMASK_LTE}.
      * network type bitmask indicating the support of radio tech LTE CA (carrier aggregation).
      *
-     * @deprecated Please use {@link #NETWORK_TYPE_BITMASK_LTE} instead.
+     * @deprecated Please use {@link #NETWORK_TYPE_BITMASK_LTE} instead. Deprecated in Android U.
      */
     @Deprecated
     public static final long NETWORK_TYPE_BITMASK_LTE_CA = (1 << (NETWORK_TYPE_LTE_CA -1));
@@ -17767,4 +17767,59 @@
         }
         return TelephonyManager.SIM_STATE_UNKNOWN;
     }
+
+    /**
+     * Set the UE's ability to accept/reject null ciphered and null integrity-protected connections.
+     *
+     * The modem is required to ignore this in case of an emergency call.
+     *
+     * <p>Requires permission: android.Manifest.MODIFY_PHONE_STATE</p>
+     *
+     * @param enabled if null ciphered and null integrity protected connections are permitted
+     * @throws IllegalStateException if the Telephony process is not currently available
+     * @throws SecurityException if the caller does not have the required privileges
+     * @throws UnsupportedOperationException if the modem does not support disabling null ciphers.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setNullCipherAndIntegrityEnabled(boolean enabled) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.setNullCipherAndIntegrityEnabled(enabled);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "setNullCipherAndIntegrityEnabled RemoteException", ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the value of the global preference for null cipher and integriy enablement.
+     * Note: This does not return the state of the modem, only the persisted global preference.
+     *
+     * <p>Requires permission: android.Manifest.READ_PHONE_STATE</p>
+     *
+     * @throws IllegalStateException if the Telephony process is not currently available
+     * @throws SecurityException if the caller does not have the required privileges
+     * @throws UnsupportedOperationException if the modem does not support disabling null ciphers.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+    public boolean isNullCipherAndIntegrityPreferenceEnabled() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.isNullCipherAndIntegrityPreferenceEnabled();
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "isNullCipherAndIntegrityPreferenceEnabled RemoteException", ex);
+            ex.rethrowFromSystemServer();
+        }
+        return true;
+    }
 }
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index a2d2019..cdb7d7c 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -1570,8 +1570,8 @@
 
     /**
      * Returns whether the passing portIndex is available.
-     * A port is available if it is active without enabled profile on it or
-     * calling app has carrier privilege over the profile installed on the selected port.
+     * A port is available if it is active without an enabled profile on it or calling app can
+     * activate a new profile on the selected port without any user interaction.
      * Always returns false if the cardId is a physical card.
      *
      * @param portIndex is an enumeration of the ports available on the UICC.
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index 9996b86..7a800a5 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Binder;
@@ -75,6 +76,41 @@
      */
     int REGISTRATION_STATE_REGISTERED = 2;
 
+    /** @hide */
+    @IntDef(prefix = {"SUGGESTED_ACTION_"},
+            value = {
+            SUGGESTED_ACTION_NONE,
+            SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK,
+            SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SuggestedAction {}
+
+    /**
+     * Default value. No action is suggested when IMS registration fails.
+     * @hide
+     */
+    @SystemApi
+    public static final int SUGGESTED_ACTION_NONE = 0;
+
+    /**
+     * Indicates that the IMS registration is failed with fatal error such as 403 or 404
+     * on all P-CSCF addresses. The radio shall block the current PLMN or disable
+     * the RAT as per the carrier requirements.
+     * @hide
+     */
+    @SystemApi
+    public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK = 1;
+
+    /**
+     * Indicates that the IMS registration on current PLMN failed multiple times.
+     * The radio shall block the current PLMN or disable the RAT during EPS or 5GS mobility
+     * management timer value as per the carrier requirements.
+     * @hide
+     */
+    @SystemApi
+    public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT = 2;
+
     /**@hide*/
     // Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
     // and WWAN are more accurate constants.
@@ -167,12 +203,12 @@
             }
 
             @Override
-            public void onDeregistered(ImsReasonInfo info) {
+            public void onDeregistered(ImsReasonInfo info, @SuggestedAction int suggestedAction) {
                 if (mLocalCallback == null) return;
 
                 final long callingIdentity = Binder.clearCallingIdentity();
                 try {
-                    mExecutor.execute(() -> mLocalCallback.onUnregistered(info));
+                    mExecutor.execute(() -> mLocalCallback.onUnregistered(info, suggestedAction));
                 } finally {
                     restoreCallingIdentity(callingIdentity);
                 }
@@ -258,6 +294,21 @@
         }
 
         /**
+         * Notifies the framework when the IMS Provider is unregistered from the IMS network.
+         *
+         * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
+         * @param suggestedAction the expected behavior of radio protocol stack.
+         *
+         * @hide
+         */
+        @SystemApi
+        public void onUnregistered(@NonNull ImsReasonInfo info,
+                @SuggestedAction int suggestedAction) {
+            // Default impl to keep backwards compatibility with old implementations
+            onUnregistered(info);
+        }
+
+        /**
          * A failure has occurred when trying to handover registration to another technology type.
          *
          * @param imsTransportType The transport type that has failed to handover registration to.
diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
index ea4480d..cf7e9e16 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
@@ -64,6 +64,7 @@
     void setSmsListener(IImsSmsListener l);
     oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry,
             in byte[] pdu);
+    oneway void onMemoryAvailable(int token);
     oneway void acknowledgeSms(int token, int messageRef, int result);
     oneway void acknowledgeSmsWithPdu(int token, int messageRef, int result, in byte[] pdu);
     oneway void acknowledgeSmsReport(int token, int messageRef, int result);
diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
index 5246470..640426b 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
@@ -34,4 +34,5 @@
     void onIncomingCall(IImsCallSession c, in Bundle extras);
     void onRejectedCall(in ImsCallProfile callProfile, in ImsReasonInfo reason);
     oneway void onVoiceMessageCountUpdate(int count);
+    oneway void onAudioModeIsVoipChanged(int imsAudioHandler);
 }
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
index 179407c..069eb46 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
@@ -31,7 +31,7 @@
 oneway interface IImsRegistrationCallback {
    void onRegistered(in ImsRegistrationAttributes attr);
    void onRegistering(in ImsRegistrationAttributes attr);
-   void onDeregistered(in ImsReasonInfo info);
+   void onDeregistered(in ImsReasonInfo info, int suggestedAction);
    void onTechnologyChangeFailed(int imsRadioTech, in ImsReasonInfo info);
    void onSubscriberAssociatedUriChanged(in Uri[] uris);
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 8184424..4710c1f 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -269,6 +269,12 @@
         }
 
         @Override
+        public void onMemoryAvailable(int token) {
+            executeMethodAsyncNoException(() -> MmTelFeature.this
+                    .onMemoryAvailable(token), "onMemoryAvailable");
+        }
+
+        @Override
         public void acknowledgeSms(int token, int messageRef, int result) {
             executeMethodAsyncNoException(() -> MmTelFeature.this
                     .acknowledgeSms(token, messageRef, result), "acknowledgeSms");
@@ -573,6 +579,17 @@
         public void onVoiceMessageCountUpdate(int count) {
 
         }
+
+        /**
+         * Called to set the audio handler for this connection.
+         * @param imsAudioHandler an {@link ImsAudioHandler} used to handle the audio
+         *        for this IMS call.
+         * @hide
+         */
+        @Override
+        public void onAudioModeIsVoipChanged(int imsAudioHandler) {
+
+        }
     }
 
     /**
@@ -622,6 +639,29 @@
     public static final String EXTRA_IS_UNKNOWN_CALL =
             "android.telephony.ims.feature.extra.IS_UNKNOWN_CALL";
 
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+                    AUDIO_HANDLER_ANDROID,
+                    AUDIO_HANDLER_BASEBAND
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ImsAudioHandler {}
+
+    /**
+    * Audio Handler - Android
+    * @hide
+    */
+    @SystemApi
+    public static final int AUDIO_HANDLER_ANDROID = 0;
+
+    /**
+    * Audio Handler - Baseband
+    * @hide
+    */
+    @SystemApi
+    public static final int AUDIO_HANDLER_BASEBAND = 1;
+
     private IImsMmTelListener mListener;
 
     /**
@@ -768,6 +808,28 @@
     }
 
     /**
+     * Sets the audio handler for this connection. The vendor IMS stack will invoke this API
+     * to inform Telephony/Telecom layers about which audio handlers i.e. either Android or Modem
+     * shall be used for handling the IMS call audio.
+     *
+     * @param imsAudioHandler {@link MmTelFeature#ImsAudioHandler} used to handle the audio
+     *        for this IMS call.
+     * @hide
+     */
+    @SystemApi
+    public final void setCallAudioHandler(@ImsAudioHandler int imsAudioHandler) {
+        IImsMmTelListener listener = getListener();
+        if (listener == null) {
+            throw new IllegalStateException("Session is not available.");
+        }
+        try {
+            listener.onAudioModeIsVoipChanged(imsAudioHandler);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
      * Provides the MmTelFeature with the ability to return the framework Capability Configuration
      * for a provided Capability. If the framework calls {@link #changeEnabledCapabilities} and
      * includes a capability A to enable or disable, this method should return the correct enabled
@@ -1089,6 +1151,10 @@
         getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu);
     }
 
+    private void onMemoryAvailable(int token) {
+        getSmsImplementation().onMemoryAvailable(token);
+    }
+
     private void acknowledgeSms(int token, int messageRef,
             @ImsSmsImplBase.DeliverStatusResult int result) {
         getSmsImplementation().acknowledgeSms(token, messageRef, result);
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index 897b57f..ad8a936 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.telephony.ims.ImsService;
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.RcsClientConfiguration;
 import android.telephony.ims.RcsConfig;
@@ -553,7 +554,7 @@
     ImsConfigStub mImsConfigStub;
 
     /**
-     * Create a ImsConfig using the Executor specified for methods being called by the
+     * Create an ImsConfig using the Executor specified for methods being called by the
      * framework.
      * @param executor The executor for the framework to use when executing the methods overridden
      * by the implementation of ImsConfig.
@@ -569,6 +570,9 @@
         mImsConfigStub = new ImsConfigStub(this, null);
     }
 
+    /**
+     * Create an ImsConfig using the Executor defined in {@link ImsService#getExecutor}
+     */
     public ImsConfigImplBase() {
         mImsConfigStub = new ImsConfigStub(this, null);
     }
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index b69eb6f..117593a 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -64,7 +64,8 @@
                     REGISTRATION_TECH_LTE,
                     REGISTRATION_TECH_IWLAN,
                     REGISTRATION_TECH_CROSS_SIM,
-                    REGISTRATION_TECH_NR
+                    REGISTRATION_TECH_NR,
+                    REGISTRATION_TECH_3G
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ImsRegistrationTech {}
@@ -92,10 +93,15 @@
     public static final int REGISTRATION_TECH_NR = 3;
 
     /**
+     * This ImsService is registered to IMS via 3G.
+     */
+    public static final int REGISTRATION_TECH_3G = 4;
+
+    /**
      * This is used to check the upper range of registration tech
      * @hide
      */
-    public static final int REGISTRATION_TECH_MAX = REGISTRATION_TECH_NR + 1;
+    public static final int REGISTRATION_TECH_MAX = REGISTRATION_TECH_3G + 1;
 
     // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current
     // state.
@@ -301,6 +307,8 @@
     private int mRegistrationState = REGISTRATION_STATE_UNKNOWN;
     // Locked on mLock, create unspecified disconnect cause.
     private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo();
+    // Locked on mLock
+    private int mLastDisconnectSuggestedAction = RegistrationManager.SUGGESTED_ACTION_NONE;
 
     // We hold onto the uris each time they change so that we can send it to a callback when its
     // first added.
@@ -467,12 +475,37 @@
      */
     @SystemApi
     public final void onDeregistered(ImsReasonInfo info) {
-        updateToDisconnectedState(info);
+        // Default impl to keep backwards compatibility with old implementations
+        onDeregistered(info, RegistrationManager.SUGGESTED_ACTION_NONE);
+    }
+
+    /**
+     * Notify the framework that the device is disconnected from the IMS network.
+     * <p>
+     * Note: Prior to calling {@link #onDeregistered(ImsReasonInfo,int)}, you should ensure that any
+     * changes to {@link android.telephony.ims.feature.ImsFeature} capability availability is sent
+     * to the framework.  For example,
+     * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}
+     * and
+     * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}
+     * may be set to unavailable to ensure the framework knows these services are no longer
+     * available due to de-registration.  If you do not report capability changes impacted by
+     * de-registration, the framework will not know which features are no longer available as a
+     * result.
+     *
+     * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
+     * @param suggestedAction the expected behavior of radio protocol stack.
+     * @hide This API is not part of the Android public SDK API
+     */
+    @SystemApi
+    public final void onDeregistered(@Nullable ImsReasonInfo info,
+            @RegistrationManager.SuggestedAction int suggestedAction) {
+        updateToDisconnectedState(info, suggestedAction);
         // ImsReasonInfo should never be null.
         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
         mCallbacks.broadcastAction((c) -> {
             try {
-                c.onDeregistered(reasonInfo);
+                c.onDeregistered(reasonInfo, suggestedAction);
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, e + "onDeregistered() - Skipping callback.");
             }
@@ -531,10 +564,12 @@
             mRegistrationAttributes = attributes;
             mRegistrationState = newState;
             mLastDisconnectCause = null;
+            mLastDisconnectSuggestedAction = RegistrationManager.SUGGESTED_ACTION_NONE;
         }
     }
 
-    private void updateToDisconnectedState(ImsReasonInfo info) {
+    private void updateToDisconnectedState(ImsReasonInfo info,
+            @RegistrationManager.SuggestedAction int suggestedAction) {
         synchronized (mLock) {
             //We don't want to send this info over if we are disconnected
             mUrisSet = false;
@@ -544,6 +579,7 @@
                     RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
             if (info != null) {
                 mLastDisconnectCause = info;
+                mLastDisconnectSuggestedAction = suggestedAction;
             } else {
                 Log.w(LOG_TAG, "updateToDisconnectedState: no ImsReasonInfo provided.");
                 mLastDisconnectCause = new ImsReasonInfo();
@@ -560,18 +596,20 @@
         int state;
         ImsRegistrationAttributes attributes;
         ImsReasonInfo disconnectInfo;
+        int suggestedAction;
         boolean urisSet;
         Uri[] uris;
         synchronized (mLock) {
             state = mRegistrationState;
             attributes = mRegistrationAttributes;
             disconnectInfo = mLastDisconnectCause;
+            suggestedAction = mLastDisconnectSuggestedAction;
             urisSet = mUrisSet;
             uris = mUris;
         }
         switch (state) {
             case RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED: {
-                c.onDeregistered(disconnectInfo);
+                c.onDeregistered(disconnectInfo, suggestedAction);
                 break;
             }
             case RegistrationManager.REGISTRATION_STATE_REGISTERING: {
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index 66833d1..daab84e 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -171,6 +171,20 @@
     }
 
     /**
+     * This method will be triggered by the platform when memory becomes available to receive SMS
+     * after a memory full event. This method should be implemented by IMS providers to
+     * send RP-SMMA notification from SMS Relay Layer to server over IMS as per section 7.3.2 of
+     * TS 124.11. Once the RP-SMMA Notification is sent to the network. The network will deliver all
+     * the pending messages which failed due to Unavailability of Memory.
+     *
+     * @param token unique token generated in {@link ImsSmsDispatcher#onMemoryAvailable(void)} that
+     *  should be used when triggering callbacks for this specific message.
+     */
+    public void onMemoryAvailable(int token) {
+        // Base Implementation - Should be overridden
+    }
+
+    /**
      * This method will be triggered by the platform after
      * {@link #onSmsReceived(int, String, byte[])} has been called to deliver the result to the IMS
      * provider.
diff --git a/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl b/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl
index 89620ea..1788bda 100644
--- a/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl
+++ b/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl
@@ -38,4 +38,6 @@
 
     String getDefaultCarrierServicePackageName();
 
+    PersistableBundle getConfigSubsetForSubIdWithFeature(int subId, String callingPackage,
+                String callingFeatureId, in String[] carrierConfigs);
 }
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 9ec3c67..0e23f36 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -530,6 +530,25 @@
             int subId, String callingPkg, String prefixes, in PendingIntent intent);
 
     /**
+     * set Memory Status in SmsStorageMonitor
+     *
+     * @param subId the subscription Id.
+     * @param callingPackage the package name of the calling app.
+     * @param isStorageAvailable sets StorageAvailable to false or true
+     *   for testing behaviour of SmsStorageMonitor
+     */
+    void setStorageMonitorMemoryStatusOverride(int subId, boolean isStorageAvailable);
+
+     /**
+     * reset Memory Status change made by TestApi setStorageMonitorMemoryStatusOverride
+     * in SmsStorageMonitor
+     *
+     * @param subId the subscription Id.
+     * @param callingPackage the package name of the calling app.
+     */
+    void clearStorageMonitorMemoryStatusOverride(int subId);
+
+    /**
      * Check if the destination is a possible premium short code.
      *
      * @param destAddress the destination address to test for possible short code
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index c361d5b..6864556 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -192,6 +192,16 @@
     }
 
     @Override
+    public void setStorageMonitorMemoryStatusOverride(int subId, boolean storageAvailable) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clearStorageMonitorMemoryStatusOverride(int subId) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public int checkSmsShortCodeDestination(int subid, String callingPackage,
             String callingFeatureId, String destAddress, String countryIso) {
         throw new UnsupportedOperationException();
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 280d259..c5f6902 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -242,8 +242,6 @@
 
     int getDefaultSubId();
 
-    int clearSubInfo();
-
     int getPhoneId(int subId);
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 7ede4ab..9445d076 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2636,4 +2636,26 @@
      * @param slotIndex Logical SIM slot index.
      */
     int getSimStateForSlotIndex(int slotIndex);
+
+    /**
+     * Set whether the radio is able to connect with null ciphering or integrity
+     * algorithms. This is a global setting and will apply to all active subscriptions
+     * and all new subscriptions after this.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     *
+     * @param enabled when true, null  cipher and integrity algorithms are allowed.
+     * @hide
+     */
+    void setNullCipherAndIntegrityEnabled(boolean enabled);
+
+    /**
+    * Get whether the radio is able to connect with null ciphering or integrity
+    * algorithms. Note that this retrieves the phone-global preference and not
+    * the state of the radio.
+    *
+    * @hide
+    */
+    boolean isNullCipherAndIntegrityPreferenceEnabled();
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index afc5f65..ca4c6a3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -106,7 +106,9 @@
     @IwTest(focusArea = "ime")
     override fun cujCompleted() {
         super.cujCompleted()
-        navBarLayerPositionAtStartAndEnd()
+        if (!flicker.scenario.isTablet) {
+            navBarLayerPositionAtStartAndEnd()
+        }
         imeLayerBecomesInvisible()
         imeAppLayerIsAlwaysVisible()
         imeAppWindowIsAlwaysVisible()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index aedf965..730c4f5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -104,7 +104,9 @@
     @IwTest(focusArea = "ime")
     override fun cujCompleted() {
         super.cujCompleted()
-        navBarLayerPositionAtStartAndEnd()
+        if (!flicker.scenario.isTablet) {
+            navBarLayerPositionAtStartAndEnd()
+        }
         imeLayerBecomesInvisible()
         imeAppWindowBecomesInvisible()
         imeWindowBecomesInvisible()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 54f38c3..2447474 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -213,6 +213,14 @@
             // not yet tablet compatible
             appLayerRotates()
             appLayerAlwaysVisible()
+            // not tablet compatible
+            navBarLayerIsVisibleAtStartAndEnd()
+            navBarWindowIsAlwaysVisible()
+        }
+
+        if (flicker.scenario.isTablet) {
+            taskBarLayerIsVisibleAtStartAndEnd()
+            taskBarWindowIsAlwaysVisible()
         }
 
         appWindowFullScreen()
@@ -223,10 +231,6 @@
         appLayerRotates_StartingPos()
         appLayerRotates_EndingPos()
         entireScreenCovered()
-        navBarLayerIsVisibleAtStartAndEnd()
-        navBarWindowIsAlwaysVisible()
-        taskBarLayerIsVisibleAtStartAndEnd()
-        taskBarWindowIsAlwaysVisible()
         visibleLayersShownMoreThanOneConsecutiveEntry()
         visibleWindowsShownMoreThanOneConsecutiveEntry()
     }
diff --git a/tests/vcn/java/android/net/vcn/VcnConfigTest.java b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
index 7ac51b7..b313c9f 100644
--- a/tests/vcn/java/android/net/vcn/VcnConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
@@ -16,7 +16,12 @@
 
 package android.net.vcn;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -24,6 +29,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Parcel;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -42,19 +48,36 @@
     private static final Set<VcnGatewayConnectionConfig> GATEWAY_CONNECTION_CONFIGS =
             Collections.singleton(VcnGatewayConnectionConfigTest.buildTestConfig());
 
+    private static final Set<Integer> RESTRICTED_TRANSPORTS = new ArraySet<>();
+
+    static {
+        RESTRICTED_TRANSPORTS.add(TRANSPORT_WIFI);
+        RESTRICTED_TRANSPORTS.add(TRANSPORT_CELLULAR);
+    }
+
     private final Context mContext = mock(Context.class);
 
     // Public visibility for VcnManagementServiceTest
-    public static VcnConfig buildTestConfig(@NonNull Context context) {
+    public static VcnConfig buildTestConfig(
+            @NonNull Context context, Set<Integer> restrictedTransports) {
         VcnConfig.Builder builder = new VcnConfig.Builder(context);
 
         for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) {
             builder.addGatewayConnectionConfig(gatewayConnectionConfig);
         }
 
+        if (restrictedTransports != null) {
+            builder.setRestrictedUnderlyingNetworkTransports(restrictedTransports);
+        }
+
         return builder.build();
     }
 
+    // Public visibility for VcnManagementServiceTest
+    public static VcnConfig buildTestConfig(@NonNull Context context) {
+        return buildTestConfig(context, null);
+    }
+
     @Before
     public void setUp() throws Exception {
         doReturn(TEST_PACKAGE_NAME).when(mContext).getOpPackageName();
@@ -91,11 +114,25 @@
     }
 
     @Test
-    public void testBuilderAndGetters() {
+    public void testBuilderAndGettersDefaultValues() {
         final VcnConfig config = buildTestConfig(mContext);
 
         assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName());
         assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
+        assertFalse(config.isTestModeProfile());
+        assertEquals(
+                Collections.singleton(TRANSPORT_WIFI),
+                config.getRestrictedUnderlyingNetworkTransports());
+    }
+
+    @Test
+    public void testBuilderAndGettersConfigRestrictedTransports() {
+        final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+
+        assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName());
+        assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
+        assertFalse(config.isTestModeProfile());
+        assertEquals(RESTRICTED_TRANSPORTS, config.getRestrictedUnderlyingNetworkTransports());
     }
 
     @Test
@@ -106,6 +143,24 @@
     }
 
     @Test
+    public void testPersistableBundleWithRestrictedTransports() {
+        final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+
+        assertEquals(config, new VcnConfig(config.toPersistableBundle()));
+    }
+
+    @Test
+    public void testEqualityWithRestrictedTransports() {
+        final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+        final VcnConfig configEqual = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+        final VcnConfig configNotEqual =
+                buildTestConfig(mContext, Collections.singleton(TRANSPORT_WIFI));
+
+        assertEquals(config, configEqual);
+        assertNotEquals(config, configNotEqual);
+    }
+
+    @Test
     public void testParceling() {
         final VcnConfig config = buildTestConfig(mContext);
 
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 2aef9ae..4040888 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -19,9 +19,11 @@
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
 import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES;
 import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_TEMPLATES_KEY;
+import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
@@ -42,7 +44,9 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
@@ -79,6 +83,9 @@
             };
     public static final int MAX_MTU = 1360;
 
+    private static final Set<Integer> GATEWAY_OPTIONS =
+            Collections.singleton(VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY);
+
     public static final IkeTunnelConnectionParams TUNNEL_CONNECTION_PARAMS =
             TunnelConnectionParamsUtilsTest.buildTestParams();
 
@@ -109,10 +116,16 @@
                 TUNNEL_CONNECTION_PARAMS);
     }
 
-    private static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(
-            VcnGatewayConnectionConfig.Builder builder, int... exposedCaps) {
+    private static VcnGatewayConnectionConfig buildTestConfigWithExposedCapsAndOptions(
+            VcnGatewayConnectionConfig.Builder builder,
+            Set<Integer> gatewayOptions,
+            int... exposedCaps) {
         builder.setRetryIntervalsMillis(RETRY_INTERVALS_MS).setMaxMtu(MAX_MTU);
 
+        for (int option : gatewayOptions) {
+            builder.addGatewayOption(option);
+        }
+
         for (int caps : exposedCaps) {
             builder.addExposedCapability(caps);
         }
@@ -120,11 +133,28 @@
         return builder.build();
     }
 
+    private static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(
+            VcnGatewayConnectionConfig.Builder builder, int... exposedCaps) {
+        return buildTestConfigWithExposedCapsAndOptions(
+                builder, Collections.emptySet(), exposedCaps);
+    }
+
     // Public for use in VcnGatewayConnectionTest
     public static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(int... exposedCaps) {
         return buildTestConfigWithExposedCaps(newBuilder(), exposedCaps);
     }
 
+    private static VcnGatewayConnectionConfig buildTestConfigWithGatewayOptions(
+            VcnGatewayConnectionConfig.Builder builder, Set<Integer> gatewayOptions) {
+        return buildTestConfigWithExposedCapsAndOptions(builder, gatewayOptions, EXPOSED_CAPS);
+    }
+
+    // Public for use in VcnGatewayConnectionTest
+    public static VcnGatewayConnectionConfig buildTestConfigWithGatewayOptions(
+            Set<Integer> gatewayOptions) {
+        return buildTestConfigWithExposedCapsAndOptions(newBuilder(), gatewayOptions, EXPOSED_CAPS);
+    }
+
     @Test
     public void testBuilderRequiresNonNullGatewayConnectionName() {
         try {
@@ -211,6 +241,15 @@
     }
 
     @Test
+    public void testBuilderRequiresValidOption() {
+        try {
+            newBuilder().addGatewayOption(-1);
+            fail("Expected exception due to the invalid VCN gateway option");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    @Test
     public void testBuilderAndGetters() {
         final VcnGatewayConnectionConfig config = buildTestConfig();
 
@@ -225,6 +264,20 @@
 
         assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis());
         assertEquals(MAX_MTU, config.getMaxMtu());
+
+        assertFalse(
+                config.hasGatewayOption(
+                        VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY));
+    }
+
+    @Test
+    public void testBuilderAndGettersWithOptions() {
+        final VcnGatewayConnectionConfig config =
+                buildTestConfigWithGatewayOptions(GATEWAY_OPTIONS);
+
+        for (int option : GATEWAY_OPTIONS) {
+            assertTrue(config.hasGatewayOption(option));
+        }
     }
 
     @Test
@@ -235,6 +288,14 @@
     }
 
     @Test
+    public void testPersistableBundleWithOptions() {
+        final VcnGatewayConnectionConfig config =
+                buildTestConfigWithGatewayOptions(GATEWAY_OPTIONS);
+
+        assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle()));
+    }
+
+    @Test
     public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() {
         PersistableBundle configBundle = buildTestConfig().toPersistableBundle();
         configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null);
@@ -318,4 +379,27 @@
         assertNotEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesNotEqual);
         assertNotEquals(config, configNotEqual);
     }
+
+    private static VcnGatewayConnectionConfig buildConfigWithGatewayOptionsForEqualityTest(
+            Set<Integer> gatewayOptions) {
+        return buildTestConfigWithGatewayOptions(
+                new VcnGatewayConnectionConfig.Builder(
+                        "buildConfigWithGatewayOptionsForEqualityTest", TUNNEL_CONNECTION_PARAMS),
+                gatewayOptions);
+    }
+
+    @Test
+    public void testVcnGatewayOptionsEquality() throws Exception {
+        final VcnGatewayConnectionConfig config =
+                buildConfigWithGatewayOptionsForEqualityTest(GATEWAY_OPTIONS);
+
+        final VcnGatewayConnectionConfig configEqual =
+                buildConfigWithGatewayOptionsForEqualityTest(GATEWAY_OPTIONS);
+
+        final VcnGatewayConnectionConfig configNotEqual =
+                buildConfigWithGatewayOptionsForEqualityTest(Collections.emptySet());
+
+        assertEquals(config, configEqual);
+        assertNotEquals(config, configNotEqual);
+    }
 }
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 258642ac..075bc5e 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -258,7 +258,7 @@
 
         doReturn(Collections.singleton(TRANSPORT_WIFI))
                 .when(mMockDeps)
-                .getRestrictedTransports(any(), any());
+                .getRestrictedTransports(any(), any(), any());
     }
 
 
@@ -1038,18 +1038,18 @@
                 new LinkProperties());
     }
 
-    private void checkGetRestrictedTransports(
+    private void checkGetRestrictedTransportsFromCarrierConfig(
             ParcelUuid subGrp,
             TelephonySubscriptionSnapshot lastSnapshot,
             Set<Integer> expectedTransports) {
         Set<Integer> result =
                 new VcnManagementService.Dependencies()
-                        .getRestrictedTransports(subGrp, lastSnapshot);
+                        .getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot);
         assertEquals(expectedTransports, result);
     }
 
     @Test
-    public void testGetRestrictedTransports() {
+    public void testGetRestrictedTransportsFromCarrierConfig() {
         final Set<Integer> restrictedTransports = new ArraySet<>();
         restrictedTransports.add(TRANSPORT_CELLULAR);
         restrictedTransports.add(TRANSPORT_WIFI);
@@ -1065,11 +1065,12 @@
                 mock(TelephonySubscriptionSnapshot.class);
         doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
 
-        checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+        checkGetRestrictedTransportsFromCarrierConfig(
+                TEST_UUID_2, lastSnapshot, restrictedTransports);
     }
 
     @Test
-    public void testGetRestrictedTransports_noRestrictPolicyConfigured() {
+    public void testGetRestrictedTransportsFromCarrierConfig_noRestrictPolicyConfigured() {
         final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
 
         final PersistableBundleWrapper carrierConfig =
@@ -1078,17 +1079,54 @@
                 mock(TelephonySubscriptionSnapshot.class);
         doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
 
-        checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+        checkGetRestrictedTransportsFromCarrierConfig(
+                TEST_UUID_2, lastSnapshot, restrictedTransports);
     }
 
     @Test
-    public void testGetRestrictedTransports_noCarrierConfig() {
+    public void testGetRestrictedTransportsFromCarrierConfig_noCarrierConfig() {
         final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
 
         final TelephonySubscriptionSnapshot lastSnapshot =
                 mock(TelephonySubscriptionSnapshot.class);
 
-        checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+        checkGetRestrictedTransportsFromCarrierConfig(
+                TEST_UUID_2, lastSnapshot, restrictedTransports);
+    }
+
+    @Test
+    public void testGetRestrictedTransportsFromCarrierConfigAndVcnConfig() {
+        // Configure restricted transport in CarrierConfig
+        final Set<Integer> restrictedTransportInCarrierConfig =
+                Collections.singleton(TRANSPORT_WIFI);
+
+        PersistableBundle carrierConfigBundle = new PersistableBundle();
+        carrierConfigBundle.putIntArray(
+                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+                restrictedTransportInCarrierConfig.stream().mapToInt(i -> i).toArray());
+        final PersistableBundleWrapper carrierConfig =
+                new PersistableBundleWrapper(carrierConfigBundle);
+
+        final TelephonySubscriptionSnapshot lastSnapshot =
+                mock(TelephonySubscriptionSnapshot.class);
+        doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
+
+        // Configure restricted transport in VcnConfig
+        final Context mockContext = mock(Context.class);
+        doReturn(TEST_PACKAGE_NAME).when(mockContext).getOpPackageName();
+        final VcnConfig vcnConfig =
+                VcnConfigTest.buildTestConfig(
+                        mockContext, Collections.singleton(TRANSPORT_CELLULAR));
+
+        // Verifications
+        final Set<Integer> expectedTransports = new ArraySet<>();
+        expectedTransports.add(TRANSPORT_CELLULAR);
+        expectedTransports.add(TRANSPORT_WIFI);
+
+        Set<Integer> result =
+                new VcnManagementService.Dependencies()
+                        .getRestrictedTransports(TEST_UUID_2, lastSnapshot, vcnConfig);
+        assertEquals(expectedTransports, result);
     }
 
     private void checkGetUnderlyingNetworkPolicy(
@@ -1103,7 +1141,7 @@
         if (isTransportRestricted) {
             restrictedTransports.add(transportType);
         }
-        doReturn(restrictedTransports).when(mMockDeps).getRestrictedTransports(any(), any());
+        doReturn(restrictedTransports).when(mMockDeps).getRestrictedTransports(any(), any(), any());
 
         final VcnUnderlyingNetworkPolicy policy =
                 startVcnAndGetPolicyForTransport(
@@ -1201,7 +1239,7 @@
     public void testGetUnderlyingNetworkPolicyCell_restrictWifi() throws Exception {
         doReturn(Collections.singleton(TRANSPORT_WIFI))
                 .when(mMockDeps)
-                .getRestrictedTransports(any(), any());
+                .getRestrictedTransports(any(), any(), any());
 
         setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isVcnActive */);
 
@@ -1344,6 +1382,23 @@
     }
 
     @Test
+    public void testVcnConfigChangeUpdatesPolicyListener() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
+
+        final Context mockContext = mock(Context.class);
+        doReturn(TEST_PACKAGE_NAME).when(mockContext).getOpPackageName();
+        final VcnConfig vcnConfig =
+                VcnConfigTest.buildTestConfig(
+                        mockContext, Collections.singleton(TRANSPORT_CELLULAR));
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, vcnConfig, TEST_PACKAGE_NAME);
+
+        verify(mMockPolicyListener).onPolicyChanged();
+    }
+
+    @Test
     public void testRemoveVcnUpdatesPolicyListener() throws Exception {
         setupActiveSubscription(TEST_UUID_2);
 
@@ -1375,7 +1430,7 @@
         setupActiveSubscription(TEST_UUID_2);
 
         mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
-        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListenerForTest(mMockPolicyListener);
+        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
         final TelephonySubscriptionSnapshot snapshot =
                 buildSubscriptionSnapshot(
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 15d4f10..1c21a06 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -50,9 +50,11 @@
 
 import static java.util.Collections.singletonList;
 
+import android.net.ConnectivityDiagnosticsManager.DataStallReport;
 import android.net.ConnectivityManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.Network;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
 import android.net.ipsec.ike.ChildSaProposal;
@@ -63,10 +65,12 @@
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.net.vcn.VcnManager.VcnErrorCode;
+import android.os.PersistableBundle;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
 import com.android.server.vcn.util.MtuUtils;
 
 import org.junit.Before;
@@ -88,6 +92,7 @@
 public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase {
     private VcnIkeSession mIkeSession;
     private VcnNetworkAgent mNetworkAgent;
+    private Network mVcnNetwork;
 
     @Before
     public void setUp() throws Exception {
@@ -98,6 +103,9 @@
                 .when(mDeps)
                 .newNetworkAgent(any(), any(), any(), any(), any(), any(), any(), any(), any());
 
+        mVcnNetwork = mock(Network.class);
+        doReturn(mVcnNetwork).when(mNetworkAgent).getNetwork();
+
         mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
 
         mIkeSession = mGatewayConnection.buildIkeSession(TEST_UNDERLYING_NETWORK_RECORD_1.network);
@@ -166,6 +174,56 @@
         assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
     }
 
+    private void verifyDataStallTriggersMigration(
+            UnderlyingNetworkRecord networkRecord,
+            Network networkWithDataStall,
+            boolean expectMobilityUpdate)
+            throws Exception {
+        mGatewayConnection.setUnderlyingNetwork(networkRecord);
+        triggerChildOpened();
+        mTestLooper.dispatchAll();
+
+        final DataStallReport report =
+                new DataStallReport(
+                        networkWithDataStall,
+                        1234 /* reportTimestamp */,
+                        1 /* detectionMethod */,
+                        new LinkProperties(),
+                        new NetworkCapabilities(),
+                        new PersistableBundle());
+
+        mGatewayConnection.getConnectivityDiagnosticsCallback().onDataStallSuspected(report);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+
+        if (expectMobilityUpdate) {
+            verify(mIkeSession).setNetwork(networkRecord.network);
+        } else {
+            verify(mIkeSession, never()).setNetwork(any(Network.class));
+        }
+    }
+
+    @Test
+    public void testDataStallTriggersMigration() throws Exception {
+        verifyDataStallTriggersMigration(
+                TEST_UNDERLYING_NETWORK_RECORD_1, mVcnNetwork, true /* expectMobilityUpdate */);
+    }
+
+    @Test
+    public void testDataStallWontTriggerMigrationWhenOnOtherNetwork() throws Exception {
+        verifyDataStallTriggersMigration(
+                TEST_UNDERLYING_NETWORK_RECORD_1,
+                mock(Network.class),
+                false /* expectMobilityUpdate */);
+    }
+
+    @Test
+    public void testDataStallWontTriggerMigrationWhenUnderlyingNetworkLost() throws Exception {
+        verifyDataStallTriggersMigration(
+                null /* networkRecord */, mock(Network.class), false /* expectMobilityUpdate */);
+    }
+
     private void verifyVcnTransformsApplied(
             VcnGatewayConnection vcnGatewayConnection, boolean expectForwardTransform)
             throws Exception {
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 6a9a1e2..a4ee2de 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -24,6 +24,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
 
 import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
 import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
@@ -34,20 +35,25 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
 import android.net.IpSecManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.net.vcn.VcnTransportInfo;
 import android.net.wifi.WifiInfo;
@@ -64,6 +70,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 import java.net.InetAddress;
 import java.util.Arrays;
@@ -71,7 +78,9 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.Executor;
 
 /** Tests for TelephonySubscriptionTracker */
 @RunWith(AndroidJUnit4.class)
@@ -287,5 +296,60 @@
         verify(vcnNetworkAgent).unregister();
 
         verifyWakeLockReleased();
+
+        verify(mConnDiagMgr)
+                .unregisterConnectivityDiagnosticsCallback(
+                        mGatewayConnection.getConnectivityDiagnosticsCallback());
+    }
+
+    private VcnGatewayConnection buildConnectionWithDataStallHandling(
+            boolean datatStallHandlingEnabled) throws Exception {
+        Set<Integer> options =
+                datatStallHandlingEnabled
+                        ? Collections.singleton(
+                                VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY)
+                        : Collections.emptySet();
+        final VcnGatewayConnectionConfig gatewayConfig =
+                VcnGatewayConnectionConfigTest.buildTestConfigWithGatewayOptions(options);
+        final VcnGatewayConnection gatewayConnection =
+                new VcnGatewayConnection(
+                        mVcnContext,
+                        TEST_SUB_GRP,
+                        TEST_SUBSCRIPTION_SNAPSHOT,
+                        gatewayConfig,
+                        mGatewayStatusCallback,
+                        true /* isMobileDataEnabled */,
+                        mDeps);
+        return gatewayConnection;
+    }
+
+    @Test
+    public void testDataStallHandlingEnabled() throws Exception {
+        final VcnGatewayConnection gatewayConnection =
+                buildConnectionWithDataStallHandling(true /* datatStallHandlingEnabled */);
+
+        final ArgumentCaptor<NetworkRequest> networkRequestCaptor =
+                ArgumentCaptor.forClass(NetworkRequest.class);
+        verify(mConnDiagMgr)
+                .registerConnectivityDiagnosticsCallback(
+                        networkRequestCaptor.capture(),
+                        any(Executor.class),
+                        eq(gatewayConnection.getConnectivityDiagnosticsCallback()));
+
+        final NetworkRequest nr = networkRequestCaptor.getValue();
+        final NetworkRequest expected =
+                new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build();
+        assertEquals(expected, nr);
+    }
+
+    @Test
+    public void testDataStallHandlingDisabled() throws Exception {
+        buildConnectionWithDataStallHandling(false /* datatStallHandlingEnabled */);
+
+        verify(mConnDiagMgr, never())
+                .registerConnectivityDiagnosticsCallback(
+                        any(NetworkRequest.class),
+                        any(Executor.class),
+                        any(ConnectivityDiagnosticsCallback.class));
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 785bff1..7bafd24 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -35,6 +35,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.net.ConnectivityDiagnosticsManager;
 import android.net.ConnectivityManager;
 import android.net.InetAddresses;
 import android.net.IpSecConfig;
@@ -157,6 +158,7 @@
 
     @NonNull protected final IpSecService mIpSecSvc;
     @NonNull protected final ConnectivityManager mConnMgr;
+    @NonNull protected final ConnectivityDiagnosticsManager mConnDiagMgr;
 
     @NonNull protected final IkeSessionConnectionInfo mIkeConnectionInfo;
     @NonNull protected final IkeSessionConfiguration mIkeSessionConfiguration;
@@ -186,6 +188,13 @@
         VcnTestUtils.setupSystemService(
                 mContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
 
+        mConnDiagMgr = mock(ConnectivityDiagnosticsManager.class);
+        VcnTestUtils.setupSystemService(
+                mContext,
+                mConnDiagMgr,
+                Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
+                ConnectivityDiagnosticsManager.class);
+
         mIkeConnectionInfo =
                 new IkeSessionConnectionInfo(TEST_ADDR, TEST_ADDR_2, mock(Network.class));
         mIkeSessionConfiguration = new IkeSessionConfiguration.Builder(mIkeConnectionInfo).build();
diff --git a/tools/aapt2/ApkInfo.proto b/tools/aapt2/ApkInfo.proto
index 80bdccb..b5ff71f 100644
--- a/tools/aapt2/ApkInfo.proto
+++ b/tools/aapt2/ApkInfo.proto
@@ -40,7 +40,8 @@
   PackageInfo package = 1;
   Application application = 2;
   UsesSdk uses_sdk = 3;
-  UsesConfiguration uses_configuration = 4;
+  // Previously: UsesConfiguration uses_configuration = 4;
+  reserved 4;
   SupportsScreen supports_screen = 5;
   SupportsInput supports_input = 6;
   LaunchableActivity launchable_activity = 7;
@@ -57,6 +58,8 @@
   repeated string locales = 17;
   repeated int32 densities = 18;
 
+  repeated UsesPackage uses_packages = 51;
+  repeated UsesConfiguration uses_configurations = 52;
   repeated FeatureGroup feature_groups = 53;
   repeated UsesPermission uses_permissions = 54;
   repeated Permission permissions = 55;
@@ -64,7 +67,6 @@
   repeated UsesStaticLibrary uses_static_libraries = 57;
   repeated UsesSdkLibrary uses_sdk_libraries = 58;
   repeated UsesNativeLibrary uses_native_libraries = 59;
-  repeated UsesPackage uses_packages = 51;
 
   repeated Metadata metadata = 62;
   repeated Property properties = 63;
diff --git a/tools/aapt2/cmd/Dump_test.cpp b/tools/aapt2/cmd/Dump_test.cpp
index b1c69cd..df35ebb 100644
--- a/tools/aapt2/cmd/Dump_test.cpp
+++ b/tools/aapt2/cmd/Dump_test.cpp
@@ -108,4 +108,21 @@
   ASSERT_EQ(output, expected);
 }
 
+TEST_F(DumpTest, DumpBadgingApkBuiltWithAaptAndTagsInWrongPlace) {
+  auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests",
+                                   "DumpTest", "built_with_aapt.apk"});
+  auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
+
+  std::string output;
+  DumpBadgingToString(loaded_apk.get(), &output, /* include_meta_data= */ false,
+                      /* only_permissions= */ false);
+
+  std::string expected;
+  auto expected_path =
+      file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "DumpTest",
+                       "built_with_aapt_expected.txt"});
+  ::android::base::ReadFileToString(expected_path, &expected);
+  ASSERT_EQ(output, expected);
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index d60869a..d1957fb 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -223,7 +223,8 @@
     Element() = default;
     virtual ~Element() = default;
 
-    static std::unique_ptr<Element> Inflate(ManifestExtractor* extractor, xml::Element* el);
+    static std::unique_ptr<Element> Inflate(ManifestExtractor* extractor, xml::Element* el,
+                                            const std::string& parent_tag);
 
     /** Writes out the extracted contents of the element. */
     virtual void Print(text::Printer* printer) {
@@ -249,10 +250,15 @@
     }
 
     /** Retrieves the extracted xml element tag. */
-    const std::string tag() const {
+    const std::string& tag() const {
       return tag_;
     }
 
+    /** Whether this element has special Extract/Print/ToProto logic. */
+    bool is_featured() const {
+      return featured_;
+    }
+
    protected:
     ManifestExtractor* extractor() const {
       return extractor_;
@@ -394,6 +400,8 @@
               return &(*intValue->value);
             } else if (RawString* rawValue = ValueCast<RawString>(value)) {
               return &(*rawValue->value);
+            } else if (StyledString* styledStrValue = ValueCast<StyledString>(value)) {
+              return &(styledStrValue->value->value);
             } else if (FileReference* strValue = ValueCast<FileReference>(value)) {
               return &(*strValue->path);
             }
@@ -424,6 +432,7 @@
       ManifestExtractor* extractor_;
       std::vector<std::unique_ptr<Element>> children_;
       std::string tag_;
+      bool featured_ = false;
   };
 
   friend Element;
@@ -446,7 +455,7 @@
   bool DumpProto(pb::Badging* out_badging);
 
   /** Recursively visit the xml element tree and return a processed badging element tree. */
-  std::unique_ptr<Element> Visit(xml::Element* element);
+  std::unique_ptr<Element> Visit(xml::Element* element, const std::string& parent_tag);
 
   /** Resets target SDK to 0. */
   void ResetTargetSdk() {
@@ -485,7 +494,7 @@
   }
 
   /** Retrieves the current stack of parent during data extraction. */
-  const std::vector<Element*> parent_stack() const {
+  const std::vector<Element*>& parent_stack() const {
     return parent_stack_;
   }
 
@@ -533,8 +542,9 @@
   if (f(root)) {
     return root;
   }
-  for (auto& child : root->children()) {
-    if (auto b2 = FindElement(child.get(), f)) {
+  const auto& children = root->children();
+  for (auto it = children.rbegin(); it != children.rend(); ++it) {
+    if (auto b2 = FindElement(it->get(), f)) {
       return b2;
     }
   }
@@ -901,7 +911,7 @@
   }
 
   void ToProto(pb::Badging* out_badging) override {
-    auto out_configuration = out_badging->mutable_uses_configuration();
+    auto out_configuration = out_badging->add_uses_configurations();
     out_configuration->set_req_touch_screen(req_touch_screen);
     out_configuration->set_req_keyboard_type(req_keyboard_type);
     out_configuration->set_req_hard_keyboard(req_hard_keyboard);
@@ -1348,11 +1358,6 @@
   std::string impliedReason;
 
   void Extract(xml::Element* element) override {
-    const auto parent_stack = extractor()->parent_stack();
-    if (!extractor()->options_.only_permissions &&
-        (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) {
-      return;
-    }
     name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
     std::string feature =
         GetAttributeStringDefault(FindAttribute(element, REQUIRED_FEATURE_ATTR), "");
@@ -1477,11 +1482,6 @@
   const int32_t* maxSdkVersion = nullptr;
 
   void Extract(xml::Element* element) override {
-    const auto parent_stack = extractor()->parent_stack();
-    if (!extractor()->options_.only_permissions &&
-        (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) {
-      return;
-    }
     name = GetAttributeString(FindAttribute(element, NAME_ATTR));
     maxSdkVersion = GetAttributeInteger(FindAttribute(element, MAX_SDK_VERSION_ATTR));
 
@@ -1717,11 +1717,8 @@
   int required;
 
   void Extract(xml::Element* element) override {
-    auto parent_stack = extractor()->parent_stack();
-    if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) {
-      name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
-      required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1);
-    }
+    name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+    required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1);
   }
 
   void Print(text::Printer* printer) override {
@@ -1749,12 +1746,9 @@
   int versionMajor;
 
   void Extract(xml::Element* element) override {
-    auto parent_stack = extractor()->parent_stack();
-    if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) {
-      name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
-      version = GetAttributeIntegerDefault(FindAttribute(element, VERSION_ATTR), 0);
-      versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0);
-    }
+    name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+    version = GetAttributeIntegerDefault(FindAttribute(element, VERSION_ATTR), 0);
+    versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0);
   }
 
   void Print(text::Printer* printer) override {
@@ -1781,13 +1775,10 @@
   std::vector<std::string> certDigests;
 
   void Extract(xml::Element* element) override {
-    auto parent_stack = extractor()->parent_stack();
-    if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) {
-      name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
-      version = GetAttributeIntegerDefault(FindAttribute(element, VERSION_ATTR), 0);
-      versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0);
-      AddCertDigest(element);
-    }
+    name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+    version = GetAttributeIntegerDefault(FindAttribute(element, VERSION_ATTR), 0);
+    versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0);
+    AddCertDigest(element);
   }
 
   void AddCertDigest(xml::Element* element) {
@@ -1829,11 +1820,8 @@
   int versionMajor;
 
   void Extract(xml::Element* element) override {
-    auto parent_stack = extractor()->parent_stack();
-    if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) {
-      name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
-      versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0);
-    }
+    name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+    versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0);
   }
 
   void Print(text::Printer* printer) override {
@@ -1857,12 +1845,9 @@
   std::vector<std::string> certDigests;
 
   void Extract(xml::Element* element) override {
-    auto parent_stack = extractor()->parent_stack();
-    if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) {
-      name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
-      versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0);
-      AddCertDigest(element);
-    }
+    name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+    versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0);
+    AddCertDigest(element);
   }
 
   void AddCertDigest(xml::Element* element) {
@@ -1902,11 +1887,8 @@
   int required;
 
   void Extract(xml::Element* element) override {
-    auto parent_stack = extractor()->parent_stack();
-    if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) {
-      name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
-      required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1);
-    }
+    name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+    required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1);
   }
 
   void Print(text::Printer* printer) override {
@@ -2251,14 +2233,11 @@
   std::vector<std::string> certDigests;
 
   void Extract(xml::Element* element) override {
-    auto parent_stack = extractor()->parent_stack();
-    if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) {
-      packageType = GetAttributeString(FindAttribute(element, PACKAGE_TYPE_ATTR));
-      name = GetAttributeString(FindAttribute(element, NAME_ATTR));
-      version = GetAttributeIntegerDefault(FindAttribute(element, VERSION_ATTR), 0);
-      versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0);
-      AddCertDigest(element);
-    }
+    packageType = GetAttributeString(FindAttribute(element, PACKAGE_TYPE_ATTR));
+    name = GetAttributeString(FindAttribute(element, NAME_ATTR));
+    version = GetAttributeIntegerDefault(FindAttribute(element, VERSION_ATTR), 0);
+    versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0);
+    AddCertDigest(element);
   }
 
   void AddCertDigest(xml::Element* element) {
@@ -2480,7 +2459,7 @@
   // Print only the <uses-permission>, <uses-permission-sdk23>, and <permission> elements if
   // printing only permission elements is requested
   if (options_.only_permissions) {
-    root_element_ = ManifestExtractor::Element::Inflate(this, element);
+    root_element_ = ManifestExtractor::Element::Inflate(this, element, "");
 
     if (auto manifest = ElementCast<Manifest>(root_element_.get())) {
       manifest->only_package_name = true;
@@ -2489,7 +2468,7 @@
         if (child->name == "uses-permission" || child->name == "uses-permission-sdk-23"
             || child->name == "permission") {
           // Inflate the element and its descendants
-          auto permission_element = Visit(child);
+          auto permission_element = Visit(child, "manifest");
           manifest->AddChild(permission_element);
         }
       }
@@ -2528,7 +2507,7 @@
   }
 
   // Extract badging information
-  root_element_ = Visit(element);
+  root_element_ = Visit(element, "");
 
   // Filter out all "uses-sdk" tags besides the very last tag. The android runtime only uses the
   // attribute values from the last defined tag.
@@ -2683,7 +2662,7 @@
                            (meta_data->name == "android.nfc.cardemulation.off_host_apdu_service" &&
                             offhost_apdu_action)) {
                          // Attempt to load the resource file
-                         if (!meta_data->resource.empty()) {
+                         if (meta_data->resource.empty()) {
                            return;
                          }
                          auto resource = this->apk_->LoadXml(meta_data->resource, diag);
@@ -2878,58 +2857,66 @@
   return true;
 }
 
+template <typename T>
+constexpr const char* GetExpectedTagForType() {
+  // This array does not appear at runtime, as GetExpectedTagForType function is used by compiler
+  // to inject proper 'expected_tag' into ElementCast.
+  std::array<std::pair<const char*, bool>, 37> tags = {
+      std::make_pair("action", std::is_same<Action, T>::value),
+      std::make_pair("activity", std::is_same<Activity, T>::value),
+      std::make_pair("additional-certificate", std::is_same<AdditionalCertificate, T>::value),
+      std::make_pair("application", std::is_same<Application, T>::value),
+      std::make_pair("category", std::is_same<Category, T>::value),
+      std::make_pair("compatible-screens", std::is_same<CompatibleScreens, T>::value),
+      std::make_pair("feature-group", std::is_same<FeatureGroup, T>::value),
+      std::make_pair("input-type", std::is_same<InputType, T>::value),
+      std::make_pair("intent-filter", std::is_same<IntentFilter, T>::value),
+      std::make_pair("meta-data", std::is_same<MetaData, T>::value),
+      std::make_pair("manifest", std::is_same<Manifest, T>::value),
+      std::make_pair("original-package", std::is_same<OriginalPackage, T>::value),
+      std::make_pair("overlay", std::is_same<Overlay, T>::value),
+      std::make_pair("package-verifier", std::is_same<PackageVerifier, T>::value),
+      std::make_pair("permission", std::is_same<Permission, T>::value),
+      std::make_pair("property", std::is_same<Property, T>::value),
+      std::make_pair("provider", std::is_same<Provider, T>::value),
+      std::make_pair("receiver", std::is_same<Receiver, T>::value),
+      std::make_pair("required-feature", std::is_same<RequiredFeature, T>::value),
+      std::make_pair("required-not-feature", std::is_same<RequiredNotFeature, T>::value),
+      std::make_pair("screen", std::is_same<Screen, T>::value),
+      std::make_pair("service", std::is_same<Service, T>::value),
+      std::make_pair("sdk-library", std::is_same<SdkLibrary, T>::value),
+      std::make_pair("static-library", std::is_same<StaticLibrary, T>::value),
+      std::make_pair("supports-gl-texture", std::is_same<SupportsGlTexture, T>::value),
+      std::make_pair("supports-input", std::is_same<SupportsInput, T>::value),
+      std::make_pair("supports-screens", std::is_same<SupportsScreen, T>::value),
+      std::make_pair("uses-configuration", std::is_same<UsesConfiguarion, T>::value),
+      std::make_pair("uses-feature", std::is_same<UsesFeature, T>::value),
+      std::make_pair("uses-library", std::is_same<UsesLibrary, T>::value),
+      std::make_pair("uses-native-library", std::is_same<UsesNativeLibrary, T>::value),
+      std::make_pair("uses-package", std::is_same<UsesPackage, T>::value),
+      std::make_pair("uses-permission", std::is_same<UsesPermission, T>::value),
+      std::make_pair("uses-permission-sdk-23", std::is_same<UsesPermissionSdk23, T>::value),
+      std::make_pair("uses-sdk", std::is_same<UsesSdkBadging, T>::value),
+      std::make_pair("uses-sdk-library", std::is_same<UsesSdkLibrary, T>::value),
+      std::make_pair("uses-static-library", std::is_same<UsesStaticLibrary, T>::value),
+  };
+  for (const auto& pair : tags) {
+    if (pair.second) {
+      return pair.first;
+    }
+  }
+  return nullptr;
+}
+
 /**
  * Returns the element casted to the type if the element is of that type. Otherwise, returns a null
  * pointer.
  **/
 template<typename T>
 T* ElementCast(ManifestExtractor::Element* element) {
-  if (element == nullptr) {
-    return nullptr;
-  }
-
-  const std::unordered_map<std::string, bool> kTagCheck = {
-      {"action", std::is_base_of<Action, T>::value},
-      {"activity", std::is_base_of<Activity, T>::value},
-      {"additional-certificate", std::is_base_of<AdditionalCertificate, T>::value},
-      {"application", std::is_base_of<Application, T>::value},
-      {"category", std::is_base_of<Category, T>::value},
-      {"compatible-screens", std::is_base_of<CompatibleScreens, T>::value},
-      {"feature-group", std::is_base_of<FeatureGroup, T>::value},
-      {"input-type", std::is_base_of<InputType, T>::value},
-      {"intent-filter", std::is_base_of<IntentFilter, T>::value},
-      {"meta-data", std::is_base_of<MetaData, T>::value},
-      {"manifest", std::is_base_of<Manifest, T>::value},
-      {"original-package", std::is_base_of<OriginalPackage, T>::value},
-      {"overlay", std::is_base_of<Overlay, T>::value},
-      {"package-verifier", std::is_base_of<PackageVerifier, T>::value},
-      {"permission", std::is_base_of<Permission, T>::value},
-      {"property", std::is_base_of<Property, T>::value},
-      {"provider", std::is_base_of<Provider, T>::value},
-      {"receiver", std::is_base_of<Receiver, T>::value},
-      {"required-feature", std::is_base_of<RequiredFeature, T>::value},
-      {"required-not-feature", std::is_base_of<RequiredNotFeature, T>::value},
-      {"screen", std::is_base_of<Screen, T>::value},
-      {"service", std::is_base_of<Service, T>::value},
-      {"sdk-library", std::is_base_of<SdkLibrary, T>::value},
-      {"static-library", std::is_base_of<StaticLibrary, T>::value},
-      {"supports-gl-texture", std::is_base_of<SupportsGlTexture, T>::value},
-      {"supports-input", std::is_base_of<SupportsInput, T>::value},
-      {"supports-screens", std::is_base_of<SupportsScreen, T>::value},
-      {"uses-configuration", std::is_base_of<UsesConfiguarion, T>::value},
-      {"uses-feature", std::is_base_of<UsesFeature, T>::value},
-      {"uses-library", std::is_base_of<UsesLibrary, T>::value},
-      {"uses-native-library", std::is_base_of<UsesNativeLibrary, T>::value},
-      {"uses-package", std::is_base_of<UsesPackage, T>::value},
-      {"uses-permission", std::is_base_of<UsesPermission, T>::value},
-      {"uses-permission-sdk-23", std::is_base_of<UsesPermissionSdk23, T>::value},
-      {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value},
-      {"uses-sdk-library", std::is_base_of<UsesSdkLibrary, T>::value},
-      {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value},
-  };
-
-  auto check = kTagCheck.find(element->tag());
-  if (check != kTagCheck.end() && check->second) {
+  constexpr const char* expected_tag = GetExpectedTagForType<T>();
+  if (element != nullptr && expected_tag != nullptr && element->is_featured() &&
+      element->tag() == expected_tag) {
     return static_cast<T*>(element);
   }
   return nullptr;
@@ -2941,9 +2928,9 @@
 }
 
 std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate(
-    ManifestExtractor* extractor, xml::Element* el) {
-  const std::unordered_map<std::string,
-                           std::function<std::unique_ptr<ManifestExtractor::Element>()>>
+    ManifestExtractor* extractor, xml::Element* el, const std::string& parent_tag) {
+  static const std::unordered_map<std::string_view,
+                                  std::function<std::unique_ptr<ManifestExtractor::Element>()>>
       kTagCheck = {
           {"action", &CreateType<Action>},
           {"activity", &CreateType<Activity>},
@@ -2983,12 +2970,71 @@
           {"uses-sdk-library", &CreateType<UsesSdkLibrary>},
           {"uses-static-library", &CreateType<UsesStaticLibrary>},
       };
-
+  static constexpr std::array<std::pair<std::string_view, std::string_view>, 53>
+      kValidChildParentTags = {
+          std::make_pair("action", "intent-filter"),
+          std::make_pair("activity", "application"),
+          std::make_pair("additional-certificate", "uses-package"),
+          std::make_pair("additional-certificate", "uses-static-library"),
+          std::make_pair("application", "manifest"),
+          std::make_pair("category", "intent-filter"),
+          std::make_pair("compatible-screens", "manifest"),
+          std::make_pair("feature-group", "manifest"),
+          std::make_pair("input-type", "supports-input"),
+          std::make_pair("intent-filter", "activity"),
+          std::make_pair("intent-filter", "activity-alias"),
+          std::make_pair("intent-filter", "service"),
+          std::make_pair("intent-filter", "receiver"),
+          std::make_pair("intent-filter", "provider"),
+          std::make_pair("manifest", ""),
+          std::make_pair("meta-data", "activity"),
+          std::make_pair("meta-data", "activity-alias"),
+          std::make_pair("meta-data", "application"),
+          std::make_pair("meta-data", "service"),
+          std::make_pair("meta-data", "receiver"),
+          std::make_pair("meta-data", "provider"),
+          std::make_pair("original-package", "manifest"),
+          std::make_pair("overlay", "manifest"),
+          std::make_pair("package-verifier", "manifest"),
+          std::make_pair("permission", "manifest"),
+          std::make_pair("property", "activity"),
+          std::make_pair("property", "activity-alias"),
+          std::make_pair("property", "application"),
+          std::make_pair("property", "service"),
+          std::make_pair("property", "receiver"),
+          std::make_pair("property", "provider"),
+          std::make_pair("provider", "application"),
+          std::make_pair("receiver", "application"),
+          std::make_pair("required-feature", "uses-permission"),
+          std::make_pair("required-not-feature", "uses-permission"),
+          std::make_pair("screen", "compatible-screens"),
+          std::make_pair("service", "application"),
+          std::make_pair("sdk-library", "application"),
+          std::make_pair("static-library", "application"),
+          std::make_pair("supports-gl-texture", "manifest"),
+          std::make_pair("supports-input", "manifest"),
+          std::make_pair("supports-screens", "manifest"),
+          std::make_pair("uses-configuration", "manifest"),
+          std::make_pair("uses-feature", "feature-group"),
+          std::make_pair("uses-feature", "manifest"),
+          std::make_pair("uses-library", "application"),
+          std::make_pair("uses-native-library", "application"),
+          std::make_pair("uses-package", "application"),
+          std::make_pair("uses-permission", "manifest"),
+          std::make_pair("uses-permission-sdk-23", "manifest"),
+          std::make_pair("uses-sdk", "manifest"),
+          std::make_pair("uses-sdk-library", "application"),
+          std::make_pair("uses-static-library", "application"),
+      };
+  bool is_valid_tag = std::find(kValidChildParentTags.begin(), kValidChildParentTags.end(),
+                                std::make_pair<std::string_view, std::string_view>(
+                                    el->name, parent_tag)) != kValidChildParentTags.end();
   // Attempt to map the xml tag to a element inflater
   std::unique_ptr<ManifestExtractor::Element> element;
   auto check = kTagCheck.find(el->name);
-  if (check != kTagCheck.end()) {
+  if (check != kTagCheck.end() && is_valid_tag) {
     element = check->second();
+    element->featured_ = true;
   } else {
     element = util::make_unique<ManifestExtractor::Element>();
   }
@@ -2999,13 +3045,14 @@
   return element;
 }
 
-std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Visit(xml::Element* el) {
-  auto element = ManifestExtractor::Element::Inflate(this, el);
+std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Visit(
+    xml::Element* el, const std::string& parent_tag) {
+  auto element = ManifestExtractor::Element::Inflate(this, el, parent_tag);
   parent_stack_.insert(parent_stack_.begin(), element.get());
 
   // Process the element and recursively visit the children
   for (xml::Element* child : el->GetChildElements()) {
-    auto v = Visit(child);
+    auto v = Visit(child, el->name);
     element->AddChild(v);
   }
 
diff --git a/tools/aapt2/integration-tests/DumpTest/built_with_aapt.apk b/tools/aapt2/integration-tests/DumpTest/built_with_aapt.apk
new file mode 100644
index 0000000..090ebe56
--- /dev/null
+++ b/tools/aapt2/integration-tests/DumpTest/built_with_aapt.apk
Binary files differ
diff --git a/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt b/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt
new file mode 100644
index 0000000..cc0b3bf
--- /dev/null
+++ b/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt
@@ -0,0 +1,11 @@
+package: name='com.aapt.app' versionCode='222' versionName='222' platformBuildVersionName='12' platformBuildVersionCode='32' compileSdkVersion='32' compileSdkVersionCodename='12'
+sdkVersion:'22'
+targetSdkVersion:'32'
+application: label='App' icon=''
+feature-group: label=''
+  uses-feature: name='android.hardware.faketouch'
+  uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'
+supports-screens: 'small' 'normal' 'large' 'xlarge'
+supports-any-density: 'true'
+locales:
+densities:
diff --git a/tools/aapt2/integration-tests/DumpTest/components_expected.txt b/tools/aapt2/integration-tests/DumpTest/components_expected.txt
index 79b6706..9c81fb8 100644
--- a/tools/aapt2/integration-tests/DumpTest/components_expected.txt
+++ b/tools/aapt2/integration-tests/DumpTest/components_expected.txt
@@ -36,6 +36,7 @@
 provides-component:'wallpaper'
 provides-component:'accessibility'
 provides-component:'print-service'
+provides-component:'payment'
 provides-component:'search'
 provides-component:'document-provider'
 provides-component:'notification-listener'
diff --git a/tools/aapt2/integration-tests/DumpTest/components_expected_proto.txt b/tools/aapt2/integration-tests/DumpTest/components_expected_proto.txt
index 4aed4af..d866479 100644
--- a/tools/aapt2/integration-tests/DumpTest/components_expected_proto.txt
+++ b/tools/aapt2/integration-tests/DumpTest/components_expected_proto.txt
@@ -40,13 +40,6 @@
     min_sdk_version: 21
     target_sdk_version: 31
   }
-  uses_configuration {
-    req_touch_screen: 3
-    req_keyboard_type: 2
-    req_hard_keyboard: -1
-    req_navigation: 3
-    req_five_way_nav: -1
-  }
   supports_screen {
     screens: NORMAL
     screens: LARGE
@@ -87,6 +80,7 @@
     provided_components: "wallpaper"
     provided_components: "accessibility"
     provided_components: "print-service"
+    provided_components: "payment"
     provided_components: "search"
     provided_components: "document-provider"
     provided_components: "notification-listener"
@@ -101,6 +95,13 @@
   densities: 480
   densities: 640
   densities: 65534
+  uses_configurations {
+    req_touch_screen: 3
+    req_keyboard_type: 2
+    req_hard_keyboard: -1
+    req_navigation: 3
+    req_five_way_nav: -1
+  }
   feature_groups {
     features {
       name: "android.hardware.bluetooth"
diff --git a/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt b/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt
index c783f47..6da6fc6 100644
--- a/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt
+++ b/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt
@@ -40,13 +40,6 @@
     min_sdk_version: 21
     target_sdk_version: 31
   }
-  uses_configuration {
-    req_touch_screen: 3
-    req_keyboard_type: 2
-    req_hard_keyboard: -1
-    req_navigation: 3
-    req_five_way_nav: -1
-  }
   supports_screen {
     screens: NORMAL
     screens: LARGE
@@ -87,6 +80,7 @@
     provided_components: "wallpaper"
     provided_components: "accessibility"
     provided_components: "print-service"
+    provided_components: "payment"
     provided_components: "search"
     provided_components: "document-provider"
     provided_components: "notification-listener"
@@ -101,6 +95,13 @@
   densities: 480
   densities: 640
   densities: 65534
+  uses_configurations {
+    req_touch_screen: 3
+    req_keyboard_type: 2
+    req_hard_keyboard: -1
+    req_navigation: 3
+    req_five_way_nav: -1
+  }
   feature_groups {
     features {
       name: "android.hardware.bluetooth"
diff --git a/tools/lint/framework/Android.bp b/tools/lint/framework/Android.bp
index 7f27e8a..b752503 100644
--- a/tools/lint/framework/Android.bp
+++ b/tools/lint/framework/Android.bp
@@ -31,8 +31,6 @@
     ],
     static_libs: [
         "AndroidCommonLint",
-        // TODO: remove once b/236558918 is resolved and the below checks actually run globally
-        "AndroidGlobalLintChecker",
     ],
     kotlincflags: ["-Xjvm-default=all"],
 }
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index c5cf0fb..423a684 100644
--- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -19,9 +19,6 @@
 import com.android.tools.lint.client.api.IssueRegistry
 import com.android.tools.lint.client.api.Vendor
 import com.android.tools.lint.detector.api.CURRENT_API
-import com.google.android.lint.aidl.EnforcePermissionDetector
-import com.google.android.lint.aidl.EnforcePermissionHelperDetector
-import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
 import com.google.android.lint.parcel.SaferParcelChecker
 import com.google.auto.service.AutoService
 
@@ -37,10 +34,6 @@
         CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
         CallingIdentityTokenDetector.ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE,
         CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
-        EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
-        EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
-        EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
-        SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
         SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
         PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
         RegisterReceiverFlagDetector.ISSUE_RECEIVER_EXPORTED_FLAG,
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index bcef917..2665b3c 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -31,10 +31,11 @@
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
 import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiArrayInitializerMemberValue
 import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiElement
 import com.intellij.psi.PsiMethod
 import org.jetbrains.uast.UAnnotation
-import org.jetbrains.uast.UClass
 import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UMethod
 
@@ -65,6 +66,12 @@
         return listOf(UAnnotation::class.java)
     }
 
+    private fun annotationValueGetChildren(elem: PsiElement): Array<PsiElement> {
+        if (elem is PsiArrayInitializerMemberValue)
+            return elem.getInitializers().map { it as PsiElement }.toTypedArray()
+        return elem.getChildren()
+    }
+
     private fun areAnnotationsEquivalent(
         context: JavaContext,
         anno1: PsiAnnotation,
@@ -82,18 +89,28 @@
             if (attr1[i].name != attr2[i].name) {
                 return false
             }
-            val value1 = attr1[i].value
-            val value2 = attr2[i].value
-            if (value1 == null && value2 == null) {
-                continue
-            }
-            if (value1 == null || value2 == null) {
-                return false
-            }
+            val value1 = attr1[i].value ?: return false
+            val value2 = attr2[i].value ?: return false
+            // Try to compare values directly with each other.
             val v1 = ConstantEvaluator.evaluate(context, value1)
             val v2 = ConstantEvaluator.evaluate(context, value2)
-            if (v1 != v2) {
-                return false
+            if (v1 != null && v2 != null) {
+                if (v1 != v2) {
+                    return false
+                }
+            } else {
+                val children1 = annotationValueGetChildren(value1)
+                val children2 = annotationValueGetChildren(value2)
+                if (children1.size != children2.size) {
+                    return false
+                }
+                for (j in children1.indices) {
+                    val c1 = ConstantEvaluator.evaluate(context, children1[j])
+                    val c2 = ConstantEvaluator.evaluate(context, children2[j])
+                    if (c1 != c2) {
+                        return false
+                    }
+                }
             }
         }
         return true
@@ -140,50 +157,13 @@
         }
     }
 
-    private fun compareClasses(
-        context: JavaContext,
-        element: UElement,
-        newClass: PsiClass,
-        extendedClass: PsiClass,
-        checkEquivalence: Boolean = true
-    ) {
-        val newAnnotation = newClass.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
-        val extendedAnnotation = extendedClass.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
-
-        val location = context.getLocation(element)
-        val newClassName = newClass.qualifiedName
-        val extendedClassName = extendedClass.qualifiedName
-        if (newAnnotation == null) {
-            val msg = "The class $newClassName extends the class $extendedClassName which " +
-                "is annotated with @EnforcePermission. The same annotation must be used " +
-                "on $newClassName."
-            context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg)
-        } else if (extendedAnnotation == null) {
-            val msg = "The class $newClassName extends the class $extendedClassName which " +
-                "is not annotated with @EnforcePermission. The same annotation must be used " +
-                "on $extendedClassName. Did you forget to annotate the AIDL definition?"
-            context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg)
-        } else if (checkEquivalence && !areAnnotationsEquivalent(
-            context, newAnnotation, extendedAnnotation)) {
-            val msg = "The class $newClassName is annotated with ${newAnnotation.text} " +
-                "which differs from the parent class $extendedClassName: " +
-                "${extendedAnnotation.text}. The same annotation must be used for " +
-                "both classes."
-            context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg)
-        }
-    }
-
     override fun visitAnnotationUsage(
         context: JavaContext,
         element: UElement,
         annotationInfo: AnnotationInfo,
         usageInfo: AnnotationUsageInfo
     ) {
-        if (usageInfo.type == AnnotationUsageType.EXTENDS) {
-            val newClass = element.sourcePsi?.parent?.parent as PsiClass
-            val extendedClass: PsiClass = usageInfo.referenced as PsiClass
-            compareClasses(context, element, newClass, extendedClass)
-        } else if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
+        if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
             annotationInfo.origin == AnnotationOrigin.METHOD) {
             val overridingMethod = element.sourcePsi as PsiMethod
             val overriddenMethod = usageInfo.referenced as PsiMethod
@@ -198,17 +178,7 @@
                     return
                 }
                 val method = node.uastParent as? UMethod
-                val klass = node.uastParent as? UClass
-                if (klass != null) {
-                    val newClass = klass as PsiClass
-                    val extendedClass = newClass.getSuperClass()
-                    if (extendedClass != null && extendedClass.qualifiedName != JAVA_OBJECT) {
-                        // The equivalence check can be skipped, if both classes are
-                        // annotated, it will be verified by visitAnnotationUsage.
-                        compareClasses(context, klass, newClass,
-                            extendedClass, checkEquivalence = false)
-                    }
-                } else if (method != null) {
+                if (method != null) {
                     val overridingMethod = method as PsiMethod
                     val parents = overridingMethod.findSuperMethods()
                     for (overriddenMethod in parents) {
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index 618bbcc..4ed68a8 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -33,21 +33,6 @@
 
     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
-    fun testDoesNotDetectIssuesCorrectAnnotationOnClass() {
-        lint().files(java(
-            """
-            package test.pkg;
-            @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-            public class TestClass1 extends IFoo.Stub {
-                public void testMethod() {}
-            }
-            """).indented(),
-                *stubs
-        )
-        .run()
-        .expectClean()
-    }
-
     fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() {
         lint().files(java(
             """
@@ -65,26 +50,72 @@
         .expectClean()
     }
 
-    fun testDetectIssuesMismatchingAnnotationOnClass() {
+    fun testDoesNotDetectIssuesCorrectAnnotationAllOnMethod() {
         lint().files(java(
             """
             package test.pkg;
-            @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
-            public class TestClass3 extends IFoo.Stub {
-                public void testMethod() {}
+            import android.annotation.EnforcePermission;
+            public class TestClass11 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAll() {}
             }
             """).indented(),
                 *stubs
         )
         .run()
-        .expect("""src/test/pkg/TestClass3.java:3: Error: The class test.pkg.TestClass3 is \
-annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
-which differs from the parent class IFoo.Stub: \
-@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The \
-same annotation must be used for both classes. [MismatchingEnforcePermissionAnnotation]
-public class TestClass3 extends IFoo.Stub {
-                                ~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
+        .expectClean()
+    }
+
+    fun testDoesNotDetectIssuesCorrectAnnotationAllLiteralOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass111 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(allOf={"android.permission.INTERNET", android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAllLiteral() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
+    fun testDoesNotDetectIssuesCorrectAnnotationAnyOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass12 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAny() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
+    fun testDoesNotDetectIssuesCorrectAnnotationAnyLiteralOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass121 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(anyOf={"android.permission.INTERNET", android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAnyLiteral() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
     }
 
     fun testDetectIssuesMismatchingAnnotationOnMethod() {
@@ -99,33 +130,132 @@
                 *stubs
         )
         .run()
-        .expect("""src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is \
-annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
-which differs from the overridden method Stub.testMethod: \
-@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same \
-annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
-    public void testMethod() {}
-                ~~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
+        .expect("""
+                src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
+                which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
     }
 
-    fun testDetectIssuesMissingAnnotationOnClass() {
+    fun testDetectIssuesEmptyAnnotationOnMethod() {
         lint().files(java(
             """
             package test.pkg;
-            public class TestClass5 extends IFoo.Stub {
+            public class TestClass41 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission
                 public void testMethod() {}
             }
             """).indented(),
                 *stubs
         )
         .run()
-        .expect("""src/test/pkg/TestClass5.java:2: Error: The class test.pkg.TestClass5 extends \
-the class IFoo.Stub which is annotated with @EnforcePermission. The same annotation must be \
-used on test.pkg.TestClass5. [MissingEnforcePermissionAnnotation]
-public class TestClass5 extends IFoo.Stub {
-                                ~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
+        .expect("""
+                src/test/pkg/TestClass41.java:4: Error: The method TestClass41.testMethod is annotated with @android.annotation.EnforcePermission \
+                which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAnyAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass9 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC})
+                public void testMethodAny() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass9.java:4: Error: The method TestClass9.testMethodAny is annotated with \
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) \
+                which differs from the overridden method Stub.testMethodAny: \
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAny() {}
+                                ~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAnyLiteralAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass91 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(anyOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"})
+                public void testMethodAnyLiteral() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass91.java:4: Error: The method TestClass91.testMethodAnyLiteral is annotated with \
+                @android.annotation.EnforcePermission(anyOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) \
+                which differs from the overridden method Stub.testMethodAnyLiteral: \
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAnyLiteral() {}
+                                ~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAllAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass10 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC})
+                public void testMethodAll() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass10.java:4: Error: The method TestClass10.testMethodAll is annotated with \
+                @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) \
+                which differs from the overridden method Stub.testMethodAll: \
+                @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAll() {}
+                                ~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAllLiteralAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass101 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(allOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"})
+                public void testMethodAllLiteral() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass101.java:4: Error: The method TestClass101.testMethodAllLiteral is annotated with \
+                @android.annotation.EnforcePermission(allOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) \
+                which differs from the overridden method Stub.testMethodAllLiteral: \
+                @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAllLiteral() {}
+                                ~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
     }
 
     fun testDetectIssuesMissingAnnotationOnMethod() {
@@ -139,12 +269,13 @@
                 *stubs
         )
         .run()
-        .expect("""src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod \
-overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same \
-annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation]
-    public void testMethod() {}
-                ~~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
+        .expect("""
+                src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod overrides the method Stub.testMethod which is annotated with @EnforcePermission. \
+                The same annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
     }
 
     fun testDetectIssuesExtraAnnotationMethod() {
@@ -159,34 +290,13 @@
                 *stubs
         )
         .run()
-        .expect("""src/test/pkg/TestClass7.java:4: Error: The method TestClass7.testMethod \
-overrides the method Stub.testMethod which is not annotated with @EnforcePermission. The same \
-annotation must be used on Stub.testMethod. Did you forget to annotate the AIDL definition? \
-[MissingEnforcePermissionAnnotation]
-    public void testMethod() {}
-                ~~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
-    }
-
-    fun testDetectIssuesExtraAnnotationInterface() {
-        lint().files(java(
-            """
-            package test.pkg;
-            @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
-            public class TestClass8 extends IBar.Stub {
-                public void testMethod() {}
-            }
-            """).indented(),
-                *stubs
-        )
-        .run()
-        .expect("""src/test/pkg/TestClass8.java:2: Error: The class test.pkg.TestClass8 \
-extends the class IBar.Stub which is not annotated with @EnforcePermission. The same annotation \
-must be used on IBar.Stub. Did you forget to annotate the AIDL definition? \
-[MissingEnforcePermissionAnnotation]
-@android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
-^
-1 errors, 0 warnings""".addLineContinuation())
+        .expect("""
+                src/test/pkg/TestClass7.java:4: Error: The method TestClass7.testMethod overrides the method Stub.testMethod which is not annotated with @EnforcePermission. \
+                The same annotation must be used on Stub.testMethod. Did you forget to annotate the AIDL definition? [MissingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
     }
 
     fun testDetectIssuesMissingAnnotationOnMethodWhenClassIsCalledDefault() {
@@ -213,21 +323,6 @@
 
     /* Stubs */
 
-    // A service with permission annotation on the class.
-    private val interfaceIFooStub: TestFile = java(
-        """
-        @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-        public interface IFoo {
-         @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-         public static abstract class Stub extends android.os.Binder implements IFoo {
-           @Override
-           public void testMethod() {}
-         }
-         public void testMethod();
-        }
-        """
-    ).indented()
-
     // A service with permission annotation on the method.
     private val interfaceIFooMethodStub: TestFile = java(
         """
@@ -236,9 +331,29 @@
             @Override
             @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
             public void testMethod() {}
+            @Override
+            @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+            public void testMethodAny() {}
+            @Override
+            @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+            public void testMethodAnyLiteral() {}
+            @Override
+            @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+            public void testMethodAll() {}
+            @Override
+            @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+            public void testMethodAllLiteral() {}
           }
           @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
           public void testMethod();
+          @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+          public void testMethodAny() {}
+          @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+          public void testMethodAnyLiteral() {}
+          @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+          public void testMethodAll() {}
+          @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+          public void testMethodAllLiteral() {}
         }
         """
     ).indented()
@@ -261,6 +376,7 @@
         package android.Manifest;
         class permission {
           public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+          public static final String NFC = "android.permission.NFC";
           public static final String INTERNET = "android.permission.INTERNET";
         }
         """
@@ -273,7 +389,7 @@
         """
     ).indented()
 
-    private val stubs = arrayOf(interfaceIFooStub, interfaceIFooMethodStub, interfaceIBarStub,
+    private val stubs = arrayOf(interfaceIFooMethodStub, interfaceIBarStub,
             manifestPermissionStub, enforcePermissionAnnotationStub)
 
     // Substitutes "backslash + new line" with an empty string to imitate line continuation