Merge "QuickSettings: System accent color for seekbars." into lmp-preview-dev
diff --git a/api/current.txt b/api/current.txt
index cc3c0ab..be81477 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1862,28 +1862,28 @@
     field public static final int TextAppearance_Medium = 16973892; // 0x1030044
     field public static final int TextAppearance_Medium_Inverse = 16973893; // 0x1030045
     field public static final int TextAppearance_Quantum = 16974348; // 0x103020c
-    field public static final int TextAppearance_Quantum_Body1 = 16974545; // 0x10302d1
-    field public static final int TextAppearance_Quantum_Body2 = 16974544; // 0x10302d0
-    field public static final int TextAppearance_Quantum_Button = 16974548; // 0x10302d4
-    field public static final int TextAppearance_Quantum_Caption = 16974546; // 0x10302d2
+    field public static final int TextAppearance_Quantum_Body1 = 16974546; // 0x10302d2
+    field public static final int TextAppearance_Quantum_Body2 = 16974545; // 0x10302d1
+    field public static final int TextAppearance_Quantum_Button = 16974549; // 0x10302d5
+    field public static final int TextAppearance_Quantum_Caption = 16974547; // 0x10302d3
     field public static final int TextAppearance_Quantum_DialogWindowTitle = 16974349; // 0x103020d
-    field public static final int TextAppearance_Quantum_Display1 = 16974540; // 0x10302cc
-    field public static final int TextAppearance_Quantum_Display2 = 16974539; // 0x10302cb
-    field public static final int TextAppearance_Quantum_Display3 = 16974538; // 0x10302ca
-    field public static final int TextAppearance_Quantum_Display4 = 16974537; // 0x10302c9
-    field public static final int TextAppearance_Quantum_Headline = 16974541; // 0x10302cd
+    field public static final int TextAppearance_Quantum_Display1 = 16974541; // 0x10302cd
+    field public static final int TextAppearance_Quantum_Display2 = 16974540; // 0x10302cc
+    field public static final int TextAppearance_Quantum_Display3 = 16974539; // 0x10302cb
+    field public static final int TextAppearance_Quantum_Display4 = 16974538; // 0x10302ca
+    field public static final int TextAppearance_Quantum_Headline = 16974542; // 0x10302ce
     field public static final int TextAppearance_Quantum_Inverse = 16974350; // 0x103020e
     field public static final int TextAppearance_Quantum_Large = 16974351; // 0x103020f
     field public static final int TextAppearance_Quantum_Large_Inverse = 16974352; // 0x1030210
     field public static final int TextAppearance_Quantum_Medium = 16974353; // 0x1030211
     field public static final int TextAppearance_Quantum_Medium_Inverse = 16974354; // 0x1030212
-    field public static final int TextAppearance_Quantum_Menu = 16974547; // 0x10302d3
+    field public static final int TextAppearance_Quantum_Menu = 16974548; // 0x10302d4
     field public static final int TextAppearance_Quantum_SearchResult_Subtitle = 16974355; // 0x1030213
     field public static final int TextAppearance_Quantum_SearchResult_Title = 16974356; // 0x1030214
     field public static final int TextAppearance_Quantum_Small = 16974357; // 0x1030215
     field public static final int TextAppearance_Quantum_Small_Inverse = 16974358; // 0x1030216
-    field public static final int TextAppearance_Quantum_Subhead = 16974543; // 0x10302cf
-    field public static final int TextAppearance_Quantum_Title = 16974542; // 0x10302ce
+    field public static final int TextAppearance_Quantum_Subhead = 16974544; // 0x10302d0
+    field public static final int TextAppearance_Quantum_Title = 16974543; // 0x10302cf
     field public static final int TextAppearance_Quantum_Widget = 16974360; // 0x1030218
     field public static final int TextAppearance_Quantum_Widget_ActionBar_Menu = 16974361; // 0x1030219
     field public static final int TextAppearance_Quantum_Widget_ActionBar_Subtitle = 16974362; // 0x103021a
@@ -1932,9 +1932,10 @@
     field public static final int Theme = 16973829; // 0x1030005
     field public static final int ThemeOverlay = 16974412; // 0x103024c
     field public static final int ThemeOverlay_Quantum = 16974413; // 0x103024d
-    field public static final int ThemeOverlay_Quantum_ActionBarWidget = 16974416; // 0x1030250
-    field public static final int ThemeOverlay_Quantum_Dark = 16974415; // 0x103024f
-    field public static final int ThemeOverlay_Quantum_Light = 16974414; // 0x103024e
+    field public static final int ThemeOverlay_Quantum_ActionBar = 16974414; // 0x103024e
+    field public static final int ThemeOverlay_Quantum_Dark = 16974416; // 0x1030250
+    field public static final int ThemeOverlay_Quantum_Dark_ActionBar = 16974417; // 0x1030251
+    field public static final int ThemeOverlay_Quantum_Light = 16974415; // 0x103024f
     field public static final int Theme_Black = 16973832; // 0x1030008
     field public static final int Theme_Black_NoTitleBar = 16973833; // 0x1030009
     field public static final int Theme_Black_NoTitleBar_Fullscreen = 16973834; // 0x103000a
@@ -2324,126 +2325,126 @@
     field public static final int Widget_ProgressBar_Large_Inverse = 16973916; // 0x103005c
     field public static final int Widget_ProgressBar_Small = 16973854; // 0x103001e
     field public static final int Widget_ProgressBar_Small_Inverse = 16973917; // 0x103005d
-    field public static final int Widget_Quantum = 16974417; // 0x1030251
-    field public static final int Widget_Quantum_ActionBar = 16974418; // 0x1030252
-    field public static final int Widget_Quantum_ActionBar_Solid = 16974419; // 0x1030253
-    field public static final int Widget_Quantum_ActionBar_TabBar = 16974420; // 0x1030254
-    field public static final int Widget_Quantum_ActionBar_TabText = 16974421; // 0x1030255
-    field public static final int Widget_Quantum_ActionBar_TabView = 16974422; // 0x1030256
-    field public static final int Widget_Quantum_ActionButton = 16974423; // 0x1030257
-    field public static final int Widget_Quantum_ActionButton_CloseMode = 16974424; // 0x1030258
-    field public static final int Widget_Quantum_ActionButton_Overflow = 16974425; // 0x1030259
-    field public static final int Widget_Quantum_ActionMode = 16974426; // 0x103025a
-    field public static final int Widget_Quantum_AutoCompleteTextView = 16974427; // 0x103025b
-    field public static final int Widget_Quantum_Button = 16974428; // 0x103025c
-    field public static final int Widget_Quantum_ButtonBar = 16974434; // 0x1030262
-    field public static final int Widget_Quantum_ButtonBar_AlertDialog = 16974435; // 0x1030263
-    field public static final int Widget_Quantum_Button_Borderless = 16974429; // 0x103025d
-    field public static final int Widget_Quantum_Button_Borderless_Small = 16974430; // 0x103025e
-    field public static final int Widget_Quantum_Button_Inset = 16974431; // 0x103025f
-    field public static final int Widget_Quantum_Button_Small = 16974432; // 0x1030260
-    field public static final int Widget_Quantum_Button_Toggle = 16974433; // 0x1030261
-    field public static final int Widget_Quantum_CalendarView = 16974436; // 0x1030264
-    field public static final int Widget_Quantum_CheckedTextView = 16974437; // 0x1030265
-    field public static final int Widget_Quantum_CompoundButton_CheckBox = 16974438; // 0x1030266
-    field public static final int Widget_Quantum_CompoundButton_RadioButton = 16974439; // 0x1030267
-    field public static final int Widget_Quantum_CompoundButton_Star = 16974440; // 0x1030268
-    field public static final int Widget_Quantum_DatePicker = 16974441; // 0x1030269
-    field public static final int Widget_Quantum_DropDownItem = 16974442; // 0x103026a
-    field public static final int Widget_Quantum_DropDownItem_Spinner = 16974443; // 0x103026b
-    field public static final int Widget_Quantum_EditText = 16974444; // 0x103026c
-    field public static final int Widget_Quantum_ExpandableListView = 16974445; // 0x103026d
-    field public static final int Widget_Quantum_FastScroll = 16974446; // 0x103026e
-    field public static final int Widget_Quantum_GridView = 16974447; // 0x103026f
-    field public static final int Widget_Quantum_HorizontalScrollView = 16974448; // 0x1030270
-    field public static final int Widget_Quantum_ImageButton = 16974449; // 0x1030271
-    field public static final int Widget_Quantum_Light = 16974476; // 0x103028c
-    field public static final int Widget_Quantum_Light_ActionBar = 16974477; // 0x103028d
-    field public static final int Widget_Quantum_Light_ActionBar_Solid = 16974478; // 0x103028e
-    field public static final int Widget_Quantum_Light_ActionBar_TabBar = 16974479; // 0x103028f
-    field public static final int Widget_Quantum_Light_ActionBar_TabText = 16974480; // 0x1030290
-    field public static final int Widget_Quantum_Light_ActionBar_TabView = 16974481; // 0x1030291
-    field public static final int Widget_Quantum_Light_ActionButton = 16974482; // 0x1030292
-    field public static final int Widget_Quantum_Light_ActionButton_CloseMode = 16974483; // 0x1030293
-    field public static final int Widget_Quantum_Light_ActionButton_Overflow = 16974484; // 0x1030294
-    field public static final int Widget_Quantum_Light_ActionMode = 16974485; // 0x1030295
-    field public static final int Widget_Quantum_Light_AutoCompleteTextView = 16974486; // 0x1030296
-    field public static final int Widget_Quantum_Light_Button = 16974487; // 0x1030297
-    field public static final int Widget_Quantum_Light_ButtonBar = 16974493; // 0x103029d
-    field public static final int Widget_Quantum_Light_ButtonBar_AlertDialog = 16974494; // 0x103029e
-    field public static final int Widget_Quantum_Light_Button_Borderless = 16974488; // 0x1030298
-    field public static final int Widget_Quantum_Light_Button_Borderless_Small = 16974489; // 0x1030299
-    field public static final int Widget_Quantum_Light_Button_Inset = 16974490; // 0x103029a
-    field public static final int Widget_Quantum_Light_Button_Small = 16974491; // 0x103029b
-    field public static final int Widget_Quantum_Light_Button_Toggle = 16974492; // 0x103029c
-    field public static final int Widget_Quantum_Light_CalendarView = 16974495; // 0x103029f
-    field public static final int Widget_Quantum_Light_CheckedTextView = 16974496; // 0x10302a0
-    field public static final int Widget_Quantum_Light_CompoundButton_CheckBox = 16974497; // 0x10302a1
-    field public static final int Widget_Quantum_Light_CompoundButton_RadioButton = 16974498; // 0x10302a2
-    field public static final int Widget_Quantum_Light_CompoundButton_Star = 16974499; // 0x10302a3
-    field public static final int Widget_Quantum_Light_DropDownItem = 16974500; // 0x10302a4
-    field public static final int Widget_Quantum_Light_DropDownItem_Spinner = 16974501; // 0x10302a5
-    field public static final int Widget_Quantum_Light_EditText = 16974502; // 0x10302a6
-    field public static final int Widget_Quantum_Light_ExpandableListView = 16974503; // 0x10302a7
-    field public static final int Widget_Quantum_Light_FastScroll = 16974504; // 0x10302a8
-    field public static final int Widget_Quantum_Light_GridView = 16974505; // 0x10302a9
-    field public static final int Widget_Quantum_Light_HorizontalScrollView = 16974506; // 0x10302aa
-    field public static final int Widget_Quantum_Light_ImageButton = 16974507; // 0x10302ab
-    field public static final int Widget_Quantum_Light_ListPopupWindow = 16974508; // 0x10302ac
-    field public static final int Widget_Quantum_Light_ListView = 16974509; // 0x10302ad
-    field public static final int Widget_Quantum_Light_ListView_DropDown = 16974510; // 0x10302ae
-    field public static final int Widget_Quantum_Light_MediaRouteButton = 16974511; // 0x10302af
-    field public static final int Widget_Quantum_Light_PopupMenu = 16974512; // 0x10302b0
-    field public static final int Widget_Quantum_Light_PopupMenu_Overflow = 16974513; // 0x10302b1
-    field public static final int Widget_Quantum_Light_PopupWindow = 16974514; // 0x10302b2
-    field public static final int Widget_Quantum_Light_ProgressBar = 16974515; // 0x10302b3
-    field public static final int Widget_Quantum_Light_ProgressBar_Horizontal = 16974516; // 0x10302b4
-    field public static final int Widget_Quantum_Light_ProgressBar_Inverse = 16974517; // 0x10302b5
-    field public static final int Widget_Quantum_Light_ProgressBar_Large = 16974518; // 0x10302b6
-    field public static final int Widget_Quantum_Light_ProgressBar_Large_Inverse = 16974519; // 0x10302b7
-    field public static final int Widget_Quantum_Light_ProgressBar_Small = 16974520; // 0x10302b8
-    field public static final int Widget_Quantum_Light_ProgressBar_Small_Inverse = 16974521; // 0x10302b9
-    field public static final int Widget_Quantum_Light_ProgressBar_Small_Title = 16974522; // 0x10302ba
-    field public static final int Widget_Quantum_Light_RatingBar = 16974523; // 0x10302bb
-    field public static final int Widget_Quantum_Light_RatingBar_Indicator = 16974524; // 0x10302bc
-    field public static final int Widget_Quantum_Light_RatingBar_Small = 16974525; // 0x10302bd
-    field public static final int Widget_Quantum_Light_ScrollView = 16974526; // 0x10302be
-    field public static final int Widget_Quantum_Light_SeekBar = 16974527; // 0x10302bf
-    field public static final int Widget_Quantum_Light_SegmentedButton = 16974528; // 0x10302c0
-    field public static final int Widget_Quantum_Light_Spinner = 16974530; // 0x10302c2
-    field public static final int Widget_Quantum_Light_StackView = 16974529; // 0x10302c1
-    field public static final int Widget_Quantum_Light_Tab = 16974531; // 0x10302c3
-    field public static final int Widget_Quantum_Light_TabWidget = 16974532; // 0x10302c4
-    field public static final int Widget_Quantum_Light_TextView = 16974533; // 0x10302c5
-    field public static final int Widget_Quantum_Light_TextView_SpinnerItem = 16974534; // 0x10302c6
-    field public static final int Widget_Quantum_Light_WebTextView = 16974535; // 0x10302c7
-    field public static final int Widget_Quantum_Light_WebView = 16974536; // 0x10302c8
-    field public static final int Widget_Quantum_ListPopupWindow = 16974450; // 0x1030272
-    field public static final int Widget_Quantum_ListView = 16974451; // 0x1030273
-    field public static final int Widget_Quantum_ListView_DropDown = 16974452; // 0x1030274
-    field public static final int Widget_Quantum_MediaRouteButton = 16974453; // 0x1030275
-    field public static final int Widget_Quantum_PopupMenu = 16974454; // 0x1030276
-    field public static final int Widget_Quantum_PopupMenu_Overflow = 16974455; // 0x1030277
-    field public static final int Widget_Quantum_PopupWindow = 16974456; // 0x1030278
-    field public static final int Widget_Quantum_ProgressBar = 16974457; // 0x1030279
-    field public static final int Widget_Quantum_ProgressBar_Horizontal = 16974458; // 0x103027a
-    field public static final int Widget_Quantum_ProgressBar_Large = 16974459; // 0x103027b
-    field public static final int Widget_Quantum_ProgressBar_Small = 16974460; // 0x103027c
-    field public static final int Widget_Quantum_ProgressBar_Small_Title = 16974461; // 0x103027d
-    field public static final int Widget_Quantum_RatingBar = 16974462; // 0x103027e
-    field public static final int Widget_Quantum_RatingBar_Indicator = 16974463; // 0x103027f
-    field public static final int Widget_Quantum_RatingBar_Small = 16974464; // 0x1030280
-    field public static final int Widget_Quantum_ScrollView = 16974465; // 0x1030281
-    field public static final int Widget_Quantum_SeekBar = 16974466; // 0x1030282
-    field public static final int Widget_Quantum_SegmentedButton = 16974467; // 0x1030283
-    field public static final int Widget_Quantum_Spinner = 16974469; // 0x1030285
-    field public static final int Widget_Quantum_StackView = 16974468; // 0x1030284
-    field public static final int Widget_Quantum_Tab = 16974470; // 0x1030286
-    field public static final int Widget_Quantum_TabWidget = 16974471; // 0x1030287
-    field public static final int Widget_Quantum_TextView = 16974472; // 0x1030288
-    field public static final int Widget_Quantum_TextView_SpinnerItem = 16974473; // 0x1030289
-    field public static final int Widget_Quantum_WebTextView = 16974474; // 0x103028a
-    field public static final int Widget_Quantum_WebView = 16974475; // 0x103028b
+    field public static final int Widget_Quantum = 16974418; // 0x1030252
+    field public static final int Widget_Quantum_ActionBar = 16974419; // 0x1030253
+    field public static final int Widget_Quantum_ActionBar_Solid = 16974420; // 0x1030254
+    field public static final int Widget_Quantum_ActionBar_TabBar = 16974421; // 0x1030255
+    field public static final int Widget_Quantum_ActionBar_TabText = 16974422; // 0x1030256
+    field public static final int Widget_Quantum_ActionBar_TabView = 16974423; // 0x1030257
+    field public static final int Widget_Quantum_ActionButton = 16974424; // 0x1030258
+    field public static final int Widget_Quantum_ActionButton_CloseMode = 16974425; // 0x1030259
+    field public static final int Widget_Quantum_ActionButton_Overflow = 16974426; // 0x103025a
+    field public static final int Widget_Quantum_ActionMode = 16974427; // 0x103025b
+    field public static final int Widget_Quantum_AutoCompleteTextView = 16974428; // 0x103025c
+    field public static final int Widget_Quantum_Button = 16974429; // 0x103025d
+    field public static final int Widget_Quantum_ButtonBar = 16974435; // 0x1030263
+    field public static final int Widget_Quantum_ButtonBar_AlertDialog = 16974436; // 0x1030264
+    field public static final int Widget_Quantum_Button_Borderless = 16974430; // 0x103025e
+    field public static final int Widget_Quantum_Button_Borderless_Small = 16974431; // 0x103025f
+    field public static final int Widget_Quantum_Button_Inset = 16974432; // 0x1030260
+    field public static final int Widget_Quantum_Button_Small = 16974433; // 0x1030261
+    field public static final int Widget_Quantum_Button_Toggle = 16974434; // 0x1030262
+    field public static final int Widget_Quantum_CalendarView = 16974437; // 0x1030265
+    field public static final int Widget_Quantum_CheckedTextView = 16974438; // 0x1030266
+    field public static final int Widget_Quantum_CompoundButton_CheckBox = 16974439; // 0x1030267
+    field public static final int Widget_Quantum_CompoundButton_RadioButton = 16974440; // 0x1030268
+    field public static final int Widget_Quantum_CompoundButton_Star = 16974441; // 0x1030269
+    field public static final int Widget_Quantum_DatePicker = 16974442; // 0x103026a
+    field public static final int Widget_Quantum_DropDownItem = 16974443; // 0x103026b
+    field public static final int Widget_Quantum_DropDownItem_Spinner = 16974444; // 0x103026c
+    field public static final int Widget_Quantum_EditText = 16974445; // 0x103026d
+    field public static final int Widget_Quantum_ExpandableListView = 16974446; // 0x103026e
+    field public static final int Widget_Quantum_FastScroll = 16974447; // 0x103026f
+    field public static final int Widget_Quantum_GridView = 16974448; // 0x1030270
+    field public static final int Widget_Quantum_HorizontalScrollView = 16974449; // 0x1030271
+    field public static final int Widget_Quantum_ImageButton = 16974450; // 0x1030272
+    field public static final int Widget_Quantum_Light = 16974477; // 0x103028d
+    field public static final int Widget_Quantum_Light_ActionBar = 16974478; // 0x103028e
+    field public static final int Widget_Quantum_Light_ActionBar_Solid = 16974479; // 0x103028f
+    field public static final int Widget_Quantum_Light_ActionBar_TabBar = 16974480; // 0x1030290
+    field public static final int Widget_Quantum_Light_ActionBar_TabText = 16974481; // 0x1030291
+    field public static final int Widget_Quantum_Light_ActionBar_TabView = 16974482; // 0x1030292
+    field public static final int Widget_Quantum_Light_ActionButton = 16974483; // 0x1030293
+    field public static final int Widget_Quantum_Light_ActionButton_CloseMode = 16974484; // 0x1030294
+    field public static final int Widget_Quantum_Light_ActionButton_Overflow = 16974485; // 0x1030295
+    field public static final int Widget_Quantum_Light_ActionMode = 16974486; // 0x1030296
+    field public static final int Widget_Quantum_Light_AutoCompleteTextView = 16974487; // 0x1030297
+    field public static final int Widget_Quantum_Light_Button = 16974488; // 0x1030298
+    field public static final int Widget_Quantum_Light_ButtonBar = 16974494; // 0x103029e
+    field public static final int Widget_Quantum_Light_ButtonBar_AlertDialog = 16974495; // 0x103029f
+    field public static final int Widget_Quantum_Light_Button_Borderless = 16974489; // 0x1030299
+    field public static final int Widget_Quantum_Light_Button_Borderless_Small = 16974490; // 0x103029a
+    field public static final int Widget_Quantum_Light_Button_Inset = 16974491; // 0x103029b
+    field public static final int Widget_Quantum_Light_Button_Small = 16974492; // 0x103029c
+    field public static final int Widget_Quantum_Light_Button_Toggle = 16974493; // 0x103029d
+    field public static final int Widget_Quantum_Light_CalendarView = 16974496; // 0x10302a0
+    field public static final int Widget_Quantum_Light_CheckedTextView = 16974497; // 0x10302a1
+    field public static final int Widget_Quantum_Light_CompoundButton_CheckBox = 16974498; // 0x10302a2
+    field public static final int Widget_Quantum_Light_CompoundButton_RadioButton = 16974499; // 0x10302a3
+    field public static final int Widget_Quantum_Light_CompoundButton_Star = 16974500; // 0x10302a4
+    field public static final int Widget_Quantum_Light_DropDownItem = 16974501; // 0x10302a5
+    field public static final int Widget_Quantum_Light_DropDownItem_Spinner = 16974502; // 0x10302a6
+    field public static final int Widget_Quantum_Light_EditText = 16974503; // 0x10302a7
+    field public static final int Widget_Quantum_Light_ExpandableListView = 16974504; // 0x10302a8
+    field public static final int Widget_Quantum_Light_FastScroll = 16974505; // 0x10302a9
+    field public static final int Widget_Quantum_Light_GridView = 16974506; // 0x10302aa
+    field public static final int Widget_Quantum_Light_HorizontalScrollView = 16974507; // 0x10302ab
+    field public static final int Widget_Quantum_Light_ImageButton = 16974508; // 0x10302ac
+    field public static final int Widget_Quantum_Light_ListPopupWindow = 16974509; // 0x10302ad
+    field public static final int Widget_Quantum_Light_ListView = 16974510; // 0x10302ae
+    field public static final int Widget_Quantum_Light_ListView_DropDown = 16974511; // 0x10302af
+    field public static final int Widget_Quantum_Light_MediaRouteButton = 16974512; // 0x10302b0
+    field public static final int Widget_Quantum_Light_PopupMenu = 16974513; // 0x10302b1
+    field public static final int Widget_Quantum_Light_PopupMenu_Overflow = 16974514; // 0x10302b2
+    field public static final int Widget_Quantum_Light_PopupWindow = 16974515; // 0x10302b3
+    field public static final int Widget_Quantum_Light_ProgressBar = 16974516; // 0x10302b4
+    field public static final int Widget_Quantum_Light_ProgressBar_Horizontal = 16974517; // 0x10302b5
+    field public static final int Widget_Quantum_Light_ProgressBar_Inverse = 16974518; // 0x10302b6
+    field public static final int Widget_Quantum_Light_ProgressBar_Large = 16974519; // 0x10302b7
+    field public static final int Widget_Quantum_Light_ProgressBar_Large_Inverse = 16974520; // 0x10302b8
+    field public static final int Widget_Quantum_Light_ProgressBar_Small = 16974521; // 0x10302b9
+    field public static final int Widget_Quantum_Light_ProgressBar_Small_Inverse = 16974522; // 0x10302ba
+    field public static final int Widget_Quantum_Light_ProgressBar_Small_Title = 16974523; // 0x10302bb
+    field public static final int Widget_Quantum_Light_RatingBar = 16974524; // 0x10302bc
+    field public static final int Widget_Quantum_Light_RatingBar_Indicator = 16974525; // 0x10302bd
+    field public static final int Widget_Quantum_Light_RatingBar_Small = 16974526; // 0x10302be
+    field public static final int Widget_Quantum_Light_ScrollView = 16974527; // 0x10302bf
+    field public static final int Widget_Quantum_Light_SeekBar = 16974528; // 0x10302c0
+    field public static final int Widget_Quantum_Light_SegmentedButton = 16974529; // 0x10302c1
+    field public static final int Widget_Quantum_Light_Spinner = 16974531; // 0x10302c3
+    field public static final int Widget_Quantum_Light_StackView = 16974530; // 0x10302c2
+    field public static final int Widget_Quantum_Light_Tab = 16974532; // 0x10302c4
+    field public static final int Widget_Quantum_Light_TabWidget = 16974533; // 0x10302c5
+    field public static final int Widget_Quantum_Light_TextView = 16974534; // 0x10302c6
+    field public static final int Widget_Quantum_Light_TextView_SpinnerItem = 16974535; // 0x10302c7
+    field public static final int Widget_Quantum_Light_WebTextView = 16974536; // 0x10302c8
+    field public static final int Widget_Quantum_Light_WebView = 16974537; // 0x10302c9
+    field public static final int Widget_Quantum_ListPopupWindow = 16974451; // 0x1030273
+    field public static final int Widget_Quantum_ListView = 16974452; // 0x1030274
+    field public static final int Widget_Quantum_ListView_DropDown = 16974453; // 0x1030275
+    field public static final int Widget_Quantum_MediaRouteButton = 16974454; // 0x1030276
+    field public static final int Widget_Quantum_PopupMenu = 16974455; // 0x1030277
+    field public static final int Widget_Quantum_PopupMenu_Overflow = 16974456; // 0x1030278
+    field public static final int Widget_Quantum_PopupWindow = 16974457; // 0x1030279
+    field public static final int Widget_Quantum_ProgressBar = 16974458; // 0x103027a
+    field public static final int Widget_Quantum_ProgressBar_Horizontal = 16974459; // 0x103027b
+    field public static final int Widget_Quantum_ProgressBar_Large = 16974460; // 0x103027c
+    field public static final int Widget_Quantum_ProgressBar_Small = 16974461; // 0x103027d
+    field public static final int Widget_Quantum_ProgressBar_Small_Title = 16974462; // 0x103027e
+    field public static final int Widget_Quantum_RatingBar = 16974463; // 0x103027f
+    field public static final int Widget_Quantum_RatingBar_Indicator = 16974464; // 0x1030280
+    field public static final int Widget_Quantum_RatingBar_Small = 16974465; // 0x1030281
+    field public static final int Widget_Quantum_ScrollView = 16974466; // 0x1030282
+    field public static final int Widget_Quantum_SeekBar = 16974467; // 0x1030283
+    field public static final int Widget_Quantum_SegmentedButton = 16974468; // 0x1030284
+    field public static final int Widget_Quantum_Spinner = 16974470; // 0x1030286
+    field public static final int Widget_Quantum_StackView = 16974469; // 0x1030285
+    field public static final int Widget_Quantum_Tab = 16974471; // 0x1030287
+    field public static final int Widget_Quantum_TabWidget = 16974472; // 0x1030288
+    field public static final int Widget_Quantum_TextView = 16974473; // 0x1030289
+    field public static final int Widget_Quantum_TextView_SpinnerItem = 16974474; // 0x103028a
+    field public static final int Widget_Quantum_WebTextView = 16974475; // 0x103028b
+    field public static final int Widget_Quantum_WebView = 16974476; // 0x103028c
     field public static final int Widget_RatingBar = 16973857; // 0x1030021
     field public static final int Widget_ScrollView = 16973869; // 0x103002d
     field public static final int Widget_SeekBar = 16973856; // 0x1030020
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index 7738d2d..d62958f 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -54,7 +54,7 @@
 public abstract class CameraCaptureSession implements AutoCloseable {
 
     /**
-     * Get the camera device that this session is created for
+     * Get the camera device that this session is created for.
      */
     public abstract CameraDevice getDevice();
 
@@ -90,8 +90,9 @@
      *
      * @throws CameraAccessException if the camera device is no longer connected or has
      *                               encountered a fatal error
-     * @throws IllegalStateException if this session is no longer active, either because a new
-     *                               session has been created or the camera device has been closed.
+     * @throws IllegalStateException if this session is no longer active, either because the session
+     *                               was explicitly closed, a new session has been created
+     *                               or the camera device has been closed.
      * @throws IllegalArgumentException if the request targets Surfaces that are not configured as
      *                                  outputs for this session. Or if the handler is null, the
      *                                  listener is not null, and the calling thread has no looper.
@@ -99,6 +100,7 @@
      * @see #captureBurst
      * @see #setRepeatingRequest
      * @see #setRepeatingBurst
+     * @see #abortCaptures
      */
     public abstract int capture(CaptureRequest request, CaptureListener listener, Handler handler)
             throws CameraAccessException;
@@ -132,8 +134,9 @@
      *
      * @throws CameraAccessException if the camera device is no longer connected or has
      *                               encountered a fatal error
-     * @throws IllegalStateException if this session is no longer active, either because a new
-     *                               session has been created or the camera device has been closed.
+     * @throws IllegalStateException if this session is no longer active, either because the session
+     *                               was explicitly closed, a new session has been created
+     *                               or the camera device has been closed.
      * @throws IllegalArgumentException If the requests target Surfaces not currently configured as
      *                                  outputs. Or if the handler is null, the listener is not
      *                                  null, and the calling thread has no looper.
@@ -141,6 +144,7 @@
      * @see #capture
      * @see #setRepeatingRequest
      * @see #setRepeatingBurst
+     * @see #abortCaptures
      */
     public abstract int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
             Handler handler) throws CameraAccessException;
