diff options
170 files changed, 6429 insertions, 5093 deletions
diff --git a/api/current.txt b/api/current.txt index df908e0..f5b524e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1863,28 +1863,28 @@ package android { 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 = 16974543; // 0x10302cf - field public static final int TextAppearance_Quantum_Body2 = 16974542; // 0x10302ce - field public static final int TextAppearance_Quantum_Button = 16974546; // 0x10302d2 - field public static final int TextAppearance_Quantum_Caption = 16974544; // 0x10302d0 + 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_DialogWindowTitle = 16974349; // 0x103020d - field public static final int TextAppearance_Quantum_Display1 = 16974538; // 0x10302ca - field public static final int TextAppearance_Quantum_Display2 = 16974537; // 0x10302c9 - field public static final int TextAppearance_Quantum_Display3 = 16974536; // 0x10302c8 - field public static final int TextAppearance_Quantum_Display4 = 16974535; // 0x10302c7 - field public static final int TextAppearance_Quantum_Headline = 16974539; // 0x10302cb + 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_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 = 16974545; // 0x10302d1 + field public static final int TextAppearance_Quantum_Menu = 16974547; // 0x10302d3 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 = 16974541; // 0x10302cd - field public static final int TextAppearance_Quantum_Title = 16974540; // 0x10302cc + 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_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 @@ -1931,11 +1931,11 @@ package android { field public static final int TextAppearance_Widget_TextView_SpinnerItem = 16973906; // 0x1030052 field public static final int TextAppearance_WindowTitle = 16973907; // 0x1030053 field public static final int Theme = 16973829; // 0x1030005 - field public static final int ThemeOverlay = 16974410; // 0x103024a - field public static final int ThemeOverlay_Quantum = 16974411; // 0x103024b - field public static final int ThemeOverlay_Quantum_ActionBarWidget = 16974414; // 0x103024e - field public static final int ThemeOverlay_Quantum_Dark = 16974413; // 0x103024d - field public static final int ThemeOverlay_Quantum_Light = 16974412; // 0x103024c + 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 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 @@ -2015,26 +2015,28 @@ package android { field public static final int Theme_Quantum_Dialog_NoActionBar = 16974385; // 0x1030231 field public static final int Theme_Quantum_Dialog_NoActionBar_MinWidth = 16974386; // 0x1030232 field public static final int Theme_Quantum_InputMethod = 16974389; // 0x1030235 - field public static final int Theme_Quantum_Light = 16974397; // 0x103023d - field public static final int Theme_Quantum_Light_DarkActionBar = 16974398; // 0x103023e - field public static final int Theme_Quantum_Light_Dialog = 16974399; // 0x103023f - field public static final int Theme_Quantum_Light_DialogWhenLarge = 16974403; // 0x1030243 - field public static final int Theme_Quantum_Light_DialogWhenLarge_NoActionBar = 16974404; // 0x1030244 - field public static final int Theme_Quantum_Light_Dialog_MinWidth = 16974400; // 0x1030240 - field public static final int Theme_Quantum_Light_Dialog_NoActionBar = 16974401; // 0x1030241 - field public static final int Theme_Quantum_Light_Dialog_NoActionBar_MinWidth = 16974402; // 0x1030242 - field public static final int Theme_Quantum_Light_NoActionBar = 16974405; // 0x1030245 - field public static final int Theme_Quantum_Light_NoActionBar_Fullscreen = 16974406; // 0x1030246 - field public static final int Theme_Quantum_Light_NoActionBar_Overscan = 16974407; // 0x1030247 - field public static final int Theme_Quantum_Light_NoActionBar_TranslucentDecor = 16974408; // 0x1030248 - field public static final int Theme_Quantum_Light_Panel = 16974409; // 0x1030249 + field public static final int Theme_Quantum_Light = 16974398; // 0x103023e + field public static final int Theme_Quantum_Light_DarkActionBar = 16974399; // 0x103023f + field public static final int Theme_Quantum_Light_Dialog = 16974400; // 0x1030240 + field public static final int Theme_Quantum_Light_DialogWhenLarge = 16974404; // 0x1030244 + field public static final int Theme_Quantum_Light_DialogWhenLarge_NoActionBar = 16974405; // 0x1030245 + field public static final int Theme_Quantum_Light_Dialog_MinWidth = 16974401; // 0x1030241 + field public static final int Theme_Quantum_Light_Dialog_NoActionBar = 16974402; // 0x1030242 + field public static final int Theme_Quantum_Light_Dialog_NoActionBar_MinWidth = 16974403; // 0x1030243 + field public static final int Theme_Quantum_Light_NoActionBar = 16974406; // 0x1030246 + field public static final int Theme_Quantum_Light_NoActionBar_Fullscreen = 16974407; // 0x1030247 + field public static final int Theme_Quantum_Light_NoActionBar_Overscan = 16974408; // 0x1030248 + field public static final int Theme_Quantum_Light_NoActionBar_TranslucentDecor = 16974409; // 0x1030249 + field public static final int Theme_Quantum_Light_Panel = 16974410; // 0x103024a + field public static final int Theme_Quantum_Light_Voice = 16974411; // 0x103024b field public static final int Theme_Quantum_NoActionBar = 16974390; // 0x1030236 field public static final int Theme_Quantum_NoActionBar_Fullscreen = 16974391; // 0x1030237 field public static final int Theme_Quantum_NoActionBar_Overscan = 16974392; // 0x1030238 field public static final int Theme_Quantum_NoActionBar_TranslucentDecor = 16974393; // 0x1030239 field public static final int Theme_Quantum_Panel = 16974394; // 0x103023a - field public static final int Theme_Quantum_Wallpaper = 16974395; // 0x103023b - field public static final int Theme_Quantum_Wallpaper_NoTitleBar = 16974396; // 0x103023c + field public static final int Theme_Quantum_Voice = 16974395; // 0x103023b + field public static final int Theme_Quantum_Wallpaper = 16974396; // 0x103023c + field public static final int Theme_Quantum_Wallpaper_NoTitleBar = 16974397; // 0x103023d field public static final int Theme_Translucent = 16973839; // 0x103000f field public static final int Theme_Translucent_NoTitleBar = 16973840; // 0x1030010 field public static final int Theme_Translucent_NoTitleBar_Fullscreen = 16973841; // 0x1030011 @@ -2323,126 +2325,126 @@ package android { 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 = 16974415; // 0x103024f - field public static final int Widget_Quantum_ActionBar = 16974416; // 0x1030250 - field public static final int Widget_Quantum_ActionBar_Solid = 16974417; // 0x1030251 - field public static final int Widget_Quantum_ActionBar_TabBar = 16974418; // 0x1030252 - field public static final int Widget_Quantum_ActionBar_TabText = 16974419; // 0x1030253 - field public static final int Widget_Quantum_ActionBar_TabView = 16974420; // 0x1030254 - field public static final int Widget_Quantum_ActionButton = 16974421; // 0x1030255 - field public static final int Widget_Quantum_ActionButton_CloseMode = 16974422; // 0x1030256 - field public static final int Widget_Quantum_ActionButton_Overflow = 16974423; // 0x1030257 - field public static final int Widget_Quantum_ActionMode = 16974424; // 0x1030258 - field public static final int Widget_Quantum_AutoCompleteTextView = 16974425; // 0x1030259 - field public static final int Widget_Quantum_Button = 16974426; // 0x103025a - field public static final int Widget_Quantum_ButtonBar = 16974432; // 0x1030260 - field public static final int Widget_Quantum_ButtonBar_AlertDialog = 16974433; // 0x1030261 - field public static final int Widget_Quantum_Button_Borderless = 16974427; // 0x103025b - field public static final int Widget_Quantum_Button_Borderless_Small = 16974428; // 0x103025c - field public static final int Widget_Quantum_Button_Inset = 16974429; // 0x103025d - field public static final int Widget_Quantum_Button_Small = 16974430; // 0x103025e - field public static final int Widget_Quantum_Button_Toggle = 16974431; // 0x103025f - field public static final int Widget_Quantum_CalendarView = 16974434; // 0x1030262 - field public static final int Widget_Quantum_CheckedTextView = 16974435; // 0x1030263 - field public static final int Widget_Quantum_CompoundButton_CheckBox = 16974436; // 0x1030264 - field public static final int Widget_Quantum_CompoundButton_RadioButton = 16974437; // 0x1030265 - field public static final int Widget_Quantum_CompoundButton_Star = 16974438; // 0x1030266 - field public static final int Widget_Quantum_DatePicker = 16974439; // 0x1030267 - field public static final int Widget_Quantum_DropDownItem = 16974440; // 0x1030268 - field public static final int Widget_Quantum_DropDownItem_Spinner = 16974441; // 0x1030269 - field public static final int Widget_Quantum_EditText = 16974442; // 0x103026a - field public static final int Widget_Quantum_ExpandableListView = 16974443; // 0x103026b - field public static final int Widget_Quantum_FastScroll = 16974444; // 0x103026c - field public static final int Widget_Quantum_GridView = 16974445; // 0x103026d - field public static final int Widget_Quantum_HorizontalScrollView = 16974446; // 0x103026e - field public static final int Widget_Quantum_ImageButton = 16974447; // 0x103026f - field public static final int Widget_Quantum_Light = 16974474; // 0x103028a - field public static final int Widget_Quantum_Light_ActionBar = 16974475; // 0x103028b - field public static final int Widget_Quantum_Light_ActionBar_Solid = 16974476; // 0x103028c - field public static final int Widget_Quantum_Light_ActionBar_TabBar = 16974477; // 0x103028d - field public static final int Widget_Quantum_Light_ActionBar_TabText = 16974478; // 0x103028e - field public static final int Widget_Quantum_Light_ActionBar_TabView = 16974479; // 0x103028f - field public static final int Widget_Quantum_Light_ActionButton = 16974480; // 0x1030290 - field public static final int Widget_Quantum_Light_ActionButton_CloseMode = 16974481; // 0x1030291 - field public static final int Widget_Quantum_Light_ActionButton_Overflow = 16974482; // 0x1030292 - field public static final int Widget_Quantum_Light_ActionMode = 16974483; // 0x1030293 - field public static final int Widget_Quantum_Light_AutoCompleteTextView = 16974484; // 0x1030294 - field public static final int Widget_Quantum_Light_Button = 16974485; // 0x1030295 - field public static final int Widget_Quantum_Light_ButtonBar = 16974491; // 0x103029b - field public static final int Widget_Quantum_Light_ButtonBar_AlertDialog = 16974492; // 0x103029c - field public static final int Widget_Quantum_Light_Button_Borderless = 16974486; // 0x1030296 - field public static final int Widget_Quantum_Light_Button_Borderless_Small = 16974487; // 0x1030297 - field public static final int Widget_Quantum_Light_Button_Inset = 16974488; // 0x1030298 - field public static final int Widget_Quantum_Light_Button_Small = 16974489; // 0x1030299 - field public static final int Widget_Quantum_Light_Button_Toggle = 16974490; // 0x103029a - field public static final int Widget_Quantum_Light_CalendarView = 16974493; // 0x103029d - field public static final int Widget_Quantum_Light_CheckedTextView = 16974494; // 0x103029e - field public static final int Widget_Quantum_Light_CompoundButton_CheckBox = 16974495; // 0x103029f - field public static final int Widget_Quantum_Light_CompoundButton_RadioButton = 16974496; // 0x10302a0 - field public static final int Widget_Quantum_Light_CompoundButton_Star = 16974497; // 0x10302a1 - field public static final int Widget_Quantum_Light_DropDownItem = 16974498; // 0x10302a2 - field public static final int Widget_Quantum_Light_DropDownItem_Spinner = 16974499; // 0x10302a3 - field public static final int Widget_Quantum_Light_EditText = 16974500; // 0x10302a4 - field public static final int Widget_Quantum_Light_ExpandableListView = 16974501; // 0x10302a5 - field public static final int Widget_Quantum_Light_FastScroll = 16974502; // 0x10302a6 - field public static final int Widget_Quantum_Light_GridView = 16974503; // 0x10302a7 - field public static final int Widget_Quantum_Light_HorizontalScrollView = 16974504; // 0x10302a8 - field public static final int Widget_Quantum_Light_ImageButton = 16974505; // 0x10302a9 - field public static final int Widget_Quantum_Light_ListPopupWindow = 16974506; // 0x10302aa - field public static final int Widget_Quantum_Light_ListView = 16974507; // 0x10302ab - field public static final int Widget_Quantum_Light_ListView_DropDown = 16974508; // 0x10302ac - field public static final int Widget_Quantum_Light_MediaRouteButton = 16974509; // 0x10302ad - field public static final int Widget_Quantum_Light_PopupMenu = 16974510; // 0x10302ae - field public static final int Widget_Quantum_Light_PopupMenu_Overflow = 16974511; // 0x10302af - field public static final int Widget_Quantum_Light_PopupWindow = 16974512; // 0x10302b0 - field public static final int Widget_Quantum_Light_ProgressBar = 16974513; // 0x10302b1 - field public static final int Widget_Quantum_Light_ProgressBar_Horizontal = 16974514; // 0x10302b2 - field public static final int Widget_Quantum_Light_ProgressBar_Inverse = 16974515; // 0x10302b3 - field public static final int Widget_Quantum_Light_ProgressBar_Large = 16974516; // 0x10302b4 - field public static final int Widget_Quantum_Light_ProgressBar_Large_Inverse = 16974517; // 0x10302b5 - field public static final int Widget_Quantum_Light_ProgressBar_Small = 16974518; // 0x10302b6 - field public static final int Widget_Quantum_Light_ProgressBar_Small_Inverse = 16974519; // 0x10302b7 - field public static final int Widget_Quantum_Light_ProgressBar_Small_Title = 16974520; // 0x10302b8 - field public static final int Widget_Quantum_Light_RatingBar = 16974521; // 0x10302b9 - field public static final int Widget_Quantum_Light_RatingBar_Indicator = 16974522; // 0x10302ba - field public static final int Widget_Quantum_Light_RatingBar_Small = 16974523; // 0x10302bb - field public static final int Widget_Quantum_Light_ScrollView = 16974524; // 0x10302bc - field public static final int Widget_Quantum_Light_SeekBar = 16974525; // 0x10302bd - field public static final int Widget_Quantum_Light_SegmentedButton = 16974526; // 0x10302be - field public static final int Widget_Quantum_Light_Spinner = 16974528; // 0x10302c0 - field public static final int Widget_Quantum_Light_StackView = 16974527; // 0x10302bf - field public static final int Widget_Quantum_Light_Tab = 16974529; // 0x10302c1 - field public static final int Widget_Quantum_Light_TabWidget = 16974530; // 0x10302c2 - field public static final int Widget_Quantum_Light_TextView = 16974531; // 0x10302c3 - field public static final int Widget_Quantum_Light_TextView_SpinnerItem = 16974532; // 0x10302c4 - field public static final int Widget_Quantum_Light_WebTextView = 16974533; // 0x10302c5 - field public static final int Widget_Quantum_Light_WebView = 16974534; // 0x10302c6 - field public static final int Widget_Quantum_ListPopupWindow = 16974448; // 0x1030270 - field public static final int Widget_Quantum_ListView = 16974449; // 0x1030271 - field public static final int Widget_Quantum_ListView_DropDown = 16974450; // 0x1030272 - field public static final int Widget_Quantum_MediaRouteButton = 16974451; // 0x1030273 - field public static final int Widget_Quantum_PopupMenu = 16974452; // 0x1030274 - field public static final int Widget_Quantum_PopupMenu_Overflow = 16974453; // 0x1030275 - field public static final int Widget_Quantum_PopupWindow = 16974454; // 0x1030276 - field public static final int Widget_Quantum_ProgressBar = 16974455; // 0x1030277 - field public static final int Widget_Quantum_ProgressBar_Horizontal = 16974456; // 0x1030278 - field public static final int Widget_Quantum_ProgressBar_Large = 16974457; // 0x1030279 - field public static final int Widget_Quantum_ProgressBar_Small = 16974458; // 0x103027a - field public static final int Widget_Quantum_ProgressBar_Small_Title = 16974459; // 0x103027b - field public static final int Widget_Quantum_RatingBar = 16974460; // 0x103027c - field public static final int Widget_Quantum_RatingBar_Indicator = 16974461; // 0x103027d - field public static final int Widget_Quantum_RatingBar_Small = 16974462; // 0x103027e - field public static final int Widget_Quantum_ScrollView = 16974463; // 0x103027f - field public static final int Widget_Quantum_SeekBar = 16974464; // 0x1030280 - field public static final int Widget_Quantum_SegmentedButton = 16974465; // 0x1030281 - field public static final int Widget_Quantum_Spinner = 16974467; // 0x1030283 - field public static final int Widget_Quantum_StackView = 16974466; // 0x1030282 - field public static final int Widget_Quantum_Tab = 16974468; // 0x1030284 - field public static final int Widget_Quantum_TabWidget = 16974469; // 0x1030285 - field public static final int Widget_Quantum_TextView = 16974470; // 0x1030286 - field public static final int Widget_Quantum_TextView_SpinnerItem = 16974471; // 0x1030287 - field public static final int Widget_Quantum_WebTextView = 16974472; // 0x1030288 - field public static final int Widget_Quantum_WebView = 16974473; // 0x1030289 + 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_RatingBar = 16973857; // 0x1030021 field public static final int Widget_ScrollView = 16973869; // 0x103002d field public static final int Widget_SeekBar = 16973856; // 0x1030020 @@ -3423,11 +3425,11 @@ package android.app { method public static void getMyMemoryState(android.app.ActivityManager.RunningAppProcessInfo); method public android.os.Debug.MemoryInfo[] getProcessMemoryInfo(int[]); method public java.util.List<android.app.ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState(); - method public java.util.List<android.app.ActivityManager.RecentTaskInfo> getRecentTasks(int, int) throws java.lang.SecurityException; + method public deprecated java.util.List<android.app.ActivityManager.RecentTaskInfo> getRecentTasks(int, int) throws java.lang.SecurityException; method public java.util.List<android.app.ActivityManager.RunningAppProcessInfo> getRunningAppProcesses(); method public android.app.PendingIntent getRunningServiceControlPanel(android.content.ComponentName) throws java.lang.SecurityException; method public java.util.List<android.app.ActivityManager.RunningServiceInfo> getRunningServices(int) throws java.lang.SecurityException; - method public java.util.List<android.app.ActivityManager.RunningTaskInfo> getRunningTasks(int) throws java.lang.SecurityException; + method public deprecated java.util.List<android.app.ActivityManager.RunningTaskInfo> getRunningTasks(int) throws java.lang.SecurityException; method public boolean isLowRamDevice(); method public static boolean isRunningInTestHarness(); method public static boolean isUserAMonkey(); @@ -5042,7 +5044,9 @@ package android.app { method public void cancel(); method public android.app.Activity getActivity(); method public android.content.Context getContext(); + method public void onAttached(android.app.Activity); method public void onCancel(); + method public void onDetached(); } public final class WallpaperInfo implements android.os.Parcelable { @@ -5355,13 +5359,13 @@ package android.app.task { method public int describeContents(); method public int getBackoffPolicy(); method public android.os.Bundle getExtras(); + method public int getId(); method public long getInitialBackoffMillis(); method public long getIntervalMillis(); method public long getMaxExecutionDelayMillis(); method public long getMinLatencyMillis(); method public int getNetworkCapabilities(); method public android.content.ComponentName getService(); - method public int getTaskId(); method public boolean isPeriodic(); method public boolean isRequireCharging(); method public boolean isRequireDeviceIdle(); @@ -5374,7 +5378,7 @@ package android.app.task { field public static final int LINEAR = 0; // 0x0 } - public final class Task.Builder { + public static final class Task.Builder { ctor public Task.Builder(int, android.content.ComponentName); method public android.app.task.Task build(); method public android.app.task.Task.Builder setBackoffCriteria(long, int); @@ -5388,8 +5392,9 @@ package android.app.task { } public static abstract interface Task.NetworkType { - field public static final int ANY = 0; // 0x0 - field public static final int UNMETERED = 1; // 0x1 + field public static final int ANY = 1; // 0x1 + field public static final int NONE = 0; // 0x0 + field public static final int UNMETERED = 2; // 0x2 } public abstract class TaskManager { @@ -5398,8 +5403,8 @@ package android.app.task { method public abstract void cancelAll(); method public abstract java.util.List<android.app.task.Task> getAllPendingTasks(); method public abstract int schedule(android.app.task.Task); - field public static final int RESULT_INVALID_PARAMETERS = -1; // 0xffffffff - field public static final int RESULT_OVER_QUOTA = -2; // 0xfffffffe + field public static final int RESULT_FAILURE = 0; // 0x0 + field public static final int RESULT_SUCCESS = 1; // 0x1 } public class TaskParams implements android.os.Parcelable { @@ -5561,8 +5566,8 @@ package android.bluetooth { method public boolean disable(); method public boolean enable(); method public java.lang.String getAddress(); - method public android.bluetooth.BluetoothLeAdvertiser getBluetoothLeAdvertiser(); - method public android.bluetooth.BluetoothLeScanner getBluetoothLeScanner(); + method public android.bluetooth.le.BluetoothLeAdvertiser getBluetoothLeAdvertiser(); + method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner(); method public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices(); method public static synchronized android.bluetooth.BluetoothAdapter getDefaultAdapter(); method public java.lang.String getName(); @@ -6205,74 +6210,71 @@ package android.bluetooth { method public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int); } - public final class BluetoothLeAdvertiseScanData { - ctor public BluetoothLeAdvertiseScanData(); - field public static final int ADVERTISING_DATA = 0; // 0x0 - field public static final int PARSED_SCAN_RECORD = 2; // 0x2 - field public static final int SCAN_RESPONSE_DATA = 1; // 0x1 + public final class BluetoothManager { + method public android.bluetooth.BluetoothAdapter getAdapter(); + method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(int); + method public int getConnectionState(android.bluetooth.BluetoothDevice, int); + method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int, int[]); + method public android.bluetooth.BluetoothGattServer openGattServer(android.content.Context, android.bluetooth.BluetoothGattServerCallback); } - public static abstract class BluetoothLeAdvertiseScanData.AdvertiseBaseData { - method public int getDataType(); - method public int getManufacturerId(); - method public byte[] getManufacturerSpecificData(); - method public byte[] getServiceData(); - method public android.os.ParcelUuid getServiceDataUuid(); - method public java.util.List<android.os.ParcelUuid> getServiceUuids(); + public abstract interface BluetoothProfile { + method public abstract java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method public abstract int getConnectionState(android.bluetooth.BluetoothDevice); + method public abstract java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]); + field public static final int A2DP = 2; // 0x2 + field public static final java.lang.String EXTRA_PREVIOUS_STATE = "android.bluetooth.profile.extra.PREVIOUS_STATE"; + field public static final java.lang.String EXTRA_STATE = "android.bluetooth.profile.extra.STATE"; + field public static final int GATT = 7; // 0x7 + field public static final int GATT_SERVER = 8; // 0x8 + field public static final int HEADSET = 1; // 0x1 + field public static final int HEALTH = 3; // 0x3 + field public static final int STATE_CONNECTED = 2; // 0x2 + field public static final int STATE_CONNECTING = 1; // 0x1 + field public static final int STATE_DISCONNECTED = 0; // 0x0 + field public static final int STATE_DISCONNECTING = 3; // 0x3 } - public static final class BluetoothLeAdvertiseScanData.AdvertisementData extends android.bluetooth.BluetoothLeAdvertiseScanData.AdvertiseBaseData implements android.os.Parcelable { - method public int describeContents(); - method public boolean getIncludeTxPowerLevel(); - method public static android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData.Builder newBuilder(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; + public static abstract interface BluetoothProfile.ServiceListener { + method public abstract void onServiceConnected(int, android.bluetooth.BluetoothProfile); + method public abstract void onServiceDisconnected(int); } - public static final class BluetoothLeAdvertiseScanData.AdvertisementData.Builder { - ctor public BluetoothLeAdvertiseScanData.AdvertisementData.Builder(); - method public android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData build(); - method public android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData.Builder dataType(int); - method public android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData.Builder includeTxPowerLevel(boolean); - method public android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData.Builder manufacturerData(int, byte[]); - method public android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData.Builder serviceData(android.os.ParcelUuid, byte[]); - method public android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData.Builder serviceUuids(java.util.List<android.os.ParcelUuid>); + public final class BluetoothServerSocket implements java.io.Closeable { + method public android.bluetooth.BluetoothSocket accept() throws java.io.IOException; + method public android.bluetooth.BluetoothSocket accept(int) throws java.io.IOException; + method public void close() throws java.io.IOException; } - public static final class BluetoothLeAdvertiseScanData.ScanRecord extends android.bluetooth.BluetoothLeAdvertiseScanData.AdvertiseBaseData { - method public int getAdvertiseFlags(); - method public java.lang.String getLocalName(); - method public static android.bluetooth.BluetoothLeAdvertiseScanData.ScanRecord.Parser getParser(); - method public int getTxPowerLevel(); + public final class BluetoothSocket implements java.io.Closeable { + method public void close() throws java.io.IOException; + method public void connect() throws java.io.IOException; + method public java.io.InputStream getInputStream() throws java.io.IOException; + method public java.io.OutputStream getOutputStream() throws java.io.IOException; + method public android.bluetooth.BluetoothDevice getRemoteDevice(); + method public boolean isConnected(); } - public static final class BluetoothLeAdvertiseScanData.ScanRecord.Parser { - ctor public BluetoothLeAdvertiseScanData.ScanRecord.Parser(); - method public android.bluetooth.BluetoothLeAdvertiseScanData.ScanRecord parseFromScanRecord(byte[]); - } +} - public class BluetoothLeAdvertiser { - method public void startAdvertising(android.bluetooth.BluetoothLeAdvertiser.Settings, android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData, android.bluetooth.BluetoothLeAdvertiser.AdvertiseCallback); - method public void startAdvertising(android.bluetooth.BluetoothLeAdvertiser.Settings, android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData, android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData, android.bluetooth.BluetoothLeAdvertiser.AdvertiseCallback); - method public void stopAdvertising(android.bluetooth.BluetoothLeAdvertiser.Settings, android.bluetooth.BluetoothLeAdvertiser.AdvertiseCallback); - } +package android.bluetooth.le { - public static abstract interface BluetoothLeAdvertiser.AdvertiseCallback { + public abstract class AdvertiseCallback { + ctor public AdvertiseCallback(); method public abstract void onFailure(int); - method public abstract void onSuccess(android.bluetooth.BluetoothLeAdvertiser.Settings); - field public static final int ADVERISING_NOT_STARTED = 4; // 0x4 - field public static final int ADVERTISING_ALREADY_STARTED = 3; // 0x3 - field public static final int ADVERTISING_SERVICE_UNKNOWN = 1; // 0x1 - field public static final int CONTROLLER_FAILURE = 5; // 0x5 - field public static final int TOO_MANY_ADVERTISERS = 2; // 0x2 + method public abstract void onSuccess(android.bluetooth.le.AdvertiseSettings); + field public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; // 0x3 + field public static final int ADVERTISE_FAILED_CONTROLLER_FAILURE = 5; // 0x5 + field public static final int ADVERTISE_FAILED_NOT_STARTED = 4; // 0x4 + field public static final int ADVERTISE_FAILED_SERVICE_UNKNOWN = 1; // 0x1 + field public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; // 0x2 } - public static final class BluetoothLeAdvertiser.Settings implements android.os.Parcelable { + public final class AdvertiseSettings implements android.os.Parcelable { method public int describeContents(); method public int getMode(); method public int getTxPowerLevel(); method public int getType(); - method public static android.bluetooth.BluetoothLeAdvertiser.Settings.Builder newBuilder(); method public void writeToParcel(android.os.Parcel, int); field public static final int ADVERTISE_MODE_BALANCED = 1; // 0x1 field public static final int ADVERTISE_MODE_LOW_LATENCY = 2; // 0x2 @@ -6287,14 +6289,57 @@ package android.bluetooth { field public static final android.os.Parcelable.Creator CREATOR; } - public static final class BluetoothLeAdvertiser.Settings.Builder { - method public android.bluetooth.BluetoothLeAdvertiser.Settings.Builder advertiseMode(int); - method public android.bluetooth.BluetoothLeAdvertiser.Settings build(); - method public android.bluetooth.BluetoothLeAdvertiser.Settings.Builder txPowerLevel(int); - method public android.bluetooth.BluetoothLeAdvertiser.Settings.Builder type(int); + public static final class AdvertiseSettings.Builder { + ctor public AdvertiseSettings.Builder(); + method public android.bluetooth.le.AdvertiseSettings build(); + method public android.bluetooth.le.AdvertiseSettings.Builder setAdvertiseMode(int); + method public android.bluetooth.le.AdvertiseSettings.Builder setTxPowerLevel(int); + method public android.bluetooth.le.AdvertiseSettings.Builder setType(int); + } + + public final class AdvertisementData implements android.os.Parcelable { + method public int describeContents(); + method public boolean getIncludeTxPowerLevel(); + method public int getManufacturerId(); + method public byte[] getManufacturerSpecificData(); + method public byte[] getServiceData(); + method public android.os.ParcelUuid getServiceDataUuid(); + method public java.util.List<android.os.ParcelUuid> getServiceUuids(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class AdvertisementData.Builder { + ctor public AdvertisementData.Builder(); + method public android.bluetooth.le.AdvertisementData build(); + method public android.bluetooth.le.AdvertisementData.Builder setIncludeTxPowerLevel(boolean); + method public android.bluetooth.le.AdvertisementData.Builder setManufacturerData(int, byte[]); + method public android.bluetooth.le.AdvertisementData.Builder setServiceData(android.os.ParcelUuid, byte[]); + method public android.bluetooth.le.AdvertisementData.Builder setServiceUuids(java.util.List<android.os.ParcelUuid>); + } + + public final class BluetoothLeAdvertiser { + method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertisementData, android.bluetooth.le.AdvertiseCallback); + method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertisementData, android.bluetooth.le.AdvertisementData, android.bluetooth.le.AdvertiseCallback); + method public void stopAdvertising(android.bluetooth.le.AdvertiseCallback); + } + + public final class BluetoothLeScanner { + method public void startScan(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback); + method public void stopScan(android.bluetooth.le.ScanCallback); } - public final class BluetoothLeScanFilter implements android.os.Parcelable { + public abstract class ScanCallback { + ctor public ScanCallback(); + method public abstract void onAdvertisementUpdate(android.bluetooth.le.ScanResult); + method public abstract void onScanFailed(int); + field public static final int SCAN_FAILED_ALREADY_STARTED = 1; // 0x1 + field public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2; // 0x2 + field public static final int SCAN_FAILED_CONTROLLER_FAILURE = 4; // 0x4 + field public static final int SCAN_FAILED_GATT_SERVICE_FAILURE = 3; // 0x3 + } + + public final class ScanFilter implements android.os.Parcelable { method public int describeContents(); method public java.lang.String getDeviceAddress(); method public java.lang.String getLocalName(); @@ -6307,63 +6352,54 @@ package android.bluetooth { method public byte[] getServiceDataMask(); method public android.os.ParcelUuid getServiceUuid(); method public android.os.ParcelUuid getServiceUuidMask(); - method public boolean matches(android.bluetooth.BluetoothLeScanner.ScanResult); - method public static android.bluetooth.BluetoothLeScanFilter.Builder newBuilder(); + method public boolean matches(android.bluetooth.le.ScanResult); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } - public static class BluetoothLeScanFilter.Builder { - method public android.bluetooth.BluetoothLeScanFilter build(); - method public android.bluetooth.BluetoothLeScanFilter.Builder macAddress(java.lang.String); - method public android.bluetooth.BluetoothLeScanFilter.Builder manufacturerData(int, byte[]); - method public android.bluetooth.BluetoothLeScanFilter.Builder manufacturerDataMask(byte[]); - method public android.bluetooth.BluetoothLeScanFilter.Builder name(java.lang.String); - method public android.bluetooth.BluetoothLeScanFilter.Builder rssiRange(int, int); - method public android.bluetooth.BluetoothLeScanFilter.Builder serviceData(byte[]); - method public android.bluetooth.BluetoothLeScanFilter.Builder serviceDataMask(byte[]); - method public android.bluetooth.BluetoothLeScanFilter.Builder serviceUuid(android.os.ParcelUuid); - method public android.bluetooth.BluetoothLeScanFilter.Builder serviceUuidMask(android.os.ParcelUuid); - } - - public class BluetoothLeScanner { - method public void startScan(java.util.List<android.bluetooth.BluetoothLeScanFilter>, android.bluetooth.BluetoothLeScanner.Settings, android.bluetooth.BluetoothLeScanner.ScanCallback); - method public void stopScan(android.bluetooth.BluetoothLeScanner.Settings); + public static final class ScanFilter.Builder { + ctor public ScanFilter.Builder(); + method public android.bluetooth.le.ScanFilter build(); + method public android.bluetooth.le.ScanFilter.Builder setMacAddress(java.lang.String); + method public android.bluetooth.le.ScanFilter.Builder setManufacturerData(int, byte[]); + method public android.bluetooth.le.ScanFilter.Builder setManufacturerData(int, byte[], byte[]); + method public android.bluetooth.le.ScanFilter.Builder setName(java.lang.String); + method public android.bluetooth.le.ScanFilter.Builder setRssiRange(int, int); + method public android.bluetooth.le.ScanFilter.Builder setServiceData(byte[]); + method public android.bluetooth.le.ScanFilter.Builder setServiceData(byte[], byte[]); + method public android.bluetooth.le.ScanFilter.Builder setServiceUuid(android.os.ParcelUuid); + method public android.bluetooth.le.ScanFilter.Builder setServiceUuid(android.os.ParcelUuid, android.os.ParcelUuid); } - public static abstract interface BluetoothLeScanner.ScanCallback { - method public abstract void onBatchScanResults(java.util.List<android.bluetooth.BluetoothLeScanner.ScanResult>); - method public abstract void onDeviceFound(android.bluetooth.BluetoothLeScanner.ScanResult); - method public abstract void onDeviceLost(android.bluetooth.BluetoothDevice); - method public abstract void onDeviceUpdate(android.bluetooth.BluetoothLeScanner.ScanResult); - method public abstract void onScanFailed(int); - field public static final int APPLICATION_REGISTRATION_FAILED = 2; // 0x2 - field public static final int CONTROLLER_FAILURE = 4; // 0x4 - field public static final int GATT_SERVICE_FAILURE = 3; // 0x3 - field public static final int SCAN_ALREADY_STARTED = 1; // 0x1 + public final class ScanRecord { + method public int getAdvertiseFlags(); + method public java.lang.String getLocalName(); + method public int getManufacturerId(); + method public byte[] getManufacturerSpecificData(); + method public byte[] getServiceData(); + method public android.os.ParcelUuid getServiceDataUuid(); + method public java.util.List<android.os.ParcelUuid> getServiceUuids(); + method public int getTxPowerLevel(); + method public static android.bluetooth.le.ScanRecord parseFromBytes(byte[]); } - public static final class BluetoothLeScanner.ScanResult implements android.os.Parcelable { - ctor public BluetoothLeScanner.ScanResult(android.bluetooth.BluetoothDevice, byte[], int, long); + public final class ScanResult implements android.os.Parcelable { method public int describeContents(); method public android.bluetooth.BluetoothDevice getDevice(); method public int getRssi(); method public byte[] getScanRecord(); - method public long getTimestampMicros(); + method public long getTimestampNanos(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } - public static final class BluetoothLeScanner.Settings implements android.os.Parcelable { + public final class ScanSettings implements android.os.Parcelable { method public int describeContents(); method public int getCallbackType(); - method public long getReportDelayMicros(); + method public long getReportDelayNanos(); method public int getScanMode(); method public int getScanResultType(); - method public static android.bluetooth.BluetoothLeScanner.Settings.Builder newBuilder(); method public void writeToParcel(android.os.Parcel, int); - field public static final int CALLBACK_TYPE_ON_FOUND = 1; // 0x1 - field public static final int CALLBACK_TYPE_ON_LOST = 2; // 0x2 field public static final int CALLBACK_TYPE_ON_UPDATE = 0; // 0x0 field public static final android.os.Parcelable.Creator CREATOR; field public static final int SCAN_MODE_BALANCED = 1; // 0x1 @@ -6372,56 +6408,12 @@ package android.bluetooth { field public static final int SCAN_RESULT_TYPE_FULL = 0; // 0x0 } - public static class BluetoothLeScanner.Settings.Builder { - method public android.bluetooth.BluetoothLeScanner.Settings build(); - method public android.bluetooth.BluetoothLeScanner.Settings.Builder callbackType(int); - method public android.bluetooth.BluetoothLeScanner.Settings.Builder reportDelayMicros(long); - method public android.bluetooth.BluetoothLeScanner.Settings.Builder scanMode(int); - } - - public final class BluetoothManager { - method public android.bluetooth.BluetoothAdapter getAdapter(); - method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(int); - method public int getConnectionState(android.bluetooth.BluetoothDevice, int); - method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int, int[]); - method public android.bluetooth.BluetoothGattServer openGattServer(android.content.Context, android.bluetooth.BluetoothGattServerCallback); - } - - public abstract interface BluetoothProfile { - method public abstract java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); - method public abstract int getConnectionState(android.bluetooth.BluetoothDevice); - method public abstract java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]); - field public static final int A2DP = 2; // 0x2 - field public static final java.lang.String EXTRA_PREVIOUS_STATE = "android.bluetooth.profile.extra.PREVIOUS_STATE"; - field public static final java.lang.String EXTRA_STATE = "android.bluetooth.profile.extra.STATE"; - field public static final int GATT = 7; // 0x7 - field public static final int GATT_SERVER = 8; // 0x8 - field public static final int HEADSET = 1; // 0x1 - field public static final int HEALTH = 3; // 0x3 - field public static final int STATE_CONNECTED = 2; // 0x2 - field public static final int STATE_CONNECTING = 1; // 0x1 - field public static final int STATE_DISCONNECTED = 0; // 0x0 - field public static final int STATE_DISCONNECTING = 3; // 0x3 - } - - public static abstract interface BluetoothProfile.ServiceListener { - method public abstract void onServiceConnected(int, android.bluetooth.BluetoothProfile); - method public abstract void onServiceDisconnected(int); - } - - public final class BluetoothServerSocket implements java.io.Closeable { - method public android.bluetooth.BluetoothSocket accept() throws java.io.IOException; - method public android.bluetooth.BluetoothSocket accept(int) throws java.io.IOException; - method public void close() throws java.io.IOException; - } - - public final class BluetoothSocket implements java.io.Closeable { - method public void close() throws java.io.IOException; - method public void connect() throws java.io.IOException; - method public java.io.InputStream getInputStream() throws java.io.IOException; - method public java.io.OutputStream getOutputStream() throws java.io.IOException; - method public android.bluetooth.BluetoothDevice getRemoteDevice(); - method public boolean isConnected(); + public static final class ScanSettings.Builder { + ctor public ScanSettings.Builder(); + method public android.bluetooth.le.ScanSettings build(); + method public android.bluetooth.le.ScanSettings.Builder setCallbackType(int); + method public android.bluetooth.le.ScanSettings.Builder setReportDelayNanos(long); + method public android.bluetooth.le.ScanSettings.Builder setScanMode(int); } } @@ -7035,7 +7027,6 @@ package android.content { field public static final java.lang.String VIBRATOR_SERVICE = "vibrator"; field public static final java.lang.String WALLPAPER_SERVICE = "wallpaper"; field public static final java.lang.String WIFI_P2P_SERVICE = "wifip2p"; - field public static final java.lang.String WIFI_PASSPOINT_SERVICE = "wifipasspoint"; field public static final java.lang.String WIFI_SERVICE = "wifi"; field public static final java.lang.String WINDOW_SERVICE = "window"; } @@ -8195,7 +8186,7 @@ package android.content.pm { } public class LauncherActivityInfo { - method public int getApplicationFlags(); + method public android.content.pm.ApplicationInfo getApplicationInfo(); method public android.graphics.drawable.Drawable getBadgedIcon(int); method public android.content.ComponentName getComponentName(); method public long getFirstInstallTime(); @@ -8206,21 +8197,21 @@ package android.content.pm { } public class LauncherApps { - method public synchronized void addOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener); + method public void addOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener); method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle); method public boolean isActivityEnabledForProfile(android.content.ComponentName, android.os.UserHandle); method public boolean isPackageEnabledForProfile(java.lang.String, android.os.UserHandle); - method public synchronized void removeOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener); + method public void removeOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener); method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle); - method public void startActivityForProfile(android.content.ComponentName, android.graphics.Rect, android.os.Bundle, android.os.UserHandle); + method public void startActivityForProfile(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); } public static abstract interface LauncherApps.OnAppsChangedListener { - method public abstract void onPackageAdded(android.os.UserHandle, java.lang.String); - method public abstract void onPackageChanged(android.os.UserHandle, java.lang.String); - method public abstract void onPackageRemoved(android.os.UserHandle, java.lang.String); - method public abstract void onPackagesAvailable(android.os.UserHandle, java.lang.String[], boolean); - method public abstract void onPackagesUnavailable(android.os.UserHandle, java.lang.String[], boolean); + method public abstract void onPackageAdded(java.lang.String, android.os.UserHandle); + method public abstract void onPackageChanged(java.lang.String, android.os.UserHandle); + method public abstract void onPackageRemoved(java.lang.String, android.os.UserHandle); + method public abstract void onPackagesAvailable(java.lang.String[], android.os.UserHandle, boolean); + method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean); } public class PackageInfo implements android.os.Parcelable { @@ -12668,6 +12659,9 @@ package android.hardware.camera2.params { method public int getWidth(); method public int getX(); method public int getY(); + field public static final int METERING_WEIGHT_DONT_CARE = 0; // 0x0 + field public static final int METERING_WEIGHT_MAX = 1000; // 0x3e8 + field public static final int METERING_WEIGHT_MIN = 0; // 0x0 } public final class RggbChannelVector { @@ -17107,7 +17101,6 @@ package android.net.wifi { public class WifiManager { method public int addNetwork(android.net.wifi.WifiConfiguration); method public static int calculateSignalLevel(int, int); - method public void cancelWps(android.net.wifi.WifiManager.ActionListener); method public static int compareSignalLevel(int, int); method public android.net.wifi.WifiManager.MulticastLock createMulticastLock(java.lang.String); method public android.net.wifi.WifiManager.WifiLock createWifiLock(int, java.lang.String); @@ -17131,12 +17124,9 @@ package android.net.wifi { method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean); method public boolean setWifiEnabled(boolean); method public boolean startScan(); - method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsListener); method public int updateNetwork(android.net.wifi.WifiConfiguration); field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK"; field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE"; - field public static final int BUSY = 2; // 0x2 - field public static final int ERROR = 0; // 0x0 field public static final int ERROR_AUTHENTICATING = 1; // 0x1 field public static final java.lang.String EXTRA_BSSID = "bssid"; field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo"; @@ -17147,8 +17137,6 @@ package android.net.wifi { field public static final java.lang.String EXTRA_SUPPLICANT_ERROR = "supplicantError"; field public static final java.lang.String EXTRA_WIFI_INFO = "wifiInfo"; field public static final java.lang.String EXTRA_WIFI_STATE = "wifi_state"; - field public static final int INVALID_ARGS = 8; // 0x8 - field public static final int IN_PROGRESS = 1; // 0x1 field public static final java.lang.String NETWORK_IDS_CHANGED_ACTION = "android.net.wifi.NETWORK_IDS_CHANGED"; field public static final java.lang.String NETWORK_STATE_CHANGED_ACTION = "android.net.wifi.STATE_CHANGE"; field public static final java.lang.String RSSI_CHANGED_ACTION = "android.net.wifi.RSSI_CHANGED"; @@ -17164,16 +17152,6 @@ package android.net.wifi { field public static final int WIFI_STATE_ENABLED = 3; // 0x3 field public static final int WIFI_STATE_ENABLING = 2; // 0x2 field public static final int WIFI_STATE_UNKNOWN = 4; // 0x4 - field public static final int WPS_AUTH_FAILURE = 6; // 0x6 - field public static final int WPS_OVERLAP_ERROR = 3; // 0x3 - field public static final int WPS_TIMED_OUT = 7; // 0x7 - field public static final int WPS_TKIP_ONLY_PROHIBITED = 5; // 0x5 - field public static final int WPS_WEP_PROHIBITED = 4; // 0x4 - } - - public static abstract interface WifiManager.ActionListener { - method public abstract void onFailure(int); - method public abstract void onSuccess(); } public class WifiManager.MulticastLock { @@ -17191,12 +17169,6 @@ package android.net.wifi { method public void setWorkSource(android.os.WorkSource); } - public static abstract interface WifiManager.WpsListener { - method public abstract void onCompletion(); - method public abstract void onFailure(int); - method public abstract void onStartSuccess(java.lang.String); - } - public class WifiScanner { method public void configureWifiChange(int, int, int, int, int, android.net.wifi.WifiScanner.HotspotInfo[]); method public void resetHotlist(android.net.wifi.WifiScanner.HotlistListener); @@ -17282,7 +17254,6 @@ package android.net.wifi { ctor public WpsInfo(android.net.wifi.WpsInfo); method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); - field public java.lang.String BSSID; field public static final android.os.Parcelable.Creator CREATOR; field public static final int DISPLAY = 1; // 0x1 field public static final int INVALID = 4; // 0x4 @@ -17490,29 +17461,6 @@ package android.net.wifi.p2p.nsd { } -package android.net.wifi.passpoint { - - public class WifiPasspointCredential implements android.os.Parcelable { - ctor public WifiPasspointCredential(java.lang.String, android.net.wifi.WifiEnterpriseConfig); - method public int describeContents(); - method public java.lang.String getClientCertPath(); - method public int getEapMethod(); - method public java.lang.String getFqdn(); - method public java.lang.String getImsi(); - method public java.lang.String getRealm(); - method public java.lang.String getUserName(); - method public void writeToParcel(android.os.Parcel, int); - } - - public class WifiPasspointManager { - method public boolean addCredential(android.net.wifi.passpoint.WifiPasspointCredential); - method public java.util.List<android.net.wifi.passpoint.WifiPasspointCredential> getSavedCredentials(); - method public boolean removeCredential(android.net.wifi.passpoint.WifiPasspointCredential); - method public boolean updateCredential(android.net.wifi.passpoint.WifiPasspointCredential); - } - -} - package android.nfc { public class FormatException extends java.lang.Exception { @@ -17647,25 +17595,15 @@ package android.nfc { package android.nfc.cardemulation { - public final class AidGroup implements android.os.Parcelable { - ctor public AidGroup(java.util.ArrayList<java.lang.String>, java.lang.String); - method public int describeContents(); - method public java.util.ArrayList<java.lang.String> getAids(); - method public java.lang.String getCategory(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - field public static final int MAX_NUM_AIDS = 256; // 0x100 - } - public final class CardEmulation { method public boolean categoryAllowsForegroundPreference(java.lang.String); - method public android.nfc.cardemulation.AidGroup getAidGroupForService(android.content.ComponentName, java.lang.String); + method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, java.lang.String); method public static synchronized android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter); method public int getSelectionModeForCategory(java.lang.String); method public boolean isDefaultServiceForAid(android.content.ComponentName, java.lang.String); method public boolean isDefaultServiceForCategory(android.content.ComponentName, java.lang.String); - method public boolean registerAidGroupForService(android.content.ComponentName, android.nfc.cardemulation.AidGroup); - method public boolean removeAidGroupForService(android.content.ComponentName, java.lang.String); + method public boolean registerAidsForService(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>); + method public boolean removeAidsForService(android.content.ComponentName, java.lang.String); method public boolean setPreferredService(android.app.Activity, android.content.ComponentName); method public boolean unsetPreferredService(android.app.Activity); field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT"; @@ -20506,6 +20444,37 @@ package android.os { ctor public BadParcelableException(java.lang.Exception); } + public class BaseBundle { + method public void clear(); + method public boolean containsKey(java.lang.String); + method public java.lang.Object get(java.lang.String); + method public double getDouble(java.lang.String); + method public double getDouble(java.lang.String, double); + method public double[] getDoubleArray(java.lang.String); + method public int getInt(java.lang.String); + method public int getInt(java.lang.String, int); + method public int[] getIntArray(java.lang.String); + method public long getLong(java.lang.String); + method public long getLong(java.lang.String, long); + method public long[] getLongArray(java.lang.String); + method public java.lang.String getString(java.lang.String); + method public java.lang.String getString(java.lang.String, java.lang.String); + method public java.lang.String[] getStringArray(java.lang.String); + method public boolean isEmpty(); + method public java.util.Set<java.lang.String> keySet(); + method public void putAll(android.os.PersistableBundle); + method public void putDouble(java.lang.String, double); + method public void putDoubleArray(java.lang.String, double[]); + method public void putInt(java.lang.String, int); + method public void putIntArray(java.lang.String, int[]); + method public void putLong(java.lang.String, long); + method public void putLongArray(java.lang.String, long[]); + method public void putString(java.lang.String, java.lang.String); + method public void putStringArray(java.lang.String, java.lang.String[]); + method public void remove(java.lang.String); + method public int size(); + } + public class BatteryManager { ctor public BatteryManager(); method public android.os.BatteryProperty getProperty(int) throws android.os.RemoteException; @@ -20634,17 +20603,14 @@ package android.os { field public static final int L = 10000; // 0x2710 } - public final class Bundle extends android.os.CommonBundle { + public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable { ctor public Bundle(); ctor public Bundle(java.lang.ClassLoader); ctor public Bundle(int); ctor public Bundle(android.os.Bundle); ctor public Bundle(android.os.PersistableBundle); - method public void clear(); method public java.lang.Object clone(); - method public boolean containsKey(java.lang.String); method public int describeContents(); - method public java.lang.Object get(java.lang.String); method public android.os.IBinder getBinder(java.lang.String); method public boolean getBoolean(java.lang.String); method public boolean getBoolean(java.lang.String, boolean); @@ -20661,37 +20627,21 @@ package android.os { method public java.lang.CharSequence[] getCharSequenceArray(java.lang.String); method public java.util.ArrayList<java.lang.CharSequence> getCharSequenceArrayList(java.lang.String); method public java.lang.ClassLoader getClassLoader(); - method public double getDouble(java.lang.String); - method public double getDouble(java.lang.String, double); - method public double[] getDoubleArray(java.lang.String); method public float getFloat(java.lang.String); method public float getFloat(java.lang.String, float); method public float[] getFloatArray(java.lang.String); - method public int getInt(java.lang.String); - method public int getInt(java.lang.String, int); - method public int[] getIntArray(java.lang.String); method public java.util.ArrayList<java.lang.Integer> getIntegerArrayList(java.lang.String); - method public long getLong(java.lang.String); - method public long getLong(java.lang.String, long); - method public long[] getLongArray(java.lang.String); method public T getParcelable(java.lang.String); method public android.os.Parcelable[] getParcelableArray(java.lang.String); method public java.util.ArrayList<T> getParcelableArrayList(java.lang.String); - method public android.os.PersistableBundle getPersistableBundle(java.lang.String); method public java.io.Serializable getSerializable(java.lang.String); method public short getShort(java.lang.String); method public short getShort(java.lang.String, short); method public short[] getShortArray(java.lang.String); method public android.util.SparseArray<T> getSparseParcelableArray(java.lang.String); - method public java.lang.String getString(java.lang.String); - method public java.lang.String getString(java.lang.String, java.lang.String); - method public java.lang.String[] getStringArray(java.lang.String); method public java.util.ArrayList<java.lang.String> getStringArrayList(java.lang.String); method public boolean hasFileDescriptors(); - method public boolean isEmpty(); - method public java.util.Set<java.lang.String> keySet(); method public void putAll(android.os.Bundle); - method public void putAll(android.os.PersistableBundle); method public void putBinder(java.lang.String, android.os.IBinder); method public void putBoolean(java.lang.String, boolean); method public void putBooleanArray(java.lang.String, boolean[]); @@ -20703,30 +20653,19 @@ package android.os { method public void putCharSequence(java.lang.String, java.lang.CharSequence); method public void putCharSequenceArray(java.lang.String, java.lang.CharSequence[]); method public void putCharSequenceArrayList(java.lang.String, java.util.ArrayList<java.lang.CharSequence>); - method public void putDouble(java.lang.String, double); - method public void putDoubleArray(java.lang.String, double[]); method public void putFloat(java.lang.String, float); method public void putFloatArray(java.lang.String, float[]); - method public void putInt(java.lang.String, int); - method public void putIntArray(java.lang.String, int[]); method public void putIntegerArrayList(java.lang.String, java.util.ArrayList<java.lang.Integer>); - method public void putLong(java.lang.String, long); - method public void putLongArray(java.lang.String, long[]); method public void putParcelable(java.lang.String, android.os.Parcelable); method public void putParcelableArray(java.lang.String, android.os.Parcelable[]); method public void putParcelableArrayList(java.lang.String, java.util.ArrayList<? extends android.os.Parcelable>); - method public void putPersistableBundle(java.lang.String, android.os.PersistableBundle); method public void putSerializable(java.lang.String, java.io.Serializable); method public void putShort(java.lang.String, short); method public void putShortArray(java.lang.String, short[]); method public void putSparseParcelableArray(java.lang.String, android.util.SparseArray<? extends android.os.Parcelable>); - method public void putString(java.lang.String, java.lang.String); - method public void putStringArray(java.lang.String, java.lang.String[]); method public void putStringArrayList(java.lang.String, java.util.ArrayList<java.lang.String>); method public void readFromParcel(android.os.Parcel); - method public void remove(java.lang.String); method public void setClassLoader(java.lang.ClassLoader); - method public int size(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final android.os.Bundle EMPTY; @@ -20744,9 +20683,6 @@ package android.os { method public abstract void onCancel(); } - abstract class CommonBundle implements java.lang.Cloneable android.os.Parcelable { - } - public class ConditionVariable { ctor public ConditionVariable(); ctor public ConditionVariable(boolean); @@ -21326,46 +21262,14 @@ package android.os { field public static final int PATTERN_SIMPLE_GLOB = 2; // 0x2 } - public final class PersistableBundle extends android.os.CommonBundle { + public final class PersistableBundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable { ctor public PersistableBundle(); - ctor public PersistableBundle(java.lang.ClassLoader); ctor public PersistableBundle(int); ctor public PersistableBundle(android.os.PersistableBundle); - method public void clear(); method public java.lang.Object clone(); - method public boolean containsKey(java.lang.String); method public int describeContents(); - method public java.lang.Object get(java.lang.String); - method public java.lang.ClassLoader getClassLoader(); - method public double getDouble(java.lang.String); - method public double getDouble(java.lang.String, double); - method public double[] getDoubleArray(java.lang.String); - method public int getInt(java.lang.String); - method public int getInt(java.lang.String, int); - method public int[] getIntArray(java.lang.String); - method public long getLong(java.lang.String); - method public long getLong(java.lang.String, long); - method public long[] getLongArray(java.lang.String); method public android.os.PersistableBundle getPersistableBundle(java.lang.String); - method public java.lang.String getString(java.lang.String); - method public java.lang.String getString(java.lang.String, java.lang.String); - method public java.lang.String[] getStringArray(java.lang.String); - method public boolean isEmpty(); - method public java.util.Set<java.lang.String> keySet(); - method public void putAll(android.os.PersistableBundle); - method public void putDouble(java.lang.String, double); - method public void putDoubleArray(java.lang.String, double[]); - method public void putInt(java.lang.String, int); - method public void putIntArray(java.lang.String, int[]); - method public void putLong(java.lang.String, long); - method public void putLongArray(java.lang.String, long[]); method public void putPersistableBundle(java.lang.String, android.os.PersistableBundle); - method public void putString(java.lang.String, java.lang.String); - method public void putStringArray(java.lang.String, java.lang.String[]); - method public void readFromParcel(android.os.Parcel); - method public void remove(java.lang.String); - method public void setClassLoader(java.lang.ClassLoader); - method public int size(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final android.os.PersistableBundle EMPTY; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index b5281ff..5257430 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -716,6 +716,7 @@ public class Activity extends ContextThemeWrapper HashMap<String, Object> children; ArrayList<Fragment> fragments; ArrayMap<String, LoaderManagerImpl> loaders; + VoiceInteractor voiceInteractor; } /* package */ NonConfigurationInstances mLastNonConfigurationInstances; @@ -920,6 +921,9 @@ public class Activity extends ContextThemeWrapper } mFragments.dispatchCreate(); getApplication().dispatchActivityCreated(this, savedInstanceState); + if (mVoiceInteractor != null) { + mVoiceInteractor.attachActivity(this); + } mCalled = true; } @@ -1830,7 +1834,8 @@ public class Activity extends ContextThemeWrapper } } } - if (activity == null && children == null && fragments == null && !retainLoaders) { + if (activity == null && children == null && fragments == null && !retainLoaders + && mVoiceInteractor == null) { return null; } @@ -1839,6 +1844,7 @@ public class Activity extends ContextThemeWrapper nci.children = children; nci.fragments = fragments; nci.loaders = mAllLoaderManagers; + nci.voiceInteractor = mVoiceInteractor; return nci; } @@ -5632,8 +5638,14 @@ public class Activity extends ContextThemeWrapper mParent = parent; mEmbeddedID = id; mLastNonConfigurationInstances = lastNonConfigurationInstances; - mVoiceInteractor = voiceInteractor != null - ? new VoiceInteractor(this, this, voiceInteractor, Looper.myLooper()) : null; + if (voiceInteractor != null) { + if (lastNonConfigurationInstances != null) { + mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor; + } else { + mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this, + Looper.myLooper()); + } + } mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), @@ -5842,6 +5854,9 @@ public class Activity extends ContextThemeWrapper if (mLoaderManager != null) { mLoaderManager.doDestroy(); } + if (mVoiceInteractor != null) { + mVoiceInteractor.detachActivity(); + } } /** diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index abcb0d0..788ac56 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -738,7 +738,7 @@ public class ActivityManager { public static final int RECENT_INCLUDE_PROFILES = 0x0004; /** - * Return a list of the tasks that the user has recently launched, with + * <p></p>Return a list of the tasks that the user has recently launched, with * the most recent being first and older ones after in order. * * <p><b>Note: this method is only intended for debugging and presenting @@ -750,6 +750,15 @@ public class ActivityManager { * same time, assumptions made about the meaning of the data here for * purposes of control flow will be incorrect.</p> * + * @deprecated As of {@link android.os.Build.VERSION_CODES#L}, this method is + * no longer available to third party applications: as the introduction of + * document-centric recents means + * it can leak personal information to the caller. For backwards compatibility, + * it will still return a small subset of its data: at least the caller's + * own tasks (though see {@link #getAppTasks()} for the correct supported + * way to retrieve that information), and possibly some other tasks + * such as home that are known to not be sensitive. + * * @param maxNum The maximum number of entries to return in the list. The * actual number returned may be smaller, depending on how many tasks the * user has started and the maximum number the system can remember. @@ -762,6 +771,7 @@ public class ActivityManager { * @throws SecurityException Throws SecurityException if the caller does * not hold the {@link android.Manifest.permission#GET_TASKS} permission. */ + @Deprecated public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags) throws SecurityException { try { @@ -946,6 +956,14 @@ public class ActivityManager { * same time, assumptions made about the meaning of the data here for * purposes of control flow will be incorrect.</p> * + * @deprecated As of {@link android.os.Build.VERSION_CODES#L}, this method + * is no longer available to third party + * applications: the introduction of document-centric recents means + * it can leak person information to the caller. For backwards compatibility, + * it will still retu rn a small subset of its data: at least the caller's + * own tasks, and possibly some other tasks + * such as home that are known to not be sensitive. + * * @param maxNum The maximum number of entries to return in the list. The * actual number returned may be smaller, depending on how many tasks the * user has started. @@ -956,6 +974,7 @@ public class ActivityManager { * @throws SecurityException Throws SecurityException if the caller does * not hold the {@link android.Manifest.permission#GET_TASKS} permission. */ + @Deprecated public List<RunningTaskInfo> getRunningTasks(int maxNum) throws SecurityException { try { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 90aeaae..8dba1dc 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1671,7 +1671,6 @@ public class Notification implements Parcelable private Notification mPublicVersion = null; private final NotificationColorUtil mColorUtil; private ArrayList<String> mPeople; - private boolean mPreQuantum; private int mColor = COLOR_DEFAULT; /** @@ -1694,6 +1693,15 @@ public class Notification implements Parcelable * object. */ public Builder(Context context) { + /* + * Important compatibility note! + * Some apps out in the wild create a Notification.Builder in their Activity subclass + * constructor for later use. At this point Activities - themselves subclasses of + * ContextWrapper - do not have their inner Context populated yet. This means that + * any calls to Context methods from within this constructor can cause NPEs in existing + * apps. Any data populated from mContext should therefore be populated lazily to + * preserve compatibility. + */ mContext = context; // Set defaults to match the defaults of a Notification @@ -1702,7 +1710,6 @@ public class Notification implements Parcelable mPriority = PRIORITY_DEFAULT; mPeople = new ArrayList<String>(); - mPreQuantum = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.L; mColorUtil = NotificationColorUtil.getInstance(); } diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java index 6dc48b0..fe85ef4 100644 --- a/core/java/android/app/VoiceInteractor.java +++ b/core/java/android/app/VoiceInteractor.java @@ -30,7 +30,7 @@ import com.android.internal.app.IVoiceInteractorRequest; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; -import java.util.WeakHashMap; +import java.util.ArrayList; /** * Interface for an {@link Activity} to interact with the user through voice. @@ -39,9 +39,11 @@ public class VoiceInteractor { static final String TAG = "VoiceInteractor"; static final boolean DEBUG = true; - final Context mContext; - final Activity mActivity; final IVoiceInteractor mInteractor; + + Context mContext; + Activity mActivity; + final HandlerCaller mHandlerCaller; final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() { @Override @@ -140,6 +142,12 @@ public class VoiceInteractor { public void onCancel() { } + public void onAttached(Activity activity) { + } + + public void onDetached() { + } + void clear() { mRequestInterface = null; mContext = null; @@ -220,11 +228,11 @@ public class VoiceInteractor { } } - VoiceInteractor(Context context, Activity activity, IVoiceInteractor interactor, + VoiceInteractor(IVoiceInteractor interactor, Context context, Activity activity, Looper looper) { + mInteractor = interactor; mContext = context; mActivity = activity; - mInteractor = interactor; mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true); } @@ -238,6 +246,49 @@ public class VoiceInteractor { } } + private ArrayList<Request> makeRequestList() { + final int N = mActiveRequests.size(); + if (N < 1) { + return null; + } + ArrayList<Request> list = new ArrayList<Request>(N); + for (int i=0; i<N; i++) { + list.add(mActiveRequests.valueAt(i)); + } + return list; + } + + void attachActivity(Activity activity) { + if (mActivity == activity) { + return; + } + mContext = activity; + mActivity = activity; + ArrayList<Request> reqs = makeRequestList(); + if (reqs != null) { + for (int i=0; i<reqs.size(); i++) { + Request req = reqs.get(i); + req.mContext = activity; + req.mActivity = activity; + req.onAttached(activity); + } + } + } + + void detachActivity() { + ArrayList<Request> reqs = makeRequestList(); + if (reqs != null) { + for (int i=0; i<reqs.size(); i++) { + Request req = reqs.get(i); + req.onDetached(); + req.mActivity = null; + req.mContext = null; + } + } + mContext = null; + mActivity = null; + } + public boolean submitRequest(Request request) { try { IVoiceInteractorRequest ireq = request.submit(mInteractor, diff --git a/core/java/android/app/task/Task.java b/core/java/android/app/task/Task.java index dd184a5..ca4aeb2 100644 --- a/core/java/android/app/task/Task.java +++ b/core/java/android/app/task/Task.java @@ -27,10 +27,13 @@ import android.os.Parcelable; * using the {@link Task.Builder}. */ public class Task implements Parcelable { - public interface NetworkType { - public final int ANY = 0; - public final int UNMETERED = 1; + /** Default. */ + public final int NONE = 0; + /** This task requires network connectivity. */ + public final int ANY = 1; + /** This task requires network connectivity that is unmetered. */ + public final int UNMETERED = 2; } /** @@ -48,6 +51,8 @@ public class Task implements Parcelable { private final ComponentName service; private final boolean requireCharging; private final boolean requireDeviceIdle; + private final boolean hasEarlyConstraint; + private final boolean hasLateConstraint; private final int networkCapabilities; private final long minLatencyMillis; private final long maxExecutionDelayMillis; @@ -59,7 +64,7 @@ public class Task implements Parcelable { /** * Unique task id associated with this class. This is assigned to your task by the scheduler. */ - public int getTaskId() { + public int getId() { return taskId; } @@ -146,6 +151,24 @@ public class Task implements Parcelable { return backoffPolicy; } + /** + * User can specify an early constraint of 0L, which is valid, so we keep track of whether the + * function was called at all. + * @hide + */ + public boolean hasEarlyConstraint() { + return hasEarlyConstraint; + } + + /** + * User can specify a late constraint of 0L, which is valid, so we keep track of whether the + * function was called at all. + * @hide + */ + public boolean hasLateConstraint() { + return hasLateConstraint; + } + private Task(Parcel in) { taskId = in.readInt(); extras = in.readBundle(); @@ -159,6 +182,8 @@ public class Task implements Parcelable { intervalMillis = in.readLong(); initialBackoffMillis = in.readLong(); backoffPolicy = in.readInt(); + hasEarlyConstraint = in.readInt() == 1; + hasLateConstraint = in.readInt() == 1; } private Task(Task.Builder b) { @@ -174,6 +199,8 @@ public class Task implements Parcelable { intervalMillis = b.mIntervalMillis; initialBackoffMillis = b.mInitialBackoffMillis; backoffPolicy = b.mBackoffPolicy; + hasEarlyConstraint = b.mHasEarlyConstraint; + hasLateConstraint = b.mHasLateConstraint; } @Override @@ -195,6 +222,8 @@ public class Task implements Parcelable { out.writeLong(intervalMillis); out.writeLong(initialBackoffMillis); out.writeInt(backoffPolicy); + out.writeInt(hasEarlyConstraint ? 1 : 0); + out.writeInt(hasLateConstraint ? 1 : 0); } public static final Creator<Task> CREATOR = new Creator<Task>() { @@ -212,7 +241,7 @@ public class Task implements Parcelable { /** * Builder class for constructing {@link Task} objects. */ - public final class Builder { + public static final class Builder { private int mTaskId; private Bundle mExtras; private ComponentName mTaskService; @@ -225,6 +254,8 @@ public class Task implements Parcelable { private long mMaxExecutionDelayMillis; // Periodic parameters. private boolean mIsPeriodic; + private boolean mHasEarlyConstraint; + private boolean mHasLateConstraint; private long mIntervalMillis; // Back-off parameters. private long mInitialBackoffMillis = 5000L; @@ -307,6 +338,7 @@ public class Task implements Parcelable { public Builder setPeriodic(long intervalMillis) { mIsPeriodic = true; mIntervalMillis = intervalMillis; + mHasEarlyConstraint = mHasLateConstraint = true; return this; } @@ -320,6 +352,7 @@ public class Task implements Parcelable { */ public Builder setMinimumLatency(long minLatencyMillis) { mMinLatencyMillis = minLatencyMillis; + mHasEarlyConstraint = true; return this; } @@ -332,6 +365,7 @@ public class Task implements Parcelable { */ public Builder setOverrideDeadline(long maxExecutionDelayMillis) { mMaxExecutionDelayMillis = maxExecutionDelayMillis; + mHasLateConstraint = true; return this; } @@ -360,31 +394,18 @@ public class Task implements Parcelable { * @return The task object to hand to the TaskManager. This object is immutable. */ public Task build() { - // Check that extras bundle only contains primitive types. - try { - for (String key : extras.keySet()) { - Object value = extras.get(key); - if (value == null) continue; - if (value instanceof Long) continue; - if (value instanceof Integer) continue; - if (value instanceof Boolean) continue; - if (value instanceof Float) continue; - if (value instanceof Double) continue; - if (value instanceof String) continue; - throw new IllegalArgumentException("Unexpected value type: " - + value.getClass().getName()); - } - } catch (IllegalArgumentException e) { - throw e; - } catch (RuntimeException exc) { - throw new IllegalArgumentException("error unparcelling Bundle", exc); + if (mExtras == null) { + mExtras = Bundle.EMPTY; + } + if (mTaskId < 0) { + throw new IllegalArgumentException("Task id must be greater than 0."); } // Check that a deadline was not set on a periodic task. - if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) { + if (mIsPeriodic && mHasLateConstraint) { throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " + "periodic task."); } - if (mIsPeriodic && (mMinLatencyMillis != 0L)) { + if (mIsPeriodic && mHasEarlyConstraint) { throw new IllegalArgumentException("Can't call setMinimumLatency() on a " + "periodic task"); } diff --git a/core/java/android/app/task/TaskManager.java b/core/java/android/app/task/TaskManager.java index 0fbe37d..00f57da 100644 --- a/core/java/android/app/task/TaskManager.java +++ b/core/java/android/app/task/TaskManager.java @@ -34,14 +34,13 @@ public abstract class TaskManager { * if the run-time for your task is too short, or perhaps the system can't resolve the * requisite {@link TaskService} in your package. */ - public static final int RESULT_INVALID_PARAMETERS = -1; - + public static final int RESULT_FAILURE = 0; /** * Returned from {@link #schedule(Task)} if this application has made too many requests for * work over too short a time. */ // TODO: Determine if this is necessary. - public static final int RESULT_OVER_QUOTA = -2; + public static final int RESULT_SUCCESS = 1; /** * @param task The task you wish scheduled. See diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 9e1c995..42c2aeb 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -18,6 +18,8 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.le.BluetoothLeAdvertiser; +import android.bluetooth.le.BluetoothLeScanner; import android.content.Context; import android.os.Handler; import android.os.IBinder; diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java b/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java deleted file mode 100644 index 2fa5e49..0000000 --- a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java +++ /dev/null @@ -1,645 +0,0 @@ -/* - * 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.bluetooth; - -import android.annotation.Nullable; -import android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Represents Bluetooth LE advertise and scan response data. This could be either the advertisement - * data to be advertised, or the scan record obtained from BLE scans. - * <p> - * The exact bluetooth advertising and scan response data fields and types are defined in Bluetooth - * 4.0 specification, Volume 3, Part C, Section 11 and 18, as well as Supplement to the Bluetooth - * Core Specification Version 4. Currently the following fields are allowed to be set: - * <li>Service UUIDs which identify the bluetooth gatt services running on the device. - * <li>Tx power level which is the transmission power level. - * <li>Service data which is the data associated with a service. - * <li>Manufacturer specific data which is the data associated with a particular manufacturer. - * - * @see BluetoothLeAdvertiser - */ -public final class BluetoothLeAdvertiseScanData { - private static final String TAG = "BluetoothLeAdvertiseScanData"; - - /** - * Bluetooth LE Advertising Data type, the data will be placed in AdvData field of advertising - * packet. - */ - public static final int ADVERTISING_DATA = 0; - /** - * Bluetooth LE scan response data, the data will be placed in ScanRspData field of advertising - * packet. - * <p> - */ - public static final int SCAN_RESPONSE_DATA = 1; - /** - * Scan record parsed from Bluetooth LE scans. The content can contain a concatenation of - * advertising data and scan response data. - */ - public static final int PARSED_SCAN_RECORD = 2; - - /** - * Base data type which contains the common fields for {@link AdvertisementData} and - * {@link ScanRecord}. - */ - public abstract static class AdvertiseBaseData { - - private final int mDataType; - - @Nullable - private final List<ParcelUuid> mServiceUuids; - - private final int mManufacturerId; - @Nullable - private final byte[] mManufacturerSpecificData; - - @Nullable - private final ParcelUuid mServiceDataUuid; - @Nullable - private final byte[] mServiceData; - - private AdvertiseBaseData(int dataType, - List<ParcelUuid> serviceUuids, - ParcelUuid serviceDataUuid, byte[] serviceData, - int manufacturerId, - byte[] manufacturerSpecificData) { - mDataType = dataType; - mServiceUuids = serviceUuids; - mManufacturerId = manufacturerId; - mManufacturerSpecificData = manufacturerSpecificData; - mServiceDataUuid = serviceDataUuid; - mServiceData = serviceData; - } - - /** - * Returns the type of data, indicating whether the data is advertising data, scan response - * data or scan record. - */ - public int getDataType() { - return mDataType; - } - - /** - * Returns a list of service uuids within the advertisement that are used to identify the - * bluetooth gatt services. - */ - public List<ParcelUuid> getServiceUuids() { - return mServiceUuids; - } - - /** - * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth - * SIG. - */ - public int getManufacturerId() { - return mManufacturerId; - } - - /** - * Returns the manufacturer specific data which is the content of manufacturer specific data - * field. The first 2 bytes of the data contain the company id. - */ - public byte[] getManufacturerSpecificData() { - return mManufacturerSpecificData; - } - - /** - * Returns a 16 bit uuid of the service that the service data is associated with. - */ - public ParcelUuid getServiceDataUuid() { - return mServiceDataUuid; - } - - /** - * Returns service data. The first two bytes should be a 16 bit service uuid associated with - * the service data. - */ - public byte[] getServiceData() { - return mServiceData; - } - - @Override - public String toString() { - return "AdvertiseBaseData [mDataType=" + mDataType + ", mServiceUuids=" + mServiceUuids - + ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData=" - + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid=" - + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData) + "]"; - } - } - - /** - * Advertisement data packet for Bluetooth LE advertising. This represents the data to be - * broadcasted in Bluetooth LE advertising. - * <p> - * Use {@link AdvertisementData.Builder} to create an instance of {@link AdvertisementData} to - * be advertised. - * - * @see BluetoothLeAdvertiser - */ - public static final class AdvertisementData extends AdvertiseBaseData implements Parcelable { - - private boolean mIncludeTxPowerLevel; - - /** - * Whether the transmission power level will be included in the advertisement packet. - */ - public boolean getIncludeTxPowerLevel() { - return mIncludeTxPowerLevel; - } - - /** - * Returns a {@link Builder} to build {@link AdvertisementData}. - */ - public static Builder newBuilder() { - return new Builder(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(getDataType()); - List<ParcelUuid> uuids = getServiceUuids(); - if (uuids == null) { - dest.writeInt(0); - } else { - dest.writeInt(uuids.size()); - dest.writeList(uuids); - } - - dest.writeInt(getManufacturerId()); - byte[] manufacturerData = getManufacturerSpecificData(); - if (manufacturerData == null) { - dest.writeInt(0); - } else { - dest.writeInt(manufacturerData.length); - dest.writeByteArray(manufacturerData); - } - - ParcelUuid serviceDataUuid = getServiceDataUuid(); - if (serviceDataUuid == null) { - dest.writeInt(0); - } else { - dest.writeInt(1); - dest.writeParcelable(serviceDataUuid, flags); - byte[] serviceData = getServiceData(); - if (serviceData == null) { - dest.writeInt(0); - } else { - dest.writeInt(serviceData.length); - dest.writeByteArray(serviceData); - } - } - dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0)); - } - - private AdvertisementData(int dataType, - List<ParcelUuid> serviceUuids, - ParcelUuid serviceDataUuid, byte[] serviceData, - int manufacturerId, - byte[] manufacturerSpecificData, boolean mIncludeTxPowerLevel) { - super(dataType, serviceUuids, serviceDataUuid, serviceData, manufacturerId, - manufacturerSpecificData); - this.mIncludeTxPowerLevel = mIncludeTxPowerLevel; - } - - public static final Parcelable.Creator<AdvertisementData> CREATOR = - new Creator<AdvertisementData>() { - @Override - public AdvertisementData[] newArray(int size) { - return new AdvertisementData[size]; - } - - @Override - public AdvertisementData createFromParcel(Parcel in) { - Builder builder = newBuilder(); - int dataType = in.readInt(); - builder.dataType(dataType); - if (in.readInt() > 0) { - List<ParcelUuid> uuids = new ArrayList<ParcelUuid>(); - in.readList(uuids, ParcelUuid.class.getClassLoader()); - builder.serviceUuids(uuids); - } - int manufacturerId = in.readInt(); - int manufacturerDataLength = in.readInt(); - if (manufacturerDataLength > 0) { - byte[] manufacturerData = new byte[manufacturerDataLength]; - in.readByteArray(manufacturerData); - builder.manufacturerData(manufacturerId, manufacturerData); - } - if (in.readInt() == 1) { - ParcelUuid serviceDataUuid = in.readParcelable( - ParcelUuid.class.getClassLoader()); - int serviceDataLength = in.readInt(); - if (serviceDataLength > 0) { - byte[] serviceData = new byte[serviceDataLength]; - in.readByteArray(serviceData); - builder.serviceData(serviceDataUuid, serviceData); - } - } - builder.includeTxPowerLevel(in.readByte() == 1); - return builder.build(); - } - }; - - /** - * Builder for {@link BluetoothLeAdvertiseScanData.AdvertisementData}. Use - * {@link AdvertisementData#newBuilder()} to get an instance of the Builder. - */ - public static final class Builder { - private static final int MAX_ADVERTISING_DATA_BYTES = 31; - // Each fields need one byte for field length and another byte for field type. - private static final int OVERHEAD_BYTES_PER_FIELD = 2; - // Flags field will be set by system. - private static final int FLAGS_FIELD_BYTES = 3; - - private int mDataType; - @Nullable - private List<ParcelUuid> mServiceUuids; - private boolean mIncludeTxPowerLevel; - private int mManufacturerId; - @Nullable - private byte[] mManufacturerSpecificData; - @Nullable - private ParcelUuid mServiceDataUuid; - @Nullable - private byte[] mServiceData; - - /** - * Set data type. - * - * @param dataType Data type, could only be - * {@link BluetoothLeAdvertiseScanData#ADVERTISING_DATA} - * @throws IllegalArgumentException If the {@code dataType} is invalid. - */ - public Builder dataType(int dataType) { - if (mDataType != ADVERTISING_DATA && mDataType != SCAN_RESPONSE_DATA) { - throw new IllegalArgumentException("invalid data type - " + dataType); - } - mDataType = dataType; - return this; - } - - /** - * Set the service uuids. Note the corresponding bluetooth Gatt services need to be - * already added on the device before start BLE advertising. - * - * @param serviceUuids Service uuids to be advertised, could be 16-bit, 32-bit or - * 128-bit uuids. - * @throws IllegalArgumentException If the {@code serviceUuids} are null. - */ - public Builder serviceUuids(List<ParcelUuid> serviceUuids) { - if (serviceUuids == null) { - throw new IllegalArgumentException("serivceUuids are null"); - } - mServiceUuids = serviceUuids; - return this; - } - - /** - * Add service data to advertisement. - * - * @param serviceDataUuid A 16 bit uuid of the service data - * @param serviceData Service data - the first two bytes of the service data are the - * service data uuid. - * @throws IllegalArgumentException If the {@code serviceDataUuid} or - * {@code serviceData} is empty. - */ - public Builder serviceData(ParcelUuid serviceDataUuid, byte[] serviceData) { - if (serviceDataUuid == null || serviceData == null) { - throw new IllegalArgumentException( - "serviceDataUuid or serviceDataUuid is null"); - } - mServiceDataUuid = serviceDataUuid; - mServiceData = serviceData; - return this; - } - - /** - * Set manufacturer id and data. See <a - * href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">assigned - * manufacturer identifies</a> for the existing company identifiers. - * - * @param manufacturerId Manufacturer id assigned by Bluetooth SIG. - * @param manufacturerSpecificData Manufacturer specific data - the first two bytes of - * the manufacturer specific data are the manufacturer id. - * @throws IllegalArgumentException If the {@code manufacturerId} is negative or - * {@code manufacturerSpecificData} is null. - */ - public Builder manufacturerData(int manufacturerId, byte[] manufacturerSpecificData) { - if (manufacturerId < 0) { - throw new IllegalArgumentException( - "invalid manufacturerId - " + manufacturerId); - } - if (manufacturerSpecificData == null) { - throw new IllegalArgumentException("manufacturerSpecificData is null"); - } - mManufacturerId = manufacturerId; - mManufacturerSpecificData = manufacturerSpecificData; - return this; - } - - /** - * Whether the transmission power level should be included in the advertising packet. - */ - public Builder includeTxPowerLevel(boolean includeTxPowerLevel) { - mIncludeTxPowerLevel = includeTxPowerLevel; - return this; - } - - /** - * Build the {@link BluetoothLeAdvertiseScanData}. - * - * @throws IllegalArgumentException If the data size is larger than 31 bytes. - */ - public AdvertisementData build() { - if (totalBytes() > MAX_ADVERTISING_DATA_BYTES) { - throw new IllegalArgumentException( - "advertisement data size is larger than 31 bytes"); - } - return new AdvertisementData(mDataType, - mServiceUuids, - mServiceDataUuid, - mServiceData, mManufacturerId, mManufacturerSpecificData, - mIncludeTxPowerLevel); - } - - // Compute the size of the advertisement data. - private int totalBytes() { - int size = FLAGS_FIELD_BYTES; // flags field is always set. - if (mServiceUuids != null) { - int num16BitUuids = 0; - int num32BitUuids = 0; - int num128BitUuids = 0; - for (ParcelUuid uuid : mServiceUuids) { - if (BluetoothUuid.is16BitUuid(uuid)) { - ++num16BitUuids; - } else if (BluetoothUuid.is32BitUuid(uuid)) { - ++num32BitUuids; - } else { - ++num128BitUuids; - } - } - // 16 bit service uuids are grouped into one field when doing advertising. - if (num16BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + - num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; - } - // 32 bit service uuids are grouped into one field when doing advertising. - if (num32BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + - num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; - } - // 128 bit service uuids are grouped into one field when doing advertising. - if (num128BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + - num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; - } - } - if (mServiceData != null) { - size += OVERHEAD_BYTES_PER_FIELD + mServiceData.length; - } - if (mManufacturerSpecificData != null) { - size += OVERHEAD_BYTES_PER_FIELD + mManufacturerSpecificData.length; - } - if (mIncludeTxPowerLevel) { - size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. - } - return size; - } - } - - } - - /** - * Represents a scan record from Bluetooth LE scan. - */ - public static final class ScanRecord extends AdvertiseBaseData { - // Flags of the advertising data. - private final int mAdvertiseFlags; - - // Transmission power level(in dB). - private final int mTxPowerLevel; - - // Local name of the Bluetooth LE device. - private final String mLocalName; - - /** - * Returns the advertising flags indicating the discoverable mode and capability of the - * device. Returns -1 if the flag field is not set. - */ - public int getAdvertiseFlags() { - return mAdvertiseFlags; - } - - /** - * Returns the transmission power level of the packet in dBm. Returns - * {@link Integer#MIN_VALUE} if the field is not set. This value can be used to calculate - * the path loss of a received packet using the following equation: - * <p> - * <code>pathloss = txPowerLevel - rssi</code> - */ - public int getTxPowerLevel() { - return mTxPowerLevel; - } - - /** - * Returns the local name of the BLE device. The is a UTF-8 encoded string. - */ - @Nullable - public String getLocalName() { - return mLocalName; - } - - ScanRecord(int dataType, - List<ParcelUuid> serviceUuids, - ParcelUuid serviceDataUuid, byte[] serviceData, - int manufacturerId, - byte[] manufacturerSpecificData, int advertiseFlags, int txPowerLevel, - String localName) { - super(dataType, serviceUuids, serviceDataUuid, serviceData, manufacturerId, - manufacturerSpecificData); - mLocalName = localName; - mAdvertiseFlags = advertiseFlags; - mTxPowerLevel = txPowerLevel; - } - - /** - * Get a {@link Parser} to parse the scan record byte array into {@link ScanRecord}. - */ - public static Parser getParser() { - return new Parser(); - } - - /** - * A parser class used to parse a Bluetooth LE scan record to - * {@link BluetoothLeAdvertiseScanData}. Note not all field types would be parsed. - */ - public static final class Parser { - private static final String PARSER_TAG = "BluetoothLeAdvertiseDataParser"; - - // The following data type values are assigned by Bluetooth SIG. - // For more details refer to Bluetooth 4.0 specification, Volume 3, Part C, Section 18. - private static final int DATA_TYPE_FLAGS = 0x01; - private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02; - private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03; - private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04; - private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05; - private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06; - private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07; - private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08; - private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09; - private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A; - private static final int DATA_TYPE_SERVICE_DATA = 0x16; - private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; - - // Helper method to extract bytes from byte array. - private static byte[] extractBytes(byte[] scanRecord, int start, int length) { - byte[] bytes = new byte[length]; - System.arraycopy(scanRecord, start, bytes, 0, length); - return bytes; - } - - /** - * Parse scan record to {@link BluetoothLeAdvertiseScanData.ScanRecord}. - * <p> - * The format is defined in Bluetooth 4.0 specification, Volume 3, Part C, Section 11 - * and 18. - * <p> - * All numerical multi-byte entities and values shall use little-endian - * <strong>byte</strong> order. - * - * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response. - */ - public ScanRecord parseFromScanRecord(byte[] scanRecord) { - if (scanRecord == null) { - return null; - } - - int currentPos = 0; - int advertiseFlag = -1; - List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>(); - String localName = null; - int txPowerLevel = Integer.MIN_VALUE; - ParcelUuid serviceDataUuid = null; - byte[] serviceData = null; - int manufacturerId = -1; - byte[] manufacturerSpecificData = null; - - try { - while (currentPos < scanRecord.length) { - // length is unsigned int. - int length = scanRecord[currentPos++] & 0xFF; - if (length == 0) { - break; - } - // Note the length includes the length of the field type itself. - int dataLength = length - 1; - // fieldType is unsigned int. - int fieldType = scanRecord[currentPos++] & 0xFF; - switch (fieldType) { - case DATA_TYPE_FLAGS: - advertiseFlag = scanRecord[currentPos] & 0xFF; - break; - case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, - dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids); - break; - case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids); - break; - case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids); - break; - case DATA_TYPE_LOCAL_NAME_SHORT: - case DATA_TYPE_LOCAL_NAME_COMPLETE: - localName = new String( - extractBytes(scanRecord, currentPos, dataLength)); - break; - case DATA_TYPE_TX_POWER_LEVEL: - txPowerLevel = scanRecord[currentPos]; - break; - case DATA_TYPE_SERVICE_DATA: - serviceData = extractBytes(scanRecord, currentPos, dataLength); - // The first two bytes of the service data are service data uuid. - int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT; - byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos, - serviceUuidLength); - serviceDataUuid = BluetoothUuid.parseUuidFrom(serviceDataUuidBytes); - break; - case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA: - manufacturerSpecificData = extractBytes(scanRecord, currentPos, - dataLength); - // The first two bytes of the manufacturer specific data are - // manufacturer ids in little endian. - manufacturerId = ((manufacturerSpecificData[1] & 0xFF) << 8) + - (manufacturerSpecificData[0] & 0xFF); - break; - default: - // Just ignore, we don't handle such data type. - break; - } - currentPos += dataLength; - } - - if (serviceUuids.isEmpty()) { - serviceUuids = null; - } - return new ScanRecord(PARSED_SCAN_RECORD, - serviceUuids, serviceDataUuid, serviceData, - manufacturerId, manufacturerSpecificData, advertiseFlag, txPowerLevel, - localName); - } catch (IndexOutOfBoundsException e) { - Log.e(PARSER_TAG, - "unable to parse scan record: " + Arrays.toString(scanRecord)); - return null; - } - } - - // Parse service uuids. - private int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, - int uuidLength, List<ParcelUuid> serviceUuids) { - while (dataLength > 0) { - byte[] uuidBytes = extractBytes(scanRecord, currentPos, - uuidLength); - serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes)); - dataLength -= uuidLength; - currentPos += uuidLength; - } - return currentPos; - } - } - } - -} diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/BluetoothLeAdvertiser.java deleted file mode 100644 index 30c90c4..0000000 --- a/core/java/android/bluetooth/BluetoothLeAdvertiser.java +++ /dev/null @@ -1,615 +0,0 @@ -/* - * 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.bluetooth; - -import android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData; -import android.os.Handler; -import android.os.Looper; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; -import android.os.RemoteException; -import android.util.Log; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -/** - * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop - * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by - * {@link BluetoothLeAdvertiseScanData.AdvertisementData}. - * <p> - * To get an instance of {@link BluetoothLeAdvertiser}, call the - * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method. - * <p> - * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * permission. - * - * @see BluetoothLeAdvertiseScanData.AdvertisementData - */ -public class BluetoothLeAdvertiser { - - private static final String TAG = "BluetoothLeAdvertiser"; - - /** - * The {@link Settings} provide a way to adjust advertising preferences for each individual - * advertisement. Use {@link Settings.Builder} to create a {@link Settings} instance. - */ - public static final class Settings implements Parcelable { - /** - * Perform Bluetooth LE advertising in low power mode. This is the default and preferred - * advertising mode as it consumes the least power. - */ - public static final int ADVERTISE_MODE_LOW_POWER = 0; - /** - * Perform Bluetooth LE advertising in balanced power mode. This is balanced between - * advertising frequency and power consumption. - */ - public static final int ADVERTISE_MODE_BALANCED = 1; - /** - * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest - * power consumption and should not be used for background continuous advertising. - */ - public static final int ADVERTISE_MODE_LOW_LATENCY = 2; - - /** - * Advertise using the lowest transmission(tx) power level. An app can use low transmission - * power to restrict the visibility range of its advertising packet. - */ - public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; - /** - * Advertise using low tx power level. - */ - public static final int ADVERTISE_TX_POWER_LOW = 1; - /** - * Advertise using medium tx power level. - */ - public static final int ADVERTISE_TX_POWER_MEDIUM = 2; - /** - * Advertise using high tx power level. This is corresponding to largest visibility range of - * the advertising packet. - */ - public static final int ADVERTISE_TX_POWER_HIGH = 3; - - /** - * Non-connectable undirected advertising event, as defined in Bluetooth Specification V4.0 - * vol6, part B, section 4.4.2 - Advertising state. - */ - public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0; - /** - * Scannable undirected advertise type, as defined in same spec mentioned above. This event - * type allows a scanner to send a scan request asking additional information about the - * advertiser. - */ - public static final int ADVERTISE_TYPE_SCANNABLE = 1; - /** - * Connectable undirected advertising type, as defined in same spec mentioned above. This - * event type allows a scanner to send scan request asking additional information about the - * advertiser. It also allows an initiator to send a connect request for connection. - */ - public static final int ADVERTISE_TYPE_CONNECTABLE = 2; - - private final int mAdvertiseMode; - private final int mAdvertiseTxPowerLevel; - private final int mAdvertiseEventType; - - private Settings(int advertiseMode, int advertiseTxPowerLevel, - int advertiseEventType) { - mAdvertiseMode = advertiseMode; - mAdvertiseTxPowerLevel = advertiseTxPowerLevel; - mAdvertiseEventType = advertiseEventType; - } - - private Settings(Parcel in) { - mAdvertiseMode = in.readInt(); - mAdvertiseTxPowerLevel = in.readInt(); - mAdvertiseEventType = in.readInt(); - } - - /** - * Creates a {@link Builder} to construct a {@link Settings} object. - */ - public static Builder newBuilder() { - return new Builder(); - } - - /** - * Returns the advertise mode. - */ - public int getMode() { - return mAdvertiseMode; - } - - /** - * Returns the tx power level for advertising. - */ - public int getTxPowerLevel() { - return mAdvertiseTxPowerLevel; - } - - /** - * Returns the advertise event type. - */ - public int getType() { - return mAdvertiseEventType; - } - - @Override - public String toString() { - return "Settings [mAdvertiseMode=" + mAdvertiseMode + ", mAdvertiseTxPowerLevel=" - + mAdvertiseTxPowerLevel + ", mAdvertiseEventType=" + mAdvertiseEventType + "]"; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mAdvertiseMode); - dest.writeInt(mAdvertiseTxPowerLevel); - dest.writeInt(mAdvertiseEventType); - } - - public static final Parcelable.Creator<Settings> CREATOR = - new Creator<BluetoothLeAdvertiser.Settings>() { - @Override - public Settings[] newArray(int size) { - return new Settings[size]; - } - - @Override - public Settings createFromParcel(Parcel in) { - return new Settings(in); - } - }; - - /** - * Builder class for {@link BluetoothLeAdvertiser.Settings}. Caller should use - * {@link Settings#newBuilder()} to get an instance of the builder. - */ - public static final class Builder { - private int mMode = ADVERTISE_MODE_LOW_POWER; - private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM; - private int mType = ADVERTISE_TYPE_NON_CONNECTABLE; - - // Private constructor, use Settings.newBuilder() get an instance of BUILDER. - private Builder() { - } - - /** - * Set advertise mode to control the advertising power and latency. - * - * @param advertiseMode Bluetooth LE Advertising mode, can only be one of - * {@link Settings#ADVERTISE_MODE_LOW_POWER}, - * {@link Settings#ADVERTISE_MODE_BALANCED}, or - * {@link Settings#ADVERTISE_MODE_LOW_LATENCY}. - * @throws IllegalArgumentException If the advertiseMode is invalid. - */ - public Builder advertiseMode(int advertiseMode) { - if (advertiseMode < ADVERTISE_MODE_LOW_POWER - || advertiseMode > ADVERTISE_MODE_LOW_LATENCY) { - throw new IllegalArgumentException("unknown mode " + advertiseMode); - } - mMode = advertiseMode; - return this; - } - - /** - * Set advertise tx power level to control the transmission power level for the - * advertising. - * - * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one - * of {@link Settings#ADVERTISE_TX_POWER_ULTRA_LOW}, - * {@link Settings#ADVERTISE_TX_POWER_LOW}, - * {@link Settings#ADVERTISE_TX_POWER_MEDIUM} or - * {@link Settings#ADVERTISE_TX_POWER_HIGH}. - * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid. - */ - public Builder txPowerLevel(int txPowerLevel) { - if (txPowerLevel < ADVERTISE_TX_POWER_ULTRA_LOW - || txPowerLevel > ADVERTISE_TX_POWER_HIGH) { - throw new IllegalArgumentException("unknown tx power level " + txPowerLevel); - } - mTxPowerLevel = txPowerLevel; - return this; - } - - /** - * Set advertise type to control the event type of advertising. - * - * @param type Bluetooth LE Advertising type, can be either - * {@link Settings#ADVERTISE_TYPE_NON_CONNECTABLE}, - * {@link Settings#ADVERTISE_TYPE_SCANNABLE} or - * {@link Settings#ADVERTISE_TYPE_CONNECTABLE}. - * @throws IllegalArgumentException If the {@code type} is invalid. - */ - public Builder type(int type) { - if (type < ADVERTISE_TYPE_NON_CONNECTABLE - || type > ADVERTISE_TYPE_CONNECTABLE) { - throw new IllegalArgumentException("unknown advertise type " + type); - } - mType = type; - return this; - } - - /** - * Build the {@link Settings} object. - */ - public Settings build() { - return new Settings(mMode, mTxPowerLevel, mType); - } - } - } - - /** - * Callback of Bluetooth LE advertising, which is used to deliver operation status for start and - * stop advertising. - */ - public interface AdvertiseCallback { - - /** - * The operation is success. - * - * @hide - */ - public static final int SUCCESS = 0; - /** - * Fails to start advertising as the advertisement data contains services that are not added - * to the local bluetooth Gatt server. - */ - public static final int ADVERTISING_SERVICE_UNKNOWN = 1; - /** - * Fails to start advertising as system runs out of quota for advertisers. - */ - public static final int TOO_MANY_ADVERTISERS = 2; - - /** - * Fails to start advertising as the advertising is already started. - */ - public static final int ADVERTISING_ALREADY_STARTED = 3; - /** - * Fails to stop advertising as the advertising is not started. - */ - public static final int ADVERISING_NOT_STARTED = 4; - - /** - * Operation fails due to bluetooth controller failure. - */ - public static final int CONTROLLER_FAILURE = 5; - - /** - * Callback when advertising operation succeeds. - * - * @param settingsInEffect The actual settings used for advertising, which may be different - * from what the app asks. - */ - public void onSuccess(Settings settingsInEffect); - - /** - * Callback when advertising operation fails. - * - * @param errorCode Error code for failures. - */ - public void onFailure(int errorCode); - } - - private final IBluetoothGatt mBluetoothGatt; - private final Handler mHandler; - private final Map<Settings, AdvertiseCallbackWrapper> - mLeAdvertisers = new HashMap<Settings, AdvertiseCallbackWrapper>(); - - // Package private constructor, use BluetoothAdapter.getLeAdvertiser() instead. - BluetoothLeAdvertiser(IBluetoothGatt bluetoothGatt) { - mBluetoothGatt = bluetoothGatt; - mHandler = new Handler(Looper.getMainLooper()); - } - - /** - * Bluetooth GATT interface callbacks for advertising. - */ - private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub { - private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000; - private final AdvertiseCallback mAdvertiseCallback; - private final AdvertisementData mAdvertisement; - private final AdvertisementData mScanResponse; - private final Settings mSettings; - private final IBluetoothGatt mBluetoothGatt; - - // mLeHandle 0: not registered - // -1: scan stopped - // >0: registered and scan started - private int mLeHandle; - private boolean isAdvertising = false; - - public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback, - AdvertisementData advertiseData, AdvertisementData scanResponse, Settings settings, - IBluetoothGatt bluetoothGatt) { - mAdvertiseCallback = advertiseCallback; - mAdvertisement = advertiseData; - mScanResponse = scanResponse; - mSettings = settings; - mBluetoothGatt = bluetoothGatt; - mLeHandle = 0; - } - - public boolean advertiseStarted() { - boolean started = false; - synchronized (this) { - if (mLeHandle == -1) { - return false; - } - try { - wait(LE_CALLBACK_TIMEOUT_MILLIS); - } catch (InterruptedException e) { - Log.e(TAG, "Callback reg wait interrupted: " + e); - } - started = (mLeHandle > 0 && isAdvertising); - } - return started; - } - - public boolean advertiseStopped() { - synchronized (this) { - try { - wait(LE_CALLBACK_TIMEOUT_MILLIS); - } catch (InterruptedException e) { - Log.e(TAG, "Callback reg wait interrupted: " + e); - } - return !isAdvertising; - } - } - - /** - * Application interface registered - app is ready to go - */ - @Override - public void onClientRegistered(int status, int clientIf) { - Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf); - synchronized (this) { - if (status == BluetoothGatt.GATT_SUCCESS) { - mLeHandle = clientIf; - try { - mBluetoothGatt.startMultiAdvertising(mLeHandle, mAdvertisement, - mScanResponse, mSettings); - } catch (RemoteException e) { - Log.e(TAG, "fail to start le advertise: " + e); - mLeHandle = -1; - notifyAll(); - } catch (Exception e) { - Log.e(TAG, "fail to start advertise: " + e.getStackTrace()); - } - } else { - // registration failed - mLeHandle = -1; - notifyAll(); - } - } - } - - @Override - public void onClientConnectionState(int status, int clientIf, - boolean connected, String address) { - // no op - } - - @Override - public void onScanResult(String address, int rssi, byte[] advData) { - // no op - } - - @Override - public void onGetService(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid) { - // no op - } - - @Override - public void onGetIncludedService(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int inclSrvcType, int inclSrvcInstId, - ParcelUuid inclSrvcUuid) { - // no op - } - - @Override - public void onGetCharacteristic(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int charProps) { - // no op - } - - @Override - public void onGetDescriptor(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int descInstId, ParcelUuid descUuid) { - // no op - } - - @Override - public void onSearchComplete(String address, int status) { - // no op - } - - @Override - public void onCharacteristicRead(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, byte[] value) { - // no op - } - - @Override - public void onCharacteristicWrite(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid) { - // no op - } - - @Override - public void onNotify(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - byte[] value) { - // no op - } - - @Override - public void onDescriptorRead(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int descInstId, ParcelUuid descrUuid, byte[] value) { - // no op - } - - @Override - public void onDescriptorWrite(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int descInstId, ParcelUuid descrUuid) { - // no op - } - - @Override - public void onExecuteWrite(String address, int status) { - // no op - } - - @Override - public void onReadRemoteRssi(String address, int rssi, int status) { - // no op - } - - @Override - public void onAdvertiseStateChange(int advertiseState, int status) { - // no op - } - - @Override - public void onMultiAdvertiseCallback(int status) { - synchronized (this) { - if (status == 0) { - isAdvertising = !isAdvertising; - if (!isAdvertising) { - try { - mBluetoothGatt.unregisterClient(mLeHandle); - mLeHandle = -1; - } catch (RemoteException e) { - Log.e(TAG, "remote exception when unregistering", e); - } - } - mAdvertiseCallback.onSuccess(null); - } else { - mAdvertiseCallback.onFailure(status); - } - notifyAll(); - } - - } - - /** - * Callback reporting LE ATT MTU. - * - * @hide - */ - public void onConfigureMTU(String address, int mtu, int status) { - // no op - } - } - - /** - * Start Bluetooth LE Advertising. - * - * @param settings {@link Settings} for Bluetooth LE advertising. - * @param advertiseData {@link AdvertisementData} to be advertised. - * @param callback {@link AdvertiseCallback} for advertising status. - */ - public void startAdvertising(Settings settings, - AdvertisementData advertiseData, final AdvertiseCallback callback) { - startAdvertising(settings, advertiseData, null, callback); - } - - /** - * Start Bluetooth LE Advertising. - * @param settings {@link Settings} for Bluetooth LE advertising. - * @param advertiseData {@link AdvertisementData} to be advertised in advertisement packet. - * @param scanResponse {@link AdvertisementData} for scan response. - * @param callback {@link AdvertiseCallback} for advertising status. - */ - public void startAdvertising(Settings settings, - AdvertisementData advertiseData, AdvertisementData scanResponse, - final AdvertiseCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - if (mLeAdvertisers.containsKey(settings)) { - postCallbackFailure(callback, AdvertiseCallback.ADVERTISING_ALREADY_STARTED); - return; - } - AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData, - scanResponse, settings, mBluetoothGatt); - UUID uuid = UUID.randomUUID(); - try { - mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper); - if (wrapper.advertiseStarted()) { - mLeAdvertisers.put(settings, wrapper); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to stop advertising", e); - } - } - - /** - * Stop Bluetooth LE advertising. Returns immediately, the operation status will be delivered - * through the {@code callback}. - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * - * @param settings {@link Settings} used to start Bluetooth LE advertising. - * @param callback {@link AdvertiseCallback} for delivering stopping advertising status. - */ - public void stopAdvertising(final Settings settings, final AdvertiseCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(settings); - if (wrapper == null) { - postCallbackFailure(callback, AdvertiseCallback.ADVERISING_NOT_STARTED); - return; - } - try { - mBluetoothGatt.stopMultiAdvertising(wrapper.mLeHandle); - if (wrapper.advertiseStopped()) { - mLeAdvertisers.remove(settings); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to stop advertising", e); - } - } - - private void postCallbackFailure(final AdvertiseCallback callback, final int error) { - mHandler.post(new Runnable() { - @Override - public void run() { - callback.onFailure(error); - } - }); - } -} diff --git a/core/java/android/bluetooth/BluetoothLeScanner.java b/core/java/android/bluetooth/BluetoothLeScanner.java deleted file mode 100644 index ed3188b..0000000 --- a/core/java/android/bluetooth/BluetoothLeScanner.java +++ /dev/null @@ -1,759 +0,0 @@ -/* - * 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.bluetooth; - -import android.annotation.Nullable; -import android.os.Handler; -import android.os.Looper; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; -import android.os.RemoteException; -import android.os.SystemClock; -import android.util.Log; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -/** - * This class provides methods to perform scan related operations for Bluetooth LE devices. An - * application can scan for a particular type of BLE devices using {@link BluetoothLeScanFilter}. It - * can also request different types of callbacks for delivering the result. - * <p> - * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of - * {@link BluetoothLeScanner}. - * <p> - * Note most of the scan methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * permission. - * - * @see BluetoothLeScanFilter - */ -public class BluetoothLeScanner { - - private static final String TAG = "BluetoothLeScanner"; - private static final boolean DBG = true; - - /** - * Settings for Bluetooth LE scan. - */ - public static final class Settings implements Parcelable { - /** - * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes - * the least power. - */ - public static final int SCAN_MODE_LOW_POWER = 0; - /** - * Perform Bluetooth LE scan in balanced power mode. - */ - public static final int SCAN_MODE_BALANCED = 1; - /** - * Scan using highest duty cycle. It's recommended only using this mode when the application - * is running in foreground. - */ - public static final int SCAN_MODE_LOW_LATENCY = 2; - - /** - * Callback each time when a bluetooth advertisement is found. - */ - public static final int CALLBACK_TYPE_ON_UPDATE = 0; - /** - * Callback when a bluetooth advertisement is found for the first time. - */ - public static final int CALLBACK_TYPE_ON_FOUND = 1; - /** - * Callback when a bluetooth advertisement is found for the first time, then lost. - */ - public static final int CALLBACK_TYPE_ON_LOST = 2; - - /** - * Full scan result which contains device mac address, rssi, advertising and scan response - * and scan timestamp. - */ - public static final int SCAN_RESULT_TYPE_FULL = 0; - /** - * Truncated scan result which contains device mac address, rssi and scan timestamp. Note - * it's possible for an app to get more scan results that it asks if there are multiple apps - * using this type. TODO: decide whether we could unhide this setting. - * - * @hide - */ - public static final int SCAN_RESULT_TYPE_TRUNCATED = 1; - - // Bluetooth LE scan mode. - private int mScanMode; - - // Bluetooth LE scan callback type - private int mCallbackType; - - // Bluetooth LE scan result type - private int mScanResultType; - - // Time of delay for reporting the scan result - private long mReportDelayMicros; - - public int getScanMode() { - return mScanMode; - } - - public int getCallbackType() { - return mCallbackType; - } - - public int getScanResultType() { - return mScanResultType; - } - - /** - * Returns report delay timestamp based on the device clock. - */ - public long getReportDelayMicros() { - return mReportDelayMicros; - } - - /** - * Creates a new {@link Builder} to build {@link Settings} object. - */ - public static Builder newBuilder() { - return new Builder(); - } - - private Settings(int scanMode, int callbackType, int scanResultType, - long reportDelayMicros) { - mScanMode = scanMode; - mCallbackType = callbackType; - mScanResultType = scanResultType; - mReportDelayMicros = reportDelayMicros; - } - - private Settings(Parcel in) { - mScanMode = in.readInt(); - mCallbackType = in.readInt(); - mScanResultType = in.readInt(); - mReportDelayMicros = in.readLong(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mScanMode); - dest.writeInt(mCallbackType); - dest.writeInt(mScanResultType); - dest.writeLong(mReportDelayMicros); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<Settings> CREATOR = new Creator<Settings>() { - @Override - public Settings[] newArray(int size) { - return new Settings[size]; - } - - @Override - public Settings createFromParcel(Parcel in) { - return new Settings(in); - } - }; - - /** - * Builder for {@link BluetoothLeScanner.Settings}. - */ - public static class Builder { - private int mScanMode = SCAN_MODE_LOW_POWER; - private int mCallbackType = CALLBACK_TYPE_ON_UPDATE; - private int mScanResultType = SCAN_RESULT_TYPE_FULL; - private long mReportDelayMicros = 0; - - // Hidden constructor. - private Builder() { - } - - /** - * Set scan mode for Bluetooth LE scan. - * - * @param scanMode The scan mode can be one of {@link Settings#SCAN_MODE_LOW_POWER}, - * {@link Settings#SCAN_MODE_BALANCED} or - * {@link Settings#SCAN_MODE_LOW_LATENCY}. - * @throws IllegalArgumentException If the {@code scanMode} is invalid. - */ - public Builder scanMode(int scanMode) { - if (scanMode < SCAN_MODE_LOW_POWER || scanMode > SCAN_MODE_LOW_LATENCY) { - throw new IllegalArgumentException("invalid scan mode " + scanMode); - } - mScanMode = scanMode; - return this; - } - - /** - * Set callback type for Bluetooth LE scan. - * - * @param callbackType The callback type for the scan. Can be either one of - * {@link Settings#CALLBACK_TYPE_ON_UPDATE}, - * {@link Settings#CALLBACK_TYPE_ON_FOUND} or - * {@link Settings#CALLBACK_TYPE_ON_LOST}. - * @throws IllegalArgumentException If the {@code callbackType} is invalid. - */ - public Builder callbackType(int callbackType) { - if (callbackType < CALLBACK_TYPE_ON_UPDATE - || callbackType > CALLBACK_TYPE_ON_LOST) { - throw new IllegalArgumentException("invalid callback type - " + callbackType); - } - mCallbackType = callbackType; - return this; - } - - /** - * Set scan result type for Bluetooth LE scan. - * - * @param scanResultType Type for scan result, could be either - * {@link Settings#SCAN_RESULT_TYPE_FULL} or - * {@link Settings#SCAN_RESULT_TYPE_TRUNCATED}. - * @throws IllegalArgumentException If the {@code scanResultType} is invalid. - * @hide - */ - public Builder scanResultType(int scanResultType) { - if (scanResultType < SCAN_RESULT_TYPE_FULL - || scanResultType > SCAN_RESULT_TYPE_TRUNCATED) { - throw new IllegalArgumentException( - "invalid scanResultType - " + scanResultType); - } - mScanResultType = scanResultType; - return this; - } - - /** - * Set report delay timestamp for Bluetooth LE scan. - */ - public Builder reportDelayMicros(long reportDelayMicros) { - mReportDelayMicros = reportDelayMicros; - return this; - } - - /** - * Build {@link Settings}. - */ - public Settings build() { - return new Settings(mScanMode, mCallbackType, mScanResultType, mReportDelayMicros); - } - } - } - - /** - * ScanResult for Bluetooth LE scan. - */ - public static final class ScanResult implements Parcelable { - // Remote bluetooth device. - private BluetoothDevice mDevice; - - // Scan record, including advertising data and scan response data. - private byte[] mScanRecord; - - // Received signal strength. - private int mRssi; - - // Device timestamp when the result was last seen. - private long mTimestampMicros; - - // Constructor of scan result. - public ScanResult(BluetoothDevice device, byte[] scanRecord, int rssi, long timestampMicros) { - mDevice = device; - mScanRecord = scanRecord; - mRssi = rssi; - mTimestampMicros = timestampMicros; - } - - private ScanResult(Parcel in) { - readFromParcel(in); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - if (mDevice != null) { - dest.writeInt(1); - mDevice.writeToParcel(dest, flags); - } else { - dest.writeInt(0); - } - if (mScanRecord != null) { - dest.writeInt(1); - dest.writeByteArray(mScanRecord); - } else { - dest.writeInt(0); - } - dest.writeInt(mRssi); - dest.writeLong(mTimestampMicros); - } - - private void readFromParcel(Parcel in) { - if (in.readInt() == 1) { - mDevice = BluetoothDevice.CREATOR.createFromParcel(in); - } - if (in.readInt() == 1) { - mScanRecord = in.createByteArray(); - } - mRssi = in.readInt(); - mTimestampMicros = in.readLong(); - } - - @Override - public int describeContents() { - return 0; - } - - /** - * Returns the remote bluetooth device identified by the bluetooth device address. - */ - @Nullable - public BluetoothDevice getDevice() { - return mDevice; - } - - @Nullable /** - * Returns the scan record, which can be a combination of advertisement and scan response. - */ - public byte[] getScanRecord() { - return mScanRecord; - } - - /** - * Returns the received signal strength in dBm. The valid range is [-127, 127]. - */ - public int getRssi() { - return mRssi; - } - - /** - * Returns timestamp since boot when the scan record was observed. - */ - public long getTimestampMicros() { - return mTimestampMicros; - } - - @Override - public int hashCode() { - return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampMicros); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - ScanResult other = (ScanResult) obj; - return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) && - Objects.deepEquals(mScanRecord, other.mScanRecord) - && (mTimestampMicros == other.mTimestampMicros); - } - - @Override - public String toString() { - return "ScanResult{" + "mDevice=" + mDevice + ", mScanRecord=" - + Arrays.toString(mScanRecord) + ", mRssi=" + mRssi + ", mTimestampMicros=" - + mTimestampMicros + '}'; - } - - public static final Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() { - @Override - public ScanResult createFromParcel(Parcel source) { - return new ScanResult(source); - } - - @Override - public ScanResult[] newArray(int size) { - return new ScanResult[size]; - } - }; - - } - - /** - * Callback of Bluetooth LE scans. The results of the scans will be delivered through the - * callbacks. - */ - public interface ScanCallback { - /** - * Callback when any BLE beacon is found. - * - * @param result A Bluetooth LE scan result. - */ - public void onDeviceUpdate(ScanResult result); - - /** - * Callback when the BLE beacon is found for the first time. - * - * @param result The Bluetooth LE scan result when the onFound event is triggered. - */ - public void onDeviceFound(ScanResult result); - - /** - * Callback when the BLE device was lost. Note a device has to be "found" before it's lost. - * - * @param device The Bluetooth device that is lost. - */ - public void onDeviceLost(BluetoothDevice device); - - /** - * Callback when batch results are delivered. - * - * @param results List of scan results that are previously scanned. - */ - public void onBatchScanResults(List<ScanResult> results); - - /** - * Fails to start scan as BLE scan with the same settings is already started by the app. - */ - public static final int SCAN_ALREADY_STARTED = 1; - /** - * Fails to start scan as app cannot be registered. - */ - public static final int APPLICATION_REGISTRATION_FAILED = 2; - /** - * Fails to start scan due to gatt service failure. - */ - public static final int GATT_SERVICE_FAILURE = 3; - /** - * Fails to start scan due to controller failure. - */ - public static final int CONTROLLER_FAILURE = 4; - - /** - * Callback when scan failed. - */ - public void onScanFailed(int errorCode); - } - - private final IBluetoothGatt mBluetoothGatt; - private final Handler mHandler; - private final Map<Settings, BleScanCallbackWrapper> mLeScanClients; - - BluetoothLeScanner(IBluetoothGatt bluetoothGatt) { - mBluetoothGatt = bluetoothGatt; - mHandler = new Handler(Looper.getMainLooper()); - mLeScanClients = new HashMap<Settings, BleScanCallbackWrapper>(); - } - - /** - * Bluetooth GATT interface callbacks - */ - private static class BleScanCallbackWrapper extends IBluetoothGattCallback.Stub { - private static final int REGISTRATION_CALLBACK_TIMEOUT_SECONDS = 5; - - private final ScanCallback mScanCallback; - private final List<BluetoothLeScanFilter> mFilters; - private Settings mSettings; - private IBluetoothGatt mBluetoothGatt; - - // mLeHandle 0: not registered - // -1: scan stopped - // > 0: registered and scan started - private int mLeHandle; - - public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt, - List<BluetoothLeScanFilter> filters, Settings settings, ScanCallback scanCallback) { - mBluetoothGatt = bluetoothGatt; - mFilters = filters; - mSettings = settings; - mScanCallback = scanCallback; - mLeHandle = 0; - } - - public boolean scanStarted() { - synchronized (this) { - if (mLeHandle == -1) { - return false; - } - try { - wait(REGISTRATION_CALLBACK_TIMEOUT_SECONDS); - } catch (InterruptedException e) { - Log.e(TAG, "Callback reg wait interrupted: " + e); - } - } - return mLeHandle > 0; - } - - public void stopLeScan() { - synchronized (this) { - if (mLeHandle <= 0) { - Log.e(TAG, "Error state, mLeHandle: " + mLeHandle); - return; - } - try { - mBluetoothGatt.stopScan(mLeHandle, false); - mBluetoothGatt.unregisterClient(mLeHandle); - } catch (RemoteException e) { - Log.e(TAG, "Failed to stop scan and unregister" + e); - } - mLeHandle = -1; - notifyAll(); - } - } - - /** - * Application interface registered - app is ready to go - */ - @Override - public void onClientRegistered(int status, int clientIf) { - Log.d(TAG, "onClientRegistered() - status=" + status + - " clientIf=" + clientIf); - - synchronized (this) { - if (mLeHandle == -1) { - if (DBG) - Log.d(TAG, "onClientRegistered LE scan canceled"); - } - - if (status == BluetoothGatt.GATT_SUCCESS) { - mLeHandle = clientIf; - try { - mBluetoothGatt.startScanWithFilters(mLeHandle, false, mSettings, mFilters); - } catch (RemoteException e) { - Log.e(TAG, "fail to start le scan: " + e); - mLeHandle = -1; - } - } else { - // registration failed - mLeHandle = -1; - } - notifyAll(); - } - } - - @Override - public void onClientConnectionState(int status, int clientIf, - boolean connected, String address) { - // no op - } - - /** - * Callback reporting an LE scan result. - * - * @hide - */ - @Override - public void onScanResult(String address, int rssi, byte[] advData) { - if (DBG) - Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" + rssi); - - // Check null in case the scan has been stopped - synchronized (this) { - if (mLeHandle <= 0) - return; - } - BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( - address); - long scanMicros = TimeUnit.NANOSECONDS.toMicros(SystemClock.elapsedRealtimeNanos()); - ScanResult result = new ScanResult(device, advData, rssi, - scanMicros); - mScanCallback.onDeviceUpdate(result); - } - - @Override - public void onGetService(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid) { - // no op - } - - @Override - public void onGetIncludedService(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int inclSrvcType, int inclSrvcInstId, - ParcelUuid inclSrvcUuid) { - // no op - } - - @Override - public void onGetCharacteristic(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int charProps) { - // no op - } - - @Override - public void onGetDescriptor(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int descInstId, ParcelUuid descUuid) { - // no op - } - - @Override - public void onSearchComplete(String address, int status) { - // no op - } - - @Override - public void onCharacteristicRead(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, byte[] value) { - // no op - } - - @Override - public void onCharacteristicWrite(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid) { - // no op - } - - @Override - public void onNotify(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - byte[] value) { - // no op - } - - @Override - public void onDescriptorRead(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int descInstId, ParcelUuid descrUuid, byte[] value) { - // no op - } - - @Override - public void onDescriptorWrite(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int descInstId, ParcelUuid descrUuid) { - // no op - } - - @Override - public void onExecuteWrite(String address, int status) { - // no op - } - - @Override - public void onReadRemoteRssi(String address, int rssi, int status) { - // no op - } - - @Override - public void onAdvertiseStateChange(int advertiseState, int status) { - // no op - } - - @Override - public void onMultiAdvertiseCallback(int status) { - // no op - } - - @Override - public void onConfigureMTU(String address, int mtu, int status) { - // no op - } - } - - /** - * Scan Bluetooth LE scan. The scan results will be delivered through {@code callback}. - * - * @param filters {@link BluetoothLeScanFilter}s for finding exact BLE devices. - * @param settings Settings for ble scan. - * @param callback Callback when scan results are delivered. - * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. - */ - public void startScan(List<BluetoothLeScanFilter> filters, Settings settings, - final ScanCallback callback) { - if (settings == null || callback == null) { - throw new IllegalArgumentException("settings or callback is null"); - } - synchronized (mLeScanClients) { - if (mLeScanClients.get(settings) != null) { - postCallbackError(callback, ScanCallback.SCAN_ALREADY_STARTED); - return; - } - BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(mBluetoothGatt, filters, - settings, callback); - try { - UUID uuid = UUID.randomUUID(); - mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper); - if (wrapper.scanStarted()) { - mLeScanClients.put(settings, wrapper); - } else { - postCallbackError(callback, ScanCallback.APPLICATION_REGISTRATION_FAILED); - return; - } - } catch (RemoteException e) { - Log.e(TAG, "GATT service exception when starting scan", e); - postCallbackError(callback, ScanCallback.GATT_SERVICE_FAILURE); - } - } - } - - private void postCallbackError(final ScanCallback callback, final int errorCode) { - mHandler.post(new Runnable() { - @Override - public void run() { - callback.onScanFailed(errorCode); - } - }); - } - - /** - * Stop Bluetooth LE scan. - * - * @param settings The same settings as used in {@link #startScan}, which is used to identify - * the BLE scan. - */ - public void stopScan(Settings settings) { - synchronized (mLeScanClients) { - BleScanCallbackWrapper wrapper = mLeScanClients.remove(settings); - if (wrapper == null) { - return; - } - wrapper.stopLeScan(); - } - } - - /** - * Returns available storage size for batch scan results. It's recommended not to use batch scan - * if available storage size is small (less than 1k bytes, for instance). - * - * @hide TODO: unhide when batching is supported in stack. - */ - public int getAvailableBatchStorageSizeBytes() { - throw new UnsupportedOperationException("not impelemented"); - } - - /** - * Poll scan results from bluetooth controller. This will return Bluetooth LE scan results - * batched on bluetooth controller. - * - * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one - * used to start scan. - * @param flush Whether to flush the batch scan buffer. Note the other batch scan clients will - * get batch scan callback if the batch scan buffer is flushed. - * @return Batch Scan results. - * @hide TODO: unhide when batching is supported in stack. - */ - public List<ScanResult> getBatchScanResults(ScanCallback callback, boolean flush) { - throw new UnsupportedOperationException("not impelemented"); - } - -} diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl index ceed52b..00a0750 100644 --- a/core/java/android/bluetooth/IBluetoothGatt.aidl +++ b/core/java/android/bluetooth/IBluetoothGatt.aidl @@ -17,10 +17,10 @@ package android.bluetooth; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothLeAdvertiseScanData; -import android.bluetooth.BluetoothLeAdvertiser; -import android.bluetooth.BluetoothLeScanFilter; -import android.bluetooth.BluetoothLeScanner; +import android.bluetooth.le.AdvertiseSettings; +import android.bluetooth.le.AdvertisementData; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanSettings; import android.os.ParcelUuid; import android.bluetooth.IBluetoothGattCallback; @@ -38,13 +38,12 @@ interface IBluetoothGatt { void startScanWithUuidsScanParam(in int appIf, in boolean isServer, in ParcelUuid[] ids, int scanWindow, int scanInterval); void startScanWithFilters(in int appIf, in boolean isServer, - in BluetoothLeScanner.Settings settings, - in List<BluetoothLeScanFilter> filters); + in ScanSettings settings, in List<ScanFilter> filters); void stopScan(in int appIf, in boolean isServer); void startMultiAdvertising(in int appIf, - in BluetoothLeAdvertiseScanData.AdvertisementData advertiseData, - in BluetoothLeAdvertiseScanData.AdvertisementData scanResponse, - in BluetoothLeAdvertiser.Settings settings); + in AdvertisementData advertiseData, + in AdvertisementData scanResponse, + in AdvertiseSettings settings); void stopMultiAdvertising(in int appIf); void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback); void unregisterClient(in int clientIf); diff --git a/core/java/android/bluetooth/le/AdvertiseCallback.java b/core/java/android/bluetooth/le/AdvertiseCallback.java new file mode 100644 index 0000000..f1334c2 --- /dev/null +++ b/core/java/android/bluetooth/le/AdvertiseCallback.java @@ -0,0 +1,68 @@ +/* + * 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.bluetooth.le; + +/** + * Callback of Bluetooth LE advertising, which is used to deliver advertising operation status. + */ +public abstract class AdvertiseCallback { + + /** + * The operation is success. + * + * @hide + */ + public static final int SUCCESS = 0; + /** + * Fails to start advertising as the advertisement data contains services that are not added to + * the local bluetooth GATT server. + */ + public static final int ADVERTISE_FAILED_SERVICE_UNKNOWN = 1; + /** + * Fails to start advertising as system runs out of quota for advertisers. + */ + public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; + + /** + * Fails to start advertising as the advertising is already started. + */ + public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; + /** + * Fails to stop advertising as the advertising is not started. + */ + public static final int ADVERTISE_FAILED_NOT_STARTED = 4; + + /** + * Operation fails due to bluetooth controller failure. + */ + public static final int ADVERTISE_FAILED_CONTROLLER_FAILURE = 5; + + /** + * Callback when advertising operation succeeds. + * + * @param settingsInEffect The actual settings used for advertising, which may be different from + * what the app asks. + */ + public abstract void onSuccess(AdvertiseSettings settingsInEffect); + + /** + * Callback when advertising operation fails. + * + * @param errorCode Error code for failures. + */ + public abstract void onFailure(int errorCode); +} diff --git a/core/java/android/bluetooth/BluetoothLeScanFilter.aidl b/core/java/android/bluetooth/le/AdvertiseSettings.aidl index 86ee06d..9f47d74 100644 --- a/core/java/android/bluetooth/BluetoothLeScanFilter.aidl +++ b/core/java/android/bluetooth/le/AdvertiseSettings.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; -parcelable BluetoothLeScanFilter; +parcelable AdvertiseSettings;
\ No newline at end of file diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.java b/core/java/android/bluetooth/le/AdvertiseSettings.java new file mode 100644 index 0000000..87d0346 --- /dev/null +++ b/core/java/android/bluetooth/le/AdvertiseSettings.java @@ -0,0 +1,218 @@ +/* + * 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.bluetooth.le; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * The {@link AdvertiseSettings} provide a way to adjust advertising preferences for each + * individual advertisement. Use {@link AdvertiseSettings.Builder} to create an instance. + */ +public final class AdvertiseSettings implements Parcelable { + /** + * Perform Bluetooth LE advertising in low power mode. This is the default and preferred + * advertising mode as it consumes the least power. + */ + public static final int ADVERTISE_MODE_LOW_POWER = 0; + /** + * Perform Bluetooth LE advertising in balanced power mode. This is balanced between advertising + * frequency and power consumption. + */ + public static final int ADVERTISE_MODE_BALANCED = 1; + /** + * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest power + * consumption and should not be used for background continuous advertising. + */ + public static final int ADVERTISE_MODE_LOW_LATENCY = 2; + + /** + * Advertise using the lowest transmission(tx) power level. An app can use low transmission + * power to restrict the visibility range of its advertising packet. + */ + public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; + /** + * Advertise using low tx power level. + */ + public static final int ADVERTISE_TX_POWER_LOW = 1; + /** + * Advertise using medium tx power level. + */ + public static final int ADVERTISE_TX_POWER_MEDIUM = 2; + /** + * Advertise using high tx power level. This is corresponding to largest visibility range of the + * advertising packet. + */ + public static final int ADVERTISE_TX_POWER_HIGH = 3; + + /** + * Non-connectable undirected advertising event, as defined in Bluetooth Specification V4.1 + * vol6, part B, section 4.4.2 - Advertising state. + */ + public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0; + /** + * Scannable undirected advertise type, as defined in same spec mentioned above. This event type + * allows a scanner to send a scan request asking additional information about the advertiser. + */ + public static final int ADVERTISE_TYPE_SCANNABLE = 1; + /** + * Connectable undirected advertising type, as defined in same spec mentioned above. This event + * type allows a scanner to send scan request asking additional information about the + * advertiser. It also allows an initiator to send a connect request for connection. + */ + public static final int ADVERTISE_TYPE_CONNECTABLE = 2; + + private final int mAdvertiseMode; + private final int mAdvertiseTxPowerLevel; + private final int mAdvertiseEventType; + + private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel, + int advertiseEventType) { + mAdvertiseMode = advertiseMode; + mAdvertiseTxPowerLevel = advertiseTxPowerLevel; + mAdvertiseEventType = advertiseEventType; + } + + private AdvertiseSettings(Parcel in) { + mAdvertiseMode = in.readInt(); + mAdvertiseTxPowerLevel = in.readInt(); + mAdvertiseEventType = in.readInt(); + } + + /** + * Returns the advertise mode. + */ + public int getMode() { + return mAdvertiseMode; + } + + /** + * Returns the tx power level for advertising. + */ + public int getTxPowerLevel() { + return mAdvertiseTxPowerLevel; + } + + /** + * Returns the advertise event type. + */ + public int getType() { + return mAdvertiseEventType; + } + + @Override + public String toString() { + return "Settings [mAdvertiseMode=" + mAdvertiseMode + ", mAdvertiseTxPowerLevel=" + + mAdvertiseTxPowerLevel + ", mAdvertiseEventType=" + mAdvertiseEventType + "]"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mAdvertiseMode); + dest.writeInt(mAdvertiseTxPowerLevel); + dest.writeInt(mAdvertiseEventType); + } + + public static final Parcelable.Creator<AdvertiseSettings> CREATOR = + new Creator<AdvertiseSettings>() { + @Override + public AdvertiseSettings[] newArray(int size) { + return new AdvertiseSettings[size]; + } + + @Override + public AdvertiseSettings createFromParcel(Parcel in) { + return new AdvertiseSettings(in); + } + }; + + /** + * Builder class for {@link AdvertiseSettings}. + */ + public static final class Builder { + private int mMode = ADVERTISE_MODE_LOW_POWER; + private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM; + private int mType = ADVERTISE_TYPE_NON_CONNECTABLE; + + /** + * Set advertise mode to control the advertising power and latency. + * + * @param advertiseMode Bluetooth LE Advertising mode, can only be one of + * {@link AdvertiseSettings#ADVERTISE_MODE_LOW_POWER}, + * {@link AdvertiseSettings#ADVERTISE_MODE_BALANCED}, or + * {@link AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY}. + * @throws IllegalArgumentException If the advertiseMode is invalid. + */ + public Builder setAdvertiseMode(int advertiseMode) { + if (advertiseMode < ADVERTISE_MODE_LOW_POWER + || advertiseMode > ADVERTISE_MODE_LOW_LATENCY) { + throw new IllegalArgumentException("unknown mode " + advertiseMode); + } + mMode = advertiseMode; + return this; + } + + /** + * Set advertise tx power level to control the transmission power level for the advertising. + * + * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one of + * {@link AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW}, + * {@link AdvertiseSettings#ADVERTISE_TX_POWER_LOW}, + * {@link AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM} or + * {@link AdvertiseSettings#ADVERTISE_TX_POWER_HIGH}. + * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid. + */ + public Builder setTxPowerLevel(int txPowerLevel) { + if (txPowerLevel < ADVERTISE_TX_POWER_ULTRA_LOW + || txPowerLevel > ADVERTISE_TX_POWER_HIGH) { + throw new IllegalArgumentException("unknown tx power level " + txPowerLevel); + } + mTxPowerLevel = txPowerLevel; + return this; + } + + /** + * Set advertise type to control the event type of advertising. + * + * @param type Bluetooth LE Advertising type, can be either + * {@link AdvertiseSettings#ADVERTISE_TYPE_NON_CONNECTABLE}, + * {@link AdvertiseSettings#ADVERTISE_TYPE_SCANNABLE} or + * {@link AdvertiseSettings#ADVERTISE_TYPE_CONNECTABLE}. + * @throws IllegalArgumentException If the {@code type} is invalid. + */ + public Builder setType(int type) { + if (type < ADVERTISE_TYPE_NON_CONNECTABLE + || type > ADVERTISE_TYPE_CONNECTABLE) { + throw new IllegalArgumentException("unknown advertise type " + type); + } + mType = type; + return this; + } + + /** + * Build the {@link AdvertiseSettings} object. + */ + public AdvertiseSettings build() { + return new AdvertiseSettings(mMode, mTxPowerLevel, mType); + } + } +} diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiser.aidl b/core/java/android/bluetooth/le/AdvertisementData.aidl index 3108610..3da1321 100644 --- a/core/java/android/bluetooth/BluetoothLeAdvertiser.aidl +++ b/core/java/android/bluetooth/le/AdvertisementData.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; -parcelable BluetoothLeAdvertiser.Settings;
\ No newline at end of file +parcelable AdvertisementData;
\ No newline at end of file diff --git a/core/java/android/bluetooth/le/AdvertisementData.java b/core/java/android/bluetooth/le/AdvertisementData.java new file mode 100644 index 0000000..c587204 --- /dev/null +++ b/core/java/android/bluetooth/le/AdvertisementData.java @@ -0,0 +1,344 @@ +/* + * 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.bluetooth.le; + +import android.annotation.Nullable; +import android.bluetooth.BluetoothUuid; +import android.os.Parcel; +import android.os.ParcelUuid; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Advertisement data packet for Bluetooth LE advertising. This represents the data to be + * broadcasted in Bluetooth LE advertising as well as the scan response for active scan. + * <p> + * Use {@link AdvertisementData.Builder} to create an instance of {@link AdvertisementData} to be + * advertised. + * + * @see BluetoothLeAdvertiser + * @see ScanRecord + */ +public final class AdvertisementData implements Parcelable { + + @Nullable + private final List<ParcelUuid> mServiceUuids; + + private final int mManufacturerId; + @Nullable + private final byte[] mManufacturerSpecificData; + + @Nullable + private final ParcelUuid mServiceDataUuid; + @Nullable + private final byte[] mServiceData; + + private boolean mIncludeTxPowerLevel; + + private AdvertisementData(List<ParcelUuid> serviceUuids, + ParcelUuid serviceDataUuid, byte[] serviceData, + int manufacturerId, + byte[] manufacturerSpecificData, boolean includeTxPowerLevel) { + mServiceUuids = serviceUuids; + mManufacturerId = manufacturerId; + mManufacturerSpecificData = manufacturerSpecificData; + mServiceDataUuid = serviceDataUuid; + mServiceData = serviceData; + mIncludeTxPowerLevel = includeTxPowerLevel; + } + + /** + * Returns a list of service uuids within the advertisement that are used to identify the + * bluetooth GATT services. + */ + public List<ParcelUuid> getServiceUuids() { + return mServiceUuids; + } + + /** + * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth + * SIG. + */ + public int getManufacturerId() { + return mManufacturerId; + } + + /** + * Returns the manufacturer specific data which is the content of manufacturer specific data + * field. The first 2 bytes of the data contain the company id. + */ + public byte[] getManufacturerSpecificData() { + return mManufacturerSpecificData; + } + + /** + * Returns a 16 bit uuid of the service that the service data is associated with. + */ + public ParcelUuid getServiceDataUuid() { + return mServiceDataUuid; + } + + /** + * Returns service data. The first two bytes should be a 16 bit service uuid associated with the + * service data. + */ + public byte[] getServiceData() { + return mServiceData; + } + + /** + * Whether the transmission power level will be included in the advertisement packet. + */ + public boolean getIncludeTxPowerLevel() { + return mIncludeTxPowerLevel; + } + + @Override + public String toString() { + return "AdvertisementData [mServiceUuids=" + mServiceUuids + ", mManufacturerId=" + + mManufacturerId + ", mManufacturerSpecificData=" + + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid=" + + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData) + + ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + "]"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (mServiceUuids == null) { + dest.writeInt(0); + } else { + dest.writeInt(mServiceUuids.size()); + dest.writeList(mServiceUuids); + } + + dest.writeInt(mManufacturerId); + if (mManufacturerSpecificData == null) { + dest.writeInt(0); + } else { + dest.writeInt(mManufacturerSpecificData.length); + dest.writeByteArray(mManufacturerSpecificData); + } + + if (mServiceDataUuid == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + dest.writeParcelable(mServiceDataUuid, flags); + if (mServiceData == null) { + dest.writeInt(0); + } else { + dest.writeInt(mServiceData.length); + dest.writeByteArray(mServiceData); + } + } + dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0)); + } + + public static final Parcelable.Creator<AdvertisementData> CREATOR = + new Creator<AdvertisementData>() { + @Override + public AdvertisementData[] newArray(int size) { + return new AdvertisementData[size]; + } + + @Override + public AdvertisementData createFromParcel(Parcel in) { + Builder builder = new Builder(); + if (in.readInt() > 0) { + List<ParcelUuid> uuids = new ArrayList<ParcelUuid>(); + in.readList(uuids, ParcelUuid.class.getClassLoader()); + builder.setServiceUuids(uuids); + } + int manufacturerId = in.readInt(); + int manufacturerDataLength = in.readInt(); + if (manufacturerDataLength > 0) { + byte[] manufacturerData = new byte[manufacturerDataLength]; + in.readByteArray(manufacturerData); + builder.setManufacturerData(manufacturerId, manufacturerData); + } + if (in.readInt() == 1) { + ParcelUuid serviceDataUuid = in.readParcelable( + ParcelUuid.class.getClassLoader()); + int serviceDataLength = in.readInt(); + if (serviceDataLength > 0) { + byte[] serviceData = new byte[serviceDataLength]; + in.readByteArray(serviceData); + builder.setServiceData(serviceDataUuid, serviceData); + } + } + builder.setIncludeTxPowerLevel(in.readByte() == 1); + return builder.build(); + } + }; + + /** + * Builder for {@link AdvertisementData}. + */ + public static final class Builder { + private static final int MAX_ADVERTISING_DATA_BYTES = 31; + // Each fields need one byte for field length and another byte for field type. + private static final int OVERHEAD_BYTES_PER_FIELD = 2; + // Flags field will be set by system. + private static final int FLAGS_FIELD_BYTES = 3; + + @Nullable + private List<ParcelUuid> mServiceUuids; + private boolean mIncludeTxPowerLevel; + private int mManufacturerId; + @Nullable + private byte[] mManufacturerSpecificData; + @Nullable + private ParcelUuid mServiceDataUuid; + @Nullable + private byte[] mServiceData; + + /** + * Set the service uuids. Note the corresponding bluetooth Gatt services need to be already + * added on the device before start BLE advertising. + * + * @param serviceUuids Service uuids to be advertised, could be 16-bit, 32-bit or 128-bit + * uuids. + * @throws IllegalArgumentException If the {@code serviceUuids} are null. + */ + public Builder setServiceUuids(List<ParcelUuid> serviceUuids) { + if (serviceUuids == null) { + throw new IllegalArgumentException("serivceUuids are null"); + } + mServiceUuids = serviceUuids; + return this; + } + + /** + * Add service data to advertisement. + * + * @param serviceDataUuid A 16 bit uuid of the service data + * @param serviceData Service data - the first two bytes of the service data are the service + * data uuid. + * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is + * empty. + */ + public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) { + if (serviceDataUuid == null || serviceData == null) { + throw new IllegalArgumentException( + "serviceDataUuid or serviceDataUuid is null"); + } + mServiceDataUuid = serviceDataUuid; + mServiceData = serviceData; + return this; + } + + /** + * Set manufacturer id and data. See <a + * href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">assigned + * manufacturer identifies</a> for the existing company identifiers. + * + * @param manufacturerId Manufacturer id assigned by Bluetooth SIG. + * @param manufacturerSpecificData Manufacturer specific data - the first two bytes of the + * manufacturer specific data are the manufacturer id. + * @throws IllegalArgumentException If the {@code manufacturerId} is negative or + * {@code manufacturerSpecificData} is null. + */ + public Builder setManufacturerData(int manufacturerId, byte[] manufacturerSpecificData) { + if (manufacturerId < 0) { + throw new IllegalArgumentException( + "invalid manufacturerId - " + manufacturerId); + } + if (manufacturerSpecificData == null) { + throw new IllegalArgumentException("manufacturerSpecificData is null"); + } + mManufacturerId = manufacturerId; + mManufacturerSpecificData = manufacturerSpecificData; + return this; + } + + /** + * Whether the transmission power level should be included in the advertising packet. + */ + public Builder setIncludeTxPowerLevel(boolean includeTxPowerLevel) { + mIncludeTxPowerLevel = includeTxPowerLevel; + return this; + } + + /** + * Build the {@link AdvertisementData}. + * + * @throws IllegalArgumentException If the data size is larger than 31 bytes. + */ + public AdvertisementData build() { + if (totalBytes() > MAX_ADVERTISING_DATA_BYTES) { + throw new IllegalArgumentException( + "advertisement data size is larger than 31 bytes"); + } + return new AdvertisementData(mServiceUuids, + mServiceDataUuid, + mServiceData, mManufacturerId, mManufacturerSpecificData, + mIncludeTxPowerLevel); + } + + // Compute the size of the advertisement data. + private int totalBytes() { + int size = FLAGS_FIELD_BYTES; // flags field is always set. + if (mServiceUuids != null) { + int num16BitUuids = 0; + int num32BitUuids = 0; + int num128BitUuids = 0; + for (ParcelUuid uuid : mServiceUuids) { + if (BluetoothUuid.is16BitUuid(uuid)) { + ++num16BitUuids; + } else if (BluetoothUuid.is32BitUuid(uuid)) { + ++num32BitUuids; + } else { + ++num128BitUuids; + } + } + // 16 bit service uuids are grouped into one field when doing advertising. + if (num16BitUuids != 0) { + size += OVERHEAD_BYTES_PER_FIELD + + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; + } + // 32 bit service uuids are grouped into one field when doing advertising. + if (num32BitUuids != 0) { + size += OVERHEAD_BYTES_PER_FIELD + + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; + } + // 128 bit service uuids are grouped into one field when doing advertising. + if (num128BitUuids != 0) { + size += OVERHEAD_BYTES_PER_FIELD + + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; + } + } + if (mServiceData != null) { + size += OVERHEAD_BYTES_PER_FIELD + mServiceData.length; + } + if (mManufacturerSpecificData != null) { + size += OVERHEAD_BYTES_PER_FIELD + mManufacturerSpecificData.length; + } + if (mIncludeTxPowerLevel) { + size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. + } + return size; + } + } +} diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java new file mode 100644 index 0000000..ed43407 --- /dev/null +++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java @@ -0,0 +1,368 @@ +/* + * 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.bluetooth.le; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothGattCallback; +import android.os.Handler; +import android.os.Looper; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop + * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by + * {@link AdvertisementData}. + * <p> + * To get an instance of {@link BluetoothLeAdvertiser}, call the + * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method. + * <p> + * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @see AdvertisementData + */ +public final class BluetoothLeAdvertiser { + + private static final String TAG = "BluetoothLeAdvertiser"; + + private final IBluetoothGatt mBluetoothGatt; + private final Handler mHandler; + private final Map<AdvertiseCallback, AdvertiseCallbackWrapper> + mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>(); + + /** + * Use BluetoothAdapter.getLeAdvertiser() instead. + * + * @param bluetoothGatt + * @hide + */ + public BluetoothLeAdvertiser(IBluetoothGatt bluetoothGatt) { + mBluetoothGatt = bluetoothGatt; + mHandler = new Handler(Looper.getMainLooper()); + } + + /** + * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the + * operation succeeds. Returns immediately, the operation status are delivered through + * {@code callback}. + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param settings Settings for Bluetooth LE advertising. + * @param advertiseData Advertisement data to be broadcasted. + * @param callback Callback for advertising status. + */ + public void startAdvertising(AdvertiseSettings settings, + AdvertisementData advertiseData, final AdvertiseCallback callback) { + startAdvertising(settings, advertiseData, null, callback); + } + + /** + * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the + * operation succeeds. The {@code scanResponse} would be returned when the scanning device sends + * active scan request. Method returns immediately, the operation status are delivered through + * {@code callback}. + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @param settings Settings for Bluetooth LE advertising. + * @param advertiseData Advertisement data to be advertised in advertisement packet. + * @param scanResponse Scan response associated with the advertisement data. + * @param callback Callback for advertising status. + */ + public void startAdvertising(AdvertiseSettings settings, + AdvertisementData advertiseData, AdvertisementData scanResponse, + final AdvertiseCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + if (mLeAdvertisers.containsKey(callback)) { + postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); + return; + } + AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData, + scanResponse, settings, mBluetoothGatt); + UUID uuid = UUID.randomUUID(); + try { + mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper); + if (wrapper.advertiseStarted()) { + mLeAdvertisers.put(callback, wrapper); + } + } catch (RemoteException e) { + Log.e(TAG, "failed to stop advertising", e); + } + } + + /** + * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in + * {@link BluetoothLeAdvertiser#startAdvertising}. + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param callback {@link AdvertiseCallback} for delivering stopping advertising status. + */ + public void stopAdvertising(final AdvertiseCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback); + if (wrapper == null) { + postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_NOT_STARTED); + return; + } + try { + mBluetoothGatt.stopMultiAdvertising(wrapper.mLeHandle); + if (wrapper.advertiseStopped()) { + mLeAdvertisers.remove(callback); + } + } catch (RemoteException e) { + Log.e(TAG, "failed to stop advertising", e); + } + } + + /** + * Bluetooth GATT interface callbacks for advertising. + */ + private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub { + private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000; + private final AdvertiseCallback mAdvertiseCallback; + private final AdvertisementData mAdvertisement; + private final AdvertisementData mScanResponse; + private final AdvertiseSettings mSettings; + private final IBluetoothGatt mBluetoothGatt; + + // mLeHandle 0: not registered + // -1: scan stopped + // >0: registered and scan started + private int mLeHandle; + private boolean isAdvertising = false; + + public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback, + AdvertisementData advertiseData, AdvertisementData scanResponse, + AdvertiseSettings settings, + IBluetoothGatt bluetoothGatt) { + mAdvertiseCallback = advertiseCallback; + mAdvertisement = advertiseData; + mScanResponse = scanResponse; + mSettings = settings; + mBluetoothGatt = bluetoothGatt; + mLeHandle = 0; + } + + public boolean advertiseStarted() { + boolean started = false; + synchronized (this) { + if (mLeHandle == -1) { + return false; + } + try { + wait(LE_CALLBACK_TIMEOUT_MILLIS); + } catch (InterruptedException e) { + Log.e(TAG, "Callback reg wait interrupted: ", e); + } + started = (mLeHandle > 0 && isAdvertising); + } + return started; + } + + public boolean advertiseStopped() { + synchronized (this) { + try { + wait(LE_CALLBACK_TIMEOUT_MILLIS); + } catch (InterruptedException e) { + Log.e(TAG, "Callback reg wait interrupted: " + e); + } + return !isAdvertising; + } + } + + /** + * Application interface registered - app is ready to go + */ + @Override + public void onClientRegistered(int status, int clientIf) { + Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf); + synchronized (this) { + if (status == BluetoothGatt.GATT_SUCCESS) { + mLeHandle = clientIf; + try { + mBluetoothGatt.startMultiAdvertising(mLeHandle, mAdvertisement, + mScanResponse, mSettings); + } catch (RemoteException e) { + Log.e(TAG, "fail to start le advertise: " + e); + mLeHandle = -1; + notifyAll(); + } catch (Exception e) { + Log.e(TAG, "fail to start advertise: " + e.getStackTrace()); + } + } else { + // registration failed + mLeHandle = -1; + notifyAll(); + } + } + } + + @Override + public void onClientConnectionState(int status, int clientIf, + boolean connected, String address) { + // no op + } + + @Override + public void onScanResult(String address, int rssi, byte[] advData) { + // no op + } + + @Override + public void onGetService(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid) { + // no op + } + + @Override + public void onGetIncludedService(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int inclSrvcType, int inclSrvcInstId, + ParcelUuid inclSrvcUuid) { + // no op + } + + @Override + public void onGetCharacteristic(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int charProps) { + // no op + } + + @Override + public void onGetDescriptor(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descUuid) { + // no op + } + + @Override + public void onSearchComplete(String address, int status) { + // no op + } + + @Override + public void onCharacteristicRead(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, byte[] value) { + // no op + } + + @Override + public void onCharacteristicWrite(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid) { + // no op + } + + @Override + public void onNotify(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + byte[] value) { + // no op + } + + @Override + public void onDescriptorRead(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descrUuid, byte[] value) { + // no op + } + + @Override + public void onDescriptorWrite(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descrUuid) { + // no op + } + + @Override + public void onExecuteWrite(String address, int status) { + // no op + } + + @Override + public void onReadRemoteRssi(String address, int rssi, int status) { + // no op + } + + @Override + public void onAdvertiseStateChange(int advertiseState, int status) { + // no op + } + + @Override + public void onMultiAdvertiseCallback(int status) { + synchronized (this) { + if (status == 0) { + isAdvertising = !isAdvertising; + if (!isAdvertising) { + try { + mBluetoothGatt.unregisterClient(mLeHandle); + mLeHandle = -1; + } catch (RemoteException e) { + Log.e(TAG, "remote exception when unregistering", e); + } + } + mAdvertiseCallback.onSuccess(null); + } else { + mAdvertiseCallback.onFailure(status); + } + notifyAll(); + } + + } + + /** + * Callback reporting LE ATT MTU. + * + * @hide + */ + @Override + public void onConfigureMTU(String address, int mtu, int status) { + // no op + } + } + + private void postCallbackFailure(final AdvertiseCallback callback, final int error) { + mHandler.post(new Runnable() { + @Override + public void run() { + callback.onFailure(error); + } + }); + } +} diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java new file mode 100644 index 0000000..4c6346c --- /dev/null +++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java @@ -0,0 +1,371 @@ +/* + * 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.bluetooth.le; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothGattCallback; +import android.os.Handler; +import android.os.Looper; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * This class provides methods to perform scan related operations for Bluetooth LE devices. An + * application can scan for a particular type of BLE devices using {@link ScanFilter}. It can also + * request different types of callbacks for delivering the result. + * <p> + * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of + * {@link BluetoothLeScanner}. + * <p> + * Note most of the scan methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @see ScanFilter + */ +public final class BluetoothLeScanner { + + private static final String TAG = "BluetoothLeScanner"; + private static final boolean DBG = true; + + private final IBluetoothGatt mBluetoothGatt; + private final Handler mHandler; + private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients; + + /** + * @hide + */ + public BluetoothLeScanner(IBluetoothGatt bluetoothGatt) { + mBluetoothGatt = bluetoothGatt; + mHandler = new Handler(Looper.getMainLooper()); + mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>(); + } + + /** + * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}. + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param filters {@link ScanFilter}s for finding exact BLE devices. + * @param settings Settings for ble scan. + * @param callback Callback when scan results are delivered. + * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. + */ + public void startScan(List<ScanFilter> filters, ScanSettings settings, + final ScanCallback callback) { + if (settings == null || callback == null) { + throw new IllegalArgumentException("settings or callback is null"); + } + synchronized (mLeScanClients) { + if (mLeScanClients.containsKey(callback)) { + postCallbackError(callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED); + return; + } + BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(mBluetoothGatt, filters, + settings, callback); + try { + UUID uuid = UUID.randomUUID(); + mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper); + if (wrapper.scanStarted()) { + mLeScanClients.put(callback, wrapper); + } else { + postCallbackError(callback, + ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED); + return; + } + } catch (RemoteException e) { + Log.e(TAG, "GATT service exception when starting scan", e); + postCallbackError(callback, ScanCallback.SCAN_FAILED_GATT_SERVICE_FAILURE); + } + } + } + + /** + * Stops an ongoing Bluetooth LE scan. + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param callback + */ + public void stopScan(ScanCallback callback) { + synchronized (mLeScanClients) { + BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback); + if (wrapper == null) { + return; + } + wrapper.stopLeScan(); + } + } + + /** + * Returns available storage size for batch scan results. It's recommended not to use batch scan + * if available storage size is small (less than 1k bytes, for instance). + * + * @hide TODO: unhide when batching is supported in stack. + */ + public int getAvailableBatchStorageSizeBytes() { + throw new UnsupportedOperationException("not impelemented"); + } + + /** + * Poll scan results from bluetooth controller. This will return Bluetooth LE scan results + * batched on bluetooth controller. + * + * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one + * used to start scan. + * @param flush Whether to flush the batch scan buffer. Note the other batch scan clients will + * get batch scan callback if the batch scan buffer is flushed. + * @return Batch Scan results. + * @hide TODO: unhide when batching is supported in stack. + */ + public List<ScanResult> getBatchScanResults(ScanCallback callback, boolean flush) { + throw new UnsupportedOperationException("not impelemented"); + } + + /** + * Bluetooth GATT interface callbacks + */ + private static class BleScanCallbackWrapper extends IBluetoothGattCallback.Stub { + private static final int REGISTRATION_CALLBACK_TIMEOUT_SECONDS = 5; + + private final ScanCallback mScanCallback; + private final List<ScanFilter> mFilters; + private ScanSettings mSettings; + private IBluetoothGatt mBluetoothGatt; + + // mLeHandle 0: not registered + // -1: scan stopped + // > 0: registered and scan started + private int mLeHandle; + + public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt, + List<ScanFilter> filters, ScanSettings settings, + ScanCallback scanCallback) { + mBluetoothGatt = bluetoothGatt; + mFilters = filters; + mSettings = settings; + mScanCallback = scanCallback; + mLeHandle = 0; + } + + public boolean scanStarted() { + synchronized (this) { + if (mLeHandle == -1) { + return false; + } + try { + wait(REGISTRATION_CALLBACK_TIMEOUT_SECONDS); + } catch (InterruptedException e) { + Log.e(TAG, "Callback reg wait interrupted: " + e); + } + } + return mLeHandle > 0; + } + + public void stopLeScan() { + synchronized (this) { + if (mLeHandle <= 0) { + Log.e(TAG, "Error state, mLeHandle: " + mLeHandle); + return; + } + try { + mBluetoothGatt.stopScan(mLeHandle, false); + mBluetoothGatt.unregisterClient(mLeHandle); + } catch (RemoteException e) { + Log.e(TAG, "Failed to stop scan and unregister" + e); + } + mLeHandle = -1; + notifyAll(); + } + } + + /** + * Application interface registered - app is ready to go + */ + @Override + public void onClientRegistered(int status, int clientIf) { + Log.d(TAG, "onClientRegistered() - status=" + status + + " clientIf=" + clientIf); + + synchronized (this) { + if (mLeHandle == -1) { + if (DBG) + Log.d(TAG, "onClientRegistered LE scan canceled"); + } + + if (status == BluetoothGatt.GATT_SUCCESS) { + mLeHandle = clientIf; + try { + mBluetoothGatt.startScanWithFilters(mLeHandle, false, mSettings, mFilters); + } catch (RemoteException e) { + Log.e(TAG, "fail to start le scan: " + e); + mLeHandle = -1; + } + } else { + // registration failed + mLeHandle = -1; + } + notifyAll(); + } + } + + @Override + public void onClientConnectionState(int status, int clientIf, + boolean connected, String address) { + // no op + } + + /** + * Callback reporting an LE scan result. + * + * @hide + */ + @Override + public void onScanResult(String address, int rssi, byte[] advData) { + if (DBG) + Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" + rssi); + + // Check null in case the scan has been stopped + synchronized (this) { + if (mLeHandle <= 0) + return; + } + BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( + address); + long scanNanos = SystemClock.elapsedRealtimeNanos(); + ScanResult result = new ScanResult(device, advData, rssi, + scanNanos); + mScanCallback.onAdvertisementUpdate(result); + } + + @Override + public void onGetService(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid) { + // no op + } + + @Override + public void onGetIncludedService(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int inclSrvcType, int inclSrvcInstId, + ParcelUuid inclSrvcUuid) { + // no op + } + + @Override + public void onGetCharacteristic(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int charProps) { + // no op + } + + @Override + public void onGetDescriptor(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descUuid) { + // no op + } + + @Override + public void onSearchComplete(String address, int status) { + // no op + } + + @Override + public void onCharacteristicRead(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, byte[] value) { + // no op + } + + @Override + public void onCharacteristicWrite(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid) { + // no op + } + + @Override + public void onNotify(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + byte[] value) { + // no op + } + + @Override + public void onDescriptorRead(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descrUuid, byte[] value) { + // no op + } + + @Override + public void onDescriptorWrite(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descrUuid) { + // no op + } + + @Override + public void onExecuteWrite(String address, int status) { + // no op + } + + @Override + public void onReadRemoteRssi(String address, int rssi, int status) { + // no op + } + + @Override + public void onAdvertiseStateChange(int advertiseState, int status) { + // no op + } + + @Override + public void onMultiAdvertiseCallback(int status) { + // no op + } + + @Override + public void onConfigureMTU(String address, int mtu, int status) { + // no op + } + } + + private void postCallbackError(final ScanCallback callback, final int errorCode) { + mHandler.post(new Runnable() { + @Override + public void run() { + callback.onScanFailed(errorCode); + } + }); + } +} diff --git a/core/java/android/bluetooth/le/ScanCallback.java b/core/java/android/bluetooth/le/ScanCallback.java new file mode 100644 index 0000000..50ebf50 --- /dev/null +++ b/core/java/android/bluetooth/le/ScanCallback.java @@ -0,0 +1,79 @@ +/* + * 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.bluetooth.le; + +import java.util.List; + +/** + * Callback of Bluetooth LE scans. The results of the scans will be delivered through the callbacks. + */ +public abstract class ScanCallback { + + /** + * Fails to start scan as BLE scan with the same settings is already started by the app. + */ + public static final int SCAN_FAILED_ALREADY_STARTED = 1; + /** + * Fails to start scan as app cannot be registered. + */ + public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2; + /** + * Fails to start scan due to gatt service failure. + */ + public static final int SCAN_FAILED_GATT_SERVICE_FAILURE = 3; + /** + * Fails to start scan due to controller failure. + */ + public static final int SCAN_FAILED_CONTROLLER_FAILURE = 4; + + /** + * Callback when a BLE advertisement is found. + * + * @param result A Bluetooth LE scan result. + */ + public abstract void onAdvertisementUpdate(ScanResult result); + + /** + * Callback when the BLE advertisement is found for the first time. + * + * @param result The Bluetooth LE scan result when the onFound event is triggered. + * @hide + */ + public abstract void onAdvertisementFound(ScanResult result); + + /** + * Callback when the BLE advertisement was lost. Note a device has to be "found" before it's + * lost. + * + * @param result The Bluetooth scan result that was last found. + * @hide + */ + public abstract void onAdvertisementLost(ScanResult result); + + /** + * Callback when batch results are delivered. + * + * @param results List of scan results that are previously scanned. + * @hide + */ + public abstract void onBatchScanResults(List<ScanResult> results); + + /** + * Callback when scan failed. + */ + public abstract void onScanFailed(int errorCode); +} diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl b/core/java/android/bluetooth/le/ScanFilter.aidl index 4aa8881..4cecfe6 100644 --- a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl +++ b/core/java/android/bluetooth/le/ScanFilter.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; -parcelable BluetoothLeAdvertiseScanData.AdvertisementData;
\ No newline at end of file +parcelable ScanFilter; diff --git a/core/java/android/bluetooth/BluetoothLeScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java index 2ed85ba..c2e316b 100644 --- a/core/java/android/bluetooth/BluetoothLeScanFilter.java +++ b/core/java/android/bluetooth/le/ScanFilter.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; import android.annotation.Nullable; -import android.bluetooth.BluetoothLeAdvertiseScanData.ScanRecord; -import android.bluetooth.BluetoothLeScanner.ScanResult; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.os.Parcel; import android.os.ParcelUuid; import android.os.Parcelable; @@ -29,8 +29,7 @@ import java.util.Objects; import java.util.UUID; /** - * {@link BluetoothLeScanFilter} abstracts different scan filters across Bluetooth Advertisement - * packet fields. + * {@link ScanFilter} abstracts different scan filters across Bluetooth Advertisement packet fields. * <p> * Current filtering on the following fields are supported: * <li>Service UUIDs which identify the bluetooth gatt services running on the device. @@ -40,10 +39,10 @@ import java.util.UUID; * <li>Service data which is the data associated with a service. * <li>Manufacturer specific data which is the data associated with a particular manufacturer. * - * @see BluetoothLeAdvertiseScanData.ScanRecord + * @see ScanRecord * @see BluetoothLeScanner */ -public final class BluetoothLeScanFilter implements Parcelable { +public final class ScanFilter implements Parcelable { @Nullable private final String mLocalName; @@ -70,7 +69,7 @@ public final class BluetoothLeScanFilter implements Parcelable { private final int mMinRssi; private final int mMaxRssi; - private BluetoothLeScanFilter(String name, String macAddress, ParcelUuid uuid, + private ScanFilter(String name, String macAddress, ParcelUuid uuid, ParcelUuid uuidMask, byte[] serviceData, byte[] serviceDataMask, int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask, int minRssi, int maxRssi) { @@ -105,88 +104,93 @@ public final class BluetoothLeScanFilter implements Parcelable { dest.writeInt(mServiceUuid == null ? 0 : 1); if (mServiceUuid != null) { dest.writeParcelable(mServiceUuid, flags); - } - dest.writeInt(mServiceUuidMask == null ? 0 : 1); - if (mServiceUuidMask != null) { - dest.writeParcelable(mServiceUuidMask, flags); + dest.writeInt(mServiceUuidMask == null ? 0 : 1); + if (mServiceUuidMask != null) { + dest.writeParcelable(mServiceUuidMask, flags); + } } dest.writeInt(mServiceData == null ? 0 : mServiceData.length); if (mServiceData != null) { dest.writeByteArray(mServiceData); - } - dest.writeInt(mServiceDataMask == null ? 0 : mServiceDataMask.length); - if (mServiceDataMask != null) { - dest.writeByteArray(mServiceDataMask); + dest.writeInt(mServiceDataMask == null ? 0 : mServiceDataMask.length); + if (mServiceDataMask != null) { + dest.writeByteArray(mServiceDataMask); + } } dest.writeInt(mManufacturerId); dest.writeInt(mManufacturerData == null ? 0 : mManufacturerData.length); if (mManufacturerData != null) { dest.writeByteArray(mManufacturerData); - } - dest.writeInt(mManufacturerDataMask == null ? 0 : mManufacturerDataMask.length); - if (mManufacturerDataMask != null) { - dest.writeByteArray(mManufacturerDataMask); + dest.writeInt(mManufacturerDataMask == null ? 0 : mManufacturerDataMask.length); + if (mManufacturerDataMask != null) { + dest.writeByteArray(mManufacturerDataMask); + } } dest.writeInt(mMinRssi); dest.writeInt(mMaxRssi); } /** - * A {@link android.os.Parcelable.Creator} to create {@link BluetoothLeScanFilter} form parcel. + * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} form parcel. */ - public static final Creator<BluetoothLeScanFilter> - CREATOR = new Creator<BluetoothLeScanFilter>() { + public static final Creator<ScanFilter> + CREATOR = new Creator<ScanFilter>() { @Override - public BluetoothLeScanFilter[] newArray(int size) { - return new BluetoothLeScanFilter[size]; + public ScanFilter[] newArray(int size) { + return new ScanFilter[size]; } @Override - public BluetoothLeScanFilter createFromParcel(Parcel in) { - Builder builder = newBuilder(); + public ScanFilter createFromParcel(Parcel in) { + Builder builder = new Builder(); if (in.readInt() == 1) { - builder.name(in.readString()); + builder.setName(in.readString()); } if (in.readInt() == 1) { - builder.macAddress(in.readString()); + builder.setMacAddress(in.readString()); } if (in.readInt() == 1) { ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader()); - builder.serviceUuid(uuid); - } - if (in.readInt() == 1) { - ParcelUuid uuidMask = in.readParcelable(ParcelUuid.class.getClassLoader()); - builder.serviceUuidMask(uuidMask); + builder.setServiceUuid(uuid); + if (in.readInt() == 1) { + ParcelUuid uuidMask = in.readParcelable( + ParcelUuid.class.getClassLoader()); + builder.setServiceUuid(uuid, uuidMask); + } } + int serviceDataLength = in.readInt(); if (serviceDataLength > 0) { byte[] serviceData = new byte[serviceDataLength]; in.readByteArray(serviceData); - builder.serviceData(serviceData); - } - int serviceDataMaskLength = in.readInt(); - if (serviceDataMaskLength > 0) { - byte[] serviceDataMask = new byte[serviceDataMaskLength]; - in.readByteArray(serviceDataMask); - builder.serviceDataMask(serviceDataMask); + builder.setServiceData(serviceData); + int serviceDataMaskLength = in.readInt(); + if (serviceDataMaskLength > 0) { + byte[] serviceDataMask = new byte[serviceDataMaskLength]; + in.readByteArray(serviceDataMask); + builder.setServiceData(serviceData, serviceDataMask); + } } + int manufacturerId = in.readInt(); int manufacturerDataLength = in.readInt(); if (manufacturerDataLength > 0) { byte[] manufacturerData = new byte[manufacturerDataLength]; in.readByteArray(manufacturerData); - builder.manufacturerData(manufacturerId, manufacturerData); - } - int manufacturerDataMaskLength = in.readInt(); - if (manufacturerDataMaskLength > 0) { - byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength]; - in.readByteArray(manufacturerDataMask); - builder.manufacturerDataMask(manufacturerDataMask); + builder.setManufacturerData(manufacturerId, manufacturerData); + int manufacturerDataMaskLength = in.readInt(); + if (manufacturerDataMaskLength > 0) { + byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength]; + in.readByteArray(manufacturerDataMask); + builder.setManufacturerData(manufacturerId, manufacturerData, + manufacturerDataMask); + } } + int minRssi = in.readInt(); int maxRssi = in.readInt(); - builder.rssiRange(minRssi, maxRssi); + builder.setRssiRange(minRssi, maxRssi); return builder.build(); } }; @@ -199,9 +203,10 @@ public final class BluetoothLeScanFilter implements Parcelable { return mLocalName; } - @Nullable /** - * Returns the filter set on the service uuid. - */ + /** + * Returns the filter set on the service uuid. + */ + @Nullable public ParcelUuid getServiceUuid() { return mServiceUuid; } @@ -277,7 +282,7 @@ public final class BluetoothLeScanFilter implements Parcelable { } byte[] scanRecordBytes = scanResult.getScanRecord(); - ScanRecord scanRecord = ScanRecord.getParser().parseFromScanRecord(scanRecordBytes); + ScanRecord scanRecord = ScanRecord.parseFromBytes(scanRecordBytes); // Scan record is null but there exist filters on it. if (scanRecord == null @@ -386,13 +391,13 @@ public final class BluetoothLeScanFilter implements Parcelable { if (obj == null || getClass() != obj.getClass()) { return false; } - BluetoothLeScanFilter other = (BluetoothLeScanFilter) obj; + ScanFilter other = (ScanFilter) obj; return Objects.equals(mLocalName, other.mLocalName) && Objects.equals(mMacAddress, other.mMacAddress) && - mManufacturerId == other.mManufacturerId && + mManufacturerId == other.mManufacturerId && Objects.deepEquals(mManufacturerData, other.mManufacturerData) && Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) && - mMinRssi == other.mMinRssi && mMaxRssi == other.mMaxRssi && + mMinRssi == other.mMinRssi && mMaxRssi == other.mMaxRssi && Objects.deepEquals(mServiceData, other.mServiceData) && Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) && Objects.equals(mServiceUuid, other.mServiceUuid) && @@ -400,17 +405,9 @@ public final class BluetoothLeScanFilter implements Parcelable { } /** - * Returns the {@link Builder} for {@link BluetoothLeScanFilter}. + * Builder class for {@link ScanFilter}. */ - public static Builder newBuilder() { - return new Builder(); - } - - /** - * Builder class for {@link BluetoothLeScanFilter}. Use - * {@link BluetoothLeScanFilter#newBuilder()} to get an instance of the {@link Builder}. - */ - public static class Builder { + public static final class Builder { private String mLocalName; private String mMacAddress; @@ -428,27 +425,23 @@ public final class BluetoothLeScanFilter implements Parcelable { private int mMinRssi = Integer.MIN_VALUE; private int mMaxRssi = Integer.MAX_VALUE; - // Private constructor, use BluetoothLeScanFilter.newBuilder instead. - private Builder() { - } - /** - * Set filtering on local name. + * Set filter on local name. */ - public Builder name(String localName) { + public Builder setName(String localName) { mLocalName = localName; return this; } /** - * Set filtering on device mac address. + * Set filter on device mac address. * * @param macAddress The device mac address for the filter. It needs to be in the format of * "01:02:03:AB:CD:EF". The mac address can be validated using * {@link BluetoothAdapter#checkBluetoothAddress}. * @throws IllegalArgumentException If the {@code macAddress} is invalid. */ - public Builder macAddress(String macAddress) { + public Builder setMacAddress(String macAddress) { if (macAddress != null && !BluetoothAdapter.checkBluetoothAddress(macAddress)) { throw new IllegalArgumentException("invalid mac address " + macAddress); } @@ -457,68 +450,115 @@ public final class BluetoothLeScanFilter implements Parcelable { } /** - * Set filtering on service uuid. + * Set filter on service uuid. */ - public Builder serviceUuid(ParcelUuid serviceUuid) { + public Builder setServiceUuid(ParcelUuid serviceUuid) { mServiceUuid = serviceUuid; + mUuidMask = null; // clear uuid mask return this; } /** - * Set partial uuid filter. The {@code uuidMask} is the bit mask for the {@code uuid} set - * through {@link #serviceUuid(ParcelUuid)} method. Set any bit in the mask to 1 to indicate - * a match is needed for the bit in {@code serviceUuid}, and 0 to ignore that bit. - * <p> - * The length of {@code uuidMask} must be the same as {@code serviceUuid}. + * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the + * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the + * bit in {@code serviceUuid}, and 0 to ignore that bit. + * + * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but + * {@code uuidMask} is not {@code null}. */ - public Builder serviceUuidMask(ParcelUuid uuidMask) { + public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) { + if (mUuidMask != null && mServiceUuid == null) { + throw new IllegalArgumentException("uuid is null while uuidMask is not null!"); + } + mServiceUuid = serviceUuid; mUuidMask = uuidMask; return this; } /** - * Set service data filter. + * Set filtering on service data. */ - public Builder serviceData(byte[] serviceData) { + public Builder setServiceData(byte[] serviceData) { mServiceData = serviceData; + mServiceDataMask = null; // clear service data mask return this; } /** - * Set partial service data filter bit mask. For any bit in the mask, set it to 1 if it - * needs to match the one in service data, otherwise set it to 0 to ignore that bit. + * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to + * match the one in service data, otherwise set it to 0 to ignore that bit. * <p> - * The {@code serviceDataMask} must have the same length of the {@code serviceData} set - * through {@link #serviceData(byte[])}. + * The {@code serviceDataMask} must have the same length of the {@code serviceData}. + * + * @throws IllegalArgumentException If {@code serviceDataMask} is {@code null} while + * {@code serviceData} is not or {@code serviceDataMask} and {@code serviceData} + * has different length. */ - public Builder serviceDataMask(byte[] serviceDataMask) { + public Builder setServiceData(byte[] serviceData, byte[] serviceDataMask) { + if (mServiceDataMask != null) { + if (mServiceData == null) { + throw new IllegalArgumentException( + "serviceData is null while serviceDataMask is not null"); + } + // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two + // byte array need to be the same. + if (mServiceData.length != mServiceDataMask.length) { + throw new IllegalArgumentException( + "size mismatch for service data and service data mask"); + } + } + mServiceData = serviceData; mServiceDataMask = serviceDataMask; return this; } /** - * Set manufacturerId and manufacturerData. A negative manufacturerId is considered as - * invalid id. + * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id. * <p> * Note the first two bytes of the {@code manufacturerData} is the manufacturerId. + * + * @throws IllegalArgumentException If the {@code manufacturerId} is invalid. */ - public Builder manufacturerData(int manufacturerId, byte[] manufacturerData) { + public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) { if (manufacturerData != null && manufacturerId < 0) { throw new IllegalArgumentException("invalid manufacture id"); } mManufacturerId = manufacturerId; mManufacturerData = manufacturerData; + mManufacturerDataMask = null; // clear manufacturer data mask return this; } /** - * Set partial manufacture data filter bit mask. For any bit in the mask, set it the 1 if it + * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it * needs to match the one in manufacturer data, otherwise set it to 0. * <p> - * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData} - * set through {@link #manufacturerData(int, byte[])}. + * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}. + * + * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or + * {@code manufacturerData} is null while {@code manufacturerDataMask} is not, + * or {@code manufacturerData} and {@code manufacturerDataMask} have different + * length. */ - public Builder manufacturerDataMask(byte[] manufacturerDataMask) { + public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData, + byte[] manufacturerDataMask) { + if (manufacturerData != null && manufacturerId < 0) { + throw new IllegalArgumentException("invalid manufacture id"); + } + if (mManufacturerDataMask != null) { + if (mManufacturerData == null) { + throw new IllegalArgumentException( + "manufacturerData is null while manufacturerDataMask is not null"); + } + // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths + // of the two byte array need to be the same. + if (mManufacturerData.length != mManufacturerDataMask.length) { + throw new IllegalArgumentException( + "size mismatch for manufacturerData and manufacturerDataMask"); + } + } + mManufacturerId = manufacturerId; + mManufacturerData = manufacturerData; mManufacturerDataMask = manufacturerDataMask; return this; } @@ -527,48 +567,19 @@ public final class BluetoothLeScanFilter implements Parcelable { * Set the desired rssi range for the filter. A scan result with rssi in the range of * [minRssi, maxRssi] will be consider as a match. */ - public Builder rssiRange(int minRssi, int maxRssi) { + public Builder setRssiRange(int minRssi, int maxRssi) { mMinRssi = minRssi; mMaxRssi = maxRssi; return this; } /** - * Build {@link BluetoothLeScanFilter}. + * Build {@link ScanFilter}. * * @throws IllegalArgumentException If the filter cannot be built. */ - public BluetoothLeScanFilter build() { - if (mUuidMask != null && mServiceUuid == null) { - throw new IllegalArgumentException("uuid is null while uuidMask is not null!"); - } - - if (mServiceDataMask != null) { - if (mServiceData == null) { - throw new IllegalArgumentException( - "serviceData is null while serviceDataMask is not null"); - } - // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two - // byte array need to be the same. - if (mServiceData.length != mServiceDataMask.length) { - throw new IllegalArgumentException( - "size mismatch for service data and service data mask"); - } - } - - if (mManufacturerDataMask != null) { - if (mManufacturerData == null) { - throw new IllegalArgumentException( - "manufacturerData is null while manufacturerDataMask is not null"); - } - // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths - // of the two byte array need to be the same. - if (mManufacturerData.length != mManufacturerDataMask.length) { - throw new IllegalArgumentException( - "size mismatch for manufacturerData and manufacturerDataMask"); - } - } - return new BluetoothLeScanFilter(mLocalName, mMacAddress, + public ScanFilter build() { + return new ScanFilter(mLocalName, mMacAddress, mServiceUuid, mUuidMask, mServiceData, mServiceDataMask, mManufacturerId, mManufacturerData, mManufacturerDataMask, mMinRssi, mMaxRssi); diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java new file mode 100644 index 0000000..bd7304b --- /dev/null +++ b/core/java/android/bluetooth/le/ScanRecord.java @@ -0,0 +1,278 @@ +/* + * 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.bluetooth.le; + +import android.annotation.Nullable; +import android.bluetooth.BluetoothUuid; +import android.os.ParcelUuid; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Represents a scan record from Bluetooth LE scan. + */ +public final class ScanRecord { + + private static final String TAG = "ScanRecord"; + + // The following data type values are assigned by Bluetooth SIG. + // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18. + private static final int DATA_TYPE_FLAGS = 0x01; + private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02; + private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03; + private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04; + private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05; + private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06; + private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07; + private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08; + private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09; + private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A; + private static final int DATA_TYPE_SERVICE_DATA = 0x16; + private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; + + // Flags of the advertising data. + private final int mAdvertiseFlags; + + @Nullable + private final List<ParcelUuid> mServiceUuids; + + private final int mManufacturerId; + @Nullable + private final byte[] mManufacturerSpecificData; + + @Nullable + private final ParcelUuid mServiceDataUuid; + @Nullable + private final byte[] mServiceData; + + // Transmission power level(in dB). + private final int mTxPowerLevel; + + // Local name of the Bluetooth LE device. + private final String mLocalName; + + /** + * Returns the advertising flags indicating the discoverable mode and capability of the device. + * Returns -1 if the flag field is not set. + */ + public int getAdvertiseFlags() { + return mAdvertiseFlags; + } + + /** + * Returns a list of service uuids within the advertisement that are used to identify the + * bluetooth gatt services. + */ + public List<ParcelUuid> getServiceUuids() { + return mServiceUuids; + } + + /** + * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth + * SIG. + */ + public int getManufacturerId() { + return mManufacturerId; + } + + /** + * Returns the manufacturer specific data which is the content of manufacturer specific data + * field. The first 2 bytes of the data contain the company id. + */ + public byte[] getManufacturerSpecificData() { + return mManufacturerSpecificData; + } + + /** + * Returns a 16 bit uuid of the service that the service data is associated with. + */ + public ParcelUuid getServiceDataUuid() { + return mServiceDataUuid; + } + + /** + * Returns service data. The first two bytes should be a 16 bit service uuid associated with the + * service data. + */ + public byte[] getServiceData() { + return mServiceData; + } + + /** + * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE} + * if the field is not set. This value can be used to calculate the path loss of a received + * packet using the following equation: + * <p> + * <code>pathloss = txPowerLevel - rssi</code> + */ + public int getTxPowerLevel() { + return mTxPowerLevel; + } + + /** + * Returns the local name of the BLE device. The is a UTF-8 encoded string. + */ + @Nullable + public String getLocalName() { + return mLocalName; + } + + private ScanRecord(List<ParcelUuid> serviceUuids, + ParcelUuid serviceDataUuid, byte[] serviceData, + int manufacturerId, + byte[] manufacturerSpecificData, int advertiseFlags, int txPowerLevel, + String localName) { + mServiceUuids = serviceUuids; + mManufacturerId = manufacturerId; + mManufacturerSpecificData = manufacturerSpecificData; + mServiceDataUuid = serviceDataUuid; + mServiceData = serviceData; + mLocalName = localName; + mAdvertiseFlags = advertiseFlags; + mTxPowerLevel = txPowerLevel; + } + + @Override + public String toString() { + return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids + + ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData=" + + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid=" + + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData) + + ", mTxPowerLevel=" + mTxPowerLevel + ", mLocalName=" + mLocalName + "]"; + } + + /** + * Parse scan record bytes to {@link ScanRecord}. + * <p> + * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18. + * <p> + * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong> + * order. + * + * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response. + */ + public static ScanRecord parseFromBytes(byte[] scanRecord) { + if (scanRecord == null) { + return null; + } + + int currentPos = 0; + int advertiseFlag = -1; + List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>(); + String localName = null; + int txPowerLevel = Integer.MIN_VALUE; + ParcelUuid serviceDataUuid = null; + byte[] serviceData = null; + int manufacturerId = -1; + byte[] manufacturerSpecificData = null; + + try { + while (currentPos < scanRecord.length) { + // length is unsigned int. + int length = scanRecord[currentPos++] & 0xFF; + if (length == 0) { + break; + } + // Note the length includes the length of the field type itself. + int dataLength = length - 1; + // fieldType is unsigned int. + int fieldType = scanRecord[currentPos++] & 0xFF; + switch (fieldType) { + case DATA_TYPE_FLAGS: + advertiseFlag = scanRecord[currentPos] & 0xFF; + break; + case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL: + case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: + parseServiceUuid(scanRecord, currentPos, + dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids); + break; + case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL: + case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE: + parseServiceUuid(scanRecord, currentPos, dataLength, + BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids); + break; + case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL: + case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE: + parseServiceUuid(scanRecord, currentPos, dataLength, + BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids); + break; + case DATA_TYPE_LOCAL_NAME_SHORT: + case DATA_TYPE_LOCAL_NAME_COMPLETE: + localName = new String( + extractBytes(scanRecord, currentPos, dataLength)); + break; + case DATA_TYPE_TX_POWER_LEVEL: + txPowerLevel = scanRecord[currentPos]; + break; + case DATA_TYPE_SERVICE_DATA: + serviceData = extractBytes(scanRecord, currentPos, dataLength); + // The first two bytes of the service data are service data uuid. + int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT; + byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos, + serviceUuidLength); + serviceDataUuid = BluetoothUuid.parseUuidFrom(serviceDataUuidBytes); + break; + case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA: + manufacturerSpecificData = extractBytes(scanRecord, currentPos, + dataLength); + // The first two bytes of the manufacturer specific data are + // manufacturer ids in little endian. + manufacturerId = ((manufacturerSpecificData[1] & 0xFF) << 8) + + (manufacturerSpecificData[0] & 0xFF); + break; + default: + // Just ignore, we don't handle such data type. + break; + } + currentPos += dataLength; + } + + if (serviceUuids.isEmpty()) { + serviceUuids = null; + } + return new ScanRecord(serviceUuids, serviceDataUuid, serviceData, + manufacturerId, manufacturerSpecificData, advertiseFlag, txPowerLevel, + localName); + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord)); + return null; + } + } + + // Parse service uuids. + private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, + int uuidLength, List<ParcelUuid> serviceUuids) { + while (dataLength > 0) { + byte[] uuidBytes = extractBytes(scanRecord, currentPos, + uuidLength); + serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes)); + dataLength -= uuidLength; + currentPos += uuidLength; + } + return currentPos; + } + + // Helper method to extract bytes from byte array. + private static byte[] extractBytes(byte[] scanRecord, int start, int length) { + byte[] bytes = new byte[length]; + System.arraycopy(scanRecord, start, bytes, 0, length); + return bytes; + } +} diff --git a/core/java/android/bluetooth/BluetoothLeScanner.aidl b/core/java/android/bluetooth/le/ScanResult.aidl index 8cecdd7..3943035 100644 --- a/core/java/android/bluetooth/BluetoothLeScanner.aidl +++ b/core/java/android/bluetooth/le/ScanResult.aidl @@ -14,7 +14,6 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; -parcelable BluetoothLeScanner.ScanResult; -parcelable BluetoothLeScanner.Settings; +parcelable ScanResult;
\ No newline at end of file diff --git a/core/java/android/bluetooth/le/ScanResult.java b/core/java/android/bluetooth/le/ScanResult.java new file mode 100644 index 0000000..7e6e8f8 --- /dev/null +++ b/core/java/android/bluetooth/le/ScanResult.java @@ -0,0 +1,162 @@ +/* + * 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.bluetooth.le; + +import android.annotation.Nullable; +import android.bluetooth.BluetoothDevice; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; +import java.util.Objects; + +/** + * ScanResult for Bluetooth LE scan. + */ +public final class ScanResult implements Parcelable { + // Remote bluetooth device. + private BluetoothDevice mDevice; + + // Scan record, including advertising data and scan response data. + private byte[] mScanRecord; + + // Received signal strength. + private int mRssi; + + // Device timestamp when the result was last seen. + private long mTimestampNanos; + + /** + * Constructor of scan result. + * + * @hide + */ + public ScanResult(BluetoothDevice device, byte[] scanRecord, int rssi, + long timestampNanos) { + mDevice = device; + mScanRecord = scanRecord; + mRssi = rssi; + mTimestampNanos = timestampNanos; + } + + private ScanResult(Parcel in) { + readFromParcel(in); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (mDevice != null) { + dest.writeInt(1); + mDevice.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } + if (mScanRecord != null) { + dest.writeInt(1); + dest.writeByteArray(mScanRecord); + } else { + dest.writeInt(0); + } + dest.writeInt(mRssi); + dest.writeLong(mTimestampNanos); + } + + private void readFromParcel(Parcel in) { + if (in.readInt() == 1) { + mDevice = BluetoothDevice.CREATOR.createFromParcel(in); + } + if (in.readInt() == 1) { + mScanRecord = in.createByteArray(); + } + mRssi = in.readInt(); + mTimestampNanos = in.readLong(); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the remote bluetooth device identified by the bluetooth device address. + */ + @Nullable + public BluetoothDevice getDevice() { + return mDevice; + } + + /** + * Returns the scan record, which can be a combination of advertisement and scan response. + */ + @Nullable + public byte[] getScanRecord() { + return mScanRecord; + } + + /** + * Returns the received signal strength in dBm. The valid range is [-127, 127]. + */ + public int getRssi() { + return mRssi; + } + + /** + * Returns timestamp since boot when the scan record was observed. + */ + public long getTimestampNanos() { + return mTimestampNanos; + } + + @Override + public int hashCode() { + return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ScanResult other = (ScanResult) obj; + return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) && + Objects.deepEquals(mScanRecord, other.mScanRecord) + && (mTimestampNanos == other.mTimestampNanos); + } + + @Override + public String toString() { + return "ScanResult{" + "mDevice=" + mDevice + ", mScanRecord=" + + Arrays.toString(mScanRecord) + ", mRssi=" + mRssi + ", mTimestampNanos=" + + mTimestampNanos + '}'; + } + + public static final Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() { + @Override + public ScanResult createFromParcel(Parcel source) { + return new ScanResult(source); + } + + @Override + public ScanResult[] newArray(int size) { + return new ScanResult[size]; + } + }; + +} diff --git a/core/java/android/bluetooth/le/ScanSettings.aidl b/core/java/android/bluetooth/le/ScanSettings.aidl new file mode 100644 index 0000000..eb169c1 --- /dev/null +++ b/core/java/android/bluetooth/le/ScanSettings.aidl @@ -0,0 +1,19 @@ +/* + * 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.bluetooth.le; + +parcelable ScanSettings; diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java new file mode 100644 index 0000000..0a85675 --- /dev/null +++ b/core/java/android/bluetooth/le/ScanSettings.java @@ -0,0 +1,221 @@ +/* + * 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.bluetooth.le; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Settings for Bluetooth LE scan. + */ +public final class ScanSettings implements Parcelable { + /** + * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the + * least power. + */ + public static final int SCAN_MODE_LOW_POWER = 0; + /** + * Perform Bluetooth LE scan in balanced power mode. + */ + public static final int SCAN_MODE_BALANCED = 1; + /** + * Scan using highest duty cycle. It's recommended only using this mode when the application is + * running in foreground. + */ + public static final int SCAN_MODE_LOW_LATENCY = 2; + + /** + * Callback each time when a bluetooth advertisement is found. + */ + public static final int CALLBACK_TYPE_ON_UPDATE = 0; + /** + * Callback when a bluetooth advertisement is found for the first time. + * + * @hide + */ + public static final int CALLBACK_TYPE_ON_FOUND = 1; + /** + * Callback when a bluetooth advertisement is found for the first time, then lost. + * + * @hide + */ + public static final int CALLBACK_TYPE_ON_LOST = 2; + + /** + * Full scan result which contains device mac address, rssi, advertising and scan response and + * scan timestamp. + */ + public static final int SCAN_RESULT_TYPE_FULL = 0; + /** + * Truncated scan result which contains device mac address, rssi and scan timestamp. Note it's + * possible for an app to get more scan results that it asks if there are multiple apps using + * this type. TODO: decide whether we could unhide this setting. + * + * @hide + */ + public static final int SCAN_RESULT_TYPE_TRUNCATED = 1; + + // Bluetooth LE scan mode. + private int mScanMode; + + // Bluetooth LE scan callback type + private int mCallbackType; + + // Bluetooth LE scan result type + private int mScanResultType; + + // Time of delay for reporting the scan result + private long mReportDelayNanos; + + public int getScanMode() { + return mScanMode; + } + + public int getCallbackType() { + return mCallbackType; + } + + public int getScanResultType() { + return mScanResultType; + } + + /** + * Returns report delay timestamp based on the device clock. + */ + public long getReportDelayNanos() { + return mReportDelayNanos; + } + + private ScanSettings(int scanMode, int callbackType, int scanResultType, + long reportDelayNanos) { + mScanMode = scanMode; + mCallbackType = callbackType; + mScanResultType = scanResultType; + mReportDelayNanos = reportDelayNanos; + } + + private ScanSettings(Parcel in) { + mScanMode = in.readInt(); + mCallbackType = in.readInt(); + mScanResultType = in.readInt(); + mReportDelayNanos = in.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mScanMode); + dest.writeInt(mCallbackType); + dest.writeInt(mScanResultType); + dest.writeLong(mReportDelayNanos); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<ScanSettings> + CREATOR = new Creator<ScanSettings>() { + @Override + public ScanSettings[] newArray(int size) { + return new ScanSettings[size]; + } + + @Override + public ScanSettings createFromParcel(Parcel in) { + return new ScanSettings(in); + } + }; + + /** + * Builder for {@link ScanSettings}. + */ + public static final class Builder { + private int mScanMode = SCAN_MODE_LOW_POWER; + private int mCallbackType = CALLBACK_TYPE_ON_UPDATE; + private int mScanResultType = SCAN_RESULT_TYPE_FULL; + private long mReportDelayNanos = 0; + + /** + * Set scan mode for Bluetooth LE scan. + * + * @param scanMode The scan mode can be one of + * {@link ScanSettings#SCAN_MODE_LOW_POWER}, + * {@link ScanSettings#SCAN_MODE_BALANCED} or + * {@link ScanSettings#SCAN_MODE_LOW_LATENCY}. + * @throws IllegalArgumentException If the {@code scanMode} is invalid. + */ + public Builder setScanMode(int scanMode) { + if (scanMode < SCAN_MODE_LOW_POWER || scanMode > SCAN_MODE_LOW_LATENCY) { + throw new IllegalArgumentException("invalid scan mode " + scanMode); + } + mScanMode = scanMode; + return this; + } + + /** + * Set callback type for Bluetooth LE scan. + * + * @param callbackType The callback type for the scan. Can only be + * {@link ScanSettings#CALLBACK_TYPE_ON_UPDATE}. + * @throws IllegalArgumentException If the {@code callbackType} is invalid. + */ + public Builder setCallbackType(int callbackType) { + if (callbackType < CALLBACK_TYPE_ON_UPDATE + || callbackType > CALLBACK_TYPE_ON_LOST) { + throw new IllegalArgumentException("invalid callback type - " + callbackType); + } + mCallbackType = callbackType; + return this; + } + + /** + * Set scan result type for Bluetooth LE scan. + * + * @param scanResultType Type for scan result, could be either + * {@link ScanSettings#SCAN_RESULT_TYPE_FULL} or + * {@link ScanSettings#SCAN_RESULT_TYPE_TRUNCATED}. + * @throws IllegalArgumentException If the {@code scanResultType} is invalid. + * @hide + */ + public Builder setScanResultType(int scanResultType) { + if (scanResultType < SCAN_RESULT_TYPE_FULL + || scanResultType > SCAN_RESULT_TYPE_TRUNCATED) { + throw new IllegalArgumentException( + "invalid scanResultType - " + scanResultType); + } + mScanResultType = scanResultType; + return this; + } + + /** + * Set report delay timestamp for Bluetooth LE scan. + */ + public Builder setReportDelayNanos(long reportDelayNanos) { + mReportDelayNanos = reportDelayNanos; + return this; + } + + /** + * Build {@link ScanSettings}. + */ + public ScanSettings build() { + return new ScanSettings(mScanMode, mCallbackType, mScanResultType, + mReportDelayNanos); + } + } +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b0673b5..2ff85c6 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2363,6 +2363,7 @@ public abstract class Context { * * @see #getSystemService * @see android.net.wifi.passpoint.WifiPasspointManager + * @hide */ public static final String WIFI_PASSPOINT_SERVICE = "wifipasspoint"; diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java index 9087338..5d48868 100644 --- a/core/java/android/content/pm/LauncherActivityInfo.java +++ b/core/java/android/content/pm/LauncherActivityInfo.java @@ -30,6 +30,7 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.util.DisplayMetrics; import android.util.Log; /** @@ -47,21 +48,22 @@ public class LauncherActivityInfo { private ActivityInfo mActivityInfo; private ComponentName mComponentName; private UserHandle mUser; - // TODO: Fetch this value from PM private long mFirstInstallTime; /** * Create a launchable activity object for a given ResolveInfo and user. - * + * * @param context The context for fetching resources. * @param info ResolveInfo from which to create the LauncherActivityInfo. * @param user The UserHandle of the profile to which this activity belongs. */ - LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user) { + LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user, + long firstInstallTime) { this(context); - this.mActivityInfo = info.activityInfo; - this.mComponentName = LauncherApps.getComponentName(info); - this.mUser = user; + mActivityInfo = info.activityInfo; + mComponentName = LauncherApps.getComponentName(info); + mUser = user; + mFirstInstallTime = firstInstallTime; } LauncherActivityInfo(Context context) { @@ -79,7 +81,13 @@ public class LauncherActivityInfo { } /** - * Returns the user handle of the user profile that this activity belongs to. + * Returns the user handle of the user profile that this activity belongs to. In order to + * persist the identity of the profile, do not store the UserHandle. Instead retrieve its + * serial number from UserManager. You can convert the serial number back to a UserHandle + * for later use. + * + * @see UserManager#getSerialNumberForUser(UserHandle) + * @see UserManager#getUserForSerialNumber(long) * * @return The UserHandle of the profile. */ @@ -89,7 +97,7 @@ public class LauncherActivityInfo { /** * Retrieves the label for the activity. - * + * * @return The label for the activity. */ public CharSequence getLabel() { @@ -98,8 +106,10 @@ public class LauncherActivityInfo { /** * Returns the icon for this activity, without any badging for the profile. - * @param density The preferred density of the icon, zero for default density. + * @param density The preferred density of the icon, zero for default density. Use + * density DPI values from {@link DisplayMetrics}. * @see #getBadgedIcon(int) + * @see DisplayMetrics * @return The drawable associated with the activity */ public Drawable getIcon(int density) { @@ -109,15 +119,25 @@ public class LauncherActivityInfo { /** * Returns the application flags from the ApplicationInfo of the activity. - * + * * @return Application flags + * @hide remove before shipping */ public int getApplicationFlags() { return mActivityInfo.applicationInfo.flags; } /** + * Returns the application info for the appliction this activity belongs to. + * @return + */ + public ApplicationInfo getApplicationInfo() { + return mActivityInfo.applicationInfo; + } + + /** * Returns the time at which the package was first installed. + * * @return The time of installation of the package, in milliseconds. */ public long getFirstInstallTime() { @@ -134,7 +154,9 @@ public class LauncherActivityInfo { /** * Returns the activity icon with badging appropriate for the profile. - * @param density Optional density for the icon, or 0 to use the default density. + * @param density Optional density for the icon, or 0 to use the default density. Use + * {@link DisplayMetrics} for DPI values. + * @see DisplayMetrics * @return A badged icon for the activity. */ public Drawable getBadgedIcon(int density) { diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 8025b60..04c0b9f 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -16,15 +16,18 @@ package android.content.pm; +import android.app.AppGlobals; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ILauncherApps; import android.content.pm.IOnAppsChangedListener; +import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Rect; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.util.Log; import java.util.ArrayList; @@ -36,6 +39,12 @@ import java.util.List; * managed profiles. This is mainly for use by launchers. Apps can be queried for each user profile. * Since the PackageManager will not deliver package broadcasts for other profiles, you can register * for package changes here. + * <p> + * To watch for managed profiles being added or removed, register for the following broadcasts: + * {@link Intent#ACTION_MANAGED_PROFILE_ADDED} and {@link Intent#ACTION_MANAGED_PROFILE_REMOVED}. + * <p> + * You can retrieve the list of profiles associated with this user with + * {@link UserManager#getUserProfiles()}. */ public class LauncherApps { @@ -44,12 +53,13 @@ public class LauncherApps { private Context mContext; private ILauncherApps mService; + private PackageManager mPm; private List<OnAppsChangedListener> mListeners = new ArrayList<OnAppsChangedListener>(); /** - * Callbacks for changes to this and related managed profiles. + * Callbacks for package changes to this and related managed profiles. */ public interface OnAppsChangedListener { /** @@ -57,6 +67,7 @@ public class LauncherApps { * * @param user The UserHandle of the profile that generated the change. * @param packageName The name of the package that was removed. + * @hide remove before ship */ void onPackageRemoved(UserHandle user, String packageName); @@ -65,6 +76,7 @@ public class LauncherApps { * * @param user The UserHandle of the profile that generated the change. * @param packageName The name of the package that was added. + * @hide remove before ship */ void onPackageAdded(UserHandle user, String packageName); @@ -73,6 +85,7 @@ public class LauncherApps { * * @param user The UserHandle of the profile that generated the change. * @param packageName The name of the package that has changed. + * @hide remove before ship */ void onPackageChanged(UserHandle user, String packageName); @@ -86,6 +99,7 @@ public class LauncherApps { * available. * @param replacing Indicates whether these packages are replacing * existing ones. + * @hide remove before ship */ void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing); @@ -99,14 +113,66 @@ public class LauncherApps { * unavailable. * @param replacing Indicates whether the packages are about to be * replaced with new versions. + * @hide remove before ship */ void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing); + + /** + * Indicates that a package was removed from the specified profile. + * + * @param packageName The name of the package that was removed. + * @param user The UserHandle of the profile that generated the change. + */ + void onPackageRemoved(String packageName, UserHandle user); + + /** + * Indicates that a package was added to the specified profile. + * + * @param packageName The name of the package that was added. + * @param user The UserHandle of the profile that generated the change. + */ + void onPackageAdded(String packageName, UserHandle user); + + /** + * Indicates that a package was modified in the specified profile. + * + * @param packageName The name of the package that has changed. + * @param user The UserHandle of the profile that generated the change. + */ + void onPackageChanged(String packageName, UserHandle user); + + /** + * Indicates that one or more packages have become available. For + * example, this can happen when a removable storage card has + * reappeared. + * + * @param packageNames The names of the packages that have become + * available. + * @param user The UserHandle of the profile that generated the change. + * @param replacing Indicates whether these packages are replacing + * existing ones. + */ + void onPackagesAvailable(String [] packageNames, UserHandle user, boolean replacing); + + /** + * Indicates that one or more packages have become unavailable. For + * example, this can happen when a removable storage card has been + * removed. + * + * @param packageNames The names of the packages that have become + * unavailable. + * @param user The UserHandle of the profile that generated the change. + * @param replacing Indicates whether the packages are about to be + * replaced with new versions. + */ + void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing); } /** @hide */ public LauncherApps(Context context, ILauncherApps service) { mContext = context; mService = service; + mPm = context.getPackageManager(); } /** @@ -131,7 +197,15 @@ public class LauncherApps { final int count = activities.size(); for (int i = 0; i < count; i++) { ResolveInfo ri = activities.get(i); - LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user); + long firstInstallTime = 0; + try { + firstInstallTime = mPm.getPackageInfo(ri.activityInfo.packageName, + PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime; + } catch (NameNotFoundException nnfe) { + // Sorry, can't find package + } + LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user, + firstInstallTime); if (DEBUG) { Log.v(TAG, "Returning activity for profile " + user + " : " + lai.getComponentName()); @@ -157,7 +231,15 @@ public class LauncherApps { try { ResolveInfo ri = mService.resolveActivity(intent, user); if (ri != null) { - LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user); + long firstInstallTime = 0; + try { + firstInstallTime = mPm.getPackageInfo(ri.activityInfo.packageName, + PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime; + } catch (NameNotFoundException nnfe) { + // Sorry, can't find package + } + LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user, + firstInstallTime); return info; } } catch (RemoteException re) { @@ -173,9 +255,23 @@ public class LauncherApps { * @param sourceBounds The Rect containing the source bounds of the clicked icon * @param opts Options to pass to startActivity * @param user The UserHandle of the profile + * @hide remove before ship */ public void startActivityForProfile(ComponentName component, Rect sourceBounds, Bundle opts, UserHandle user) { + startActivityForProfile(component, user, sourceBounds, opts); + } + + /** + * Starts an activity in the specified profile. + * + * @param component The ComponentName of the activity to launch + * @param user The UserHandle of the profile + * @param sourceBounds The Rect containing the source bounds of the clicked icon + * @param opts Options to pass to startActivity + */ + public void startActivityForProfile(ComponentName component, UserHandle user, Rect sourceBounds, + Bundle opts) { if (DEBUG) { Log.i(TAG, "StartActivityForProfile " + component + " " + user.getIdentifier()); } @@ -224,13 +320,15 @@ public class LauncherApps { * * @param listener The listener to add. */ - public synchronized void addOnAppsChangedListener(OnAppsChangedListener listener) { - if (listener != null && !mListeners.contains(listener)) { - mListeners.add(listener); - if (mListeners.size() == 1) { - try { - mService.addOnAppsChangedListener(mAppsChangedListener); - } catch (RemoteException re) { + public void addOnAppsChangedListener(OnAppsChangedListener listener) { + synchronized (this) { + if (listener != null && !mListeners.contains(listener)) { + mListeners.add(listener); + if (mListeners.size() == 1) { + try { + mService.addOnAppsChangedListener(mAppsChangedListener); + } catch (RemoteException re) { + } } } } @@ -242,12 +340,14 @@ public class LauncherApps { * @param listener The listener to remove. * @see #addOnAppsChangedListener(OnAppsChangedListener) */ - public synchronized void removeOnAppsChangedListener(OnAppsChangedListener listener) { - mListeners.remove(listener); - if (mListeners.size() == 0) { - try { - mService.removeOnAppsChangedListener(mAppsChangedListener); - } catch (RemoteException re) { + public void removeOnAppsChangedListener(OnAppsChangedListener listener) { + synchronized (this) { + mListeners.remove(listener); + if (mListeners.size() == 0) { + try { + mService.removeOnAppsChangedListener(mAppsChangedListener); + } catch (RemoteException re) { + } } } } @@ -261,7 +361,8 @@ public class LauncherApps { } synchronized (LauncherApps.this) { for (OnAppsChangedListener listener : mListeners) { - listener.onPackageRemoved(user, packageName); + listener.onPackageRemoved(user, packageName); // TODO: Remove before ship + listener.onPackageRemoved(packageName, user); } } } @@ -273,7 +374,8 @@ public class LauncherApps { } synchronized (LauncherApps.this) { for (OnAppsChangedListener listener : mListeners) { - listener.onPackageChanged(user, packageName); + listener.onPackageChanged(user, packageName); // TODO: Remove before ship + listener.onPackageChanged(packageName, user); } } } @@ -285,7 +387,8 @@ public class LauncherApps { } synchronized (LauncherApps.this) { for (OnAppsChangedListener listener : mListeners) { - listener.onPackageAdded(user, packageName); + listener.onPackageAdded(user, packageName); // TODO: Remove before ship + listener.onPackageAdded(packageName, user); } } } @@ -298,7 +401,8 @@ public class LauncherApps { } synchronized (LauncherApps.this) { for (OnAppsChangedListener listener : mListeners) { - listener.onPackagesAvailable(user, packageNames, replacing); + listener.onPackagesAvailable(user, packageNames, replacing); // TODO: Remove + listener.onPackagesAvailable(packageNames, user, replacing); } } } @@ -311,7 +415,8 @@ public class LauncherApps { } synchronized (LauncherApps.this) { for (OnAppsChangedListener listener : mListeners) { - listener.onPackagesUnavailable(user, packageNames, replacing); + listener.onPackagesUnavailable(user, packageNames, replacing); // TODO: Remove + listener.onPackagesUnavailable(packageNames, user, replacing); } } } diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java index 220b40d..2dce425 100644 --- a/core/java/android/ddm/DdmHandleHello.java +++ b/core/java/android/ddm/DdmHandleHello.java @@ -22,6 +22,7 @@ import org.apache.harmony.dalvik.ddmc.DdmServer; import android.util.Log; import android.os.Debug; import android.os.UserHandle; +import dalvik.system.VMRuntime; import java.nio.ByteBuffer; @@ -126,8 +127,21 @@ public class DdmHandleHello extends ChunkHandler { // appName = "unknown"; String appName = DdmHandleAppName.getAppName(); - ByteBuffer out = ByteBuffer.allocate(20 - + vmIdent.length()*2 + appName.length()*2); + VMRuntime vmRuntime = VMRuntime.getRuntime(); + String instructionSetDescription = + vmRuntime.is64Bit() ? "64-bit" : "32-bit"; + String vmInstructionSet = vmRuntime.vmInstructionSet(); + if (vmInstructionSet != null && vmInstructionSet.length() > 0) { + instructionSetDescription += " (" + vmInstructionSet + ")"; + } + String vmFlags = "CheckJNI=" + + (vmRuntime.isCheckJniEnabled() ? "true" : "false"); + + ByteBuffer out = ByteBuffer.allocate(28 + + vmIdent.length() * 2 + + appName.length() * 2 + + instructionSetDescription.length() * 2 + + vmFlags.length() * 2); out.order(ChunkHandler.CHUNK_ORDER); out.putInt(DdmServer.CLIENT_PROTOCOL_VERSION); out.putInt(android.os.Process.myPid()); @@ -136,6 +150,10 @@ public class DdmHandleHello extends ChunkHandler { putString(out, vmIdent); putString(out, appName); out.putInt(UserHandle.myUserId()); + out.putInt(instructionSetDescription.length()); + putString(out, instructionSetDescription); + out.putInt(vmFlags.length()); + putString(out, vmFlags); Chunk reply = new Chunk(CHUNK_HELO, out); diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index cea68d2..2f5b4fe 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -822,8 +822,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_ZSL * @see #REQUEST_AVAILABLE_CAPABILITIES_DNG */ - public static final Key<Integer> REQUEST_AVAILABLE_CAPABILITIES = - new Key<Integer>("android.request.availableCapabilities", int.class); + public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES = + new Key<int[]>("android.request.availableCapabilities", int[].class); /** * <p>A list of all keys that the camera device has available diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 6aa24e6..d4dfdd5 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -691,8 +691,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the - * bottom-right pixel in the active pixel array. The weight - * should be nonnegative.</p> + * bottom-right pixel in the active pixel array.</p> + * <p>The weight must range from 0 to 1000, and represents a weight + * for every pixel in the area. This means that a large metering area + * with the same weight as a smaller area will have more effect in + * the metering result. Metering areas can partially overlap and the + * camera device will add the weights in the overlap region.</p> * <p>If all regions have 0 weight, then no specific metering area * needs to be used by the camera device. If the metering region is * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, @@ -763,8 +767,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the - * bottom-right pixel in the active pixel array. The weight - * should be nonnegative.</p> + * bottom-right pixel in the active pixel array.</p> + * <p>The weight must range from 0 to 1000, and represents a weight + * for every pixel in the area. This means that a large metering area + * with the same weight as a smaller area will have more effect in + * the metering result. Metering areas can partially overlap and the + * camera device will add the weights in the overlap region.</p> * <p>If all regions have 0 weight, then no specific metering area * needs to be used by the camera device. If the metering region is * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, @@ -846,8 +854,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the - * bottom-right pixel in the active pixel array. The weight - * should be nonnegative.</p> + * bottom-right pixel in the active pixel array.</p> + * <p>The weight must range from 0 to 1000, and represents a weight + * for every pixel in the area. This means that a large metering area + * with the same weight as a smaller area will have more effect in + * the metering result. Metering areas can partially overlap and the + * camera device will add the weights in the overlap region.</p> * <p>If all regions have 0 weight, then no specific metering area * needs to be used by the camera device. If the metering region is * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 42020eb..7d07c92 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -537,8 +537,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the - * bottom-right pixel in the active pixel array. The weight - * should be nonnegative.</p> + * bottom-right pixel in the active pixel array.</p> + * <p>The weight must range from 0 to 1000, and represents a weight + * for every pixel in the area. This means that a large metering area + * with the same weight as a smaller area will have more effect in + * the metering result. Metering areas can partially overlap and the + * camera device will add the weights in the overlap region.</p> * <p>If all regions have 0 weight, then no specific metering area * needs to be used by the camera device. If the metering region is * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, @@ -807,8 +811,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the - * bottom-right pixel in the active pixel array. The weight - * should be nonnegative.</p> + * bottom-right pixel in the active pixel array.</p> + * <p>The weight must range from 0 to 1000, and represents a weight + * for every pixel in the area. This means that a large metering area + * with the same weight as a smaller area will have more effect in + * the metering result. Metering areas can partially overlap and the + * camera device will add the weights in the overlap region.</p> * <p>If all regions have 0 weight, then no specific metering area * needs to be used by the camera device. If the metering region is * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, @@ -1287,8 +1295,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the - * bottom-right pixel in the active pixel array. The weight - * should be nonnegative.</p> + * bottom-right pixel in the active pixel array.</p> + * <p>The weight must range from 0 to 1000, and represents a weight + * for every pixel in the area. This means that a large metering area + * with the same weight as a smaller area will have more effect in + * the metering result. Metering areas can partially overlap and the + * camera device will add the weights in the overlap region.</p> * <p>If all regions have 0 weight, then no specific metering area * needs to be used by the camera device. If the metering region is * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, @@ -1792,8 +1804,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>If variable focus not supported, can still report * fixed depth of field range</p> */ - public static final Key<android.util.Range<Float>> LENS_FOCUS_RANGE = - new Key<android.util.Range<Float>>("android.lens.focusRange", new TypeReference<android.util.Range<Float>>() {{ }}); + public static final Key<android.util.Pair<Float,Float>> LENS_FOCUS_RANGE = + new Key<android.util.Pair<Float,Float>>("android.lens.focusRange", new TypeReference<android.util.Pair<Float,Float>>() {{ }}); /** * <p>Sets whether the camera device uses optical image stabilization (OIS) diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index dc0c652..83aee5d 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -31,6 +31,7 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableColorSpaceTransform import android.hardware.camera2.marshal.impl.MarshalQueryableEnum; import android.hardware.camera2.marshal.impl.MarshalQueryableMeteringRectangle; import android.hardware.camera2.marshal.impl.MarshalQueryableNativeByteToInteger; +import android.hardware.camera2.marshal.impl.MarshalQueryablePair; import android.hardware.camera2.marshal.impl.MarshalQueryableParcelable; import android.hardware.camera2.marshal.impl.MarshalQueryablePrimitive; import android.hardware.camera2.marshal.impl.MarshalQueryableRange; @@ -1006,6 +1007,7 @@ public class CameraMetadataNative implements Parcelable { new MarshalQueryableString(), new MarshalQueryableReprocessFormatsMap(), new MarshalQueryableRange(), + new MarshalQueryablePair(), new MarshalQueryableMeteringRectangle(), new MarshalQueryableColorSpaceTransform(), new MarshalQueryableStreamConfiguration(), diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java new file mode 100644 index 0000000..0a9935d --- /dev/null +++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java @@ -0,0 +1,158 @@ +/* + * 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.marshal.impl; + +import android.hardware.camera2.marshal.Marshaler; +import android.hardware.camera2.marshal.MarshalQueryable; +import android.hardware.camera2.marshal.MarshalRegistry; +import android.hardware.camera2.utils.TypeReference; +import android.util.Pair; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.ByteBuffer; + +/** + * Marshal {@link Pair} to/from any native type + */ +public class MarshalQueryablePair<T1, T2> + implements MarshalQueryable<Pair<T1, T2>> { + + private class MarshalerPair extends Marshaler<Pair<T1, T2>> { + private final Class<? super Pair<T1, T2>> mClass; + private final Constructor<Pair<T1, T2>> mConstructor; + /** Marshal the {@code T1} inside of {@code Pair<T1, T2>} */ + private final Marshaler<T1> mNestedTypeMarshalerFirst; + /** Marshal the {@code T1} inside of {@code Pair<T1, T2>} */ + private final Marshaler<T2> mNestedTypeMarshalerSecond; + + @SuppressWarnings("unchecked") + protected MarshalerPair(TypeReference<Pair<T1, T2>> typeReference, + int nativeType) { + super(MarshalQueryablePair.this, typeReference, nativeType); + + mClass = typeReference.getRawType(); + + /* + * Lookup the actual type arguments, e.g. Pair<Integer, Float> --> [Integer, Float] + * and then get the marshalers for that managed type. + */ + ParameterizedType paramType; + try { + paramType = (ParameterizedType) typeReference.getType(); + } catch (ClassCastException e) { + throw new AssertionError("Raw use of Pair is not supported", e); + } + + // Get type marshaler for T1 + { + Type actualTypeArgument = paramType.getActualTypeArguments()[0]; + + TypeReference<?> actualTypeArgToken = + TypeReference.createSpecializedTypeReference(actualTypeArgument); + + mNestedTypeMarshalerFirst = (Marshaler<T1>)MarshalRegistry.getMarshaler( + actualTypeArgToken, mNativeType); + } + // Get type marshaler for T2 + { + Type actualTypeArgument = paramType.getActualTypeArguments()[1]; + + TypeReference<?> actualTypeArgToken = + TypeReference.createSpecializedTypeReference(actualTypeArgument); + + mNestedTypeMarshalerSecond = (Marshaler<T2>)MarshalRegistry.getMarshaler( + actualTypeArgToken, mNativeType); + } + try { + mConstructor = (Constructor<Pair<T1, T2>>)mClass.getConstructor( + Object.class, Object.class); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + + @Override + public void marshal(Pair<T1, T2> value, ByteBuffer buffer) { + if (value.first == null) { + throw new UnsupportedOperationException("Pair#first must not be null"); + } else if (value.second == null) { + throw new UnsupportedOperationException("Pair#second must not be null"); + } + + mNestedTypeMarshalerFirst.marshal(value.first, buffer); + mNestedTypeMarshalerSecond.marshal(value.second, buffer); + } + + @Override + public Pair<T1, T2> unmarshal(ByteBuffer buffer) { + T1 first = mNestedTypeMarshalerFirst.unmarshal(buffer); + T2 second = mNestedTypeMarshalerSecond.unmarshal(buffer); + + try { + return mConstructor.newInstance(first, second); + } catch (InstantiationException e) { + throw new AssertionError(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (IllegalArgumentException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new AssertionError(e); + } + } + + @Override + public int getNativeSize() { + int firstSize = mNestedTypeMarshalerFirst.getNativeSize(); + int secondSize = mNestedTypeMarshalerSecond.getNativeSize(); + + if (firstSize != NATIVE_SIZE_DYNAMIC && secondSize != NATIVE_SIZE_DYNAMIC) { + return firstSize + secondSize; + } else { + return NATIVE_SIZE_DYNAMIC; + } + } + + @Override + public int calculateMarshalSize(Pair<T1, T2> value) { + int nativeSize = getNativeSize(); + + if (nativeSize != NATIVE_SIZE_DYNAMIC) { + return nativeSize; + } else { + int firstSize = mNestedTypeMarshalerFirst.calculateMarshalSize(value.first); + int secondSize = mNestedTypeMarshalerSecond.calculateMarshalSize(value.second); + + return firstSize + secondSize; + } + } + } + + @Override + public Marshaler<Pair<T1, T2>> createMarshaler(TypeReference<Pair<T1, T2>> managedType, + int nativeType) { + return new MarshalerPair(managedType, nativeType); + } + + @Override + public boolean isTypeMappingSupported(TypeReference<Pair<T1, T2>> managedType, int nativeType) { + return (Pair.class.equals(managedType.getRawType())); + } + +} diff --git a/core/java/android/hardware/camera2/params/MeteringRectangle.java b/core/java/android/hardware/camera2/params/MeteringRectangle.java index a7a3b59..93fd053 100644 --- a/core/java/android/hardware/camera2/params/MeteringRectangle.java +++ b/core/java/android/hardware/camera2/params/MeteringRectangle.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package android.hardware.camera2.params; import android.util.Size; @@ -25,22 +26,50 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.utils.HashCodeHelpers; /** - * An immutable class to represent a rectangle {@code (x,y, width, height)} with an - * additional weight component. - * - * </p>The rectangle is defined to be inclusive of the specified coordinates.</p> - * - * <p>When used with a {@link CaptureRequest}, the coordinate system is based on the active pixel + * An immutable class to represent a rectangle {@code (x, y, width, height)} with an additional + * weight component. + * <p> + * The rectangle is defined to be inclusive of the specified coordinates. + * </p> + * <p> + * When used with a {@link CaptureRequest}, the coordinate system is based on the active pixel * array, with {@code (0,0)} being the top-left pixel in the * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE active pixel array}, and * {@code (android.sensor.info.activeArraySize.width - 1, - * android.sensor.info.activeArraySize.height - 1)} - * being the bottom-right pixel in the active pixel array. + * android.sensor.info.activeArraySize.height - 1)} being the bottom-right pixel in the active pixel + * array. + * </p> + * <p> + * The weight must range from {@value #METERING_WEIGHT_MIN} to {@value #METERING_WEIGHT_MAX} + * inclusively, and represents a weight for every pixel in the area. This means that a large + * metering area with the same weight as a smaller area will have more effect in the metering + * result. Metering areas can partially overlap and the camera device will add the weights in the + * overlap rectangle. + * </p> + * <p> + * If all rectangles have 0 weight, then no specific metering area needs to be used by the camera + * device. If the metering rectangle is outside the used android.scaler.cropRegion returned in + * capture result metadata, the camera device will ignore the sections outside the rectangle and + * output the used sections in the result metadata. * </p> - * - * <p>The metering weight is nonnegative.</p> */ public final class MeteringRectangle { + /** + * The minimum value of valid metering weight. + */ + public static final int METERING_WEIGHT_MIN = 0; + + /** + * The maximum value of valid metering weight. + */ + public static final int METERING_WEIGHT_MAX = 1000; + + /** + * Weights set to this value will cause the camera device to ignore this rectangle. + * If all metering rectangles are weighed with 0, the camera device will choose its own metering + * rectangles. + */ + public static final int METERING_WEIGHT_DONT_CARE = 0; private final int mX; private final int mY; @@ -55,8 +84,8 @@ public final class MeteringRectangle { * @param y coordinate >= 0 * @param width width >= 0 * @param height height >= 0 - * @param meteringWeight weight >= 0 - * + * @param meteringWeight weight between {@value #METERING_WEIGHT_MIN} and + * {@value #METERING_WEIGHT_MAX} inclusively * @throws IllegalArgumentException if any of the parameters were negative */ public MeteringRectangle(int x, int y, int width, int height, int meteringWeight) { @@ -64,7 +93,8 @@ public final class MeteringRectangle { mY = checkArgumentNonnegative(y, "y must be nonnegative"); mWidth = checkArgumentNonnegative(width, "width must be nonnegative"); mHeight = checkArgumentNonnegative(height, "height must be nonnegative"); - mWeight = checkArgumentNonnegative(meteringWeight, "meteringWeight must be nonnegative"); + mWeight = checkArgumentInRange( + meteringWeight, METERING_WEIGHT_MIN, METERING_WEIGHT_MAX, "meteringWeight"); } /** diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 06d8e4a..857e335 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -69,6 +69,7 @@ class IInputMethodWrapper extends IInputMethod.Stub private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80; final WeakReference<AbstractInputMethodService> mTarget; + final Context mContext; final HandlerCaller mCaller; final WeakReference<InputMethod> mInputMethod; final int mTargetSdkVersion; @@ -111,8 +112,8 @@ class IInputMethodWrapper extends IInputMethod.Stub public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) { mTarget = new WeakReference<AbstractInputMethodService>(context); - mCaller = new HandlerCaller(context.getApplicationContext(), null, - this, true /*asyncHandler*/); + mContext = context.getApplicationContext(); + mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/); mInputMethod = new WeakReference<InputMethod>(inputMethod); mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; } @@ -186,7 +187,7 @@ class IInputMethodWrapper extends IInputMethod.Stub case DO_CREATE_SESSION: { SomeArgs args = (SomeArgs)msg.obj; inputMethod.createSession(new InputMethodSessionCallbackWrapper( - mCaller.mContext, (InputChannel)args.arg1, + mContext, (InputChannel)args.arg1, (IInputSessionCallback)args.arg2)); args.recycle(); return; diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index 38a65c5..795117e 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -115,6 +115,10 @@ public class SoftInputWindow extends Dialog { getWindow().setAttributes(lp); } + public int getGravity() { + return getWindow().getAttributes().gravity; + } + private void updateWidthHeight(WindowManager.LayoutParams lp) { if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) { lp.width = WindowManager.LayoutParams.MATCH_PARENT; diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index e489e05..64516e6 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -94,11 +94,26 @@ public class Network implements Parcelable { mNetId = netId; } + private void connectToHost(Socket socket, String host, int port) throws IOException { + // Lookup addresses only on this Network. + InetAddress[] hostAddresses = getAllByName(host); + // Try all but last address ignoring exceptions. + for (int i = 0; i < hostAddresses.length - 1; i++) { + try { + socket.connect(new InetSocketAddress(hostAddresses[i], port)); + return; + } catch (IOException e) { + } + } + // Try last address. Do throw exceptions. + socket.connect(new InetSocketAddress(hostAddresses[hostAddresses.length - 1], port)); + } + @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { Socket socket = createSocket(); socket.bind(new InetSocketAddress(localHost, localPort)); - socket.connect(new InetSocketAddress(host, port)); + connectToHost(socket, host, port); return socket; } @@ -121,7 +136,7 @@ public class Network implements Parcelable { @Override public Socket createSocket(String host, int port) throws IOException { Socket socket = createSocket(); - socket.connect(new InetSocketAddress(host, port)); + connectToHost(socket, host, port); return socket; } diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java index b0449224..cabda5d 100644 --- a/core/java/android/nfc/cardemulation/AidGroup.java +++ b/core/java/android/nfc/cardemulation/AidGroup.java @@ -2,6 +2,7 @@ package android.nfc.cardemulation; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -21,6 +22,8 @@ import android.util.Log; * <p>The format of AIDs is defined in the ISO/IEC 7816-4 specification. This class * requires the AIDs to be input as a hexadecimal string, with an even amount of * hexadecimal characters, e.g. "F014811481". + * + * @hide */ public final class AidGroup implements Parcelable { /** @@ -30,7 +33,7 @@ public final class AidGroup implements Parcelable { static final String TAG = "AidGroup"; - final ArrayList<String> aids; + final List<String> aids; final String category; final String description; @@ -40,7 +43,7 @@ public final class AidGroup implements Parcelable { * @param aids The list of AIDs present in the group * @param category The category of this group, e.g. {@link CardEmulation#CATEGORY_PAYMENT} */ - public AidGroup(ArrayList<String> aids, String category) { + public AidGroup(List<String> aids, String category) { if (aids == null || aids.size() == 0) { throw new IllegalArgumentException("No AIDS in AID group."); } @@ -72,7 +75,7 @@ public final class AidGroup implements Parcelable { /** * @return the list of AIDs in this group */ - public ArrayList<String> getAids() { + public List<String> getAids() { return aids; } @@ -121,11 +124,6 @@ public final class AidGroup implements Parcelable { } }; - /** - * @hide - * Note: description is not serialized, since it's not localized - * and resource identifiers don't make sense to persist. - */ static public AidGroup createFromXml(XmlPullParser parser) throws XmlPullParserException, IOException { String category = parser.getAttributeValue(null, "category"); ArrayList<String> aids = new ArrayList<String>(); @@ -152,9 +150,6 @@ public final class AidGroup implements Parcelable { } } - /** - * @hide - */ public void writeAsXml(XmlSerializer out) throws IOException { out.attribute(null, "category", category); for (String aid : aids) { diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index e24a22a..4b9e890 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -303,12 +303,13 @@ public final class CardEmulation { } /** - * Registers a group of AIDs for the specified service. + * Registers a list of AIDs for a specific category for the + * specified service. * - * <p>If an AID group for that category was previously + * <p>If a list of AIDs for that category was previously * registered for this service (either statically * through the manifest, or dynamically by using this API), - * that AID group will be replaced with this one. + * that list of AIDs will be replaced with this one. * * <p>Note that you can only register AIDs for a service that * is running under the same UID as the caller of this API. Typically @@ -317,10 +318,13 @@ public final class CardEmulation { * be shared between packages using shared UIDs. * * @param service The component name of the service - * @param aidGroup The group of AIDs to be registered + * @param category The category of AIDs to be registered + * @param aids A list containing the AIDs to be registered * @return whether the registration was successful. */ - public boolean registerAidGroupForService(ComponentName service, AidGroup aidGroup) { + public boolean registerAidsForService(ComponentName service, String category, + List<String> aids) { + AidGroup aidGroup = new AidGroup(aids, category); try { return sService.registerAidGroupForService(UserHandle.myUserId(), service, aidGroup); } catch (RemoteException e) { @@ -341,21 +345,24 @@ public final class CardEmulation { } /** - * Retrieves the currently registered AID group for the specified + * Retrieves the currently registered AIDs for the specified * category for a service. * - * <p>Note that this will only return AID groups that were dynamically - * registered using {@link #registerAidGroupForService(ComponentName, AidGroup)} - * method. It will *not* return AID groups that were statically registered + * <p>Note that this will only return AIDs that were dynamically + * registered using {@link #registerAidsForService(ComponentName, String, List)} + * method. It will *not* return AIDs that were statically registered * in the manifest. * * @param service The component name of the service - * @param category The category of the AID group to be returned, e.g. {@link #CATEGORY_PAYMENT} - * @return The AID group, or null if it couldn't be found + * @param category The category for which the AIDs were registered, + * e.g. {@link #CATEGORY_PAYMENT} + * @return The list of AIDs registered for this category, or null if it couldn't be found. */ - public AidGroup getAidGroupForService(ComponentName service, String category) { + public List<String> getAidsForService(ComponentName service, String category) { try { - return sService.getAidGroupForService(UserHandle.myUserId(), service, category); + AidGroup group = sService.getAidGroupForService(UserHandle.myUserId(), service, + category); + return (group != null ? group.getAids() : null); } catch (RemoteException e) { recoverService(); if (sService == null) { @@ -363,7 +370,9 @@ public final class CardEmulation { return null; } try { - return sService.getAidGroupForService(UserHandle.myUserId(), service, category); + AidGroup group = sService.getAidGroupForService(UserHandle.myUserId(), service, + category); + return (group != null ? group.getAids() : null); } catch (RemoteException ee) { Log.e(TAG, "Failed to recover CardEmulationService."); return null; @@ -372,21 +381,21 @@ public final class CardEmulation { } /** - * Removes a registered AID group for the specified category for the + * Removes a previously registered list of AIDs for the specified category for the * service provided. * - * <p>Note that this will only remove AID groups that were dynamically - * registered using the {@link #registerAidGroupForService(ComponentName, AidGroup)} - * method. It will *not* remove AID groups that were statically registered in - * the manifest. If a dynamically registered AID group is removed using + * <p>Note that this will only remove AIDs that were dynamically + * registered using the {@link #registerAidsForService(ComponentName, String, List)} + * method. It will *not* remove AIDs that were statically registered in + * the manifest. If dynamically registered AIDs are removed using * this method, and a statically registered AID group for the same category * exists in the manifest, the static AID group will become active again. * * @param service The component name of the service - * @param category The category of the AID group to be removed, e.g. {@link #CATEGORY_PAYMENT} + * @param category The category of the AIDs to be removed, e.g. {@link #CATEGORY_PAYMENT} * @return whether the group was successfully removed. */ - public boolean removeAidGroupForService(ComponentName service, String category) { + public boolean removeAidsForService(ComponentName service, String category) { try { return sService.removeAidGroupForService(UserHandle.myUserId(), service, category); } catch (RemoteException e) { diff --git a/core/java/android/os/CommonBundle.java b/core/java/android/os/BaseBundle.java index c1b202c..c2a45ba 100644 --- a/core/java/android/os/CommonBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -27,7 +27,7 @@ import java.util.Set; /** * A mapping from String values to various types. */ -abstract class CommonBundle implements Parcelable, Cloneable { +public class BaseBundle { private static final String TAG = "Bundle"; static final boolean DEBUG = false; @@ -63,7 +63,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * inside of the Bundle. * @param capacity Initial size of the ArrayMap. */ - CommonBundle(ClassLoader loader, int capacity) { + BaseBundle(ClassLoader loader, int capacity) { mMap = capacity > 0 ? new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>(); mClassLoader = loader == null ? getClass().getClassLoader() : loader; @@ -72,7 +72,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { /** * Constructs a new, empty Bundle. */ - CommonBundle() { + BaseBundle() { this((ClassLoader) null, 0); } @@ -82,11 +82,11 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @param parcelledData a Parcel containing a Bundle */ - CommonBundle(Parcel parcelledData) { + BaseBundle(Parcel parcelledData) { readFromParcelInner(parcelledData); } - CommonBundle(Parcel parcelledData, int length) { + BaseBundle(Parcel parcelledData, int length) { readFromParcelInner(parcelledData, length); } @@ -97,7 +97,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param loader An explicit ClassLoader to use when instantiating objects * inside of the Bundle. */ - CommonBundle(ClassLoader loader) { + BaseBundle(ClassLoader loader) { this(loader, 0); } @@ -107,7 +107,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @param capacity the initial capacity of the Bundle */ - CommonBundle(int capacity) { + BaseBundle(int capacity) { this((ClassLoader) null, capacity); } @@ -117,7 +117,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @param b a Bundle to be copied. */ - CommonBundle(CommonBundle b) { + BaseBundle(BaseBundle b) { if (b.mParcelledData != null) { if (b.mParcelledData == EMPTY_PARCEL) { mParcelledData = EMPTY_PARCEL; @@ -148,7 +148,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @hide */ - String getPairValue() { + public String getPairValue() { unparcel(); int size = mMap.size(); if (size > 1) { @@ -228,7 +228,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { /** * @hide */ - boolean isParcelled() { + public boolean isParcelled() { return mParcelledData != null; } @@ -237,7 +237,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @return the number of mappings as an int. */ - int size() { + public int size() { unparcel(); return mMap.size(); } @@ -245,7 +245,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { /** * Returns true if the mapping of this Bundle is empty, false otherwise. */ - boolean isEmpty() { + public boolean isEmpty() { unparcel(); return mMap.isEmpty(); } @@ -253,7 +253,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { /** * Removes all elements from the mapping of this Bundle. */ - void clear() { + public void clear() { unparcel(); mMap.clear(); } @@ -265,7 +265,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String key * @return true if the key is part of the mapping, false otherwise */ - boolean containsKey(String key) { + public boolean containsKey(String key) { unparcel(); return mMap.containsKey(key); } @@ -276,7 +276,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String key * @return an Object, or null */ - Object get(String key) { + public Object get(String key) { unparcel(); return mMap.get(key); } @@ -286,24 +286,24 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @param key a String key */ - void remove(String key) { + public void remove(String key) { unparcel(); mMap.remove(key); } /** - * Inserts all mappings from the given PersistableBundle into this CommonBundle. + * Inserts all mappings from the given PersistableBundle into this BaseBundle. * * @param bundle a PersistableBundle */ - void putAll(PersistableBundle bundle) { + public void putAll(PersistableBundle bundle) { unparcel(); bundle.unparcel(); mMap.putAll(bundle.mMap); } /** - * Inserts all mappings from the given Map into this CommonBundle. + * Inserts all mappings from the given Map into this BaseBundle. * * @param map a Map */ @@ -317,7 +317,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @return a Set of String keys */ - Set<String> keySet() { + public Set<String> keySet() { unparcel(); return mMap.keySet(); } @@ -377,7 +377,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value an int, or null */ - void putInt(String key, int value) { + public void putInt(String key, int value) { unparcel(); mMap.put(key, value); } @@ -389,7 +389,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a long */ - void putLong(String key, long value) { + public void putLong(String key, long value) { unparcel(); mMap.put(key, value); } @@ -413,7 +413,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a double */ - void putDouble(String key, double value) { + public void putDouble(String key, double value) { unparcel(); mMap.put(key, value); } @@ -425,7 +425,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a String, or null */ - void putString(String key, String value) { + public void putString(String key, String value) { unparcel(); mMap.put(key, value); } @@ -545,7 +545,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value an int array object, or null */ - void putIntArray(String key, int[] value) { + public void putIntArray(String key, int[] value) { unparcel(); mMap.put(key, value); } @@ -557,7 +557,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a long array object, or null */ - void putLongArray(String key, long[] value) { + public void putLongArray(String key, long[] value) { unparcel(); mMap.put(key, value); } @@ -581,7 +581,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a double array object, or null */ - void putDoubleArray(String key, double[] value) { + public void putDoubleArray(String key, double[] value) { unparcel(); mMap.put(key, value); } @@ -593,7 +593,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a String array object, or null */ - void putStringArray(String key, String[] value) { + public void putStringArray(String key, String[] value) { unparcel(); mMap.put(key, value); } @@ -611,18 +611,6 @@ abstract class CommonBundle implements Parcelable, Cloneable { } /** - * Inserts a PersistableBundle value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a Bundle object, or null - */ - void putPersistableBundle(String key, PersistableBundle value) { - unparcel(); - mMap.put(key, value); - } - - /** * Returns the value associated with the given key, or false if * no mapping of the desired type exists for the given key. * @@ -789,7 +777,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String * @return an int value */ - int getInt(String key) { + public int getInt(String key) { unparcel(); return getInt(key, 0); } @@ -802,7 +790,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return an int value */ - int getInt(String key, int defaultValue) { + public int getInt(String key, int defaultValue) { unparcel(); Object o = mMap.get(key); if (o == null) { @@ -823,7 +811,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String * @return a long value */ - long getLong(String key) { + public long getLong(String key) { unparcel(); return getLong(key, 0L); } @@ -836,7 +824,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return a long value */ - long getLong(String key, long defaultValue) { + public long getLong(String key, long defaultValue) { unparcel(); Object o = mMap.get(key); if (o == null) { @@ -891,7 +879,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String * @return a double value */ - double getDouble(String key) { + public double getDouble(String key) { unparcel(); return getDouble(key, 0.0); } @@ -904,7 +892,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return a double value */ - double getDouble(String key, double defaultValue) { + public double getDouble(String key, double defaultValue) { unparcel(); Object o = mMap.get(key); if (o == null) { @@ -926,7 +914,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @return a String value, or null */ - String getString(String key) { + public String getString(String key) { unparcel(); final Object o = mMap.get(key); try { @@ -946,7 +934,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @return the String value associated with the given key, or defaultValue * if no valid String object is currently mapped to that key. */ - String getString(String key, String defaultValue) { + public String getString(String key, String defaultValue) { final String s = getString(key); return (s == null) ? defaultValue : s; } @@ -990,28 +978,6 @@ abstract class CommonBundle implements Parcelable, Cloneable { * value is explicitly associated with the key. * * @param key a String, or null - * @return a Bundle value, or null - */ - PersistableBundle getPersistableBundle(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (PersistableBundle) o; - } catch (ClassCastException e) { - typeWarning(key, o, "Bundle", e); - return null; - } - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null * @return a Serializable value, or null */ Serializable getSerializable(String key) { @@ -1190,7 +1156,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @return an int[] value, or null */ - int[] getIntArray(String key) { + public int[] getIntArray(String key) { unparcel(); Object o = mMap.get(key); if (o == null) { @@ -1212,7 +1178,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @return a long[] value, or null */ - long[] getLongArray(String key) { + public long[] getLongArray(String key) { unparcel(); Object o = mMap.get(key); if (o == null) { @@ -1256,7 +1222,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @return a double[] value, or null */ - double[] getDoubleArray(String key) { + public double[] getDoubleArray(String key) { unparcel(); Object o = mMap.get(key); if (o == null) { @@ -1278,7 +1244,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @return a String[] value, or null */ - String[] getStringArray(String key) { + public String[] getStringArray(String key) { unparcel(); Object o = mMap.get(key); if (o == null) { diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index c85e418..e42c3fe 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -28,14 +28,14 @@ import java.util.Set; * A mapping from String values to various Parcelable types. * */ -public final class Bundle extends CommonBundle { +public final class Bundle extends BaseBundle implements Cloneable, Parcelable { public static final Bundle EMPTY; static final Parcel EMPTY_PARCEL; static { EMPTY = new Bundle(); EMPTY.mMap = ArrayMap.EMPTY; - EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL; + EMPTY_PARCEL = BaseBundle.EMPTY_PARCEL; } private boolean mHasFds = false; @@ -125,14 +125,6 @@ public final class Bundle extends CommonBundle { } /** - * @hide - */ - @Override - public String getPairValue() { - return super.getPairValue(); - } - - /** * Changes the ClassLoader this Bundle uses when instantiating objects. * * @param loader An explicit ClassLoader to use when instantiating objects @@ -168,32 +160,6 @@ public final class Bundle extends CommonBundle { } /** - * @hide - */ - @Override - public boolean isParcelled() { - return super.isParcelled(); - } - - /** - * Returns the number of mappings contained in this Bundle. - * - * @return the number of mappings as an int. - */ - @Override - public int size() { - return super.size(); - } - - /** - * Returns true if the mapping of this Bundle is empty, false otherwise. - */ - @Override - public boolean isEmpty() { - return super.isEmpty(); - } - - /** * Removes all elements from the mapping of this Bundle. */ @Override @@ -205,39 +171,6 @@ public final class Bundle extends CommonBundle { } /** - * Returns true if the given key is contained in the mapping - * of this Bundle. - * - * @param key a String key - * @return true if the key is part of the mapping, false otherwise - */ - @Override - public boolean containsKey(String key) { - return super.containsKey(key); - } - - /** - * Returns the entry with the given key as an object. - * - * @param key a String key - * @return an Object, or null - */ - @Override - public Object get(String key) { - return super.get(key); - } - - /** - * Removes any entry with the given key from the mapping of this Bundle. - * - * @param key a String key - */ - @Override - public void remove(String key) { - super.remove(key); - } - - /** * Inserts all mappings from the given Bundle into this Bundle. * * @param bundle a Bundle @@ -253,25 +186,6 @@ public final class Bundle extends CommonBundle { } /** - * Inserts all mappings from the given PersistableBundle into this Bundle. - * - * @param bundle a PersistableBundle - */ - public void putAll(PersistableBundle bundle) { - super.putAll(bundle); - } - - /** - * Returns a Set containing the Strings used as keys in this Bundle. - * - * @return a Set of String keys - */ - @Override - public Set<String> keySet() { - return super.keySet(); - } - - /** * Reports whether the bundle contains any parcelled file descriptors. */ public boolean hasFileDescriptors() { @@ -384,30 +298,6 @@ public final class Bundle extends CommonBundle { } /** - * Inserts an int value into the mapping of this Bundle, replacing - * any existing value for the given key. - * - * @param key a String, or null - * @param value an int, or null - */ - @Override - public void putInt(String key, int value) { - super.putInt(key, value); - } - - /** - * Inserts a long value into the mapping of this Bundle, replacing - * any existing value for the given key. - * - * @param key a String, or null - * @param value a long - */ - @Override - public void putLong(String key, long value) { - super.putLong(key, value); - } - - /** * Inserts a float value into the mapping of this Bundle, replacing * any existing value for the given key. * @@ -420,30 +310,6 @@ public final class Bundle extends CommonBundle { } /** - * Inserts a double value into the mapping of this Bundle, replacing - * any existing value for the given key. - * - * @param key a String, or null - * @param value a double - */ - @Override - public void putDouble(String key, double value) { - super.putDouble(key, value); - } - - /** - * Inserts a String value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a String, or null - */ - @Override - public void putString(String key, String value) { - super.putString(key, value); - } - - /** * Inserts a CharSequence value into the mapping of this Bundle, replacing * any existing value for the given key. Either key or value may be null. * @@ -616,30 +482,6 @@ public final class Bundle extends CommonBundle { } /** - * Inserts an int array value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value an int array object, or null - */ - @Override - public void putIntArray(String key, int[] value) { - super.putIntArray(key, value); - } - - /** - * Inserts a long array value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a long array object, or null - */ - @Override - public void putLongArray(String key, long[] value) { - super.putLongArray(key, value); - } - - /** * Inserts a float array value into the mapping of this Bundle, replacing * any existing value for the given key. Either key or value may be null. * @@ -652,30 +494,6 @@ public final class Bundle extends CommonBundle { } /** - * Inserts a double array value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a double array object, or null - */ - @Override - public void putDoubleArray(String key, double[] value) { - super.putDoubleArray(key, value); - } - - /** - * Inserts a String array value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a String array object, or null - */ - @Override - public void putStringArray(String key, String[] value) { - super.putStringArray(key, value); - } - - /** * Inserts a CharSequence array value into the mapping of this Bundle, replacing * any existing value for the given key. Either key or value may be null. * @@ -700,17 +518,6 @@ public final class Bundle extends CommonBundle { } /** - * Inserts a PersistableBundle value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a Bundle object, or null - */ - public void putPersistableBundle(String key, PersistableBundle value) { - super.putPersistableBundle(key, value); - } - - /** * Inserts an {@link IBinder} value into the mapping of this Bundle, replacing * any existing value for the given key. Either key or value may be null. * @@ -846,56 +653,6 @@ public final class Bundle extends CommonBundle { } /** - * Returns the value associated with the given key, or 0 if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @return an int value - */ - @Override - public int getInt(String key) { - return super.getInt(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @param defaultValue Value to return if key does not exist - * @return an int value - */ - @Override - public int getInt(String key, int defaultValue) { - return super.getInt(key, defaultValue); - } - - /** - * Returns the value associated with the given key, or 0L if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @return a long value - */ - @Override - public long getLong(String key) { - return super.getLong(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @param defaultValue Value to return if key does not exist - * @return a long value - */ - @Override - public long getLong(String key, long defaultValue) { - return super.getLong(key, defaultValue); - } - - /** * Returns the value associated with the given key, or 0.0f if * no mapping of the desired type exists for the given key. * @@ -921,58 +678,6 @@ public final class Bundle extends CommonBundle { } /** - * Returns the value associated with the given key, or 0.0 if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @return a double value - */ - @Override - public double getDouble(String key) { - return super.getDouble(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @param defaultValue Value to return if key does not exist - * @return a double value - */ - @Override - public double getDouble(String key, double defaultValue) { - return super.getDouble(key, defaultValue); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a String value, or null - */ - @Override - public String getString(String key) { - return super.getString(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String, or null - * @param defaultValue Value to return if key does not exist - * @return the String value associated with the given key, or defaultValue - * if no valid String object is currently mapped to that key. - */ - @Override - public String getString(String key, String defaultValue) { - return super.getString(key, defaultValue); - } - - /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null * value is explicitly associated with the key. @@ -1027,18 +732,6 @@ public final class Bundle extends CommonBundle { * value is explicitly associated with the key. * * @param key a String, or null - * @return a PersistableBundle value, or null - */ - public PersistableBundle getPersistableBundle(String key) { - return super.getPersistableBundle(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null * @return a Parcelable value, or null */ public <T extends Parcelable> T getParcelable(String key) { @@ -1232,32 +925,6 @@ public final class Bundle extends CommonBundle { * value is explicitly associated with the key. * * @param key a String, or null - * @return an int[] value, or null - */ - @Override - public int[] getIntArray(String key) { - return super.getIntArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a long[] value, or null - */ - @Override - public long[] getLongArray(String key) { - return super.getLongArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null * @return a float[] value, or null */ @Override @@ -1271,32 +938,6 @@ public final class Bundle extends CommonBundle { * value is explicitly associated with the key. * * @param key a String, or null - * @return a double[] value, or null - */ - @Override - public double[] getDoubleArray(String key) { - return super.getDoubleArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a String[] value, or null - */ - @Override - public String[] getStringArray(String key) { - return super.getStringArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null * @return a CharSequence[] value, or null */ @Override diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java index cd8d515..c01f688 100644 --- a/core/java/android/os/PersistableBundle.java +++ b/core/java/android/os/PersistableBundle.java @@ -32,7 +32,8 @@ import java.util.Set; * restored. * */ -public final class PersistableBundle extends CommonBundle implements XmlUtils.WriteMapCallback { +public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable, + XmlUtils.WriteMapCallback { private static final String TAG_PERSISTABLEMAP = "pbundle_as_map"; public static final PersistableBundle EMPTY; static final Parcel EMPTY_PARCEL; @@ -40,7 +41,7 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr static { EMPTY = new PersistableBundle(); EMPTY.mMap = ArrayMap.EMPTY; - EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL; + EMPTY_PARCEL = BaseBundle.EMPTY_PARCEL; } /** @@ -51,31 +52,6 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr } /** - * Constructs a PersistableBundle whose data is stored as a Parcel. The data - * will be unparcelled on first contact, using the assigned ClassLoader. - * - * @param parcelledData a Parcel containing a PersistableBundle - */ - PersistableBundle(Parcel parcelledData) { - super(parcelledData); - } - - /* package */ PersistableBundle(Parcel parcelledData, int length) { - super(parcelledData, length); - } - - /** - * Constructs a new, empty PersistableBundle that uses a specific ClassLoader for - * instantiating Parcelable and Serializable objects. - * - * @param loader An explicit ClassLoader to use when instantiating objects - * inside of the PersistableBundle. - */ - public PersistableBundle(ClassLoader loader) { - super(loader); - } - - /** * Constructs a new, empty PersistableBundle sized to hold the given number of * elements. The PersistableBundle will grow as needed. * @@ -127,6 +103,10 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr } } + /* package */ PersistableBundle(Parcel parcelledData, int length) { + super(parcelledData, length); + } + /** * Make a PersistableBundle for a single key/value pair. * @@ -139,33 +119,6 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr } /** - * @hide - */ - @Override - public String getPairValue() { - return super.getPairValue(); - } - - /** - * Changes the ClassLoader this PersistableBundle uses when instantiating objects. - * - * @param loader An explicit ClassLoader to use when instantiating objects - * inside of the PersistableBundle. - */ - @Override - public void setClassLoader(ClassLoader loader) { - super.setClassLoader(loader); - } - - /** - * Return the ClassLoader currently associated with this PersistableBundle. - */ - @Override - public ClassLoader getClassLoader() { - return super.getClassLoader(); - } - - /** * Clones the current PersistableBundle. The internal map is cloned, but the keys and * values to which it refers are copied by reference. */ @@ -175,300 +128,15 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr } /** - * @hide - */ - @Override - public boolean isParcelled() { - return super.isParcelled(); - } - - /** - * Returns the number of mappings contained in this PersistableBundle. - * - * @return the number of mappings as an int. - */ - @Override - public int size() { - return super.size(); - } - - /** - * Returns true if the mapping of this PersistableBundle is empty, false otherwise. - */ - @Override - public boolean isEmpty() { - return super.isEmpty(); - } - - /** - * Removes all elements from the mapping of this PersistableBundle. - */ - @Override - public void clear() { - super.clear(); - } - - /** - * Returns true if the given key is contained in the mapping - * of this PersistableBundle. - * - * @param key a String key - * @return true if the key is part of the mapping, false otherwise - */ - @Override - public boolean containsKey(String key) { - return super.containsKey(key); - } - - /** - * Returns the entry with the given key as an object. - * - * @param key a String key - * @return an Object, or null - */ - @Override - public Object get(String key) { - return super.get(key); - } - - /** - * Removes any entry with the given key from the mapping of this PersistableBundle. - * - * @param key a String key - */ - @Override - public void remove(String key) { - super.remove(key); - } - - /** - * Inserts all mappings from the given PersistableBundle into this Bundle. - * - * @param bundle a PersistableBundle - */ - @Override - public void putAll(PersistableBundle bundle) { - super.putAll(bundle); - } - - /** - * Returns a Set containing the Strings used as keys in this PersistableBundle. - * - * @return a Set of String keys - */ - @Override - public Set<String> keySet() { - return super.keySet(); - } - - /** - * Inserts an int value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. - * - * @param key a String, or null - * @param value an int, or null - */ - @Override - public void putInt(String key, int value) { - super.putInt(key, value); - } - - /** - * Inserts a long value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. - * - * @param key a String, or null - * @param value a long - */ - @Override - public void putLong(String key, long value) { - super.putLong(key, value); - } - - /** - * Inserts a double value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. - * - * @param key a String, or null - * @param value a double - */ - @Override - public void putDouble(String key, double value) { - super.putDouble(key, value); - } - - /** - * Inserts a String value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a String, or null - */ - @Override - public void putString(String key, String value) { - super.putString(key, value); - } - - /** - * Inserts an int array value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value an int array object, or null - */ - @Override - public void putIntArray(String key, int[] value) { - super.putIntArray(key, value); - } - - /** - * Inserts a long array value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a long array object, or null - */ - @Override - public void putLongArray(String key, long[] value) { - super.putLongArray(key, value); - } - - /** - * Inserts a double array value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a double array object, or null - */ - @Override - public void putDoubleArray(String key, double[] value) { - super.putDoubleArray(key, value); - } - - /** - * Inserts a String array value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a String array object, or null - */ - @Override - public void putStringArray(String key, String[] value) { - super.putStringArray(key, value); - } - - /** * Inserts a PersistableBundle value into the mapping of this Bundle, replacing * any existing value for the given key. Either key or value may be null. * * @param key a String, or null * @param value a Bundle object, or null */ - @Override public void putPersistableBundle(String key, PersistableBundle value) { - super.putPersistableBundle(key, value); - } - - /** - * Returns the value associated with the given key, or 0 if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @return an int value - */ - @Override - public int getInt(String key) { - return super.getInt(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @param defaultValue Value to return if key does not exist - * @return an int value - */ - @Override - public int getInt(String key, int defaultValue) { - return super.getInt(key, defaultValue); - } - - /** - * Returns the value associated with the given key, or 0L if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @return a long value - */ - @Override - public long getLong(String key) { - return super.getLong(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @param defaultValue Value to return if key does not exist - * @return a long value - */ - @Override - public long getLong(String key, long defaultValue) { - return super.getLong(key, defaultValue); - } - - /** - * Returns the value associated with the given key, or 0.0 if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @return a double value - */ - @Override - public double getDouble(String key) { - return super.getDouble(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @param defaultValue Value to return if key does not exist - * @return a double value - */ - @Override - public double getDouble(String key, double defaultValue) { - return super.getDouble(key, defaultValue); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a String value, or null - */ - @Override - public String getString(String key) { - return super.getString(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String, or null - * @param defaultValue Value to return if key does not exist - * @return the String value associated with the given key, or defaultValue - * if no valid String object is currently mapped to that key. - */ - @Override - public String getString(String key, String defaultValue) { - return super.getString(key, defaultValue); + unparcel(); + mMap.put(key, value); } /** @@ -479,61 +147,18 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr * @param key a String, or null * @return a Bundle value, or null */ - @Override public PersistableBundle getPersistableBundle(String key) { - return super.getPersistableBundle(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return an int[] value, or null - */ - @Override - public int[] getIntArray(String key) { - return super.getIntArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a long[] value, or null - */ - @Override - public long[] getLongArray(String key) { - return super.getLongArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a double[] value, or null - */ - @Override - public double[] getDoubleArray(String key) { - return super.getDoubleArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a String[] value, or null - */ - @Override - public String[] getStringArray(String key) { - return super.getStringArray(key); + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (PersistableBundle) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Bundle", e); + return null; + } } public static final Parcelable.Creator<PersistableBundle> CREATOR = @@ -549,38 +174,6 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr } }; - /** - * Report the nature of this Parcelable's contents - */ - @Override - public int describeContents() { - return 0; - } - - /** - * Writes the PersistableBundle contents to a Parcel, typically in order for - * it to be passed through an IBinder connection. - * @param parcel The parcel to copy this bundle to. - */ - @Override - public void writeToParcel(Parcel parcel, int flags) { - final boolean oldAllowFds = parcel.pushAllowFds(false); - try { - super.writeToParcelInner(parcel, flags); - } finally { - parcel.restoreAllowFds(oldAllowFds); - } - } - - /** - * Reads the Parcel contents into this PersistableBundle, typically in order for - * it to be passed through an IBinder connection. - * @param parcel The parcel to overwrite this bundle from. - */ - public void readFromParcel(Parcel parcel) { - super.readFromParcelInner(parcel); - } - /** @hide */ @Override public void writeUnknownObject(Object v, String name, XmlSerializer out) @@ -614,8 +207,29 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr } /** - * @hide + * Report the nature of this Parcelable's contents */ + @Override + public int describeContents() { + return 0; + } + + /** + * Writes the PersistableBundle contents to a Parcel, typically in order for + * it to be passed through an IBinder connection. + * @param parcel The parcel to copy this bundle to. + */ + @Override + public void writeToParcel(Parcel parcel, int flags) { + final boolean oldAllowFds = parcel.pushAllowFds(false); + try { + writeToParcelInner(parcel, flags); + } finally { + parcel.restoreAllowFds(oldAllowFds); + } + } + + /** @hide */ public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException, XmlPullParserException { final int outerDepth = in.getDepth(); diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 846e292..d02fc7b 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -41,12 +41,18 @@ public class ZenModeConfig implements Parcelable { public static final String SLEEP_MODE_NIGHTS = "nights"; public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights"; + public static final int SOURCE_ANYONE = 0; + public static final int SOURCE_CONTACT = 1; + public static final int SOURCE_STAR = 2; + public static final int MAX_SOURCE = SOURCE_STAR; + private static final int XML_VERSION = 1; private static final String ZEN_TAG = "zen"; private static final String ZEN_ATT_VERSION = "version"; private static final String ALLOW_TAG = "allow"; private static final String ALLOW_ATT_CALLS = "calls"; private static final String ALLOW_ATT_MESSAGES = "messages"; + private static final String ALLOW_ATT_FROM = "from"; private static final String SLEEP_TAG = "sleep"; private static final String SLEEP_ATT_MODE = "mode"; @@ -61,6 +67,7 @@ public class ZenModeConfig implements Parcelable { public boolean allowCalls; public boolean allowMessages; + public int allowFrom = SOURCE_ANYONE; public String sleepMode; public int sleepStartHour; @@ -92,6 +99,7 @@ public class ZenModeConfig implements Parcelable { conditionIds = new Uri[len]; source.readTypedArray(conditionIds, Uri.CREATOR); } + allowFrom = source.readInt(); } @Override @@ -120,6 +128,7 @@ public class ZenModeConfig implements Parcelable { } else { dest.writeInt(0); } + dest.writeInt(allowFrom); } @Override @@ -127,6 +136,7 @@ public class ZenModeConfig implements Parcelable { return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') .append("allowCalls=").append(allowCalls) .append(",allowMessages=").append(allowMessages) + .append(",allowFrom=").append(sourceToString(allowFrom)) .append(",sleepMode=").append(sleepMode) .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute) .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute) @@ -137,6 +147,19 @@ public class ZenModeConfig implements Parcelable { .append(']').toString(); } + public static String sourceToString(int source) { + switch (source) { + case SOURCE_ANYONE: + return "anyone"; + case SOURCE_CONTACT: + return "contacts"; + case SOURCE_STAR: + return "stars"; + default: + return "UNKNOWN"; + } + } + @Override public boolean equals(Object o) { if (!(o instanceof ZenModeConfig)) return false; @@ -144,6 +167,7 @@ public class ZenModeConfig implements Parcelable { final ZenModeConfig other = (ZenModeConfig) o; return other.allowCalls == allowCalls && other.allowMessages == allowMessages + && other.allowFrom == allowFrom && Objects.equals(other.sleepMode, sleepMode) && other.sleepStartHour == sleepStartHour && other.sleepStartMinute == sleepStartMinute @@ -155,8 +179,8 @@ public class ZenModeConfig implements Parcelable { @Override public int hashCode() { - return Objects.hash(allowCalls, allowMessages, sleepMode, sleepStartHour, - sleepStartMinute, sleepEndHour, sleepEndMinute, + return Objects.hash(allowCalls, allowMessages, allowFrom, sleepMode, + sleepStartHour, sleepStartMinute, sleepEndHour, sleepEndMinute, Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds)); } @@ -191,6 +215,10 @@ public class ZenModeConfig implements Parcelable { if (ALLOW_TAG.equals(tag)) { rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false); rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false); + rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE); + if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) { + throw new IndexOutOfBoundsException("bad source in config:" + rt.allowFrom); + } } else if (SLEEP_TAG.equals(tag)) { final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE); rt.sleepMode = (SLEEP_MODE_NIGHTS.equals(mode) @@ -224,6 +252,7 @@ public class ZenModeConfig implements Parcelable { out.startTag(null, ALLOW_TAG); out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls)); out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages)); + out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom)); out.endTag(null, ALLOW_TAG); out.startTag(null, SLEEP_TAG); diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index cd357b7..1e29f8e 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -20,7 +20,6 @@ import android.app.Dialog; import android.app.Instrumentation; import android.content.Context; import android.content.Intent; -import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.Region; diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java index 6dd7c00..f1163e2 100644 --- a/core/java/android/view/GLRenderer.java +++ b/core/java/android/view/GLRenderer.java @@ -61,6 +61,7 @@ import android.view.Surface.OutOfResourcesException; import com.google.android.gles_jni.EGLImpl; +import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -106,7 +107,6 @@ public class GLRenderer extends HardwareRenderer { private static final String[] VISUALIZERS = { PROFILE_PROPERTY_VISUALIZE_BARS, - PROFILE_PROPERTY_VISUALIZE_LINES }; private static final String[] OVERDRAW = { @@ -178,7 +178,7 @@ public class GLRenderer extends HardwareRenderer { private static EGLSurface sPbuffer; private static final Object[] sPbufferLock = new Object[0]; - private List<HardwareLayer> mAttachedLayers = new ArrayList<HardwareLayer>(); + private List<HardwareLayer> mLayerUpdates = new ArrayList<HardwareLayer>(); private static class GLRendererEglContext extends ManagedEGLContext { final Handler mHandler = new Handler(); @@ -471,7 +471,7 @@ public class GLRenderer extends HardwareRenderer { @Override void pushLayerUpdate(HardwareLayer layer) { - mGlCanvas.pushLayerUpdate(layer); + mLayerUpdates.add(layer); } @Override @@ -494,11 +494,6 @@ public class GLRenderer extends HardwareRenderer { return HardwareLayer.createDisplayListLayer(this, width, height); } - @Override - void onLayerCreated(HardwareLayer hardwareLayer) { - mAttachedLayers.add(hardwareLayer); - } - boolean hasContext() { return sEgl != null && mEglContext != null && mEglContext.equals(sEgl.eglGetCurrentContext()); @@ -509,11 +504,7 @@ public class GLRenderer extends HardwareRenderer { if (mGlCanvas != null) { mGlCanvas.cancelLayerUpdate(layer); } - if (hasContext()) { - long backingLayer = layer.detachBackingLayer(); - nDestroyLayer(backingLayer); - } - mAttachedLayers.remove(layer); + mLayerUpdates.remove(layer); } @Override @@ -674,7 +665,7 @@ public class GLRenderer extends HardwareRenderer { mProfilePaint = null; if (value) { - mDebugDataProvider = new DrawPerformanceDataProvider(graphType); + mDebugDataProvider = new GraphDataProvider(graphType); } else { mDebugDataProvider = null; } @@ -742,7 +733,7 @@ public class GLRenderer extends HardwareRenderer { } @Override - void dumpGfxInfo(PrintWriter pw) { + void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) { if (mProfileEnabled) { pw.printf("\n\tDraw\tProcess\tExecute\n"); @@ -763,11 +754,6 @@ public class GLRenderer extends HardwareRenderer { } } - @Override - long getFrameCount() { - return mFrameCount; - } - /** * Indicates whether this renderer instance can track and update dirty regions. */ @@ -1203,16 +1189,19 @@ public class GLRenderer extends HardwareRenderer { private void flushLayerChanges() { // Loop through and apply any pending layer changes - for (int i = 0; i < mAttachedLayers.size(); i++) { - HardwareLayer layer = mAttachedLayers.get(i); + for (int i = 0; i < mLayerUpdates.size(); i++) { + HardwareLayer layer = mLayerUpdates.get(i); layer.flushChanges(); if (!layer.isValid()) { // The layer was removed from mAttachedLayers, rewind i by 1 // Note that this shouldn't actually happen as View.getHardwareLayer() // is already flushing for error checking reasons i--; + } else if (layer.hasDisplayList()) { + mCanvas.pushLayerUpdate(layer); } } + mLayerUpdates.clear(); } @Override @@ -1446,7 +1435,18 @@ public class GLRenderer extends HardwareRenderer { private static native void nPrepareTree(long displayListPtr); - class DrawPerformanceDataProvider extends GraphDataProvider { + class GraphDataProvider { + /** + * Draws the graph as bars. Frame elements are stacked on top of + * each other. + */ + public static final int GRAPH_TYPE_BARS = 0; + /** + * Draws the graph as lines. The number of series drawn corresponds + * to the number of elements. + */ + public static final int GRAPH_TYPE_LINES = 1; + private final int mGraphType; private int mVerticalUnit; @@ -1454,11 +1454,10 @@ public class GLRenderer extends HardwareRenderer { private int mHorizontalMargin; private int mThresholdStroke; - DrawPerformanceDataProvider(int graphType) { + public GraphDataProvider(int graphType) { mGraphType = graphType; } - @Override void prepare(DisplayMetrics metrics) { final float density = metrics.density; @@ -1468,64 +1467,52 @@ public class GLRenderer extends HardwareRenderer { mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density); } - @Override int getGraphType() { return mGraphType; } - @Override int getVerticalUnitSize() { return mVerticalUnit; } - @Override int getHorizontalUnitSize() { return mHorizontalUnit; } - @Override int getHorizontaUnitMargin() { return mHorizontalMargin; } - @Override float[] getData() { return mProfileData; } - @Override float getThreshold() { return 16; } - @Override int getFrameCount() { return mProfileData.length / PROFILE_FRAME_DATA_COUNT; } - @Override int getElementCount() { return PROFILE_FRAME_DATA_COUNT; } - @Override int getCurrentFrame() { return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT; } - @Override void setupGraphPaint(Paint paint, int elementIndex) { paint.setColor(PROFILE_DRAW_COLORS[elementIndex]); if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); } - @Override void setupThresholdPaint(Paint paint) { paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR); paint.setStrokeWidth(mThresholdStroke); } - @Override void setupCurrentFramePaint(Paint paint) { paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR); if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java index 4d78733..652bcd2 100644 --- a/core/java/android/view/HardwareLayer.java +++ b/core/java/android/view/HardwareLayer.java @@ -22,6 +22,8 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import com.android.internal.util.VirtualRefBasePtr; + /** * A hardware layer can be used to render graphics operations into a hardware * friendly buffer. For instance, with an OpenGL backend a hardware layer @@ -36,7 +38,7 @@ final class HardwareLayer { private static final int LAYER_TYPE_DISPLAY_LIST = 2; private HardwareRenderer mRenderer; - private Finalizer mFinalizer; + private VirtualRefBasePtr mFinalizer; private RenderNode mDisplayList; private final int mLayerType; @@ -47,10 +49,7 @@ final class HardwareLayer { } mRenderer = renderer; mLayerType = type; - mFinalizer = new Finalizer(deferredUpdater); - - // Layer is considered initialized at this point, notify the HardwareRenderer - mRenderer.onLayerCreated(this); + mFinalizer = new VirtualRefBasePtr(deferredUpdater); } private void assertType(int type) { @@ -59,6 +58,10 @@ final class HardwareLayer { } } + boolean hasDisplayList() { + return mDisplayList != null; + } + /** * Update the paint used when drawing this layer. * @@ -66,7 +69,8 @@ final class HardwareLayer { * @see View#setLayerPaint(android.graphics.Paint) */ public void setLayerPaint(Paint paint) { - nSetLayerPaint(mFinalizer.mDeferredUpdater, paint.mNativePaint); + nSetLayerPaint(mFinalizer.get(), paint.mNativePaint); + mRenderer.pushLayerUpdate(this); } /** @@ -75,7 +79,7 @@ final class HardwareLayer { * @return True if the layer can be rendered into, false otherwise */ public boolean isValid() { - return mFinalizer != null && mFinalizer.mDeferredUpdater != 0; + return mFinalizer != null && mFinalizer.get() != 0; } /** @@ -91,35 +95,14 @@ final class HardwareLayer { mDisplayList.destroyDisplayListData(); mDisplayList = null; } - if (mRenderer != null) { - mRenderer.onLayerDestroyed(this); - mRenderer = null; - } - doDestroyLayerUpdater(); + mRenderer.onLayerDestroyed(this); + mRenderer = null; + mFinalizer.release(); + mFinalizer = null; } public long getDeferredLayerUpdater() { - return mFinalizer.mDeferredUpdater; - } - - /** - * Destroys the deferred layer updater but not the backing layer. The - * backing layer is instead returned and is the caller's responsibility - * to destroy/recycle as appropriate. - * - * It is safe to call this in onLayerDestroyed only - */ - public long detachBackingLayer() { - long backingLayer = nDetachBackingLayer(mFinalizer.mDeferredUpdater); - doDestroyLayerUpdater(); - return backingLayer; - } - - private void doDestroyLayerUpdater() { - if (mFinalizer != null) { - mFinalizer.destroy(); - mFinalizer = null; - } + return mFinalizer.get(); } public RenderNode startRecording() { @@ -132,7 +115,7 @@ final class HardwareLayer { } public void endRecording(Rect dirtyRect) { - nUpdateRenderLayer(mFinalizer.mDeferredUpdater, mDisplayList.getNativeDisplayList(), + nUpdateRenderLayer(mFinalizer.get(), mDisplayList.getNativeDisplayList(), dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom); mRenderer.pushLayerUpdate(this); } @@ -160,7 +143,7 @@ final class HardwareLayer { * match the desired values. */ public boolean prepare(int width, int height, boolean isOpaque) { - return nPrepare(mFinalizer.mDeferredUpdater, width, height, isOpaque); + return nPrepare(mFinalizer.get(), width, height, isOpaque); } /** @@ -169,7 +152,8 @@ final class HardwareLayer { * @param matrix The transform to apply to the layer. */ public void setTransform(Matrix matrix) { - nSetTransform(mFinalizer.mDeferredUpdater, matrix.native_instance); + nSetTransform(mFinalizer.get(), matrix.native_instance); + mRenderer.pushLayerUpdate(this); } /** @@ -183,7 +167,7 @@ final class HardwareLayer { surface.detachFromGLContext(); // SurfaceTexture owns the texture name and detachFromGLContext // should have deleted it - nOnTextureDestroyed(mFinalizer.mDeferredUpdater); + nOnTextureDestroyed(mFinalizer.get()); } }); } @@ -200,24 +184,26 @@ final class HardwareLayer { return; } - boolean success = nFlushChanges(mFinalizer.mDeferredUpdater); + boolean success = nFlushChanges(mFinalizer.get()); if (!success) { destroy(); } } public long getLayer() { - return nGetLayer(mFinalizer.mDeferredUpdater); + return nGetLayer(mFinalizer.get()); } public void setSurfaceTexture(SurfaceTexture surface) { assertType(LAYER_TYPE_TEXTURE); - nSetSurfaceTexture(mFinalizer.mDeferredUpdater, surface, false); + nSetSurfaceTexture(mFinalizer.get(), surface, false); + mRenderer.pushLayerUpdate(this); } public void updateSurfaceTexture() { assertType(LAYER_TYPE_TEXTURE); - nUpdateSurfaceTexture(mFinalizer.mDeferredUpdater); + nUpdateSurfaceTexture(mFinalizer.get()); + mRenderer.pushLayerUpdate(this); } /** @@ -225,8 +211,8 @@ final class HardwareLayer { */ SurfaceTexture createSurfaceTexture() { assertType(LAYER_TYPE_TEXTURE); - SurfaceTexture st = new SurfaceTexture(nGetTexName(mFinalizer.mDeferredUpdater)); - nSetSurfaceTexture(mFinalizer.mDeferredUpdater, st, true); + SurfaceTexture st = new SurfaceTexture(nGetTexName(mFinalizer.get())); + nSetSurfaceTexture(mFinalizer.get(), st, true); return st; } @@ -258,15 +244,6 @@ final class HardwareLayer { private static native long nCreateRenderLayer(int width, int height); private static native void nOnTextureDestroyed(long layerUpdater); - private static native long nDetachBackingLayer(long layerUpdater); - - /** This also destroys the underlying layer if it is still attached. - * Note it does not recycle the underlying layer, but instead queues it - * for deferred deletion. - * The HardwareRenderer should use detachBackingLayer() in the - * onLayerDestroyed() callback to do recycling if desired. - */ - private static native void nDestroyLayerUpdater(long layerUpdater); private static native boolean nPrepare(long layerUpdater, int width, int height, boolean isOpaque); private static native void nSetLayerPaint(long layerUpdater, long paint); @@ -281,28 +258,4 @@ final class HardwareLayer { private static native long nGetLayer(long layerUpdater); private static native int nGetTexName(long layerUpdater); - - private static class Finalizer { - private long mDeferredUpdater; - - public Finalizer(long deferredUpdater) { - mDeferredUpdater = deferredUpdater; - } - - @Override - protected void finalize() throws Throwable { - try { - destroy(); - } finally { - super.finalize(); - } - } - - void destroy() { - if (mDeferredUpdater != 0) { - nDestroyLayerUpdater(mDeferredUpdater); - mDeferredUpdater = 0; - } - } - } } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 3c4d83f..d71de9f 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -17,13 +17,13 @@ package android.view; import android.graphics.Bitmap; -import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.util.DisplayMetrics; import android.view.Surface.OutOfResourcesException; import java.io.File; +import java.io.FileDescriptor; import java.io.PrintWriter; /** @@ -61,11 +61,9 @@ public abstract class HardwareRenderer { * Possible values: * "true", to enable profiling * "visual_bars", to enable profiling and visualize the results on screen - * "visual_lines", to enable profiling and visualize the results on screen * "false", to disable profiling * * @see #PROFILE_PROPERTY_VISUALIZE_BARS - * @see #PROFILE_PROPERTY_VISUALIZE_LINES * * @hide */ @@ -80,14 +78,6 @@ public abstract class HardwareRenderer { public static final String PROFILE_PROPERTY_VISUALIZE_BARS = "visual_bars"; /** - * Value for {@link #PROFILE_PROPERTY}. When the property is set to this - * value, profiling data will be visualized on screen as a line chart. - * - * @hide - */ - public static final String PROFILE_PROPERTY_VISUALIZE_LINES = "visual_lines"; - - /** * System property used to specify the number of frames to be used * when doing hardware rendering profiling. * The default value of this property is #PROFILE_MAX_FRAMES. @@ -298,16 +288,8 @@ public abstract class HardwareRenderer { /** * Outputs extra debugging information in the specified file descriptor. - * @param pw - */ - abstract void dumpGfxInfo(PrintWriter pw); - - /** - * Outputs the total number of frames rendered (used for fps calculations) - * - * @return the number of frames rendered */ - abstract long getFrameCount(); + abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd); /** * Loads system properties used by the renderer. This method is invoked @@ -341,12 +323,6 @@ public abstract class HardwareRenderer { abstract void pushLayerUpdate(HardwareLayer layer); /** - * Tells the HardwareRenderer that a layer was created. The renderer should - * make sure to apply any pending layer changes at the start of a new frame - */ - abstract void onLayerCreated(HardwareLayer hardwareLayer); - - /** * Tells the HardwareRenderer that the layer is destroyed. The renderer * should remove the layer from any update queues. */ @@ -583,98 +559,4 @@ public abstract class HardwareRenderer { */ public void notifyFramePending() { } - - /** - * Describes a series of frames that should be drawn on screen as a graph. - * Each frame is composed of 1 or more elements. - */ - abstract class GraphDataProvider { - /** - * Draws the graph as bars. Frame elements are stacked on top of - * each other. - */ - public static final int GRAPH_TYPE_BARS = 0; - /** - * Draws the graph as lines. The number of series drawn corresponds - * to the number of elements. - */ - public static final int GRAPH_TYPE_LINES = 1; - - /** - * Returns the type of graph to render. - * - * @return {@link #GRAPH_TYPE_BARS} or {@link #GRAPH_TYPE_LINES} - */ - abstract int getGraphType(); - - /** - * This method is invoked before the graph is drawn. This method - * can be used to compute sizes, etc. - * - * @param metrics The display metrics - */ - abstract void prepare(DisplayMetrics metrics); - - /** - * @return The size in pixels of a vertical unit. - */ - abstract int getVerticalUnitSize(); - - /** - * @return The size in pixels of a horizontal unit. - */ - abstract int getHorizontalUnitSize(); - - /** - * @return The size in pixels of the margin between horizontal units. - */ - abstract int getHorizontaUnitMargin(); - - /** - * An optional threshold value. - * - * @return A value >= 0 to draw the threshold, a negative value - * to ignore it. - */ - abstract float getThreshold(); - - /** - * The data to draw in the graph. The number of elements in the - * array must be at least {@link #getFrameCount()} * {@link #getElementCount()}. - * If a value is negative the following values will be ignored. - */ - abstract float[] getData(); - - /** - * Returns the number of frames to render in the graph. - */ - abstract int getFrameCount(); - - /** - * Returns the number of elements in each frame. This directly affects - * the number of series drawn in the graph. - */ - abstract int getElementCount(); - - /** - * Returns the current frame, if any. If the returned value is negative - * the current frame is ignored. - */ - abstract int getCurrentFrame(); - - /** - * Prepares the paint to draw the specified element (or series.) - */ - abstract void setupGraphPaint(Paint paint, int elementIndex); - - /** - * Prepares the paint to draw the threshold. - */ - abstract void setupThresholdPaint(Paint paint); - - /** - * Prepares the paint to draw the current frame indicator. - */ - abstract void setupCurrentFramePaint(Paint paint); - } } diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index cf125bc..e63829e 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -850,6 +850,13 @@ public class RenderNode { nOutput(mNativeRenderNode); } + /** + * Gets the size of the DisplayList for debug purposes. + */ + public int getDebugSize() { + return nGetDebugSize(mNativeRenderNode); + } + /////////////////////////////////////////////////////////////////////////// // Animations /////////////////////////////////////////////////////////////////////////// @@ -941,6 +948,7 @@ public class RenderNode { private static native float nGetPivotX(long renderNode); private static native float nGetPivotY(long renderNode); private static native void nOutput(long renderNode); + private static native int nGetDebugSize(long renderNode); /////////////////////////////////////////////////////////////////////////// // Animations diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 1765c43..2a9f7d5 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -405,7 +405,9 @@ public class TextureView extends View { // To cancel updates, the easiest thing to do is simply to remove the // updates listener if (visibility == VISIBLE) { - mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler); + if (mLayer != null) { + mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler); + } updateLayerAndInvalidate(); } else { mSurface.setOnFrameAvailableListener(null); diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 9c9a939..11db996 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -22,12 +22,14 @@ import android.graphics.SurfaceTexture; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.Trace; import android.util.Log; import android.util.TimeUtils; import android.view.Surface.OutOfResourcesException; import android.view.View.AttachInfo; +import java.io.FileDescriptor; import java.io.PrintWriter; /** @@ -60,11 +62,16 @@ public class ThreadedRenderer extends HardwareRenderer { // Needs a ViewRoot invalidate private static final int SYNC_INVALIDATE_REQUIRED = 0x1; + private static final String[] VISUALIZERS = { + PROFILE_PROPERTY_VISUALIZE_BARS, + }; + private int mWidth, mHeight; private long mNativeProxy; private boolean mInitialized = false; private RenderNode mRootNode; private Choreographer mChoreographer; + private boolean mProfilingEnabled; ThreadedRenderer(boolean translucent) { AtlasInitializer.sInstance.init(); @@ -77,6 +84,8 @@ public class ThreadedRenderer extends HardwareRenderer { // Setup timing mChoreographer = Choreographer.getInstance(); nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos()); + + loadSystemProperties(); } @Override @@ -166,19 +175,33 @@ public class ThreadedRenderer extends HardwareRenderer { } @Override - void dumpGfxInfo(PrintWriter pw) { - // TODO Auto-generated method stub + void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) { + pw.flush(); + nDumpProfileInfo(mNativeProxy, fd); } - @Override - long getFrameCount() { - // TODO Auto-generated method stub - return 0; + private static int search(String[] values, String value) { + for (int i = 0; i < values.length; i++) { + if (values[i].equals(value)) return i; + } + return -1; + } + + private static boolean checkIfProfilingRequested() { + String profiling = SystemProperties.get(HardwareRenderer.PROFILE_PROPERTY); + int graphType = search(VISUALIZERS, profiling); + return (graphType >= 0) || Boolean.parseBoolean(profiling); } @Override boolean loadSystemProperties() { - return nLoadSystemProperties(mNativeProxy); + boolean changed = nLoadSystemProperties(mNativeProxy); + boolean wantProfiling = checkIfProfilingRequested(); + if (wantProfiling != mProfilingEnabled) { + mProfilingEnabled = wantProfiling; + changed = true; + } + return changed; } private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) { @@ -210,14 +233,24 @@ public class ThreadedRenderer extends HardwareRenderer { long frameTimeNanos = mChoreographer.getFrameTimeNanos(); attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS; + long recordDuration = 0; + if (mProfilingEnabled) { + recordDuration = System.nanoTime(); + } + updateRootDisplayList(view, callbacks); + if (mProfilingEnabled) { + recordDuration = System.nanoTime() - recordDuration; + } + attachInfo.mIgnoreDirtyState = false; if (dirty == null) { dirty = NULL_RECT; } int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos, + recordDuration, view.getResources().getDisplayMetrics().density, dirty.left, dirty.top, dirty.right, dirty.bottom); if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) { attachInfo.mViewRootImpl.invalidate(); @@ -261,12 +294,7 @@ public class ThreadedRenderer extends HardwareRenderer { @Override void pushLayerUpdate(HardwareLayer layer) { - // TODO: Remove this, it's not needed outside of GLRenderer - } - - @Override - void onLayerCreated(HardwareLayer layer) { - // TODO: Is this actually useful? + nPushLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater()); } @Override @@ -276,7 +304,7 @@ public class ThreadedRenderer extends HardwareRenderer { @Override void onLayerDestroyed(HardwareLayer layer) { - nDestroyLayer(mNativeProxy, layer.getDeferredLayerUpdater()); + nCancelLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater()); } @Override @@ -354,7 +382,8 @@ public class ThreadedRenderer extends HardwareRenderer { private static native void nSetup(long nativeProxy, int width, int height, float lightX, float lightY, float lightZ, float lightRadius); private static native void nSetOpaque(long nativeProxy, boolean opaque); - private static native int nSyncAndDrawFrame(long nativeProxy, long frameTimeNanos, + private static native int nSyncAndDrawFrame(long nativeProxy, + long frameTimeNanos, long recordDuration, float density, int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom); private static native void nRunWithGlContext(long nativeProxy, Runnable runnable); private static native void nDestroyCanvasAndSurface(long nativeProxy); @@ -364,10 +393,13 @@ public class ThreadedRenderer extends HardwareRenderer { private static native long nCreateDisplayListLayer(long nativeProxy, int width, int height); private static native long nCreateTextureLayer(long nativeProxy); private static native boolean nCopyLayerInto(long nativeProxy, long layer, long bitmap); - private static native void nDestroyLayer(long nativeProxy, long layer); + private static native void nPushLayerUpdate(long nativeProxy, long layer); + private static native void nCancelLayerUpdate(long nativeProxy, long layer); private static native void nFlushCaches(long nativeProxy, int flushMode); private static native void nFence(long nativeProxy); private static native void nNotifyFramePending(long nativeProxy); + + private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fc7bf0e..aa06d15 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -5329,7 +5329,7 @@ public final class ViewRootImpl implements ViewParent, RenderNode renderNode = view.mRenderNode; info[0]++; if (renderNode != null) { - info[1] += 0; /* TODO: Memory used by RenderNodes (properties + DisplayLists) */ + info[1] += renderNode.getDebugSize(); } if (view instanceof ViewGroup) { diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 96c0ed2..b4779f4 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -430,7 +430,7 @@ public final class WindowManagerGlobal { HardwareRenderer renderer = root.getView().mAttachInfo.mHardwareRenderer; if (renderer != null) { - renderer.dumpGfxInfo(pw); + renderer.dumpGfxInfo(pw, fd); } } @@ -447,11 +447,6 @@ public final class WindowManagerGlobal { String name = getWindowName(root); pw.printf(" %s\n %d views, %.2f kB of display lists", name, info[0], info[1] / 1024.0f); - HardwareRenderer renderer = - root.getView().mAttachInfo.mHardwareRenderer; - if (renderer != null) { - pw.printf(", %d frames rendered", renderer.getFrameCount()); - } pw.printf("\n\n"); viewsCount += info[0]; diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index 5c7a4e6..c0b5b97 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -588,7 +588,7 @@ public class WindowDecorActionBar extends ActionBar implements return; } - final FragmentTransaction trans = ((View) mDecorToolbar).isInEditMode() ? null : + final FragmentTransaction trans = mDecorToolbar.getViewGroup().isInEditMode() ? null : mActivity.getFragmentManager().beginTransaction().disallowAddToBackStack(); if (mSelectedTab == tab) { @@ -847,7 +847,7 @@ public class WindowDecorActionBar extends ActionBar implements mDecorToolbar.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE); if (mTabScrollView != null && !mDecorToolbar.hasEmbeddedTabs() && - isCollapsed((View) mDecorToolbar)) { + isCollapsed(mDecorToolbar.getViewGroup())) { mTabScrollView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); } } @@ -959,7 +959,7 @@ public class WindowDecorActionBar extends ActionBar implements // Clear out the context mode views after the animation finishes mContextView.closeMode(); - ((View) mDecorToolbar).sendAccessibilityEvent( + mDecorToolbar.getViewGroup().sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll); diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java index 40834ba..17685fd 100644 --- a/core/java/com/android/internal/os/HandlerCaller.java +++ b/core/java/com/android/internal/os/HandlerCaller.java @@ -22,9 +22,6 @@ import android.os.Looper; import android.os.Message; public class HandlerCaller { - - public final Context mContext; - final Looper mMainLooper; final Handler mH; @@ -47,7 +44,6 @@ public class HandlerCaller { public HandlerCaller(Context context, Looper looper, Callback callback, boolean asyncHandler) { - mContext = context; mMainLooper = looper != null ? looper : context.getMainLooper(); mH = new MyHandler(mMainLooper, asyncHandler); mCallback = callback; diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index f6722a6..c0d1e88 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -183,6 +183,33 @@ public class Preconditions { } /** + * Ensures that the argument int value is within the inclusive range. + * + * @param value a int value + * @param lower the lower endpoint of the inclusive range + * @param upper the upper endpoint of the inclusive range + * @param valueName the name of the argument to use if the check fails + * + * @return the validated int value + * + * @throws IllegalArgumentException if {@code value} was not within the range + */ + public static int checkArgumentInRange(int value, int lower, int upper, + String valueName) { + if (value < lower) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%d, %d] (too low)", valueName, lower, upper)); + } else if (value > upper) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%d, %d] (too high)", valueName, lower, upper)); + } + + return value; + } + + /** * Ensures that the array is not {@code null}, and none if its elements are {@code null}. * * @param value an array of boxed objects diff --git a/core/java/com/android/internal/util/VirtualRefBasePtr.java b/core/java/com/android/internal/util/VirtualRefBasePtr.java index 0bd4d3a..52306f1 100644 --- a/core/java/com/android/internal/util/VirtualRefBasePtr.java +++ b/core/java/com/android/internal/util/VirtualRefBasePtr.java @@ -32,11 +32,17 @@ public final class VirtualRefBasePtr { return mNativePtr; } + public void release() { + if (mNativePtr != 0) { + nDecStrong(mNativePtr); + mNativePtr = 0; + } + } + @Override protected void finalize() throws Throwable { try { - nDecStrong(mNativePtr); - mNativePtr = 0; + release(); } finally { super.finalize(); } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 25e3463..d31c5cc 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -43,7 +43,6 @@ import android.view.View; import android.widget.Button; import com.android.internal.R; -import com.android.internal.telephony.ITelephony; import com.google.android.collect.Lists; import java.security.MessageDigest; @@ -1360,19 +1359,11 @@ public class LockPatternUtils { /** * Resumes a call in progress. Typically launched from the EmergencyCall button * on various lockscreens. - * - * @return true if we were able to tell InCallScreen to show. */ - public boolean resumeCall() { - ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); - try { - if (phone != null && phone.showCallScreen()) { - return true; - } - } catch (RemoteException e) { - // What can we do? - } - return false; + public void resumeCall() { + TelephonyManager telephonyManager = + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + telephonyManager.showCallScreen(); } private void finishBiometricWeak() { diff --git a/core/java/com/android/internal/widget/LockPatternUtilsCache.java b/core/java/com/android/internal/widget/LockPatternUtilsCache.java index 550aa6d..624f67c 100644 --- a/core/java/com/android/internal/widget/LockPatternUtilsCache.java +++ b/core/java/com/android/internal/widget/LockPatternUtilsCache.java @@ -28,6 +28,11 @@ import android.util.ArrayMap; */ public class LockPatternUtilsCache implements ILockSettings { + private static final String HAS_LOCK_PATTERN_CACHE_KEY + = "LockPatternUtils.Cache.HasLockPatternCacheKey"; + private static final String HAS_LOCK_PASSWORD_CACHE_KEY + = "LockPatternUtils.Cache.HasLockPasswordCacheKey"; + private static LockPatternUtilsCache sInstance; private final ILockSettings mService; @@ -109,7 +114,9 @@ public class LockPatternUtilsCache implements ILockSettings { @Override public void setLockPattern(String pattern, int userId) throws RemoteException { + invalidateCache(HAS_LOCK_PATTERN_CACHE_KEY, userId); mService.setLockPattern(pattern, userId); + putCache(HAS_LOCK_PATTERN_CACHE_KEY, userId, pattern != null); } @Override @@ -119,7 +126,9 @@ public class LockPatternUtilsCache implements ILockSettings { @Override public void setLockPassword(String password, int userId) throws RemoteException { + invalidateCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId); mService.setLockPassword(password, userId); + putCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId, password != null); } @Override @@ -134,12 +143,24 @@ public class LockPatternUtilsCache implements ILockSettings { @Override public boolean havePattern(int userId) throws RemoteException { - return mService.havePattern(userId); + Object value = peekCache(HAS_LOCK_PATTERN_CACHE_KEY, userId); + if (value instanceof Boolean) { + return (boolean) value; + } + boolean result = mService.havePattern(userId); + putCache(HAS_LOCK_PATTERN_CACHE_KEY, userId, result); + return result; } @Override public boolean havePassword(int userId) throws RemoteException { - return mService.havePassword(userId); + Object value = peekCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId); + if (value instanceof Boolean) { + return (boolean) value; + } + boolean result = mService.havePassword(userId); + putCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId, result); + return result; } @Override diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 36ed344..d841d53 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -57,6 +57,7 @@ public class LockPatternView extends View { private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h) private static final boolean PROFILE_DRAWING = false; + private final CellState[][] mCellStates; private boolean mDrawingProfilingStarted = false; private Paint mPaint = new Paint(); @@ -187,6 +188,12 @@ public class LockPatternView extends View { } } + public static class CellState { + public float scale = 1.0f; + public float translateY = 0.0f; + public float alpha = 1.0f; + } + /** * How to display the current pattern. */ @@ -296,6 +303,18 @@ public class LockPatternView extends View { mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight()); } + mPaint.setFilterBitmap(true); + + mCellStates = new CellState[3][3]; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + mCellStates[i][j] = new CellState(); + } + } + } + + public CellState[][] getCellStates() { + return mCellStates; } private Bitmap getBitmapFor(int resId) { @@ -873,18 +892,22 @@ public class LockPatternView extends View { //float centerY = mPaddingTop + i * mSquareHeight + (mSquareHeight / 2); for (int j = 0; j < 3; j++) { float leftX = paddingLeft + j * squareWidth; - drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]); + float scale = mCellStates[i][j].scale; + mPaint.setAlpha((int) (mCellStates[i][j].alpha * 255)); + float translationY = mCellStates[i][j].translateY; + drawCircle(canvas, (int) leftX, (int) topY + translationY, scale, drawLookup[i][j]); } } + // Reset the alpha to draw normally + mPaint.setAlpha(255); + // TODO: the path should be created and cached every time we hit-detect a cell // only the last segment of the path should be computed here // draw the path of the pattern (unless we are in stealth mode) final boolean drawPath = !mInStealthMode; // draw the arrows associated with the path (unless we are in stealth mode) - boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0; - mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms if (drawPath) { for (int i = 0; i < count - 1; i++) { Cell cell = pattern.get(i); @@ -898,7 +921,8 @@ public class LockPatternView extends View { } float leftX = paddingLeft + cell.column * squareWidth; - float topY = paddingTop + cell.row * squareHeight; + float topY = paddingTop + cell.row * squareHeight + + mCellStates[cell.row][cell.column].translateY; drawArrow(canvas, leftX, topY, cell, next); } @@ -919,6 +943,9 @@ public class LockPatternView extends View { float centerX = getCenterXForColumn(cell.column); float centerY = getCenterYForRow(cell.row); + + // Respect translation in animation + centerY += mCellStates[cell.row][cell.column].translateY; if (i == 0) { currentPath.moveTo(centerX, centerY); } else { @@ -933,8 +960,6 @@ public class LockPatternView extends View { } canvas.drawPath(currentPath, mPathPaint); } - - mPaint.setFilterBitmap(oldFlag); // restore default flag } private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) { @@ -979,7 +1004,8 @@ public class LockPatternView extends View { * @param topY * @param partOfPattern Whether this circle is part of the pattern. */ - private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) { + private void drawCircle(Canvas canvas, float leftX, float topY, float scale, + boolean partOfPattern) { Bitmap outerCircle; Bitmap innerCircle; @@ -1019,7 +1045,7 @@ public class LockPatternView extends View { mCircleMatrix.setTranslate(leftX + offsetX, topY + offsetY); mCircleMatrix.preTranslate(mBitmapWidth/2, mBitmapHeight/2); - mCircleMatrix.preScale(sx, sy); + mCircleMatrix.preScale(sx * scale, sy * scale); mCircleMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2); canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint); diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index 041790f..3bab8a2 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -31,9 +31,14 @@ namespace android { -static jlong FontFamily_create(JNIEnv* env, jobject clazz) { +static jlong FontFamily_create(JNIEnv* env, jobject clazz, jstring lang, jint variant) { #ifdef USE_MINIKIN - return (jlong)new FontFamily(); + FontLanguage fontLanguage; + if (lang != NULL) { + ScopedUtfChars str(env, lang); + fontLanguage = FontLanguage(str.c_str(), str.size()); + } + return (jlong)new FontFamily(fontLanguage, variant); #else return 0; #endif @@ -67,7 +72,7 @@ static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong familyPtr, /////////////////////////////////////////////////////////////////////////////// static JNINativeMethod gFontFamilyMethods[] = { - { "nCreateFamily", "()J", (void*)FontFamily_create }, + { "nCreateFamily", "(Ljava/lang/String;I)J", (void*)FontFamily_create }, { "nUnrefFamily", "(J)V", (void*)FontFamily_unref }, { "nAddFont", "(JLjava/lang/String;)Z", (void*)FontFamily_addFont }, }; diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp index ee04d6f..79381ad 100644 --- a/core/jni/android/graphics/MinikinUtils.cpp +++ b/core/jni/android/graphics/MinikinUtils.cpp @@ -28,11 +28,17 @@ void MinikinUtils::SetLayoutProperties(Layout* layout, SkPaint* paint, int flags layout->setFontCollection(resolvedFace->fFontCollection); FontStyle style = resolvedFace->fStyle; char css[256]; - sprintf(css, "font-size: %d; font-weight: %d; font-style: %s; -minikin-bidi: %d", + int off = snprintf(css, sizeof(css), + "font-size: %d; font-weight: %d; font-style: %s; -minikin-bidi: %d;", (int)paint->getTextSize(), style.getWeight() * 100, style.getItalic() ? "italic" : "normal", flags); + SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag(); + off += snprintf(css + off, sizeof(css) - off, " lang: %s;", langString.c_str()); + SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant(); + const char* varstr = var == SkPaintOptionsAndroid::kElegant_Variant ? "elegant" : "compact"; + off += snprintf(css + off, sizeof(css) - off, " -minikin-variant: %s;", varstr); layout->setProperties(css); } diff --git a/core/jni/android_view_HardwareLayer.cpp b/core/jni/android_view_HardwareLayer.cpp index b2f17de..33a2705 100644 --- a/core/jni/android_view_HardwareLayer.cpp +++ b/core/jni/android_view_HardwareLayer.cpp @@ -55,9 +55,7 @@ static jlong android_view_HardwareLayer_createRenderLayer(JNIEnv* env, jobject c Layer* layer = LayerRenderer::createRenderLayer(width, height); if (!layer) return 0; - OpenGLRenderer* renderer = new LayerRenderer(layer); - renderer->initProperties(); - return reinterpret_cast<jlong>( new DeferredLayerUpdater(layer, renderer) ); + return reinterpret_cast<jlong>( new DeferredLayerUpdater(layer) ); } static void android_view_HardwareLayer_onTextureDestroyed(JNIEnv* env, jobject clazz, @@ -66,18 +64,6 @@ static void android_view_HardwareLayer_onTextureDestroyed(JNIEnv* env, jobject c layer->backingLayer()->clearTexture(); } -static jlong android_view_HardwareLayer_detachBackingLayer(JNIEnv* env, jobject clazz, - jlong layerUpdaterPtr) { - DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); - return reinterpret_cast<jlong>( layer->detachBackingLayer() ); -} - -static void android_view_HardwareLayer_destroyLayerUpdater(JNIEnv* env, jobject clazz, - jlong layerUpdaterPtr) { - DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); - delete layer; -} - static jboolean android_view_HardwareLayer_prepare(JNIEnv* env, jobject clazz, jlong layerUpdaterPtr, jint width, jint height, jboolean isOpaque) { DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); @@ -157,8 +143,6 @@ static JNINativeMethod gMethods[] = { { "nCreateTextureLayer", "()J", (void*) android_view_HardwareLayer_createTextureLayer }, { "nCreateRenderLayer", "(II)J", (void*) android_view_HardwareLayer_createRenderLayer }, { "nOnTextureDestroyed", "(J)V", (void*) android_view_HardwareLayer_onTextureDestroyed }, - { "nDetachBackingLayer", "(J)J", (void*) android_view_HardwareLayer_detachBackingLayer }, - { "nDestroyLayerUpdater", "(J)V", (void*) android_view_HardwareLayer_destroyLayerUpdater }, { "nPrepare", "(JIIZ)Z", (void*) android_view_HardwareLayer_prepare }, { "nSetLayerPaint", "(JJ)V", (void*) android_view_HardwareLayer_setLayerPaint }, diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp index 867c1b1..26022e0 100644 --- a/core/jni/android_view_RenderNode.cpp +++ b/core/jni/android_view_RenderNode.cpp @@ -48,6 +48,12 @@ static void android_view_RenderNode_output(JNIEnv* env, renderNode->output(); } +static jint android_view_RenderNode_getDebugSize(JNIEnv* env, + jobject clazz, jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->getDebugSize(); +} + static jlong android_view_RenderNode_create(JNIEnv* env, jobject clazz, jstring name) { RenderNode* renderNode = new RenderNode(); renderNode->incStrong(0); @@ -505,6 +511,7 @@ static JNINativeMethod gMethods[] = { { "nDestroyRenderNode", "(J)V", (void*) android_view_RenderNode_destroyRenderNode }, { "nSetDisplayListData", "(JJ)V", (void*) android_view_RenderNode_setDisplayListData }, { "nOutput", "(J)V", (void*) android_view_RenderNode_output }, + { "nGetDebugSize", "(J)I", (void*) android_view_RenderNode_getDebugSize }, { "nSetCaching", "(JZ)V", (void*) android_view_RenderNode_setCaching }, { "nSetStaticMatrix", "(JJ)V", (void*) android_view_RenderNode_setStaticMatrix }, diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 6f256f0..1397131 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -238,10 +238,11 @@ static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz, } static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz, - jlong proxyPtr, jlong frameTimeNanos, jint dirtyLeft, jint dirtyTop, - jint dirtyRight, jint dirtyBottom) { + jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density, + jint dirtyLeft, jint dirtyTop, jint dirtyRight, jint dirtyBottom) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); - return proxy->syncAndDrawFrame(frameTimeNanos, dirtyLeft, dirtyTop, dirtyRight, dirtyBottom); + return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density, + dirtyLeft, dirtyTop, dirtyRight, dirtyBottom); } static void android_view_ThreadedRenderer_destroyCanvasAndSurface(JNIEnv* env, jobject clazz, @@ -286,11 +287,18 @@ static jboolean android_view_ThreadedRenderer_copyLayerInto(JNIEnv* env, jobject return proxy->copyLayerInto(layer, bitmap); } -static void android_view_ThreadedRenderer_destroyLayer(JNIEnv* env, jobject clazz, +static void android_view_ThreadedRenderer_pushLayerUpdate(JNIEnv* env, jobject clazz, jlong proxyPtr, jlong layerPtr) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr); - proxy->destroyLayer(layer); + proxy->pushLayerUpdate(layer); +} + +static void android_view_ThreadedRenderer_cancelLayerUpdate(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong layerPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr); + proxy->cancelLayerUpdate(layer); } static void android_view_ThreadedRenderer_flushCaches(JNIEnv* env, jobject clazz, @@ -311,6 +319,13 @@ static void android_view_ThreadedRenderer_notifyFramePending(JNIEnv* env, jobjec proxy->notifyFramePending(); } +static void android_view_ThreadedRenderer_dumpProfileInfo(JNIEnv* env, jobject clazz, + jlong proxyPtr, jobject javaFileDescriptor) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor); + proxy->dumpProfileInfo(fd); +} + #endif // ---------------------------------------------------------------------------- @@ -332,17 +347,19 @@ static JNINativeMethod gMethods[] = { { "nPauseSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_pauseSurface }, { "nSetup", "(JIIFFFF)V", (void*) android_view_ThreadedRenderer_setup }, { "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque }, - { "nSyncAndDrawFrame", "(JJIIII)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame }, + { "nSyncAndDrawFrame", "(JJJFIIII)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame }, { "nDestroyCanvasAndSurface", "(J)V", (void*) android_view_ThreadedRenderer_destroyCanvasAndSurface }, { "nInvokeFunctor", "(JJZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor }, { "nRunWithGlContext", "(JLjava/lang/Runnable;)V", (void*) android_view_ThreadedRenderer_runWithGlContext }, { "nCreateDisplayListLayer", "(JII)J", (void*) android_view_ThreadedRenderer_createDisplayListLayer }, { "nCreateTextureLayer", "(J)J", (void*) android_view_ThreadedRenderer_createTextureLayer }, { "nCopyLayerInto", "(JJJ)Z", (void*) android_view_ThreadedRenderer_copyLayerInto }, - { "nDestroyLayer", "(JJ)V", (void*) android_view_ThreadedRenderer_destroyLayer }, + { "nPushLayerUpdate", "(JJ)V", (void*) android_view_ThreadedRenderer_pushLayerUpdate }, + { "nCancelLayerUpdate", "(JJ)V", (void*) android_view_ThreadedRenderer_cancelLayerUpdate }, { "nFlushCaches", "(JI)V", (void*) android_view_ThreadedRenderer_flushCaches }, { "nFence", "(J)V", (void*) android_view_ThreadedRenderer_fence }, { "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending }, + { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo }, #endif }; diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 7dc967c..2d5477c 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2252,6 +2252,7 @@ <public type="style" name="Theme.Quantum.NoActionBar.Overscan" /> <public type="style" name="Theme.Quantum.NoActionBar.TranslucentDecor" /> <public type="style" name="Theme.Quantum.Panel" /> + <public type="style" name="Theme.Quantum.Voice" /> <public type="style" name="Theme.Quantum.Wallpaper" /> <public type="style" name="Theme.Quantum.Wallpaper.NoTitleBar" /> @@ -2268,6 +2269,7 @@ <public type="style" name="Theme.Quantum.Light.NoActionBar.Overscan" /> <public type="style" name="Theme.Quantum.Light.NoActionBar.TranslucentDecor" /> <public type="style" name="Theme.Quantum.Light.Panel" /> + <public type="style" name="Theme.Quantum.Light.Voice" /> <public type="style" name="ThemeOverlay" /> <public type="style" name="ThemeOverlay.Quantum" /> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 933063f..a0b3b63 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -169,6 +169,12 @@ please see styles_device_defaults.xml. <item name="windowExitAnimation">@anim/input_method_exit</item> </style> + <!-- Window animations that are applied to voice activity windows. --> + <style name="Animation.VoiceActivity"> + <item name="windowEnterAnimation">@anim/voice_activity_open_enter</item> + <item name="windowExitAnimation">@anim/voice_activity_close_exit</item> + </style> + <!-- Window animations that are applied to voice interaction overlay windows. --> <style name="Animation.VoiceInteractionSession"> <item name="windowEnterAnimation">@anim/voice_layer_enter</item> diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml index 484c694..47ba764 100644 --- a/core/res/res/values/themes_quantum.xml +++ b/core/res/res/values/themes_quantum.xml @@ -913,6 +913,22 @@ please see themes_device_defaults.xml. <item name="windowNoTitle">true</item> </style> + <!-- Quantum theme for an activity that is to be used for voice interaction. + This gives the activity a floating dialog style, to incorporate with the + system voice experience. --> + <style name="Theme.Quantum.Voice" parent="@style/Theme.Quantum.Dialog"> + <item name="windowAnimationStyle">@style/Animation.VoiceActivity</item> + <item name="backgroundDimEnabled">false</item> + </style> + + <!-- Quantum light theme for an activity that is to be used for voice interaction. + This gives the activity a floating dialog style, to incorporate with the + system voice experience. --> + <style name="Theme.Quantum.Light.Voice" parent="@style/Theme.Quantum.Light.Dialog"> + <item name="windowAnimationStyle">@style/Animation.VoiceActivity</item> + <item name="backgroundDimEnabled">false</item> + </style> + <!-- Default theme for quantum style input methods, which is used by the {@link android.inputmethodservice.InputMethodService} class. this inherits from Theme.Panel, but sets up IME appropriate animations @@ -929,7 +945,7 @@ please see themes_device_defaults.xml. this inherits from Theme.Panel, but sets up appropriate animations and a few custom attributes. --> <style name="Theme.Quantum.VoiceInteractionSession" parent="Theme.Quantum.Light.Panel"> - <item name="android:windowAnimationStyle">@android:style/Animation.VoiceInteractionSession</item> + <item name="windowAnimationStyle">@style/Animation.VoiceInteractionSession</item> </style> <!-- Theme for the search input bar. --> diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScanFilterTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java index ec35d85..bf34f1d 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScanFilterTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; -import android.bluetooth.BluetoothLeScanner.ScanResult; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.os.Parcel; import android.os.ParcelUuid; import android.test.suitebuilder.annotation.SmallTest; @@ -26,22 +27,21 @@ import junit.framework.TestCase; /** * Unit test cases for Bluetooth LE scan filters. * <p> - * To run this test, use adb shell am instrument -e class - * 'android.bluetooth.BluetoothLeScanFilterTest' -w + * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanFilterTest' -w * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' */ -public class BluetoothLeScanFilterTest extends TestCase { +public class ScanFilterTest extends TestCase { private static final String DEVICE_MAC = "01:02:03:04:05:AB"; private ScanResult mScanResult; - private BluetoothLeScanFilter.Builder mFilterBuilder; + private ScanFilter.Builder mFilterBuilder; @Override protected void setUp() throws Exception { byte[] scanRecord = new byte[] { 0x02, 0x01, 0x1a, // advertising flags 0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids - 0x04, 0x09, 0x50, 0x65, 0x64, // name + 0x04, 0x09, 0x50, 0x65, 0x64, // setName 0x02, 0x0A, (byte) 0xec, // tx power level 0x05, 0x16, 0x0b, 0x11, 0x50, 0x64, // service data 0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data @@ -51,134 +51,135 @@ public class BluetoothLeScanFilterTest extends TestCase { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); BluetoothDevice device = adapter.getRemoteDevice(DEVICE_MAC); mScanResult = new ScanResult(device, scanRecord, -10, 1397545200000000L); - mFilterBuilder = BluetoothLeScanFilter.newBuilder(); + mFilterBuilder = new ScanFilter.Builder(); } @SmallTest - public void testNameFilter() { - BluetoothLeScanFilter filter = mFilterBuilder.name("Ped").build(); - assertTrue("name filter fails", filter.matches(mScanResult)); + public void testsetNameFilter() { + ScanFilter filter = mFilterBuilder.setName("Ped").build(); + assertTrue("setName filter fails", filter.matches(mScanResult)); - filter = mFilterBuilder.name("Pem").build(); - assertFalse("name filter fails", filter.matches(mScanResult)); + filter = mFilterBuilder.setName("Pem").build(); + assertFalse("setName filter fails", filter.matches(mScanResult)); } @SmallTest public void testDeviceFilter() { - BluetoothLeScanFilter filter = mFilterBuilder.macAddress(DEVICE_MAC).build(); + ScanFilter filter = mFilterBuilder.setMacAddress(DEVICE_MAC).build(); assertTrue("device filter fails", filter.matches(mScanResult)); - filter = mFilterBuilder.macAddress("11:22:33:44:55:66").build(); + filter = mFilterBuilder.setMacAddress("11:22:33:44:55:66").build(); assertFalse("device filter fails", filter.matches(mScanResult)); } @SmallTest - public void testServiceUuidFilter() { - BluetoothLeScanFilter filter = mFilterBuilder.serviceUuid( + public void testsetServiceUuidFilter() { + ScanFilter filter = mFilterBuilder.setServiceUuid( ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB")).build(); assertTrue("uuid filter fails", filter.matches(mScanResult)); - filter = mFilterBuilder.serviceUuid( + filter = mFilterBuilder.setServiceUuid( ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build(); assertFalse("uuid filter fails", filter.matches(mScanResult)); filter = mFilterBuilder - .serviceUuid(ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")) - .serviceUuidMask(ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .setServiceUuid(ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"), + ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) .build(); assertTrue("uuid filter fails", filter.matches(mScanResult)); } @SmallTest - public void testServiceDataFilter() { - byte[] serviceData = new byte[] { + public void testsetServiceDataFilter() { + byte[] setServiceData = new byte[] { 0x0b, 0x11, 0x50, 0x64 }; - BluetoothLeScanFilter filter = mFilterBuilder.serviceData(serviceData).build(); + ScanFilter filter = mFilterBuilder.setServiceData(setServiceData).build(); assertTrue("service data filter fails", filter.matches(mScanResult)); byte[] nonMatchData = new byte[] { 0x0b, 0x01, 0x50, 0x64 }; - filter = mFilterBuilder.serviceData(nonMatchData).build(); + filter = mFilterBuilder.setServiceData(nonMatchData).build(); assertFalse("service data filter fails", filter.matches(mScanResult)); byte[] mask = new byte[] { (byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF }; - filter = mFilterBuilder.serviceData(nonMatchData).serviceDataMask(mask).build(); + filter = mFilterBuilder.setServiceData(nonMatchData, mask).build(); assertTrue("partial service data filter fails", filter.matches(mScanResult)); } @SmallTest public void testManufacturerSpecificData() { - byte[] manufacturerData = new byte[] { + byte[] setManufacturerData = new byte[] { (byte) 0xE0, 0x00, 0x02, 0x15 }; int manufacturerId = 224; - BluetoothLeScanFilter filter = - mFilterBuilder.manufacturerData(manufacturerId, manufacturerData).build(); - assertTrue("manufacturerData filter fails", filter.matches(mScanResult)); + ScanFilter filter = + mFilterBuilder.setManufacturerData(manufacturerId, setManufacturerData).build(); + assertTrue("setManufacturerData filter fails", filter.matches(mScanResult)); byte[] nonMatchData = new byte[] { (byte) 0xF0, 0x00, 0x02, 0x15 }; - filter = mFilterBuilder.manufacturerData(manufacturerId, nonMatchData).build(); - assertFalse("manufacturerData filter fails", filter.matches(mScanResult)); + filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData).build(); + assertFalse("setManufacturerData filter fails", filter.matches(mScanResult)); byte[] mask = new byte[] { (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; - filter = mFilterBuilder.manufacturerData(manufacturerId, nonMatchData) - .manufacturerDataMask(mask).build(); - assertTrue("partial manufacturerData filter fails", filter.matches(mScanResult)); + filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData, mask).build(); + assertTrue("partial setManufacturerData filter fails", filter.matches(mScanResult)); } @SmallTest public void testReadWriteParcel() { - BluetoothLeScanFilter filter = mFilterBuilder.build(); + ScanFilter filter = mFilterBuilder.build(); testReadWriteParcelForFilter(filter); - filter = mFilterBuilder.name("Ped").build(); + filter = mFilterBuilder.setName("Ped").build(); testReadWriteParcelForFilter(filter); - filter = mFilterBuilder.macAddress("11:22:33:44:55:66").build(); + filter = mFilterBuilder.setMacAddress("11:22:33:44:55:66").build(); testReadWriteParcelForFilter(filter); - filter = mFilterBuilder.serviceUuid( + filter = mFilterBuilder.setServiceUuid( ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build(); testReadWriteParcelForFilter(filter); - filter = mFilterBuilder.serviceUuidMask( + filter = mFilterBuilder.setServiceUuid( + ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"), ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")).build(); testReadWriteParcelForFilter(filter); - byte[] serviceData = new byte[] { + byte[] setServiceData = new byte[] { 0x0b, 0x11, 0x50, 0x64 }; - filter = mFilterBuilder.serviceData(serviceData).build(); + filter = mFilterBuilder.setServiceData(setServiceData).build(); testReadWriteParcelForFilter(filter); byte[] serviceDataMask = new byte[] { (byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF }; - filter = mFilterBuilder.serviceDataMask(serviceDataMask).build(); + filter = mFilterBuilder.setServiceData(setServiceData, serviceDataMask).build(); testReadWriteParcelForFilter(filter); byte[] manufacturerData = new byte[] { (byte) 0xE0, 0x00, 0x02, 0x15 }; int manufacturerId = 224; - filter = mFilterBuilder.manufacturerData(manufacturerId, manufacturerData).build(); + filter = mFilterBuilder.setManufacturerData(manufacturerId, manufacturerData).build(); testReadWriteParcelForFilter(filter); byte[] manufacturerDataMask = new byte[] { (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; - filter = mFilterBuilder.manufacturerDataMask(manufacturerDataMask).build(); + filter = mFilterBuilder.setManufacturerData(manufacturerId, manufacturerData, + manufacturerDataMask).build(); testReadWriteParcelForFilter(filter); } - private void testReadWriteParcelForFilter(BluetoothLeScanFilter filter) { + private void testReadWriteParcelForFilter(ScanFilter filter) { Parcel parcel = Parcel.obtain(); filter.writeToParcel(parcel, 0); parcel.setDataPosition(0); - BluetoothLeScanFilter filterFromParcel = - BluetoothLeScanFilter.CREATOR.createFromParcel(parcel); + ScanFilter filterFromParcel = + ScanFilter.CREATOR.createFromParcel(parcel); System.out.println(filterFromParcel); assertEquals(filter, filterFromParcel); } diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAdvertiseScanDataTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java index eb6c419..cece96b 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAdvertiseScanDataTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; +import android.bluetooth.le.ScanRecord; import android.os.ParcelUuid; import android.test.suitebuilder.annotation.SmallTest; @@ -24,13 +25,13 @@ import junit.framework.TestCase; import java.util.Arrays; /** - * Unit test cases for {@link BluetoothLeAdvertiseScanData}. + * Unit test cases for {@link ScanRecord}. * <p> * To run this test, use adb shell am instrument -e class - * 'android.bluetooth.BluetoothLeAdvertiseScanDataTest' -w + * 'android.bluetooth.ScanRecordTest' -w * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' */ -public class BluetoothLeAdvertiseScanDataTest extends TestCase { +public class ScanRecordTest extends TestCase { @SmallTest public void testParser() { @@ -43,8 +44,7 @@ public class BluetoothLeAdvertiseScanDataTest extends TestCase { 0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data 0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble }; - BluetoothLeAdvertiseScanData.ScanRecord data = BluetoothLeAdvertiseScanData.ScanRecord - .getParser().parseFromScanRecord(scanRecord); + ScanRecord data = ScanRecord.parseFromBytes(scanRecord); assertEquals(0x1a, data.getAdvertiseFlags()); ParcelUuid uuid1 = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); ParcelUuid uuid2 = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScannerTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java index 8064ba8..241e88f 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScannerTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; -import android.bluetooth.BluetoothLeScanner.ScanResult; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.os.Parcel; import android.test.suitebuilder.annotation.SmallTest; @@ -25,17 +26,18 @@ import junit.framework.TestCase; /** * Unit test cases for Bluetooth LE scans. * <p> - * To run this test, use adb shell am instrument -e class 'android.bluetooth.BluetoothLeScannerTest' - * -w 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' + * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanResultTest' -w + * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' */ -public class BluetoothLeScannerTest extends TestCase { +public class ScanResultTest extends TestCase { /** * Test read and write parcel of ScanResult */ @SmallTest public void testScanResultParceling() { - BluetoothDevice device = new BluetoothDevice("01:02:03:04:05:06"); + BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( + "01:02:03:04:05:06"); byte[] scanRecord = new byte[] { 1, 2, 3 }; int rssi = -10; diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index 210ea86b..6802b9a 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -30,9 +30,22 @@ public class FontFamily { public long mNativePtr; public FontFamily() { - mNativePtr = nCreateFamily(); + mNativePtr = nCreateFamily(null, 0); if (mNativePtr == 0) { - throw new RuntimeException(); + throw new IllegalStateException("error creating native FontFamily"); + } + } + + public FontFamily(String lang, String variant) { + int varEnum = 0; + if ("compact".equals(variant)) { + varEnum = 1; + } else if ("elegant".equals(variant)) { + varEnum = 2; + } + mNativePtr = nCreateFamily(lang, varEnum); + if (mNativePtr == 0) { + throw new IllegalStateException("error creating native FontFamily"); } } @@ -49,7 +62,7 @@ public class FontFamily { return nAddFont(mNativePtr, path.getAbsolutePath()); } - static native long nCreateFamily(); + static native long nCreateFamily(String lang, int variant); static native void nUnrefFamily(long nativePtr); static native boolean nAddFont(long nativeFamily, String path); } diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index f304f4e..a863a06 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -34,14 +34,18 @@ import java.util.List; public class FontListParser { public static class Family { - public Family(List<String> names, List<String> fontFiles) { + public Family(List<String> names, List<String> fontFiles, String lang, String variant) { this.names = names; this.fontFiles = fontFiles; + this.lang = lang; + this.variant = variant; } public List<String> names; // todo: need attributes for font files public List<String> fontFiles; + public String lang; + public String variant; } /* Parse fallback list (no names) */ @@ -75,6 +79,8 @@ public class FontListParser { throws XmlPullParserException, IOException { List<String> names = null; List<String> fontFiles = new ArrayList<String>(); + String lang = null; + String variant = null; while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); @@ -82,6 +88,12 @@ public class FontListParser { while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; if (parser.getName().equals("file")) { + if (lang == null) { + lang = parser.getAttributeValue(null, "lang"); + } + if (variant == null) { + variant = parser.getAttributeValue(null, "variant"); + } String filename = parser.nextText(); String fullFilename = "/system/fonts/" + filename; fontFiles.add(fullFilename); @@ -98,7 +110,7 @@ public class FontListParser { } } } - return new Family(names, fontFiles); + return new Family(names, fontFiles, lang, variant); } private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index b7613fb..2b07c3f 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -245,7 +245,7 @@ public class Typeface { private static FontFamily makeFamilyFromParsed(FontListParser.Family family) { // TODO: expand to handle attributes like lang and variant - FontFamily fontFamily = new FontFamily(); + FontFamily fontFamily = new FontFamily(family.lang, family.variant); for (String fontFile : family.fontFiles) { fontFamily.addFont(new File(fontFile)); } diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 2cadf09..442f327 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -23,6 +23,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) DisplayListLogBuffer.cpp \ DisplayListRenderer.cpp \ Dither.cpp \ + DrawProfiler.cpp \ Extensions.cpp \ FboCache.cpp \ GradientCache.cpp \ diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp index 285c8c3..97e9bf6 100644 --- a/libs/hwui/DeferredLayerUpdater.cpp +++ b/libs/hwui/DeferredLayerUpdater.cpp @@ -22,14 +22,19 @@ namespace android { namespace uirenderer { -DeferredLayerUpdater::DeferredLayerUpdater(Layer* layer, OpenGLRenderer* renderer) +static void defaultLayerDestroyer(Layer* layer) { + Caches::getInstance().resourceCache.decrementRefcount(layer); +} + +DeferredLayerUpdater::DeferredLayerUpdater(Layer* layer, LayerDestroyer destroyer) : mDisplayList(0) , mSurfaceTexture(0) , mTransform(0) , mNeedsGLContextAttach(false) , mUpdateTexImage(false) , mLayer(layer) - , mCaches(Caches::getInstance()) { + , mCaches(Caches::getInstance()) + , mDestroyer(destroyer) { mWidth = mLayer->layer.getWidth(); mHeight = mLayer->layer.getHeight(); mBlend = mLayer->isBlend(); @@ -37,14 +42,16 @@ DeferredLayerUpdater::DeferredLayerUpdater(Layer* layer, OpenGLRenderer* rendere mAlpha = mLayer->getAlpha(); mMode = mLayer->getMode(); mDirtyRect.setEmpty(); + + if (!mDestroyer) { + mDestroyer = defaultLayerDestroyer; + } } DeferredLayerUpdater::~DeferredLayerUpdater() { SkSafeUnref(mColorFilter); setTransform(0); - if (mLayer) { - mCaches.resourceCache.decrementRefcount(mLayer); - } + mDestroyer(mLayer); } void DeferredLayerUpdater::setPaint(const SkPaint* paint) { diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h index cc62caa..b7cfe80 100644 --- a/libs/hwui/DeferredLayerUpdater.h +++ b/libs/hwui/DeferredLayerUpdater.h @@ -30,13 +30,15 @@ namespace android { namespace uirenderer { +typedef void (*LayerDestroyer)(Layer* layer); + // Container to hold the properties a layer should be set to at the start // of a render pass -class DeferredLayerUpdater { +class DeferredLayerUpdater : public VirtualLightRefBase { public: // Note that DeferredLayerUpdater assumes it is taking ownership of the layer // and will not call incrementRef on it as a result. - ANDROID_API DeferredLayerUpdater(Layer* layer, OpenGLRenderer* renderer = 0); + ANDROID_API DeferredLayerUpdater(Layer* layer, LayerDestroyer = 0); ANDROID_API ~DeferredLayerUpdater(); ANDROID_API bool setSize(uint32_t width, uint32_t height) { @@ -83,12 +85,6 @@ public: return mLayer; } - ANDROID_API Layer* detachBackingLayer() { - Layer* layer = mLayer; - mLayer = 0; - return layer; - } - private: // Generic properties uint32_t mWidth; @@ -111,6 +107,8 @@ private: Layer* mLayer; Caches& mCaches; + LayerDestroyer mDestroyer; + void doUpdateTexImage(); }; diff --git a/libs/hwui/DrawProfiler.cpp b/libs/hwui/DrawProfiler.cpp new file mode 100644 index 0000000..971a66e --- /dev/null +++ b/libs/hwui/DrawProfiler.cpp @@ -0,0 +1,261 @@ +/* + * 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. + */ +#include "DrawProfiler.h" + +#include <cutils/compiler.h> + +#include "OpenGLRenderer.h" +#include "Properties.h" + +#define DEFAULT_MAX_FRAMES 128 + +#define RETURN_IF_DISABLED() if (CC_LIKELY(mType == kNone)) return + +#define NANOS_TO_MILLIS_FLOAT(nanos) ((nanos) * 0.000001f) + +#define PROFILE_DRAW_WIDTH 3 +#define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2 +#define PROFILE_DRAW_DP_PER_MS 7 + +// Number of floats we want to display from FrameTimingData +// If this is changed make sure to update the indexes below +#define NUM_ELEMENTS 4 + +#define RECORD_INDEX 0 +#define PREPARE_INDEX 1 +#define PLAYBACK_INDEX 2 +#define SWAPBUFFERS_INDEX 3 + +// Must be NUM_ELEMENTS in size +static const SkColor ELEMENT_COLORS[] = { 0xcf3e66cc, 0xcf8f00ff, 0xcfdc3912, 0xcfe69800 }; +static const SkColor CURRENT_FRAME_COLOR = 0xcf5faa4d; +static const SkColor THRESHOLD_COLOR = 0xff5faa4d; + +// We could get this from TimeLord and use the actual frame interval, but +// this is good enough +#define FRAME_THRESHOLD 16 + +namespace android { +namespace uirenderer { + +static int dpToPx(int dp, float density) { + return (int) (dp * density + 0.5f); +} + +DrawProfiler::DrawProfiler() + : mType(kNone) + , mDensity(0) + , mData(NULL) + , mDataSize(0) + , mCurrentFrame(-1) + , mPreviousTime(0) + , mVerticalUnit(0) + , mHorizontalUnit(0) + , mThresholdStroke(0) { + setDensity(1); +} + +DrawProfiler::~DrawProfiler() { + destroyData(); +} + +void DrawProfiler::setDensity(float density) { + if (CC_UNLIKELY(mDensity != density)) { + mDensity = density; + mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density); + mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density); + mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density); + } +} + +void DrawProfiler::startFrame(nsecs_t recordDurationNanos) { + RETURN_IF_DISABLED(); + mData[mCurrentFrame].record = NANOS_TO_MILLIS_FLOAT(recordDurationNanos); + mPreviousTime = systemTime(CLOCK_MONOTONIC); +} + +void DrawProfiler::markPlaybackStart() { + RETURN_IF_DISABLED(); + nsecs_t now = systemTime(CLOCK_MONOTONIC); + mData[mCurrentFrame].prepare = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime); + mPreviousTime = now; +} + +void DrawProfiler::markPlaybackEnd() { + RETURN_IF_DISABLED(); + nsecs_t now = systemTime(CLOCK_MONOTONIC); + mData[mCurrentFrame].playback = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime); + mPreviousTime = now; +} + +void DrawProfiler::finishFrame() { + RETURN_IF_DISABLED(); + nsecs_t now = systemTime(CLOCK_MONOTONIC); + mData[mCurrentFrame].swapBuffers = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime); + mPreviousTime = now; + mCurrentFrame = (mCurrentFrame + 1) % mDataSize; +} + +void DrawProfiler::unionDirty(Rect* dirty) { + RETURN_IF_DISABLED(); + // Not worth worrying about minimizing the dirty region for debugging, so just + // dirty the entire viewport. + if (dirty) { + dirty->setEmpty(); + } +} + +void DrawProfiler::draw(OpenGLRenderer* canvas) { + if (CC_LIKELY(mType != kBars)) { + return; + } + + prepareShapes(canvas->getViewportHeight()); + drawGraph(canvas); + drawCurrentFrame(canvas); + drawThreshold(canvas); +} + +void DrawProfiler::createData() { + if (mData) return; + + mDataSize = property_get_int32(PROPERTY_PROFILE_MAXFRAMES, DEFAULT_MAX_FRAMES); + if (mDataSize <= 0) mDataSize = 1; + if (mDataSize > 4096) mDataSize = 4096; // Reasonable maximum + mData = (FrameTimingData*) calloc(mDataSize, sizeof(FrameTimingData)); + mRects = new float*[NUM_ELEMENTS]; + for (int i = 0; i < NUM_ELEMENTS; i++) { + // 4 floats per rect + mRects[i] = (float*) calloc(mDataSize, 4 * sizeof(float)); + } + mCurrentFrame = 0; +} + +void DrawProfiler::destroyData() { + delete mData; + mData = NULL; +} + +void DrawProfiler::addRect(Rect& r, float data, float* shapeOutput) { + r.top = r.bottom - (data * mVerticalUnit); + shapeOutput[0] = r.left; + shapeOutput[1] = r.top; + shapeOutput[2] = r.right; + shapeOutput[3] = r.bottom; + r.bottom = r.top; +} + +void DrawProfiler::prepareShapes(const int baseline) { + Rect r; + r.right = mHorizontalUnit; + for (int i = 0; i < mDataSize; i++) { + const int shapeIndex = i * 4; + r.bottom = baseline; + addRect(r, mData[i].record, mRects[RECORD_INDEX] + shapeIndex); + addRect(r, mData[i].prepare, mRects[PREPARE_INDEX] + shapeIndex); + addRect(r, mData[i].playback, mRects[PLAYBACK_INDEX] + shapeIndex); + addRect(r, mData[i].swapBuffers, mRects[SWAPBUFFERS_INDEX] + shapeIndex); + r.translate(mHorizontalUnit, 0); + } +} + +void DrawProfiler::drawGraph(OpenGLRenderer* canvas) { + SkPaint paint; + for (int i = 0; i < NUM_ELEMENTS; i++) { + paint.setColor(ELEMENT_COLORS[i]); + canvas->drawRects(mRects[i], mDataSize * 4, &paint); + } +} + +void DrawProfiler::drawCurrentFrame(OpenGLRenderer* canvas) { + // This draws a solid rect over the entirety of the current frame's shape + // To do so we use the bottom of mRects[0] and the top of mRects[NUM_ELEMENTS-1] + // which will therefore fully overlap the previously drawn rects + SkPaint paint; + paint.setColor(CURRENT_FRAME_COLOR); + const int i = mCurrentFrame * 4; + canvas->drawRect(mRects[0][i], mRects[NUM_ELEMENTS-1][i+1], mRects[0][i+2], + mRects[0][i+3], &paint); +} + +void DrawProfiler::drawThreshold(OpenGLRenderer* canvas) { + SkPaint paint; + paint.setColor(THRESHOLD_COLOR); + paint.setStrokeWidth(mThresholdStroke); + + float pts[4]; + pts[0] = 0.0f; + pts[1] = pts[3] = canvas->getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit); + pts[2] = canvas->getViewportWidth(); + canvas->drawLines(pts, 4, &paint); +} + +DrawProfiler::ProfileType DrawProfiler::loadRequestedProfileType() { + ProfileType type = kNone; + char buf[PROPERTY_VALUE_MAX] = {'\0',}; + if (property_get(PROPERTY_PROFILE, buf, "") > 0) { + if (!strcmp(buf, PROPERTY_PROFILE_VISUALIZE_BARS)) { + type = kBars; + } else if (!strcmp(buf, "true")) { + type = kConsole; + } + } + return type; +} + +bool DrawProfiler::loadSystemProperties() { + ProfileType newType = loadRequestedProfileType(); + if (newType != mType) { + mType = newType; + if (mType == kNone) { + destroyData(); + } else { + createData(); + } + return true; + } + return false; +} + +void DrawProfiler::dumpData(int fd) { + RETURN_IF_DISABLED(); + + // This method logs the last N frames (where N is <= mDataSize) since the + // last call to dumpData(). In other words if there's a dumpData(), draw frame, + // dumpData(), the last dumpData() should only log 1 frame. + + const FrameTimingData emptyData = {0, 0, 0, 0}; + + FILE *file = fdopen(fd, "a"); + fprintf(file, "\n\tDraw\tPrepare\tProcess\tExecute\n"); + + for (int frameOffset = 1; frameOffset <= mDataSize; frameOffset++) { + int i = (mCurrentFrame + frameOffset) % mDataSize; + if (!memcmp(mData + i, &emptyData, sizeof(FrameTimingData))) { + continue; + } + fprintf(file, "\t%3.2f\t%3.2f\t%3.2f\t%3.2f\n", + mData[i].record, mData[i].prepare, mData[i].playback, mData[i].swapBuffers); + } + // reset the buffer + memset(mData, 0, sizeof(FrameTimingData) * mDataSize); + mCurrentFrame = 0; + + fflush(file); +} + +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/DrawProfiler.h b/libs/hwui/DrawProfiler.h new file mode 100644 index 0000000..c1aa1c6 --- /dev/null +++ b/libs/hwui/DrawProfiler.h @@ -0,0 +1,96 @@ +/* + * 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. + */ +#ifndef DRAWPROFILER_H +#define DRAWPROFILER_H + +#include <utils/Timers.h> +#include "Rect.h" + +namespace android { +namespace uirenderer { + +class OpenGLRenderer; + +class DrawProfiler { +public: + DrawProfiler(); + ~DrawProfiler(); + + bool loadSystemProperties(); + void setDensity(float density); + + void startFrame(nsecs_t recordDurationNanos = 0); + void markPlaybackStart(); + void markPlaybackEnd(); + void finishFrame(); + + void unionDirty(Rect* dirty); + void draw(OpenGLRenderer* canvas); + + void dumpData(int fd); + +private: + enum ProfileType { + kNone, + kConsole, + kBars, + }; + + typedef struct { + float record; + float prepare; + float playback; + float swapBuffers; + } FrameTimingData; + + void createData(); + void destroyData(); + + void addRect(Rect& r, float data, float* shapeOutput); + void prepareShapes(const int baseline); + void drawGraph(OpenGLRenderer* canvas); + void drawCurrentFrame(OpenGLRenderer* canvas); + void drawThreshold(OpenGLRenderer* canvas); + + ProfileType loadRequestedProfileType(); + + ProfileType mType; + float mDensity; + + FrameTimingData* mData; + int mDataSize; + + int mCurrentFrame; + nsecs_t mPreviousTime; + + int mVerticalUnit; + int mHorizontalUnit; + int mThresholdStroke; + + /* + * mRects represents an array of rect shapes, divided into NUM_ELEMENTS + * groups such that each group is drawn with the same paint. + * For example mRects[0] is the array of rect floats suitable for + * OpenGLRenderer:drawRects() that makes up all the FrameTimingData:record + * information. + */ + float** mRects; +}; + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* DRAWPROFILER_H */ diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 20b8f2f..12241b8 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -89,6 +89,36 @@ enum DebugLevel { #define PROPERTY_DEBUG_NV_PROFILING "debug.hwui.nv_profiling" /** + * System property used to enable or disable hardware rendering profiling. + * The default value of this property is assumed to be false. + * + * When profiling is enabled, the adb shell dumpsys gfxinfo command will + * output extra information about the time taken to execute by the last + * frames. + * + * Possible values: + * "true", to enable profiling + * "visual_bars", to enable profiling and visualize the results on screen + * "false", to disable profiling + */ +#define PROPERTY_PROFILE "debug.hwui.profile" +#define PROPERTY_PROFILE_VISUALIZE_BARS "visual_bars" + +/** + * System property used to specify the number of frames to be used + * when doing hardware rendering profiling. + * The default value of this property is #PROFILE_MAX_FRAMES. + * + * When profiling is enabled, the adb shell dumpsys gfxinfo command will + * output extra information about the time taken to execute by the last + * frames. + * + * Possible values: + * "60", to set the limit of frames to 60 + */ +#define PROPERTY_PROFILE_MAXFRAMES "debug.hwui.profile.maxframes" + +/** * Used to enable/disable non-rectangular clipping debugging. * * The accepted values are: diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index df74f31..d964efc 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -93,6 +93,17 @@ void RenderNode::output(uint32_t level) { ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, getName()); } +int RenderNode::getDebugSize() { + int size = sizeof(RenderNode); + if (mStagingDisplayListData) { + size += mStagingDisplayListData->allocator.usedSize(); + } + if (mDisplayListData && mDisplayListData != mStagingDisplayListData) { + size += mDisplayListData->allocator.usedSize(); + } + return size; +} + void RenderNode::prepareTree(TreeInfo& info) { ATRACE_CALL(); diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 1811a7b..1a5377b 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -119,6 +119,7 @@ public: void replayNodeInParent(ReplayStateStruct& replayStruct, const int level); ANDROID_API void output(uint32_t level = 1); + ANDROID_API int getDebugSize(); bool isRenderable() const { return mDisplayListData && mDisplayListData->hasDrawOps; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 160fbea..9ebee1d 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -31,7 +31,6 @@ #define PROPERTY_RENDER_DIRTY_REGIONS "debug.hwui.render_dirty_regions" #define GLES_VERSION 2 -#define USE_TEXTURE_ATLAS false // Android-specific addition that is used to show when frames began in systrace EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface); @@ -229,7 +228,7 @@ void GlobalContext::setTextureAtlas(const sp<GraphicBuffer>& buffer, } void GlobalContext::initAtlas() { - if (USE_TEXTURE_ATLAS) { + if (mAtlasBuffer.get()) { Caches::getInstance().assetAtlas.init(mAtlasBuffer, mAtlasMap, mAtlasMapSize); } } @@ -428,27 +427,11 @@ void CanvasContext::makeCurrent() { mHaveNewSurface |= mGlobalContext->makeCurrent(mEglSurface); } -void CanvasContext::prepareDraw(const Vector<DeferredLayerUpdater*>* layerUpdaters, - TreeInfo& info) { - LOG_ALWAYS_FATAL_IF(!mCanvas, "Cannot prepareDraw without a canvas!"); - makeCurrent(); - - processLayerUpdates(layerUpdaters, info); - if (info.out.hasAnimations) { - // TODO: Uh... crap? - } - prepareTree(info); -} - -void CanvasContext::processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters, - TreeInfo& info) { - for (size_t i = 0; i < layerUpdaters->size(); i++) { - DeferredLayerUpdater* update = layerUpdaters->itemAt(i); - bool success = update->apply(info); - LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!"); - if (update->backingLayer()->deferredUpdateScheduled) { - mCanvas->pushLayerUpdate(update->backingLayer()); - } +void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater, TreeInfo& info) { + bool success = layerUpdater->apply(info); + LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!"); + if (layerUpdater->backingLayer()->deferredUpdateScheduled) { + mCanvas->pushLayerUpdate(layerUpdater->backingLayer()); } } @@ -486,6 +469,8 @@ void CanvasContext::draw(Rect* dirty) { LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE, "drawDisplayList called on a context with no canvas or surface!"); + profiler().markPlaybackStart(); + EGLint width, height; mGlobalContext->beginFrame(mEglSurface, &width, &height); if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) { @@ -493,6 +478,8 @@ void CanvasContext::draw(Rect* dirty) { dirty = NULL; } else if (!mDirtyRegionsEnabled || mHaveNewSurface) { dirty = NULL; + } else { + profiler().unionDirty(dirty); } status_t status; @@ -506,14 +493,17 @@ void CanvasContext::draw(Rect* dirty) { Rect outBounds; status |= mCanvas->drawDisplayList(mRootRenderNode.get(), outBounds); - // TODO: Draw debug info - // TODO: Performance tracking + profiler().draw(mCanvas); mCanvas->finish(); + profiler().markPlaybackEnd(); + if (status & DrawGlInfo::kStatusDrew) { swapBuffers(); } + + profiler().finishFrame(); } // Called by choreographer to do an RT-driven animation @@ -524,6 +514,8 @@ void CanvasContext::doFrame() { ATRACE_CALL(); + profiler().startFrame(); + TreeInfo info; info.evaluateAnimations = true; info.performStagingPush = false; diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index da85d44..00c5bf0 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -23,6 +23,7 @@ #include <utils/Functor.h> #include <utils/Vector.h> +#include "../DrawProfiler.h" #include "../RenderNode.h" #include "RenderTask.h" #include "RenderThread.h" @@ -54,7 +55,8 @@ public: void setup(int width, int height, const Vector3& lightCenter, float lightRadius); void setOpaque(bool opaque); void makeCurrent(); - void prepareDraw(const Vector<DeferredLayerUpdater*>* layerUpdaters, TreeInfo& info); + void processLayerUpdate(DeferredLayerUpdater* layerUpdater, TreeInfo& info); + void prepareTree(TreeInfo& info); void draw(Rect* dirty); void destroyCanvasAndSurface(); @@ -77,12 +79,11 @@ public: void notifyFramePending(); + DrawProfiler& profiler() { return mProfiler; } + private: friend class RegisterFrameCallbackTask; - void processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters, TreeInfo& info); - void prepareTree(TreeInfo& info); - void setSurface(ANativeWindow* window); void swapBuffers(); void requireSurface(); @@ -100,6 +101,8 @@ private: bool mHaveNewSurface; const sp<RenderNode> mRootRenderNode; + + DrawProfiler mProfiler; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index ee3e059..61d67ca 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -21,6 +21,7 @@ #include <utils/Log.h> #include <utils/Trace.h> +#include "../DeferredLayerUpdater.h" #include "../DisplayList.h" #include "../RenderNode.h" #include "CanvasContext.h" @@ -34,6 +35,8 @@ DrawFrameTask::DrawFrameTask() : mRenderThread(NULL) , mContext(NULL) , mFrameTimeNanos(0) + , mRecordDurationNanos(0) + , mDensity(1.0f) // safe enough default , mSyncResult(kSync_OK) { } @@ -45,17 +48,22 @@ void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context) { mContext = context; } -void DrawFrameTask::addLayer(DeferredLayerUpdater* layer) { - LOG_ALWAYS_FATAL_IF(!mContext, "Lifecycle violation, there's no context to addLayer with!"); +void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) { + LOG_ALWAYS_FATAL_IF(!mContext, "Lifecycle violation, there's no context to pushLayerUpdate with!"); - mLayers.push(layer); + for (size_t i = 0; i < mLayers.size(); i++) { + if (mLayers[i].get() == layer) { + return; + } + } + mLayers.push_back(layer); } -void DrawFrameTask::removeLayer(DeferredLayerUpdater* layer) { +void DrawFrameTask::removeLayerUpdate(DeferredLayerUpdater* layer) { for (size_t i = 0; i < mLayers.size(); i++) { - if (mLayers[i] == layer) { - mLayers.removeAt(i); - break; + if (mLayers[i].get() == layer) { + mLayers.erase(mLayers.begin() + i); + return; } } } @@ -64,15 +72,17 @@ void DrawFrameTask::setDirty(int left, int top, int right, int bottom) { mDirty.set(left, top, right, bottom); } -int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos) { +int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos) { LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!"); mSyncResult = kSync_OK; mFrameTimeNanos = frameTimeNanos; + mRecordDurationNanos = recordDurationNanos; postAndWait(); // Reset the single-frame data mFrameTimeNanos = 0; + mRecordDurationNanos = 0; mDirty.setEmpty(); return mSyncResult; @@ -87,6 +97,9 @@ void DrawFrameTask::postAndWait() { void DrawFrameTask::run() { ATRACE_NAME("DrawFrame"); + mContext->profiler().setDensity(mDensity); + mContext->profiler().startFrame(mRecordDurationNanos); + bool canUnblockUiThread; bool canDrawThisFrame; { @@ -125,7 +138,16 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { mContext->makeCurrent(); Caches::getInstance().textureCache.resetMarkInUse(); initTreeInfo(info); - mContext->prepareDraw(&mLayers, info); + + for (size_t i = 0; i < mLayers.size(); i++) { + mContext->processLayerUpdate(mLayers[i].get(), info); + } + mLayers.clear(); + if (info.out.hasAnimations) { + // TODO: Uh... crap? + } + mContext->prepareTree(info); + if (info.out.hasAnimations) { // TODO: dirty calculations, for now just do a full-screen inval mDirty.setEmpty(); diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index acbc02a..d4129b6 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -16,10 +16,11 @@ #ifndef DRAWFRAMETASK_H #define DRAWFRAMETASK_H +#include <vector> + #include <utils/Condition.h> #include <utils/Mutex.h> #include <utils/StrongPointer.h> -#include <utils/Vector.h> #include "RenderTask.h" @@ -56,11 +57,12 @@ public: void setContext(RenderThread* thread, CanvasContext* context); - void addLayer(DeferredLayerUpdater* layer); - void removeLayer(DeferredLayerUpdater* layer); + void pushLayerUpdate(DeferredLayerUpdater* layer); + void removeLayerUpdate(DeferredLayerUpdater* layer); void setDirty(int left, int top, int right, int bottom); - int drawFrame(nsecs_t frameTimeNanos); + void setDensity(float density) { mDensity = density; } + int drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos); virtual void run(); @@ -80,13 +82,11 @@ private: *********************************************/ Rect mDirty; nsecs_t mFrameTimeNanos; + nsecs_t mRecordDurationNanos; + float mDensity; + std::vector< sp<DeferredLayerUpdater> > mLayers; int mSyncResult; - - /********************************************* - * Multi frame data - *********************************************/ - Vector<DeferredLayerUpdater*> mLayers; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 8e772f2..0901963 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -99,16 +99,20 @@ void RenderProxy::setFrameInterval(nsecs_t frameIntervalNanos) { post(task); } -CREATE_BRIDGE0(loadSystemProperties) { +CREATE_BRIDGE1(loadSystemProperties, CanvasContext* context) { bool needsRedraw = false; if (Caches::hasInstance()) { needsRedraw = Caches::getInstance().initProperties(); } + if (args->context->profiler().loadSystemProperties()) { + needsRedraw = true; + } return (void*) needsRedraw; } bool RenderProxy::loadSystemProperties() { SETUP_TASK(loadSystemProperties); + args->context = mContext; return (bool) postAndWait(task); } @@ -175,10 +179,11 @@ void RenderProxy::setOpaque(bool opaque) { post(task); } -int RenderProxy::syncAndDrawFrame(nsecs_t frameTimeNanos, - int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom) { +int RenderProxy::syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos, + float density, int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom) { mDrawFrameTask.setDirty(dirtyLeft, dirtyTop, dirtyRight, dirtyBottom); - return mDrawFrameTask.drawFrame(frameTimeNanos); + mDrawFrameTask.setDensity(density); + return mDrawFrameTask.drawFrame(frameTimeNanos, recordDurationNanos); } CREATE_BRIDGE1(destroyCanvasAndSurface, CanvasContext* context) { @@ -224,10 +229,21 @@ void RenderProxy::runWithGlContext(RenderTask* gltask) { postAndWait(task); } +CREATE_BRIDGE1(destroyLayer, Layer* layer) { + LayerRenderer::destroyLayer(args->layer); + return NULL; +} + +static void enqueueDestroyLayer(Layer* layer) { + SETUP_TASK(destroyLayer); + args->layer = layer; + RenderThread::getInstance().queue(task); +} + CREATE_BRIDGE3(createDisplayListLayer, CanvasContext* context, int width, int height) { Layer* layer = args->context->createRenderLayer(args->width, args->height); if (!layer) return 0; - return new DeferredLayerUpdater(layer); + return new DeferredLayerUpdater(layer, enqueueDestroyLayer); } DeferredLayerUpdater* RenderProxy::createDisplayListLayer(int width, int height) { @@ -237,14 +253,13 @@ DeferredLayerUpdater* RenderProxy::createDisplayListLayer(int width, int height) args->context = mContext; void* retval = postAndWait(task); DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(retval); - mDrawFrameTask.addLayer(layer); return layer; } CREATE_BRIDGE1(createTextureLayer, CanvasContext* context) { Layer* layer = args->context->createTextureLayer(); if (!layer) return 0; - return new DeferredLayerUpdater(layer); + return new DeferredLayerUpdater(layer, enqueueDestroyLayer); } DeferredLayerUpdater* RenderProxy::createTextureLayer() { @@ -252,15 +267,9 @@ DeferredLayerUpdater* RenderProxy::createTextureLayer() { args->context = mContext; void* retval = postAndWait(task); DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(retval); - mDrawFrameTask.addLayer(layer); return layer; } -CREATE_BRIDGE1(destroyLayer, Layer* layer) { - LayerRenderer::destroyLayer(args->layer); - return NULL; -} - CREATE_BRIDGE3(copyLayerInto, CanvasContext* context, DeferredLayerUpdater* layer, SkBitmap* bitmap) { bool success = args->context->copyLayerInto(args->layer, args->bitmap); @@ -275,11 +284,12 @@ bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { return (bool) postAndWait(task); } -void RenderProxy::destroyLayer(DeferredLayerUpdater* layer) { - mDrawFrameTask.removeLayer(layer); - SETUP_TASK(destroyLayer); - args->layer = layer->detachBackingLayer(); - post(task); +void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) { + mDrawFrameTask.pushLayerUpdate(layer); +} + +void RenderProxy::cancelLayerUpdate(DeferredLayerUpdater* layer) { + mDrawFrameTask.removeLayerUpdate(layer); } CREATE_BRIDGE2(flushCaches, CanvasContext* context, Caches::FlushMode flushMode) { @@ -315,6 +325,18 @@ void RenderProxy::notifyFramePending() { mRenderThread.queueAtFront(task); } +CREATE_BRIDGE2(dumpProfileInfo, CanvasContext* context, int fd) { + args->context->profiler().dumpData(args->fd); + return NULL; +} + +void RenderProxy::dumpProfileInfo(int fd) { + SETUP_TASK(dumpProfileInfo); + args->context = mContext; + args->fd = fd; + postAndWait(task); +} + void RenderProxy::post(RenderTask* task) { mRenderThread.queue(task); } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 22d4e22..944ff9c 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -69,8 +69,8 @@ public: ANDROID_API void pauseSurface(const sp<ANativeWindow>& window); ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius); ANDROID_API void setOpaque(bool opaque); - ANDROID_API int syncAndDrawFrame(nsecs_t frameTimeNanos, - int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom); + ANDROID_API int syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos, + float density, int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom); ANDROID_API void destroyCanvasAndSurface(); ANDROID_API void invokeFunctor(Functor* functor, bool waitForCompletion); @@ -80,13 +80,16 @@ public: ANDROID_API DeferredLayerUpdater* createDisplayListLayer(int width, int height); ANDROID_API DeferredLayerUpdater* createTextureLayer(); ANDROID_API bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap); - ANDROID_API void destroyLayer(DeferredLayerUpdater* layer); + ANDROID_API void pushLayerUpdate(DeferredLayerUpdater* layer); + ANDROID_API void cancelLayerUpdate(DeferredLayerUpdater* layer); ANDROID_API void flushCaches(Caches::FlushMode flushMode); ANDROID_API void fence(); ANDROID_API void notifyFramePending(); + ANDROID_API void dumpProfileInfo(int fd); + private: RenderThread& mRenderThread; CanvasContext* mContext; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java index afc2931..ef06d2c 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java @@ -18,6 +18,7 @@ package com.android.mediaframeworktest.unit; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; +import android.util.Pair; import android.util.Range; import android.util.Rational; import android.util.SizeF; @@ -790,6 +791,22 @@ public class CameraMetadataTest extends junit.framework.TestCase { } @SmallTest + public void testReadWritePair() { + // float x 2 + checkKeyMarshal("android.lens.focusRange", + new TypeReference<Pair<Float, Float>>() {{ }}, + Pair.create(1.0f / 2.0f, 1.0f / 3.0f), + toByteArray(1.0f / 2.0f, 1.0f / 3.0f)); + + // byte, int (fake from TYPE_BYTE) + // This takes advantage of the TYPE_BYTE -> int marshaler designed for enums. + checkKeyMarshal("android.flash.mode", + new TypeReference<Pair<Byte, Integer>>() {{ }}, + Pair.create((byte)123, 22), + toByteArray((byte)123, (byte)22)); + } + + @SmallTest public void testReadWriteRange() { // int32 x 2 checkKeyMarshal("android.control.aeTargetFpsRange", diff --git a/packages/Keyguard/res/values/dimens.xml b/packages/Keyguard/res/values/dimens.xml index 01d9ab3..e9cdfcd 100644 --- a/packages/Keyguard/res/values/dimens.xml +++ b/packages/Keyguard/res/values/dimens.xml @@ -162,5 +162,5 @@ <dimen name="big_font_size">120dp</dimen> <!-- The y translation to apply at the start in appear animations. --> - <dimen name="appear_y_translation_start">24dp</dimen> + <dimen name="appear_y_translation_start">32dp</dimen> </resources> diff --git a/packages/Keyguard/src/com/android/keyguard/AppearAnimationCreator.java b/packages/Keyguard/src/com/android/keyguard/AppearAnimationCreator.java new file mode 100644 index 0000000..0d30ea6 --- /dev/null +++ b/packages/Keyguard/src/com/android/keyguard/AppearAnimationCreator.java @@ -0,0 +1,29 @@ +/* + * 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.keyguard; + +import android.animation.Animator; +import android.view.animation.Interpolator; + +/** + * An interface which can create animations when starting an appear animation with + * {@link com.android.keyguard.AppearAnimationUtils} + */ +public interface AppearAnimationCreator<T> { + void createAnimation(T animatedObject, long delay, long duration, + float startTranslationY, Interpolator interpolator, Runnable finishListener); +} diff --git a/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java b/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java index ea896d5..6bb1f2c 100644 --- a/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java +++ b/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java @@ -16,84 +16,124 @@ package com.android.keyguard; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.TimeInterpolator; import android.content.Context; import android.view.View; -import android.view.ViewPropertyAnimator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; /** * A class to make nice appear transitions for views in a tabular layout. */ -public class AppearAnimationUtils { +public class AppearAnimationUtils implements AppearAnimationCreator<View> { public static final long APPEAR_DURATION = 220; private final Interpolator mLinearOutSlowIn; private final float mStartTranslation; + private final AppearAnimationProperties mProperties = new AppearAnimationProperties(); + private final float mDelayScale; public AppearAnimationUtils(Context ctx) { + this(ctx, 1.0f, 1.0f); + } + + public AppearAnimationUtils(Context ctx, float delayScaleFactor, + float translationScaleFactor) { mLinearOutSlowIn = AnimationUtils.loadInterpolator( ctx, android.R.interpolator.linear_out_slow_in); - mStartTranslation = - ctx.getResources().getDimensionPixelOffset(R.dimen.appear_y_translation_start); + mStartTranslation = ctx.getResources().getDimensionPixelOffset( + R.dimen.appear_y_translation_start) * translationScaleFactor; + mDelayScale = delayScaleFactor; } - public void startAppearAnimation(View[][] views, final Runnable finishListener) { - long maxDelay = 0; - ViewPropertyAnimator maxDelayAnimator = null; - for (int row = 0; row < views.length; row++) { - View[] columns = views[row]; + public void startAppearAnimation(View[][] objects, final Runnable finishListener) { + startAppearAnimation(objects, finishListener, this); + } + + public <T> void startAppearAnimation(T[][] objects, final Runnable finishListener, + AppearAnimationCreator<T> creator) { + AppearAnimationProperties properties = getDelays(objects); + startAnimations(properties, objects, finishListener, creator); + } + + private <T> void startAnimations(AppearAnimationProperties properties, T[][] objects, + final Runnable finishListener, AppearAnimationCreator creator) {; + if (properties.maxDelayRowIndex == -1 || properties.maxDelayColIndex == -1) { + finishListener.run(); + return; + } + for (int row = 0; row < properties.delays.length; row++) { + long[] columns = properties.delays[row]; for (int col = 0; col < columns.length; col++) { - long delay = calculateDelay(row, col); - ViewPropertyAnimator animator = startAppearAnimation(columns[col], delay); - if (animator != null && delay > maxDelay) { - maxDelay = delay; - maxDelayAnimator = animator; + long delay = columns[col]; + Runnable endRunnable = null; + if (properties.maxDelayRowIndex == row && properties.maxDelayColIndex == col) { + endRunnable = finishListener; } + creator.createAnimation(objects[row][col], delay, APPEAR_DURATION, + mStartTranslation, mLinearOutSlowIn, endRunnable); } } - if (maxDelayAnimator != null) { - maxDelayAnimator.setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - finishListener.run(); - } - }); - } else { - finishListener.run(); - } + } - private ViewPropertyAnimator startAppearAnimation(View view, long delay) { - if (view == null) return null; - view.setAlpha(0f); - view.setTranslationY(mStartTranslation); - view.animate() - .alpha(1f) - .translationY(0) - .setInterpolator(mLinearOutSlowIn) - .setDuration(APPEAR_DURATION) - .setStartDelay(delay) - .setListener(null); - if (view.hasOverlappingRendering()) { - view.animate().withLayer(); + private <T> AppearAnimationProperties getDelays(T[][] items) { + long maxDelay = 0; + mProperties.maxDelayColIndex = -1; + mProperties.maxDelayRowIndex = -1; + mProperties.delays = new long[items.length][]; + for (int row = 0; row < items.length; row++) { + T[] columns = items[row]; + mProperties.delays[row] = new long[columns.length]; + for (int col = 0; col < columns.length; col++) { + long delay = calculateDelay(row, col); + mProperties.delays[row][col] = delay; + if (items[row][col] != null && delay > maxDelay) { + maxDelay = delay; + mProperties.maxDelayColIndex = col; + mProperties.maxDelayRowIndex = row; + } + } } - return view.animate(); + return mProperties; } private long calculateDelay(int row, int col) { - return (long) (row * 40 + col * (Math.pow(row, 0.4) + 0.4) * 20); + return (long) ((row * 40 + col * (Math.pow(row, 0.4) + 0.4) * 20) * mDelayScale); } - public TimeInterpolator getInterpolator() { + public Interpolator getInterpolator() { return mLinearOutSlowIn; } public float getStartTranslation() { return mStartTranslation; } + + @Override + public void createAnimation(View view, long delay, long duration, float startTranslationY, + Interpolator interpolator, Runnable endRunnable) { + if (view != null) { + view.setAlpha(0f); + view.setTranslationY(startTranslationY); + view.animate() + .alpha(1f) + .translationY(0) + .setInterpolator(interpolator) + .setDuration(duration) + .setStartDelay(delay); + if (view.hasOverlappingRendering()) { + view.animate().withLayer(); + } + if (endRunnable != null) { + view.animate().withEndAction(endRunnable); + } + } + } + + public class AppearAnimationProperties { + public long[][] delays; + public int maxDelayRowIndex; + public int maxDelayColIndex; + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java index 5853ff9..e6de72f 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java @@ -21,6 +21,9 @@ import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -28,10 +31,13 @@ import android.os.Bundle; import android.os.CountDownTimer; import android.os.SystemClock; import android.os.UserHandle; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Interpolator; import android.widget.Button; import android.widget.LinearLayout; @@ -41,7 +47,8 @@ import com.android.internal.widget.LockPatternView; import java.io.IOException; import java.util.List; -public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView { +public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView, + AppearAnimationCreator<LockPatternView.CellState> { private static final String TAG = "SecurityPatternView"; private static final boolean DEBUG = KeyguardConstants.DEBUG; @@ -59,6 +66,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final AppearAnimationUtils mAppearAnimationUtils; private CountDownTimer mCountdownTimer = null; private LockPatternUtils mLockPatternUtils; @@ -87,6 +95,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit private SecurityMessageDisplay mSecurityMessageDisplay; private View mEcaView; private Drawable mBouncerFrame; + private ViewGroup mKeyguardBouncerFrame; + private KeyguardMessageArea mHelpMessage; enum FooterMode { Normal, @@ -101,6 +111,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit public KeyguardPatternView(Context context, AttributeSet attrs) { super(context, attrs); mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); + mAppearAnimationUtils = new AppearAnimationUtils(context, 1.5f /* delayScale */, + 2.0f /* transitionScale */); } public void setKeyguardCallback(KeyguardSecurityCallback callback) { @@ -148,6 +160,9 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit if (bouncerFrameView != null) { mBouncerFrame = bouncerFrameView.getBackground(); } + + mKeyguardBouncerFrame = (ViewGroup) findViewById(R.id.keyguard_bouncer_frame); + mHelpMessage = (KeyguardMessageArea) findViewById(R.id.keyguard_message_area); } private void updateFooter(FooterMode mode) { @@ -403,8 +418,69 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit @Override public void startAppearAnimation() { - // TODO: Fancy animation. - setAlpha(0); - animate().alpha(1).withLayer().setDuration(200); + enableClipping(false); + mAppearAnimationUtils.startAppearAnimation( + mLockPatternView.getCellStates(), + new Runnable() { + @Override + public void run() { + enableClipping(true); + } + }, + this); + if (!TextUtils.isEmpty(mHelpMessage.getText())) { + mAppearAnimationUtils.createAnimation(mHelpMessage, 0, + AppearAnimationUtils.APPEAR_DURATION, + mAppearAnimationUtils.getStartTranslation(), + mAppearAnimationUtils.getInterpolator(), + null /* finishRunnable */); + } + } + + private void enableClipping(boolean enable) { + setClipChildren(enable); + mKeyguardBouncerFrame.setClipToPadding(enable); + mKeyguardBouncerFrame.setClipChildren(enable); + } + + @Override + public void createAnimation(final LockPatternView.CellState animatedCell, long delay, + long duration, float startTranslationY, Interpolator interpolator, + final Runnable finishListener) { + animatedCell.scale = 0.0f; + animatedCell.translateY = startTranslationY; + ValueAnimator animator = ValueAnimator.ofFloat(startTranslationY, 0.0f); + animator.setInterpolator(interpolator); + animator.setDuration(duration); + animator.setStartDelay(delay); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float animatedFraction = animation.getAnimatedFraction(); + animatedCell.scale = animatedFraction; + animatedCell.translateY = (float) animation.getAnimatedValue(); + mLockPatternView.invalidate(); + } + }); + if (finishListener != null) { + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + finishListener.run(); + } + }); + + // Also animate the Emergency call + mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, startTranslationY, + interpolator, null); + + // And the forgot pattern button + if (mForgotPatternButton.getVisibility() == View.VISIBLE) { + mAppearAnimationUtils.createAnimation(mForgotPatternButton, delay, duration, + startTranslationY, interpolator, null); + } + } + animator.start(); + mLockPatternView.invalidate(); } } diff --git a/packages/SystemUI/res/drawable/ic_qs_close.xml b/packages/SystemUI/res/drawable/ic_qs_back.xml index dd43e6c..52039f5 100644 --- a/packages/SystemUI/res/drawable/ic_qs_close.xml +++ b/packages/SystemUI/res/drawable/ic_qs_back.xml @@ -24,5 +24,5 @@ Copyright (C) 2014 The Android Open Source Project <path android:fill="#FFFFFFFF" - android:pathData="M19.0,6.4l-1.3999996,-1.4000001 -5.6000004,5.6000004 -5.6,-5.6000004 -1.4000001,1.4000001 5.6000004,5.6 -5.6000004,5.6000004 1.4000001,1.3999996 5.6,-5.6000004 5.6000004,5.6000004 1.3999996,-1.3999996 -5.6000004,-5.6000004z"/> + android:pathData="M20.0,11.0L7.8,11.0l5.6,-5.6L12.0,4.0l-8.0,8.0l8.0,8.0l1.4,-1.4L7.8,13.0L20.0,13.0L20.0,11.0z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_zen_off.xml b/packages/SystemUI/res/drawable/ic_qs_zen_off.xml index 73886ec..88a2c3a 100644 --- a/packages/SystemUI/res/drawable/ic_qs_zen_off.xml +++ b/packages/SystemUI/res/drawable/ic_qs_zen_off.xml @@ -23,8 +23,6 @@ Copyright (C) 2014 The Android Open Source Project android:viewportHeight="24.0"/> <path - android:fill="#00000000" - android:stroke="#CCCCCC" - android:strokeWidth="1.0" - android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" /> + android:fill="#4DFFFFFF" + android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_zen_on.xml b/packages/SystemUI/res/drawable/ic_qs_zen_on.xml index 8dff318..0e933cf 100644 --- a/packages/SystemUI/res/drawable/ic_qs_zen_on.xml +++ b/packages/SystemUI/res/drawable/ic_qs_zen_on.xml @@ -24,5 +24,5 @@ Copyright (C) 2014 The Android Open Source Project <path android:fill="#FFFFFFFF" - android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" /> + android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml index ea5ab70..51be1a8 100644 --- a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml +++ b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml @@ -23,8 +23,6 @@ Copyright (C) 2014 The Android Open Source Project android:viewportHeight="24.0"/> <path - android:fill="#00000000" - android:stroke="#CCCCCC" - android:strokeWidth="1.0" - android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" /> + android:fill="#4DFFFFFF" + android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_on.xml b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml index 44024f3..c8c217d 100644 --- a/packages/SystemUI/res/drawable/ic_vol_zen_on.xml +++ b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml @@ -24,5 +24,5 @@ Copyright (C) 2014 The Android Open Source Project <path android:fill="#FFFFFFFF" - android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" /> + android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/> </vector> diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml index afab88f..8b104d1 100644 --- a/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml +++ b/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml @@ -24,5 +24,5 @@ Copyright (C) 2014 The Android Open Source Project <path android:fill="#FFFFFFFF" - android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" /> + android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/> </vector> diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml index e73b431..e1c460c 100644 --- a/packages/SystemUI/res/layout/qs_detail.xml +++ b/packages/SystemUI/res/layout/qs_detail.xml @@ -26,7 +26,7 @@ android:layout_alignParentStart="true" android:contentDescription="@string/accessibility_quick_settings_close" android:padding="@dimen/qs_panel_padding" - android:src="@drawable/ic_qs_close" /> + android:src="@drawable/ic_qs_back" /> <TextView android:id="@android:id/title" diff --git a/packages/SystemUI/res/layout/recents_empty.xml b/packages/SystemUI/res/layout/recents_empty.xml index 6268628..ac6450b 100644 --- a/packages/SystemUI/res/layout/recents_empty.xml +++ b/packages/SystemUI/res/layout/recents_empty.xml @@ -19,8 +19,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" - android:textSize="40sp" + android:textSize="20sp" android:textColor="#ffffffff" + android:textStyle="italic" android:text="@string/recents_empty_message" - android:fontFamily="sans-serif-thin" + android:fontFamily="sans-serif-light" android:visibility="gone" />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml index 7de421c..85d2f16 100644 --- a/packages/SystemUI/res/layout/recents_task_view.xml +++ b/packages/SystemUI/res/layout/recents_task_view.xml @@ -25,36 +25,37 @@ <com.android.systemui.recents.views.TaskBarView android:id="@+id/task_view_bar" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="56dp" android:layout_gravity="top|center_horizontal" android:background="@color/recents_task_bar_default_background_color"> <ImageView android:id="@+id/application_icon" android:layout_width="@dimen/recents_task_view_application_icon_size" android:layout_height="@dimen/recents_task_view_application_icon_size" - android:layout_gravity="center_vertical|start" - android:padding="8dp" /> + android:layout_marginStart="16dp" + android:layout_gravity="center_vertical|start" /> <TextView android:id="@+id/activity_description" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center_vertical|left" - android:layout_marginStart="@dimen/recents_task_view_application_icon_size" - android:layout_marginEnd="@dimen/recents_task_view_application_icon_size" - android:textSize="22sp" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="64dp" + android:layout_marginEnd="64dp" + android:textSize="16sp" android:textColor="#ffffffff" android:text="@string/recents_empty_message" - android:fontFamily="sans-serif-light" + android:fontFamily="sans-serif-medium" android:singleLine="true" android:maxLines="2" android:ellipsize="marquee" android:fadingEdge="horizontal" /> <ImageView android:id="@+id/dismiss_task" - android:layout_width="@dimen/recents_task_view_application_icon_size" - android:layout_height="@dimen/recents_task_view_application_icon_size" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginEnd="4dp" android:layout_gravity="center_vertical|end" - android:padding="23dp" + android:padding="18dp" android:src="@drawable/recents_dismiss_light" /> </com.android.systemui.recents.views.TaskBarView> </com.android.systemui.recents.views.TaskView> diff --git a/packages/SystemUI/res/layout/zen_mode_condition.xml b/packages/SystemUI/res/layout/zen_mode_condition.xml index 8b34400..6d63bb0 100644 --- a/packages/SystemUI/res/layout/zen_mode_condition.xml +++ b/packages/SystemUI/res/layout/zen_mode_condition.xml @@ -16,11 +16,14 @@ --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/zen_mode_condition_detail_button_padding" + android:layout_marginRight="@dimen/zen_mode_condition_detail_button_padding" > <RadioButton android:id="@android:id/checkbox" - android:layout_width="32dp" + android:layout_width="40dp" + android:layout_marginStart="2dp" android:layout_height="@dimen/zen_mode_condition_height" android:layout_alignParentStart="true" android:gravity="center" /> diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml index 0936cc2..0a8f852 100644 --- a/packages/SystemUI/res/layout/zen_mode_panel.xml +++ b/packages/SystemUI/res/layout/zen_mode_panel.xml @@ -21,9 +21,7 @@ android:layout_height="wrap_content" android:background="@color/system_primary_color" android:orientation="vertical" - android:paddingTop="@dimen/qs_panel_padding" - android:paddingLeft="@dimen/qs_panel_padding" - android:paddingRight="@dimen/qs_panel_padding" > + android:paddingTop="@dimen/qs_panel_padding" > <TextView android:id="@android:id/title" @@ -31,8 +29,10 @@ android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_marginBottom="8dp" + android:layout_marginStart="@dimen/qs_panel_padding" + android:layout_marginEnd="@dimen/qs_panel_padding" android:text="@string/zen_mode_title" - android:textAppearance="@style/TextAppearance.QS.DetailHeader" /> + android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary" /> <LinearLayout android:id="@android:id/content" @@ -46,6 +46,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" + android:layout_marginEnd="4dp" android:layout_gravity="end" android:text="@string/quick_settings_more_settings" android:textAppearance="@style/TextAppearance.QS.DetailButton" /> diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 39ce0a2..42c4392 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -38,6 +38,9 @@ <dimen name="status_bar_recents_app_icon_left_margin">8dp</dimen> <dimen name="status_bar_recents_app_icon_top_margin">8dp</dimen> + <!-- The side padding for the task stack as a percentage of the width. --> + <item name="recents_stack_width_padding_percentage" format="float" type="dimen">0.2229</item> + <!-- Width of the zen mode interstitial dialog. --> <dimen name="zen_mode_dialog_width">384dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml index b510fef..326f602 100644 --- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml @@ -19,6 +19,9 @@ <!-- Recent Applications parameters --> <dimen name="status_bar_recents_app_label_width">190dip</dimen> + <!-- The side padding for the task stack as a percentage of the width. --> + <item name="recents_stack_width_padding_percentage" format="float" type="dimen">0.25</item> + <fraction name="keyguard_clock_y_fraction_max">37%</fraction> <fraction name="keyguard_clock_y_fraction_min">14%</fraction> </resources> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 5750faa..313e2e8 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -46,6 +46,9 @@ <!-- On tablets this is just the close_handle_height --> <dimen name="peek_height">@dimen/close_handle_height</dimen> + <!-- The side padding for the task stack as a percentage of the width. --> + <item name="recents_stack_width_padding_percentage" format="float" type="dimen">0.075</item> + <!-- Width of the zen mode interstitial dialog. --> <dimen name="zen_mode_dialog_width">384dp</dimen> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 4e37dbb..757d4ad 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -67,11 +67,13 @@ <!-- The recents task bar light text color to be drawn on top of dark backgrounds. --> <color name="recents_task_bar_light_text_color">#ffeeeeee</color> <!-- The recents task bar dark text color to be drawn on top of light backgrounds. --> - <color name="recents_task_bar_dark_text_color">#ff222222</color> + <color name="recents_task_bar_dark_text_color">#ff333333</color> <!-- The recents task bar light dismiss icon color to be drawn on top of dark backgrounds. --> <color name="recents_task_bar_light_dismiss_color">#ffeeeeee</color> <!-- The recents task bar dark dismiss icon color to be drawn on top of light backgrounds. --> <color name="recents_task_bar_dark_dismiss_color">#ff333333</color> + <!-- The recents task bar highlight color. --> + <color name="recents_task_bar_highlight_color">#28ffffff</color> <color name="keyguard_affordance">#ffffffff</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 6405ae6..0184df2 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -113,9 +113,9 @@ <!-- The min animation duration for animating views that are newly visible. --> <integer name="recents_filter_animate_new_views_min_duration">125</integer> <!-- The min animation duration for animating the task bar in. --> - <integer name="recents_animate_task_bar_enter_duration">225</integer> + <integer name="recents_animate_task_bar_enter_duration">300</integer> <!-- The min animation duration for animating the task bar out. --> - <integer name="recents_animate_task_bar_exit_duration">175</integer> + <integer name="recents_animate_task_bar_exit_duration">150</integer> <!-- The animation duration for animating in the info pane. --> <integer name="recents_animate_task_view_info_pane_duration">150</integer> <!-- The animation duration for animating the removal of a task view. --> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 610b376..3646ade 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -212,7 +212,7 @@ <dimen name="glowpadview_inner_radius">15dip</dimen> <!-- The size of the application icon in the recents task view. --> - <dimen name="recents_task_view_application_icon_size">60dp</dimen> + <dimen name="recents_task_view_application_icon_size">32dp</dimen> <!-- The size of the activity icon in the recents task view. --> <dimen name="recents_task_view_activity_icon_size">60dp</dimen> @@ -221,20 +221,32 @@ <dimen name="recents_task_view_rounded_corners_radius">2dp</dimen> <!-- The min translation in the Z index for the last task. --> - <dimen name="recents_task_view_z_min">3dp</dimen> + <dimen name="recents_task_view_z_min">5dp</dimen> <!-- The translation in the Z index for each task above the last task. --> - <dimen name="recents_task_view_z_increment">5dp</dimen> + <dimen name="recents_task_view_z_increment">10dp</dimen> + + <!-- The amount of bottom inset in the shadow outline. --> + <dimen name="recents_task_view_shadow_outline_bottom_inset">5dp</dimen> <!-- The amount to translate when animating the removal of a task. --> <dimen name="recents_task_view_remove_anim_translation_x">100dp</dimen> + <!-- The amount of highlight to make on each task view. --> + <dimen name="recents_task_view_highlight">1dp</dimen> + <!-- The amount of space a user has to scroll to dismiss any info panes. --> <dimen name="recents_task_stack_scroll_dismiss_info_pane_distance">50dp</dimen> <!-- The height of the search bar space. --> <dimen name="recents_search_bar_space_height">64dp</dimen> + <!-- The side padding for the task stack as a percentage of the width. --> + <item name="recents_stack_width_padding_percentage" format="float" type="dimen">0.04444</item> + + <!-- The top offset for the task stack. --> + <dimen name="recents_stack_top_padding">16dp</dimen> + <!-- Used to calculate the translation animation duration, the expected amount of movement in dps over one second of time. --> <dimen name="recents_animation_movement_in_dps_per_second">800dp</dimen> @@ -302,4 +314,7 @@ <!-- Volume panel dialog width --> <dimen name="volume_panel_width">300dp</dimen> + + <!-- Volume panel z depth --> + <dimen name="volume_panel_z">3dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ef3956e..59f1efd 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -522,7 +522,7 @@ <string name="quick_settings_notifications_label">Notifications</string> <!-- Recents: The empty recents string. [CHAR LIMIT=NONE] --> - <string name="recents_empty_message">RECENTS</string> + <string name="recents_empty_message">No recent apps</string> <!-- Recents: The info panel app info button string. [CHAR LIMIT=NONE] --> <string name="recents_app_info_button_label">Application Info</string> <!-- Recents: Temporary string for the button in the recents search bar. [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 626fc0d..c8cf05d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -47,8 +47,10 @@ public class QSPanel extends ViewGroup { private int mCellHeight; private int mLargeCellWidth; private int mLargeCellHeight; + private boolean mExpanded; private TileRecord mDetailRecord; + private Callback mCallback; public QSPanel(Context context) { this(context, null); @@ -59,6 +61,7 @@ public class QSPanel extends ViewGroup { mContext = context; mDetail = new FrameLayout(mContext); + mDetail.setBackgroundColor(mContext.getResources().getColor(R.color.system_primary_color)); mDetail.setVisibility(GONE); mDetail.setClickable(true); addView(mDetail); @@ -66,6 +69,10 @@ public class QSPanel extends ViewGroup { updateResources(); } + public void setCallback(Callback callback) { + mCallback = callback; + } + public void updateResources() { final Resources res = mContext.getResources(); final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); @@ -84,12 +91,14 @@ public class QSPanel extends ViewGroup { } public void setExpanded(boolean expanded) { - if (!expanded) { + if (mExpanded == expanded) return; + mExpanded = expanded; + if (!mExpanded) { showDetail(false /*show*/, mDetailRecord); } for (TileRecord r : mRecords) { - r.tile.setListening(expanded); - if (expanded) { + r.tile.setListening(mExpanded); + if (mExpanded) { r.tile.refreshState(); } } @@ -156,6 +165,7 @@ public class QSPanel extends ViewGroup { if (mDetailRecord == null) return; listener = mTeardownDetailWhenDone; } + fireShowingDetail(show); int x = r.tileView.getLeft() + r.tileView.getWidth() / 2; int y = r.tileView.getTop() + r.tileView.getHeight() / 2; mClipper.animateCircularClip(x, y, show, listener); @@ -195,7 +205,7 @@ public class QSPanel extends ViewGroup { mDetail.measure(exactly(width), unspecified()); if (mDetail.getVisibility() == VISIBLE && mDetail.getChildCount() > 0) { final int dmh = mDetail.getMeasuredHeight(); - if (dmh > 0) h = dmh; + if (dmh > 0) h = Math.max(h, dmh); } setMeasuredDimension(width, h); } @@ -222,7 +232,8 @@ public class QSPanel extends ViewGroup { left + record.tileView.getMeasuredWidth(), top + record.tileView.getMeasuredHeight()); } - mDetail.layout(0, 0, mDetail.getMeasuredWidth(), mDetail.getMeasuredHeight()); + final int dh = Math.max(mDetail.getMeasuredHeight(), getMeasuredHeight()); + mDetail.layout(0, 0, mDetail.getMeasuredWidth(), dh); } private int getRowTop(int row) { @@ -239,6 +250,12 @@ public class QSPanel extends ViewGroup { return cols; } + private void fireShowingDetail(boolean showingDetail) { + if (mCallback != null) { + mCallback.onShowingDetail(showingDetail); + } + } + private class H extends Handler { private static final int SHOW_DETAIL = 1; private static final int SET_TILE_VISIBILITY = 2; @@ -265,4 +282,8 @@ public class QSPanel extends ViewGroup { mDetailRecord = null; }; }; + + public interface Callback { + void onShowingDetail(boolean showingDetail); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java index ffd64d4..ca9bb94 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java @@ -35,6 +35,7 @@ import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.util.DisplayMetrics; import android.view.Display; @@ -49,7 +50,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** A proxy implementation for the recents component */ -public class AlternateRecentsComponent { +public class AlternateRecentsComponent implements ActivityOptions.OnAnimationStartedListener { /** A handler for messages from the recents implementation */ class RecentsMessageHandler extends Handler { @@ -62,12 +63,15 @@ public class AlternateRecentsComponent { Bundle replyData = msg.getData().getParcelable(KEY_CONFIGURATION_DATA); mSingleCountFirstTaskRect = replyData.getParcelable(KEY_SINGLE_TASK_STACK_RECT); mSingleCountFirstTaskRect.offset(0, (int) statusBarHeight); + mTwoCountFirstTaskRect = replyData.getParcelable(KEY_TWO_TASK_STACK_RECT); + mTwoCountFirstTaskRect.offset(0, (int) statusBarHeight); mMultipleCountFirstTaskRect = replyData.getParcelable(KEY_MULTIPLE_TASK_STACK_RECT); mMultipleCountFirstTaskRect.offset(0, (int) statusBarHeight); if (Console.Enabled) { Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|RecentsMessageHandler|handleMessage]", "singleTaskRect: " + mSingleCountFirstTaskRect + + " twoTaskRect: " + mTwoCountFirstTaskRect + " multipleTaskRect: " + mMultipleCountFirstTaskRect); } @@ -123,6 +127,7 @@ public class AlternateRecentsComponent { final public static int MSG_SHOW_RECENTS = 4; final public static int MSG_HIDE_RECENTS = 5; final public static int MSG_TOGGLE_RECENTS = 6; + final public static int MSG_START_ENTER_ANIMATION = 7; final public static String EXTRA_ANIMATING_WITH_THUMBNAIL = "recents.animatingWithThumbnail"; final public static String EXTRA_FROM_ALT_TAB = "recents.triggeredFromAltTab"; @@ -130,6 +135,7 @@ public class AlternateRecentsComponent { final public static String KEY_WINDOW_RECT = "recents.windowRect"; final public static String KEY_SYSTEM_INSETS = "recents.systemInsets"; final public static String KEY_SINGLE_TASK_STACK_RECT = "recents.singleCountTaskRect"; + final public static String KEY_TWO_TASK_STACK_RECT = "recents.twoCountTaskRect"; final public static String KEY_MULTIPLE_TASK_STACK_RECT = "recents.multipleCountTaskRect"; @@ -155,6 +161,7 @@ public class AlternateRecentsComponent { boolean mTriggeredFromAltTab; Rect mSingleCountFirstTaskRect = new Rect(); + Rect mTwoCountFirstTaskRect = new Rect(); Rect mMultipleCountFirstTaskRect = new Rect(); long mLastToggleTime; @@ -261,8 +268,10 @@ public class AlternateRecentsComponent { /** Returns whether we have valid task rects to animate to. */ boolean hasValidTaskRects() { return mSingleCountFirstTaskRect != null && mSingleCountFirstTaskRect.width() > 0 && - mSingleCountFirstTaskRect.height() > 0 && mMultipleCountFirstTaskRect != null && - mMultipleCountFirstTaskRect.width() > 0 && mMultipleCountFirstTaskRect.height() > 0; + mSingleCountFirstTaskRect.height() > 0 && mTwoCountFirstTaskRect != null && + mTwoCountFirstTaskRect.width() > 0 && mTwoCountFirstTaskRect.height() > 0 && + mMultipleCountFirstTaskRect != null && mMultipleCountFirstTaskRect.width() > 0 && + mMultipleCountFirstTaskRect.height() > 0; } /** Updates each of the task animation rects. */ @@ -303,8 +312,8 @@ public class AlternateRecentsComponent { return null; } - /** Returns whether there is are multiple recents tasks */ - boolean hasMultipleRecentsTask(List<ActivityManager.RecentTaskInfo> tasks) { + /** Returns the proper rect to use for the animation, given the number of tasks. */ + Rect getAnimationTaskRect(List<ActivityManager.RecentTaskInfo> tasks) { // NOTE: Currently there's no method to get the number of non-home tasks, so we have to // compute this ourselves SystemServicesProxy ssp = mSystemServicesProxy; @@ -318,7 +327,13 @@ public class AlternateRecentsComponent { continue; } } - return (tasks.size() > 1); + if (tasks.size() <= 1) { + return mSingleCountFirstTaskRect; + } else if (tasks.size() <= 2) { + return mTwoCountFirstTaskRect; + } else { + return mMultipleCountFirstTaskRect; + } } /** Converts from the device rotation to the degree */ @@ -392,7 +407,7 @@ public class AlternateRecentsComponent { } return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView, thumbnail, - taskRect.left, taskRect.top, null); + taskRect.left, taskRect.top, this); } /** Returns whether the recents is currently running */ @@ -472,9 +487,8 @@ public class AlternateRecentsComponent { // which can differ depending on the number of items in the list. SystemServicesProxy ssp = mSystemServicesProxy; List<ActivityManager.RecentTaskInfo> recentTasks = - ssp.getRecentTasks(2, UserHandle.CURRENT.getIdentifier()); - Rect taskRect = hasMultipleRecentsTask(recentTasks) ? mMultipleCountFirstTaskRect : - mSingleCountFirstTaskRect; + ssp.getRecentTasks(3, UserHandle.CURRENT.getIdentifier()); + Rect taskRect = getAnimationTaskRect(recentTasks); boolean useThumbnailTransition = !isTopTaskHome && hasValidTaskRects(); @@ -517,4 +531,18 @@ public class AlternateRecentsComponent { mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); } } + + + /**** OnAnimationStartedListener Implementation ****/ + + @Override + public void onAnimationStarted() { + // Notify recents to start the enter animation + try { + Message msg = Message.obtain(null, MSG_START_ENTER_ANIMATION, 0, 0); + mService.send(msg); + } catch (RemoteException re) { + re.printStackTrace(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index 57957a8..76e88a5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -36,7 +36,7 @@ public class Constants { // Enables the search bar layout public static final boolean EnableSearchLayout = true; // Enables the dynamic shadows behind each task - public static final boolean EnableShadows = false; + public static final boolean EnableShadows = true; // This disables the bitmap and icon caches public static final boolean DisableBackgroundCache = false; // For debugging, this enables us to create mock recents tasks @@ -101,8 +101,6 @@ public class Constants { public static final int TaskStackOverscrollRange = 150; public static final int FilterStartDelay = 25; - // The padding will be applied to the smallest dimension, and then applied to all sides - public static final float StackPaddingPct = 0.1f; // The overlap height relative to the task height public static final float StackOverlapPct = 0.65f; // The height of the peek space relative to the stack height @@ -113,4 +111,4 @@ public class Constants { public static final int StackPeekNumCards = 3; } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index befe8b4..df387c1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -98,6 +98,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // If there are no filtered stacks, dismiss recents and launch the first task dismissRecentsIfVisible(); } + } else if (action.equals(RecentsService.ACTION_START_ENTER_ANIMATION)) { + // Try and start the enter animation + mRecentsView.startOnEnterAnimation(); } } }; @@ -345,6 +348,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView IntentFilter filter = new IntentFilter(); filter.addAction(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY); filter.addAction(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY); + filter.addAction(RecentsService.ACTION_START_ENTER_ANIMATION); registerReceiver(mServiceBroadcastReceiver, filter); // Register the broadcast receiver to handle messages when the screen is turned off diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index 03f7e36..6391685 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -46,26 +46,37 @@ public class RecentsConfiguration { public float animationPxMovementPerSecond; - public Interpolator defaultBezierInterpolator; + public Interpolator fastOutSlowInInterpolator; + public Interpolator fastOutLinearInInterpolator; + public Interpolator linearOutSlowInInterpolator; public int filteringCurrentViewsMinAnimDuration; public int filteringNewViewsMinAnimDuration; - public int taskBarEnterAnimDuration; - public int taskBarExitAnimDuration; + public int taskStackScrollDismissInfoPaneDistance; public int taskStackMaxDim; + public float taskStackWidthPaddingPct; + public int taskStackTopPaddingPx; + public int taskViewInfoPaneAnimDuration; public int taskViewRemoveAnimDuration; public int taskViewRemoveAnimTranslationXPx; public int taskViewTranslationZMinPx; public int taskViewTranslationZIncrementPx; + public int taskViewShadowOutlineBottomInsetPx; public int taskViewRoundedCornerRadiusPx; + public int taskViewHighlightPx; + public int searchBarSpaceHeightPx; public int taskBarViewDefaultBackgroundColor; public int taskBarViewDefaultTextColor; public int taskBarViewLightTextColor; public int taskBarViewDarkTextColor; + public int taskBarViewHighlightColor; + + public int taskBarEnterAnimDuration; + public int taskBarExitAnimDuration; public boolean launchedFromAltTab; public boolean launchedWithThumbnailAnimation; @@ -112,13 +123,16 @@ public class RecentsConfiguration { res.getInteger(R.integer.recents_filter_animate_current_views_min_duration); filteringNewViewsMinAnimDuration = res.getInteger(R.integer.recents_filter_animate_new_views_min_duration); - taskBarEnterAnimDuration = - res.getInteger(R.integer.recents_animate_task_bar_enter_duration); - taskBarExitAnimDuration = - res.getInteger(R.integer.recents_animate_task_bar_exit_duration); + taskStackScrollDismissInfoPaneDistance = res.getDimensionPixelSize( R.dimen.recents_task_stack_scroll_dismiss_info_pane_distance); taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim); + + TypedValue widthPaddingPctValue = new TypedValue(); + res.getValue(R.dimen.recents_stack_width_padding_percentage, widthPaddingPctValue, true); + taskStackWidthPaddingPct = widthPaddingPctValue.getFloat(); + taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.recents_stack_top_padding); + taskViewInfoPaneAnimDuration = res.getInteger(R.integer.recents_animate_task_view_info_pane_duration); taskViewRemoveAnimDuration = @@ -127,9 +141,13 @@ public class RecentsConfiguration { res.getDimensionPixelSize(R.dimen.recents_task_view_remove_anim_translation_x); taskViewRoundedCornerRadiusPx = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius); + taskViewHighlightPx = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight); taskViewTranslationZMinPx = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min); taskViewTranslationZIncrementPx = res.getDimensionPixelSize(R.dimen.recents_task_view_z_increment); + taskViewShadowOutlineBottomInsetPx = + res.getDimensionPixelSize(R.dimen.recents_task_view_shadow_outline_bottom_inset); + searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height); taskBarViewDefaultBackgroundColor = @@ -140,9 +158,20 @@ public class RecentsConfiguration { res.getColor(R.color.recents_task_bar_light_text_color); taskBarViewDarkTextColor = res.getColor(R.color.recents_task_bar_dark_text_color); + taskBarViewHighlightColor = + res.getColor(R.color.recents_task_bar_highlight_color); - defaultBezierInterpolator = AnimationUtils.loadInterpolator(context, + taskBarEnterAnimDuration = + res.getInteger(R.integer.recents_animate_task_bar_enter_duration); + taskBarExitAnimDuration = + res.getInteger(R.integer.recents_animate_task_bar_exit_duration); + + fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_slow_in); + fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.fast_out_linear_in); + linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.linear_out_slow_in); // Check if the developer options are enabled ContentResolver cr = context.getContentResolver(); @@ -167,6 +196,13 @@ public class RecentsConfiguration { appWidgetId).apply(); } + /** Called when the configuration has changed, and we want to reset any configuration specific + * members. */ + public void updateOnConfigurationChange() { + launchedFromAltTab = false; + launchedWithThumbnailAnimation = false; + } + /** Returns whether the search bar app widget exists */ public boolean hasSearchBarAppWidget() { return searchBarAppWidgetId >= 0; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java index 4bdbb20..0c2c11d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java @@ -55,7 +55,8 @@ class SystemUIMessageHandler extends Handler { if (msg.what == AlternateRecentsComponent.MSG_UPDATE_FOR_CONFIGURATION) { RecentsTaskLoader.initialize(context); - RecentsConfiguration.reinitialize(context); + RecentsConfiguration config = RecentsConfiguration.reinitialize(context); + config.updateOnConfigurationChange(); try { Bundle data = msg.getData(); @@ -73,7 +74,6 @@ class SystemUIMessageHandler extends Handler { // Get the task stack and search bar bounds Rect taskStackBounds = new Rect(); - RecentsConfiguration config = RecentsConfiguration.getInstance(); config.getTaskStackBounds(windowRect.width(), windowRect.height(), taskStackBounds); // Calculate the target task rect for when there is one task. @@ -83,20 +83,29 @@ class SystemUIMessageHandler extends Handler { stack.addTask(new Task()); tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() - systemInsets.top - systemInsets.bottom, 0, 0); - tsv.boundScroll(); + tsv.setStackScrollToInitialState(); transform = tsv.getStackTransform(0, tsv.getStackScroll()); transform.rect.offset(taskStackBounds.left, taskStackBounds.top); replyData.putParcelable(AlternateRecentsComponent.KEY_SINGLE_TASK_STACK_RECT, new Rect(transform.rect)); - // Also calculate the target task rect when there are multiple tasks. + // Also calculate the target task rect when there are two tasks. stack.addTask(new Task()); tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() - systemInsets.top - systemInsets.bottom, 0, 0); - tsv.setStackScrollRaw(Integer.MAX_VALUE); - tsv.boundScroll(); + tsv.setStackScrollToInitialState(); transform = tsv.getStackTransform(1, tsv.getStackScroll()); transform.rect.offset(taskStackBounds.left, taskStackBounds.top); + replyData.putParcelable(AlternateRecentsComponent.KEY_TWO_TASK_STACK_RECT, + new Rect(transform.rect)); + + // Also calculate the target task rect when there are two tasks. + stack.addTask(new Task()); + tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() - + systemInsets.top - systemInsets.bottom, 0, 0); + tsv.setStackScrollToInitialState(); + transform = tsv.getStackTransform(2, tsv.getStackScroll()); + transform.rect.offset(taskStackBounds.left, taskStackBounds.top); replyData.putParcelable(AlternateRecentsComponent.KEY_MULTIPLE_TASK_STACK_RECT, new Rect(transform.rect)); @@ -127,6 +136,11 @@ class SystemUIMessageHandler extends Handler { Constants.Log.App.TimeRecentsStartupKey, "receivedToggleRecents"); Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, Constants.Log.App.TimeRecentsLaunchKey, "receivedToggleRecents"); + } else if (msg.what == AlternateRecentsComponent.MSG_START_ENTER_ANIMATION) { + // Send a broadcast to start the enter animation + Intent intent = new Intent(RecentsService.ACTION_START_ENTER_ANIMATION); + intent.setPackage(context.getPackageName()); + context.sendBroadcast(intent); } } } @@ -135,6 +149,7 @@ class SystemUIMessageHandler extends Handler { public class RecentsService extends Service { final static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity"; final static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity"; + final static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation"; final static String EXTRA_TRIGGERED_FROM_ALT_TAB = "extra_triggered_from_alt_tab"; Messenger mSystemUIMessenger = new Messenger(new SystemUIMessageHandler(this)); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index cad9ce5..6005275 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -158,6 +158,18 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV return false; } + /** Requests all task stacks to start their enter-recents animation */ + public void startOnEnterAnimation() { + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child instanceof TaskStackView) { + TaskStackView stackView = (TaskStackView) child; + stackView.startOnEnterAnimation(); + } + } + } + /** Adds the search bar */ public void setSearchBar(View searchBar) { // Create the search bar (and hide it if we have no recent tasks) diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java index 3ee0545..cae6bd7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java @@ -278,6 +278,7 @@ public class SwipeHelper { if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) { view.setAlpha(getAlphaForOffset(view)); } + mCallback.onSwipeChanged(mCurrView, view.getTranslationX()); } }); anim.addListener(new AnimatorListenerAdapter() { @@ -313,6 +314,7 @@ public class SwipeHelper { if (mCurrView != null) { float delta = getPos(ev) - mInitialTouchPos; setSwipeAmount(delta); + mCallback.onSwipeChanged(mCurrView, delta); } break; case MotionEvent.ACTION_UP: @@ -393,6 +395,8 @@ public class SwipeHelper { void onBeginDrag(View v); + void onSwipeChanged(View v, float delta); + void onChildDismissed(View v); void onSnapBackCompleted(View v); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java index 07caa1b..c10ddd1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java @@ -16,9 +16,12 @@ package com.android.systemui.recents.views; -import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.widget.FrameLayout; @@ -42,6 +45,8 @@ class TaskBarView extends FrameLayout { Drawable mLightDismissDrawable; Drawable mDarkDismissDrawable; + static Paint sHighlightPaint; + public TaskBarView(Context context) { this(context, null); } @@ -56,9 +61,23 @@ class TaskBarView extends FrameLayout { public TaskBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + setWillNotDraw(false); + + // Load the dismiss resources Resources res = context.getResources(); mLightDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_light); mDarkDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_dark); + + // Configure the highlight paint + if (sHighlightPaint == null) { + RecentsConfiguration config = RecentsConfiguration.getInstance(); + sHighlightPaint = new Paint(); + sHighlightPaint.setStyle(Paint.Style.STROKE); + sHighlightPaint.setStrokeWidth(config.taskViewHighlightPx); + sHighlightPaint.setColor(config.taskBarViewHighlightColor); + sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); + sHighlightPaint.setAntiAlias(true); + } } @Override @@ -69,6 +88,17 @@ class TaskBarView extends FrameLayout { mDismissButton = (ImageView) findViewById(R.id.dismiss_task); } + @Override + protected void onDraw(Canvas canvas) { + RecentsConfiguration config = RecentsConfiguration.getInstance(); + + // Draw the highlight at the top edge (but put the bottom edge just out of view) + float offset = config.taskViewHighlightPx / 2f; + float radius = config.taskViewRoundedCornerRadiusPx; + canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset, + getMeasuredHeight() + radius, radius, radius, sHighlightPaint); + } + /** Synchronizes this bar view's properties with the task's transform */ void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform, TaskViewTransform toTransform, int duration) { @@ -81,7 +111,7 @@ class TaskBarView extends FrameLayout { .alpha(toTransform.dismissAlpha) .setStartDelay(0) .setDuration(duration) - .setInterpolator(config.defaultBezierInterpolator) + .setInterpolator(config.fastOutSlowInInterpolator) .withLayer() .start(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 2b08b19..053f122 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -23,7 +23,6 @@ import android.animation.ValueAnimator; import android.app.Activity; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.Region; @@ -88,6 +87,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int mStackViewsAnimationDuration; boolean mStackViewsDirty = true; boolean mAwaitingFirstLayout = true; + boolean mStartEnterAnimationRequestedAfterLayout; int[] mTmpVisibleRange = new int[2]; Rect mTmpRect = new Rect(); Rect mTmpRect2 = new Rect(); @@ -172,7 +172,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Set the alphas - transform.dismissAlpha = Math.max(-1f, Math.min(0f, t)) + 1f; + transform.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f; // Update the rect and visibility transform.rect.set(mTaskRect); @@ -300,6 +300,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void setStackScrollRaw(int value) { mStackScroll = value; } + /** Sets the current stack scroll to the initial state when you first enter recents */ + public void setStackScrollToInitialState() { + if (mStack.getTaskCount() > 2) { + int initialScroll = mMaxScroll - mTaskRect.height() / 2; + setStackScroll(initialScroll); + } else { + setStackScroll(mMaxScroll); + } + } /** * Returns the scroll to such that the task transform at that index will have t=0. (If the scroll @@ -344,7 +353,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll); mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll - curScroll, 250)); - mScrollAnimator.setInterpolator(RecentsConfiguration.getInstance().defaultBezierInterpolator); + mScrollAnimator.setInterpolator(RecentsConfiguration.getInstance().fastOutSlowInInterpolator); mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { @@ -636,6 +645,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Note: We let the stack view be the full height because we want the cards to go under the // navigation bar if possible. However, the stack rects which we use to calculate // max scroll, etc. need to take the nav bar into account + RecentsConfiguration config = RecentsConfiguration.getInstance(); // Compute the stack rects mRect.set(0, 0, width, height); @@ -643,23 +653,21 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackRect.left += insetLeft; mStackRect.bottom -= insetBottom; - int smallestDimension = Math.min(width, height); - int padding = (int) (Constants.Values.TaskStackView.StackPaddingPct * smallestDimension / 2f); + int widthPadding = (int) (config.taskStackWidthPaddingPct * mStackRect.width()); + int heightPadding = config.taskStackTopPaddingPx; if (Constants.DebugFlags.App.EnableSearchLayout) { - mStackRect.top += padding; - mStackRect.left += padding; - mStackRect.right -= padding; - mStackRect.bottom -= padding; + mStackRect.top += heightPadding; + mStackRect.left += widthPadding; + mStackRect.right -= widthPadding; + mStackRect.bottom -= heightPadding; } else { - mStackRect.inset(padding, padding); + mStackRect.inset(widthPadding, heightPadding); } mStackRectSansPeek.set(mStackRect); mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height(); // Compute the task rect - int minHeight = (int) (mStackRect.height() - - (Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height())); - int size = Math.min(minHeight, Math.min(mStackRect.width(), mStackRect.height())); + int size = mStackRect.width(); int left = mStackRect.left + (mStackRect.width() - size) / 2; mTaskRect.set(left, mStackRectSansPeek.top, left + size, mStackRectSansPeek.top + size); @@ -700,22 +708,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // If this is the first layout, then scroll to the front of the stack and synchronize the // stack views immediately if (mAwaitingFirstLayout) { - setStackScroll(mMaxScroll); + setStackScrollToInitialState(); requestSynchronizeStackViewsWithModel(); synchronizeStackViewsWithModel(); - - // Update the focused task index to be the next item to the top task - if (config.launchedFromAltTab) { - focusTask(Math.max(0, mStack.getTaskCount() - 2), false); - } - - // Animate the task bar of the first task view - if (config.launchedWithThumbnailAnimation) { - TaskView tv = (TaskView) getChildAt(getChildCount() - 1); - if (tv != null) { - tv.animateOnEnterRecents(); - } - } } // Measure each of the children @@ -758,7 +753,47 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } if (mAwaitingFirstLayout) { + RecentsConfiguration config = RecentsConfiguration.getInstance(); + + // Update the focused task index to be the next item to the top task + if (config.launchedFromAltTab) { + focusTask(Math.max(0, mStack.getTaskCount() - 2), false); + } + + // Prepare the first view for its enter animation + if (config.launchedWithThumbnailAnimation) { + TaskView tv = (TaskView) getChildAt(getChildCount() - 1); + if (tv != null) { + tv.prepareAnimateOnEnterRecents(); + } + } + + // Mark that we have completely the first layout mAwaitingFirstLayout = false; + + // If the enter animation started already and we haven't completed a layout yet, do the + // enter animation now + if (mStartEnterAnimationRequestedAfterLayout) { + startOnEnterAnimation(); + } + } + } + + /** Requests this task stacks to start it's enter-recents animation */ + public void startOnEnterAnimation() { + RecentsConfiguration config = RecentsConfiguration.getInstance(); + if (!config.launchedWithThumbnailAnimation) return; + + // If we are still waiting to layout, then just defer until then + if (mAwaitingFirstLayout) { + mStartEnterAnimationRequestedAfterLayout = true; + return; + } + + // Animate the task bar of the first task view + TaskView tv = (TaskView) getChildAt(getChildCount() - 1); + if (tv != null) { + tv.animateOnEnterRecents(); } } @@ -1529,6 +1564,11 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { } @Override + public void onSwipeChanged(View v, float delta) { + // Do nothing + } + + @Override public void onChildDismissed(View v) { TaskView tv = (TaskView) v; mSv.onTaskDismissed(tv); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 8575661..ffa181d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -21,7 +21,6 @@ import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Outline; -import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; @@ -113,7 +112,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On // Update the outline Outline o = new Outline(); - o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), radius); + o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight() - + config.taskViewShadowOutlineBottomInsetPx, radius); setOutline(o); } @@ -167,7 +167,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On .scaleY(toTransform.scale) .alpha(toTransform.alpha) .setDuration(duration) - .setInterpolator(config.defaultBezierInterpolator) + .setInterpolator(config.fastOutSlowInInterpolator) .withLayer() .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override @@ -221,14 +221,21 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On fromTransform.alpha = 0f; } + /** Prepares this task view for the enter-recents animations. This is called earlier in the + * first layout because the actual animation into recents may take a long time. */ + public void prepareAnimateOnEnterRecents() { + mBarView.setVisibility(View.INVISIBLE); + } + /** Animates this task view as it enters recents */ public void animateOnEnterRecents() { RecentsConfiguration config = RecentsConfiguration.getInstance(); - mBarView.setAlpha(0f); + mBarView.setVisibility(View.VISIBLE); + mBarView.setTranslationY(-mBarView.getMeasuredHeight()); mBarView.animate() - .alpha(1f) - .setStartDelay(300) - .setInterpolator(config.defaultBezierInterpolator) + .translationY(0) + .setStartDelay(200) + .setInterpolator(config.fastOutSlowInInterpolator) .setDuration(config.taskBarEnterAnimDuration) .withLayer() .start(); @@ -238,9 +245,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On public void animateOnLeavingRecents(final Runnable r) { RecentsConfiguration config = RecentsConfiguration.getInstance(); mBarView.animate() - .alpha(0f) + .translationY(-mBarView.getMeasuredHeight()) .setStartDelay(0) - .setInterpolator(config.defaultBezierInterpolator) + .setInterpolator(config.fastOutLinearInInterpolator) .setDuration(config.taskBarExitAnimDuration) .withLayer() .withEndAction(new Runnable() { @@ -261,7 +268,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On animate().translationX(config.taskViewRemoveAnimTranslationXPx) .alpha(0f) .setStartDelay(0) - .setInterpolator(config.defaultBezierInterpolator) + .setInterpolator(config.fastOutSlowInInterpolator) .setDuration(config.taskViewRemoveAnimDuration) .withLayer() .withEndAction(new Runnable() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index fbe76f9..06cc476 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -83,6 +83,7 @@ import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Locale; @@ -117,6 +118,9 @@ public abstract class BaseStatusBar extends SystemUI implements public static final int EXPANDED_LEAVE_ALONE = -10000; public static final int EXPANDED_FULL_OPEN = -10001; + /** If true, delays dismissing the Keyguard until the ActivityManager calls back. */ + protected static final boolean DELAY_DISMISS_TO_ACTIVITY_LAUNCH = false; + protected CommandQueue mCommandQueue; protected IStatusBarService mBarService; protected H mHandler = createHandler(); @@ -182,6 +186,7 @@ public abstract class BaseStatusBar extends SystemUI implements */ protected int mState; protected boolean mBouncerShowing; + protected boolean mShowLockscreenNotifications; protected NotificationOverflowContainer mKeyguardIconOverflowContainer; @@ -201,6 +206,9 @@ public abstract class BaseStatusBar extends SystemUI implements final int mode = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); setZenMode(mode); + final boolean show = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1) != 0; + setShowLockscreenNotifications(show); } }; @@ -224,7 +232,7 @@ public abstract class BaseStatusBar extends SystemUI implements } final boolean isActivity = pendingIntent.isActivity(); if (isActivity) { - startNotificationActivity(new OnDismissAction() { + dismissKeyguardThenExecute(new OnDismissAction() { @Override public boolean onDismiss() { try { @@ -246,7 +254,8 @@ public abstract class BaseStatusBar extends SystemUI implements animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); visibilityChanged(false); } - return handled; // Wait for activity start. + // Wait for activity start. + return handled && DELAY_DISMISS_TO_ACTIVITY_LAUNCH; } }); return true; @@ -328,8 +337,7 @@ public abstract class BaseStatusBar extends SystemUI implements mHandler.post(new Runnable() { @Override public void run() { - mNotificationData.updateRanking(currentRanking); - updateNotifications(); + updateRankingInternal(currentRanking); } }); } @@ -362,6 +370,9 @@ public abstract class BaseStatusBar extends SystemUI implements mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, mSettingsObserver); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, + mSettingsObserver); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), @@ -472,7 +483,7 @@ public abstract class BaseStatusBar extends SystemUI implements * Takes the necessary steps to prepare the status bar for starting an activity, then starts it. * @param action A dismiss action that is called if it's safe to start the activity. */ - protected void startNotificationActivity(OnDismissAction action) { + protected void dismissKeyguardThenExecute(OnDismissAction action) { action.onDismiss(); } @@ -1042,7 +1053,7 @@ public abstract class BaseStatusBar extends SystemUI implements } public void onClick(final View v) { - startNotificationActivity(new OnDismissAction() { + dismissKeyguardThenExecute(new OnDismissAction() { public boolean onDismiss() { try { // The intent we are sending is for the application, which @@ -1062,7 +1073,7 @@ public abstract class BaseStatusBar extends SystemUI implements v.getLocationOnScreen(pos); Intent overlay = new Intent(); overlay.setSourceBounds(new Rect(pos[0], pos[1], - pos[0]+v.getWidth(), pos[1]+v.getHeight())); + pos[0] + v.getWidth(), pos[1] + v.getHeight())); try { mIntent.send(mContext, 0, overlay); sent = true; @@ -1087,7 +1098,7 @@ public abstract class BaseStatusBar extends SystemUI implements visibilityChanged(false); boolean waitForActivityLaunch = sent && mIntent.isActivity(); - return waitForActivityLaunch; + return waitForActivityLaunch && DELAY_DISMISS_TO_ACTIVITY_LAUNCH; } }); } @@ -1247,6 +1258,10 @@ public abstract class BaseStatusBar extends SystemUI implements updateNotifications(); } + protected void setShowLockscreenNotifications(boolean show) { + mShowLockscreenNotifications = show; + } + protected abstract void haltTicker(); protected abstract void setAreThereNotifications(); protected abstract void updateNotifications(); @@ -1264,6 +1279,8 @@ public abstract class BaseStatusBar extends SystemUI implements public abstract void addNotificationInternal(StatusBarNotification notification, Ranking ranking); + protected abstract void updateRankingInternal(Ranking ranking); + @Override public void removeNotification(String key) { if (!USE_NOTIFICATION_LISTENER) { @@ -1271,7 +1288,7 @@ public abstract class BaseStatusBar extends SystemUI implements } } - protected abstract void removeNotificationInternal(String key, Ranking ranking); + public abstract void removeNotificationInternal(String key, Ranking ranking); public void updateNotification(StatusBarNotification notification) { if (!USE_NOTIFICATION_LISTENER) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java b/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java index 24da5c2..de27119 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java @@ -20,8 +20,10 @@ import android.app.Notification; import android.content.Context; import android.os.Process; import android.provider.Settings; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; +import android.util.ArraySet; import android.view.View; import com.android.systemui.R; @@ -30,12 +32,13 @@ import com.android.systemui.statusbar.phone.PhoneStatusBar; public class InterceptedNotifications { private static final String TAG = "InterceptedNotifications"; - private static final String EXTRA_INTERCEPT = "android.intercept"; + private static final String SYNTHETIC_KEY = "InterceptedNotifications.SYNTHETIC_KEY"; private final Context mContext; private final PhoneStatusBar mBar; private final ArrayMap<String, StatusBarNotification> mIntercepted = new ArrayMap<String, StatusBarNotification>(); + private final ArraySet<String> mReleased = new ArraySet<String>(); private String mSynKey; @@ -48,25 +51,45 @@ public class InterceptedNotifications { final int n = mIntercepted.size(); for (int i = 0; i < n; i++) { final StatusBarNotification sbn = mIntercepted.valueAt(i); - sbn.getNotification().extras.putBoolean(EXTRA_INTERCEPT, false); + mReleased.add(sbn.getKey()); mBar.addNotificationInternal(sbn, null); } mIntercepted.clear(); updateSyntheticNotification(); } - public boolean tryIntercept(StatusBarNotification notification) { - if (!notification.getNotification().extras.getBoolean(EXTRA_INTERCEPT)) return false; + public boolean tryIntercept(StatusBarNotification notification, Ranking ranking) { + if (ranking == null) return false; if (shouldDisplayIntercepted()) return false; + if (mReleased.contains(notification.getKey())) return false; + if (!ranking.isInterceptedByDoNotDisturb(notification.getKey())) return false; mIntercepted.put(notification.getKey(), notification); updateSyntheticNotification(); return true; } + public void retryIntercepts(Ranking ranking) { + if (ranking == null) return; + + boolean changed = false; + final int N = mIntercepted.size(); + for (int i = 0; i < N; i++) { + final StatusBarNotification sbn = mIntercepted.valueAt(i); + if (!tryIntercept(sbn, ranking)) { + changed = true; + mBar.addNotificationInternal(sbn, ranking); + } + } + if (changed) { + updateSyntheticNotification(); + } + } + public void remove(String key) { if (mIntercepted.remove(key) != null) { updateSyntheticNotification(); } + mReleased.remove(key); } public boolean isSyntheticEntry(Entry ent) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 802e5e5..c8f185c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -47,6 +47,8 @@ public class NotificationPanelView extends PanelView implements ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, View.OnClickListener, KeyguardPageSwipeHelper.Callback { + private static float EXPANSION_RUBBER_BAND_EXTRA_FACTOR = 0.4f; + private KeyguardPageSwipeHelper mPageSwiper; PhoneStatusBar mStatusBar; private StatusBarHeaderView mHeader; @@ -70,6 +72,7 @@ public class NotificationPanelView extends PanelView implements */ private boolean mIntercepting; private boolean mQsExpanded; + private boolean mQsFullyExpanded; private boolean mKeyguardShowing; private float mInitialHeightOnTouch; private float mInitialTouchX; @@ -165,7 +168,9 @@ public class NotificationPanelView extends PanelView implements mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight; mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight(); if (mQsExpanded) { - setQsStackScrollerPadding(mQsMaxExpansionHeight); + if (mQsFullyExpanded) { + setQsStackScrollerPadding(mQsMaxExpansionHeight); + } } else { setQsExpansion(mQsMinExpansionHeight); positionClockAndNotifications(); @@ -522,6 +527,7 @@ public class NotificationPanelView extends PanelView implements private void setQsExpansion(float height) { height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); + mQsFullyExpanded = height == mQsMaxExpansionHeight; if (height > mQsMinExpansionHeight && !mQsExpanded) { setQsExpanded(true); } else if (height <= mQsMinExpansionHeight && mQsExpanded) { @@ -701,8 +707,11 @@ public class NotificationPanelView extends PanelView implements @Override protected void onOverExpansionChanged(float overExpansion) { float currentOverScroll = mNotificationStackScroller.getCurrentOverScrolledPixels(true); - mNotificationStackScroller.setOverScrolledPixels(currentOverScroll + overExpansion - - mOverExpansion, true /* onTop */, false /* animate */); + float expansionChange = overExpansion - mOverExpansion; + expansionChange *= EXPANSION_RUBBER_BAND_EXTRA_FACTOR; + mNotificationStackScroller.setOverScrolledPixels(currentOverScroll + expansionChange, + true /* onTop */, + false /* animate */); super.onOverExpansionChanged(overExpansion); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 5dcd61c..f9afcf3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -505,6 +505,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override + protected void setShowLockscreenNotifications(boolean show) { + super.setShowLockscreenNotifications(show); + updateStackScrollerState(); + } + + @Override public void start() { mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); @@ -1044,7 +1050,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (shadeEntry == null) { return; } - if (mZenMode != Global.ZEN_MODE_OFF && mIntercepted.tryIntercept(notification)) { + if (mZenMode != Global.ZEN_MODE_OFF && mIntercepted.tryIntercept(notification, ranking)) { // Forward the ranking so we can sort the new notification. mNotificationData.updateRanking(ranking); return; @@ -1108,6 +1114,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override + protected void updateRankingInternal(Ranking ranking) { + mNotificationData.updateRanking(ranking); + mIntercepted.retryIntercepts(ranking); + updateNotifications(); + } + + @Override public void removeNotificationInternal(String key, Ranking ranking) { StatusBarNotification old = removeNotificationViews(key, ranking); if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); @@ -2342,16 +2355,24 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } }; - public void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned) { + public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned) { if (onlyProvisioned && !isDeviceProvisioned()) return; - try { - // Dismiss the lock screen when Settings starts. - ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); - } catch (RemoteException e) { - } - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); - animateCollapsePanels(); + + dismissKeyguardThenExecute(new OnDismissAction() { + @Override + public boolean onDismiss() { + try { + // Dismiss the lock screen when Settings starts. + ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); + } catch (RemoteException e) { + } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); + animateCollapsePanels(); + + return DELAY_DISMISS_TO_ACTIVITY_LAUNCH; + } + }); } private View.OnClickListener mClockClickListener = new View.OnClickListener() { @@ -2412,7 +2433,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, }; @Override - protected void startNotificationActivity(OnDismissAction action) { + protected void dismissKeyguardThenExecute(OnDismissAction action) { if (mStatusBarKeyguardViewManager.isShowing()) { mStatusBarKeyguardViewManager.dismissWithAction(action); } else { @@ -2851,7 +2872,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } public void updateStackScrollerState() { + if (mStackScroller == null) return; mStackScroller.setDimmed(mState == StatusBarState.KEYGUARD, false /* animate */); + mStackScroller.setVisibility(!mShowLockscreenNotifications && mState == StatusBarState.KEYGUARD + ? View.INVISIBLE : View.VISIBLE); } public void userActivity() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java index 3245f1a..c097e2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java @@ -293,5 +293,15 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL public void setQSPanel(QSPanel qsp) { mQSPanel = qsp; + if (mQSPanel != null) { + mQSPanel.setCallback(mQsPanelCallback); + } } + + private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() { + @Override + public void onShowingDetail(boolean showingDetail) { + mBrightnessContainer.animate().alpha(showingDetail ? 0 : 1).withLayer().start(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index c2bd1cb..faea8de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.tv; import android.os.IBinder; +import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.view.View; @@ -54,11 +55,15 @@ public class TvStatusBar extends BaseStatusBar { } @Override + protected void updateRankingInternal(Ranking ranking) { + } + + @Override public void updateNotification(StatusBarNotification notification) { } @Override - protected void removeNotificationInternal(String key, Ranking ranking) { + public void removeNotificationInternal(String key, Ranking ranking) { } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java index 67f3a3d..cbfc641 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java @@ -25,12 +25,15 @@ import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.graphics.drawable.ColorDrawable; import android.media.AudioManager; import android.media.AudioService; import android.media.AudioSystem; import android.media.RingtoneManager; import android.media.ToneGenerator; import android.net.Uri; +import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.os.Vibrator; @@ -44,6 +47,7 @@ import android.view.ViewStub; import android.view.Window; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; @@ -76,6 +80,7 @@ public class VolumePanel extends Handler { private static final int MAX_VOLUME = 100; private static final int FREE_DELAY = 10000; private static final int TIMEOUT_DELAY = 3000; + private static final int TIMEOUT_DELAY_EXPANDED = 10000; private static final int MSG_VOLUME_CHANGED = 0; private static final int MSG_FREE_RESOURCES = 1; @@ -103,6 +108,7 @@ public class VolumePanel extends Handler { private boolean mRingIsSilent; private boolean mVoiceCapable; private boolean mZenModeCapable; + private int mTimeoutDelay = TIMEOUT_DELAY; // True if we want to play tones on the system stream when the master stream is specified. private final boolean mPlayMasterStreamTones; @@ -301,17 +307,26 @@ public class VolumePanel extends Handler { lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top); lp.width = res.getDimensionPixelSize(com.android.systemui.R.dimen.volume_panel_width); lp.type = LayoutParams.TYPE_VOLUME_OVERLAY; + lp.format = PixelFormat.TRANSLUCENT; lp.windowAnimations = R.style.Animation_VolumePanel; - window.setBackgroundDrawableResource(com.android.systemui.R.drawable.qs_panel_background); window.setAttributes(lp); window.setGravity(Gravity.TOP); window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); window.requestFeature(Window.FEATURE_NO_TITLE); window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL - | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); + | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | LayoutParams.FLAG_HARDWARE_ACCELERATED); mDialog.setCanceledOnTouchOutside(true); - mDialog.setContentView(layoutId); + // temporary workaround for no window shadows + final FrameLayout layout = new FrameLayout(mContext); + final int z = res.getDimensionPixelSize(com.android.systemui.R.dimen.volume_panel_z); + layout.setPadding(z, z, z, z); + final View v = LayoutInflater.from(mContext).inflate(layoutId, layout, false); + v.setBackgroundResource(com.android.systemui.R.drawable.qs_panel_background); + v.setElevation(z); + layout.addView(v); + mDialog.setContentView(layout); mDialog.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { @@ -321,6 +336,7 @@ public class VolumePanel extends Handler { }); mDialog.create(); + mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(0x00000000)); mView = window.findViewById(R.id.content); mView.setOnTouchListener(new View.OnTouchListener() { @@ -513,6 +529,7 @@ public class VolumePanel extends Handler { @Override public void onInteraction() { + resetTimeout(); if (mZenPanelCallback != null) { mZenPanelCallback.onInteraction(); } @@ -521,6 +538,8 @@ public class VolumePanel extends Handler { } mZenPanel.setVisibility(View.VISIBLE); mZenPanelDivider.setVisibility(View.VISIBLE); + mTimeoutDelay = TIMEOUT_DELAY_EXPANDED; + resetTimeout(); } private void collapse() { @@ -529,6 +548,8 @@ public class VolumePanel extends Handler { mZenPanel.setVisibility(View.GONE); } mZenPanelDivider.setVisibility(View.GONE); + mTimeoutDelay = TIMEOUT_DELAY; + resetTimeout(); } public void updateStates() { @@ -1082,7 +1103,7 @@ public class VolumePanel extends Handler { public void resetTimeout() { if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis()); removeMessages(MSG_TIMEOUT); - sendEmptyMessageDelayed(MSG_TIMEOUT, TIMEOUT_DELAY); + sendEmptyMessageDelayed(MSG_TIMEOUT, mTimeoutDelay); } private void forceTimeout() { @@ -1134,7 +1155,12 @@ public class VolumePanel extends Handler { public void onClick(View v) { if (v == mExpandButton && mZenController != null) { final boolean newZen = !mZenController.isZen(); - mZenController.setZen(newZen); + AsyncTask.execute(new Runnable() { + @Override + public void run() { + mZenController.setZen(newZen); + } + }); if (newZen) { expand(); } else { diff --git a/policy/src/com/android/internal/policy/impl/GlobalKeyManager.java b/policy/src/com/android/internal/policy/impl/GlobalKeyManager.java index 3cf7e82..8c8209f 100644 --- a/policy/src/com/android/internal/policy/impl/GlobalKeyManager.java +++ b/policy/src/com/android/internal/policy/impl/GlobalKeyManager.java @@ -30,6 +30,7 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.PrintWriter; /** * Stores a mapping of global keys. @@ -123,4 +124,21 @@ final class GlobalKeyManager { } } } + + public void dump(String prefix, PrintWriter pw) { + final int numKeys = mKeyMapping.size(); + if (numKeys == 0) { + pw.print(prefix); pw.println("mKeyMapping.size=0"); + return; + } + pw.print(prefix); pw.println("mKeyMapping={"); + for (int i = 0; i < numKeys; ++i) { + pw.print(" "); + pw.print(prefix); + pw.print(KeyEvent.keyCodeToString(mKeyMapping.keyAt(i))); + pw.print("="); + pw.println(mKeyMapping.valueAt(i).flattenToString()); + } + pw.print(prefix); pw.println("}"); + } } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 6b0095a..5dc9e58 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -1434,30 +1434,58 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { final int features = getLocalFeatures(); if (value == PROGRESS_VISIBILITY_ON) { if ((features & (1 << FEATURE_PROGRESS)) != 0) { - int level = horizontalProgressBar.getProgress(); - int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ? - View.VISIBLE : View.INVISIBLE; - horizontalProgressBar.setVisibility(visibility); + if (horizontalProgressBar != null) { + int level = horizontalProgressBar.getProgress(); + int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ? + View.VISIBLE : View.INVISIBLE; + horizontalProgressBar.setVisibility(visibility); + } else { + Log.e(TAG, "Horizontal progress bar not located in current window decor"); + } } if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { - circularProgressBar.setVisibility(View.VISIBLE); + if (circularProgressBar != null) { + circularProgressBar.setVisibility(View.VISIBLE); + } else { + Log.e(TAG, "Circular progress bar not located in current window decor"); + } } } else if (value == PROGRESS_VISIBILITY_OFF) { if ((features & (1 << FEATURE_PROGRESS)) != 0) { - horizontalProgressBar.setVisibility(View.GONE); + if (horizontalProgressBar != null) { + horizontalProgressBar.setVisibility(View.GONE); + } else { + Log.e(TAG, "Horizontal progress bar not located in current window decor"); + } } if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { - circularProgressBar.setVisibility(View.GONE); + if (circularProgressBar != null) { + circularProgressBar.setVisibility(View.GONE); + } else { + Log.e(TAG, "Circular progress bar not located in current window decor"); + } } } else if (value == PROGRESS_INDETERMINATE_ON) { - horizontalProgressBar.setIndeterminate(true); + if (horizontalProgressBar != null) { + horizontalProgressBar.setIndeterminate(true); + } else { + Log.e(TAG, "Horizontal progress bar not located in current window decor"); + } } else if (value == PROGRESS_INDETERMINATE_OFF) { - horizontalProgressBar.setIndeterminate(false); + if (horizontalProgressBar != null) { + horizontalProgressBar.setIndeterminate(false); + } else { + Log.e(TAG, "Horizontal progress bar not located in current window decor"); + } } else if (PROGRESS_START <= value && value <= PROGRESS_END) { // We want to set the progress value before testing for visibility // so that when the progress bar becomes visible again, it has the // correct level. - horizontalProgressBar.setProgress(value - PROGRESS_START); + if (horizontalProgressBar != null) { + horizontalProgressBar.setProgress(value - PROGRESS_START); + } else { + Log.e(TAG, "Horizontal progress bar not located in current window decor"); + } if (value < PROGRESS_END) { showProgressBars(horizontalProgressBar, circularProgressBar); @@ -1465,7 +1493,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { hideProgressBars(horizontalProgressBar, circularProgressBar); } } else if (PROGRESS_SECONDARY_START <= value && value <= PROGRESS_SECONDARY_END) { - horizontalProgressBar.setSecondaryProgress(value - PROGRESS_SECONDARY_START); + if (horizontalProgressBar != null) { + horizontalProgressBar.setSecondaryProgress(value - PROGRESS_SECONDARY_START); + } else { + Log.e(TAG, "Horizontal progress bar not located in current window decor"); + } showProgressBars(horizontalProgressBar, circularProgressBar); } @@ -1475,11 +1507,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private void showProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) { final int features = getLocalFeatures(); if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 && - spinnyProgressBar.getVisibility() == View.INVISIBLE) { + spinnyProgressBar != null && spinnyProgressBar.getVisibility() == View.INVISIBLE) { spinnyProgressBar.setVisibility(View.VISIBLE); } // Only show the progress bars if the primary progress is not complete - if ((features & (1 << FEATURE_PROGRESS)) != 0 && + if ((features & (1 << FEATURE_PROGRESS)) != 0 && horizontalProgressBar != null && horizontalProgressBar.getProgress() < 10000) { horizontalProgressBar.setVisibility(View.VISIBLE); } @@ -1490,11 +1522,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { Animation anim = AnimationUtils.loadAnimation(getContext(), com.android.internal.R.anim.fade_out); anim.setDuration(1000); if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 && + spinnyProgressBar != null && spinnyProgressBar.getVisibility() == View.VISIBLE) { spinnyProgressBar.startAnimation(anim); spinnyProgressBar.setVisibility(View.INVISIBLE); } - if ((features & (1 << FEATURE_PROGRESS)) != 0 && + if ((features & (1 << FEATURE_PROGRESS)) != 0 && horizontalProgressBar != null && horizontalProgressBar.getVisibility() == View.VISIBLE) { horizontalProgressBar.startAnimation(anim); horizontalProgressBar.setVisibility(View.INVISIBLE); diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 8e68dfc..c483836 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -3348,7 +3348,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { pf.top = mContentTop; pf.right = mContentRight; pf.bottom = mContentBottom; - if (adjust != SOFT_INPUT_ADJUST_RESIZE) { + if (win.isVoiceInteraction()) { + df.left = of.left = cf.left = mVoiceContentLeft; + df.top = of.top = cf.top = mVoiceContentTop; + df.right = of.right = cf.right = mVoiceContentRight; + df.bottom = of.bottom = cf.bottom = mVoiceContentBottom; + } else if (adjust != SOFT_INPUT_ADJUST_RESIZE) { df.left = of.left = cf.left = mDockLeft; df.top = of.top = cf.top = mDockTop; df.right = of.right = cf.right = mDockRight; @@ -5623,6 +5628,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print(prefix); pw.print("mDemoHdmiRotation="); pw.print(mDemoHdmiRotation); pw.print(" mDemoHdmiRotationLock="); pw.println(mDemoHdmiRotationLock); pw.print(prefix); pw.print("mUndockedHdmiRotation="); pw.println(mUndockedHdmiRotation); + mGlobalKeyManager.dump(prefix, pw); mStatusBarController.dump(pw, prefix); mNavigationBarController.dump(pw, prefix); PolicyControl.dump(prefix, pw); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 5527528..d7a19ad 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -514,6 +514,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { // sequence number of NetworkRequests private int mNextNetworkRequestId = 1; + private static final int UID_UNUSED = -1; + public ConnectivityService(Context context, INetworkManagementService netd, INetworkStatsService statsService, INetworkPolicyManager policyManager) { // Currently, omitting a NetworkFactory will create one internally @@ -1673,10 +1675,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { } return false; } + final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { LinkProperties lp = tracker.getLinkProperties(); - boolean ok = addRouteToAddress(lp, addr, exempt, tracker.getNetwork().netId); + boolean ok = modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt, + tracker.getNetwork().netId, uid); if (DBG) log("requestRouteToHostAddress ok=" + ok); return ok; } finally { @@ -1686,24 +1690,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable, boolean exempt, int netId) { - return modifyRoute(p, r, 0, ADD, toDefaultTable, exempt, netId); + return modifyRoute(p, r, 0, ADD, toDefaultTable, exempt, netId, false, UID_UNUSED); } private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable, int netId) { - return modifyRoute(p, r, 0, REMOVE, toDefaultTable, UNEXEMPT, netId); - } - - private boolean addRouteToAddress(LinkProperties lp, InetAddress addr, boolean exempt, - int netId) { - return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt, netId); - } - - private boolean removeRouteToAddress(LinkProperties lp, InetAddress addr, int netId) { - return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE, UNEXEMPT, netId); + return modifyRoute(p, r, 0, REMOVE, toDefaultTable, UNEXEMPT, netId, false, UID_UNUSED); } private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd, - boolean toDefaultTable, boolean exempt, int netId) { + boolean toDefaultTable, boolean exempt, int netId, int uid) { RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr); if (bestRoute == null) { bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName()); @@ -1718,11 +1713,18 @@ public class ConnectivityService extends IConnectivityManager.Stub { bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface); } } - return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable, exempt, netId); + return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable, exempt, netId, true, uid); } + /* + * TODO: Clean all this stuff up. Once we have UID-based routing, stuff will break due to + * incorrect tracking of mAddedRoutes, so a cleanup becomes necessary and urgent. But at + * the same time, there'll be no more need to track mAddedRoutes or mExemptAddresses, + * or even have the concept of an exempt address, or do things like "selectBestRoute", or + * determine "default" vs "secondary" table, etc., so the cleanup becomes possible. + */ private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd, - boolean toDefaultTable, boolean exempt, int netId) { + boolean toDefaultTable, boolean exempt, int netId, boolean legacy, int uid) { if ((lp == null) || (r == null)) { if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r); return false; @@ -1751,7 +1753,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { bestRoute.getGateway(), ifaceName); } - modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable, exempt, netId); + modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable, exempt, netId, + legacy, uid); } } if (doAdd) { @@ -1761,7 +1764,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { synchronized (mRoutesLock) { // only track default table - only one apps can effect mAddedRoutes.add(r); - mNetd.addRoute(netId, r); + if (legacy) { + mNetd.addLegacyRouteForNetId(netId, r, uid); + } else { + mNetd.addRoute(netId, r); + } if (exempt) { LinkAddress dest = r.getDestination(); if (!mExemptAddresses.contains(dest)) { @@ -1771,7 +1778,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } } else { - mNetd.addRoute(netId, r); + if (legacy) { + mNetd.addLegacyRouteForNetId(netId, r, uid); + } else { + mNetd.addRoute(netId, r); + } } } catch (Exception e) { // never crash - catch them all @@ -1787,7 +1798,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (mAddedRoutes.contains(r) == false) { if (VDBG) log("Removing " + r + " for interface " + ifaceName); try { - mNetd.removeRoute(netId, r); + if (legacy) { + mNetd.removeLegacyRouteForNetId(netId, r, uid); + } else { + mNetd.removeRoute(netId, r); + } LinkAddress dest = r.getDestination(); if (mExemptAddresses.contains(dest)) { mNetd.clearHostExemption(dest); @@ -1805,7 +1820,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else { if (VDBG) log("Removing " + r + " for interface " + ifaceName); try { - mNetd.removeRoute(netId, r); + if (legacy) { + mNetd.removeLegacyRouteForNetId(netId, r, uid); + } else { + mNetd.removeRoute(netId, r); + } } catch (Exception e) { // never crash - catch them all if (VDBG) loge("Exception trying to remove a route: " + e); diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 137387e..eefe8da 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -885,7 +885,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub final LinkAddress la = route.getDestination(); cmd.appendArg(route.getInterface()); cmd.appendArg(la.getAddress().getHostAddress() + "/" + la.getNetworkPrefixLength()); - cmd.appendArg(route.getGateway().getHostAddress()); + if (route.hasGateway()) { + cmd.appendArg(route.getGateway().getHostAddress()); + } try { mConnector.execute(cmd); @@ -1993,14 +1995,15 @@ public class NetworkManagementService extends INetworkManagementService.Stub private void modifyLegacyRouteForNetId(int netId, RouteInfo routeInfo, int uid, String action) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - final Command cmd = new Command("network", "legacy", uid, "route", action, netId); + final Command cmd = new Command("network", "route", "legacy", uid, action, netId); - // create quadlet: dest-ip-addr prefixlength gateway-ip-addr iface + // create triplet: interface dest-ip-addr/prefixlength gateway-ip-addr final LinkAddress la = routeInfo.getDestination(); - cmd.appendArg(la.getAddress().getHostAddress()); - cmd.appendArg(la.getNetworkPrefixLength()); - cmd.appendArg(routeInfo.getGateway().getHostAddress()); cmd.appendArg(routeInfo.getInterface()); + cmd.appendArg(la.getAddress().getHostAddress() + "/" + la.getNetworkPrefixLength()); + if (routeInfo.hasGateway()) { + cmd.appendArg(routeInfo.getGateway().getHostAddress()); + } try { mConnector.execute(cmd); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 033b967..108a079 100755 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -247,7 +247,8 @@ public final class ActiveServices { maxBg = Integer.parseInt(SystemProperties.get("ro.config.max_starting_bg", "0")); } catch(RuntimeException e) { } - mMaxStartingBackground = maxBg > 0 ? maxBg : ActivityManager.isLowRamDeviceStatic() ? 1 : 3; + mMaxStartingBackground = maxBg > 0 + ? maxBg : ActivityManager.isLowRamDeviceStatic() ? 1 : 8; } ServiceRecord getServiceByName(ComponentName name, int callingUser) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 0b1c2b8..0cc53d1 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -204,6 +204,10 @@ public final class ActivityStackSupervisor implements DisplayListener { /** Set when we have taken too long waiting to go to sleep. */ boolean mSleepTimeout = false; + /** Indicates if we are running on a Leanback-only (TV) device. Only initialized after + * setWindowManager is called. **/ + private boolean mLeanbackOnlyDevice; + /** * We don't want to allow the device to go to sleep while in the process * of launching an activity. This is primarily to allow alarm intent @@ -268,6 +272,9 @@ public final class ActivityStackSupervisor implements DisplayListener { mHomeStack = mFocusedStack = mLastFocusedStack = getStack(HOME_STACK_ID); mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); + + // Initialize this here, now that we can get a valid reference to PackageManager. + mLeanbackOnlyDevice = isLeanbackOnlyDevice(); } } @@ -1375,7 +1382,10 @@ public final class ActivityStackSupervisor implements DisplayListener { ActivityStack adjustStackFocus(ActivityRecord r, boolean newTask) { final TaskRecord task = r.task; - if (r.isApplicationActivity() || (task != null && task.isApplicationTask())) { + + // On leanback only devices we should keep all activities in the same stack. + if (!mLeanbackOnlyDevice && + (r.isApplicationActivity() || (task != null && task.isApplicationTask()))) { if (task != null) { final ActivityStack taskStack = task.stack; if (taskStack.isOnHomeDisplay()) { @@ -3423,4 +3433,16 @@ public final class ActivityStackSupervisor implements DisplayListener { return "VirtualActivityDisplay={" + mDisplayId + "}"; } } + + private boolean isLeanbackOnlyDevice() { + boolean onLeanbackOnly = false; + try { + onLeanbackOnly = AppGlobals.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_LEANBACK_ONLY); + } catch (RemoteException e) { + // noop + } + + return onLeanbackOnly; + } } diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java index 49293d3..b30baea 100644 --- a/services/core/java/com/android/server/notification/NotificationComparator.java +++ b/services/core/java/com/android/server/notification/NotificationComparator.java @@ -21,11 +21,10 @@ import java.util.Comparator; * Sorts notificaitons into attention-relelvant order. */ public class NotificationComparator - implements Comparator<NotificationManagerService.NotificationRecord> { + implements Comparator<NotificationRecord> { @Override - public int compare(NotificationManagerService.NotificationRecord lhs, - NotificationManagerService.NotificationRecord rhs) { + public int compare(NotificationRecord lhs, NotificationRecord rhs) { if (lhs.isRecentlyIntrusive() != rhs.isRecentlyIntrusive()) { return lhs.isRecentlyIntrusive() ? -1 : 1; } diff --git a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java index db17f3a..d8ab9d7 100644 --- a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java @@ -20,8 +20,6 @@ import android.app.Notification; import android.content.Context; import android.util.Slog; -import com.android.server.notification.NotificationManagerService.NotificationRecord; - /** * This {@link com.android.server.notification.NotificationSignalExtractor} noticies noisy * notifications and marks them to get a temporary ranking bump. diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index bbc3091..cb78a45 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -43,7 +43,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.res.Resources; import android.database.ContentObserver; -import android.graphics.Bitmap; import android.media.AudioManager; import android.media.IRingtonePlayer; import android.net.Uri; @@ -60,13 +59,13 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.Vibrator; import android.provider.Settings; -import android.service.notification.INotificationListener; +import android.service.notification.Condition; import android.service.notification.IConditionListener; import android.service.notification.IConditionProvider; +import android.service.notification.INotificationListener; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationRankingUpdate; import android.service.notification.StatusBarNotification; -import android.service.notification.Condition; import android.service.notification.ZenModeConfig; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -87,7 +86,6 @@ import com.android.server.lights.Light; import com.android.server.lights.LightsManager; import com.android.server.notification.ManagedServices.ManagedServiceInfo; import com.android.server.notification.ManagedServices.UserProfiles; -import com.android.server.notification.NotificationUsageStats.SingleNotificationStats; import com.android.server.statusbar.StatusBarManagerInternal; import libcore.io.IoUtils; @@ -103,10 +101,8 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.lang.reflect.Array; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; @@ -211,8 +207,6 @@ public class NotificationManagerService extends SystemService { private ConditionProviders mConditionProviders; private NotificationUsageStats mUsageStats; - private static final String EXTRA_INTERCEPT = "android.intercept"; - private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); private static final int REASON_DELEGATE_CLICK = 1; @@ -425,144 +419,6 @@ public class NotificationManagerService extends SystemService { return true; } - private static String idDebugString(Context baseContext, String packageName, int id) { - Context c = null; - - if (packageName != null) { - try { - c = baseContext.createPackageContext(packageName, 0); - } catch (NameNotFoundException e) { - c = baseContext; - } - } else { - c = baseContext; - } - - String pkg; - String type; - String name; - - Resources r = c.getResources(); - try { - return r.getResourceName(id); - } catch (Resources.NotFoundException e) { - return "<name unknown>"; - } - } - - - - public static final class NotificationRecord { - final StatusBarNotification sbn; - SingleNotificationStats stats; - boolean isCanceled; - - // These members are used by NotificationSignalExtractors - // to communicate with the ranking module. - private float mContactAffinity; - private boolean mRecentlyIntrusive; - - NotificationRecord(StatusBarNotification sbn) - { - this.sbn = sbn; - } - - public Notification getNotification() { return sbn.getNotification(); } - public int getFlags() { return sbn.getNotification().flags; } - public int getUserId() { return sbn.getUserId(); } - public String getKey() { return sbn.getKey(); } - - void dump(PrintWriter pw, String prefix, Context baseContext) { - final Notification notification = sbn.getNotification(); - pw.println(prefix + this); - pw.println(prefix + " uid=" + sbn.getUid() + " userId=" + sbn.getUserId()); - pw.println(prefix + " icon=0x" + Integer.toHexString(notification.icon) - + " / " + idDebugString(baseContext, sbn.getPackageName(), notification.icon)); - pw.println(prefix + " pri=" + notification.priority + " score=" + sbn.getScore()); - pw.println(prefix + " key=" + sbn.getKey()); - pw.println(prefix + " contentIntent=" + notification.contentIntent); - pw.println(prefix + " deleteIntent=" + notification.deleteIntent); - pw.println(prefix + " tickerText=" + notification.tickerText); - pw.println(prefix + " contentView=" + notification.contentView); - pw.println(prefix + String.format(" defaults=0x%08x flags=0x%08x", - notification.defaults, notification.flags)); - pw.println(prefix + " sound=" + notification.sound); - pw.println(prefix + String.format(" color=0x%08x", notification.color)); - pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate)); - pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d", - notification.ledARGB, notification.ledOnMS, notification.ledOffMS)); - if (notification.actions != null && notification.actions.length > 0) { - pw.println(prefix + " actions={"); - final int N = notification.actions.length; - for (int i=0; i<N; i++) { - final Notification.Action action = notification.actions[i]; - pw.println(String.format("%s [%d] \"%s\" -> %s", - prefix, - i, - action.title, - action.actionIntent.toString() - )); - } - pw.println(prefix + " }"); - } - if (notification.extras != null && notification.extras.size() > 0) { - pw.println(prefix + " extras={"); - for (String key : notification.extras.keySet()) { - pw.print(prefix + " " + key + "="); - Object val = notification.extras.get(key); - if (val == null) { - pw.println("null"); - } else { - pw.print(val.getClass().getSimpleName()); - if (val instanceof CharSequence || val instanceof String) { - // redact contents from bugreports - } else if (val instanceof Bitmap) { - pw.print(String.format(" (%dx%d)", - ((Bitmap) val).getWidth(), - ((Bitmap) val).getHeight())); - } else if (val.getClass().isArray()) { - final int N = Array.getLength(val); - pw.println(" (" + N + ")"); - } else { - pw.print(" (" + String.valueOf(val) + ")"); - } - pw.println(); - } - } - pw.println(prefix + " }"); - } - pw.println(prefix + " stats=" + stats.toString()); - pw.println(prefix + " mContactAffinity=" + mContactAffinity); - pw.println(prefix + " mRecentlyIntrusive=" + mRecentlyIntrusive); - } - - @Override - public final String toString() { - return String.format( - "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d key=%s: %s)", - System.identityHashCode(this), - this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(), - this.sbn.getTag(), this.sbn.getScore(), this.sbn.getKey(), - this.sbn.getNotification()); - } - - public void setContactAffinity(float contactAffinity) { - mContactAffinity = contactAffinity; - } - - public float getContactAffinity() { - return mContactAffinity; - } - - public void setRecentlyIntusive(boolean recentlyIntrusive) { - mRecentlyIntrusive = recentlyIntrusive; - } - - public boolean isRecentlyIntrusive() { - return mRecentlyIntrusive; - } - } - private static final class ToastRecord { final int pid; @@ -1657,15 +1513,15 @@ public class NotificationManagerService extends SystemService { return; } - // Is this notification intercepted by zen mode? - final boolean intercept = mZenModeHelper.shouldIntercept(pkg, notification); - notification.extras.putBoolean(EXTRA_INTERCEPT, intercept); - - // Should this notification make noise, vibe, or use the LED? - final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD) && !intercept; - if (DBG || intercept) Slog.v(TAG, - "pkg=" + pkg + " canInterrupt=" + canInterrupt + " intercept=" + intercept); synchronized (mNotificationList) { + applyZenModeLocked(r); + + // Should this notification make noise, vibe, or use the LED? + final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD) && + !r.isIntercepted(); + if (DBG || r.isIntercepted()) Slog.v(TAG, + "pkg=" + pkg + " canInterrupt=" + canInterrupt + + " intercept=" + r.isIntercepted()); NotificationRecord old = null; int index = indexOfNotificationLocked(n.getKey()); if (index < 0) { @@ -1678,6 +1534,8 @@ public class NotificationManagerService extends SystemService { // Make sure we don't lose the foreground service state. notification.flags |= old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; + // Retain ranking information from previous record + r.copyRankingInformation(old); mNotificationsByKey.remove(old.sbn.getKey()); } mNotificationsByKey.put(n.getKey(), r); @@ -1724,7 +1582,7 @@ public class NotificationManagerService extends SystemService { sendAccessibilityEvent(notification, pkg); } - mListeners.notifyPostedLocked(r.sbn, cloneNotificationListLocked()); + mListeners.notifyPostedLocked(r.sbn); } else { Slog.e(TAG, "Not posting notification with icon==0: " + notification); if (old != null && !old.isCanceled) { @@ -1735,7 +1593,7 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } - mListeners.notifyRemovedLocked(r.sbn, cloneNotificationListLocked()); + mListeners.notifyRemovedLocked(r.sbn); } // ATTENTION: in a future release we will bail out here // so that we do not play sounds, show lights, etc. for invalid @@ -1992,23 +1850,32 @@ public class NotificationManagerService extends SystemService { if (!(message.obj instanceof RankingReconsideration)) return; RankingReconsideration recon = (RankingReconsideration) message.obj; recon.run(); - boolean orderChanged; + boolean changed; synchronized (mNotificationList) { final NotificationRecord record = mNotificationsByKey.get(recon.getKey()); if (record == null) { return; } - int before = findNotificationRecordIndexLocked(record); + int indexBefore = findNotificationRecordIndexLocked(record); + boolean interceptBefore = record.isIntercepted(); recon.applyChangesLocked(record); + applyZenModeLocked(record); Collections.sort(mNotificationList, mRankingComparator); - int after = findNotificationRecordIndexLocked(record); - orderChanged = before != after; + int indexAfter = findNotificationRecordIndexLocked(record); + boolean interceptAfter = record.isIntercepted(); + changed = indexBefore != indexAfter || interceptBefore != interceptAfter; } - if (orderChanged) { + if (changed) { scheduleSendRankingUpdate(); } } + // let zen mode evaluate this record and then make note of that for the future + private void applyZenModeLocked(NotificationRecord record) { + record.setIntercepted(mZenModeHelper.shouldIntercept(record, record.wasTouchedByZen())); + record.setTouchedByZen(); + } + // lock on mNotificationList private int findNotificationRecordIndexLocked(NotificationRecord target) { return Collections.binarySearch(mNotificationList, target, mRankingComparator); @@ -2022,19 +1889,10 @@ public class NotificationManagerService extends SystemService { private void handleSendRankingUpdate() { synchronized (mNotificationList) { - mListeners.notifyRankingUpdateLocked(cloneNotificationListLocked()); + mListeners.notifyRankingUpdateLocked(); } } - private ArrayList<StatusBarNotification> cloneNotificationListLocked() { - final int N = mNotificationList.size(); - ArrayList<StatusBarNotification> sbns = new ArrayList<StatusBarNotification>(N); - for (int i = 0; i < N; i++) { - sbns.add(mNotificationList.get(i).sbn); - } - return sbns; - } - private final class WorkerHandler extends Handler { @Override @@ -2120,7 +1978,7 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } r.isCanceled = true; - mListeners.notifyRemovedLocked(r.sbn, cloneNotificationListLocked()); + mListeners.notifyRemovedLocked(r.sbn); } // sound @@ -2441,22 +2299,25 @@ public class NotificationManagerService extends SystemService { /** * Generates a NotificationRankingUpdate from 'sbns', considering only * notifications visible to the given listener. + * + * <p>Caller must hold a lock on mNotificationList.</p> */ - private static NotificationRankingUpdate makeRankingUpdateForListener(ManagedServiceInfo info, - ArrayList<StatusBarNotification> sbns) { + private NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) { int speedBumpIndex = -1; - ArrayList<String> keys = new ArrayList<String>(sbns.size()); - ArrayList<String> dndKeys = new ArrayList<String>(sbns.size()); - for (StatusBarNotification sbn: sbns) { - if (!info.enabledAndUserMatches(sbn.getUserId())) { + final int N = mNotificationList.size(); + ArrayList<String> keys = new ArrayList<String>(N); + ArrayList<String> dndKeys = new ArrayList<String>(N); + for (int i = 0; i < N; i++) { + NotificationRecord record = mNotificationList.get(i); + if (!info.enabledAndUserMatches(record.sbn.getUserId())) { continue; } - keys.add(sbn.getKey()); - if (sbn.getNotification().extras.getBoolean(EXTRA_INTERCEPT)) { - dndKeys.add(sbn.getKey()); + keys.add(record.sbn.getKey()); + if (record.isIntercepted()) { + dndKeys.add(record.sbn.getKey()); } if (speedBumpIndex == -1 && - sbn.getNotification().priority == Notification.PRIORITY_MIN) { + record.sbn.getNotification().priority == Notification.PRIORITY_MIN) { speedBumpIndex = keys.size() - 1; } } @@ -2491,12 +2352,12 @@ public class NotificationManagerService extends SystemService { @Override public void onServiceAdded(ManagedServiceInfo info) { final INotificationListener listener = (INotificationListener) info.service; - final ArrayList<StatusBarNotification> sbns; + final NotificationRankingUpdate update; synchronized (mNotificationList) { - sbns = cloneNotificationListLocked(); + update = makeRankingUpdateLocked(info); } try { - listener.onListenerConnected(makeRankingUpdateForListener(info, sbns)); + listener.onListenerConnected(update); } catch (RemoteException e) { // we tried } @@ -2505,15 +2366,14 @@ public class NotificationManagerService extends SystemService { /** * asynchronously notify all listeners about a new notification */ - public void notifyPostedLocked(StatusBarNotification sbn, - final ArrayList<StatusBarNotification> sbns) { + public void notifyPostedLocked(StatusBarNotification sbn) { // make a copy in case changes are made to the underlying Notification object final StatusBarNotification sbnClone = sbn.clone(); for (final ManagedServiceInfo info : mServices) { if (!info.isEnabledForCurrentProfiles()) { continue; } - final NotificationRankingUpdate update = makeRankingUpdateForListener(info, sbns); + final NotificationRankingUpdate update = makeRankingUpdateLocked(info); if (update.getOrderedKeys().length == 0) { continue; } @@ -2529,8 +2389,7 @@ public class NotificationManagerService extends SystemService { /** * asynchronously notify all listeners about a removed notification */ - public void notifyRemovedLocked(StatusBarNotification sbn, - final ArrayList<StatusBarNotification> sbns) { + public void notifyRemovedLocked(StatusBarNotification sbn) { // make a copy in case changes are made to the underlying Notification object // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the // notification @@ -2539,11 +2398,11 @@ public class NotificationManagerService extends SystemService { if (!info.isEnabledForCurrentProfiles()) { continue; } + final NotificationRankingUpdate update = makeRankingUpdateLocked(info); mHandler.post(new Runnable() { @Override public void run() { - notifyRemovedIfUserMatch(info, sbnLight, - makeRankingUpdateForListener(info, sbns)); + notifyRemovedIfUserMatch(info, sbnLight, update); } }); } @@ -2551,20 +2410,18 @@ public class NotificationManagerService extends SystemService { /** * asynchronously notify all listeners about a reordering of notifications - * @param sbns an array of {@link StatusBarNotification}s to consider. This code - * must not rely on mutable members of these objects, such as the - * {@link Notification}. */ - public void notifyRankingUpdateLocked(final ArrayList<StatusBarNotification> sbns) { + public void notifyRankingUpdateLocked() { for (final ManagedServiceInfo serviceInfo : mServices) { if (!serviceInfo.isEnabledForCurrentProfiles()) { continue; } + final NotificationRankingUpdate update = + makeRankingUpdateLocked(serviceInfo); mHandler.post(new Runnable() { @Override public void run() { - notifyRankingUpdate(serviceInfo, - makeRankingUpdateForListener(serviceInfo, sbns)); + notifyRankingUpdate(serviceInfo, update); } }); } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java new file mode 100644 index 0000000..08f8eb4 --- /dev/null +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -0,0 +1,204 @@ +/* + * 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.server.notification; + +import android.app.Notification; +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.service.notification.StatusBarNotification; + +import java.io.PrintWriter; +import java.lang.reflect.Array; +import java.util.Arrays; + +/** + * Holds data about notifications that should not be shared with the + * {@link android.service.notification.NotificationListenerService}s. + * + * <p>These objects should not be mutated unless the code is synchronized + * on {@link NotificationManagerService#mNotificationList}, and any + * modification should be followed by a sorting of that list.</p> + * + * <p>Is sortable by {@link NotificationComparator}.</p> + * + * {@hide} + */ +public final class NotificationRecord { + final StatusBarNotification sbn; + NotificationUsageStats.SingleNotificationStats stats; + boolean isCanceled; + + // These members are used by NotificationSignalExtractors + // to communicate with the ranking module. + private float mContactAffinity; + private boolean mRecentlyIntrusive; + + // is this notification currently being intercepted by Zen Mode? + private boolean mIntercept; + // InterceptedNotifications needs to know if this has been previously evaluated. + private boolean mTouchedByZen; + + NotificationRecord(StatusBarNotification sbn) + { + this.sbn = sbn; + } + + // copy any notes that the ranking system may have made before the update + public void copyRankingInformation(NotificationRecord previous) { + mContactAffinity = previous.mContactAffinity; + mRecentlyIntrusive = previous.mRecentlyIntrusive; + mTouchedByZen = previous.mTouchedByZen; + mIntercept = previous.mIntercept; + } + + public Notification getNotification() { return sbn.getNotification(); } + public int getFlags() { return sbn.getNotification().flags; } + public int getUserId() { return sbn.getUserId(); } + public String getKey() { return sbn.getKey(); } + + void dump(PrintWriter pw, String prefix, Context baseContext) { + final Notification notification = sbn.getNotification(); + pw.println(prefix + this); + pw.println(prefix + " uid=" + sbn.getUid() + " userId=" + sbn.getUserId()); + pw.println(prefix + " icon=0x" + Integer.toHexString(notification.icon) + + " / " + idDebugString(baseContext, sbn.getPackageName(), notification.icon)); + pw.println(prefix + " pri=" + notification.priority + " score=" + sbn.getScore()); + pw.println(prefix + " key=" + sbn.getKey()); + pw.println(prefix + " contentIntent=" + notification.contentIntent); + pw.println(prefix + " deleteIntent=" + notification.deleteIntent); + pw.println(prefix + " tickerText=" + notification.tickerText); + pw.println(prefix + " contentView=" + notification.contentView); + pw.println(prefix + String.format(" defaults=0x%08x flags=0x%08x", + notification.defaults, notification.flags)); + pw.println(prefix + " sound=" + notification.sound); + pw.println(prefix + String.format(" color=0x%08x", notification.color)); + pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate)); + pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d", + notification.ledARGB, notification.ledOnMS, notification.ledOffMS)); + if (notification.actions != null && notification.actions.length > 0) { + pw.println(prefix + " actions={"); + final int N = notification.actions.length; + for (int i=0; i<N; i++) { + final Notification.Action action = notification.actions[i]; + pw.println(String.format("%s [%d] \"%s\" -> %s", + prefix, + i, + action.title, + action.actionIntent.toString() + )); + } + pw.println(prefix + " }"); + } + if (notification.extras != null && notification.extras.size() > 0) { + pw.println(prefix + " extras={"); + for (String key : notification.extras.keySet()) { + pw.print(prefix + " " + key + "="); + Object val = notification.extras.get(key); + if (val == null) { + pw.println("null"); + } else { + pw.print(val.getClass().getSimpleName()); + if (val instanceof CharSequence || val instanceof String) { + // redact contents from bugreports + } else if (val instanceof Bitmap) { + pw.print(String.format(" (%dx%d)", + ((Bitmap) val).getWidth(), + ((Bitmap) val).getHeight())); + } else if (val.getClass().isArray()) { + final int N = Array.getLength(val); + pw.println(" (" + N + ")"); + } else { + pw.print(" (" + String.valueOf(val) + ")"); + } + pw.println(); + } + } + pw.println(prefix + " }"); + } + pw.println(prefix + " stats=" + stats.toString()); + pw.println(prefix + " mContactAffinity=" + mContactAffinity); + pw.println(prefix + " mRecentlyIntrusive=" + mRecentlyIntrusive); + pw.println(prefix + " mIntercept=" + mIntercept); + } + + + static String idDebugString(Context baseContext, String packageName, int id) { + Context c; + + if (packageName != null) { + try { + c = baseContext.createPackageContext(packageName, 0); + } catch (NameNotFoundException e) { + c = baseContext; + } + } else { + c = baseContext; + } + + Resources r = c.getResources(); + try { + return r.getResourceName(id); + } catch (Resources.NotFoundException e) { + return "<name unknown>"; + } + } + + @Override + public final String toString() { + return String.format( + "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d key=%s: %s)", + System.identityHashCode(this), + this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(), + this.sbn.getTag(), this.sbn.getScore(), this.sbn.getKey(), + this.sbn.getNotification()); + } + + public void setContactAffinity(float contactAffinity) { + mContactAffinity = contactAffinity; + } + + public float getContactAffinity() { + return mContactAffinity; + } + + public void setRecentlyIntusive(boolean recentlyIntrusive) { + mRecentlyIntrusive = recentlyIntrusive; + } + + public boolean isRecentlyIntrusive() { + return mRecentlyIntrusive; + } + + public boolean setIntercepted(boolean intercept) { + mIntercept = intercept; + return mIntercept; + } + + public boolean isIntercepted() { + return mIntercept; + } + + public boolean wasTouchedByZen() { + return mTouchedByZen; + } + + public void setTouchedByZen() { + mTouchedByZen = true; + } + +} diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java index 71c819e..1537ea9 100644 --- a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java @@ -18,11 +18,9 @@ package com.android.server.notification; import android.content.Context; -import com.android.server.notification.NotificationManagerService.NotificationRecord; - /** * Extracts signals that will be useful to the {@link NotificationComparator} and caches them - * on the {@link NotificationManagerService.NotificationRecord} object. These annotations will + * on the {@link NotificationRecord} object. These annotations will * not be passed on to {@link android.service.notification.NotificationListenerService}s. */ public interface NotificationSignalExtractor { diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java index 66cc532..bab4895 100644 --- a/services/core/java/com/android/server/notification/NotificationUsageStats.java +++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java @@ -16,8 +16,6 @@ package com.android.server.notification; -import com.android.server.notification.NotificationManagerService.NotificationRecord; - import android.content.ContentValues; import android.content.Context; import android.database.Cursor; diff --git a/services/core/java/com/android/server/notification/RankingReconsideration.java b/services/core/java/com/android/server/notification/RankingReconsideration.java index cf5e210..057f0f1 100644 --- a/services/core/java/com/android/server/notification/RankingReconsideration.java +++ b/services/core/java/com/android/server/notification/RankingReconsideration.java @@ -15,8 +15,6 @@ */ package com.android.server.notification; -import com.android.server.notification.NotificationManagerService.NotificationRecord; - import java.util.concurrent.TimeUnit; /** diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index a629a5f..02f95e9 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -28,8 +28,6 @@ import android.text.TextUtils; import android.util.LruCache; import android.util.Slog; -import com.android.server.notification.NotificationManagerService.NotificationRecord; - import java.util.ArrayList; import java.util.LinkedList; @@ -49,9 +47,20 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { private static final int MAX_PEOPLE = 10; private static final int PEOPLE_CACHE_SIZE = 200; - private static final float NONE = 0f; - private static final float VALID_CONTACT = 0.5f; - private static final float STARRED_CONTACT = 1f; + /** Indicates that the notification does not reference any valid contacts. */ + static final float NONE = 0f; + + /** + * Affinity will be equal to or greater than this value on notifications + * that reference a valid contact. + */ + static final float VALID_CONTACT = 0.5f; + + /** + * Affinity will be equal to or greater than this value on notifications + * that reference a starred contact. + */ + static final float STARRED_CONTACT = 1f; protected boolean mEnabled; private Context mContext; diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 154ac96..50a32c4 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -18,7 +18,6 @@ package com.android.server.notification; import android.app.AlarmManager; import android.app.AppOpsManager; -import android.app.Notification; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -78,11 +77,13 @@ public class ZenModeHelper { // temporary, until we update apps to provide metadata private static final Set<String> CALL_PACKAGES = new HashSet<String>(Arrays.asList( "com.google.android.dialer", - "com.android.phone" + "com.android.phone", + "com.android.example.notificationshowcase" )); private static final Set<String> MESSAGE_PACKAGES = new HashSet<String>(Arrays.asList( "com.google.android.talk", - "com.android.mms" + "com.android.mms", + "com.android.example.notificationshowcase" )); private static final Set<String> ALARM_PACKAGES = new HashSet<String>(Arrays.asList( "com.google.android.deskclock" @@ -123,15 +124,23 @@ public class ZenModeHelper { mCallbacks.add(callback); } - public boolean shouldIntercept(String pkg, Notification n) { + public boolean shouldIntercept(NotificationRecord record, boolean previouslySeen) { if (mZenMode != Global.ZEN_MODE_OFF) { - if (isAlarm(pkg, n)) { + if (previouslySeen && !record.isIntercepted()) { + // notifications never transition from not intercepted to intercepted return false; } - if (isCall(pkg, n)) { + if (isAlarm(record)) { + return false; + } + // audience has veto power over all following rules + if (!audienceMatches(record)) { + return true; + } + if (isCall(record)) { return !mConfig.allowCalls; } - if (isMessage(pkg, n)) { + if (isMessage(record)) { return !mConfig.allowMessages; } return true; @@ -176,7 +185,8 @@ public class ZenModeHelper { } public boolean allowDisable(int what, IBinder token, String pkg) { - if (isCall(pkg, null)) { + // TODO(cwren): delete this API before the next release. Bug:15344099 + if (CALL_PACKAGES.contains(pkg)) { return mZenMode == Global.ZEN_MODE_OFF || mConfig.allowCalls; } return true; @@ -229,16 +239,30 @@ public class ZenModeHelper { } } - private boolean isAlarm(String pkg, Notification n) { - return ALARM_PACKAGES.contains(pkg); + private boolean isAlarm(NotificationRecord record) { + return ALARM_PACKAGES.contains(record.sbn.getPackageName()); } - private boolean isCall(String pkg, Notification n) { - return CALL_PACKAGES.contains(pkg); + private boolean isCall(NotificationRecord record) { + return CALL_PACKAGES.contains(record.sbn.getPackageName()); } - private boolean isMessage(String pkg, Notification n) { - return MESSAGE_PACKAGES.contains(pkg); + private boolean isMessage(NotificationRecord record) { + return MESSAGE_PACKAGES.contains(record.sbn.getPackageName()); + } + + private boolean audienceMatches(NotificationRecord record) { + switch (mConfig.allowFrom) { + case ZenModeConfig.SOURCE_ANYONE: + return true; + case ZenModeConfig.SOURCE_CONTACT: + return record.getContactAffinity() >= ValidateNotificationPeople.VALID_CONTACT; + case ZenModeConfig.SOURCE_STAR: + return record.getContactAffinity() >= ValidateNotificationPeople.STARRED_CONTACT; + default: + Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom); + return true; + } } private void updateAlarms() { diff --git a/services/core/java/com/android/server/task/StateChangedListener.java b/services/core/java/com/android/server/task/StateChangedListener.java index db2d4ee..b1a4636 100644 --- a/services/core/java/com/android/server/task/StateChangedListener.java +++ b/services/core/java/com/android/server/task/StateChangedListener.java @@ -27,9 +27,8 @@ public interface StateChangedListener { /** * Called by the controller to notify the TaskManager that it should check on the state of a * task. - * @param taskStatus The state of the task which has changed. */ - public void onTaskStateChanged(TaskStatus taskStatus); + public void onControllerStateChanged(); /** * Called by the controller to notify the TaskManager that regardless of the state of the task, diff --git a/services/core/java/com/android/server/task/TaskCompletedListener.java b/services/core/java/com/android/server/task/TaskCompletedListener.java index 0210442..c53f5ca 100644 --- a/services/core/java/com/android/server/task/TaskCompletedListener.java +++ b/services/core/java/com/android/server/task/TaskCompletedListener.java @@ -16,6 +16,8 @@ package com.android.server.task; +import com.android.server.task.controllers.TaskStatus; + /** * Used for communication between {@link com.android.server.task.TaskServiceContext} and the * {@link com.android.server.task.TaskManagerService}. @@ -26,13 +28,5 @@ public interface TaskCompletedListener { * Callback for when a task is completed. * @param needsReschedule Whether the implementing class should reschedule this task. */ - public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule); - - /** - * Callback for when the implementing class needs to clean up the - * {@link com.android.server.task.TaskServiceContext}. The scheduler can get this callback - * several times if the TaskServiceContext got into a bad state (for e.g. the client crashed - * and it needs to clean up). - */ - public void onAllTasksCompleted(int serviceToken); + public void onTaskCompleted(TaskStatus taskStatus, boolean needsReschedule); } diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java index 80030b4..d5b70e6 100644 --- a/services/core/java/com/android/server/task/TaskManagerService.java +++ b/services/core/java/com/android/server/task/TaskManagerService.java @@ -19,10 +19,12 @@ package com.android.server.task; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import android.app.task.ITaskManager; import android.app.task.Task; +import android.app.task.TaskManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; @@ -30,11 +32,17 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.RemoteException; -import android.os.UserHandle; +import android.os.SystemClock; import android.util.Slog; import android.util.SparseArray; +import com.android.server.task.controllers.ConnectivityController; +import com.android.server.task.controllers.IdleController; +import com.android.server.task.controllers.StateController; import com.android.server.task.controllers.TaskStatus; +import com.android.server.task.controllers.TimeController; + +import java.util.LinkedList; /** * Responsible for taking tasks representing work to be performed by a client app, and determining @@ -44,61 +52,148 @@ import com.android.server.task.controllers.TaskStatus; */ public class TaskManagerService extends com.android.server.SystemService implements StateChangedListener, TaskCompletedListener { + // TODO: Switch this off for final version. + private static final boolean DEBUG = true; + /** The number of concurrent tasks we run at one time. */ + private static final int MAX_TASK_CONTEXTS_COUNT = 3; static final String TAG = "TaskManager"; + /** + * When a task fails, it gets rescheduled according to its backoff policy. To be nice, we allow + * this amount of time from the rescheduled time by which the retry must occur. + */ + private static final long RESCHEDULE_WINDOW_SLOP_MILLIS = 5000L; /** Master list of tasks. */ private final TaskStore mTasks; + static final int MSG_TASK_EXPIRED = 0; + static final int MSG_CHECK_TASKS = 1; + + // Policy constants + /** + * Minimum # of idle tasks that must be ready in order to force the TM to schedule things + * early. + */ + private static final int MIN_IDLE_COUNT = 1; + /** + * Minimum # of connectivity tasks that must be ready in order to force the TM to schedule + * things early. + */ + private static final int MIN_CONNECTIVITY_COUNT = 2; + /** + * Minimum # of tasks (with no particular constraints) for which the TM will be happy running + * some work early. + */ + private static final int MIN_READY_TASKS_COUNT = 4; + /** * Track Services that have currently active or pending tasks. The index is provided by * {@link TaskStatus#getServiceToken()} */ - private final SparseArray<TaskServiceContext> mActiveServices = - new SparseArray<TaskServiceContext>(); + private final List<TaskServiceContext> mActiveServices = new LinkedList<TaskServiceContext>(); + /** List of controllers that will notify this service of updates to tasks. */ + private List<StateController> mControllers; + /** + * Queue of pending tasks. The TaskServiceContext class will receive tasks from this list + * when ready to execute them. + */ + private final LinkedList<TaskStatus> mPendingTasks = new LinkedList<TaskStatus>(); private final TaskHandler mHandler; private final TaskManagerStub mTaskManagerStub; - /** Check the pending queue and start any tasks. */ - static final int MSG_RUN_PENDING = 0; - /** Initiate the stop task flow. */ - static final int MSG_STOP_TASK = 1; - /** */ - static final int MSG_CHECK_TASKS = 2; - - private class TaskHandler extends Handler { + /** + * Entry point from client to schedule the provided task. + * This will add the task to the + * @param task Task object containing execution parameters + * @param uId The package identifier of the application this task is for. + * @param canPersistTask Whether or not the client has the appropriate permissions for persisting + * of this task. + * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes. + */ + public int schedule(Task task, int uId, boolean canPersistTask) { + TaskStatus taskStatus = new TaskStatus(task, uId, canPersistTask); + return startTrackingTask(taskStatus) ? + TaskManager.RESULT_SUCCESS : TaskManager.RESULT_FAILURE; + } - public TaskHandler(Looper looper) { - super(looper); + public List<Task> getPendingTasks(int uid) { + ArrayList<Task> outList = new ArrayList<Task>(); + synchronized (mTasks) { + for (TaskStatus ts : mTasks.getTasks()) { + if (ts.getUid() == uid) { + outList.add(ts.getTask()); + } + } } + return outList; + } - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MSG_RUN_PENDING: - - break; - case MSG_STOP_TASK: - - break; - case MSG_CHECK_TASKS: - checkTasks(); - break; + /** + * Entry point from client to cancel all tasks originating from their uid. + * This will remove the task from the master list, and cancel the task if it was staged for + * execution or being executed. + * @param uid To check against for removal of a task. + */ + public void cancelTaskForUid(int uid) { + // Remove from master list. + synchronized (mTasks) { + if (!mTasks.removeAllByUid(uid)) { + // If it's not in the master list, it's nowhere. + return; } } + // Remove from pending queue. + synchronized (mPendingTasks) { + Iterator<TaskStatus> it = mPendingTasks.iterator(); + while (it.hasNext()) { + TaskStatus ts = it.next(); + if (ts.getUid() == uid) { + it.remove(); + } + } + } + // Cancel if running. + synchronized (mActiveServices) { + for (TaskServiceContext tsc : mActiveServices) { + if (tsc.getRunningTask().getUid() == uid) { + tsc.cancelExecutingTask(); + } + } + } + } - /** - * Called when we need to run through the list of all tasks and start/stop executing one or - * more of them. - */ - private void checkTasks() { - synchronized (mTasks) { - final SparseArray<TaskStatus> tasks = mTasks.getTasks(); - for (int i = 0; i < tasks.size(); i++) { - TaskStatus ts = tasks.valueAt(i); - if (ts.isReady() && ! isCurrentlyActive(ts)) { - assignTaskToServiceContext(ts); - } + /** + * Entry point from client to cancel the task corresponding to the taskId provided. + * This will remove the task from the master list, and cancel the task if it was staged for + * execution or being executed. + * @param uid Uid of the calling client. + * @param taskId Id of the task, provided at schedule-time. + */ + public void cancelTask(int uid, int taskId) { + synchronized (mTasks) { + if (!mTasks.remove(uid, taskId)) { + // If it's not in the master list, it's nowhere. + return; + } + } + synchronized (mPendingTasks) { + Iterator<TaskStatus> it = mPendingTasks.iterator(); + while (it.hasNext()) { + TaskStatus ts = it.next(); + if (ts.getUid() == uid && ts.getTaskId() == taskId) { + it.remove(); + // If we got it from pending, it didn't make it to active so return. + return; + } + } + } + synchronized (mActiveServices) { + for (TaskServiceContext tsc : mActiveServices) { + if (tsc.getRunningTask().getUid() == uid && + tsc.getRunningTask().getTaskId() == taskId) { + tsc.cancelExecutingTask(); + return; } } } @@ -118,6 +213,17 @@ public class TaskManagerService extends com.android.server.SystemService mTasks = new TaskStore(context); mHandler = new TaskHandler(context.getMainLooper()); mTaskManagerStub = new TaskManagerStub(); + // Create the "runners". + for (int i = 0; i < MAX_TASK_CONTEXTS_COUNT; i++) { + mActiveServices.add( + new TaskServiceContext(this, context.getMainLooper())); + } + + mControllers = new LinkedList<StateController>(); + mControllers.add(ConnectivityController.get(this)); + mControllers.add(TimeController.get(this)); + mControllers.add(IdleController.get(this)); + // TODO: Add BatteryStateController when implemented. } @Override @@ -126,53 +232,128 @@ public class TaskManagerService extends com.android.server.SystemService } /** - * Entry point from client to schedule the provided task. - * This will add the task to the - * @param task Task object containing execution parameters - * @param userId The id of the user this task is for. - * @param uId The package identifier of the application this task is for. - * @param canPersistTask Whether or not the client has the appropriate permissions for - * persisting of this task. - * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes. + * Called when we have a task status object that we need to insert in our + * {@link com.android.server.task.TaskStore}, and make sure all the relevant controllers know + * about. */ - public int schedule(Task task, int userId, int uId, boolean canPersistTask) { - TaskStatus taskStatus = mTasks.addNewTaskForUser(task, userId, uId, canPersistTask); - return 0; + private boolean startTrackingTask(TaskStatus taskStatus) { + boolean added = false; + synchronized (mTasks) { + added = mTasks.add(taskStatus); + } + if (added) { + for (StateController controller : mControllers) { + controller.maybeStartTrackingTask(taskStatus); + } + } + return added; } - public List<Task> getPendingTasks(int uid) { - ArrayList<Task> outList = new ArrayList<Task>(3); + /** + * Called when we want to remove a TaskStatus object that we've finished executing. Returns the + * object removed. + */ + private boolean stopTrackingTask(TaskStatus taskStatus) { + boolean removed; synchronized (mTasks) { - final SparseArray<TaskStatus> tasks = mTasks.getTasks(); - final int N = tasks.size(); - for (int i = 0; i < N; i++) { - TaskStatus ts = tasks.get(i); - if (ts.getUid() == uid) { - outList.add(ts.getTask()); - } + // Remove from store as well as controllers. + removed = mTasks.remove(taskStatus); + } + if (removed) { + for (StateController controller : mControllers) { + controller.maybeStopTrackingTask(taskStatus); } } - return outList; + return removed; } - // StateChangedListener implementations. + private boolean cancelTaskOnServiceContext(TaskStatus ts) { + synchronized (mActiveServices) { + for (TaskServiceContext tsc : mActiveServices) { + if (tsc.getRunningTask() == ts) { + tsc.cancelExecutingTask(); + return true; + } + } + return false; + } + } /** - * Off-board work to our handler thread as quickly as possible, b/c this call is probably being - * made on the main thread. - * For now this takes the task and if it's ready to run it will run it. In future we might not - * provide the task, so that the StateChangedListener has to run through its list of tasks to - * see which are ready. This will further decouple the controllers from the execution logic. + * @param ts TaskStatus we are querying against. + * @return Whether or not the task represented by the status object is currently being run or + * is pending. */ - @Override - public void onTaskStateChanged(TaskStatus taskStatus) { - postCheckTasksMessage(); - + private boolean isCurrentlyActive(TaskStatus ts) { + synchronized (mActiveServices) { + for (TaskServiceContext serviceContext : mActiveServices) { + if (serviceContext.getRunningTask() == ts) { + return true; + } + } + return false; + } } - @Override - public void onTaskDeadlineExpired(TaskStatus taskStatus) { + /** + * A task is rescheduled with exponential back-off if the client requests this from their + * execution logic. + * A caveat is for idle-mode tasks, for which the idle-mode constraint will usurp the + * timeliness of the reschedule. For an idle-mode task, no deadline is given. + * @param failureToReschedule Provided task status that we will reschedule. + * @return A newly instantiated TaskStatus with the same constraints as the last task except + * with adjusted timing constraints. + */ + private TaskStatus getRescheduleTaskForFailure(TaskStatus failureToReschedule) { + final long elapsedNowMillis = SystemClock.elapsedRealtime(); + final Task task = failureToReschedule.getTask(); + + final long initialBackoffMillis = task.getInitialBackoffMillis(); + final int backoffAttempt = failureToReschedule.getNumFailures() + 1; + long newEarliestRuntimeElapsed = elapsedNowMillis; + + switch (task.getBackoffPolicy()) { + case Task.BackoffPolicy.LINEAR: + newEarliestRuntimeElapsed += initialBackoffMillis * backoffAttempt; + break; + default: + if (DEBUG) { + Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential."); + } + case Task.BackoffPolicy.EXPONENTIAL: + newEarliestRuntimeElapsed += Math.pow(initialBackoffMillis, backoffAttempt); + break; + } + long newLatestRuntimeElapsed = failureToReschedule.hasIdleConstraint() ? Long.MAX_VALUE + : newEarliestRuntimeElapsed + RESCHEDULE_WINDOW_SLOP_MILLIS; + return new TaskStatus(failureToReschedule, newEarliestRuntimeElapsed, + newLatestRuntimeElapsed, backoffAttempt); + } + /** + * Called after a periodic has executed so we can to re-add it. We take the last execution time + * of the task to be the time of completion (i.e. the time at which this function is called). + * This could be inaccurate b/c the task can run for as long as + * {@link com.android.server.task.TaskServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead + * to underscheduling at least, rather than if we had taken the last execution time to be the + * start of the execution. + * @return A new task representing the execution criteria for this instantiation of the + * recurring task. + */ + private TaskStatus getRescheduleTaskForPeriodic(TaskStatus periodicToReschedule) { + final long elapsedNow = SystemClock.elapsedRealtime(); + // Compute how much of the period is remaining. + long runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0); + long newEarliestRunTimeElapsed = elapsedNow + runEarly; + long period = periodicToReschedule.getTask().getIntervalMillis(); + long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period; + + if (DEBUG) { + Slog.v(TAG, "Rescheduling executed periodic. New execution window [" + + newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s"); + } + return new TaskStatus(periodicToReschedule, newEarliestRunTimeElapsed, + newLatestRuntimeElapsed, 0 /* backoffAttempt */); } // TaskCompletedListener implementations. @@ -181,54 +362,155 @@ public class TaskManagerService extends com.android.server.SystemService * A task just finished executing. We fetch the * {@link com.android.server.task.controllers.TaskStatus} from the store and depending on * whether we want to reschedule we readd it to the controllers. - * @param serviceToken key for the service context in {@link #mActiveServices}. - * @param taskId Id of the task that is complete. + * @param taskStatus Completed task. * @param needsReschedule Whether the implementing class should reschedule this task. */ @Override - public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule) { - final TaskServiceContext serviceContext = mActiveServices.get(serviceToken); - if (serviceContext == null) { - Slog.e(TAG, "Task completed for invalid service context; " + serviceToken); + public void onTaskCompleted(TaskStatus taskStatus, boolean needsReschedule) { + if (!stopTrackingTask(taskStatus)) { + if (DEBUG) { + Slog.e(TAG, "Error removing task: could not find task to remove. Was task" + + "removed while executing?"); + } return; } + if (needsReschedule) { + TaskStatus rescheduled = getRescheduleTaskForFailure(taskStatus); + startTrackingTask(rescheduled); + } else if (taskStatus.getTask().isPeriodic()) { + TaskStatus rescheduledPeriodic = getRescheduleTaskForPeriodic(taskStatus); + startTrackingTask(rescheduledPeriodic); + } + mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget(); + } + + // StateChangedListener implementations. + /** + * Off-board work to our handler thread as quickly as possible, b/c this call is probably being + * made on the main thread. + * For now this takes the task and if it's ready to run it will run it. In future we might not + * provide the task, so that the StateChangedListener has to run through its list of tasks to + * see which are ready. This will further decouple the controllers from the execution logic. + */ + @Override + public void onControllerStateChanged() { + // Post a message to to run through the list of tasks and start/stop any that are eligible. + mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget(); } @Override - public void onAllTasksCompleted(int serviceToken) { - + public void onTaskDeadlineExpired(TaskStatus taskStatus) { + mHandler.obtainMessage(MSG_TASK_EXPIRED, taskStatus); } - private void assignTaskToServiceContext(TaskStatus ts) { - TaskServiceContext serviceContext = - mActiveServices.get(ts.getServiceToken()); - if (serviceContext == null) { - serviceContext = new TaskServiceContext(this, mHandler.getLooper(), ts); - mActiveServices.put(ts.getServiceToken(), serviceContext); + private class TaskHandler extends Handler { + + public TaskHandler(Looper looper) { + super(looper); } - serviceContext.addPendingTask(ts); - } - /** - * @param ts TaskStatus we are querying against. - * @return Whether or not the task represented by the status object is currently being run or - * is pending. - */ - private boolean isCurrentlyActive(TaskStatus ts) { - TaskServiceContext serviceContext = mActiveServices.get(ts.getServiceToken()); - if (serviceContext == null) { - return false; + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MSG_TASK_EXPIRED: + final TaskStatus expired = (TaskStatus) message.obj; // Unused for now. + queueReadyTasksForExecutionH(); + break; + case MSG_CHECK_TASKS: + // Check the list of tasks and run some of them if we feel inclined. + maybeQueueReadyTasksForExecutionH(); + break; + } + maybeRunNextPendingTaskH(); + // Don't remove TASK_EXPIRED in case one came along while processing the queue. + removeMessages(MSG_CHECK_TASKS); } - return serviceContext.hasTaskPending(ts); - } - /** - * Post a message to {@link #mHandler} to run through the list of tasks and start/stop any that - * are eligible. - */ - private void postCheckTasksMessage() { - mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget(); + /** + * Run through list of tasks and execute all possible - at least one is expired so we do + * as many as we can. + */ + private void queueReadyTasksForExecutionH() { + synchronized (mTasks) { + for (TaskStatus ts : mTasks.getTasks()) { + final boolean criteriaSatisfied = ts.isReady(); + final boolean isRunning = isCurrentlyActive(ts); + if (criteriaSatisfied && !isRunning) { + synchronized (mPendingTasks) { + mPendingTasks.add(ts); + } + } else if (!criteriaSatisfied && isRunning) { + cancelTaskOnServiceContext(ts); + } + } + } + } + + /** + * The state of at least one task has changed. Here is where we could enforce various + * policies on when we want to execute tasks. + * Right now the policy is such: + * If >1 of the ready tasks is idle mode we send all of them off + * if more than 2 network connectivity tasks are ready we send them all off. + * If more than 4 tasks total are ready we send them all off. + * TODO: It would be nice to consolidate these sort of high-level policies somewhere. + */ + private void maybeQueueReadyTasksForExecutionH() { + synchronized (mTasks) { + int idleCount = 0; + int connectivityCount = 0; + List<TaskStatus> runnableTasks = new ArrayList<TaskStatus>(); + for (TaskStatus ts : mTasks.getTasks()) { + final boolean criteriaSatisfied = ts.isReady(); + final boolean isRunning = isCurrentlyActive(ts); + if (criteriaSatisfied && !isRunning) { + if (ts.hasIdleConstraint()) { + idleCount++; + } + if (ts.hasConnectivityConstraint() || ts.hasMeteredConstraint()) { + connectivityCount++; + } + runnableTasks.add(ts); + } else if (!criteriaSatisfied && isRunning) { + cancelTaskOnServiceContext(ts); + } + } + if (idleCount >= MIN_IDLE_COUNT || connectivityCount >= MIN_CONNECTIVITY_COUNT || + runnableTasks.size() >= MIN_READY_TASKS_COUNT) { + for (TaskStatus ts : runnableTasks) { + synchronized (mPendingTasks) { + mPendingTasks.add(ts); + } + } + } + } + } + + /** + * Checks the state of the pending queue against any available + * {@link com.android.server.task.TaskServiceContext} that can run a new task. + * {@link com.android.server.task.TaskServiceContext}. + */ + private void maybeRunNextPendingTaskH() { + TaskStatus nextPending; + synchronized (mPendingTasks) { + nextPending = mPendingTasks.poll(); + } + if (nextPending == null) { + return; + } + + synchronized (mActiveServices) { + for (TaskServiceContext tsc : mActiveServices) { + if (tsc.isAvailable()) { + if (tsc.executeRunnableTask(nextPending)) { + return; + } + } + } + } + } } /** @@ -268,11 +550,10 @@ public class TaskManagerService extends com.android.server.SystemService public int schedule(Task task) throws RemoteException { final boolean canPersist = canCallerPersistTasks(); final int uid = Binder.getCallingUid(); - final int userId = UserHandle.getCallingUserId(); long ident = Binder.clearCallingIdentity(); try { - return TaskManagerService.this.schedule(task, userId, uid, canPersist); + return TaskManagerService.this.schedule(task, uid, canPersist); } finally { Binder.restoreCallingIdentity(ident); } @@ -280,15 +561,38 @@ public class TaskManagerService extends com.android.server.SystemService @Override public List<Task> getAllPendingTasks() throws RemoteException { - return null; + final int uid = Binder.getCallingUid(); + + long ident = Binder.clearCallingIdentity(); + try { + return TaskManagerService.this.getPendingTasks(uid); + } finally { + Binder.restoreCallingIdentity(ident); + } } @Override public void cancelAll() throws RemoteException { + final int uid = Binder.getCallingUid(); + + long ident = Binder.clearCallingIdentity(); + try { + TaskManagerService.this.cancelTaskForUid(uid); + } finally { + Binder.restoreCallingIdentity(ident); + } } @Override public void cancel(int taskId) throws RemoteException { + final int uid = Binder.getCallingUid(); + + long ident = Binder.clearCallingIdentity(); + try { + TaskManagerService.this.cancelTask(uid, taskId); + } finally { + Binder.restoreCallingIdentity(ident); + } } /** @@ -311,9 +615,7 @@ public class TaskManagerService extends com.android.server.SystemService synchronized (mTasks) { pw.print("Registered tasks:"); if (mTasks.size() > 0) { - SparseArray<TaskStatus> tasks = mTasks.getTasks(); - for (int i = 0; i < tasks.size(); i++) { - TaskStatus ts = tasks.get(i); + for (TaskStatus ts : mTasks.getTasks()) { pw.println(); ts.dump(pw, " "); } diff --git a/services/core/java/com/android/server/task/TaskServiceContext.java b/services/core/java/com/android/server/task/TaskServiceContext.java index 165445a..75e9212 100644 --- a/services/core/java/com/android/server/task/TaskServiceContext.java +++ b/services/core/java/com/android/server/task/TaskServiceContext.java @@ -24,6 +24,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -36,20 +37,18 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.task.controllers.TaskStatus; import java.util.concurrent.atomic.AtomicBoolean; /** - * Maintains information required to bind to a {@link android.app.task.TaskService}. This binding - * is reused to start concurrent tasks on the TaskService. Information here is unique - * to the service. - * Functionality provided by this class: - * - Manages wakelock for the service. - * - Sends onStartTask() and onStopTask() messages to client app, and handles callbacks. - * - + * Handles client binding and lifecycle of a task. A task will only execute one at a time on an + * instance of this class. */ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceConnection { + private static final boolean DEBUG = true; private static final String TAG = "TaskServiceContext"; /** Define the maximum # of tasks allowed to run on a service at once. */ private static final int defaultMaxActiveTasksPerService = @@ -66,10 +65,10 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon }; // States that a task occupies while interacting with the client. - private static final int VERB_STARTING = 0; - private static final int VERB_EXECUTING = 1; - private static final int VERB_STOPPING = 2; - private static final int VERB_PENDING = 3; + static final int VERB_BINDING = 0; + static final int VERB_STARTING = 1; + static final int VERB_EXECUTING = 2; + static final int VERB_STOPPING = 3; // Messages that result from interactions with the client service. /** System timed out waiting for a response. */ @@ -77,178 +76,173 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon /** Received a callback from client. */ private static final int MSG_CALLBACK = 1; /** Run through list and start any ready tasks.*/ - private static final int MSG_CHECK_PENDING = 2; - /** Cancel an active task. */ + private static final int MSG_SERVICE_BOUND = 2; + /** Cancel a task. */ private static final int MSG_CANCEL = 3; - /** Add a pending task. */ - private static final int MSG_ADD_PENDING = 4; - /** Client crashed, so we need to wind things down. */ - private static final int MSG_SHUTDOWN = 5; - - /** Used to identify this task service context when communicating with the TaskManager. */ - final int token; - final ComponentName component; - final int userId; - ITaskService service; + /** Shutdown the Task. Used when the client crashes and we can't die gracefully.*/ + private static final int MSG_SHUTDOWN_EXECUTION = 4; + private final Handler mCallbackHandler; - /** Tasks that haven't been sent to the client for execution yet. */ - private final SparseArray<ActiveTask> mPending; + /** Make callbacks to {@link TaskManagerService} to inform on task completion status. */ + private final TaskCompletedListener mCompletedListener; /** Used for service binding, etc. */ private final Context mContext; - /** Make callbacks to {@link TaskManagerService} to inform on task completion status. */ - final private TaskCompletedListener mCompletedListener; - private final PowerManager.WakeLock mWakeLock; + private PowerManager.WakeLock mWakeLock; - /** Whether this service is actively bound. */ - boolean mBound; + // Execution state. + private TaskParams mParams; + @VisibleForTesting + int mVerb; + private AtomicBoolean mCancelled = new AtomicBoolean(); - TaskServiceContext(TaskManagerService taskManager, Looper looper, TaskStatus taskStatus) { - mContext = taskManager.getContext(); - this.component = taskStatus.getServiceComponent(); - this.token = taskStatus.getServiceToken(); - this.userId = taskStatus.getUserId(); - mCallbackHandler = new TaskServiceHandler(looper); - mPending = new SparseArray<ActiveTask>(); - mCompletedListener = taskManager; - final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - TM_WAKELOCK_PREFIX + component.getPackageName()); - mWakeLock.setWorkSource(new WorkSource(taskStatus.getUid())); - mWakeLock.setReferenceCounted(false); - } + /** All the information maintained about the task currently being executed. */ + private TaskStatus mRunningTask; + /** Binder to the client service. */ + ITaskService service; - @Override - public void taskFinished(int taskId, boolean reschedule) { - mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0) - .sendToTarget(); - } + private final Object mAvailableLock = new Object(); + /** Whether this context is free. */ + @GuardedBy("mAvailableLock") + private boolean mAvailable; - @Override - public void acknowledgeStopMessage(int taskId, boolean reschedule) { - mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0) - .sendToTarget(); + TaskServiceContext(TaskManagerService service, Looper looper) { + this(service.getContext(), service, looper); } - @Override - public void acknowledgeStartMessage(int taskId, boolean ongoing) { - mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, ongoing ? 1 : 0).sendToTarget(); + @VisibleForTesting + TaskServiceContext(Context context, TaskCompletedListener completedListener, Looper looper) { + mContext = context; + mCallbackHandler = new TaskServiceHandler(looper); + mCompletedListener = completedListener; } /** - * Queue up this task to run on the client. This will execute the task as quickly as possible. - * @param ts Status of the task to run. + * Give a task to this context for execution. Callers must first check {@link #isAvailable()} + * to make sure this is a valid context. + * @param ts The status of the task that we are going to run. + * @return True if the task was accepted and is going to run. */ - public void addPendingTask(TaskStatus ts) { - final TaskParams params = new TaskParams(ts.getTaskId(), ts.getExtras(), this); - final ActiveTask newTask = new ActiveTask(params, VERB_PENDING); - mCallbackHandler.obtainMessage(MSG_ADD_PENDING, newTask).sendToTarget(); - if (!mBound) { - Intent intent = new Intent().setComponent(component); - boolean binding = mContext.bindServiceAsUser(intent, this, - Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND, - new UserHandle(userId)); - if (!binding) { - Log.e(TAG, component.getShortClassName() + " unavailable."); - cancelPendingTask(ts); + boolean executeRunnableTask(TaskStatus ts) { + synchronized (mAvailableLock) { + if (!mAvailable) { + Slog.e(TAG, "Starting new runnable but context is unavailable > Error."); + return false; + } + mAvailable = false; + } + + final PowerManager pm = + (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + TM_WAKELOCK_PREFIX + ts.getServiceComponent().getPackageName()); + mWakeLock.setWorkSource(new WorkSource(ts.getUid())); + mWakeLock.setReferenceCounted(false); + + mRunningTask = ts; + mParams = new TaskParams(ts.getTaskId(), ts.getExtras(), this); + + mVerb = VERB_BINDING; + final Intent intent = new Intent().setComponent(ts.getServiceComponent()); + boolean binding = mContext.bindServiceAsUser(intent, this, + Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND, + new UserHandle(ts.getUserId())); + if (!binding) { + if (DEBUG) { + Slog.d(TAG, ts.getServiceComponent().getShortClassName() + " unavailable."); } + return false; } + + return true; } - /** - * Called externally when a task that was scheduled for execution should be cancelled. - * @param ts The status of the task to cancel. - */ - public void cancelPendingTask(TaskStatus ts) { - mCallbackHandler.obtainMessage(MSG_CANCEL, ts.getTaskId(), -1 /* arg2 */) - .sendToTarget(); + /** Used externally to query the running task. Will return null if there is no task running. */ + TaskStatus getRunningTask() { + return mRunningTask; + } + + /** Called externally when a task that was scheduled for execution should be cancelled. */ + void cancelExecutingTask() { + mCallbackHandler.obtainMessage(MSG_CANCEL).sendToTarget(); } /** - * MSG_TIMEOUT is sent with the {@link com.android.server.task.TaskServiceContext.ActiveTask} - * set in the {@link Message#obj} field. This makes it easier to remove timeouts for a given - * ActiveTask. - * @param op Operation that is taking place. + * @return Whether this context is available to handle incoming work. */ - private void scheduleOpTimeOut(ActiveTask op) { - mCallbackHandler.removeMessages(MSG_TIMEOUT, op); - - final long timeoutMillis = (op.verb == VERB_EXECUTING) ? - EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS; - if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) { - Slog.d(TAG, "Scheduling time out for '" + component.getShortClassName() + "' tId: " + - op.params.getTaskId() + ", in " + (timeoutMillis / 1000) + " s"); + boolean isAvailable() { + synchronized (mAvailableLock) { + return mAvailable; } - Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, op); - mCallbackHandler.sendMessageDelayed(m, timeoutMillis); } - /** - * @return true if this task is pending or active within this context. - */ - public boolean hasTaskPending(TaskStatus taskStatus) { - synchronized (mPending) { - return mPending.get(taskStatus.getTaskId()) != null; + @Override + public void taskFinished(int taskId, boolean reschedule) { + if (!verifyCallingUid()) { + return; + } + mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0) + .sendToTarget(); + } + + @Override + public void acknowledgeStopMessage(int taskId, boolean reschedule) { + if (!verifyCallingUid()) { + return; } + mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0) + .sendToTarget(); } - public boolean isBound() { - return mBound; + @Override + public void acknowledgeStartMessage(int taskId, boolean ongoing) { + if (!verifyCallingUid()) { + return; + } + mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, ongoing ? 1 : 0).sendToTarget(); } /** - * We acquire/release the wakelock on onServiceConnected/unbindService. This mirrors the work + * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work * we intend to send to the client - we stop sending work when the service is unbound so until * then we keep the wakelock. - * @param name The concrete component name of the service that has - * been connected. + * @param name The concrete component name of the service that has been connected. * @param service The IBinder of the Service's communication channel, */ @Override public void onServiceConnected(ComponentName name, IBinder service) { - mBound = true; + if (!name.equals(mRunningTask.getServiceComponent())) { + mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget(); + return; + } this.service = ITaskService.Stub.asInterface(service); - // Remove all timeouts. We've just connected to the client so there are no other - // MSG_TIMEOUTs at this point. + // Remove all timeouts. mCallbackHandler.removeMessages(MSG_TIMEOUT); mWakeLock.acquire(); - mCallbackHandler.obtainMessage(MSG_CHECK_PENDING).sendToTarget(); + mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget(); } /** - * When the client service crashes we can have a couple tasks executing, in various stages of - * undress. We'll cancel all of them and request that they be rescheduled. + * If the client service crashes we reschedule this task and clean up. * @param name The concrete component name of the service whose */ @Override public void onServiceDisconnected(ComponentName name) { - // Service disconnected... probably client crashed. - startShutdown(); + mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget(); } /** - * We don't just shutdown outright - we make sure the scheduler isn't going to send us any more - * tasks, then we do the shutdown. + * This class is reused across different clients, and passes itself in as a callback. Check + * whether the client exercising the callback is the client we expect. + * @return True if the binder calling is coming from the client we expect. */ - private void startShutdown() { - mCompletedListener.onAllTasksCompleted(token); - mCallbackHandler.obtainMessage(MSG_SHUTDOWN).sendToTarget(); - } - - /** Tracks a task across its various state changes. */ - private static class ActiveTask { - final TaskParams params; - int verb; - AtomicBoolean cancelled = new AtomicBoolean(); - - ActiveTask(TaskParams params, int verb) { - this.params = params; - this.verb = verb; - } - - @Override - public String toString() { - return params.getTaskId() + " " + VERB_STRINGS[verb]; + private boolean verifyCallingUid() { + if (mRunningTask == null || Binder.getCallingUid() != mRunningTask.getUid()) { + if (DEBUG) { + Slog.d(TAG, "Stale callback received, ignoring."); + } + return false; } + return true; } /** @@ -264,52 +258,67 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon @Override public void handleMessage(Message message) { switch (message.what) { - case MSG_ADD_PENDING: - if (message.obj != null) { - ActiveTask pendingTask = (ActiveTask) message.obj; - mPending.put(pendingTask.params.getTaskId(), pendingTask); - } - // fall through. - case MSG_CHECK_PENDING: - checkPendingTasksH(); + case MSG_SERVICE_BOUND: + handleServiceBoundH(); break; case MSG_CALLBACK: - ActiveTask receivedCallback = mPending.get(message.arg1); - removeMessages(MSG_TIMEOUT, receivedCallback); - - if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) { - Log.d(TAG, "MSG_CALLBACK of : " + receivedCallback); + if (DEBUG) { + Slog.d(TAG, "MSG_CALLBACK of : " + mRunningTask); } + removeMessages(MSG_TIMEOUT); - if (receivedCallback.verb == VERB_STARTING) { + if (mVerb == VERB_STARTING) { final boolean workOngoing = message.arg2 == 1; - handleStartedH(receivedCallback, workOngoing); - } else if (receivedCallback.verb == VERB_EXECUTING || - receivedCallback.verb == VERB_STOPPING) { + handleStartedH(workOngoing); + } else if (mVerb == VERB_EXECUTING || + mVerb == VERB_STOPPING) { final boolean reschedule = message.arg2 == 1; - handleFinishedH(receivedCallback, reschedule); + handleFinishedH(reschedule); } else { - if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) { - Log.d(TAG, "Unrecognised callback: " + receivedCallback); + if (DEBUG) { + Slog.d(TAG, "Unrecognised callback: " + mRunningTask); } } break; case MSG_CANCEL: - ActiveTask cancelled = mPending.get(message.arg1); - handleCancelH(cancelled); + handleCancelH(); break; case MSG_TIMEOUT: - // Timeout msgs have the ActiveTask ref so we can remove them easily. - handleOpTimeoutH((ActiveTask) message.obj); - break; - case MSG_SHUTDOWN: - handleShutdownH(); + handleOpTimeoutH(); break; + case MSG_SHUTDOWN_EXECUTION: + closeAndCleanupTaskH(true /* needsReschedule */); default: Log.e(TAG, "Unrecognised message: " + message); } } + /** Start the task on the service. */ + private void handleServiceBoundH() { + if (mVerb != VERB_BINDING) { + Slog.e(TAG, "Sending onStartTask for a task that isn't pending. " + + VERB_STRINGS[mVerb]); + closeAndCleanupTaskH(false /* reschedule */); + return; + } + if (mCancelled.get()) { + if (DEBUG) { + Slog.d(TAG, "Task cancelled while waiting for bind to complete. " + + mRunningTask); + } + closeAndCleanupTaskH(true /* reschedule */); + return; + } + try { + mVerb = VERB_STARTING; + scheduleOpTimeOut(); + service.startTask(mParams); + } catch (RemoteException e) { + Log.e(TAG, "Error sending onStart message to '" + + mRunningTask.getServiceComponent().getShortClassName() + "' ", e); + } + } + /** * State behaviours. * VERB_STARTING -> Successful start, change task to VERB_EXECUTING and post timeout. @@ -317,24 +326,25 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon * _EXECUTING -> Error * _STOPPING -> Error */ - private void handleStartedH(ActiveTask started, boolean workOngoing) { - switch (started.verb) { + private void handleStartedH(boolean workOngoing) { + switch (mVerb) { case VERB_STARTING: - started.verb = VERB_EXECUTING; + mVerb = VERB_EXECUTING; if (!workOngoing) { // Task is finished already so fast-forward to handleFinished. - handleFinishedH(started, false); + handleFinishedH(false); return; - } else if (started.cancelled.get()) { + } + if (mCancelled.get()) { // Cancelled *while* waiting for acknowledgeStartMessage from client. - handleCancelH(started); + handleCancelH(); return; - } else { - scheduleOpTimeOut(started); } + scheduleOpTimeOut(); break; default: - Log.e(TAG, "Handling started task but task wasn't starting! " + started); + Log.e(TAG, "Handling started task but task wasn't starting! Was " + + VERB_STRINGS[mVerb] + "."); return; } } @@ -345,155 +355,104 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon * _STARTING -> Error * _PENDING -> Error */ - private void handleFinishedH(ActiveTask executedTask, boolean reschedule) { - switch (executedTask.verb) { + private void handleFinishedH(boolean reschedule) { + switch (mVerb) { case VERB_EXECUTING: case VERB_STOPPING: - closeAndCleanupTaskH(executedTask, reschedule); + closeAndCleanupTaskH(reschedule); break; default: - Log.e(TAG, "Got an execution complete message for a task that wasn't being" + - "executed. " + executedTask); + Slog.e(TAG, "Got an execution complete message for a task that wasn't being" + + "executed. Was " + VERB_STRINGS[mVerb] + "."); } } /** * A task can be in various states when a cancel request comes in: - * VERB_PENDING -> Remove from queue. - * _STARTING -> Mark as cancelled and wait for {@link #acknowledgeStartMessage(int)}. + * VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for + * {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)} + * _STARTING -> Mark as cancelled and wait for + * {@link TaskServiceContext#acknowledgeStartMessage(int, boolean)} * _EXECUTING -> call {@link #sendStopMessageH}}. * _ENDING -> No point in doing anything here, so we ignore. */ - private void handleCancelH(ActiveTask cancelledTask) { - switch (cancelledTask.verb) { - case VERB_PENDING: - mPending.remove(cancelledTask.params.getTaskId()); - break; + private void handleCancelH() { + switch (mVerb) { + case VERB_BINDING: case VERB_STARTING: - cancelledTask.cancelled.set(true); + mCancelled.set(true); break; case VERB_EXECUTING: - cancelledTask.verb = VERB_STOPPING; - sendStopMessageH(cancelledTask); + sendStopMessageH(); break; case VERB_STOPPING: // Nada. break; default: - Log.e(TAG, "Cancelling a task without a valid verb: " + cancelledTask); + Slog.e(TAG, "Cancelling a task without a valid verb: " + mVerb); break; } } - /** - * This TaskServiceContext is shutting down. Remove all the tasks from the pending queue - * and reschedule them as if they had failed. - * Before posting this message, caller must invoke - * {@link com.android.server.task.TaskCompletedListener#onAllTasksCompleted(int)}. - */ - private void handleShutdownH() { - for (int i = 0; i < mPending.size(); i++) { - ActiveTask at = mPending.valueAt(i); - closeAndCleanupTaskH(at, true /* needsReschedule */); - } - mWakeLock.release(); - mContext.unbindService(TaskServiceContext.this); - service = null; - mBound = false; - } - - /** - * MSG_TIMEOUT gets processed here. - * @param timedOutTask The task that timed out. - */ - private void handleOpTimeoutH(ActiveTask timedOutTask) { + /** Process MSG_TIMEOUT here. */ + private void handleOpTimeoutH() { if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) { - Log.d(TAG, "MSG_TIMEOUT of " + component.getShortClassName() + " : " - + timedOutTask.params.getTaskId()); + Log.d(TAG, "MSG_TIMEOUT of " + + mRunningTask.getServiceComponent().getShortClassName() + " : " + + mParams.getTaskId()); } - final int taskId = timedOutTask.params.getTaskId(); - switch (timedOutTask.verb) { + final int taskId = mParams.getTaskId(); + switch (mVerb) { case VERB_STARTING: // Client unresponsive - wedged or failed to respond in time. We don't really // know what happened so let's log it and notify the TaskManager // FINISHED/NO-RETRY. Log.e(TAG, "No response from client for onStartTask '" + - component.getShortClassName() + "' tId: " + taskId); - closeAndCleanupTaskH(timedOutTask, false /* needsReschedule */); + mRunningTask.getServiceComponent().getShortClassName() + "' tId: " + + taskId); + closeAndCleanupTaskH(false /* needsReschedule */); break; case VERB_STOPPING: // At least we got somewhere, so fail but ask the TaskManager to reschedule. Log.e(TAG, "No response from client for onStopTask, '" + - component.getShortClassName() + "' tId: " + taskId); - closeAndCleanupTaskH(timedOutTask, true /* needsReschedule */); + mRunningTask.getServiceComponent().getShortClassName() + "' tId: " + + taskId); + closeAndCleanupTaskH(true /* needsReschedule */); break; case VERB_EXECUTING: // Not an error - client ran out of time. Log.i(TAG, "Client timed out while executing (no taskFinished received)." + " Reporting failure and asking for reschedule. " + - component.getShortClassName() + "' tId: " + taskId); - sendStopMessageH(timedOutTask); + mRunningTask.getServiceComponent().getShortClassName() + "' tId: " + + taskId); + sendStopMessageH(); break; default: Log.e(TAG, "Handling timeout for an unknown active task state: " - + timedOutTask); + + mRunningTask); return; } } /** - * Called on the handler thread. Checks the state of the pending queue and starts the task - * if it can. The task only starts if there is capacity on the service. - */ - private void checkPendingTasksH() { - if (!mBound) { - return; - } - for (int i = 0; i < mPending.size() && i < defaultMaxActiveTasksPerService; i++) { - ActiveTask at = mPending.valueAt(i); - if (at.verb != VERB_PENDING) { - continue; - } - sendStartMessageH(at); - } - } - - /** - * Already running, need to stop. Rund on handler. - * @param stoppingTask Task we are sending onStopMessage for. This task will be moved from - * VERB_EXECUTING -> VERB_STOPPING. + * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING -> + * VERB_STOPPING. */ - private void sendStopMessageH(ActiveTask stoppingTask) { - mCallbackHandler.removeMessages(MSG_TIMEOUT, stoppingTask); - if (stoppingTask.verb != VERB_EXECUTING) { - Log.e(TAG, "Sending onStopTask for a task that isn't started. " + stoppingTask); - // TODO: Handle error? + private void sendStopMessageH() { + mCallbackHandler.removeMessages(MSG_TIMEOUT); + if (mVerb != VERB_EXECUTING) { + Log.e(TAG, "Sending onStopTask for a task that isn't started. " + mRunningTask); + closeAndCleanupTaskH(false /* reschedule */); return; } try { - service.stopTask(stoppingTask.params); - stoppingTask.verb = VERB_STOPPING; - scheduleOpTimeOut(stoppingTask); + mVerb = VERB_STOPPING; + scheduleOpTimeOut(); + service.stopTask(mParams); } catch (RemoteException e) { Log.e(TAG, "Error sending onStopTask to client.", e); - closeAndCleanupTaskH(stoppingTask, false); - } - } - - /** Start the task on the service. */ - private void sendStartMessageH(ActiveTask pendingTask) { - if (pendingTask.verb != VERB_PENDING) { - Log.e(TAG, "Sending onStartTask for a task that isn't pending. " + pendingTask); - // TODO: Handle error? - } - try { - service.startTask(pendingTask.params); - pendingTask.verb = VERB_STARTING; - scheduleOpTimeOut(pendingTask); - } catch (RemoteException e) { - Log.e(TAG, "Error sending onStart message to '" + component.getShortClassName() - + "' ", e); + closeAndCleanupTaskH(false); } } @@ -503,13 +462,42 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon * or from acknowledging the stop message we sent. Either way, we're done tracking it and * we want to clean up internally. */ - private void closeAndCleanupTaskH(ActiveTask completedTask, boolean reschedule) { - removeMessages(MSG_TIMEOUT, completedTask); - mPending.remove(completedTask.params.getTaskId()); - if (mPending.size() == 0) { - startShutdown(); + private void closeAndCleanupTaskH(boolean reschedule) { + removeMessages(MSG_TIMEOUT); + mWakeLock.release(); + mContext.unbindService(TaskServiceContext.this); + mWakeLock = null; + + mRunningTask = null; + mParams = null; + mVerb = -1; + mCancelled.set(false); + + service = null; + + mCompletedListener.onTaskCompleted(mRunningTask, reschedule); + synchronized (mAvailableLock) { + mAvailable = true; + } + } + + /** + * Called when sending a message to the client, over whose execution we have no control. If we + * haven't received a response in a certain amount of time, we want to give up and carry on + * with life. + */ + private void scheduleOpTimeOut() { + mCallbackHandler.removeMessages(MSG_TIMEOUT); + + final long timeoutMillis = (mVerb == VERB_EXECUTING) ? + EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS; + if (DEBUG) { + Slog.d(TAG, "Scheduling time out for '" + + mRunningTask.getServiceComponent().getShortClassName() + "' tId: " + + mParams.getTaskId() + ", in " + (timeoutMillis / 1000) + " s"); } - mCompletedListener.onTaskCompleted(token, completedTask.params.getTaskId(), reschedule); + Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT); + mCallbackHandler.sendMessageDelayed(m, timeoutMillis); } } } diff --git a/services/core/java/com/android/server/task/TaskStore.java b/services/core/java/com/android/server/task/TaskStore.java index 81187c8..f72ab22 100644 --- a/services/core/java/com/android/server/task/TaskStore.java +++ b/services/core/java/com/android/server/task/TaskStore.java @@ -18,10 +18,16 @@ package com.android.server.task; import android.app.task.Task; import android.content.Context; +import android.util.ArraySet; +import android.util.Slog; import android.util.SparseArray; import com.android.server.task.controllers.TaskStatus; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + /** * Maintain a list of classes, and accessor methods/logic for these tasks. * This class offers the following functionality: @@ -35,76 +41,131 @@ import com.android.server.task.controllers.TaskStatus; * - This class is <strong>not</strong> thread-safe. */ public class TaskStore { - - /** - * Master list, indexed by {@link com.android.server.task.controllers.TaskStatus#hashCode()}. - */ - final SparseArray<TaskStatus> mTasks; + private static final String TAG = "TaskManagerStore"; + /** Threshold to adjust how often we want to write to the db. */ + private static final int MAX_OPS_BEFORE_WRITE = 1; + final ArraySet<TaskStatus> mTasks; final Context mContext; + private int mDirtyOperations; + TaskStore(Context context) { - mTasks = intialiseTaskMapFromDisk(); + mTasks = intialiseTasksFromDisk(); mContext = context; + mDirtyOperations = 0; } /** - * Add a task to the master list, persisting it if necessary. - * Will first check to see if the task already exists. If so, it will replace it. - * {@link android.content.pm.PackageManager} is queried to see if the calling package has - * permission to - * @param task Task to add. - * @return The initialised TaskStatus object if this operation was successful, null if it - * failed. + * Add a task to the master list, persisting it if necessary. If the TaskStatus already exists, + * it will be replaced. + * @param taskStatus Task to add. + * @return true if the operation succeeded. */ - public TaskStatus addNewTaskForUser(Task task, int userId, int uId, - boolean canPersistTask) { - TaskStatus taskStatus = TaskStatus.getForTaskAndUser(task, userId, uId); - if (canPersistTask && task.isPeriodic()) { - if (writeStatusToDisk()) { - mTasks.put(taskStatus.hashCode(), taskStatus); + public boolean add(TaskStatus taskStatus) { + if (taskStatus.isPersisted()) { + if (!maybeWriteStatusToDisk()) { + return false; } } - return taskStatus; + mTasks.remove(taskStatus); + mTasks.add(taskStatus); + return true; } - /** - * Remove the provided task. Will also delete the task if it was persisted. Note that this - * function does not return the validity of the operation, as we assume a delete will always - * succeed. - * @param task Task to remove. - */ - public void remove(Task task) { - + public int size() { + return mTasks.size(); } /** - * Every time the state changes we write all the tasks in one swathe, instead of trying to - * track incremental changes. + * Remove the provided task. Will also delete the task if it was persisted. + * @return The TaskStatus that was removed, or null if an invalid token was provided. */ - private boolean writeStatusToDisk() { + public boolean remove(TaskStatus taskStatus) { + boolean removed = mTasks.remove(taskStatus); + if (!removed) { + Slog.e(TAG, "Error removing task: " + taskStatus); + return false; + } else { + maybeWriteStatusToDisk(); + } return true; } /** - * - * @return + * Removes all TaskStatus objects for a given uid from the master list. Note that it is + * possible to remove a task that is pending/active. This operation will succeed, and the + * removal will take effect when the task has completed executing. + * @param uid Uid of the requesting app. + * @return True if at least one task was removed, false if nothing matching the provided uId + * was found. */ - // TODO: Implement this. - private SparseArray<TaskStatus> intialiseTaskMapFromDisk() { - return new SparseArray<TaskStatus>(); + public boolean removeAllByUid(int uid) { + Iterator<TaskStatus> it = mTasks.iterator(); + boolean removed = false; + while (it.hasNext()) { + TaskStatus ts = it.next(); + if (ts.getUid() == uid) { + it.remove(); + removed = true; + } + } + if (removed) { + maybeWriteStatusToDisk(); + } + return removed; } /** - * @return the number of tasks in the store + * Remove the TaskStatus that matches the provided uId and taskId. Note that it is possible + * to remove a task that is pending/active. This operation will succeed, and the removal will + * take effect when the task has completed executing. + * @param uid Uid of the requesting app. + * @param taskId Task id, specified at schedule-time. + * @return true if a removal occurred, false if the provided parameters didn't match anything. */ - public int size() { - return mTasks.size(); + public boolean remove(int uid, int taskId) { + Iterator<TaskStatus> it = mTasks.iterator(); + while (it.hasNext()) { + TaskStatus ts = it.next(); + if (ts.getUid() == uid && ts.getTaskId() == taskId) { + it.remove(); + maybeWriteStatusToDisk(); + return true; + } + } + return false; } /** * @return The live array of TaskStatus objects. */ - public SparseArray<TaskStatus> getTasks() { + public Set<TaskStatus> getTasks() { return mTasks; } + + /** + * Every time the state changes we write all the tasks in one swathe, instead of trying to + * track incremental changes. + * @return Whether the operation was successful. This will only fail for e.g. if the system is + * low on storage. If this happens, we continue as normal + */ + private boolean maybeWriteStatusToDisk() { + mDirtyOperations++; + if (mDirtyOperations > MAX_OPS_BEFORE_WRITE) { + for (TaskStatus ts : mTasks) { + // + } + mDirtyOperations = 0; + } + return true; + } + + /** + * + * @return + */ + // TODO: Implement this. + private ArraySet<TaskStatus> intialiseTasksFromDisk() { + return new ArraySet<TaskStatus>(); + } } diff --git a/services/core/java/com/android/server/task/controllers/ConnectivityController.java b/services/core/java/com/android/server/task/controllers/ConnectivityController.java index a0038c5..474af8f 100644 --- a/services/core/java/com/android/server/task/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/task/controllers/ConnectivityController.java @@ -25,6 +25,7 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.UserHandle; import android.util.Log; +import android.util.Slog; import com.android.server.task.TaskManagerService; @@ -32,21 +33,33 @@ import java.util.LinkedList; import java.util.List; /** - * + * Handles changes in connectivity. + * We are only interested in metered vs. unmetered networks, and we're interested in them on a + * per-user basis. */ public class ConnectivityController extends StateController { private static final String TAG = "TaskManager.Connectivity"; + private static final boolean DEBUG = true; private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>(); private final BroadcastReceiver mConnectivityChangedReceiver = new ConnectivityChangedReceiver(); + /** Singleton. */ + private static ConnectivityController mSingleton; /** Track whether the latest active network is metered. */ private boolean mMetered; /** Track whether the latest active network is connected. */ private boolean mConnectivity; - public ConnectivityController(TaskManagerService service) { + public static synchronized ConnectivityController get(TaskManagerService taskManager) { + if (mSingleton == null) { + mSingleton = new ConnectivityController(taskManager); + } + return mSingleton; + } + + private ConnectivityController(TaskManagerService service) { super(service); // Register connectivity changed BR. IntentFilter intentFilter = new IntentFilter(); @@ -56,7 +69,7 @@ public class ConnectivityController extends StateController { } @Override - public void maybeTrackTaskState(TaskStatus taskStatus) { + public synchronized void maybeStartTrackingTask(TaskStatus taskStatus) { if (taskStatus.hasConnectivityConstraint() || taskStatus.hasMeteredConstraint()) { taskStatus.connectivityConstraintSatisfied.set(mConnectivity); taskStatus.meteredConstraintSatisfied.set(mMetered); @@ -65,21 +78,28 @@ public class ConnectivityController extends StateController { } @Override - public void removeTaskStateIfTracked(TaskStatus taskStatus) { + public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) { mTrackedTasks.remove(taskStatus); } /** - * + * @param userId Id of the user for whom we are updating the connectivity state. */ - private void updateTrackedTasks() { + private void updateTrackedTasks(int userId) { + boolean changed = false; for (TaskStatus ts : mTrackedTasks) { + if (ts.getUserId() != userId) { + continue; + } boolean prevIsConnected = ts.connectivityConstraintSatisfied.getAndSet(mConnectivity); boolean prevIsMetered = ts.meteredConstraintSatisfied.getAndSet(mMetered); if (prevIsConnected != mConnectivity || prevIsMetered != mMetered) { - mStateChangedListener.onTaskStateChanged(ts); + changed = true; } } + if (changed) { + mStateChangedListener.onControllerStateChanged(); + } } class ConnectivityChangedReceiver extends BroadcastReceiver { @@ -103,17 +123,20 @@ public class ConnectivityController extends StateController { context.getSystemService(Context.CONNECTIVITY_SERVICE); final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo(); // This broadcast gets sent a lot, only update if the active network has changed. - if (activeNetwork.getType() == networkType) { + if (activeNetwork != null && activeNetwork.getType() == networkType) { + final int userid = context.getUserId(); mMetered = false; mConnectivity = !intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); if (mConnectivity) { // No point making the call if we know there's no conn. mMetered = connManager.isActiveNetworkMetered(); } - updateTrackedTasks(); + updateTrackedTasks(userid); } } else { - Log.w(TAG, "Unrecognised action in intent: " + action); + if (DEBUG) { + Slog.d(TAG, "Unrecognised action in intent: " + action); + } } } }; diff --git a/services/core/java/com/android/server/task/controllers/IdleController.java b/services/core/java/com/android/server/task/controllers/IdleController.java index a319a31..9489644 100644 --- a/services/core/java/com/android/server/task/controllers/IdleController.java +++ b/services/core/java/com/android/server/task/controllers/IdleController.java @@ -49,7 +49,7 @@ public class IdleController extends StateController { private static Object sCreationLock = new Object(); private static volatile IdleController sController; - public IdleController getController(TaskManagerService service) { + public static IdleController get(TaskManagerService service) { synchronized (sCreationLock) { if (sController == null) { sController = new IdleController(service); @@ -67,7 +67,7 @@ public class IdleController extends StateController { * StateController interface */ @Override - public void maybeTrackTaskState(TaskStatus taskStatus) { + public void maybeStartTrackingTask(TaskStatus taskStatus) { if (taskStatus.hasIdleConstraint()) { synchronized (mTrackedTasks) { mTrackedTasks.add(taskStatus); @@ -77,7 +77,7 @@ public class IdleController extends StateController { } @Override - public void removeTaskStateIfTracked(TaskStatus taskStatus) { + public void maybeStopTrackingTask(TaskStatus taskStatus) { synchronized (mTrackedTasks) { mTrackedTasks.remove(taskStatus); } @@ -90,9 +90,9 @@ public class IdleController extends StateController { synchronized (mTrackedTasks) { for (TaskStatus task : mTrackedTasks) { task.idleConstraintSatisfied.set(isIdle); - mStateChangedListener.onTaskStateChanged(task); } } + mStateChangedListener.onControllerStateChanged(); } /** diff --git a/services/core/java/com/android/server/task/controllers/StateController.java b/services/core/java/com/android/server/task/controllers/StateController.java index e1cd662..ed31eac 100644 --- a/services/core/java/com/android/server/task/controllers/StateController.java +++ b/services/core/java/com/android/server/task/controllers/StateController.java @@ -42,10 +42,10 @@ public abstract class StateController { * Also called when updating a task, so implementing controllers have to be aware of * preexisting tasks. */ - public abstract void maybeTrackTaskState(TaskStatus taskStatus); + public abstract void maybeStartTrackingTask(TaskStatus taskStatus); /** * Remove task - this will happen if the task is cancelled, completed, etc. */ - public abstract void removeTaskStateIfTracked(TaskStatus taskStatus); + public abstract void maybeStopTrackingTask(TaskStatus taskStatus); } diff --git a/services/core/java/com/android/server/task/controllers/TaskStatus.java b/services/core/java/com/android/server/task/controllers/TaskStatus.java index d270016..b7f84ec 100644 --- a/services/core/java/com/android/server/task/controllers/TaskStatus.java +++ b/services/core/java/com/android/server/task/controllers/TaskStatus.java @@ -18,7 +18,6 @@ package com.android.server.task.controllers; import android.app.task.Task; import android.content.ComponentName; -import android.content.pm.PackageParser; import android.os.Bundle; import android.os.SystemClock; import android.os.UserHandle; @@ -39,65 +38,67 @@ import java.util.concurrent.atomic.AtomicBoolean; */ public class TaskStatus { final Task task; - final int taskId; final int uId; - final Bundle extras; + /** At reschedule time we need to know whether to update task on disk. */ + final boolean persisted; + + // Constraints. final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean(); - final AtomicBoolean timeConstraintSatisfied = new AtomicBoolean(); + final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean(); + final AtomicBoolean deadlineConstraintSatisfied = new AtomicBoolean(); final AtomicBoolean idleConstraintSatisfied = new AtomicBoolean(); final AtomicBoolean meteredConstraintSatisfied = new AtomicBoolean(); final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean(); - private final boolean hasChargingConstraint; - private final boolean hasTimingConstraint; - private final boolean hasIdleConstraint; - private final boolean hasMeteredConstraint; - private final boolean hasConnectivityConstraint; - + /** + * Earliest point in the future at which this task will be eligible to run. A value of 0 + * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}. + */ private long earliestRunTimeElapsedMillis; + /** + * Latest point in the future at which this task must be run. A value of {@link Long#MAX_VALUE} + * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}. + */ private long latestRunTimeElapsedMillis; + private final int numFailures; + /** Provide a handle to the service that this task will be run on. */ public int getServiceToken() { return uId; } - /** Generate a TaskStatus object for a given task and uid. */ - // TODO: reimplement this to reuse these objects instead of creating a new one each time? - public static TaskStatus getForTaskAndUser(Task task, int userId, int uId) { - return new TaskStatus(task, userId, uId); - } - - /** Set up the state of a newly scheduled task. */ - TaskStatus(Task task, int userId, int uId) { + /** Create a newly scheduled task. */ + public TaskStatus(Task task, int uId, boolean persisted) { this.task = task; - this.taskId = task.getTaskId(); - this.extras = task.getExtras(); this.uId = uId; + this.numFailures = 0; + this.persisted = persisted; - hasChargingConstraint = task.isRequireCharging(); - hasIdleConstraint = task.isRequireDeviceIdle(); - + final long elapsedNow = SystemClock.elapsedRealtime(); // Timing constraints if (task.isPeriodic()) { - long elapsedNow = SystemClock.elapsedRealtime(); earliestRunTimeElapsedMillis = elapsedNow; latestRunTimeElapsedMillis = elapsedNow + task.getIntervalMillis(); - hasTimingConstraint = true; - } else if (task.getMinLatencyMillis() != 0L || task.getMaxExecutionDelayMillis() != 0L) { - earliestRunTimeElapsedMillis = task.getMinLatencyMillis() > 0L ? - task.getMinLatencyMillis() : Long.MAX_VALUE; - latestRunTimeElapsedMillis = task.getMaxExecutionDelayMillis() > 0L ? - task.getMaxExecutionDelayMillis() : Long.MAX_VALUE; - hasTimingConstraint = true; } else { - hasTimingConstraint = false; + earliestRunTimeElapsedMillis = task.hasEarlyConstraint() ? + elapsedNow + task.getMinLatencyMillis() : 0L; + latestRunTimeElapsedMillis = task.hasLateConstraint() ? + elapsedNow + task.getMaxExecutionDelayMillis() : Long.MAX_VALUE; } + } + + public TaskStatus(TaskStatus rescheduling, long newEarliestRuntimeElapsed, + long newLatestRuntimeElapsed, int backoffAttempt) { + this.task = rescheduling.task; - // Networking constraints - hasMeteredConstraint = task.getNetworkCapabilities() == Task.NetworkType.UNMETERED; - hasConnectivityConstraint = task.getNetworkCapabilities() == Task.NetworkType.ANY; + this.uId = rescheduling.getUid(); + this.persisted = rescheduling.isPersisted(); + this.numFailures = backoffAttempt; + + earliestRunTimeElapsedMillis = newEarliestRuntimeElapsed; + latestRunTimeElapsedMillis = newLatestRuntimeElapsed; } public Task getTask() { @@ -105,7 +106,11 @@ public class TaskStatus { } public int getTaskId() { - return taskId; + return task.getId(); + } + + public int getNumFailures() { + return numFailures; } public ComponentName getServiceComponent() { @@ -121,52 +126,60 @@ public class TaskStatus { } public Bundle getExtras() { - return extras; + return task.getExtras(); + } + + public boolean hasConnectivityConstraint() { + return task.getNetworkCapabilities() == Task.NetworkType.ANY; } - boolean hasConnectivityConstraint() { - return hasConnectivityConstraint; + public boolean hasMeteredConstraint() { + return task.getNetworkCapabilities() == Task.NetworkType.UNMETERED; } - boolean hasMeteredConstraint() { - return hasMeteredConstraint; + public boolean hasChargingConstraint() { + return task.isRequireCharging(); } - boolean hasChargingConstraint() { - return hasChargingConstraint; + public boolean hasTimingDelayConstraint() { + return earliestRunTimeElapsedMillis != 0L; } - boolean hasTimingConstraint() { - return hasTimingConstraint; + public boolean hasDeadlineConstraint() { + return latestRunTimeElapsedMillis != Long.MAX_VALUE; } - boolean hasIdleConstraint() { - return hasIdleConstraint; + public boolean hasIdleConstraint() { + return task.isRequireDeviceIdle(); } - long getEarliestRunTime() { + public long getEarliestRunTime() { return earliestRunTimeElapsedMillis; } - long getLatestRunTime() { + public long getLatestRunTimeElapsed() { return latestRunTimeElapsedMillis; } + public boolean isPersisted() { + return persisted; + } /** - * @return whether this task is ready to run, based on its requirements. + * @return Whether or not this task is ready to run, based on its requirements. */ public synchronized boolean isReady() { - return (!hasChargingConstraint || chargingConstraintSatisfied.get()) - && (!hasTimingConstraint || timeConstraintSatisfied.get()) - && (!hasConnectivityConstraint || connectivityConstraintSatisfied.get()) - && (!hasMeteredConstraint || meteredConstraintSatisfied.get()) - && (!hasIdleConstraint || idleConstraintSatisfied.get()); + return (!hasChargingConstraint() || chargingConstraintSatisfied.get()) + && (!hasTimingDelayConstraint() || timeDelayConstraintSatisfied.get()) + && (!hasConnectivityConstraint() || connectivityConstraintSatisfied.get()) + && (!hasMeteredConstraint() || meteredConstraintSatisfied.get()) + && (!hasIdleConstraint() || idleConstraintSatisfied.get()) + && (!hasDeadlineConstraint() || deadlineConstraintSatisfied.get()); } @Override public int hashCode() { int result = getServiceComponent().hashCode(); - result = 31 * result + taskId; + result = 31 * result + task.getId(); result = 31 * result + uId; return result; } @@ -177,14 +190,14 @@ public class TaskStatus { if (!(o instanceof TaskStatus)) return false; TaskStatus that = (TaskStatus) o; - return ((taskId == that.taskId) + return ((task.getId() == that.task.getId()) && (uId == that.uId) && (getServiceComponent().equals(that.getServiceComponent()))); } // Dumpsys infrastructure public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("Task "); pw.println(taskId); + pw.print(prefix); pw.print("Task "); pw.println(task.getId()); pw.print(prefix); pw.print("uid="); pw.println(uId); pw.print(prefix); pw.print("component="); pw.println(task.getService()); } diff --git a/services/core/java/com/android/server/task/controllers/TimeController.java b/services/core/java/com/android/server/task/controllers/TimeController.java index 6d97a53..72f312c 100644 --- a/services/core/java/com/android/server/task/controllers/TimeController.java +++ b/services/core/java/com/android/server/task/controllers/TimeController.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.SystemClock; -import android.util.Log; import com.android.server.task.TaskManagerService; @@ -54,8 +53,17 @@ public class TimeController extends StateController { private AlarmManager mAlarmService = null; /** List of tracked tasks, sorted asc. by deadline */ private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>(); + /** Singleton. */ + private static TimeController mSingleton; - public TimeController(TaskManagerService service) { + public static synchronized TimeController get(TaskManagerService taskManager) { + if (mSingleton == null) { + mSingleton = new TimeController(taskManager); + } + return mSingleton; + } + + private TimeController(TaskManagerService service) { super(service); mTaskExpiredAlarmIntent = PendingIntent.getBroadcast(mContext, 0 /* ignored */, @@ -75,8 +83,8 @@ public class TimeController extends StateController { * list. */ @Override - public synchronized void maybeTrackTaskState(TaskStatus task) { - if (task.hasTimingConstraint()) { + public synchronized void maybeStartTrackingTask(TaskStatus task) { + if (task.hasTimingDelayConstraint()) { ListIterator<TaskStatus> it = mTrackedTasks.listIterator(mTrackedTasks.size()); while (it.hasPrevious()) { TaskStatus ts = it.previous(); @@ -85,13 +93,13 @@ public class TimeController extends StateController { it.remove(); it.add(task); break; - } else if (ts.getLatestRunTime() < task.getLatestRunTime()) { + } else if (ts.getLatestRunTimeElapsed() < task.getLatestRunTimeElapsed()) { // Insert it.add(task); break; } } - maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTime()); + maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTimeElapsed()); } } @@ -100,12 +108,12 @@ public class TimeController extends StateController { * so, update them. */ @Override - public synchronized void removeTaskStateIfTracked(TaskStatus taskStatus) { + public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) { if (mTrackedTasks.remove(taskStatus)) { if (mNextDelayExpiredElapsedMillis <= taskStatus.getEarliestRunTime()) { handleTaskDelayExpired(); } - if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTime()) { + if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTimeElapsed()) { handleTaskDeadlineExpired(); } } @@ -140,10 +148,10 @@ public class TimeController extends StateController { * back and forth. */ private boolean canStopTrackingTask(TaskStatus taskStatus) { - final long elapsedNowMillis = SystemClock.elapsedRealtime(); - return taskStatus.timeConstraintSatisfied.get() && - (taskStatus.getLatestRunTime() == Long.MAX_VALUE || - taskStatus.getLatestRunTime() < elapsedNowMillis); + return (!taskStatus.hasTimingDelayConstraint() || + taskStatus.timeDelayConstraintSatisfied.get()) && + (!taskStatus.hasDeadlineConstraint() || + taskStatus.deadlineConstraintSatisfied.get()); } private void maybeUpdateAlarms(long delayExpiredElapsed, long deadlineExpiredElapsed) { @@ -174,10 +182,10 @@ public class TimeController extends StateController { Iterator<TaskStatus> it = mTrackedTasks.iterator(); while (it.hasNext()) { TaskStatus ts = it.next(); - final long taskDeadline = ts.getLatestRunTime(); + final long taskDeadline = ts.getLatestRunTimeElapsed(); if (taskDeadline <= nowElapsedMillis) { - ts.timeConstraintSatisfied.set(true); + ts.deadlineConstraintSatisfied.set(true); mStateChangedListener.onTaskDeadlineExpired(ts); it.remove(); } else { // Sorted by expiry time, so take the next one and stop. @@ -199,10 +207,12 @@ public class TimeController extends StateController { Iterator<TaskStatus> it = mTrackedTasks.iterator(); while (it.hasNext()) { final TaskStatus ts = it.next(); + if (!ts.hasTimingDelayConstraint()) { + continue; + } final long taskDelayTime = ts.getEarliestRunTime(); if (taskDelayTime < nowElapsedMillis) { - ts.timeConstraintSatisfied.set(true); - mStateChangedListener.onTaskStateChanged(ts); + ts.timeDelayConstraintSatisfied.set(true); if (canStopTrackingTask(ts)) { it.remove(); } @@ -212,6 +222,7 @@ public class TimeController extends StateController { } } } + mStateChangedListener.onControllerStateChanged(); maybeUpdateAlarms(nextDelayTime, Long.MAX_VALUE); } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 4f8b9d7..e007600 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -115,7 +115,7 @@ public class AppTransition implements Dump { private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.25f; private static final int DEFAULT_APP_TRANSITION_DURATION = 250; - private static final int THUMBNAIL_APP_TRANSITION_DURATION = 225; + private static final int THUMBNAIL_APP_TRANSITION_DURATION = 275; private final Context mContext; private final Handler mH; diff --git a/telecomm/java/android/telecomm/InCallService.java b/telecomm/java/android/telecomm/InCallService.java index 63b2020..3a63077 100644 --- a/telecomm/java/android/telecomm/InCallService.java +++ b/telecomm/java/android/telecomm/InCallService.java @@ -42,6 +42,7 @@ public abstract class InCallService extends Service { private static final int MSG_SET_POST_DIAL = 4; private static final int MSG_SET_POST_DIAL_WAIT = 5; private static final int MSG_ON_AUDIO_STATE_CHANGED = 6; + private static final int MSG_BRING_TO_FOREGROUND = 7; /** Default Handler used to consolidate binder method calls onto a single thread. */ private final Handler mHandler = new Handler(Looper.getMainLooper()) { @@ -83,6 +84,9 @@ public abstract class InCallService extends Service { case MSG_ON_AUDIO_STATE_CHANGED: onAudioStateChanged((CallAudioState) msg.obj); break; + case MSG_BRING_TO_FOREGROUND: + bringToForeground(msg.arg1 == 1); + break; default: break; } @@ -130,6 +134,12 @@ public abstract class InCallService extends Service { public void onAudioStateChanged(CallAudioState audioState) { mHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, audioState).sendToTarget(); } + + /** {@inheritDoc} */ + @Override + public void bringToForeground(boolean showDialpad) { + mHandler.obtainMessage(MSG_BRING_TO_FOREGROUND, showDialpad ? 1 : 0, 0).sendToTarget(); + } } private final InCallServiceBinder mBinder; @@ -206,4 +216,11 @@ public abstract class InCallService extends Service { * @param audioState The new {@link CallAudioState}. */ protected abstract void onAudioStateChanged(CallAudioState audioState); + + /** + * Brings the in-call screen to the foreground. + * + * @param showDialpad If true, put up the dialpad when the screen is shown. + */ + protected abstract void bringToForeground(boolean showDialpad); } diff --git a/telecomm/java/com/android/internal/telecomm/IInCallService.aidl b/telecomm/java/com/android/internal/telecomm/IInCallService.aidl index ccf7e3f..1635053 100644 --- a/telecomm/java/com/android/internal/telecomm/IInCallService.aidl +++ b/telecomm/java/com/android/internal/telecomm/IInCallService.aidl @@ -40,4 +40,6 @@ oneway interface IInCallService { void setPostDialWait(String callId, String remaining); void onAudioStateChanged(in CallAudioState audioState); + + void bringToForeground(boolean showDialpad); } diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl index c439211..0e94ffb 100644 --- a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl +++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl @@ -21,7 +21,7 @@ package com.android.internal.telecomm; * commands that were previously handled by ITelephony. * {@hide} */ -oneway interface ITelecommService { +interface ITelecommService { /** * Silence the ringer if an incoming call is currently ringing. @@ -31,4 +31,11 @@ oneway interface ITelecommService { * even if there's no incoming call. (If so, this method will do nothing.) */ void silenceRinger(); + + /** + * Brings the in-call screen to the foreground if there is an active call. + * + * @param showDialpad if true, make the dialpad visible initially. + */ + void showCallScreen(boolean showDialpad); } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 4aed1fe..525441d 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1980,9 +1980,10 @@ public class TelephonyManager { @PrivateApi public boolean showCallScreen() { try { - return getITelephony().showCallScreen(); + getTelecommService().showCallScreen(false); + return true; } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#showCallScreen", e); + Log.e(TAG, "Error calling ITelecommService#showCallScreen", e); } return false; } @@ -1991,9 +1992,10 @@ public class TelephonyManager { @PrivateApi public boolean showCallScreenWithDialpad(boolean showDialpad) { try { - return getITelephony().showCallScreenWithDialpad(showDialpad); + getTelecommService().showCallScreen(showDialpad); + return true; } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#showCallScreenWithDialpad", e); + Log.e(TAG, "Error calling ITelecommService#showCallScreen(" + showDialpad + ")", e); } return false; } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 6d7f158..acaa8de 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -49,28 +49,6 @@ interface ITelephony { void call(String callingPackage, String number); /** - * If there is currently a call in progress, show the call screen. - * The DTMF dialpad may or may not be visible initially, depending on - * whether it was up when the user last exited the InCallScreen. - * - * @return true if the call screen was shown. - */ - boolean showCallScreen(); - - /** - * Variation of showCallScreen() that also specifies whether the - * DTMF dialpad should be initially visible when the InCallScreen - * comes up. - * - * @param showDialpad if true, make the dialpad visible initially, - * otherwise hide the dialpad initially. - * @return true if the call screen was shown. - * - * @see showCallScreen - */ - boolean showCallScreenWithDialpad(boolean showDialpad); - - /** * End call if there is a call in progress, otherwise does nothing. * * @return whether it hung up diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml index 2d08163..e1a5854 100644 --- a/tests/VoiceInteraction/AndroidManifest.xml +++ b/tests/VoiceInteraction/AndroidManifest.xml @@ -23,7 +23,8 @@ android:permission="android.permission.BIND_VOICE_INTERACTION" android:process=":session"> </service> - <activity android:name="TestInteractionActivity" android:label="Voice Interaction Target"> + <activity android:name="TestInteractionActivity" android:label="Voice Interaction Target" + android:theme="@android:style/Theme.Quantum.Light.Voice"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java index 9c772ff..a61e0da 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java @@ -20,6 +20,8 @@ import android.app.Activity; import android.app.VoiceInteractor; import android.os.Bundle; import android.util.Log; +import android.view.Gravity; +import android.view.ViewGroup; public class TestInteractionActivity extends Activity { static final String TAG = "TestInteractionActivity"; @@ -38,6 +40,11 @@ public class TestInteractionActivity extends Activity { setContentView(R.layout.test_interaction); + // Framework should take care of these. + getWindow().setGravity(Gravity.TOP); + getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + mInteractor = getVoiceInteractor(); VoiceInteractor.ConfirmationRequest req = new VoiceInteractor.ConfirmationRequest( "This is a confirmation", null) { diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 1157de7..963117c 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -216,6 +216,18 @@ public class WifiConfiguration implements Parcelable { * <code>XX:XX:XX:XX:XX:XX</code> where each <code>X</code> is a hex digit. */ public String BSSID; + /** + * Fully qualified domain name (FQDN), for Passpoint credential. + * e.g. {@code "mail.example.com"}. + * @hide + */ + public String FQDN; + /** + * Network access identifier (NAI) realm, for Passpoint credential. + * e.g. {@code "myhost.example.com"}. + * @hide + */ + public String naiRealm; /** * Pre-shared key for use with WPA-PSK. @@ -484,6 +496,8 @@ public class WifiConfiguration implements Parcelable { networkId = INVALID_NETWORK_ID; SSID = null; BSSID = null; + FQDN = null; + naiRealm = null; priority = 0; hiddenSSID = false; disableReason = DISABLED_UNKNOWN_REASON; @@ -565,7 +579,8 @@ public class WifiConfiguration implements Parcelable { sbuf.append("- DSBLE: ").append(this.disableReason).append(" "); } sbuf.append("ID: ").append(this.networkId).append(" SSID: ").append(this.SSID). - append(" BSSID: ").append(this.BSSID).append(" PRIO: ").append(this.priority). + append(" BSSID: ").append(this.BSSID).append(" FQDN: ").append(this.FQDN). + append(" REALM: ").append(this.naiRealm).append(" PRIO: ").append(this.priority). append('\n'); sbuf.append(" KeyMgmt:"); for (int k = 0; k < this.allowedKeyManagement.size(); k++) { @@ -870,6 +885,8 @@ public class WifiConfiguration implements Parcelable { disableReason = source.disableReason; SSID = source.SSID; BSSID = source.BSSID; + FQDN = source.FQDN; + naiRealm = source.naiRealm; preSharedKey = source.preSharedKey; wepKeys = new String[4]; @@ -932,6 +949,8 @@ public class WifiConfiguration implements Parcelable { dest.writeInt(disableReason); dest.writeString(SSID); dest.writeString(BSSID); + dest.writeString(FQDN); + dest.writeString(naiRealm); dest.writeString(preSharedKey); for (String wepKey : wepKeys) { dest.writeString(wepKey); @@ -976,6 +995,8 @@ public class WifiConfiguration implements Parcelable { config.disableReason = in.readInt(); config.SSID = in.readString(); config.BSSID = in.readString(); + config.FQDN = in.readString(); + config.naiRealm = in.readString(); config.preSharedKey = in.readString(); for (int i = 0; i < config.wepKeys.length; i++) { config.wepKeys[i] = in.readString(); diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 141a69e..bf46745 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1413,12 +1413,14 @@ public class WifiManager { /** * Passed with {@link ActionListener#onFailure}. * Indicates that the operation failed due to an internal error. + * @hide */ public static final int ERROR = 0; /** * Passed with {@link ActionListener#onFailure}. * Indicates that the operation is already in progress + * @hide */ public static final int IN_PROGRESS = 1; @@ -1426,28 +1428,30 @@ public class WifiManager { * Passed with {@link ActionListener#onFailure}. * Indicates that the operation failed because the framework is busy and * unable to service the request + * @hide */ public static final int BUSY = 2; /* WPS specific errors */ - /** WPS overlap detected */ + /** WPS overlap detected {@hide} */ public static final int WPS_OVERLAP_ERROR = 3; - /** WEP on WPS is prohibited */ + /** WEP on WPS is prohibited {@hide} */ public static final int WPS_WEP_PROHIBITED = 4; - /** TKIP only prohibited */ + /** TKIP only prohibited {@hide} */ public static final int WPS_TKIP_ONLY_PROHIBITED = 5; - /** Authentication failure on WPS */ + /** Authentication failure on WPS {@hide} */ public static final int WPS_AUTH_FAILURE = 6; - /** WPS timed out */ + /** WPS timed out {@hide} */ public static final int WPS_TIMED_OUT = 7; /** * Passed with {@link ActionListener#onFailure}. * Indicates that the operation failed due to invalid inputs + * @hide */ public static final int INVALID_ARGS = 8; - /** Interface for callback invocation on an application action */ + /** Interface for callback invocation on an application action {@hide} */ public interface ActionListener { /** The operation succeeded */ public void onSuccess(); @@ -1459,7 +1463,7 @@ public class WifiManager { public void onFailure(int reason); } - /** Interface for callback invocation on a start WPS action */ + /** Interface for callback invocation on a start WPS action {@hide} */ public interface WpsListener { /** WPS start succeeded */ public void onStartSuccess(String pin); @@ -1741,6 +1745,7 @@ public class WifiManager { * @param listener for callbacks on success or failure. Can be null. * @throws IllegalStateException if the WifiManager instance needs to be * initialized again + * @hide */ public void startWps(WpsInfo config, WpsListener listener) { if (config == null) throw new IllegalArgumentException("config cannot be null"); @@ -1754,6 +1759,7 @@ public class WifiManager { * @param listener for callbacks on success or failure. Can be null. * @throws IllegalStateException if the WifiManager instance needs to be * initialized again + * @hide */ public void cancelWps(ActionListener listener) { validateChannel(); diff --git a/wifi/java/android/net/wifi/WpsInfo.java b/wifi/java/android/net/wifi/WpsInfo.java index ae2e771..2ad4ad0 100644 --- a/wifi/java/android/net/wifi/WpsInfo.java +++ b/wifi/java/android/net/wifi/WpsInfo.java @@ -40,7 +40,7 @@ public class WpsInfo implements Parcelable { /** Wi-Fi Protected Setup. www.wi-fi.org/wifi-protected-setup has details */ public int setup; - /** Passed with pin method KEYPAD */ + /** @hide */ public String BSSID; /** Passed with pin method configuration */ diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java index 08b430f..0769a64 100644 --- a/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java +++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java @@ -25,6 +25,10 @@ import java.util.Set; import java.util.Iterator; import java.util.Map; +/** + * A class representing a Wi-Fi Passpoint credential. + * @hide + */ public class WifiPasspointCredential implements Parcelable { private final static String TAG = "PasspointCredential"; diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java index ee4dc5a..e140c13 100644 --- a/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java +++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java @@ -35,6 +35,7 @@ import java.util.List; /** * Provides APIs for managing Wifi Passpoint credentials. + * @hide */ public class WifiPasspointManager { |