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, ©right,
+ 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;