@@ -188,11 +192,13 @@
      *
      * @throws CameraAccessException if the camera device is no longer connected or has
      *                               encountered a fatal error
-     * @throws IllegalStateException if this session is no longer active, either because a new
-     *                               session has been created or the camera device has been closed.
+     * @throws IllegalStateException if this session is no longer active, either because the session
+     *                               was explicitly closed, a new session has been created
+     *                               or the camera device has been closed.
      * @throws IllegalArgumentException If the requests reference Surfaces that are not currently
      *                                  configured as outputs. Or if the handler is null, the
      *                                  listener is not null, and the calling thread has no looper.
+     *                                  Or if no requests were passed in.
      *
      * @see #capture
      * @see #captureBurst
@@ -246,11 +252,13 @@
      *
      * @throws CameraAccessException if the camera device is no longer connected or has
      *                               encountered a fatal error
-     * @throws IllegalStateException if this session is no longer active, either because a new
-     *                               session has been created or the camera device has been closed.
+     * @throws IllegalStateException if this session is no longer active, either because the session
+     *                               was explicitly closed, a new session has been created
+     *                               or the camera device has been closed.
      * @throws IllegalArgumentException If the requests reference Surfaces not currently configured
      *                                  as outputs. Or if the handler is null, the listener is not
-     *                                  null, and the calling thread has no looper.
+     *                                  null, and the calling thread has no looper. Or if no
+     *                                  requests were passed in.
      *
      * @see #capture
      * @see #captureBurst
@@ -274,8 +282,9 @@
      *
      * @throws CameraAccessException if the camera device is no longer connected or has
      *                               encountered a fatal error
-     * @throws IllegalStateException if this session is no longer active, either because a new
-     *                               session has been created or the camera device has been closed.
+     * @throws IllegalStateException if this session is no longer active, either because the session
+     *                               was explicitly closed, a new session has been created
+     *                               or the camera device has been closed.
      *
      * @see #setRepeatingRequest
      * @see #setRepeatingBurst
@@ -308,8 +317,9 @@
      *
      * @throws CameraAccessException if the camera device is no longer connected or has
      *                               encountered a fatal error
-     * @throws IllegalStateException if this session is no longer active, either because a new
-     *                               session has been created or the camera device has been closed.
+     * @throws IllegalStateException if this session is no longer active, either because the session
+     *                               was explicitly closed, a new session has been created
+     *                               or the camera device has been closed.
      *
      * @see #setRepeatingRequest
      * @see #setRepeatingBurst
@@ -320,8 +330,8 @@
     /**
      * Close this capture session asynchronously.
      *
-     * <p>Closing a session frees up the target output Surfaces of the session for reuse with either a
-     * new session, or to other APIs that can draw to Surfaces.</p>
+     * <p>Closing a session frees up the target output Surfaces of the session for reuse with either
+     * a new session, or to other APIs that can draw to Surfaces.</p>
      *
      * <p>Note that creating a new capture session with {@link CameraDevice#createCaptureSession}
      * will close any existing capture session automatically, and call the older session listener's
@@ -334,6 +344,8 @@
      * However, any in-progress capture requests submitted to the session will be completed as
      * normal; once all captures have completed and the session has been torn down,
      * {@link StateListener#onClosed} will be called.</p>
+     *
+     * <p>Closing a session is idempotent; closing more than once has no effect.</p>
      */
     @Override
     public abstract void close();
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 6f5099b..f9f617a 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -244,6 +244,7 @@
      * @see StreamConfigurationMap#getOutputSizes(Class)
      * @deprecated Use {@link #createCaptureSession} instead
      */
+    @Deprecated
     public void configureOutputs(List<Surface> outputs) throws CameraAccessException;
 
     /**
@@ -432,6 +433,7 @@
      * @see #setRepeatingBurst
      * @deprecated Use {@link CameraCaptureSession} instead
      */
+    @Deprecated
     public int capture(CaptureRequest request, CaptureListener listener, Handler handler)
             throws CameraAccessException;
 
@@ -470,13 +472,15 @@
      *                               or the camera device has been closed.
      * @throws IllegalArgumentException If the requests target Surfaces not
      * currently configured as outputs. Or if the handler is null, the listener
-     * is not null, and the calling thread has no looper.
+     * is not null, and the calling thread has no looper. Or if no requests were
+     * passed in.
      *
      * @see #capture
      * @see #setRepeatingRequest
      * @see #setRepeatingBurst
      * @deprecated Use {@link CameraCaptureSession} instead
      */
+    @Deprecated
     public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
             Handler handler) throws CameraAccessException;
 
@@ -536,6 +540,7 @@
      * @see #flush
      * @deprecated Use {@link CameraCaptureSession} instead
      */
+    @Deprecated
     public int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
             Handler handler) throws CameraAccessException;
 
@@ -586,7 +591,8 @@
      *                               or the camera device has been closed.
      * @throws IllegalArgumentException If the requests reference Surfaces not
      * currently configured as outputs. Or if the handler is null, the listener
-     * is not null, and the calling thread has no looper.
+     * is not null, and the calling thread has no looper. Or if no requests were
+     * passed in.
      *
      * @see #capture
      * @see #captureBurst
@@ -595,6 +601,7 @@
      * @see #flush
      * @deprecated Use {@link CameraCaptureSession} instead
      */
+    @Deprecated
     public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
             Handler handler) throws CameraAccessException;
 
@@ -620,6 +627,7 @@
      * @see StateListener#onIdle
      * @deprecated Use {@link CameraCaptureSession} instead
      */
+    @Deprecated
     public void stopRepeating() throws CameraAccessException;
 
     /**
@@ -657,6 +665,7 @@
      * @see #configureOutputs
      * @deprecated Use {@link CameraCaptureSession} instead
      */
+    @Deprecated
     public void flush() throws CameraAccessException;
 
     /**
@@ -691,6 +700,7 @@
      * @see #setRepeatingBurst
      * @deprecated Use {@link CameraCaptureSession} instead
      */
+    @Deprecated
     public static abstract class CaptureListener {
 
         /**
@@ -1042,6 +1052,7 @@
          * @param camera the camera device has that become unconfigured
          * @deprecated Use {@link CameraCaptureSession.StateListener} instead.
          */
+        @Deprecated
         public void onUnconfigured(CameraDevice camera) {
             // Default empty implementation
         }
@@ -1072,6 +1083,7 @@
          * @see CameraDevice#setRepeatingRequest
          * @deprecated Use {@link CameraCaptureSession.StateListener} instead.
          */
+        @Deprecated
         public void onActive(CameraDevice camera) {
             // Default empty implementation
         }
@@ -1106,6 +1118,7 @@
          * @see CameraDevice#flush
          * @deprecated Use {@link CameraCaptureSession.StateListener} instead.
          */
+        @Deprecated
         public void onBusy(CameraDevice camera) {
             // Default empty implementation
         }
@@ -1154,6 +1167,7 @@
          * @see CameraDevice#flush
          * @deprecated Use {@link CameraCaptureSession.StateListener} instead.
          */
+        @Deprecated
         public void onIdle(CameraDevice camera) {
             // Default empty implementation
         }
diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java
index 54568ed..e64deeb 100644
--- a/core/java/android/hardware/camera2/DngCreator.java
+++ b/core/java/android/hardware/camera2/DngCreator.java
@@ -22,12 +22,16 @@
 import android.location.Location;
 import android.media.ExifInterface;
 import android.media.Image;
+import android.os.SystemClock;
 import android.util.Size;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.TimeZone;
 
 /**
  * The {@link DngCreator} class provides functions to write raw pixel data as a DNG file.
@@ -55,6 +59,7 @@
  */
 public final class DngCreator implements AutoCloseable {
 
+    private static final String TAG = "DngCreator";
     /**
      * Create a new DNG object.
      *
@@ -75,7 +80,25 @@
         if (characteristics == null || metadata == null) {
             throw new NullPointerException("Null argument to DngCreator constructor");
         }
-        nativeInit(characteristics.getNativeCopy(), metadata.getNativeCopy());
+
+        // Find current time
+        long currentTime = System.currentTimeMillis();
+
+        // Find boot time
+        long bootTimeMillis = currentTime - SystemClock.elapsedRealtime();
+
+        // Find capture time (nanos since boot)
+        Long timestamp = metadata.get(CaptureResult.SENSOR_TIMESTAMP);
+        long captureTime = currentTime;
+        if (timestamp != null) {
+            captureTime = timestamp / 1000000 + bootTimeMillis;
+        }
+
+        // Format for metadata
+        String formattedCaptureTime = sDateTimeStampFormat.format(captureTime);
+
+        nativeInit(characteristics.getNativeCopy(), metadata.getNativeCopy(),
+                formattedCaptureTime);
     }
 
     /**
@@ -329,6 +352,13 @@
         }
     }
 
+    private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd kk:mm:ss";
+    private static final DateFormat sDateTimeStampFormat =
+            new SimpleDateFormat(TIFF_DATETIME_FORMAT);
+
+    static {
+        sDateTimeStampFormat.setTimeZone(TimeZone.getDefault());
+    }
     /**
      * This field is used by native code, do not access or modify.
      */
@@ -337,7 +367,8 @@
     private static native void nativeClassInit();
 
     private synchronized native void nativeInit(CameraMetadataNative nativeCharacteristics,
-                                                CameraMetadataNative nativeResult);
+                                                CameraMetadataNative nativeResult,
+                                                String captureTime);
 
     private synchronized native void nativeDestroy();
 
diff --git a/core/java/android/hardware/camera2/dispatch/BroadcastDispatcher.java b/core/java/android/hardware/camera2/dispatch/BroadcastDispatcher.java
new file mode 100644
index 0000000..fe575b2
--- /dev/null
+++ b/core/java/android/hardware/camera2/dispatch/BroadcastDispatcher.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 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.dispatch;
+
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * Broadcast a single dispatch into multiple other dispatchables.
+ *
+ * <p>Every time {@link #dispatch} is invoked, all the broadcast targets will
+ * see the same dispatch as well. The first target's return value is returned.</p>
+ *
+ * <p>This enables a single listener to be converted into a multi-listener.</p>
+ */
+public class BroadcastDispatcher<T> implements Dispatchable<T> {
+
+    private final List<Dispatchable<T>> mDispatchTargets;
+
+    /**
+     * Create a broadcast dispatcher from the supplied dispatch targets.
+     *
+     * @param dispatchTargets one or more targets to dispatch to
+     */
+    @SafeVarargs
+    public BroadcastDispatcher(Dispatchable<T>... dispatchTargets) {
+        mDispatchTargets = Arrays.asList(
+                checkNotNull(dispatchTargets, "dispatchTargets must not be null"));
+    }
+
+    @Override
+    public Object dispatch(Method method, Object[] args) throws Throwable {
+        Object result = null;
+        boolean gotResult = false;
+
+        for (Dispatchable<T> dispatchTarget : mDispatchTargets) {
+            Object localResult = dispatchTarget.dispatch(method, args);
+
+            if (!gotResult) {
+                gotResult = true;
+                result = localResult;
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/core/java/android/hardware/camera2/dispatch/Dispatchable.java b/core/java/android/hardware/camera2/dispatch/Dispatchable.java
new file mode 100644
index 0000000..753103f
--- /dev/null
+++ b/core/java/android/hardware/camera2/dispatch/Dispatchable.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 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.dispatch;
+
+import java.lang.reflect.Method;
+
+/**
+ * Dynamically dispatch a method and its argument to some object.
+ *
+ * <p>This can be used to intercept method calls and do work around them, redirect work,
+ * or block calls entirely.</p>
+ */
+public interface Dispatchable<T> {
+    /**
+     * Dispatch the method and arguments to this object.
+     * @param method a method defined in class {@code T}
+     * @param args arguments corresponding to said {@code method}
+     * @return the object returned when invoking {@code method}
+     * @throws Throwable any exception that might have been raised while invoking the method
+     */
+    public Object dispatch(Method method, Object[] args) throws Throwable;
+}
diff --git a/core/java/android/hardware/camera2/dispatch/DuckTypingDispatcher.java b/core/java/android/hardware/camera2/dispatch/DuckTypingDispatcher.java
new file mode 100644
index 0000000..75f97e4
--- /dev/null
+++ b/core/java/android/hardware/camera2/dispatch/DuckTypingDispatcher.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 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.dispatch;
+
+
+import java.lang.reflect.Method;
+
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * Duck typing dispatcher; converts dispatch methods calls from one class to another by
+ * looking up equivalently methods at runtime by name.
+ *
+ * <p>For example, if two types have identical method names and arguments, but
+ * are not subclasses/subinterfaces of each other, this dispatcher will allow calls to be
+ * made from one type to the other.</p>
+ *
+ * @param <TFrom> source dispatch type, whose methods with {@link #dispatch} will be called
+ * @param <T> destination dispatch type, methods will be converted to the class of {@code T}
+ */
+public class DuckTypingDispatcher<TFrom, T> implements Dispatchable<TFrom> {
+
+    private final MethodNameInvoker<T> mDuck;
+
+    /**
+     * Create a new duck typing dispatcher.
+     *
+     * @param target destination dispatch type, methods will be redirected to this dispatcher
+     * @param targetClass destination dispatch class, methods will be converted to this class's
+     */
+    public DuckTypingDispatcher(Dispatchable<T> target, Class<T> targetClass) {
+        checkNotNull(targetClass, "targetClass must not be null");
+        checkNotNull(target, "target must not be null");
+
+        mDuck = new MethodNameInvoker<T>(target, targetClass);
+    }
+
+    @Override
+    public Object dispatch(Method method, Object[] args) {
+        return mDuck.invoke(method.getName(), args);
+    }
+}
diff --git a/core/java/android/hardware/camera2/dispatch/HandlerDispatcher.java b/core/java/android/hardware/camera2/dispatch/HandlerDispatcher.java
new file mode 100644
index 0000000..f8e9d49
--- /dev/null
+++ b/core/java/android/hardware/camera2/dispatch/HandlerDispatcher.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 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.dispatch;
+
+import android.hardware.camera2.utils.UncheckedThrow;
+import android.os.Handler;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * Forward all interface calls into a handler by posting it as a {@code Runnable}.
+ *
+ * <p>All calls will return immediately; functions with return values will return a default
+ * value of {@code null}, {@code 0}, or {@code false} where that value is legal.</p>
+ *
+ * <p>Any exceptions thrown on the handler while trying to invoke a method
+ * will be re-thrown. Throwing checked exceptions on a handler which doesn't expect any
+ * checked exceptions to be thrown will result in "undefined" behavior
+ * (although in practice it is usually thrown as normal).</p>
+ */
+public class HandlerDispatcher<T> implements Dispatchable<T> {
+
+    private static final String TAG = "HandlerDispatcher";
+
+    private final Dispatchable<T> mDispatchTarget;
+    private final Handler mHandler;
+
+    /**
+     * Create a dispatcher that forwards it's dispatch calls by posting
+     * them onto the {@code handler} as a {@code Runnable}.
+     *
+     * @param dispatchTarget the destination whose method calls will be redirected into the handler
+     * @param handler all calls into {@code dispatchTarget} will be posted onto this handler
+     * @param <T> the type of the element you want to wrap.
+     * @return a dispatcher that will forward it's dispatch calls to a handler
+     */
+    public HandlerDispatcher(Dispatchable<T> dispatchTarget, Handler handler) {
+        mDispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null");
+        mHandler = checkNotNull(handler, "handler must not be null");
+    }
+
+    @Override
+    public Object dispatch(final Method method, final Object[] args) throws Throwable {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mDispatchTarget.dispatch(method, args);
+                } catch (InvocationTargetException e) {
+                    Throwable t = e.getTargetException();
+                    // Potential UB. Hopefully 't' is a runtime exception.
+                    UncheckedThrow.throwAnyException(t);
+                } catch (IllegalAccessException e) {
+                    // Impossible
+                    Log.wtf(TAG, "IllegalAccessException while invoking " + method, e);
+                } catch (IllegalArgumentException e) {
+                    // Impossible
+                    Log.wtf(TAG, "IllegalArgumentException while invoking " + method, e);
+                } catch (Throwable e) {
+                    UncheckedThrow.throwAnyException(e);
+                }
+            }
+        });
+
+        // TODO handle primitive return values that would avoid NPE if unboxed
+        return null;
+    }
+}
diff --git a/core/java/android/hardware/camera2/dispatch/InvokeDispatcher.java b/core/java/android/hardware/camera2/dispatch/InvokeDispatcher.java
new file mode 100644
index 0000000..ac5f526
--- /dev/null
+++ b/core/java/android/hardware/camera2/dispatch/InvokeDispatcher.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 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.dispatch;
+
+import android.hardware.camera2.utils.UncheckedThrow;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import static com.android.internal.util.Preconditions.*;
+
+
+public class InvokeDispatcher<T> implements Dispatchable<T> {
+
+    private static final String TAG = "InvocationSink";
+    private final T mTarget;
+
+    public InvokeDispatcher(T target) {
+        mTarget = checkNotNull(target, "target must not be null");
+    }
+
+    @Override
+    public Object dispatch(Method method, Object[] args) {
+        try {
+            return method.invoke(mTarget, args);
+        } catch (InvocationTargetException e) {
+            Throwable t = e.getTargetException();
+            // Potential UB. Hopefully 't' is a runtime exception.
+            UncheckedThrow.throwAnyException(t);
+        } catch (IllegalAccessException e) {
+            // Impossible
+            Log.wtf(TAG, "IllegalAccessException while invoking " + method, e);
+        } catch (IllegalArgumentException e) {
+            // Impossible
+            Log.wtf(TAG, "IllegalArgumentException while invoking " + method, e);
+        }
+
+        // unreachable
+        return null;
+    }
+}
diff --git a/core/java/android/hardware/camera2/dispatch/MethodNameInvoker.java b/core/java/android/hardware/camera2/dispatch/MethodNameInvoker.java
new file mode 100644
index 0000000..02c3d87
--- /dev/null
+++ b/core/java/android/hardware/camera2/dispatch/MethodNameInvoker.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 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.dispatch;
+
+import android.hardware.camera2.utils.UncheckedThrow;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * Invoke a method on a dispatchable by its name (without knowing the {@code Method} ahead of time).
+ *
+ * @param <T> destination dispatch type, methods will be looked up in the class of {@code T}
+ */
+public class MethodNameInvoker<T> {
+
+    private final Dispatchable<T> mTarget;
+    private final Class<T> mTargetClass;
+    private final ConcurrentHashMap<String, Method> mMethods =
+            new ConcurrentHashMap<>();
+
+    /**
+     * Create a new method name invoker.
+     *
+     * @param target destination dispatch type, invokes will be redirected to this dispatcher
+     * @param targetClass destination dispatch class, the invoked methods will be from this class
+     */
+    public MethodNameInvoker(Dispatchable<T> target, Class<T> targetClass) {
+        mTargetClass = targetClass;
+        mTarget = target;
+    }
+
+    /**
+     * Invoke a method by its name.
+     *
+     * <p>If more than one method exists in {@code targetClass}, the first method will be used.</p>
+     *
+     * @param methodName
+     *          The name of the method, which will be matched 1:1 to the destination method
+     * @param params
+     *          Variadic parameter list.
+     * @return
+     *          The same kind of value that would normally be returned by calling {@code methodName}
+     *          statically.
+     *
+     * @throws IllegalArgumentException if {@code methodName} does not exist on the target class
+     * @throws Throwable will rethrow anything that the target method would normally throw
+     */
+    @SuppressWarnings("unchecked")
+    public <K> K invoke(String methodName, Object... params) {
+        checkNotNull(methodName, "methodName must not be null");
+
+        Method targetMethod = mMethods.get(methodName);
+        if (targetMethod == null) {
+            for (Method method : mTargetClass.getMethods()) {
+                // TODO future: match by # of params and types of params if possible
+                if (method.getName().equals(methodName)) {
+                    targetMethod = method;
+                    mMethods.put(methodName, targetMethod);
+                    break;
+                }
+            }
+
+            if (targetMethod == null) {
+                throw new IllegalArgumentException(
+                        "Method " + methodName + " does not exist on class " + mTargetClass);
+            }
+        }
+
+        try {
+            return (K) mTarget.dispatch(targetMethod, params);
+        } catch (Throwable e) {
+            UncheckedThrow.throwAnyException(e);
+            // unreachable
+            return null;
+        }
+    }
+}
diff --git a/core/java/android/hardware/camera2/dispatch/NullDispatcher.java b/core/java/android/hardware/camera2/dispatch/NullDispatcher.java
new file mode 100644
index 0000000..fada075
--- /dev/null
+++ b/core/java/android/hardware/camera2/dispatch/NullDispatcher.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 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.dispatch;
+
+
+import java.lang.reflect.Method;
+
+/**
+ * Do nothing when dispatching; follows the null object pattern.
+ */
+public class NullDispatcher<T> implements Dispatchable<T> {
+    /**
+     * Create a dispatcher that does nothing when dispatched to.
+     */
+    public NullDispatcher() {
+    }
+
+    /**
+     * Do nothing; all parameters are ignored.
+     */
+    @Override
+    public Object dispatch(Method method, Object[] args) {
+        return null;
+    }
+}
diff --git a/core/java/android/hardware/camera2/dispatch/package.html b/core/java/android/hardware/camera2/dispatch/package.html
new file mode 100644
index 0000000..783d0a1
--- /dev/null
+++ b/core/java/android/hardware/camera2/dispatch/package.html
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
new file mode 100644
index 0000000..e129783
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2014 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.impl;
+
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.dispatch.BroadcastDispatcher;
+import android.hardware.camera2.dispatch.Dispatchable;
+import android.hardware.camera2.dispatch.DuckTypingDispatcher;
+import android.hardware.camera2.dispatch.HandlerDispatcher;
+import android.hardware.camera2.dispatch.InvokeDispatcher;
+import android.hardware.camera2.dispatch.NullDispatcher;
+import android.hardware.camera2.utils.TaskDrainer;
+import android.hardware.camera2.utils.TaskSingleDrainer;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Surface;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static android.hardware.camera2.impl.CameraDevice.checkHandler;
+import static com.android.internal.util.Preconditions.*;
+
+public class CameraCaptureSessionImpl extends CameraCaptureSession {
+    private static final String TAG = "CameraCaptureSession";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    /** User-specified set of surfaces used as the configuration outputs */
+    private final List<Surface> mOutputs;
+    /**
+     * User-specified state listener, used for outgoing events; calls to this object will be
+     * automatically {@link Handler#post(Runnable) posted} to {@code mStateHandler}.
+     */
+    private final CameraCaptureSession.StateListener mStateListener;
+    /** User-specified state handler used for outgoing state listener events */
+    private final Handler mStateHandler;
+
+    /** Internal camera device; used to translate calls into existing deprecated API */
+    private final android.hardware.camera2.impl.CameraDevice mDeviceImpl;
+    /** Internal handler; used for all incoming events to preserve total order */
+    private final Handler mDeviceHandler;
+
+    /** Drain Sequence IDs which have been queued but not yet finished with aborted/completed */
+    private final TaskDrainer<Integer> mSequenceDrainer;
+    /** Drain state transitions from ACTIVE -> IDLE */
+    private final TaskSingleDrainer mIdleDrainer;
+    /** Drain state transitions from BUSY -> IDLE */
+    private final TaskSingleDrainer mAbortDrainer;
+    /** Drain the UNCONFIGURED state transition */
+    private final TaskSingleDrainer mUnconfigureDrainer;
+
+    /** This session is closed; all further calls will throw ISE */
+    private boolean mClosed = false;
+    /** Do not unconfigure if this is set; another session will overwrite configuration */
+    private boolean mSkipUnconfigure = false;
+
+    /** Is the session in the process of aborting? Pay attention to BUSY->IDLE transitions. */
+    private boolean mAborting;
+
+    /**
+     * Create a new CameraCaptureSession.
+     *
+     * <p>The camera device must already be in the {@code IDLE} state when this is invoked.
+     * There must be no pending actions
+     * (e.g. no pending captures, no repeating requests, no flush).</p>
+     */
+    CameraCaptureSessionImpl(List<Surface> outputs,
+            CameraCaptureSession.StateListener listener, Handler stateHandler,
+            android.hardware.camera2.impl.CameraDevice deviceImpl,
+            Handler deviceStateHandler, boolean configureSuccess) {
+        if (outputs == null || outputs.isEmpty()) {
+            throw new IllegalArgumentException("outputs must be a non-null, non-empty list");
+        } else if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        // TODO: extra verification of outputs
+        mOutputs = outputs;
+        mStateHandler = checkHandler(stateHandler);
+        mStateListener = createUserStateListenerProxy(mStateHandler, listener);
+
+        mDeviceHandler = checkNotNull(deviceStateHandler, "deviceStateHandler must not be null");
+        mDeviceImpl = checkNotNull(deviceImpl, "deviceImpl must not be null");
+
+        /*
+         * Use the same handler as the device's StateListener for all the internal coming events
+         *
+         * This ensures total ordering between CameraDevice.StateListener and
+         * CameraDevice.CaptureListener events.
+         */
+        mSequenceDrainer = new TaskDrainer<>(mDeviceHandler, new SequenceDrainListener(),
+                /*name*/"seq");
+        mIdleDrainer = new TaskSingleDrainer(mDeviceHandler, new IdleDrainListener(),
+                /*name*/"idle");
+        mAbortDrainer = new TaskSingleDrainer(mDeviceHandler, new AbortDrainListener(),
+                /*name*/"abort");
+        mUnconfigureDrainer = new TaskSingleDrainer(mDeviceHandler, new UnconfigureDrainListener(),
+                /*name*/"unconf");
+
+        // CameraDevice should call configureOutputs and have it finish before constructing us
+
+        if (configureSuccess) {
+            mStateListener.onConfigured(this);
+        } else {
+            mStateListener.onConfigureFailed(this);
+            mClosed = true; // do not fire any other callbacks, do not allow any other work
+        }
+    }
+
+    @Override
+    public CameraDevice getDevice() {
+        return mDeviceImpl;
+    }
+
+    @Override
+    public synchronized int capture(CaptureRequest request, CaptureListener listener,
+            Handler handler) throws CameraAccessException {
+        checkNotClosed();
+        checkLegalToCapture();
+
+        handler = checkHandler(handler);
+
+        if (VERBOSE) {
+            Log.v(TAG, "capture - request " + request + ", listener " + listener + " handler" +
+                    "" + handler);
+        }
+
+        return addPendingSequence(mDeviceImpl.capture(request,
+                createCaptureListenerProxy(handler, listener), mDeviceHandler));
+    }
+
+    @Override
+    public synchronized int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
+            Handler handler) throws CameraAccessException {
+        checkNotClosed();
+        checkLegalToCapture();
+
+        handler = checkHandler(handler);
+
+        if (VERBOSE) {
+            CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
+            Log.v(TAG, "captureBurst - requests " + Arrays.toString(requestArray) + ", listener " +
+                    listener + " handler" + "" + handler);
+        }
+
+        return addPendingSequence(mDeviceImpl.captureBurst(requests,
+                createCaptureListenerProxy(handler, listener), mDeviceHandler));
+    }
+
+    @Override
+    public synchronized int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
+            Handler handler) throws CameraAccessException {
+        checkNotClosed();
+        checkLegalToCapture();
+
+        handler = checkHandler(handler);
+
+        return addPendingSequence(mDeviceImpl.setRepeatingRequest(request,
+                createCaptureListenerProxy(handler, listener), mDeviceHandler));
+    }
+
+    @Override
+    public synchronized int setRepeatingBurst(List<CaptureRequest> requests,
+            CaptureListener listener, Handler handler) throws CameraAccessException {
+        checkNotClosed();
+        checkLegalToCapture();
+
+        handler = checkHandler(handler);
+
+        if (VERBOSE) {
+            CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
+            Log.v(TAG, "setRepeatingBurst - requests " + Arrays.toString(requestArray) +
+                    ", listener " + listener + " handler" + "" + handler);
+        }
+
+        return addPendingSequence(mDeviceImpl.setRepeatingBurst(requests,
+                createCaptureListenerProxy(handler, listener), mDeviceHandler));
+    }
+
+    @Override
+    public synchronized void stopRepeating() throws CameraAccessException {
+        checkNotClosed();
+
+        if (VERBOSE) {
+            Log.v(TAG, "stopRepeating");
+        }
+
+        mDeviceImpl.stopRepeating();
+    }
+
+    @Override
+    public synchronized void abortCaptures() throws CameraAccessException {
+        checkNotClosed();
+
+        if (VERBOSE) {
+            Log.v(TAG, "abortCaptures");
+        }
+
+        if (mAborting) {
+            Log.w(TAG, "abortCaptures - Session is already aborting; doing nothing");
+            return;
+        }
+
+        mAborting = true;
+        mAbortDrainer.taskStarted();
+
+        mDeviceImpl.flush();
+        // The next BUSY -> IDLE set of transitions will mark the end of the abort.
+    }
+
+    /**
+     * Replace this session with another session.
+     *
+     * <p>This is an optimization to avoid unconfiguring and then immediately having to
+     * reconfigure again.</p>
+     *
+     * <p>The semantics are identical to {@link #close}, except that unconfiguring will be skipped.
+     * <p>
+     *
+     * @see CameraCaptureSession#close
+     */
+    synchronized void replaceSessionClose(CameraCaptureSession other) {
+        /*
+         * In order for creating new sessions to be fast, the new session should be created
+         * before the old session is closed.
+         *
+         * Otherwise the old session will always unconfigure if there is no new session to
+         * replace it.
+         *
+         * Unconfiguring could add hundreds of milliseconds of delay. We could race and attempt
+         * to skip unconfigure if a new session is created before the captures are all drained,
+         * but this would introduce nondeterministic behavior.
+         */
+
+        // #close was already called explicitly, keep going the slow route
+        if (mClosed) {
+            return;
+        }
+
+        mSkipUnconfigure = true;
+        close();
+    }
+
+    @Override
+    public synchronized void close() {
+        if (mClosed) {
+            return;
+        }
+
+        mClosed = true;
+
+        /*
+         * Flush out any repeating request. Since camera is closed, no new requests
+         * can be queued, and eventually the entire request queue will be drained.
+         *
+         * Once this is done, wait for camera to idle, then unconfigure the camera.
+         * Once that's done, fire #onClosed.
+         */
+        try {
+            mDeviceImpl.stopRepeating();
+        } catch (CameraAccessException e) {
+            // OK: close does not throw checked exceptions.
+            Log.e(TAG, "Exception while stopping repeating: ", e);
+
+            // TODO: call onError instead of onClosed if this happens
+        }
+
+        // If no sequences are pending, fire #onClosed immediately
+        mSequenceDrainer.beginDrain();
+    }
+
+    /**
+     * Post calls into a CameraCaptureSession.StateListener to the user-specified {@code handler}.
+     */
+    private StateListener createUserStateListenerProxy(Handler handler, StateListener listener) {
+        InvokeDispatcher<StateListener> userListenerSink = new InvokeDispatcher<>(listener);
+        HandlerDispatcher<StateListener> handlerPassthrough =
+                new HandlerDispatcher<>(userListenerSink, handler);
+
+        return new ListenerProxies.SessionStateListenerProxy(handlerPassthrough);
+    }
+
+    /**
+     * Forward callbacks from
+     * CameraDevice.CaptureListener to the CameraCaptureSession.CaptureListener.
+     *
+     * <p>In particular, all calls are automatically split to go both to our own
+     * internal listener, and to the user-specified listener (by transparently posting
+     * to the user-specified handler).</p>
+     *
+     * <p>When a capture sequence finishes, update the pending checked sequences set.</p>
+     */
+    @SuppressWarnings("deprecation")
+    private CameraDevice.CaptureListener createCaptureListenerProxy(
+            Handler handler, CaptureListener listener) {
+        CameraDevice.CaptureListener localListener = new CameraDevice.CaptureListener() {
+            @Override
+            public void onCaptureSequenceCompleted(CameraDevice camera,
+                    int sequenceId, long frameNumber) {
+                finishPendingSequence(sequenceId);
+            }
+
+            @Override
+            public void onCaptureSequenceAborted(CameraDevice camera,
+                    int sequenceId) {
+                finishPendingSequence(sequenceId);
+            }
+        };
+
+        /*
+         * Split the calls from the device listener into local listener and the following chain:
+         * - duck type from device listener to session listener
+         * - then forward the call to a handler
+         * - then finally invoke the destination method on the session listener object
+         */
+        Dispatchable<CaptureListener> userListenerSink;
+        if (listener == null) { // OK: API allows the user to not specify a listener
+            userListenerSink = new NullDispatcher<>();
+        } else {
+            userListenerSink = new InvokeDispatcher<>(listener);
+        }
+
+        InvokeDispatcher<CameraDevice.CaptureListener> localSink =
+                new InvokeDispatcher<>(localListener);
+        HandlerDispatcher<CaptureListener> handlerPassthrough =
+                new HandlerDispatcher<>(userListenerSink, handler);
+        DuckTypingDispatcher<CameraDevice.CaptureListener, CaptureListener> duckToSessionCaptureListener
+                = new DuckTypingDispatcher<>(handlerPassthrough, CaptureListener.class);
+
+        BroadcastDispatcher<CameraDevice.CaptureListener> broadcaster =
+                new BroadcastDispatcher<CameraDevice.CaptureListener>(
+                        duckToSessionCaptureListener,
+                        localSink);
+
+        return new ListenerProxies.DeviceCaptureListenerProxy(broadcaster);
+    }
+
+    /**
+     *
+     * Create an internal state listener, to be invoked on the mDeviceHandler
+     *
+     * <p>It has a few behaviors:
+     * <ul>
+     * <li>Convert device state changes into session state changes.
+     * <li>Keep track of async tasks that the session began (idle, abort).
+     * </ul>
+     * </p>
+     * */
+    CameraDevice.StateListener getDeviceStateListener() {
+        final CameraCaptureSession session = this;
+
+        return new CameraDevice.StateListener() {
+            private boolean mBusy = false;
+            private boolean mActive = false;
+
+            @Override
+            public void onOpened(CameraDevice camera) {
+                throw new AssertionError("Camera must already be open before creating a session");
+            }
+
+            @Override
+            public void onDisconnected(CameraDevice camera) {
+                close();
+            }
+
+            @Override
+            public void onError(CameraDevice camera, int error) {
+                // TODO: Handle errors somehow.
+                Log.wtf(TAG, "Got device error " + error);
+            }
+
+            @Override
+            public void onActive(CameraDevice camera) {
+                mIdleDrainer.taskStarted();
+                mActive = true;
+
+                mStateListener.onActive(session);
+            }
+
+            @Override
+            public void onIdle(CameraDevice camera) {
+                boolean isAborting;
+                synchronized (session) {
+                    isAborting = mAborting;
+                }
+
+                /*
+                 * Check which states we transitioned through:
+                 *
+                 * (ACTIVE -> IDLE)
+                 * (BUSY -> IDLE)
+                 *
+                 * Note that this is also legal:
+                 * (ACTIVE -> BUSY -> IDLE)
+                 *
+                 * and mark those tasks as finished
+                 */
+                if (mBusy && isAborting) {
+                    mAbortDrainer.taskFinished();
+
+                    synchronized (session) {
+                        mAborting = false;
+                    }
+                }
+
+                if (mActive) {
+                    mIdleDrainer.taskFinished();
+                }
+
+                mBusy = false;
+                mActive = false;
+
+                mStateListener.onReady(session);
+            }
+
+            @Override
+            public void onBusy(CameraDevice camera) {
+                mBusy = true;
+
+                // TODO: Queue captures during abort instead of failing them
+                // since the app won't be able to distinguish the two actives
+                Log.w(TAG, "Device is now busy; do not submit new captures (TODO: allow this)");
+                mStateListener.onActive(session);
+            }
+
+            @Override
+            public void onUnconfigured(CameraDevice camera) {
+                mUnconfigureDrainer.taskFinished();
+            }
+        };
+
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private void checkLegalToCapture() {
+        if (mAborting) {
+            throw new IllegalStateException(
+                    "Session is aborting captures; new captures are not permitted");
+        }
+    }
+
+    private void checkNotClosed() {
+        if (mClosed) {
+            throw new IllegalStateException(
+                    "Session has been closed; further changes are illegal.");
+        }
+    }
+
+    /**
+     * Notify the session that a pending capture sequence has just been queued.
+     *
+     * <p>During a shutdown/close, the session waits until all pending sessions are finished
+     * before taking any further steps to shut down itself.</p>
+     *
+     * @see #finishPendingSequence
+     */
+    private int addPendingSequence(int sequenceId) {
+        mSequenceDrainer.taskStarted(sequenceId);
+        return sequenceId;
+    }
+
+    /**
+     * Notify the session that a pending capture sequence is now finished.
+     *
+     * <p>During a shutdown/close, once all pending sequences finish, it is safe to
+     * close the camera further by unconfiguring and then firing {@code onClosed}.</p>
+     */
+    private void finishPendingSequence(int sequenceId) {
+        mSequenceDrainer.taskFinished(sequenceId);
+    }
+
+    private class SequenceDrainListener implements TaskDrainer.DrainListener {
+        @Override
+        public void onDrained() {
+            /*
+             * No repeating request is set; and the capture queue has fully drained.
+             *
+             * If no captures were queued to begin with, and an abort was queued,
+             * it's still possible to get another BUSY before the last IDLE.
+             *
+             * If the camera is already "IDLE" and no aborts are pending,
+             * then the drain immediately finishes.
+             */
+            mAbortDrainer.beginDrain();
+        }
+    }
+
+    private class AbortDrainListener implements TaskDrainer.DrainListener {
+        @Override
+        public void onDrained() {
+            synchronized (CameraCaptureSessionImpl.this) {
+                /*
+                 * Any queued aborts have now completed.
+                 *
+                 * It's now safe to wait to receive the final "IDLE" event, as the camera device
+                 * will no longer again transition to "ACTIVE" by itself.
+                 *
+                 * If the camera is already "IDLE", then the drain immediately finishes.
+                 */
+                mIdleDrainer.beginDrain();
+            }
+        }
+    }
+
+    private class IdleDrainListener implements TaskDrainer.DrainListener {
+        @Override
+        public void onDrained() {
+            synchronized (CameraCaptureSessionImpl.this) {
+                /*
+                 * The device is now IDLE, and has settled. It will not transition to
+                 * ACTIVE or BUSY again by itself.
+                 *
+                 * It's now safe to unconfigure the outputs and after it's done invoke #onClosed.
+                 *
+                 * This operation is idempotent; a session will not be closed twice.
+                 */
+
+                // Fast path: A new capture session has replaced this one; don't unconfigure.
+                if (mSkipUnconfigure) {
+                    mStateListener.onClosed(CameraCaptureSessionImpl.this);
+                    return;
+                }
+
+                // Slow path: #close was called explicitly on this session; unconfigure first
+
+                try {
+                    mUnconfigureDrainer.taskStarted();
+                    mDeviceImpl.configureOutputs(null); // begin transition to unconfigured state
+                } catch (CameraAccessException e) {
+                    // OK: do not throw checked exceptions.
+                    Log.e(TAG, "Exception while configuring outputs: ", e);
+
+                    // TODO: call onError instead of onClosed if this happens
+                }
+
+                mUnconfigureDrainer.beginDrain();
+            }
+        }
+    }
+
+    private class UnconfigureDrainListener implements TaskDrainer.DrainListener {
+        @Override
+        public void onDrained() {
+            synchronized (CameraCaptureSessionImpl.this) {
+                // The device has finished unconfiguring. It's now fully closed.
+                mStateListener.onClosed(CameraCaptureSessionImpl.this);
+            }
+        }
+    }
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index 9a4c531..e9d4b0f 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -62,6 +62,7 @@
     private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
 
     private final StateListener mDeviceListener;
+    private volatile StateListener mSessionStateListener;
     private final Handler mDeviceHandler;
 
     private boolean mIdle = true;
@@ -90,6 +91,8 @@
      */
     private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
 
+    private CameraCaptureSessionImpl mCurrentSession;
+
     // Runnables for all state transitions, except error, which needs the
     // error code argument
 
@@ -98,6 +101,10 @@
         public void run() {
             if (!CameraDevice.this.isClosed()) {
                 mDeviceListener.onOpened(CameraDevice.this);
+                StateListener sessionListener = mSessionStateListener;
+                if (sessionListener != null) {
+                    sessionListener.onOpened(CameraDevice.this);
+                }
             }
         }
     };
@@ -107,6 +114,10 @@
         public void run() {
             if (!CameraDevice.this.isClosed()) {
                 mDeviceListener.onUnconfigured(CameraDevice.this);
+                StateListener sessionListener = mSessionStateListener;
+                if (sessionListener != null) {
+                    sessionListener.onUnconfigured(CameraDevice.this);
+                }
             }
         }
     };
@@ -116,6 +127,10 @@
         public void run() {
             if (!CameraDevice.this.isClosed()) {
                 mDeviceListener.onActive(CameraDevice.this);
+                StateListener sessionListener = mSessionStateListener;
+                if (sessionListener != null) {
+                    sessionListener.onActive(CameraDevice.this);
+                }
             }
         }
     };
@@ -125,6 +140,10 @@
         public void run() {
             if (!CameraDevice.this.isClosed()) {
                 mDeviceListener.onBusy(CameraDevice.this);
+                StateListener sessionListener = mSessionStateListener;
+                if (sessionListener != null) {
+                    sessionListener.onBusy(CameraDevice.this);
+                }
             }
         }
     };
@@ -133,6 +152,10 @@
         @Override
         public void run() {
             mDeviceListener.onClosed(CameraDevice.this);
+            StateListener sessionListener = mSessionStateListener;
+            if (sessionListener != null) {
+                sessionListener.onClosed(CameraDevice.this);
+            }
         }
     };
 
@@ -141,6 +164,10 @@
         public void run() {
             if (!CameraDevice.this.isClosed()) {
                 mDeviceListener.onIdle(CameraDevice.this);
+                StateListener sessionListener = mSessionStateListener;
+                if (sessionListener != null) {
+                    sessionListener.onIdle(CameraDevice.this);
+                }
             }
         }
     };
@@ -150,6 +177,10 @@
         public void run() {
             if (!CameraDevice.this.isClosed()) {
                 mDeviceListener.onDisconnected(CameraDevice.this);
+                StateListener sessionListener = mSessionStateListener;
+                if (sessionListener != null) {
+                    sessionListener.onDisconnected(CameraDevice.this);
+                }
             }
         }
     };
@@ -170,7 +201,6 @@
             tag = tag.substring(0, MAX_TAG_LEN);
         }
         TAG = tag;
-
         DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     }
 
@@ -263,7 +293,43 @@
     public void createCaptureSession(List<Surface> outputs,
             CameraCaptureSession.StateListener listener, Handler handler)
             throws CameraAccessException {
-        // TODO
+        synchronized (mLock) {
+            if (DEBUG) {
+                Log.d(TAG, "createCaptureSession");
+            }
+
+            checkIfCameraClosed();
+
+            // TODO: we must be in UNCONFIGURED mode to begin with, or using another session
+
+            // TODO: dont block for this
+            boolean configureSuccess = true;
+            CameraAccessException pendingException = null;
+            try {
+                configureOutputs(outputs); // and then block until IDLE
+            } catch (CameraAccessException e) {
+                configureSuccess = false;
+                pendingException = e;
+            }
+
+            // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
+            CameraCaptureSessionImpl newSession =
+                    new CameraCaptureSessionImpl(outputs, listener, handler, this, mDeviceHandler,
+                            configureSuccess);
+
+            if (mCurrentSession != null) {
+                mCurrentSession.replaceSessionClose(newSession);
+            }
+
+            // TODO: wait until current session closes, then create the new session
+            mCurrentSession = newSession;
+
+            if (pendingException != null) {
+                throw pendingException;
+            }
+
+            mSessionStateListener = mCurrentSession.getDeviceStateListener();
+        }
     }
 
     @Override
@@ -275,7 +341,7 @@
             CameraMetadataNative templatedRequest = new CameraMetadataNative();
 
             try {
-                mRemoteDevice.createDefaultRequest(templateType, /* out */templatedRequest);
+                mRemoteDevice.createDefaultRequest(templateType, /*out*/templatedRequest);
             } catch (CameraRuntimeException e) {
                 throw e.asChecked();
             } catch (RemoteException e) {
@@ -304,10 +370,8 @@
     @Override
     public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
             Handler handler) throws CameraAccessException {
-        // TODO: remove this. Throw IAE if the request is null or empty. Need to update API doc.
-        if (requests.isEmpty()) {
-            Log.w(TAG, "Capture burst request list is empty, do nothing!");
-            return -1;
+        if (requests == null || requests.isEmpty()) {
+            throw new IllegalArgumentException("At least one request must be given");
         }
         return submitCaptureRequest(requests, listener, handler, /*streaming*/false);
     }
@@ -454,10 +518,8 @@
     @Override
     public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
             Handler handler) throws CameraAccessException {
-        // TODO: remove this. Throw IAE if the request is null or empty. Need to update API doc.
-        if (requests.isEmpty()) {
-            Log.w(TAG, "Set Repeating burst request list is empty, do nothing!");
-            return -1;
+        if (requests == null || requests.isEmpty()) {
+            throw new IllegalArgumentException("At least one request must be given");
         }
         return submitCaptureRequest(requests, listener, handler, /*streaming*/true);
     }
@@ -951,10 +1013,14 @@
     }
 
     /**
-     * Default handler management. If handler is null, get the current thread's
-     * Looper to create a Handler with. If no looper exists, throw exception.
+     * Default handler management.
+     *
+     * <p>
+     * If handler is null, get the current thread's
+     * Looper to create a Handler with. If no looper exists, throw {@code IllegalArgumentException}.
+     * </p>
      */
-    private Handler checkHandler(Handler handler) {
+    static Handler checkHandler(Handler handler) {
         if (handler == null) {
             Looper looper = Looper.myLooper();
             if (looper == null) {
diff --git a/core/java/android/hardware/camera2/impl/ListenerProxies.java b/core/java/android/hardware/camera2/impl/ListenerProxies.java
new file mode 100644
index 0000000..04c43e3
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/ListenerProxies.java
@@ -0,0 +1,168 @@
+package android.hardware.camera2.impl;
+
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.dispatch.Dispatchable;
+import android.hardware.camera2.dispatch.MethodNameInvoker;
+
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * Proxy out invocations to the camera2 API listeners into a {@link Dispatchable}.
+ *
+ * <p>Since abstract classes do not support Java's dynamic {@code Proxy}, we have to
+ * to use our own proxy mechanism.</p>
+ */
+public class ListenerProxies {
+
+    // TODO: replace with codegen
+
+    public static class DeviceStateListenerProxy extends CameraDevice.StateListener {
+        private final MethodNameInvoker<CameraDevice.StateListener> mProxy;
+
+        public DeviceStateListenerProxy(
+                Dispatchable<CameraDevice.StateListener> dispatchTarget) {
+            dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null");
+            mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDevice.StateListener.class);
+        }
+
+        @Override
+        public void onOpened(CameraDevice camera) {
+            mProxy.invoke("onOpened", camera);
+        }
+
+        @Override
+        public void onDisconnected(CameraDevice camera) {
+            mProxy.invoke("onDisconnected", camera);
+        }
+
+        @Override
+        public void onError(CameraDevice camera, int error) {
+            mProxy.invoke("onError", camera, error);
+        }
+
+        @Override
+        public void onUnconfigured(CameraDevice camera) {
+            mProxy.invoke("onUnconfigured", camera);
+        }
+
+        @Override
+        public void onActive(CameraDevice camera) {
+            mProxy.invoke("onActive", camera);
+        }
+
+        @Override
+        public void onBusy(CameraDevice camera) {
+            mProxy.invoke("onBusy", camera);
+        }
+
+        @Override
+        public void onClosed(CameraDevice camera) {
+            mProxy.invoke("onClosed", camera);
+        }
+
+        @Override
+        public void onIdle(CameraDevice camera) {
+            mProxy.invoke("onIdle", camera);
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    public static class DeviceCaptureListenerProxy extends CameraDevice.CaptureListener {
+        private final MethodNameInvoker<CameraDevice.CaptureListener> mProxy;
+
+        public DeviceCaptureListenerProxy(
+                Dispatchable<CameraDevice.CaptureListener> dispatchTarget) {
+            dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null");
+            mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDevice.CaptureListener.class);
+        }
+
+        @Override
+        public void onCaptureStarted(CameraDevice camera,
+                CaptureRequest request, long timestamp) {
+            mProxy.invoke("onCaptureStarted", camera, request, timestamp);
+        }
+
+        @Override
+        public void onCapturePartial(CameraDevice camera,
+                CaptureRequest request, CaptureResult result) {
+            mProxy.invoke("onCapturePartial", camera, request, result);
+        }
+
+        @Override
+        public void onCaptureProgressed(CameraDevice camera,
+                CaptureRequest request, CaptureResult partialResult) {
+            mProxy.invoke("onCaptureProgressed", camera, request, partialResult);
+        }
+
+        @Override
+        public void onCaptureCompleted(CameraDevice camera,
+                CaptureRequest request, TotalCaptureResult result) {
+            mProxy.invoke("onCaptureCompleted", camera, request, result);
+        }
+
+        @Override
+        public void onCaptureFailed(CameraDevice camera,
+                CaptureRequest request, CaptureFailure failure) {
+            mProxy.invoke("onCaptureFailed", camera, request, failure);
+        }
+
+        @Override
+        public void onCaptureSequenceCompleted(CameraDevice camera,
+                int sequenceId, long frameNumber) {
+            mProxy.invoke("onCaptureSequenceCompleted", camera, sequenceId, frameNumber);
+        }
+
+        @Override
+        public void onCaptureSequenceAborted(CameraDevice camera,
+                int sequenceId) {
+            mProxy.invoke("onCaptureSequenceAborted", camera, sequenceId);
+        }
+    }
+
+    public static class SessionStateListenerProxy
+            extends CameraCaptureSession.StateListener {
+        private final MethodNameInvoker<CameraCaptureSession.StateListener> mProxy;
+
+        public SessionStateListenerProxy(
+                Dispatchable<CameraCaptureSession.StateListener> dispatchTarget) {
+            dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null");
+            mProxy = new MethodNameInvoker<>(dispatchTarget,
+                    CameraCaptureSession.StateListener.class);
+        }
+
+        @Override
+        public void onConfigured(CameraCaptureSession session) {
+            mProxy.invoke("onConfigured", session);
+        }
+
+
+        @Override
+        public void onConfigureFailed(CameraCaptureSession session) {
+            mProxy.invoke("onConfigureFailed", session);
+        }
+
+        @Override
+        public void onReady(CameraCaptureSession session) {
+            mProxy.invoke("onReady", session);
+        }
+
+        @Override
+        public void onActive(CameraCaptureSession session) {
+            mProxy.invoke("onActive", session);
+        }
+
+        @Override
+        public void onClosed(CameraCaptureSession session) {
+            mProxy.invoke("onClosed", session);
+        }
+    }
+
+    private ListenerProxies() {
+        throw new AssertionError();
+    }
+}
diff --git a/core/java/android/hardware/camera2/utils/TaskDrainer.java b/core/java/android/hardware/camera2/utils/TaskDrainer.java
new file mode 100644
index 0000000..3cba9a1
--- /dev/null
+++ b/core/java/android/hardware/camera2/utils/TaskDrainer.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * Keep track of multiple concurrent tasks starting and finishing by their key;
+ * allow draining existing tasks and figuring out when all tasks have finished
+ * (and new ones won't begin).
+ *
+ * <p>The initial state is to allow all tasks to be started and finished. A task may only be started
+ * once, after which it must be finished before starting again. Likewise, finishing a task
+ * that hasn't been started is also not allowed.</p>
+ *
+ * <p>When draining begins, no more new tasks can be started. This guarantees that at some
+ * point when all the tasks are finished there will be no more collective new tasks,
+ * at which point the {@link DrainListener#onDrained} callback will be invoked.</p>
+ *
+ *
+ * @param <T>
+ *          a type for the key that will represent tracked tasks;
+ *          must implement {@code Object#equals}
+ */
+public class TaskDrainer<T> {
+    /**
+     * Fired asynchronously after draining has begun with {@link TaskDrainer#beginDrain}
+     * <em>and</em> all tasks that were started have finished.
+     */
+    public interface DrainListener {
+        /** All tasks have fully finished draining; there will be no more pending tasks. */
+        public void onDrained();
+    }
+
+    private static final String TAG = "TaskDrainer";
+    private static final boolean VERBOSE = false;
+
+    private final Handler mHandler;
+    private final DrainListener mListener;
+    private final String mName;
+
+    /** Set of tasks which have been started but not yet finished with #taskFinished */
+    private final Set<T> mTaskSet = new HashSet<T>();
+    private final Object mLock = new Object();
+
+    private boolean mDraining = false;
+    private boolean mDrainFinished = false;
+
+    /**
+     * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
+     * via the {@code handler}.
+     *
+     * @param handler a non-{@code null} handler to use to post runnables to
+     * @param listener a non-{@code null} listener where {@code onDrained} will be called
+     */
+    public TaskDrainer(Handler handler, DrainListener listener) {
+        mHandler = checkNotNull(handler, "handler must not be null");
+        mListener = checkNotNull(listener, "listener must not be null");
+        mName = null;
+    }
+
+    /**
+     * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
+     * via the {@code handler}.
+     *
+     * @param handler a non-{@code null} handler to use to post runnables to
+     * @param listener a non-{@code null} listener where {@code onDrained} will be called
+     * @param name an optional name used for debug logging
+     */
+    public TaskDrainer(Handler handler, DrainListener listener, String name) {
+        // XX: Probably don't need a handler at all here
+        mHandler = checkNotNull(handler, "handler must not be null");
+        mListener = checkNotNull(listener, "listener must not be null");
+        mName = name;
+    }
+
+    /**
+     * Mark an asynchronous task as having started.
+     *
+     * <p>A task cannot be started more than once without first having finished. Once
+     * draining begins with {@link #beginDrain}, no new tasks can be started.</p>
+     *
+     * @param task a key to identify a task
+     *
+     * @see #taskFinished
+     * @see #beginDrain
+     *
+     * @throws IllegalStateException
+     *          If attempting to start a task which is already started (and not finished),
+     *          or if attempting to start a task after draining has begun.
+     */
+    public void taskStarted(T task) {
+        synchronized (mLock) {
+            if (VERBOSE) {
+                Log.v(TAG + "[" + mName + "]", "taskStarted " + task);
+            }
+
+            if (mDraining) {
+                throw new IllegalStateException("Can't start more tasks after draining has begun");
+            }
+
+            if (!mTaskSet.add(task)) {
+                throw new IllegalStateException("Task " + task + " was already started");
+            }
+        }
+    }
+
+
+    /**
+     * Mark an asynchronous task as having finished.
+     *
+     * <p>A task cannot be finished if it hasn't started. Once finished, a task
+     * cannot be finished again (unless it's started again).</p>
+     *
+     * @param task a key to identify a task
+     *
+     * @see #taskStarted
+     * @see #beginDrain
+     *
+     * @throws IllegalStateException
+     *          If attempting to start a task which is already finished (and not re-started),
+     */
+    public void taskFinished(T task) {
+        synchronized (mLock) {
+            if (VERBOSE) {
+                Log.v(TAG + "[" + mName + "]", "taskFinished " + task);
+            }
+
+            if (!mTaskSet.remove(task)) {
+                throw new IllegalStateException("Task " + task + " was already finished");
+            }
+
+            // If this is the last finished task and draining has already begun, fire #onDrained
+            checkIfDrainFinished();
+        }
+    }
+
+    /**
+     * Do not allow any more tasks to be started; once all existing started tasks are finished,
+     * fire the {@link DrainListener#onDrained} callback asynchronously.
+     *
+     * <p>This operation is idempotent; calling it more than once has no effect.</p>
+     */
+    public void beginDrain() {
+        synchronized (mLock) {
+            if (!mDraining) {
+                if (VERBOSE) {
+                    Log.v(TAG + "[" + mName + "]", "beginDrain started");
+                }
+
+                mDraining = true;
+
+                // If all tasks that had started had already finished by now, fire #onDrained
+                checkIfDrainFinished();
+            } else {
+                if (VERBOSE) {
+                    Log.v(TAG + "[" + mName + "]", "beginDrain ignored");
+                }
+            }
+        }
+    }
+
+    private void checkIfDrainFinished() {
+        if (mTaskSet.isEmpty() && mDraining && !mDrainFinished) {
+            mDrainFinished = true;
+            postDrained();
+        }
+    }
+
+    private void postDrained() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (VERBOSE) {
+                    Log.v(TAG + "[" + mName + "]", "onDrained");
+                }
+
+                mListener.onDrained();
+            }
+        });
+    }
+}
diff --git a/core/java/android/hardware/camera2/utils/TaskSingleDrainer.java b/core/java/android/hardware/camera2/utils/TaskSingleDrainer.java
new file mode 100644
index 0000000..f6272c9
--- /dev/null
+++ b/core/java/android/hardware/camera2/utils/TaskSingleDrainer.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.hardware.camera2.utils.TaskDrainer.DrainListener;
+import android.os.Handler;
+
+/**
+ * Keep track of a single concurrent task starting and finishing;
+ * allow draining the existing task and figuring out when the task has finished
+ * (and won't restart).
+ *
+ * <p>The initial state is to allow all tasks to be started and finished. A task may only be started
+ * once, after which it must be finished before starting again. Likewise, finishing a task
+ * that hasn't been started is also not allowed.</p>
+ *
+ * <p>When draining begins, the task cannot be started again. This guarantees that at some
+ * point the task will be finished forever, at which point the {@link DrainListener#onDrained}
+ * callback will be invoked.</p>
+ */
+public class TaskSingleDrainer {
+
+    private final TaskDrainer<Object> mTaskDrainer;
+    private final Object mSingleTask = new Object();
+
+    /**
+     * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
+     * via the {@code handler}.
+     *
+     * @param handler a non-{@code null} handler to use to post runnables to
+     * @param listener a non-{@code null} listener where {@code onDrained} will be called
+     */
+    public TaskSingleDrainer(Handler handler, DrainListener listener) {
+        mTaskDrainer = new TaskDrainer<>(handler, listener);
+    }
+
+    /**
+     * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
+     * via the {@code handler}.
+     *
+     * @param handler a non-{@code null} handler to use to post runnables to
+     * @param listener a non-{@code null} listener where {@code onDrained} will be called
+     * @param name an optional name used for debug logging
+     */
+    public TaskSingleDrainer(Handler handler, DrainListener listener, String name) {
+        mTaskDrainer = new TaskDrainer<>(handler, listener, name);
+    }
+
+    /**
+     * Mark this asynchronous task as having started.
+     *
+     * <p>The task cannot be started more than once without first having finished. Once
+     * draining begins with {@link #beginDrain}, no new tasks can be started.</p>
+     *
+     * @see #taskFinished
+     * @see #beginDrain
+     *
+     * @throws IllegalStateException
+     *          If attempting to start a task which is already started (and not finished),
+     *          or if attempting to start a task after draining has begun.
+     */
+    public void taskStarted() {
+        mTaskDrainer.taskStarted(mSingleTask);
+    }
+
+    /**
+     * Do not allow any more task re-starts; once the existing task is finished,
+     * fire the {@link DrainListener#onDrained} callback asynchronously.
+     *
+     * <p>This operation is idempotent; calling it more than once has no effect.</p>
+     */
+    public void beginDrain() {
+        mTaskDrainer.beginDrain();
+    }
+
+    /**
+     * Mark this asynchronous task as having finished.
+     *
+     * <p>The task cannot be finished if it hasn't started. Once finished, a task
+     * cannot be finished again (unless it's started again).</p>
+     *
+     * @see #taskStarted
+     * @see #beginDrain
+     *
+     * @throws IllegalStateException
+     *          If attempting to start a task which is already finished (and not re-started),
+     */
+    public void taskFinished() {
+        mTaskDrainer.taskFinished(mSingleTask);
+    }
+}
diff --git a/core/java/android/hardware/camera2/utils/UncheckedThrow.java b/core/java/android/hardware/camera2/utils/UncheckedThrow.java
index 8224fed..ffcb78b 100644
--- a/core/java/android/hardware/camera2/utils/UncheckedThrow.java
+++ b/core/java/android/hardware/camera2/utils/UncheckedThrow.java
@@ -33,8 +33,20 @@
         UncheckedThrow.<RuntimeException>throwAnyImpl(e);
     }
 
+    /**
+     * Throw any kind of throwable without needing it to be checked
+     * @param e any instance of a Throwable
+     */
+    public static void throwAnyException(Throwable e) {
+        /**
+         *  Abuse type erasure by making the compiler think we are throwing RuntimeException,
+         *  which is unchecked, but then inserting any exception in there.
+         */
+        UncheckedThrow.<RuntimeException>throwAnyImpl(e);
+    }
+
     @SuppressWarnings("unchecked")
-    private static<T extends Exception> void throwAnyImpl(Exception e) throws T {
+    private static<T extends Throwable> void throwAnyImpl(Throwable e) throws T {
         throw (T) e;
     }
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index af16185..a52ccdf 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -216,12 +216,6 @@
     oneway void statusBarVisibilityChanged(int visibility);
 
     /**
-     * Block until the given window has been drawn to the screen.
-     * Returns true if really waiting, false if the window does not exist.
-     */
-    boolean waitForWindowDrawn(IBinder token, in IRemoteCallback callback);
-
-    /**
      * Device has a software navigation bar (separate from the status bar).
      */
     boolean hasNavigationBar();
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 14dc356..a92bf59 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -20,6 +20,7 @@
 import android.graphics.Region;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 
 import java.util.List;
 
@@ -105,7 +106,7 @@
      * Set by the accessibility layer to specify the magnification and panning to
      * be applied to all windows that should be magnified.
      *
-     * @param callbacks The callbacks to invoke.
+     * @param spec The MagnficationSpec to set.
      *
      * @see #setMagnificationCallbacks(MagnificationCallbacks)
      */
@@ -161,4 +162,10 @@
      * @param outBounds The frame to populate.
      */
     public abstract void getWindowFrame(IBinder token, Rect outBounds);
+
+    /**
+     * Invalidate all visible windows. Then report back on the callback once all windows have
+     * redrawn.
+     */
+    public abstract void waitForAllWindowsDrawn(IRemoteCallback callback, long timeout);
 }
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 1c9ab61..bb74c44 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -42,6 +42,7 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
+
 import com.android.internal.R;
 
 import java.text.DateFormatSymbols;
@@ -82,13 +83,13 @@
     private static final int ALPHA_TRANSPARENT = 0;
 
     // Alpha level of color for selector.
-    private static final int ALPHA_SELECTOR = 51;
+    private static final int ALPHA_SELECTOR = 255; // was 51
 
     // Alpha level of color for selected circle.
     private static final int ALPHA_AMPM_SELECTED = ALPHA_SELECTOR;
 
     // Alpha level of color for pressed circle.
-    private static final int ALPHA_AMPM_PRESSED = 175;
+    private static final int ALPHA_AMPM_PRESSED = 255; // was 175
 
     private static final float COSINE_30_DEGREES = ((float) Math.sqrt(3)) * 0.5f;
     private static final float SINE_30_DEGREES = 0.5f;
@@ -112,8 +113,15 @@
     private final String[] mAmPmText = new String[2];
 
     private final Paint[] mPaint = new Paint[2];
+    private final int[] mColor = new int[2];
+    private final IntHolder[] mAlpha = new IntHolder[2];
+
     private final Paint mPaintCenter = new Paint();
+
     private final Paint[][] mPaintSelector = new Paint[2][3];
+    private final int[][] mColorSelector = new int[2][3];
+    private final IntHolder[][] mAlphaSelector = new IntHolder[2][3];
+
     private final Paint mPaintAmPmText = new Paint();
     private final Paint[] mPaintAmPmCircle = new Paint[2];
 
@@ -309,65 +317,80 @@
         final Resources res = getResources();
 
         mAmPmUnselectedColor = a.getColor(R.styleable.TimePicker_amPmUnselectedBackgroundColor,
-                res.getColor(
-                        R.color.timepicker_default_ampm_unselected_background_color_holo_light));
+                res.getColor(R.color.timepicker_default_ampm_unselected_background_color_quantum));
 
         mAmPmSelectedColor = a.getColor(R.styleable.TimePicker_amPmSelectedBackgroundColor,
-                res.getColor(R.color.timepicker_default_ampm_selected_background_color_holo_light));
+                res.getColor(R.color.timepicker_default_ampm_selected_background_color_quantum));
 
         mAmPmTextColor = a.getColor(R.styleable.TimePicker_amPmTextColor,
-                res.getColor(R.color.timepicker_default_text_color_holo_light));
-
-        final int numbersTextColor = a.getColor(R.styleable.TimePicker_numbersTextColor,
-                res.getColor(R.color.timepicker_default_text_color_holo_light));
+                res.getColor(R.color.timepicker_default_text_color_quantum));
 
         mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);
 
+        // Initialize all alpha values to opaque.
+        for (int i = 0; i < mAlpha.length; i++) {
+            mAlpha[i] = new IntHolder(ALPHA_OPAQUE);
+        }
+        for (int i = 0; i < mAlphaSelector.length; i++) {
+            for (int j = 0; j < mAlphaSelector[i].length; j++) {
+                mAlphaSelector[i][j] = new IntHolder(ALPHA_OPAQUE);
+            }
+        }
+
+        final int numbersTextColor = a.getColor(R.styleable.TimePicker_numbersTextColor,
+                res.getColor(R.color.timepicker_default_text_color_quantum));
+
         mPaint[HOURS] = new Paint();
-        mPaint[HOURS].setColor(numbersTextColor);
         mPaint[HOURS].setAntiAlias(true);
         mPaint[HOURS].setTextAlign(Paint.Align.CENTER);
+        mColor[HOURS] = numbersTextColor;
 
         mPaint[MINUTES] = new Paint();
-        mPaint[MINUTES].setColor(numbersTextColor);
         mPaint[MINUTES].setAntiAlias(true);
         mPaint[MINUTES].setTextAlign(Paint.Align.CENTER);
+        mColor[MINUTES] = numbersTextColor;
 
         mPaintCenter.setColor(numbersTextColor);
         mPaintCenter.setAntiAlias(true);
         mPaintCenter.setTextAlign(Paint.Align.CENTER);
 
         mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint();
-        mPaintSelector[HOURS][SELECTOR_CIRCLE].setColor(
-                a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light));
         mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true);
+        mColorSelector[HOURS][SELECTOR_CIRCLE] = a.getColor(
+                R.styleable.TimePicker_numbersSelectorColor,
+                R.color.timepicker_default_selector_color_quantum);
 
         mPaintSelector[HOURS][SELECTOR_DOT] = new Paint();
-        mPaintSelector[HOURS][SELECTOR_DOT].setColor(
-                a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light));
         mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true);
+        mColorSelector[HOURS][SELECTOR_DOT] = a.getColor(
+                R.styleable.TimePicker_numbersSelectorColor,
+                R.color.timepicker_default_selector_color_quantum);
 
         mPaintSelector[HOURS][SELECTOR_LINE] = new Paint();
-        mPaintSelector[HOURS][SELECTOR_LINE].setColor(
-                a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light));
         mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true);
         mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2);
+        mColorSelector[HOURS][SELECTOR_LINE] = a.getColor(
+                R.styleable.TimePicker_numbersSelectorColor,
+                R.color.timepicker_default_selector_color_quantum);
 
         mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint();
-        mPaintSelector[MINUTES][SELECTOR_CIRCLE].setColor(
-                a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light));
         mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true);
+        mColorSelector[MINUTES][SELECTOR_CIRCLE] = a.getColor(
+                R.styleable.TimePicker_numbersSelectorColor,
+                R.color.timepicker_default_selector_color_quantum);
 
         mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint();
-        mPaintSelector[MINUTES][SELECTOR_DOT].setColor(
-                a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light));
         mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true);
+        mColorSelector[MINUTES][SELECTOR_DOT] = a.getColor(
+                R.styleable.TimePicker_numbersSelectorColor,
+                R.color.timepicker_default_selector_color_quantum);
 
         mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint();
-        mPaintSelector[MINUTES][SELECTOR_LINE].setColor(
-                a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light));
         mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true);
         mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2);
+        mColorSelector[MINUTES][SELECTOR_LINE] = a.getColor(
+                R.styleable.TimePicker_numbersSelectorColor,
+                R.color.timepicker_default_selector_color_quantum);
 
         mPaintAmPmText.setColor(mAmPmTextColor);
         mPaintAmPmText.setTypeface(mTypeface);
@@ -379,13 +402,12 @@
         mPaintAmPmCircle[PM] = new Paint();
         mPaintAmPmCircle[PM].setAntiAlias(true);
 
-        mPaintBackground.setColor(
-                a.getColor(R.styleable.TimePicker_numbersBackgroundColor, Color.WHITE));
+        mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
+                res.getColor(R.color.timepicker_default_numbers_background_color_quantum)));
         mPaintBackground.setAntiAlias(true);
 
-        final int disabledColor = a.getColor(R.styleable.TimePicker_disabledColor,
-                res.getColor(R.color.timepicker_default_disabled_color_holo_light));
-        mPaintDisabled.setColor(disabledColor);
+        mPaintDisabled.setColor(a.getColor(R.styleable.TimePicker_disabledColor,
+                res.getColor(R.color.timepicker_default_disabled_color_quantum)));
         mPaintDisabled.setAntiAlias(true);
 
         if (DEBUG) {
@@ -415,6 +437,8 @@
         mSelectionRadiusMultiplier = Float.parseFloat(
                 res.getString(R.string.timepicker_selection_radius_multiplier));
 
+        a.recycle();
+
         setOnTouchListener(this);
 
         // Initial values
@@ -622,21 +646,21 @@
         mAmPmCircleRadiusMultiplier = Float.parseFloat(
                 res.getString(R.string.timepicker_ampm_circle_radius_multiplier));
 
-        mPaint[HOURS].setAlpha(mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
-        mPaint[MINUTES].setAlpha(mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
+        mAlpha[HOURS].setValue(mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
+        mAlpha[MINUTES].setValue(mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
 
-        mPaintSelector[HOURS][SELECTOR_CIRCLE].setAlpha(
-                mShowHours ?ALPHA_SELECTOR : ALPHA_TRANSPARENT);
-        mPaintSelector[HOURS][SELECTOR_DOT].setAlpha(
+        mAlphaSelector[HOURS][SELECTOR_CIRCLE].setValue(
+                mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT);
+        mAlphaSelector[HOURS][SELECTOR_DOT].setValue(
                 mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
-        mPaintSelector[HOURS][SELECTOR_LINE].setAlpha(
+        mAlphaSelector[HOURS][SELECTOR_LINE].setValue(
                 mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT);
 
-        mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAlpha(
+        mAlphaSelector[MINUTES][SELECTOR_CIRCLE].setValue(
                 mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR);
-        mPaintSelector[MINUTES][SELECTOR_DOT].setAlpha(
+        mAlphaSelector[MINUTES][SELECTOR_DOT].setValue(
                 mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
-        mPaintSelector[MINUTES][SELECTOR_LINE].setAlpha(
+        mAlphaSelector[MINUTES][SELECTOR_LINE].setValue(
                 mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR);
     }
 
@@ -704,20 +728,23 @@
         calculateGridSizesMinutes();
 
         drawCircleBackground(canvas);
+        drawSelector(canvas);
 
         drawTextElements(canvas, mTextSize[HOURS], mTypeface, mOuterTextHours,
-                mTextGridWidths[HOURS], mTextGridHeights[HOURS], mPaint[HOURS]);
+                mTextGridWidths[HOURS], mTextGridHeights[HOURS], mPaint[HOURS],
+                mColor[HOURS], mAlpha[HOURS].getValue());
 
         if (mIs24HourMode && mInnerTextHours != null) {
             drawTextElements(canvas, mInnerTextSize, mTypeface, mInnerTextHours,
-                    mInnerTextGridWidths, mInnerTextGridHeights, mPaint[HOURS]);
+                    mInnerTextGridWidths, mInnerTextGridHeights, mPaint[HOURS],
+                    mColor[HOURS], mAlpha[HOURS].getValue());
         }
 
         drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes,
-                mTextGridWidths[MINUTES], mTextGridHeights[MINUTES], mPaint[MINUTES]);
+                mTextGridWidths[MINUTES], mTextGridHeights[MINUTES], mPaint[MINUTES],
+                mColor[MINUTES], mAlpha[MINUTES].getValue());
 
         drawCenter(canvas);
-        drawSelector(canvas);
         if (!mIs24HourMode) {
             drawAmPm(canvas);
         }
@@ -772,12 +799,12 @@
 
         // Draw the two circles
         mPaintAmPmCircle[AM].setColor(amColor);
-        mPaintAmPmCircle[AM].setAlpha(amAlpha);
+        mPaintAmPmCircle[AM].setAlpha(getMultipliedAlpha(amColor, amAlpha));
         canvas.drawCircle(isLayoutRtl ? mRightIndicatorXCenter : mLeftIndicatorXCenter,
                 mAmPmYCenter, mAmPmCircleRadius, mPaintAmPmCircle[AM]);
 
         mPaintAmPmCircle[PM].setColor(pmColor);
-        mPaintAmPmCircle[PM].setAlpha(pmAlpha);
+        mPaintAmPmCircle[PM].setAlpha(getMultipliedAlpha(pmColor, pmAlpha));
         canvas.drawCircle(isLayoutRtl ? mLeftIndicatorXCenter : mRightIndicatorXCenter,
                 mAmPmYCenter, mAmPmCircleRadius, mPaintAmPmCircle[PM]);
 
@@ -792,6 +819,10 @@
                 textYCenter, mPaintAmPmText);
     }
 
+    private int getMultipliedAlpha(int argb, int alpha) {
+        return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5);
+    }
+
     private void drawSelector(Canvas canvas, int index) {
         // Calculate the current radius at which to place the selection circle.
         mLineLength[index] = (int) (mCircleRadius[index]
@@ -802,15 +833,27 @@
         int pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians));
         int pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians));
 
+        int color;
+        int alpha;
+        Paint paint;
+
         // Draw the selection circle
-        canvas.drawCircle(pointX, pointY, mSelectionRadius[index],
-                mPaintSelector[index % 2][SELECTOR_CIRCLE]);
+        color = mColorSelector[index % 2][SELECTOR_CIRCLE];
+        alpha = mAlphaSelector[index % 2][SELECTOR_CIRCLE].getValue();
+        paint = mPaintSelector[index % 2][SELECTOR_CIRCLE];
+        paint.setColor(color);
+        paint.setAlpha(getMultipliedAlpha(color, alpha));
+        canvas.drawCircle(pointX, pointY, mSelectionRadius[index], paint);
 
         // Draw the dot if needed
         if (mSelectionDegrees[index] % 30 != 0) {
             // We're not on a direct tick
-            canvas.drawCircle(pointX, pointY, (mSelectionRadius[index] * 2 / 7),
-                    mPaintSelector[index % 2][SELECTOR_DOT]);
+            color = mColorSelector[index % 2][SELECTOR_DOT];
+            alpha = mAlphaSelector[index % 2][SELECTOR_DOT].getValue();
+            paint = mPaintSelector[index % 2][SELECTOR_DOT];
+            paint.setColor(color);
+            paint.setAlpha(getMultipliedAlpha(color, alpha));
+            canvas.drawCircle(pointX, pointY, (mSelectionRadius[index] * 2 / 7), paint);
         } else {
             // We're not drawing the dot, so shorten the line to only go as far as the edge of the
             // selection circle
@@ -820,8 +863,12 @@
         }
 
         // Draw the line
-        canvas.drawLine(mXCenter, mYCenter, pointX, pointY,
-                mPaintSelector[index % 2][SELECTOR_LINE]);
+        color = mColorSelector[index % 2][SELECTOR_LINE];
+        alpha = mAlphaSelector[index % 2][SELECTOR_LINE].getValue();
+        paint = mPaintSelector[index % 2][SELECTOR_LINE];
+        paint.setColor(color);
+        paint.setAlpha(getMultipliedAlpha(color, alpha));
+        canvas.drawLine(mXCenter, mYCenter, pointX, pointY, paint);
     }
 
     private void drawDebug(Canvas canvas) {
@@ -948,9 +995,11 @@
      * Draw the 12 text values at the positions specified by the textGrid parameters.
      */
     private void drawTextElements(Canvas canvas, float textSize, Typeface typeface, String[] texts,
-            float[] textGridWidths, float[] textGridHeights, Paint paint) {
+            float[] textGridWidths, float[] textGridHeights, Paint paint, int color, int alpha) {
         paint.setTextSize(textSize);
         paint.setTypeface(typeface);
+        paint.setColor(color);
+        paint.setAlpha(getMultipliedAlpha(color, alpha));
         canvas.drawText(texts[0], textGridWidths[3], textGridHeights[0], paint);
         canvas.drawText(texts[1], textGridWidths[4], textGridHeights[1], paint);
         canvas.drawText(texts[2], textGridWidths[5], textGridHeights[2], paint);
@@ -1023,17 +1072,17 @@
         return animator;
     }
 
-    private static ObjectAnimator getFadeOutAnimator(Object target, int startAlpha, int endAlpha,
+    private static ObjectAnimator getFadeOutAnimator(IntHolder target, int startAlpha, int endAlpha,
                 InvalidateUpdateListener updateListener) {
         int duration = 500;
-        ObjectAnimator animator = ObjectAnimator.ofInt(target, "alpha", startAlpha, endAlpha);
+        ObjectAnimator animator = ObjectAnimator.ofInt(target, "value", startAlpha, endAlpha);
         animator.setDuration(duration);
         animator.addUpdateListener(updateListener);
 
         return animator;
     }
 
-    private static ObjectAnimator getFadeInAnimator(Object target, int startAlpha, int endAlpha,
+    private static ObjectAnimator getFadeInAnimator(IntHolder target, int startAlpha, int endAlpha,
                 InvalidateUpdateListener updateListener) {
         Keyframe kf0, kf1, kf2;
         int duration = 500;
@@ -1048,7 +1097,7 @@
         kf0 = Keyframe.ofInt(0f, startAlpha);
         kf1 = Keyframe.ofInt(delayPoint, startAlpha);
         kf2 = Keyframe.ofInt(1f, endAlpha);
-        PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1, kf2);
+        PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("value", kf0, kf1, kf2);
 
         ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
                 target, fadeIn).setDuration(totalDuration);
@@ -1068,25 +1117,25 @@
             mHoursToMinutesAnims.add(getRadiusDisappearAnimator(this,
                     "animationRadiusMultiplierHours", mInvalidateUpdateListener,
                     mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
-            mHoursToMinutesAnims.add(getFadeOutAnimator(mPaint[HOURS],
+            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlpha[HOURS],
                     ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
-            mHoursToMinutesAnims.add(getFadeOutAnimator(mPaintSelector[HOURS][SELECTOR_CIRCLE],
+            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
                     ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
-            mHoursToMinutesAnims.add(getFadeOutAnimator(mPaintSelector[HOURS][SELECTOR_DOT],
+            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
                     ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
-            mHoursToMinutesAnims.add(getFadeOutAnimator(mPaintSelector[HOURS][SELECTOR_LINE],
+            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
                     ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
 
             mHoursToMinutesAnims.add(getRadiusReappearAnimator(this,
                     "animationRadiusMultiplierMinutes", mInvalidateUpdateListener,
                     mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
-            mHoursToMinutesAnims.add(getFadeInAnimator(mPaint[MINUTES],
+            mHoursToMinutesAnims.add(getFadeInAnimator(mAlpha[MINUTES],
                     ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
-            mHoursToMinutesAnims.add(getFadeInAnimator(mPaintSelector[MINUTES][SELECTOR_CIRCLE],
+            mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
                     ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
-            mHoursToMinutesAnims.add(getFadeInAnimator(mPaintSelector[MINUTES][SELECTOR_DOT],
+            mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
                     ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
-            mHoursToMinutesAnims.add(getFadeInAnimator(mPaintSelector[MINUTES][SELECTOR_LINE],
+            mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
                     ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
         }
 
@@ -1103,25 +1152,25 @@
             mMinuteToHoursAnims.add(getRadiusDisappearAnimator(this,
                     "animationRadiusMultiplierMinutes", mInvalidateUpdateListener,
                     mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
-            mMinuteToHoursAnims.add(getFadeOutAnimator(mPaint[MINUTES],
+            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlpha[MINUTES],
                     ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
-            mMinuteToHoursAnims.add(getFadeOutAnimator(mPaintSelector[MINUTES][SELECTOR_CIRCLE],
+            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
                     ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
-            mMinuteToHoursAnims.add(getFadeOutAnimator(mPaintSelector[MINUTES][SELECTOR_DOT],
+            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
                     ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
-            mMinuteToHoursAnims.add(getFadeOutAnimator(mPaintSelector[MINUTES][SELECTOR_LINE],
+            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
                     ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
 
             mMinuteToHoursAnims.add(getRadiusReappearAnimator(this,
                     "animationRadiusMultiplierHours", mInvalidateUpdateListener,
                     mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
-            mMinuteToHoursAnims.add(getFadeInAnimator(mPaint[HOURS],
+            mMinuteToHoursAnims.add(getFadeInAnimator(mAlpha[HOURS],
                     ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
-            mMinuteToHoursAnims.add(getFadeInAnimator(mPaintSelector[HOURS][SELECTOR_CIRCLE],
+            mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
                     ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
-            mMinuteToHoursAnims.add(getFadeInAnimator(mPaintSelector[HOURS][SELECTOR_DOT],
+            mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
                     ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
-            mMinuteToHoursAnims.add(getFadeInAnimator(mPaintSelector[HOURS][SELECTOR_LINE],
+            mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
                     ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
         }
 
@@ -1393,4 +1442,20 @@
         mInputEnabled = inputEnabled;
         invalidate();
     }
+
+    private static class IntHolder {
+        private int mValue;
+
+        public IntHolder(int value) {
+            mValue = value;
+        }
+
+        public void setValue(int value) {
+            mValue = value;
+        }
+
+        public int getValue() {
+            return mValue;
+        }
+    }
 }
diff --git a/core/java/android/widget/TimePickerDelegate.java b/core/java/android/widget/TimePickerDelegate.java
index 79256e5..ba93ee5 100644
--- a/core/java/android/widget/TimePickerDelegate.java
+++ b/core/java/android/widget/TimePickerDelegate.java
@@ -140,13 +140,12 @@
         mSelectMinutes = res.getString(R.string.select_minutes);
 
         mHeaderSelectedColor = a.getColor(R.styleable.TimePicker_headerSelectedTextColor,
-                android.R.color.holo_blue_light);
+                R.color.timepicker_default_selector_color_quantum);
 
-        mHeaderUnSelectedColor = getUnselectedColor(
-                R.color.timepicker_default_text_color_holo_light);
+        mHeaderUnSelectedColor = getUnselectedColor(R.color.timepicker_default_text_color_quantum);
         if (mHeaderUnSelectedColor == -1) {
             mHeaderUnSelectedColor = a.getColor(R.styleable.TimePicker_headerUnselectedTextColor,
-                    R.color.timepicker_default_text_color_holo_light);
+                    R.color.timepicker_default_text_color_quantum);
         }
 
         final int headerBackgroundColor = a.getColor(
@@ -296,7 +295,7 @@
         TypedArray a = mContext.obtainStyledAttributes(TEXT_APPEARANCE_TIME_LABEL_ATTR);
         final int textAppearanceResId = a.getResourceId(0, 0);
         tempView.setTextAppearance(mContext, (textAppearanceResId != 0) ?
-                textAppearanceResId : R.style.TextAppearance_Holo_TimePicker_TimeLabel);
+                textAppearanceResId : R.style.TextAppearance_Quantum_TimePicker_TimeLabel);
         a.recycle();
         ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                 ViewGroup.LayoutParams.WRAP_CONTENT);
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 419c582..cbd9a6a 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -1339,7 +1339,8 @@
                 return getPaddingTop();
 
             case Gravity.BOTTOM:
-                return getPaddingBottom() - child.getMeasuredHeight() - lp.bottomMargin;
+                return getHeight() - getPaddingBottom() -
+                        child.getMeasuredHeight() - lp.bottomMargin;
 
             default:
             case Gravity.CENTER_VERTICAL:
diff --git a/core/java/com/android/internal/app/NavItemSelectedListener.java b/core/java/com/android/internal/app/NavItemSelectedListener.java
new file mode 100644
index 0000000..545f44b
--- /dev/null
+++ b/core/java/com/android/internal/app/NavItemSelectedListener.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 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.internal.app;
+
+import android.app.ActionBar;
+import android.view.View;
+import android.widget.AdapterView;
+
+/**
+ * Wrapper to adapt the ActionBar.OnNavigationListener in an AdapterView.OnItemSelectedListener
+ * for use in Spinner widgets. Used by action bar implementations.
+ */
+class NavItemSelectedListener implements AdapterView.OnItemSelectedListener {
+    private final ActionBar.OnNavigationListener mListener;
+
+    public NavItemSelectedListener(ActionBar.OnNavigationListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+        if (mListener != null) {
+            mListener.onNavigationItemSelected(position, id);
+        }
+    }
+
+    @Override
+    public void onNothingSelected(AdapterView<?> parent) {
+        // Do nothing
+    }
+}
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index 6056bf2..e8a3f0a 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -173,14 +173,19 @@
 
     @Override
     public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
-        throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+        mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback));
     }
 
     @Override
     public void setSelectedNavigationItem(int position) {
-        throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+        switch (mDecorToolbar.getNavigationMode()) {
+            case NAVIGATION_MODE_LIST:
+                mDecorToolbar.setDropdownSelectedPosition(position);
+                break;
+            default:
+                throw new IllegalStateException(
+                        "setSelectedNavigationIndex not valid for current navigation mode");
+        }
     }
 
     @Override
@@ -276,8 +281,10 @@
 
     @Override
     public void setNavigationMode(@NavigationMode int mode) {
-        throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+        if (mode == ActionBar.NAVIGATION_MODE_TABS) {
+            throw new IllegalArgumentException("Tabs not supported in this configuration");
+        }
+        mDecorToolbar.setNavigationMode(mode);
     }
 
     @Override
@@ -288,67 +295,67 @@
     @Override
     public Tab newTab() {
         throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+                "Tabs are not supported in toolbar action bars");
     }
 
     @Override
     public void addTab(Tab tab) {
         throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+                "Tabs are not supported in toolbar action bars");
     }
 
     @Override
     public void addTab(Tab tab, boolean setSelected) {
         throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+                "Tabs are not supported in toolbar action bars");
     }
 
     @Override
     public void addTab(Tab tab, int position) {
         throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+                "Tabs are not supported in toolbar action bars");
     }
 
     @Override
     public void addTab(Tab tab, int position, boolean setSelected) {
         throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+                "Tabs are not supported in toolbar action bars");
     }
 
     @Override
     public void removeTab(Tab tab) {
         throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+                "Tabs are not supported in toolbar action bars");
     }
 
     @Override
     public void removeTabAt(int position) {
         throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+                "Tabs are not supported in toolbar action bars");
     }
 
     @Override
     public void removeAllTabs() {
         throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+                "Tabs are not supported in toolbar action bars");
     }
 
     @Override
     public void selectTab(Tab tab) {
         throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+                "Tabs are not supported in toolbar action bars");
     }
 
     @Override
     public Tab getSelectedTab() {
         throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+                "Tabs are not supported in toolbar action bars");
     }
 
     @Override
     public Tab getTabAt(int index) {
         throw new UnsupportedOperationException(
-                "Navigation modes are not supported in toolbar action bars");
+                "Tabs are not supported in toolbar action bars");
     }
 
     @Override
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index c0b5b97..87a80ac 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -18,9 +18,7 @@
 
 import android.animation.ValueAnimator;
 import android.content.res.TypedArray;
-import android.view.ViewGroup;
 import android.view.ViewParent;
-import android.widget.AdapterView;
 import android.widget.Toolbar;
 import com.android.internal.R;
 import com.android.internal.view.ActionBarPolicy;
@@ -30,7 +28,6 @@
 import com.android.internal.widget.ActionBarContainer;
 import com.android.internal.widget.ActionBarContextView;
 import com.android.internal.widget.ActionBarOverlayLayout;
-import com.android.internal.widget.ActionBarView;
 import com.android.internal.widget.DecorToolbar;
 import com.android.internal.widget.ScrollingTabContainerView;
 
@@ -59,7 +56,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AnimationUtils;
 import android.widget.SpinnerAdapter;
-import com.android.internal.widget.ToolbarWidgetWrapper;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -1313,23 +1309,4 @@
         }
     }
 
-    static class NavItemSelectedListener implements AdapterView.OnItemSelectedListener {
-        private final OnNavigationListener mListener;
-
-        public NavItemSelectedListener(OnNavigationListener listener) {
-            mListener = listener;
-        }
-
-        @Override
-        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-            if (mListener != null) {
-                mListener.onNavigationItemSelected(position, id);
-            }
-        }
-
-        @Override
-        public void onNothingSelected(AdapterView<?> parent) {
-            // Do nothing
-        }
-    }
 }
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index af82778..aa642fd 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -349,10 +349,7 @@
         return mIncludeTabs;
     }
 
-    public void setEmbeddedTabView(View view) {
-        setEmbeddedTabView((ScrollingTabContainerView) view);
-    }
-
+    @Override
     public void setEmbeddedTabView(ScrollingTabContainerView tabs) {
         if (mTabScrollView != null) {
             removeView(mTabScrollView);
diff --git a/core/java/com/android/internal/widget/DecorToolbar.java b/core/java/com/android/internal/widget/DecorToolbar.java
index ee6988e..5281045 100644
--- a/core/java/com/android/internal/widget/DecorToolbar.java
+++ b/core/java/com/android/internal/widget/DecorToolbar.java
@@ -71,7 +71,7 @@
 
     int getDisplayOptions();
     void setDisplayOptions(int opts);
-    void setEmbeddedTabView(View tabView);
+    void setEmbeddedTabView(ScrollingTabContainerView tabView);
     boolean hasEmbeddedTabs();
     boolean isTitleTruncated();
     void setCollapsible(boolean collapsible);
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index 3e15c32..b298d85 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -27,6 +27,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.View;
@@ -78,6 +79,8 @@
     private boolean mMenuPrepared;
     private ActionMenuPresenter mActionMenuPresenter;
 
+    private int mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD;
+
     public ToolbarWidgetWrapper(Toolbar toolbar) {
         mToolbar = toolbar;
 
@@ -394,8 +397,19 @@
     }
 
     @Override
-    public void setEmbeddedTabView(View tabView) {
+    public void setEmbeddedTabView(ScrollingTabContainerView tabView) {
+        if (mTabView != null && mTabView.getParent() == mToolbar) {
+            mToolbar.removeView(mTabView);
+        }
         mTabView = tabView;
+        if (tabView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
+            mToolbar.addView(mTabView, 0);
+            Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
+            lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+            lp.gravity = Gravity.START | Gravity.BOTTOM;
+            tabView.setAllowCollapse(true);
+        }
     }
 
     @Override
@@ -420,23 +434,63 @@
 
     @Override
     public int getNavigationMode() {
-        return 0;
+        return mNavigationMode;
     }
 
     @Override
     public void setNavigationMode(int mode) {
-        if (mode != ActionBar.NAVIGATION_MODE_STANDARD) {
-            throw new IllegalArgumentException(
-                    "Navigation modes not supported in this configuration");
+        final int oldMode = mNavigationMode;
+        if (mode != oldMode) {
+            switch (oldMode) {
+                case ActionBar.NAVIGATION_MODE_LIST:
+                    if (mSpinner != null && mSpinner.getParent() == mToolbar) {
+                        mToolbar.removeView(mSpinner);
+                    }
+                    break;
+                case ActionBar.NAVIGATION_MODE_TABS:
+                    if (mTabView != null && mTabView.getParent() == mToolbar) {
+                        mToolbar.removeView(mTabView);
+                    }
+                    break;
+            }
+
+            mNavigationMode = mode;
+
+            switch (mode) {
+                case ActionBar.NAVIGATION_MODE_STANDARD:
+                    break;
+                case ActionBar.NAVIGATION_MODE_LIST:
+                    ensureSpinner();
+                    mToolbar.addView(mSpinner, 0);
+                    break;
+                case ActionBar.NAVIGATION_MODE_TABS:
+                    if (mTabView != null) {
+                        mToolbar.addView(mTabView, 0);
+                        Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
+                        lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+                        lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+                        lp.gravity = Gravity.START | Gravity.BOTTOM;
+                    }
+                    break;
+                default:
+                    throw new IllegalArgumentException("Invalid navigation mode " + mode);
+            }
+        }
+    }
+
+    private void ensureSpinner() {
+        if (mSpinner == null) {
+            mSpinner = new Spinner(getContext());
+            Toolbar.LayoutParams lp = new Toolbar.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
+            mSpinner.setLayoutParams(lp);
         }
     }
 
     @Override
     public void setDropdownParams(SpinnerAdapter adapter,
             AdapterView.OnItemSelectedListener listener) {
-        if (mSpinner == null) {
-            mSpinner = new Spinner(getContext());
-        }
+        ensureSpinner();
         mSpinner.setAdapter(adapter);
         mSpinner.setOnItemSelectedListener(listener);
     }
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index f7acbd7..ec935cc 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -821,32 +821,45 @@
     }
 
 #ifdef USE_MINIKIN
-    static void drawGlyphsToSkia(SkCanvas* canvas, SkPaint* paint, Layout* layout, float x, float y) {
-        size_t nGlyphs = layout->nGlyphs();
-        uint16_t *glyphs = new uint16_t[nGlyphs];
-        SkPoint *pos = new SkPoint[nGlyphs];
-        SkTypeface *lastFace = NULL;
-        SkTypeface *skFace = NULL;
-        size_t start = 0;
+    class DrawTextFunctor {
+    public:
+        DrawTextFunctor(const Layout& layout, SkCanvas* canvas, jfloat x, jfloat y, SkPaint* paint,
+                    uint16_t* glyphs, SkPoint* pos)
+                : layout(layout), canvas(canvas), x(x), y(y), paint(paint), glyphs(glyphs),
+                    pos(pos) { }
 
-        paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
-        for (size_t i = 0; i < nGlyphs; i++) {
-            MinikinFontSkia *mfs = static_cast<MinikinFontSkia *>(layout->getFont(i));
-            skFace = mfs->GetSkTypeface();
-            glyphs[i] = layout->getGlyphId(i);
-            pos[i].fX = x + layout->getX(i);
-            pos[i].fY = y + layout->getY(i);
-            if (i > 0 && skFace != lastFace) {
-                paint->setTypeface(lastFace);
-                canvas->drawPosText(glyphs + start, (i - start) << 1, pos + start, *paint);
-                start = i;
+        void operator()(SkTypeface* t, size_t start, size_t end) {
+            for (size_t i = start; i < end; i++) {
+                glyphs[i] = layout.getGlyphId(i);
+                pos[i].fX = x + layout.getX(i);
+                pos[i].fY = y + layout.getY(i);
             }
-            lastFace = skFace;
+            paint->setTypeface(t);
+            canvas->drawPosText(glyphs + start, (end - start) << 1, pos + start, *paint);
         }
-        if (skFace != NULL) {
-            paint->setTypeface(skFace);
-            canvas->drawPosText(glyphs + start, (nGlyphs - start) << 1, pos + start, *paint);
-        }
+    private:
+        const Layout& layout;
+        SkCanvas* canvas;
+        jfloat x;
+        jfloat y;
+        SkPaint* paint;
+        uint16_t* glyphs;
+        SkPoint* pos;
+    };
+
+    static void drawGlyphsToSkia(SkCanvas* canvas, SkPaint* paint, const Layout& layout, float x, float y) {
+        size_t nGlyphs = layout.nGlyphs();
+        uint16_t* glyphs = new uint16_t[nGlyphs];
+        SkPoint* pos = new SkPoint[nGlyphs];
+
+        x += MinikinUtils::xOffsetForTextAlign(paint, layout);
+        SkPaint::Align align = paint->getTextAlign();
+        paint->setTextAlign(SkPaint::kLeft_Align);
+        paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+        DrawTextFunctor f(layout, canvas, x, y, paint, glyphs, pos);
+        MinikinUtils::forFontRun(layout, f);
+        doDrawTextDecorations(canvas, x, y, layout.getAdvance(), paint);
+        paint->setTextAlign(align);
         delete[] glyphs;
         delete[] pos;
     }
@@ -868,7 +881,7 @@
         Layout layout;
         MinikinUtils::SetLayoutProperties(&layout, paint, flags, typeface);
         layout.doLayout(textArray + start, count);
-        drawGlyphsToSkia(canvas, paint, &layout, x, y);
+        drawGlyphsToSkia(canvas, paint, layout, x, y);
 #else
         sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
                 textArray, start, count, contextCount, flags);
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index 79381ad..597160b 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -17,6 +17,7 @@
 #include "SkPaint.h"
 #include "minikin/Layout.h"
 #include "TypefaceImpl.h"
+#include "MinikinSkia.h"
 
 #include "MinikinUtils.h"
 
@@ -42,4 +43,18 @@
     layout->setProperties(css);
 }
 
-}
\ No newline at end of file
+float MinikinUtils::xOffsetForTextAlign(SkPaint* paint, const Layout& layout) {
+    switch (paint->getTextAlign()) {
+        case SkPaint::kCenter_Align:
+            return layout.getAdvance() * -0.5f;
+            break;
+        case SkPaint::kRight_Align:
+            return -layout.getAdvance();
+            break;
+        default:
+            break;
+    }
+    return 0;
+}
+
+}
diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h
index 42f5e2f..f85074c 100644
--- a/core/jni/android/graphics/MinikinUtils.h
+++ b/core/jni/android/graphics/MinikinUtils.h
@@ -30,6 +30,27 @@
 public:
     static void SetLayoutProperties(Layout* layout, SkPaint* paint, int flags,
         TypefaceImpl* face);
+    static float xOffsetForTextAlign(SkPaint* paint, const Layout& layout);
+
+    // f is a functor of type void f(SkTypeface *, size_t start, size_t end);
+    template <typename F>
+    static void forFontRun(const Layout& layout, F& f) {
+        SkTypeface* lastFace = NULL;
+        size_t start = 0;
+        size_t nGlyphs = layout.nGlyphs();
+        for (size_t i = 0; i < nGlyphs; i++) {
+            MinikinFontSkia* mfs = static_cast<MinikinFontSkia*>(layout.getFont(i));
+            SkTypeface* skFace = mfs->GetSkTypeface();
+            if (i > 0 && skFace != lastFace) {
+                f(lastFace, start, i);
+                start = i;
+            }
+            lastFace = skFace;
+        }
+        if (nGlyphs > start) {
+            f(lastFace, start, nGlyphs);
+        }
+    }
 };
 
 }  // namespace android
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 8418162..4642cfd 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -822,26 +822,83 @@
         return result;
     }
 
-    static void getTextPath(JNIEnv* env, SkPaint* paint, const jchar* text, jint count,
-                            jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
+#ifdef USE_MINIKIN
+    class GetTextFunctor {
+    public:
+        GetTextFunctor(const Layout& layout, SkPath* path, jfloat x, jfloat y, SkPaint* paint,
+                    uint16_t* glyphs, SkPoint* pos)
+                : layout(layout), path(path), x(x), y(y), paint(paint), glyphs(glyphs), pos(pos) {
+        }
+
+        void operator()(SkTypeface* t, size_t start, size_t end) {
+            for (size_t i = start; i < end; i++) {
+                glyphs[i] = layout.getGlyphId(i);
+                pos[i].fX = x + layout.getX(i);
+                pos[i].fY = y + layout.getY(i);
+            }
+            paint->setTypeface(t);
+            if (start == 0) {
+                paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, path);
+            } else {
+                paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, &tmpPath);
+                path->addPath(tmpPath);
+            }
+        }
+    private:
+        const Layout& layout;
+        SkPath* path;
+        jfloat x;
+        jfloat y;
+        SkPaint* paint;
+        uint16_t* glyphs;
+        SkPoint* pos;
+        SkPath tmpPath;
+    };
+#endif
+
+    static void getTextPath(JNIEnv* env, SkPaint* paint, TypefaceImpl* typeface, const jchar* text,
+            jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) {
+#ifdef USE_MINIKIN
+        Layout layout;
+        MinikinUtils::SetLayoutProperties(&layout, paint, bidiFlags, typeface);
+        layout.doLayout(text, count);
+        size_t nGlyphs = layout.nGlyphs();
+        uint16_t* glyphs = new uint16_t[nGlyphs];
+        SkPoint* pos = new SkPoint[nGlyphs];
+
+        x += MinikinUtils::xOffsetForTextAlign(paint, layout);
+        SkPaint::Align align = paint->getTextAlign();
+        paint->setTextAlign(SkPaint::kLeft_Align);
+        paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+        GetTextFunctor f(layout, path, x, y, paint, glyphs, pos);
+        MinikinUtils::forFontRun(layout, f);
+        paint->setTextAlign(align);
+        delete[] glyphs;
+        delete[] pos;
+#else
         TextLayout::getTextPath(paint, text, count, bidiFlags, x, y, path);
+#endif
     }
 
-    static void getTextPath___C(JNIEnv* env, jobject clazz, jlong paintHandle, jint bidiFlags,
+    static void getTextPath___C(JNIEnv* env, jobject clazz, jlong paintHandle,
+            jlong typefaceHandle, jint bidiFlags,
             jcharArray text, jint index, jint count, jfloat x, jfloat y, jlong pathHandle) {
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+        TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
         SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
         const jchar* textArray = env->GetCharArrayElements(text, NULL);
-        getTextPath(env, paint, textArray + index, count, bidiFlags, x, y, path);
+        getTextPath(env, paint, typeface, textArray + index, count, bidiFlags, x, y, path);
         env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
     }
 
-    static void getTextPath__String(JNIEnv* env, jobject clazz, jlong paintHandle, jint bidiFlags,
+    static void getTextPath__String(JNIEnv* env, jobject clazz, jlong paintHandle,
+            jlong typefaceHandle, jint bidiFlags,
             jstring text, jint start, jint end, jfloat x, jfloat y, jlong pathHandle) {
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+        TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
         SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
         const jchar* textArray = env->GetStringChars(text, NULL);
-        getTextPath(env, paint, textArray + start, end - start, bidiFlags, x, y, path);
+        getTextPath(env, paint, typeface, textArray + start, end - start, bidiFlags, x, y, path);
         env->ReleaseStringChars(text, textArray);
     }
 
@@ -1035,8 +1092,8 @@
     {"native_getTextRunCursor", "(J[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
     {"native_getTextRunCursor", "(JLjava/lang/String;IIIII)I",
         (void*) SkPaintGlue::getTextRunCursor__String},
-    {"native_getTextPath","(JI[CIIFFJ)V", (void*) SkPaintGlue::getTextPath___C},
-    {"native_getTextPath","(JILjava/lang/String;IIFFJ)V", (void*) SkPaintGlue::getTextPath__String},
+    {"native_getTextPath","(JJI[CIIFFJ)V", (void*) SkPaintGlue::getTextPath___C},
+    {"native_getTextPath","(JJILjava/lang/String;IIFFJ)V", (void*) SkPaintGlue::getTextPath__String},
     {"nativeGetStringBounds", "(JLjava/lang/String;IIILandroid/graphics/Rect;)V",
                                         (void*) SkPaintGlue::getStringBounds },
     {"nativeGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V",
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index 9279758..495d08a 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "TextLayoutCache"
 
 #include <utils/JenkinsHash.h>
+#include <utils/CallStack.h>
 
 #include "TextLayoutCache.h"
 #include "TextLayout.h"
@@ -89,6 +90,11 @@
 sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
             const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
     AutoMutex _l(mLock);
+#ifdef USE_MINIKIN
+    // We want to get rid of all legacy calls in the Minikin case, so log
+    ALOGW("TextLayoutCache being invoked!");
+    CallStack _cs(LOG_TAG);
+#endif
     nsecs_t startTime = 0;
     if (mDebugEnabled) {
         startTime = systemTime(SYSTEM_TIME_MONOTONIC);
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index 7b686e7..33100bf 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -31,6 +31,8 @@
 #include <utils/RefBase.h>
 #include <cutils/properties.h>
 
+#include <string.h>
+
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_hardware_camera2_CameraMetadata.h"
 
@@ -175,7 +177,7 @@
 }
 
 static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPtr,
-        jobject resultsPtr) {
+        jobject resultsPtr, jstring formattedCaptureTime) {
     ALOGV("%s:", __FUNCTION__);
     CameraMetadata characteristics;
     CameraMetadata results;
@@ -205,7 +207,6 @@
     OpcodeListBuilder::CfaLayout opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB;
 
     // TODO: Greensplit.
-    // TODO: UniqueCameraModel
     // TODO: Add remaining non-essential tags
     {
         // Set orientation
@@ -352,6 +353,176 @@
     }
 
     {
+        // image description
+        uint8_t imageDescription = '\0'; // empty
+        BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEDESCRIPTION, 1, &imageDescription, TIFF_IFD_0),
+                env, TAG_IMAGEDESCRIPTION);
+    }
+
+    {
+        // make
+        char manufacturer[PROPERTY_VALUE_MAX];
+
+        // Use "" to represent unknown make as suggested in TIFF/EP spec.
+        property_get("ro.product.manufacturer", manufacturer, "");
+        uint32_t count = static_cast<uint32_t>(strlen(manufacturer)) + 1;
+
+        BAIL_IF_INVALID(writer->addEntry(TAG_MAKE, count, reinterpret_cast<uint8_t*>(manufacturer),
+                TIFF_IFD_0), env, TAG_MAKE);
+    }
+
+    {
+        // model
+        char model[PROPERTY_VALUE_MAX];
+
+        // Use "" to represent unknown model as suggested in TIFF/EP spec.
+        property_get("ro.product.model", model, "");
+        uint32_t count = static_cast<uint32_t>(strlen(model)) + 1;
+
+        BAIL_IF_INVALID(writer->addEntry(TAG_MODEL, count, reinterpret_cast<uint8_t*>(model),
+                TIFF_IFD_0), env, TAG_MODEL);
+    }
+
+    {
+        // x resolution
+        uint32_t xres[] = { 72, 1 }; // default 72 ppi
+        BAIL_IF_INVALID(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0),
+                env, TAG_XRESOLUTION);
+
+        // y resolution
+        uint32_t yres[] = { 72, 1 }; // default 72 ppi
+        BAIL_IF_INVALID(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0),
+                env, TAG_YRESOLUTION);
+
+        uint16_t unit = 2; // inches
+        BAIL_IF_INVALID(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0),
+                env, TAG_RESOLUTIONUNIT);
+    }
+
+    {
+        // software
+        char software[PROPERTY_VALUE_MAX];
+        property_get("ro.build.fingerprint", software, "");
+        uint32_t count = static_cast<uint32_t>(strlen(software)) + 1;
+        BAIL_IF_INVALID(writer->addEntry(TAG_SOFTWARE, count, reinterpret_cast<uint8_t*>(software),
+                TIFF_IFD_0), env, TAG_SOFTWARE);
+    }
+
+    {
+        // datetime
+        const size_t DATETIME_COUNT = 20;
+        const char* captureTime = env->GetStringUTFChars(formattedCaptureTime, NULL);
+
+        size_t len = strlen(captureTime) + 1;
+        if (len != DATETIME_COUNT) {
+            jniThrowException(env, "java/lang/IllegalArgumentException",
+                    "Timestamp string length is not required 20 characters");
+            return;
+        }
+
+        BAIL_IF_INVALID(writer->addEntry(TAG_DATETIME, DATETIME_COUNT,
+                reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0), env, TAG_DATETIMEORIGINAL);
+
+        // datetime original
+        BAIL_IF_INVALID(writer->addEntry(TAG_DATETIMEORIGINAL, DATETIME_COUNT,
+                reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0), env, TAG_DATETIMEORIGINAL);
+        env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
+    }
+
+    {
+        // TIFF/EP standard id
+        uint8_t standardId[] = { 1, 0, 0, 0 };
+        BAIL_IF_INVALID(writer->addEntry(TAG_TIFFEPSTANDARDID, 4, standardId,
+                TIFF_IFD_0), env, TAG_TIFFEPSTANDARDID);
+    }
+
+    {
+        // copyright
+        uint8_t copyright = '\0'; // empty
+        BAIL_IF_INVALID(writer->addEntry(TAG_COPYRIGHT, 1, &copyright,
+                TIFF_IFD_0), env, TAG_COPYRIGHT);
+    }
+
+    {
+        // exposure time
+        camera_metadata_entry entry =
+            results.find(ANDROID_SENSOR_EXPOSURE_TIME);
+        BAIL_IF_EMPTY(entry, env, TAG_EXPOSURETIME);
+
+        int64_t exposureTime = *(entry.data.i64);
+
+        if (exposureTime < 0) {
+            // Should be unreachable
+            jniThrowException(env, "java/lang/IllegalArgumentException",
+                    "Negative exposure time in metadata");
+            return;
+        }
+
+        // Ensure exposure time doesn't overflow (for exposures > 4s)
+        uint32_t denominator = 1000000000;
+        while (exposureTime > UINT32_MAX) {
+            exposureTime >>= 1;
+            denominator >>= 1;
+            if (denominator == 0) {
+                // Should be unreachable
+                jniThrowException(env, "java/lang/IllegalArgumentException",
+                        "Exposure time too long");
+                return;
+            }
+        }
+
+        uint32_t exposure[] = { static_cast<uint32_t>(exposureTime), denominator };
+        BAIL_IF_INVALID(writer->addEntry(TAG_EXPOSURETIME, 1, exposure,
+                TIFF_IFD_0), env, TAG_EXPOSURETIME);
+
+    }
+
+    {
+        // ISO speed ratings
+        camera_metadata_entry entry =
+            results.find(ANDROID_SENSOR_SENSITIVITY);
+        BAIL_IF_EMPTY(entry, env, TAG_ISOSPEEDRATINGS);
+
+        int32_t tempIso = *(entry.data.i32);
+        if (tempIso < 0) {
+            jniThrowException(env, "java/lang/IllegalArgumentException",
+                                    "Negative ISO value");
+            return;
+        }
+
+        if (tempIso > UINT16_MAX) {
+            ALOGW("%s: ISO value overflows UINT16_MAX, clamping to max", __FUNCTION__);
+            tempIso = UINT16_MAX;
+        }
+
+        uint16_t iso = static_cast<uint16_t>(tempIso);
+        BAIL_IF_INVALID(writer->addEntry(TAG_ISOSPEEDRATINGS, 1, &iso,
+                TIFF_IFD_0), env, TAG_ISOSPEEDRATINGS);
+    }
+
+    {
+        // focal length
+        camera_metadata_entry entry =
+            results.find(ANDROID_LENS_FOCAL_LENGTH);
+        BAIL_IF_EMPTY(entry, env, TAG_FOCALLENGTH);
+
+        uint32_t focalLength[] = { static_cast<uint32_t>(*(entry.data.f) * 100), 100 };
+        BAIL_IF_INVALID(writer->addEntry(TAG_FOCALLENGTH, 1, focalLength,
+                TIFF_IFD_0), env, TAG_FOCALLENGTH);
+    }
+
+    {
+        // f number
+        camera_metadata_entry entry =
+            results.find(ANDROID_LENS_APERTURE);
+        BAIL_IF_EMPTY(entry, env, TAG_FNUMBER);
+
+        uint32_t fnum[] = { static_cast<uint32_t>(*(entry.data.f) * 100), 100 };
+        BAIL_IF_INVALID(writer->addEntry(TAG_FNUMBER, 1, fnum,
+                TIFF_IFD_0), env, TAG_FNUMBER);
+    }
+
+    {
         // Set DNG version information
         uint8_t version[4] = {1, 4, 0, 0};
         BAIL_IF_INVALID(writer->addEntry(TAG_DNGVERSION, 4, version, TIFF_IFD_0),
@@ -751,7 +922,8 @@
 static JNINativeMethod gDngCreatorMethods[] = {
     {"nativeClassInit",        "()V", (void*) DngCreator_nativeClassInit},
     {"nativeInit", "(Landroid/hardware/camera2/impl/CameraMetadataNative;"
-            "Landroid/hardware/camera2/impl/CameraMetadataNative;)V", (void*) DngCreator_init},
+            "Landroid/hardware/camera2/impl/CameraMetadataNative;Ljava/lang/String;)V",
+            (void*) DngCreator_init},
     {"nativeDestroy",           "()V",      (void*) DngCreator_destroy},
     {"nativeSetOrientation",    "(I)V",     (void*) DngCreator_nativeSetOrientation},
     {"nativeSetThumbnailBitmap","(Landroid/graphics/Bitmap;)V",
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index c5dd06f..820da17 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -632,6 +632,7 @@
 // Text
 // ----------------------------------------------------------------------------
 
+// TODO: this is moving to MinikinUtils, remove with USE_MINIKIN ifdef
 static float xOffsetForTextAlign(SkPaint* paint, float totalAdvance) {
     switch (paint->getTextAlign()) {
         case SkPaint::kCenter_Align:
@@ -647,43 +648,51 @@
 }
 
 #ifdef USE_MINIKIN
+
+class RenderTextFunctor {
+public:
+    RenderTextFunctor(const Layout& layout, OpenGLRenderer* renderer, jfloat x, jfloat y,
+                SkPaint* paint, uint16_t* glyphs, float* pos, float totalAdvance,
+                uirenderer::Rect& bounds)
+            : layout(layout), renderer(renderer), x(x), y(y), paint(paint), glyphs(glyphs),
+            pos(pos), totalAdvance(totalAdvance), bounds(bounds) { }
+    void operator()(SkTypeface* t, size_t start, size_t end) {
+        for (size_t i = start; i < end; i++) {
+            glyphs[i] = layout.getGlyphId(i);
+            pos[2 * i] = layout.getX(i);
+            pos[2 * i + 1] = layout.getY(i);
+        }
+        paint->setTypeface(t);
+        size_t glyphsCount = end - start;
+        int bytesCount = glyphsCount * sizeof(jchar);
+        renderer->drawText((const char*) (glyphs + start), bytesCount, glyphsCount,
+            x, y, pos + 2 * start, paint, totalAdvance, bounds);
+    }
+private:
+    const Layout& layout;
+    OpenGLRenderer* renderer;
+    jfloat x;
+    jfloat y;
+    SkPaint* paint;
+    uint16_t* glyphs;
+    float* pos;
+    float totalAdvance;
+    uirenderer::Rect& bounds;
+};
+
 static void renderTextLayout(OpenGLRenderer* renderer, Layout* layout,
     jfloat x, jfloat y, SkPaint* paint) {
     size_t nGlyphs = layout->nGlyphs();
     float* pos = new float[nGlyphs * 2];
     uint16_t* glyphs = new uint16_t[nGlyphs];
-    SkTypeface* lastFace = 0;
-    SkTypeface* skFace = 0;
-    size_t start = 0;
     MinikinRect b;
     layout->getBounds(&b);
     android::uirenderer::Rect bounds(b.mLeft, b.mTop, b.mRight, b.mBottom);
     bounds.translate(x, y);
     float totalAdvance = layout->getAdvance();
 
-    for (size_t i = 0; i < nGlyphs; i++) {
-        MinikinFontSkia* mfs = static_cast<MinikinFontSkia *>(layout->getFont(i));
-        skFace = mfs->GetSkTypeface();
-        glyphs[i] = layout->getGlyphId(i);
-        pos[2 * i] = layout->getX(i);
-        pos[2 * i + 1] = layout->getY(i);
-        if (i > 0 && skFace != lastFace) {
-            paint->setTypeface(lastFace);
-            size_t glyphsCount = i - start;
-            int bytesCount = glyphsCount * sizeof(jchar);
-            renderer->drawText((const char*) (glyphs + start), bytesCount, glyphsCount,
-                x, y, pos + 2 * start, paint, totalAdvance, bounds);
-            start = i;
-        }
-        lastFace = skFace;
-    }
-    if (skFace != NULL) {
-        paint->setTypeface(skFace);
-        size_t glyphsCount = nGlyphs - start;
-        int bytesCount = glyphsCount * sizeof(jchar);
-        renderer->drawText((const char*) (glyphs + start), bytesCount, glyphsCount,
-            x, y, pos + 2 * start, paint, totalAdvance, bounds);
-    }
+    RenderTextFunctor f(*layout, renderer, x, y, paint, glyphs, pos, totalAdvance, bounds);
+    MinikinUtils::forFontRun(*layout, f);
     delete[] glyphs;
     delete[] pos;
 }
diff --git a/core/res/res/drawable-hdpi/spinner_20_inner_holo.png b/core/res/res/drawable-hdpi/spinner_20_inner_holo.png
deleted file mode 100644
index 49841ea..0000000
--- a/core/res/res/drawable-hdpi/spinner_20_inner_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinner_20_outer_holo.png b/core/res/res/drawable-hdpi/spinner_20_outer_holo.png
deleted file mode 100644
index 69f0070..0000000
--- a/core/res/res/drawable-hdpi/spinner_20_outer_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/spinner_20_inner_holo.png b/core/res/res/drawable-mdpi/spinner_20_inner_holo.png
deleted file mode 100644
index 4569fae..0000000
--- a/core/res/res/drawable-mdpi/spinner_20_inner_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/spinner_20_outer_holo.png b/core/res/res/drawable-mdpi/spinner_20_outer_holo.png
deleted file mode 100644
index 9287dd7..0000000
--- a/core/res/res/drawable-mdpi/spinner_20_outer_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/spinner_20_inner_holo.png b/core/res/res/drawable-xhdpi/spinner_20_inner_holo.png
deleted file mode 100644
index 76e9428..0000000
--- a/core/res/res/drawable-xhdpi/spinner_20_inner_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/spinner_20_outer_holo.png b/core/res/res/drawable-xhdpi/spinner_20_outer_holo.png
deleted file mode 100644
index 6f693d6..0000000
--- a/core/res/res/drawable-xhdpi/spinner_20_outer_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/spinner_20_inner_holo.png b/core/res/res/drawable-xxhdpi/spinner_20_inner_holo.png
deleted file mode 100644
index 6cbd1f4..0000000
--- a/core/res/res/drawable-xxhdpi/spinner_20_inner_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/spinner_20_outer_holo.png b/core/res/res/drawable-xxhdpi/spinner_20_outer_holo.png
deleted file mode 100644
index b6af5e7..0000000
--- a/core/res/res/drawable-xxhdpi/spinner_20_outer_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/progress_large_quantum.xml b/core/res/res/drawable/progress_large_quantum.xml
new file mode 100644
index 0000000..7bef637
--- /dev/null
+++ b/core/res/res/drawable/progress_large_quantum.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<quantum-progress xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?attr/colorControlActivated"
+    android:width="76dp"
+    android:height="76dp"
+    android:thickness="6.3dp"
+    android:innerRadius="30.1dp" />
diff --git a/core/res/res/drawable/progress_medium_quantum.xml b/core/res/res/drawable/progress_medium_quantum.xml
new file mode 100644
index 0000000..adc72f0
--- /dev/null
+++ b/core/res/res/drawable/progress_medium_quantum.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<quantum-progress xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?attr/colorControlActivated"
+    android:width="48dp"
+    android:height="48dp"
+    android:thickness="4dp"
+    android:innerRadius="19dp" />
diff --git a/core/res/res/drawable/progress_small_quantum.xml b/core/res/res/drawable/progress_small_quantum.xml
new file mode 100644
index 0000000..eb4884a
--- /dev/null
+++ b/core/res/res/drawable/progress_small_quantum.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<quantum-progress xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?attr/colorControlActivated"
+    android:width="16dp"
+    android:height="16dp"
+    android:thickness="1.3dp"
+    android:innerRadius="6.3dp" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 513a495..2db6bec 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4556,6 +4556,16 @@
         <attr name="drawable" />
     </declare-styleable>
 
+    <!-- Drawable used to render the Quantum progress indicator. -->
+    <declare-styleable name="QuantumProgressDrawable">
+        <attr name="visible" />
+        <attr name="thickness" />
+        <attr name="innerRadius" />
+        <attr name="width" />
+        <attr name="height" />
+        <attr name="color" />
+    </declare-styleable>
+
     <declare-styleable name="InsetDrawable">
         <attr name="visible" />
         <attr name="drawable" />
diff --git a/core/res/res/values/colors_quantum.xml b/core/res/res/values/colors_quantum.xml
index 5986403..dffc9b0 100644
--- a/core/res/res/values/colors_quantum.xml
+++ b/core/res/res/values/colors_quantum.xml
@@ -151,4 +151,15 @@
     <color name="quantum_brown_300">#ffa1887f</color>
     <color name="quantum_brown_500">#ff795548</color>
     <color name="quantum_brown_700">#ff5d4037</color>
+
+    <!-- Time picker defaults when no theme is set -->
+    <eat-comment />
+
+    <color name="timepicker_default_background_quantum">@color/primary_text_default_quantum_light</color>
+    <color name="timepicker_default_text_color_quantum">@color/black</color>
+    <color name="timepicker_default_disabled_color_quantum">@color/bright_foreground_disabled_quantum_dark</color>
+    <color name="timepicker_default_ampm_selected_background_color_quantum">@color/quantum_light_blue_A200</color>
+    <color name="timepicker_default_ampm_unselected_background_color_quantum">@color/transparent</color>
+    <color name="timepicker_default_selector_color_quantum">@color/quantum_light_blue_A200</color>
+    <color name="timepicker_default_numbers_background_color_quantum">@color/transparent</color>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6239926..88e1cda 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2274,9 +2274,10 @@
 
   <public type="style" name="ThemeOverlay" />
   <public type="style" name="ThemeOverlay.Quantum" />
+  <public type="style" name="ThemeOverlay.Quantum.ActionBar" />
   <public type="style" name="ThemeOverlay.Quantum.Light" />
   <public type="style" name="ThemeOverlay.Quantum.Dark" />
-  <public type="style" name="ThemeOverlay.Quantum.ActionBarWidget" />
+  <public type="style" name="ThemeOverlay.Quantum.Dark.ActionBar" />
 
   <public type="style" name="Widget.Quantum" />
   <public type="style" name="Widget.Quantum.ActionBar" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index a0b3b63..c769cd9 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1207,6 +1207,7 @@
         <item name="android:buttonGravity">top</item>
         <item name="android:navigationButtonStyle">@android:style/Widget.Toolbar.Button.Navigation</item>
         <item name="android:collapseIcon">?android:attr/homeAsUpIndicator</item>
+        <item name="android:contentInsetStart">16dp</item>
     </style>
 
     <style name="Widget.Toolbar.Button.Navigation" parent="@android:style/Widget">
diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index 8c2e14e..a07d02b 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -547,7 +547,7 @@
     </style>
 
     <style name="Widget.Quantum.ProgressBar" parent="Widget.ProgressBar">
-        <item name="indeterminateDrawable">@drawable/progress_medium_holo</item>
+        <item name="indeterminateDrawable">@drawable/progress_medium_quantum</item>
     </style>
 
     <style name="Widget.Quantum.ProgressBar.Inverse"/>
@@ -560,14 +560,14 @@
     </style>
 
     <style name="Widget.Quantum.ProgressBar.Small" parent="Widget.ProgressBar.Small">
-        <item name="indeterminateDrawable">@drawable/progress_small_holo</item>
+        <item name="indeterminateDrawable">@drawable/progress_small_quantum</item>
     </style>
 
     <style name="Widget.Quantum.ProgressBar.Small.Inverse"/>
     <style name="Widget.Quantum.ProgressBar.Small.Title"/>
 
     <style name="Widget.Quantum.ProgressBar.Large" parent="Widget.ProgressBar.Large">
-        <item name="indeterminateDrawable">@drawable/progress_large_holo</item>
+        <item name="indeterminateDrawable">@drawable/progress_large_quantum</item>
     </style>
 
     <style name="Widget.Quantum.ProgressBar.Large.Inverse"/>
@@ -739,6 +739,7 @@
         <item name="itemPadding">8dip</item>
         <item name="homeLayout">@layout/action_bar_home_quantum</item>
         <item name="gravity">center_vertical</item>
+        <item name="contentInsetStart">16dp</item>
     </style>
 
     <style name="Widget.Quantum.ActionBar.Solid">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9e6588f..41238a3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1858,10 +1858,6 @@
   <java-symbol type="string" name="timepicker_numbers_radius_multiplier_normal" />
   <java-symbol type="string" name="timepicker_transition_mid_radius_multiplier" />
   <java-symbol type="string" name="timepicker_transition_end_radius_multiplier" />
-  <java-symbol type="color" name="timepicker_default_text_color_holo_light" />
-  <java-symbol type="color" name="timepicker_default_disabled_color_holo_light" />
-  <java-symbol type="color" name="timepicker_default_ampm_unselected_background_color_holo_light" />
-  <java-symbol type="color" name="timepicker_default_ampm_selected_background_color_holo_light" />
   <java-symbol type="array" name="config_clockTickVibePattern" />
 
   <!-- From various Quantum changes -->
@@ -1873,5 +1869,12 @@
   <java-symbol type="id" name="icon_frame" />
   <java-symbol type="style" name="Animation.VolumePanel" />
   <java-symbol type="transition" name="no_transition" />
+  <java-symbol type="color" name="timepicker_default_text_color_quantum" />
+  <java-symbol type="color" name="timepicker_default_disabled_color_quantum" />
+  <java-symbol type="color" name="timepicker_default_ampm_unselected_background_color_quantum" />
+  <java-symbol type="color" name="timepicker_default_ampm_selected_background_color_quantum" />
+  <java-symbol type="color" name="timepicker_default_selector_color_quantum" />
+  <java-symbol type="color" name="timepicker_default_numbers_background_color_quantum" />
+  <java-symbol type="style" name="TextAppearance.Quantum.TimePicker.TimeLabel" />
 
 </resources>
diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml
index fb3b57d..f47ea7a 100644
--- a/core/res/res/values/themes_quantum.xml
+++ b/core/res/res/values/themes_quantum.xml
@@ -306,8 +306,8 @@
         <item name="actionBarStyle">@style/Widget.Quantum.ActionBar.Solid</item>
         <item name="actionBarSize">@dimen/action_bar_default_height_quantum</item>
         <item name="actionModePopupWindowStyle">@style/Widget.Quantum.PopupWindow.ActionMode</item>
-        <item name="actionBarWidgetTheme">@style/ThemeOverlay.Quantum.ActionBarWidget</item>
-        <item name="actionBarTheme">@null</item>
+        <item name="actionBarWidgetTheme">@null</item>
+        <item name="actionBarTheme">@style/ThemeOverlay.Quantum.ActionBar</item>
         <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
 
         <item name="actionModeCutDrawable">@drawable/ic_menu_cut_quantum</item>
@@ -652,8 +652,8 @@
         <item name="actionBarStyle">@style/Widget.Quantum.Light.ActionBar.Solid</item>
         <item name="actionBarSize">@dimen/action_bar_default_height_quantum</item>
         <item name="actionModePopupWindowStyle">@style/Widget.Quantum.Light.PopupWindow.ActionMode</item>
-        <item name="actionBarWidgetTheme">@style/ThemeOverlay.Quantum.ActionBarWidget</item>
-        <item name="actionBarTheme">@null</item>
+        <item name="actionBarWidgetTheme">@null</item>
+        <item name="actionBarTheme">@style/ThemeOverlay.Quantum.ActionBar</item>
         <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
 
         <item name="actionModeCutDrawable">@drawable/ic_menu_cut_quantum</item>
@@ -728,8 +728,8 @@
          with an inverse color profile. The dark action bar sharply stands out against
          the light content. -->
     <style name="Theme.Quantum.Light.DarkActionBar">
-        <item name="actionBarWidgetTheme">@style/ThemeOverlay.Quantum.ActionBarWidget</item>
-        <item name="actionBarTheme">@style/ThemeOverlay.Quantum.Dark</item>
+        <item name="actionBarWidgetTheme">@null</item>
+        <item name="actionBarTheme">@style/ThemeOverlay.Quantum.Dark.ActionBar</item>
     </style>
 
     <style name="ThemeOverlay" />
@@ -810,7 +810,14 @@
 
     <!-- Theme overlay that replaces the normal control color, which by default is the same as the
          secondary text color, with the primary text color. -->
-    <style name="ThemeOverlay.Quantum.ActionBarWidget">
+    <style name="ThemeOverlay.Quantum.ActionBar">
+        <item name="colorControlNormal">?attr/textColorPrimary</item>
+    </style>
+
+    <!-- Theme overlay that replaces colors with their dark versions and replaces the normal
+         control color, which by default is the same as the secondary text color, with the primary
+         text color. -->
+    <style name="ThemeOverlay.Quantum.Dark.ActionBar">
         <item name="colorControlNormal">?attr/textColorPrimary</item>
     </style>
 
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 92cfd6b..f97add8 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2131,7 +2131,7 @@
         if ((index | count) < 0 || index + count > text.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        native_getTextPath(mNativePaint, mBidiFlags, text, index, count, x, y, 
+        native_getTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, index, count, x, y,
                 path.ni());
     }
 
@@ -2153,7 +2153,7 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_getTextPath(mNativePaint, mBidiFlags, text, start, end, x, y, 
+        native_getTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, start, end, x, y,
                 path.ni());
     }
     
@@ -2261,10 +2261,10 @@
     private native int native_getTextRunCursor(long native_object, String text,
             int contextStart, int contextEnd, int flags, int offset, int cursorOpt);
 
-    private static native void native_getTextPath(long native_object, int bidiFlags,
-                char[] text, int index, int count, float x, float y, long path);
-    private static native void native_getTextPath(long native_object, int bidiFlags,
-                String text, int start, int end, float x, float y, long path);
+    private static native void native_getTextPath(long native_object, long native_typeface,
+            int bidiFlags, char[] text, int index, int count, float x, float y, long path);
+    private static native void native_getTextPath(long native_object, long native_typeface,
+            int bidiFlags, String text, int start, int end, float x, float y, long path);
     private static native void nativeGetStringBounds(long nativePaint,
                                 String text, int start, int end, int bidiFlags, Rect bounds);
     private static native void nativeGetCharArrayBounds(long nativePaint,
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 76dd1c8..3a32e80 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1023,6 +1023,9 @@
             drawable = new StateListDrawable();
         } else if (name.equals("animated-selector")) {
             drawable = new AnimatedStateListDrawable();
+        } else if (name.equals("quantum-progress")) {
+            // TODO: Replace this with something less ridiculous.
+            drawable = new QuantumProgressDrawable();
         } else if (name.equals("level-list")) {
             drawable = new LevelListDrawable();
         } else if (name.equals("layer-list")) {
diff --git a/graphics/java/android/graphics/drawable/QuantumProgressDrawable.java b/graphics/java/android/graphics/drawable/QuantumProgressDrawable.java
new file mode 100644
index 0000000..d756eb1
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/QuantumProgressDrawable.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2014 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.graphics.drawable;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Paint.Cap;
+import android.graphics.Paint.Style;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Fancy progress indicator for Quantum theme.
+ *
+ * TODO: Replace this class with something less ridiculous.
+ */
+class QuantumProgressDrawable extends Drawable implements Animatable {
+    private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+    private static final TimeInterpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator();
+    private static final TimeInterpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator();
+
+    /** The list of animators operating on this drawable. */
+    private final ArrayList<Animator> mAnimators = new ArrayList<Animator>();
+
+    /** The indicator ring, used to manage animation state. */
+    private final Ring mRing;
+
+    private QuantumProgressState mState;
+
+    private boolean mMutated;
+
+    public QuantumProgressDrawable() {
+        this(new QuantumProgressState(null), null);
+    }
+
+    private QuantumProgressDrawable(QuantumProgressState state, Theme theme) {
+        mState = state;
+        if (theme != null && state.canApplyTheme()) {
+            applyTheme(theme);
+        }
+
+        mRing = new Ring(mCallback);
+        mMutated = false;
+
+        initializeFromState();
+        setupAnimators();
+    }
+
+    private void initializeFromState() {
+        final QuantumProgressState state = mState;
+
+        final Ring ring = mRing;
+        ring.setStrokeWidth(state.mStrokeWidth);
+
+        final int color = state.mColor.getColorForState(getState(), Color.TRANSPARENT);
+        ring.setColor(color);
+
+        final float minEdge = Math.min(state.mWidth, state.mHeight);
+        if (state.mInnerRadius <= 0 || minEdge < 0) {
+            ring.setInsets((int) Math.ceil(state.mStrokeWidth / 2.0f));
+        } else {
+            float insets = minEdge / 2.0f - state.mInnerRadius;
+            ring.setInsets(insets);
+        }
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mState = new QuantumProgressState(mState);
+            mMutated = true;
+        }
+        return this;
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        boolean changed = super.onStateChange(state);
+
+        final int color = mState.mColor.getColorForState(state, Color.TRANSPARENT);
+        if (color != mRing.getColor()) {
+            mRing.setColor(color);
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    @Override
+    public boolean isStateful() {
+        return super.isStateful() || mState.mColor.isStateful();
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.QuantumProgressDrawable);
+        super.inflateWithAttributes(r, parser, a, R.styleable.QuantumProgressDrawable_visible);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        initializeFromState();
+    }
+
+    @Override
+    public void applyTheme(Theme t) {
+        final TypedArray a = t.resolveAttributes(mState.mThemeAttrs,
+                R.styleable.QuantumProgressDrawable);
+        updateStateFromTypedArray(a);
+        a.recycle();
+    }
+
+    private void updateStateFromTypedArray(TypedArray a) {
+        final QuantumProgressState state = mState;
+        state.mThemeAttrs = a.extractThemeAttrs();
+        state.mWidth = a.getDimensionPixelSize(
+                R.styleable.QuantumProgressDrawable_width, state.mWidth);
+        state.mHeight = a.getDimensionPixelSize(
+                R.styleable.QuantumProgressDrawable_height, state.mHeight);
+        state.mInnerRadius = a.getDimension(
+                R.styleable.QuantumProgressDrawable_innerRadius, state.mInnerRadius);
+        state.mStrokeWidth = a.getDimension(
+                R.styleable.QuantumProgressDrawable_thickness, state.mStrokeWidth);
+
+        if (a.hasValue(R.styleable.QuantumProgressDrawable_color)) {
+            state.mColor = a.getColorStateList(R.styleable.QuantumProgressDrawable_color);
+        }
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        boolean changed = super.setVisible(visible, restart);
+        if (visible) {
+            if (changed || restart) {
+                start();
+            }
+        } else {
+            stop();
+        }
+        return changed;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mState.mHeight;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mState.mWidth;
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        mRing.draw(c, getBounds());
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mRing.setAlpha(alpha);
+    }
+
+    @Override
+    public int getAlpha() {
+        return mRing.getAlpha();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mRing.setColorFilter(colorFilter);
+    }
+
+    @Override
+    public ColorFilter getColorFilter() {
+        return mRing.getColorFilter();
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public boolean isRunning() {
+        final ArrayList<Animator> animators = mAnimators;
+        final int N = animators.size();
+        for (int i = 0; i < N; i++) {
+            final Animator animator = animators.get(i);
+            if (animator.isRunning()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void start() {
+        final ArrayList<Animator> animators = mAnimators;
+        final int N = animators.size();
+        for (int i = 0; i < N; i++) {
+            final Animator animator = animators.get(i);
+            if (animator.isPaused()) {
+                animator.resume();
+            } else if (!animator.isRunning()){
+                animator.start();
+            }
+        }
+    }
+
+    @Override
+    public void stop() {
+        final ArrayList<Animator> animators = mAnimators;
+        final int N = animators.size();
+        for (int i = 0; i < N; i++) {
+            final Animator animator = animators.get(i);
+            animator.pause();
+        }
+    }
+
+    private void setupAnimators() {
+        final Ring ring = mRing;
+
+        final ObjectAnimator endTrim = ObjectAnimator.ofFloat(ring, "endTrim", 0, 0.75f);
+        endTrim.setDuration(1000 * 80 / 60);
+        endTrim.setInterpolator(START_CURVE_INTERPOLATOR);
+        endTrim.setRepeatCount(ObjectAnimator.INFINITE);
+        endTrim.setRepeatMode(ObjectAnimator.RESTART);
+
+        final ObjectAnimator startTrim = ObjectAnimator.ofFloat(ring, "startTrim", 0.0f, 0.75f);
+        startTrim.setDuration(1000 * 80 / 60);
+        startTrim.setInterpolator(END_CURVE_INTERPOLATOR);
+        startTrim.setRepeatCount(ObjectAnimator.INFINITE);
+        startTrim.setRepeatMode(ObjectAnimator.RESTART);
+
+        final ObjectAnimator rotation = ObjectAnimator.ofFloat(ring, "rotation", 0.0f, 0.25f);
+        rotation.setDuration(1000 * 80 / 60);
+        rotation.setInterpolator(LINEAR_INTERPOLATOR);
+        rotation.setRepeatCount(ObjectAnimator.INFINITE);
+        rotation.setRepeatMode(ObjectAnimator.RESTART);
+
+        mAnimators.add(endTrim);
+        mAnimators.add(startTrim);
+        mAnimators.add(rotation);
+    }
+
+    private final Callback mCallback = new Callback() {
+        @Override
+        public void invalidateDrawable(Drawable d) {
+            invalidateSelf();
+        }
+
+        @Override
+        public void scheduleDrawable(Drawable d, Runnable what, long when) {
+            scheduleSelf(what, when);
+        }
+
+        @Override
+        public void unscheduleDrawable(Drawable d, Runnable what) {
+            unscheduleSelf(what);
+        }
+    };
+
+    private static class QuantumProgressState extends ConstantState {
+        private int[] mThemeAttrs = null;
+        private float mStrokeWidth = 5.0f;
+        private float mInnerRadius = -1.0f;
+        private int mWidth = -1;
+        private int mHeight = -1;
+        private ColorStateList mColor = ColorStateList.valueOf(Color.TRANSPARENT);
+
+        public QuantumProgressState(QuantumProgressState orig) {
+            if (orig != null) {
+                mThemeAttrs = orig.mThemeAttrs;
+                mStrokeWidth = orig.mStrokeWidth;
+                mInnerRadius = orig.mInnerRadius;
+                mWidth = orig.mWidth;
+                mHeight = orig.mHeight;
+                mColor = orig.mColor;
+            }
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null;
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return newDrawable(null, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return newDrawable(res, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res, Theme theme) {
+            return new QuantumProgressDrawable(this, theme);
+        }
+
+        @Override
+        public int getChangingConfigurations() {
+            return 0;
+        }
+    }
+
+    private static class Ring {
+        private final RectF mTempBounds = new RectF();
+        private final Paint mPaint = new Paint();
+
+        private final Callback mCallback;
+
+        private float mStartTrim = 0.0f;
+        private float mEndTrim = 0.0f;
+        private float mRotation = 0.0f;
+        private float mStrokeWidth = 5.0f;
+        private float mStrokeInset = 2.5f;
+
+        private int mAlpha = 0xFF;
+        private int mColor = Color.BLACK;
+
+        public Ring(Callback callback) {
+            mCallback = callback;
+
+            mPaint.setStrokeCap(Cap.ROUND);
+            mPaint.setAntiAlias(true);
+            mPaint.setStyle(Style.STROKE);
+        }
+
+        public void draw(Canvas c, Rect bounds) {
+            final RectF arcBounds = mTempBounds;
+            arcBounds.set(bounds);
+            arcBounds.inset(mStrokeInset, mStrokeInset);
+
+            final float startAngle = (mStartTrim + mRotation) * 360;
+            final float endAngle = (mEndTrim + mRotation) * 360;
+            float sweepAngle = endAngle - startAngle;
+
+            // Ensure the sweep angle isn't too small to draw.
+            final float diameter = Math.min(arcBounds.width(), arcBounds.height());
+            final float minAngle = (float) (360.0 / (diameter * Math.PI));
+            if (sweepAngle < minAngle && sweepAngle > -minAngle) {
+                sweepAngle = Math.signum(sweepAngle) * minAngle;
+            }
+
+            c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
+        }
+
+        public void setColorFilter(ColorFilter filter) {
+            mPaint.setColorFilter(filter);
+            invalidateSelf();
+        }
+
+        public ColorFilter getColorFilter() {
+            return mPaint.getColorFilter();
+        }
+
+        public void setAlpha(int alpha) {
+            mAlpha = alpha;
+            mPaint.setColor(mColor & 0xFFFFFF | alpha << 24);
+            invalidateSelf();
+        }
+
+        public int getAlpha() {
+            return mAlpha;
+        }
+
+        public void setColor(int color) {
+            mColor = color;
+            mPaint.setColor(color & 0xFFFFFF | mAlpha << 24);
+            invalidateSelf();
+        }
+
+        public int getColor() {
+            return mColor;
+        }
+
+        public void setStrokeWidth(float strokeWidth) {
+            mStrokeWidth = strokeWidth;
+            mPaint.setStrokeWidth(strokeWidth);
+            invalidateSelf();
+        }
+
+        @SuppressWarnings("unused")
+        public float getStrokeWidth() {
+            return mStrokeWidth;
+        }
+
+        @SuppressWarnings("unused")
+        public void setStartTrim(float startTrim) {
+            mStartTrim = startTrim;
+            invalidateSelf();
+        }
+
+        @SuppressWarnings("unused")
+        public float getStartTrim() {
+            return mStartTrim;
+        }
+
+        @SuppressWarnings("unused")
+        public void setEndTrim(float endTrim) {
+            mEndTrim = endTrim;
+            invalidateSelf();
+        }
+
+        @SuppressWarnings("unused")
+        public float getEndTrim() {
+            return mEndTrim;
+        }
+
+        @SuppressWarnings("unused")
+        public void setRotation(float rotation) {
+            mRotation = rotation;
+            invalidateSelf();
+        }
+
+        @SuppressWarnings("unused")
+        public float getRotation() {
+            return mRotation;
+        }
+
+        public void setInsets(float insets) {
+            mStrokeInset = insets;
+        }
+
+        @SuppressWarnings("unused")
+        public float getInsets() {
+            return mStrokeInset;
+        }
+
+        private void invalidateSelf() {
+            mCallback.invalidateDrawable(null);
+        }
+    }
+
+    /**
+     * Squishes the interpolation curve into the second half of the animation.
+     */
+    private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator {
+        @Override
+        public float getInterpolation(float input) {
+            return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f));
+        }
+    }
+
+    /**
+     * Squishes the interpolation curve into the first half of the animation.
+     */
+    private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator {
+        @Override
+        public float getInterpolation(float input) {
+            return super.getInterpolation(Math.min(1, input * 2.0f));
+        }
+    }
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 8ca303b..ce9fbb1 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3184,15 +3184,11 @@
                 do {
                     newPorts.clear();
                     status = AudioSystem.listAudioPorts(newPorts, portGeneration);
-                    Log.i(TAG, "updateAudioPortCache AudioSystem.listAudioPorts() status: "+
-                                    status+" num ports: "+ newPorts.size() +" portGeneration: "+portGeneration[0]);
                     if (status != SUCCESS) {
                         return status;
                     }
                     newPatches.clear();
                     status = AudioSystem.listAudioPatches(newPatches, patchGeneration);
-                    Log.i(TAG, "updateAudioPortCache AudioSystem.listAudioPatches() status: "+
-                            status+" num patches: "+ newPatches.size() +" patchGeneration: "+patchGeneration[0]);
                     if (status != SUCCESS) {
                         return status;
                     }
@@ -3200,14 +3196,16 @@
 
                 for (int i = 0; i < newPatches.size(); i++) {
                     for (int j = 0; j < newPatches.get(i).sources().length; j++) {
-                        AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sources()[j], newPorts);
+                        AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sources()[j],
+                                                                   newPorts);
                         if (portCfg == null) {
                             return ERROR;
                         }
                         newPatches.get(i).sources()[j] = portCfg;
                     }
                     for (int j = 0; j < newPatches.get(i).sinks().length; j++) {
-                        AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sinks()[j], newPorts);
+                        AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sinks()[j],
+                                                                   newPorts);
                         if (portCfg == null) {
                             return ERROR;
                         }
@@ -3238,8 +3236,6 @@
             // compare handles because the port returned by JNI is not of the correct
             // subclass
             if (ports.get(k).handle().equals(port.handle())) {
-                Log.i(TAG, "updatePortConfig match found for port handle: "+
-                            port.handle().id()+" port: "+ k);
                 port = ports.get(k);
                 break;
             }
diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java
index fbd5022..8b748422 100644
--- a/media/java/android/media/AudioPort.java
+++ b/media/java/android/media/AudioPort.java
@@ -133,7 +133,7 @@
      * Get the gain descriptor at a given index
      */
     AudioGain gain(int index) {
-        if (index < mGains.length) {
+        if (index < 0 || index >= mGains.length) {
             return null;
         }
         return mGains[index];
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index fa85234..8680786 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -454,7 +454,9 @@
     public void onBackPressed() {
         // Unfilter any stacks
         if (!mRecentsView.unfilterFilteredStacks()) {
-            super.onBackPressed();
+            if (!mRecentsView.launchFirstTask()) {
+                super.onBackPressed();
+            }
         }
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 2eee853..a8645bc 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -46,6 +46,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -213,7 +214,7 @@
     private int mTitleColor = 0;
 
     private boolean mAlwaysReadCloseOnTouchAttr = false;
-    
+
     private ContextMenuBuilder mContextMenu;
     private MenuDialogHelper mContextMenuHelper;
     private boolean mClosingActionMenu;
@@ -475,7 +476,7 @@
         if (st.isPrepared) {
             return true;
         }
-        
+
         if ((mPreparedPanel != null) && (mPreparedPanel != st)) {
             // Another Panel is prepared and possibly open, so close it
             closePanel(mPreparedPanel, false);
@@ -528,7 +529,7 @@
 
                     return false;
                 }
-                
+
                 st.refreshMenuContent = false;
             }
 
@@ -620,7 +621,7 @@
 
         // Causes the decor view to be recreated
         st.refreshDecorView = true;
-        
+
         st.clearMenuPresenters();
     }
 
@@ -872,7 +873,7 @@
         }
         st.refreshMenuContent = true;
         st.refreshDecorView = true;
-        
+
         // Prepare the options panel if we have an action bar
         if ((featureId == FEATURE_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL)
                 && mDecorContentParent != null) {
@@ -883,7 +884,7 @@
             }
         }
     }
-    
+
     /**
      * Called when the panel key is pushed down.
      * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}.
@@ -892,7 +893,7 @@
      */
     public final boolean onKeyDownPanel(int featureId, KeyEvent event) {
         final int keyCode = event.getKeyCode();
-        
+
         if (event.getRepeatCount() == 0) {
             // The panel key was pushed, so set the chording key
             mPanelChordingKey = keyCode;
@@ -919,7 +920,7 @@
             if (event.isCanceled() || (mDecor != null && mDecor.mActionMode != null)) {
                 return;
             }
-            
+
             boolean playSoundEffect = false;
             final PanelFeatureState st = getPanelState(featureId, true);
             if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
@@ -1159,22 +1160,40 @@
     protected boolean initializePanelMenu(final PanelFeatureState st) {
         Context context = getContext();
 
-        // If we have an action bar, initialize the menu with a context themed for it.
+        // If we have an action bar, initialize the menu with the right theme.
         if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR) &&
                 mDecorContentParent != null) {
-            TypedValue outValue = new TypedValue();
-            Resources.Theme currentTheme = context.getTheme();
-            currentTheme.resolveAttribute(com.android.internal.R.attr.actionBarWidgetTheme,
-                    outValue, true);
-            final int targetThemeRes = outValue.resourceId;
+            final TypedValue outValue = new TypedValue();
+            final Theme baseTheme = context.getTheme();
+            baseTheme.resolveAttribute(com.android.internal.R.attr.actionBarTheme, outValue, true);
 
-            if (targetThemeRes != 0 && context.getThemeResId() != targetThemeRes) {
-                context = new ContextThemeWrapper(context, targetThemeRes);
+            Theme widgetTheme = null;
+            if (outValue.resourceId != 0) {
+                widgetTheme = context.getResources().newTheme();
+                widgetTheme.setTo(baseTheme);
+                widgetTheme.applyStyle(outValue.resourceId, true);
+                widgetTheme.resolveAttribute(
+                        com.android.internal.R.attr.actionBarWidgetTheme, outValue, true);
+            } else {
+                baseTheme.resolveAttribute(
+                        com.android.internal.R.attr.actionBarWidgetTheme, outValue, true);
+            }
+
+            if (outValue.resourceId != 0) {
+                if (widgetTheme == null) {
+                    widgetTheme = context.getResources().newTheme();
+                    widgetTheme.setTo(baseTheme);
+                }
+                widgetTheme.applyStyle(outValue.resourceId, true);
+            }
+
+            if (widgetTheme != null) {
+                context = new ContextThemeWrapper(context, 0);
+                context.getTheme().setTo(widgetTheme);
             }
         }
 
         final MenuBuilder menu = new MenuBuilder(context);
-
         menu.setCallback(this);
         st.setMenu(menu);
 
@@ -1665,7 +1684,7 @@
                 mDecor != null ? mDecor.getKeyDispatcherState() : null;
         //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount()
         //        + " flags=0x" + Integer.toHexString(event.getFlags()));
-        
+
         switch (keyCode) {
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -1727,7 +1746,7 @@
         }
         //Log.i(TAG, "Key up: repeat=" + event.getRepeatCount()
         //        + " flags=0x" + Integer.toHexString(event.getFlags()));
-        
+
         switch (keyCode) {
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -2278,7 +2297,7 @@
             if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
                 return;
             }
- 
+
             // if we are showing a feature that should be announced and one child
             // make this child the event source since this is the feature itself
             // otherwise the callback will take over and announce its client
@@ -2829,13 +2848,13 @@
             hackTurnOffWindowResizeAnim(bg == null || bg.getOpacity()
                     != PixelFormat.OPAQUE);
         }
-        
+
         @Override
         protected void onAttachedToWindow() {
             super.onAttachedToWindow();
-            
+
             updateWindowResizeState();
-            
+
             final Callback cb = getCallback();
             if (cb != null && !isDestroyed() && mFeatureId < 0) {
                 cb.onAttachedToWindow();
@@ -2856,7 +2875,7 @@
         @Override
         protected void onDetachedFromWindow() {
             super.onDetachedFromWindow();
-            
+
             final Callback cb = getCallback();
             if (cb != null && mFeatureId < 0) {
                 cb.onDetachedFromWindow();
@@ -2890,19 +2909,19 @@
         public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
             return mFeatureId < 0 ? mTakeSurfaceCallback : null;
         }
-        
+
         public InputQueue.Callback willYouTakeTheInputQueue() {
             return mFeatureId < 0 ? mTakeInputQueueCallback : null;
         }
-        
+
         public void setSurfaceType(int type) {
             PhoneWindow.this.setType(type);
         }
-        
+
         public void setSurfaceFormat(int format) {
             PhoneWindow.this.setFormat(format);
         }
-        
+
         public void setSurfaceKeepScreenOn(boolean keepOn) {
             if (keepOn) PhoneWindow.this.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
             else PhoneWindow.this.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -3119,7 +3138,7 @@
                 setCloseOnTouchOutsideIfNotSet(true);
             }
         }
-        
+
         WindowManager.LayoutParams params = getAttributes();
 
         if (!hasSoftInputMode()) {
@@ -3897,11 +3916,11 @@
         boolean refreshDecorView;
 
         boolean refreshMenuContent;
-        
+
         boolean wasLastOpen;
-        
+
         boolean wasLastExpanded;
-        
+
         /**
          * Contains the state of the menu when told to freeze.
          */
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 3146e16..684fd9f 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -91,6 +91,7 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
+import android.view.WindowManagerInternal;
 import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -102,14 +103,17 @@
 import com.android.internal.policy.IKeyguardServiceConstants;
 import com.android.internal.policy.PolicyManager;
 import com.android.internal.policy.impl.keyguard.KeyguardServiceDelegate;
+import com.android.internal.policy.impl.keyguard.KeyguardServiceDelegate.ShowListener;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.widget.PointerLocationView;
+import com.android.server.LocalServices;
 
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashSet;
 
 import static android.view.WindowManager.LayoutParams.*;
@@ -131,6 +135,7 @@
     static final boolean DEBUG_LAYOUT = false;
     static final boolean DEBUG_INPUT = false;
     static final boolean DEBUG_STARTING_WINDOW = false;
+    static final boolean DEBUG_WAKEUP = false;
     static final boolean SHOW_STARTING_ANIMATIONS = true;
     static final boolean SHOW_PROCESSES_ON_ALT_MENU = false;
 
@@ -223,6 +228,7 @@
     Context mContext;
     IWindowManager mWindowManager;
     WindowManagerFuncs mWindowManagerFuncs;
+    WindowManagerInternal mWindowManagerInternal;
     PowerManager mPowerManager;
     IStatusBarService mStatusBarService;
     boolean mPreloadedRecentApps;
@@ -264,6 +270,25 @@
     int[] mNavigationBarWidthForRotation = new int[4];
 
     KeyguardServiceDelegate mKeyguardDelegate;
+    // The following are only accessed on the mHandler thread.
+    boolean mKeyguardDrawComplete;
+    boolean mWindowManagerDrawComplete;
+    ArrayList<ScreenOnListener> mScreenOnListeners = new ArrayList<ScreenOnListener>();
+    final IRemoteCallback mWindowManagerDrawCallback = new IRemoteCallback.Stub() {
+        @Override
+        public void sendResult(Bundle data) {
+            if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display!");
+            mHandler.sendEmptyMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE);
+        }
+    };
+    final ShowListener mKeyguardDelegateCallback = new ShowListener() {
+        @Override
+        public void onShown(IBinder windowToken) {
+            if (DEBUG_WAKEUP) Slog.d(TAG, "mKeyguardDelegate.ShowListener.onShown.");
+            mHandler.sendEmptyMessage(MSG_KEYGUARD_DRAWN_COMPLETE);
+        }
+    };
+
     GlobalActions mGlobalActions;
     volatile boolean mPowerKeyHandled; // accessed from input reader and handler thread
     boolean mPendingPowerKeyUpCanceled;
@@ -483,6 +508,10 @@
     private static final int MSG_DISABLE_POINTER_LOCATION = 2;
     private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3;
     private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4;
+    private static final int MSG_KEYGUARD_DRAWN_COMPLETE = 5;
+    private static final int MSG_KEYGUARD_DRAWN_TIMEOUT = 6;
+    private static final int MSG_WINDOW_MANAGER_DRAWN_COMPLETE = 7;
+    private static final int MSG_WAKING_UP = 8;
 
     private class PolicyHandler extends Handler {
         @Override
@@ -500,6 +529,25 @@
                 case MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK:
                     dispatchMediaKeyRepeatWithWakeLock((KeyEvent)msg.obj);
                     break;
+                case MSG_KEYGUARD_DRAWN_COMPLETE:
+                    if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mKeyguardDrawComplete");
+                    mKeyguardDrawComplete = true;
+                    finishScreenTurningOn();
+                    break;
+                case MSG_KEYGUARD_DRAWN_TIMEOUT:
+                    Slog.w(TAG, "Keyguard drawn timeout. Setting mKeyguardDrawComplete");
+                    mKeyguardDrawComplete = true;
+                    finishScreenTurningOn();
+                    break;
+                case MSG_WINDOW_MANAGER_DRAWN_COMPLETE:
+                    if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mWindowManagerDrawComplete");
+                    mHandler.removeMessages(MSG_KEYGUARD_DRAWN_TIMEOUT);
+                    mWindowManagerDrawComplete = true;
+                    finishScreenTurningOn();
+                    break;
+                case MSG_WAKING_UP:
+                    handleWakingUp((ScreenOnListener) msg.obj);
+                    break;
             }
         }
     }
@@ -855,6 +903,8 @@
         mContext = context;
         mWindowManager = windowManager;
         mWindowManagerFuncs = windowManagerFuncs;
+        mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+
         mHandler = new PolicyHandler();
         mOrientationListener = new MyOrientationListener(mContext, mHandler);
         try {
@@ -4419,10 +4469,15 @@
     @Override
     public void wakingUp(final ScreenOnListener screenOnListener) {
         EventLog.writeEvent(70000, 1);
-        if (false) {
-            RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
-            Slog.i(TAG, "Screen turning on...", here);
+        if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turning on...",
+                new RuntimeException("here").fillInStackTrace());
+        mHandler.obtainMessage(MSG_WAKING_UP, screenOnListener).sendToTarget();
+    }
+
+    // Called on the mHandler thread.
+    private void handleWakingUp(final ScreenOnListener screenOnListener) {
+        if (screenOnListener != null) {
+            mScreenOnListeners.add(screenOnListener);
         }
 
         synchronized (mLock) {
@@ -4431,51 +4486,28 @@
             updateLockScreenTimeout();
         }
 
-        waitForKeyguard(screenOnListener);
-    }
-
-    private void waitForKeyguard(final ScreenOnListener screenOnListener) {
+        mKeyguardDrawComplete = false;
+        mWindowManagerDrawComplete = false;
         if (mKeyguardDelegate != null) {
-            mKeyguardDelegate.onScreenTurnedOn(new KeyguardServiceDelegate.ShowListener() {
-                @Override
-                public void onShown(IBinder windowToken) {
-                    waitForKeyguardWindowDrawn(windowToken, screenOnListener);
-                }
-            });
+            mHandler.removeMessages(MSG_KEYGUARD_DRAWN_TIMEOUT);
+            mHandler.sendEmptyMessageDelayed(MSG_KEYGUARD_DRAWN_TIMEOUT, 1000);
+            mKeyguardDelegate.onScreenTurnedOn(mKeyguardDelegateCallback);
         } else {
-            Slog.i(TAG, "No keyguard interface!");
-            finishScreenTurningOn(screenOnListener);
+            if (DEBUG_WAKEUP) Slog.d(TAG, "null mKeyguardDelegate: setting mKeyguardDrawComplete.");
+            mKeyguardDrawComplete = true;
         }
+        mWindowManagerInternal.waitForAllWindowsDrawn(mWindowManagerDrawCallback, 500);
     }
 
-    private void waitForKeyguardWindowDrawn(IBinder windowToken,
-            final ScreenOnListener screenOnListener) {
-        if (windowToken != null && !mHideLockScreen) {
-            try {
-                if (mWindowManager.waitForWindowDrawn(
-                        windowToken, new IRemoteCallback.Stub() {
-                    @Override
-                    public void sendResult(Bundle data) {
-                        Slog.i(TAG, "Lock screen displayed!");
-                        finishScreenTurningOn(screenOnListener);
-                        setKeyguardDrawn();
-                    }
-                })) {
-                    return;
-                }
-                Slog.i(TAG, "No lock screen! waitForWindowDrawn false");
-
-            } catch (RemoteException ex) {
-                // Can't happen in system process.
-            }
+    // Called on the mHandler thread.
+    private void finishScreenTurningOn() {
+        if (DEBUG_WAKEUP) Slog.d(TAG,
+                "finishScreenTurningOn: mKeyguardDrawComplete=" + mKeyguardDrawComplete
+                        + " mWindowManagerDrawComplete=" + mWindowManagerDrawComplete);
+        if (!mKeyguardDrawComplete || !mWindowManagerDrawComplete) {
+            return;
         }
 
-        Slog.i(TAG, "No lock screen! windowToken=" + windowToken);
-        finishScreenTurningOn(screenOnListener);
-        setKeyguardDrawn();
-    }
-
-    private void finishScreenTurningOn(ScreenOnListener screenOnListener) {
         synchronized (mLock) {
             mScreenOnFully = true;
         }
@@ -4485,9 +4517,11 @@
         } catch (RemoteException unhandled) {
         }
 
-        if (screenOnListener != null) {
-            screenOnListener.onScreenOn();
+        for (int i = mScreenOnListeners.size() - 1; i >=0; --i) {
+            mScreenOnListeners.remove(i).onScreenOn();
         }
+
+        setKeyguardDrawn();
     }
 
     @Override
@@ -4860,7 +4894,7 @@
         synchronized (mLock) {
             mSystemBooted = true;
         }
-        waitForKeyguard(null);
+        wakingUp(null);
     }
 
     ProgressDialog mBootMsgDialog = null;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b2b4217..abb8cc5 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1754,31 +1754,34 @@
             if (DBG) log("requestRouteToHostAddress on invalid network: " + networkType);
             return false;
         }
-        NetworkStateTracker tracker = mNetTrackers[networkType];
-        DetailedState netState = DetailedState.DISCONNECTED;
-        if (tracker != null) {
-            netState = tracker.getNetworkInfo().getDetailedState();
+
+        NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+        if (nai == null) {
+            if (mLegacyTypeTracker.isTypeSupported(networkType) == false) {
+                if (DBG) log("requestRouteToHostAddress on unsupported network: " + networkType);
+            } else {
+                if (DBG) log("requestRouteToHostAddress on down network: " + networkType);
+            }
+            return false;
         }
 
+        DetailedState netState = nai.networkInfo.getDetailedState();
+
         if ((netState != DetailedState.CONNECTED &&
-                netState != DetailedState.CAPTIVE_PORTAL_CHECK) ||
-                tracker.isTeardownRequested()) {
+                netState != DetailedState.CAPTIVE_PORTAL_CHECK)) {
             if (VDBG) {
                 log("requestRouteToHostAddress on down network "
                         + "(" + networkType + ") - dropped"
-                        + " tracker=" + tracker
-                        + " netState=" + netState
-                        + " isTeardownRequested="
-                            + ((tracker != null) ? tracker.isTeardownRequested() : "tracker:null"));
+                        + " netState=" + netState);
             }
             return false;
         }
         final int uid = Binder.getCallingUid();
         final long token = Binder.clearCallingIdentity();
         try {
-            LinkProperties lp = tracker.getLinkProperties();
+            LinkProperties lp = nai.linkProperties;
             boolean ok = modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt,
-                    tracker.getNetwork().netId, uid);
+                    nai.network.netId, uid);
             if (DBG) log("requestRouteToHostAddress ok=" + ok);
             return ok;
         } finally {
@@ -3316,6 +3319,7 @@
         if (bestNetwork != null) {
             if (VDBG) log("using " + bestNetwork.name());
             bestNetwork.addRequest(nri.request);
+            mNetworkForRequestId.put(nri.request.requestId, bestNetwork);
             int legacyType = nri.request.legacyType;
             if (legacyType != TYPE_NONE) {
                 mLegacyTypeTracker.add(legacyType, bestNetwork);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index b492edd..eb253eb 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -669,6 +669,7 @@
         int flags = 0;
         boolean isCheckin = false;
         boolean noOutput = false;
+        boolean writeData = false;
         long historyStart = -1;
         int reqUid = -1;
         if (args != null) {
@@ -687,6 +688,7 @@
                         return;
                     }
                     historyStart = Long.parseLong(args[i]);
+                    writeData = true;
                 } else if ("-c".equals(arg)) {
                     isCheckin = true;
                     flags |= BatteryStats.DUMP_INCLUDE_HISTORY;
@@ -749,10 +751,16 @@
             List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0);
             synchronized (mStats) {
                 mStats.dumpCheckinLocked(mContext, pw, apps, flags, historyStart);
+                if (writeData) {
+                    mStats.writeAsyncLocked();
+                }
             }
         } else {
             synchronized (mStats) {
                 mStats.dumpLocked(mContext, pw, flags, reqUid, historyStart);
+                if (writeData) {
+                    mStats.writeAsyncLocked();
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7382f4ca..da584d8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -407,8 +407,11 @@
     /**
      * Windows that clients are waiting to have drawn.
      */
-    ArrayList<Pair<WindowState, IRemoteCallback>> mWaitingForDrawn
-            = new ArrayList<Pair<WindowState, IRemoteCallback>>();
+    ArrayList<WindowState> mWaitingForDrawn = new ArrayList<WindowState>();
+    /**
+     * And the callback to make when they've all been drawn.
+     */
+    IRemoteCallback mWaitingForDrawnCallback;
 
     /**
      * Windows that have called relayout() while we were running animations,
@@ -814,6 +817,7 @@
 
         mAnimator = new WindowAnimator(this);
 
+        LocalServices.addService(WindowManagerInternal.class, new LocalService());
         initPolicy();
 
         // Add ourself to the Watchdog monitors.
@@ -828,7 +832,6 @@
             SurfaceControl.closeTransaction();
         }
 
-        LocalServices.addService(WindowManagerInternal.class, new LocalService());
         showCircularDisplayMaskIfNeeded();
     }
 
@@ -7174,6 +7177,7 @@
         public static final int NOTIFY_ACTIVITY_DRAWN = 32;
 
         public static final int SHOW_DISPLAY_MASK = 33;
+        public static final int ALL_WINDOWS_DRAWN = 34;
 
         @Override
         public void handleMessage(Message msg) {
@@ -7550,17 +7554,18 @@
                 }
 
                 case WAITING_FOR_DRAWN_TIMEOUT: {
-                    Pair<WindowState, IRemoteCallback> pair;
+                    IRemoteCallback callback = null;
                     synchronized (mWindowMap) {
-                        pair = (Pair<WindowState, IRemoteCallback>)msg.obj;
-                        Slog.w(TAG, "Timeout waiting for drawn: " + pair.first);
-                        if (!mWaitingForDrawn.remove(pair)) {
-                            return;
-                        }
+                        Slog.w(TAG, "Timeout waiting for drawn: undrawn=" + mWaitingForDrawn);
+                        mWaitingForDrawn.clear();
+                        callback = mWaitingForDrawnCallback;
+                        mWaitingForDrawnCallback = null;
                     }
-                    try {
-                        pair.second.sendResult(null);
-                    } catch (RemoteException e) {
+                    if (callback != null) {
+                        try {
+                            callback.sendResult(null);
+                        } catch (RemoteException e) {
+                        }
                     }
                     break;
                 }
@@ -7618,6 +7623,19 @@
                     } catch (RemoteException e) {
                     }
                     break;
+                case ALL_WINDOWS_DRAWN: {
+                    IRemoteCallback callback;
+                    synchronized (mWindowMap) {
+                        callback = mWaitingForDrawnCallback;
+                        mWaitingForDrawnCallback = null;
+                    }
+                    if (callback != null) {
+                        try {
+                            callback.sendResult(null);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                }
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG, "handleMessage: exit");
@@ -9556,53 +9574,30 @@
     }
 
     void checkDrawnWindowsLocked() {
-        if (mWaitingForDrawn.size() > 0) {
-            for (int j=mWaitingForDrawn.size()-1; j>=0; j--) {
-                Pair<WindowState, IRemoteCallback> pair = mWaitingForDrawn.get(j);
-                WindowState win = pair.first;
-                //Slog.i(TAG, "Waiting for drawn " + win + ": removed="
-                //        + win.mRemoved + " visible=" + win.isVisibleLw()
-                //        + " shown=" + win.mSurfaceShown);
-                if (win.mRemoved) {
-                    // Window has been removed; no draw will now happen, so stop waiting.
-                    Slog.w(TAG, "Aborted waiting for drawn: " + pair.first);
-                    try {
-                        pair.second.sendResult(null);
-                    } catch (RemoteException e) {
-                    }
-                    mWaitingForDrawn.remove(pair);
-                    mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, pair);
-                } else if (win.mWinAnimator.mSurfaceShown) {
-                    // Window is now drawn (and shown).
-                    try {
-                        pair.second.sendResult(null);
-                    } catch (RemoteException e) {
-                    }
-                    mWaitingForDrawn.remove(pair);
-                    mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, pair);
-                }
+        if (mWaitingForDrawn.isEmpty() || mWaitingForDrawnCallback == null) {
+            return;
+        }
+        for (int j = mWaitingForDrawn.size() - 1; j >= 0; j--) {
+            WindowState win = mWaitingForDrawn.get(j);
+            if (DEBUG_SCREEN_ON) Slog.i(TAG, "Waiting for drawn " + win +
+                    ": removed=" + win.mRemoved + " visible=" + win.isVisibleLw() +
+                    " mHasSurface=" + win.mHasSurface +
+                    " drawState=" + win.mWinAnimator.mDrawState);
+            if (win.mRemoved || !win.mHasSurface) {
+                // Window has been removed; no draw will now happen, so stop waiting.
+                if (DEBUG_SCREEN_ON) Slog.w(TAG, "Aborted waiting for drawn: " + win);
+                mWaitingForDrawn.remove(win);
+            } else if (win.hasDrawnLw()) {
+                // Window is now drawn (and shown).
+                if (DEBUG_SCREEN_ON) Slog.d(TAG, "Window drawn win=" + win);
+                mWaitingForDrawn.remove(win);
             }
         }
-    }
-
-    @Override
-    public boolean waitForWindowDrawn(IBinder token, IRemoteCallback callback) {
-        if (token != null && callback != null) {
-            synchronized (mWindowMap) {
-                WindowState win = windowForClientLocked(null, token, true);
-                if (win != null) {
-                    Pair<WindowState, IRemoteCallback> pair =
-                            new Pair<WindowState, IRemoteCallback>(win, callback);
-                    Message m = mH.obtainMessage(H.WAITING_FOR_DRAWN_TIMEOUT, pair);
-                    mH.sendMessageDelayed(m, 2000);
-                    mWaitingForDrawn.add(pair);
-                    checkDrawnWindowsLocked();
-                    return true;
-                }
-                Slog.i(TAG, "waitForWindowDrawn: win null");
-            }
+        if (mWaitingForDrawn.isEmpty()) {
+            if (DEBUG_SCREEN_ON) Slog.d(TAG, "All windows drawn!");
+            mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
+            mH.sendEmptyMessage(H.ALL_WINDOWS_DRAWN);
         }
-        return false;
     }
 
     void setHoldScreenLocked(final Session newHoldScreen) {
@@ -10550,9 +10545,8 @@
             pw.println();
             pw.println("  Clients waiting for these windows to be drawn:");
             for (int i=mWaitingForDrawn.size()-1; i>=0; i--) {
-                Pair<WindowState, IRemoteCallback> pair = mWaitingForDrawn.get(i);
-                pw.print("  Waiting #"); pw.print(i); pw.print(' '); pw.print(pair.first);
-                        pw.print(": "); pw.println(pair.second);
+                WindowState win = mWaitingForDrawn.get(i);
+                pw.print("  Waiting #"); pw.print(i); pw.print(' '); pw.print(win);
             }
         }
         pw.println();
@@ -11062,7 +11056,7 @@
                 }
                 mAccessibilityController.setMagnificationCallbacksLocked(callbacks);
                 if (!mAccessibilityController.hasCallbacksLocked()) {
-                     mAccessibilityController = null;
+                    mAccessibilityController = null;
                 }
             }
         }
@@ -11076,7 +11070,7 @@
                 }
                 mAccessibilityController.setWindowsForAccessibilityCallback(callback);
                 if (!mAccessibilityController.hasCallbacksLocked()) {
-                     mAccessibilityController = null;
+                    mAccessibilityController = null;
                 }
             }
         }
@@ -11113,5 +11107,25 @@
                 }
             }
         }
+
+        public void waitForAllWindowsDrawn(IRemoteCallback callback, long timeout) {
+            synchronized (mWindowMap) {
+                mWaitingForDrawnCallback = callback;
+                final WindowList windows = getDefaultWindowListLocked();
+                for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
+                    final WindowState win = windows.get(winNdx);
+                    if (win.mHasSurface) {
+                        win.mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
+                        // Force add to mResizingWindows.
+                        win.mLastContentInsets.set(-1, -1, -1, -1);
+                        mWaitingForDrawn.add(win);
+                    }
+                }
+                requestTraversalLocked();
+                mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
+                mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, timeout);
+            }
+            checkDrawnWindowsLocked();
+        }
     }
 }
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 3bf2b20..cfe8e15 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -419,11 +419,6 @@
     }
 
     @Override
-    public boolean waitForWindowDrawn(IBinder token, IRemoteCallback callback) {
-        return false;
-    }
-
-    @Override
     public IBinder asBinder() {
         // TODO Auto-generated method stub
         return null;