diff options
408 files changed, 16861 insertions, 8844 deletions
@@ -118,6 +118,7 @@ LOCAL_SRC_FILES += \ core/java/android/content/IIntentReceiver.aidl \ core/java/android/content/IIntentSender.aidl \ core/java/android/content/IOnPrimaryClipChangedListener.aidl \ + core/java/android/content/IRestrictionsManager.aidl \ core/java/android/content/ISyncAdapter.aidl \ core/java/android/content/ISyncContext.aidl \ core/java/android/content/ISyncServiceAdapter.aidl \ diff --git a/api/current.txt b/api/current.txt index 5c81416..b059841 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1017,6 +1017,7 @@ package android { field public static final int selectAllOnFocus = 16843102; // 0x101015e field public static final int selectable = 16843238; // 0x10101e6 field public static final int selectableItemBackground = 16843534; // 0x101030e + field public static final int selectableItemBackgroundBorderless = 16843871; // 0x101045f field public static final int selectedDateVerticalBar = 16843591; // 0x1010347 field public static final int selectedWeekBackgroundColor = 16843586; // 0x1010342 field public static final int sessionService = 16843841; // 0x1010441 @@ -1867,28 +1868,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 @@ -1935,11 +1936,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 @@ -2019,26 +2020,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 @@ -2327,126 +2330,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 @@ -3427,11 +3430,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(); @@ -5031,6 +5034,11 @@ package android.app { method public boolean[] supportsCommands(java.lang.String[]); } + public static class VoiceInteractor.AbortVoiceRequest extends android.app.VoiceInteractor.Request { + ctor public VoiceInteractor.AbortVoiceRequest(java.lang.CharSequence, android.os.Bundle); + method public void onAbortResult(android.os.Bundle); + } + public static class VoiceInteractor.CommandRequest extends android.app.VoiceInteractor.Request { ctor public VoiceInteractor.CommandRequest(java.lang.String, android.os.Bundle); method public void onCommandResult(android.os.Bundle); @@ -5046,7 +5054,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 { @@ -5220,6 +5230,7 @@ package android.app.admin { method public void setPasswordMinimumUpperCase(android.content.ComponentName, int); method public void setPasswordQuality(android.content.ComponentName, int); method public void setProfileEnabled(android.content.ComponentName); + method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName); method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public int setStorageEncryption(android.content.ComponentName, boolean); method public void wipeData(int); @@ -5566,8 +5577,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(); @@ -6210,74 +6221,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 @@ -6292,14 +6300,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 BluetoothLeScanFilter implements android.os.Parcelable { + 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 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(); @@ -6312,63 +6363,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 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 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 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 @@ -6377,56 +6419,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); } } @@ -6915,6 +6913,7 @@ package android.content { method public abstract java.io.File[] getExternalCacheDirs(); method public abstract java.io.File getExternalFilesDir(java.lang.String); method public abstract java.io.File[] getExternalFilesDirs(java.lang.String); + method public abstract java.io.File[] getExternalMediaDirs(); method public abstract java.io.File getFileStreamPath(java.lang.String); method public abstract java.io.File getFilesDir(); method public abstract android.os.Looper getMainLooper(); @@ -7027,6 +7026,7 @@ package android.content { field public static final java.lang.String NSD_SERVICE = "servicediscovery"; field public static final java.lang.String POWER_SERVICE = "power"; field public static final java.lang.String PRINT_SERVICE = "print"; + field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions"; field public static final java.lang.String SEARCH_SERVICE = "search"; field public static final java.lang.String SENSOR_SERVICE = "sensor"; field public static final java.lang.String STORAGE_SERVICE = "storage"; @@ -7040,7 +7040,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"; } @@ -7084,6 +7083,7 @@ package android.content { method public java.io.File[] getExternalCacheDirs(); method public java.io.File getExternalFilesDir(java.lang.String); method public java.io.File[] getExternalFilesDirs(java.lang.String); + method public java.io.File[] getExternalMediaDirs(); method public java.io.File getFileStreamPath(java.lang.String); method public java.io.File getFilesDir(); method public android.os.Looper getMainLooper(); @@ -7783,12 +7783,14 @@ package android.content { ctor public RestrictionEntry(java.lang.String, java.lang.String); ctor public RestrictionEntry(java.lang.String, boolean); ctor public RestrictionEntry(java.lang.String, java.lang.String[]); + ctor public RestrictionEntry(java.lang.String, int); ctor public RestrictionEntry(android.os.Parcel); method public int describeContents(); method public java.lang.String[] getAllSelectedStrings(); method public java.lang.String[] getChoiceEntries(); method public java.lang.String[] getChoiceValues(); method public java.lang.String getDescription(); + method public int getIntValue(); method public java.lang.String getKey(); method public boolean getSelectedState(); method public java.lang.String getSelectedString(); @@ -7800,6 +7802,7 @@ package android.content { method public void setChoiceValues(java.lang.String[]); method public void setChoiceValues(android.content.Context, int); method public void setDescription(java.lang.String); + method public void setIntValue(int); method public void setSelectedState(boolean); method public void setSelectedString(java.lang.String); method public void setTitle(java.lang.String); @@ -7808,10 +7811,36 @@ package android.content { field public static final android.os.Parcelable.Creator CREATOR; field public static final int TYPE_BOOLEAN = 1; // 0x1 field public static final int TYPE_CHOICE = 2; // 0x2 + field public static final int TYPE_INTEGER = 5; // 0x5 field public static final int TYPE_MULTI_SELECT = 4; // 0x4 field public static final int TYPE_NULL = 0; // 0x0 } + public class RestrictionsManager { + method public android.os.Bundle getApplicationRestrictions(); + method public java.util.List<android.content.RestrictionEntry> getManifestRestrictions(java.lang.String); + method public boolean hasRestrictionsProvider(); + method public void notifyPermissionResponse(java.lang.String, android.os.Bundle); + method public void requestPermission(java.lang.String, android.os.Bundle); + field public static final java.lang.String ACTION_PERMISSION_RESPONSE_RECEIVED = "android.intent.action.PERMISSION_RESPONSE_RECEIVED"; + field public static final java.lang.String ACTION_REQUEST_PERMISSION = "android.intent.action.REQUEST_PERMISSION"; + field public static final java.lang.String EXTRA_PACKAGE_NAME = "package_name"; + field public static final java.lang.String EXTRA_REQUEST_BUNDLE = "request_bundle"; + field public static final java.lang.String EXTRA_RESPONSE_BUNDLE = "response_bundle"; + field public static final java.lang.String EXTRA_TEMPLATE_ID = "template_id"; + field public static final java.lang.String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept"; + field public static final java.lang.String REQUEST_KEY_DATA = "android.req_template.data"; + field public static final java.lang.String REQUEST_KEY_DENY_LABEL = "android.req_template.reject"; + field public static final java.lang.String REQUEST_KEY_DEVICE_NAME = "android.req_template.device"; + field public static final java.lang.String REQUEST_KEY_ICON = "android.req_template.icon"; + field public static final java.lang.String REQUEST_KEY_ID = "android.req_template.req_id"; + field public static final java.lang.String REQUEST_KEY_MESSAGE = "android.req_template.mesg"; + field public static final java.lang.String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor"; + field public static final java.lang.String REQUEST_KEY_TITLE = "android.req_template.title"; + field public static final java.lang.String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple"; + field public static final java.lang.String RESPONSE_KEY_BOOLEAN = "android.req_template.response"; + } + public class SearchRecentSuggestionsProvider extends android.content.ContentProvider { ctor public SearchRecentSuggestionsProvider(); method public int delete(android.net.Uri, java.lang.String, java.lang.String[]); @@ -8200,7 +8229,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(); @@ -8211,21 +8240,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 { @@ -11477,6 +11506,7 @@ package android.graphics.drawable { } public class RippleDrawable extends android.graphics.drawable.LayerDrawable { + ctor public RippleDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable); } public class RotateDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { @@ -12725,6 +12755,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 { @@ -14296,7 +14329,6 @@ package android.media { method public final void release(); method public final void releaseOutputBuffer(int, boolean); method public final void releaseOutputBuffer(int, long); - method public void setNotificationCallback(android.media.MediaCodec.NotificationCallback); method public final void setParameters(android.os.Bundle); method public final void setVideoScalingMode(int); method public final void signalEndOfInputStream(); @@ -14346,10 +14378,6 @@ package android.media { field public int numSubSamples; } - public static abstract interface MediaCodec.NotificationCallback { - method public abstract void onCodecNotify(android.media.MediaCodec); - } - public final class MediaCodecInfo { method public final android.media.MediaCodecInfo.CodecCapabilities getCapabilitiesForType(java.lang.String); method public final java.lang.String getName(); @@ -14678,6 +14706,7 @@ package android.media { method public long getLong(java.lang.String); method public android.media.Rating getRating(java.lang.String); method public java.lang.String getString(java.lang.String); + method public java.util.Set<java.lang.String> keySet(); method public int size(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -15815,56 +15844,81 @@ package android.media.session { public final class MediaController { method public void addCallback(android.media.session.MediaController.Callback); method public void addCallback(android.media.session.MediaController.Callback, android.os.Handler); + method public boolean dispatchMediaButtonEvent(android.view.KeyEvent); method public static android.media.session.MediaController fromToken(android.media.session.MediaSessionToken); - method public android.media.session.TransportController getTransportController(); + method public android.media.MediaMetadata getMetadata(); + method public android.media.session.PlaybackState getPlaybackState(); + method public int getRatingType(); + method public android.media.session.MediaController.TransportControls getTransportControls(); method public void removeCallback(android.media.session.MediaController.Callback); - method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver); - method public void sendMediaButton(int); + method public void sendControlCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver); } public static abstract class MediaController.Callback { ctor public MediaController.Callback(); - method public void onEvent(java.lang.String, android.os.Bundle); + method public void onMetadataChanged(android.media.MediaMetadata); + method public void onPlaybackStateChanged(android.media.session.PlaybackState); + method public void onSessionEvent(java.lang.String, android.os.Bundle); + } + + public final class MediaController.TransportControls { + method public void fastForward(); + method public void pause(); + method public void play(); + method public void rewind(); + method public void seekTo(long); + method public void setRating(android.media.Rating); + method public void skipToNext(); + method public void skipToPrevious(); + method public void stop(); } public final class MediaSession { method public void addCallback(android.media.session.MediaSession.Callback); method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler); + method public void addTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback); + method public void addTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback, android.os.Handler); method public android.media.session.MediaSessionToken getSessionToken(); - method public android.media.session.TransportPerformer getTransportPerformer(); method public boolean isActive(); method public void release(); method public void removeCallback(android.media.session.MediaSession.Callback); - method public void sendEvent(java.lang.String, android.os.Bundle); + method public void removeTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback); + method public void sendSessionEvent(java.lang.String, android.os.Bundle); method public void setActive(boolean); method public void setFlags(int); method public void setLaunchPendingIntent(android.app.PendingIntent); - method public void useLocalPlayback(int); - method public void useRemotePlayback(android.media.session.RemoteVolumeProvider); + method public void setMetadata(android.media.MediaMetadata); + method public void setPlaybackState(android.media.session.PlaybackState); + method public void setPlaybackToLocal(int); + method public void setPlaybackToRemote(android.media.session.RemoteVolumeProvider); field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1 field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2 } public static abstract class MediaSession.Callback { ctor public MediaSession.Callback(); - method public void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver); - method public void onMediaButton(android.content.Intent); + method public void onControlCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver); + method public void onMediaButtonEvent(android.content.Intent); } - public final class MediaSessionInfo implements android.os.Parcelable { - method public int describeContents(); - method public java.lang.String getId(); - method public java.lang.String getPackageName(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; + public static abstract class MediaSession.TransportControlsCallback { + ctor public MediaSession.TransportControlsCallback(); + method public void onFastForward(); + method public void onPause(); + method public void onPlay(); + method public void onRewind(); + method public void onSeekTo(long); + method public void onSetRating(android.media.Rating); + method public void onSkipToNext(); + method public void onSkipToPrevious(); + method public void onStop(); } public final class MediaSessionManager { method public android.media.session.MediaSession createSession(java.lang.String); - method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName); } - public class MediaSessionToken implements android.os.Parcelable { + public final class MediaSessionToken implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -15876,95 +15930,50 @@ package android.media.session { method public int describeContents(); method public long getActions(); method public long getBufferPosition(); - method public java.lang.String getErrorMessage(); + method public java.lang.CharSequence getErrorMessage(); + method public float getPlaybackRate(); method public long getPosition(); - method public float getRate(); method public int getState(); method public void setActions(long); method public void setBufferPosition(long); - method public void setErrorMessage(java.lang.String); + method public void setErrorMessage(java.lang.CharSequence); method public void setState(int, long, float); method public void writeToParcel(android.os.Parcel, int); - field public static final long ACTION_FASTFORWARD = 64L; // 0x40L - field public static final long ACTION_NEXT_ITEM = 32L; // 0x20L + field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L field public static final long ACTION_PAUSE = 2L; // 0x2L field public static final long ACTION_PLAY = 4L; // 0x4L field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L - field public static final long ACTION_PREVIOUS_ITEM = 16L; // 0x10L - field public static final long ACTION_RATING = 128L; // 0x80L field public static final long ACTION_REWIND = 8L; // 0x8L field public static final long ACTION_SEEK_TO = 256L; // 0x100L + field public static final long ACTION_SET_RATING = 128L; // 0x80L + field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L + field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L field public static final long ACTION_STOP = 1L; // 0x1L field public static final android.os.Parcelable.Creator CREATOR; field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL - field public static final int PLAYSTATE_BUFFERING = 6; // 0x6 - field public static final int PLAYSTATE_ERROR = 7; // 0x7 - field public static final int PLAYSTATE_FAST_FORWARDING = 4; // 0x4 - field public static final int PLAYSTATE_NONE = 0; // 0x0 - field public static final int PLAYSTATE_PAUSED = 2; // 0x2 - field public static final int PLAYSTATE_PLAYING = 3; // 0x3 - field public static final int PLAYSTATE_REWINDING = 5; // 0x5 - field public static final int PLAYSTATE_SKIPPING_BACKWARDS = 9; // 0x9 - field public static final int PLAYSTATE_SKIPPING_FORWARDS = 10; // 0xa - field public static final int PLAYSTATE_STOPPED = 1; // 0x1 + field public static final int STATE_BUFFERING = 6; // 0x6 + field public static final int STATE_ERROR = 7; // 0x7 + field public static final int STATE_FAST_FORWARDING = 4; // 0x4 + field public static final int STATE_NONE = 0; // 0x0 + field public static final int STATE_PAUSED = 2; // 0x2 + field public static final int STATE_PLAYING = 3; // 0x3 + field public static final int STATE_REWINDING = 5; // 0x5 + field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa + field public static final int STATE_SKIPPING_TO_PREVIOUS = 9; // 0x9 + field public static final int STATE_STOPPED = 1; // 0x1 } public abstract class RemoteVolumeProvider { ctor public RemoteVolumeProvider(int, int); - method public abstract int getCurrentVolume(); - method public final int getFlags(); method public final int getMaxVolume(); + method public final int getVolumeControl(); method public final void notifyVolumeChanged(); - method public void onAdjustVolume(int); - method public void onSetVolume(int); - field public static final int FLAG_VOLUME_ABSOLUTE = 2; // 0x2 - field public static final int FLAG_VOLUME_RELATIVE = 1; // 0x1 - } - - public final class TransportController { - method public void addStateListener(android.media.session.TransportController.TransportStateListener); - method public void addStateListener(android.media.session.TransportController.TransportStateListener, android.os.Handler); - method public void fastForward(); - method public android.media.MediaMetadata getMetadata(); - method public android.media.session.PlaybackState getPlaybackState(); - method public int getRatingType(); - method public void next(); - method public void pause(); - method public void play(); - method public void previous(); - method public void rate(android.media.Rating); - method public void removeStateListener(android.media.session.TransportController.TransportStateListener); - method public void rewind(); - method public void seekTo(long); - method public void stop(); - } - - public static abstract class TransportController.TransportStateListener { - ctor public TransportController.TransportStateListener(); - method public void onMetadataChanged(android.media.MediaMetadata); - method public void onPlaybackStateChanged(android.media.session.PlaybackState); - } - - public final class TransportPerformer { - method public void addListener(android.media.session.TransportPerformer.Listener); - method public void addListener(android.media.session.TransportPerformer.Listener, android.os.Handler); - method public void removeListener(android.media.session.TransportPerformer.Listener); - method public final void setMetadata(android.media.MediaMetadata); - method public final void setPlaybackState(android.media.session.PlaybackState); - } - - public static abstract class TransportPerformer.Listener { - ctor public TransportPerformer.Listener(); - method public void onFastForward(); - method public void onNext(); - method public void onPause(); - method public void onPlay(); - method public void onPrevious(); - method public void onRate(android.media.Rating); - method public void onRewind(); - method public void onRouteFocusChange(int); - method public void onSeekTo(long); - method public void onStop(); + method public void onAdjustVolumeBy(int); + method public abstract int onGetCurrentVolume(); + method public void onSetVolumeTo(int); + field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2 + field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0 + field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1 } } @@ -17290,86 +17299,6 @@ package android.net.wifi { 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); - method public void retrieveScanResults(boolean, android.net.wifi.WifiScanner.ScanListener); - method public void setHotlist(android.net.wifi.WifiScanner.HotspotInfo[], int, android.net.wifi.WifiScanner.HotlistListener); - method public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener); - method public void startTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener); - method public void stopBackgroundScan(android.net.wifi.WifiScanner.ScanListener); - method public void stopTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener); - field public static final int MAX_SCAN_PERIOD_MS = 1024000; // 0xfa000 - field public static final int MIN_SCAN_PERIOD_MS = 2000; // 0x7d0 - field public static final int REASON_CONFLICTING_REQUEST = -4; // 0xfffffffc - field public static final int REASON_INVALID_LISTENER = -2; // 0xfffffffe - field public static final int REASON_INVALID_REQUEST = -3; // 0xfffffffd - field public static final int REASON_SUCCEEDED = 0; // 0x0 - field public static final int REASON_UNSPECIFIED = -1; // 0xffffffff - field public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0; // 0x0 - field public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1; // 0x1 - field public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2; // 0x2 - field public static final int WIFI_BAND_24_GHZ = 1; // 0x1 - field public static final int WIFI_BAND_5_GHZ = 2; // 0x2 - field public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; // 0x4 - field public static final int WIFI_BAND_5_GHZ_WITH_DFS = 6; // 0x6 - field public static final int WIFI_BAND_BOTH = 3; // 0x3 - field public static final int WIFI_BAND_BOTH_WITH_DFS = 7; // 0x7 - field public static final int WIFI_BAND_UNSPECIFIED = 0; // 0x0 - } - - public static class WifiScanner.ChannelSpec { - ctor public WifiScanner.ChannelSpec(int); - field public int frequency; - } - - public static class WifiScanner.FullScanResult implements android.os.Parcelable { - ctor public WifiScanner.FullScanResult(); - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public android.net.wifi.WifiScanner.InformationElement[] informationElements; - field public android.net.wifi.ScanResult result; - } - - public static abstract interface WifiScanner.HotlistListener { - method public abstract void onFound(android.net.wifi.ScanResult[]); - } - - public static class WifiScanner.HotspotInfo { - ctor public WifiScanner.HotspotInfo(); - field public java.lang.String bssid; - field public int frequencyHint; - field public int high; - field public int low; - } - - public static class WifiScanner.InformationElement { - ctor public WifiScanner.InformationElement(); - field public byte[] bytes; - field public int id; - } - - public static abstract interface WifiScanner.ScanListener { - method public abstract void onFullResult(android.net.wifi.WifiScanner.FullScanResult); - method public abstract void onPeriodChanged(int); - method public abstract void onResults(android.net.wifi.ScanResult[]); - } - - public static class WifiScanner.ScanSettings implements android.os.Parcelable { - ctor public WifiScanner.ScanSettings(); - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public int band; - field public android.net.wifi.WifiScanner.ChannelSpec[] channels; - field public int periodInMs; - field public int reportEvents; - } - - public static abstract interface WifiScanner.WifiChangeListener { - method public abstract void onChanging(android.net.wifi.ScanResult[]); - method public abstract void onQuiescence(android.net.wifi.ScanResult[]); - } - public class WpsInfo implements android.os.Parcelable { ctor public WpsInfo(); ctor public WpsInfo(android.net.wifi.WpsInfo); @@ -17583,29 +17512,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, java.lang.String, android.net.wifi.WifiEnterpriseConfig); - method public int describeContents(); - method public android.net.wifi.WifiEnterpriseConfig getEnterpriseConfig(); - method public java.lang.String getFqdn(); - method public java.lang.String getRealm(); - method public void setEnterpriseConfig(android.net.wifi.WifiEnterpriseConfig); - method public void setFqdn(java.lang.String); - method public void setRealm(java.lang.String); - 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 { @@ -17740,25 +17646,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"; @@ -20599,6 +20495,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; @@ -20727,17 +20654,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); @@ -20754,37 +20678,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[]); @@ -20796,30 +20704,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; @@ -20837,9 +20734,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); @@ -21419,46 +21313,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; @@ -21763,10 +21625,6 @@ package android.os.storage { method public boolean isObbMounted(java.lang.String); method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener); method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener); - field public static final int CRYPT_TYPE_DEFAULT = 1; // 0x1 - field public static final int CRYPT_TYPE_PASSWORD = 0; // 0x0 - field public static final int CRYPT_TYPE_PATTERN = 2; // 0x2 - field public static final int CRYPT_TYPE_PIN = 3; // 0x3 } } @@ -23513,6 +23371,13 @@ package android.provider { field public static final java.lang.String URL = "data1"; } + public static final class ContactsContract.ContactCounts { + ctor public ContactsContract.ContactCounts(); + field public static final java.lang.String ADDRESS_BOOK_INDEX_EXTRAS = "address_book_index_extras"; + field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "address_book_index_counts"; + field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "address_book_index_titles"; + } + protected static abstract interface ContactsContract.ContactNameColumns { field public static final java.lang.String DISPLAY_NAME_ALTERNATIVE = "display_name_alt"; field public static final java.lang.String DISPLAY_NAME_PRIMARY = "display_name"; @@ -26412,16 +26277,17 @@ package android.service.voice { method public android.view.LayoutInflater getLayoutInflater(); method public android.app.Dialog getWindow(); method public void hideWindow(); + method public void onAbortVoice(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle); method public void onBackPressed(); method public abstract void onCancel(android.service.voice.VoiceInteractionSession.Request); method public void onCloseSystemDialogs(); method public abstract void onCommand(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle); method public void onComputeInsets(android.service.voice.VoiceInteractionSession.Insets); - method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle); + method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle); method public void onCreate(android.os.Bundle); method public android.view.View onCreateContentView(); method public void onDestroy(); - method public abstract boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]); + method public boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]); method public boolean onKeyDown(int, android.view.KeyEvent); method public boolean onKeyLongPress(int, android.view.KeyEvent); method public boolean onKeyMultiple(int, int, android.view.KeyEvent); @@ -26448,6 +26314,7 @@ package android.service.voice { } public static class VoiceInteractionSession.Request { + method public void sendAbortVoiceResult(android.os.Bundle); method public void sendCancelResult(); method public void sendCommandResult(boolean, android.os.Bundle); method public void sendConfirmResult(boolean, android.os.Bundle); @@ -26602,6 +26469,28 @@ package android.speech { package android.speech.tts { + public final class Markup implements android.os.Parcelable { + ctor public Markup(); + ctor public Markup(java.lang.String); + ctor public Markup(android.speech.tts.Markup); + method public android.speech.tts.Markup addNestedMarkup(android.speech.tts.Markup); + method public int describeContents(); + method public android.speech.tts.Markup getNestedMarkup(int); + method public java.util.List<android.speech.tts.Markup> getNestedMarkups(); + method public java.lang.String getParameter(java.lang.String); + method public java.lang.String getPlainText(); + method public java.lang.String getType(); + method public static android.speech.tts.Markup markupFromString(java.lang.String) throws java.lang.IllegalArgumentException; + method public int nestedMarkupSize(); + method public int parametersSize(); + method public boolean removeNestedMarkup(android.speech.tts.Markup); + method public void removeParameter(java.lang.String); + method public android.speech.tts.Markup setParameter(java.lang.String, java.lang.String); + method public void setPlainText(java.lang.String); + method public void setType(java.lang.String); + method public void writeToParcel(android.os.Parcel, int); + } + public final class RequestConfig { method public android.os.Bundle getAudioParams(); method public android.speech.tts.VoiceInfo getVoice(); @@ -26664,9 +26553,10 @@ package android.speech.tts { } public final class SynthesisRequestV2 implements android.os.Parcelable { - ctor public SynthesisRequestV2(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle, android.os.Bundle); + ctor public SynthesisRequestV2(android.speech.tts.Markup, java.lang.String, java.lang.String, android.os.Bundle, android.os.Bundle); method public int describeContents(); method public android.os.Bundle getAudioParams(); + method public android.speech.tts.Markup getMarkup(); method public java.lang.String getText(); method public java.lang.String getUtteranceId(); method public java.lang.String getVoiceName(); @@ -26769,7 +26659,9 @@ package android.speech.tts { method public void queueAudio(android.net.Uri, android.speech.tts.TextToSpeechClient.UtteranceId, android.speech.tts.RequestConfig, android.speech.tts.TextToSpeechClient.RequestCallbacks); method public void queueSilence(long, android.speech.tts.TextToSpeechClient.UtteranceId, android.speech.tts.TextToSpeechClient.RequestCallbacks); method public void queueSpeak(java.lang.String, android.speech.tts.TextToSpeechClient.UtteranceId, android.speech.tts.RequestConfig, android.speech.tts.TextToSpeechClient.RequestCallbacks); + method public void queueSpeak(android.speech.tts.Markup, android.speech.tts.TextToSpeechClient.UtteranceId, android.speech.tts.RequestConfig, android.speech.tts.TextToSpeechClient.RequestCallbacks); method public void queueSynthesizeToFile(java.lang.String, android.speech.tts.TextToSpeechClient.UtteranceId, java.io.File, android.speech.tts.RequestConfig, android.speech.tts.TextToSpeechClient.RequestCallbacks); + method public void queueSynthesizeToFile(android.speech.tts.Markup, android.speech.tts.TextToSpeechClient.UtteranceId, java.io.File, android.speech.tts.RequestConfig, android.speech.tts.TextToSpeechClient.RequestCallbacks); method public void stop(); } @@ -26843,6 +26735,85 @@ package android.speech.tts { method protected void onVoicesInfoChange(); } + public class Utterance { + ctor public Utterance(); + method public android.speech.tts.Utterance append(android.speech.tts.Utterance.AbstractTts<? extends android.speech.tts.Utterance.AbstractTts<?>>); + method public android.speech.tts.Utterance append(java.lang.String); + method public android.speech.tts.Utterance append(int); + method public android.speech.tts.Markup createMarkup(); + method public android.speech.tts.Utterance.AbstractTts<? extends android.speech.tts.Utterance.AbstractTts<?>> get(int); + method public android.speech.tts.Utterance setNoWarningOnFallback(boolean); + method public int size(); + method public static android.speech.tts.Utterance utteranceFromString(java.lang.String) throws java.lang.IllegalArgumentException; + field public static final int ANIMACY_ANIMATE = 1; // 0x1 + field public static final int ANIMACY_INANIMATE = 2; // 0x2 + field public static final int ANIMACY_UNKNOWN = 0; // 0x0 + field public static final int CASE_ABLATIVE = 4; // 0x4 + field public static final int CASE_ACCUSATIVE = 2; // 0x2 + field public static final int CASE_DATIVE = 3; // 0x3 + field public static final int CASE_GENITIVE = 5; // 0x5 + field public static final int CASE_INSTRUMENTAL = 8; // 0x8 + field public static final int CASE_LOCATIVE = 7; // 0x7 + field public static final int CASE_NOMINATIVE = 1; // 0x1 + field public static final int CASE_UNKNOWN = 0; // 0x0 + field public static final int CASE_VOCATIVE = 6; // 0x6 + field public static final int GENDER_FEMALE = 3; // 0x3 + field public static final int GENDER_MALE = 2; // 0x2 + field public static final int GENDER_NEUTRAL = 1; // 0x1 + field public static final int GENDER_UNKNOWN = 0; // 0x0 + field public static final java.lang.String KEY_NO_WARNING_ON_FALLBACK = "no_warning_on_fallback"; + field public static final int MULTIPLICITY_DUAL = 2; // 0x2 + field public static final int MULTIPLICITY_PLURAL = 3; // 0x3 + field public static final int MULTIPLICITY_SINGLE = 1; // 0x1 + field public static final int MULTIPLICITY_UNKNOWN = 0; // 0x0 + field public static final java.lang.String TYPE_UTTERANCE = "utterance"; + } + + public static abstract class Utterance.AbstractTts { + ctor protected Utterance.AbstractTts(); + ctor protected Utterance.AbstractTts(android.speech.tts.Markup); + method public java.lang.String generatePlainText(); + method public android.speech.tts.Markup getMarkup(); + method protected java.lang.String getParameter(java.lang.String); + method public java.lang.String getPlainText(); + method public java.lang.String getType(); + method protected C removeParameter(java.lang.String); + method protected C setParameter(java.lang.String, java.lang.String); + method public C setPlainText(java.lang.String); + field protected android.speech.tts.Markup mMarkup; + } + + public static abstract class Utterance.AbstractTtsSemioticClass extends android.speech.tts.Utterance.AbstractTts { + ctor protected Utterance.AbstractTtsSemioticClass(); + ctor protected Utterance.AbstractTtsSemioticClass(android.speech.tts.Markup); + method public int getAnimacy(); + method public int getCase(); + method public int getGender(); + method public int getMultiplicity(); + method public C setAnimacy(int); + method public C setCase(int); + method public C setGender(int); + method public C setMultiplicity(int); + } + + public static class Utterance.TtsCardinal extends android.speech.tts.Utterance.AbstractTtsSemioticClass { + ctor public Utterance.TtsCardinal(); + ctor public Utterance.TtsCardinal(int); + ctor public Utterance.TtsCardinal(java.lang.String); + method public java.lang.String getInteger(); + method public android.speech.tts.Utterance.TtsCardinal setInteger(int); + method public android.speech.tts.Utterance.TtsCardinal setInteger(java.lang.String); + field protected static final java.lang.String TYPE_CARDINAL = "cardinal"; + } + + public static class Utterance.TtsText extends android.speech.tts.Utterance.AbstractTtsSemioticClass { + ctor public Utterance.TtsText(); + ctor public Utterance.TtsText(java.lang.String); + method public java.lang.String getText(); + method public android.speech.tts.Utterance.TtsText setText(java.lang.String); + field protected static final java.lang.String TYPE_TEXT = "text"; + } + public abstract class UtteranceProgressListener { ctor public UtteranceProgressListener(); method public abstract void onDone(java.lang.String); @@ -27604,6 +27575,7 @@ package android.telecomm { method public void setDisconnected(java.lang.String, int, java.lang.String); method public void setIsCompatibleWith(java.lang.String, boolean); method public void setOnHold(java.lang.String); + method public void setRequestingRingback(java.lang.String, boolean); method public void setRinging(java.lang.String); } @@ -27669,6 +27641,7 @@ package android.telecomm { ctor protected Connection(); method public final android.telecomm.CallAudioState getCallAudioState(); method public final android.net.Uri getHandle(); + method public boolean isRequestingRingback(); method protected void onAbort(); method protected void onAnswer(); method protected void onDisconnect(); @@ -27677,6 +27650,7 @@ package android.telecomm { method protected void onReject(); method protected void onSetAudioState(android.telecomm.CallAudioState); method protected void onSetSignal(android.os.Bundle); + method protected void onSetState(int); method protected void onStopDtmfTone(); method protected void onUnhold(); method protected void setActive(); @@ -27685,6 +27659,7 @@ package android.telecomm { method protected void setDisconnected(int, java.lang.String); method protected void setHandle(android.net.Uri); method protected void setOnHold(); + method protected void setRequestingRingback(boolean); method protected void setRinging(); method public static java.lang.String stateToString(int); } @@ -27694,6 +27669,7 @@ package android.telecomm { method public abstract void onDestroyed(android.telecomm.Connection); method public abstract void onDisconnected(android.telecomm.Connection, int, java.lang.String); method public abstract void onHandleChanged(android.telecomm.Connection, android.net.Uri); + method public abstract void onRequestingRingback(android.telecomm.Connection, boolean); method public abstract void onSignalChanged(android.telecomm.Connection, android.os.Bundle); method public abstract void onStateChanged(android.telecomm.Connection, int); } @@ -27704,6 +27680,7 @@ package android.telecomm { method public void onDestroyed(android.telecomm.Connection); method public void onDisconnected(android.telecomm.Connection, int, java.lang.String); method public void onHandleChanged(android.telecomm.Connection, android.net.Uri); + method public void onRequestingRingback(android.telecomm.Connection, boolean); method public void onSignalChanged(android.telecomm.Connection, android.os.Bundle); method public void onStateChanged(android.telecomm.Connection, int); } @@ -27784,6 +27761,7 @@ package android.telecomm { public abstract class InCallService extends android.app.Service { ctor protected InCallService(); method protected abstract void addCall(android.telecomm.InCallCall); + method protected abstract void bringToForeground(boolean); method protected final android.telecomm.InCallAdapter getAdapter(); method protected void onAdapterAttached(android.telecomm.InCallAdapter); method protected abstract void onAudioStateChanged(android.telecomm.CallAudioState); @@ -28783,6 +28761,7 @@ package android.test.mock { method public java.io.File[] getExternalCacheDirs(); method public java.io.File getExternalFilesDir(java.lang.String); method public java.io.File[] getExternalFilesDirs(java.lang.String); + method public java.io.File[] getExternalMediaDirs(); method public java.io.File getFileStreamPath(java.lang.String); method public java.io.File getFilesDir(); method public android.os.Looper getMainLooper(); @@ -38162,8 +38141,10 @@ package android.widget { method public void setOnMenuItemClickListener(android.widget.Toolbar.OnMenuItemClickListener); method public void setSubtitle(int); method public void setSubtitle(java.lang.CharSequence); + method public void setSubtitleTextAppearance(android.content.Context, int); method public void setTitle(int); method public void setTitle(java.lang.CharSequence); + method public void setTitleTextAppearance(android.content.Context, int); method public boolean showOverflowMenu(); } diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 6b55b7b..8945526 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -35,6 +35,7 @@ import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -46,6 +47,8 @@ import android.util.AndroidException; import android.view.IWindowManager; import com.android.internal.os.BaseCommand; +import dalvik.system.VMRuntime; + import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; @@ -94,7 +97,11 @@ public class Am extends BaseCommand { " am broadcast [--user <USER_ID> | all | current] <INTENT>\n" + " am instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]\n" + " [--user <USER_ID> | current]\n" + - " [--no-window-animation] <COMPONENT>\n" + + " [--no-window-animation]\n" + + " [--abi <ABI>]\n : Launch the instrumented process with the " + + " selected ABI. This assumes that the process supports the" + + " selected ABI." + + " <COMPONENT>\n" + " am profile start [--user <USER_ID> current] <PROCESS> <FILE>\n" + " am profile stop [--user <USER_ID> current] [<PROCESS>]\n" + " am dumpheap [--user <USER_ID> current] [-n] <PROCESS> <FILE>\n" + @@ -835,6 +842,7 @@ public class Am extends BaseCommand { Bundle args = new Bundle(); String argKey = null, argValue = null; IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); + String abi = null; String opt; while ((opt=nextOption()) != null) { @@ -853,6 +861,8 @@ public class Am extends BaseCommand { no_window_animation = true; } else if (opt.equals("--user")) { userId = parseUserArg(nextArgRequired()); + } else if (opt.equals("--abi")) { + abi = nextArgRequired(); } else { System.err.println("Error: Unknown option: " + opt); return; @@ -883,7 +893,24 @@ public class Am extends BaseCommand { wm.setAnimationScale(1, 0.0f); } - if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId)) { + if (abi != null) { + final String[] supportedAbis = Build.SUPPORTED_ABIS; + boolean matched = false; + for (String supportedAbi : supportedAbis) { + if (supportedAbi.equals(abi)) { + matched = true; + break; + } + } + + if (!matched) { + throw new AndroidException( + "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi); + } + } + + if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, + abi)) { throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString()); } diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index f05f4c7..d4c4318 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -26,6 +26,7 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.ActionMode; import android.view.Gravity; +import android.view.KeyEvent; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; @@ -1013,6 +1014,26 @@ public abstract class ActionBar { return null; } + /** @hide */ + public boolean openOptionsMenu() { + return false; + } + + /** @hide */ + public boolean invalidateOptionsMenu() { + return false; + } + + /** @hide */ + public boolean onMenuKeyEvent(KeyEvent event) { + return false; + } + + /** @hide */ + public boolean collapseActionView() { + return false; + } + /** * Listener interface for ActionBar navigation events. * diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index b5281ff..23b5f29 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; } @@ -2069,15 +2075,16 @@ public class Activity extends ContextThemeWrapper * <p>In order to use a Toolbar within the Activity's window content the application * must not request the window feature {@link Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p> * - * @param actionBar Toolbar to set as the Activity's action bar + * @param toolbar Toolbar to set as the Activity's action bar */ - public void setActionBar(@Nullable Toolbar actionBar) { + public void setActionBar(@Nullable Toolbar toolbar) { if (getActionBar() instanceof WindowDecorActionBar) { throw new IllegalStateException("This Activity already has an action bar supplied " + "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " + "android:windowActionBar to false in your theme to use a Toolbar instead."); } - mActionBar = new ToolbarActionBar(actionBar); + mActionBar = new ToolbarActionBar(toolbar, getTitle(), this); + mActionBar.invalidateOptionsMenu(); } /** @@ -2443,6 +2450,10 @@ public class Activity extends ContextThemeWrapper * but you can override this to do whatever you want. */ public void onBackPressed() { + if (mActionBar != null && mActionBar.collapseActionView()) { + return; + } + if (!mFragments.popBackStackImmediate()) { finishAfterTransition(); } @@ -2654,6 +2665,14 @@ public class Activity extends ContextThemeWrapper */ public boolean dispatchKeyEvent(KeyEvent event) { onUserInteraction(); + + // Let action bars open menus in response to the menu key prioritized over + // the window handling it + if (event.getKeyCode() == KeyEvent.KEYCODE_MENU && + mActionBar != null && mActionBar.onMenuKeyEvent(event)) { + return true; + } + Window win = getWindow(); if (win.superDispatchKeyEvent(event)) { return true; @@ -2901,7 +2920,9 @@ public class Activity extends ContextThemeWrapper * time it needs to be displayed. */ public void invalidateOptionsMenu() { - mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL); + if (mActionBar == null || !mActionBar.invalidateOptionsMenu()) { + mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL); + } } /** @@ -3111,7 +3132,9 @@ public class Activity extends ContextThemeWrapper * open, this method does nothing. */ public void openOptionsMenu() { - mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null); + if (mActionBar == null || !mActionBar.openOptionsMenu()) { + mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null); + } } /** @@ -5632,8 +5655,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 +5871,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/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 0f65454..56462ae 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -943,7 +943,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM b = data.readStrongBinder(); IUiAutomationConnection c = IUiAutomationConnection.Stub.asInterface(b); int userId = data.readInt(); - boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId); + String abiOverride = data.readString(); + boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId, + abiOverride); reply.writeNoException(); reply.writeInt(res ? 1 : 0); return true; @@ -3339,7 +3341,8 @@ class ActivityManagerProxy implements IActivityManager public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher, - IUiAutomationConnection connection, int userId) throws RemoteException { + IUiAutomationConnection connection, int userId, String instructionSet) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -3350,6 +3353,7 @@ class ActivityManagerProxy implements IActivityManager data.writeStrongBinder(watcher != null ? watcher.asBinder() : null); data.writeStrongBinder(connection != null ? connection.asBinder() : null); data.writeInt(userId); + data.writeString(instructionSet); mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0); reply.readException(); boolean res = reply.readInt() != 0; diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index d08978b..5e4ddd0 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -15,14 +15,23 @@ */ package android.app; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.os.Bundle; import android.os.Handler; import android.os.ResultReceiver; import android.transition.Transition; import android.transition.TransitionSet; import android.util.ArrayMap; +import android.util.Pair; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.view.Window; import android.widget.ImageView; @@ -181,6 +190,11 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { */ public static final int MSG_CANCEL = 106; + /** + * When returning, this is the destination location for the shared element. + */ + public static final int MSG_SHARED_ELEMENT_DESTINATION = 107; + final private Window mWindow; final protected ArrayList<String> mAllSharedElementNames; final protected ArrayList<View> mSharedElements = new ArrayList<View>(); @@ -334,6 +348,210 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { protected abstract Transition getViewsTransition(); + private static void setSharedElementState(View view, String name, Bundle transitionArgs, + int[] parentLoc) { + Bundle sharedElementBundle = transitionArgs.getBundle(name); + if (sharedElementBundle == null) { + return; + } + + if (view instanceof ImageView) { + int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1); + if (scaleTypeInt >= 0) { + ImageView imageView = (ImageView) view; + ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt]; + imageView.setScaleType(scaleType); + if (scaleType == ImageView.ScaleType.MATRIX) { + float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX); + Matrix matrix = new Matrix(); + matrix.setValues(matrixValues); + imageView.setImageMatrix(matrix); + } + } + } + + float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); + view.setTranslationZ(z); + + int x = sharedElementBundle.getInt(KEY_SCREEN_X); + int y = sharedElementBundle.getInt(KEY_SCREEN_Y); + int width = sharedElementBundle.getInt(KEY_WIDTH); + int height = sharedElementBundle.getInt(KEY_HEIGHT); + + int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); + int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); + view.measure(widthSpec, heightSpec); + + int left = x - parentLoc[0]; + int top = y - parentLoc[1]; + int right = left + width; + int bottom = top + height; + view.layout(left, top, right, bottom); + } + + protected ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState( + Bundle sharedElementState, final ArrayList<View> snapshots) { + ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState = + new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>(); + if (sharedElementState != null) { + int[] tempLoc = new int[2]; + for (int i = 0; i < mSharedElementNames.size(); i++) { + View sharedElement = mSharedElements.get(i); + String name = mSharedElementNames.get(i); + Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement, + name, sharedElementState); + if (originalState != null) { + originalImageState.put((ImageView) sharedElement, originalState); + } + View parent = (View) sharedElement.getParent(); + parent.getLocationOnScreen(tempLoc); + setSharedElementState(sharedElement, name, sharedElementState, tempLoc); + } + } + mListener.setSharedElementStart(mSharedElementNames, mSharedElements, snapshots); + + getDecor().getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + mListener.setSharedElementEnd(mSharedElementNames, mSharedElements, + snapshots); + return true; + } + } + ); + return originalImageState; + } + + private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name, + Bundle transitionArgs) { + if (!(view instanceof ImageView)) { + return null; + } + Bundle bundle = transitionArgs.getBundle(name); + if (bundle == null) { + return null; + } + int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1); + if (scaleTypeInt < 0) { + return null; + } + + ImageView imageView = (ImageView) view; + ImageView.ScaleType originalScaleType = imageView.getScaleType(); + + Matrix originalMatrix = null; + if (originalScaleType == ImageView.ScaleType.MATRIX) { + originalMatrix = new Matrix(imageView.getImageMatrix()); + } + + return Pair.create(originalScaleType, originalMatrix); + } + + protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) { + int numSharedElements = names.size(); + if (numSharedElements == 0) { + return null; + } + ArrayList<View> snapshots = new ArrayList<View>(numSharedElements); + Context context = getWindow().getContext(); + int[] parentLoc = new int[2]; + getDecor().getLocationOnScreen(parentLoc); + for (String name: names) { + Bundle sharedElementBundle = state.getBundle(name); + if (sharedElementBundle != null) { + Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP); + View snapshot = new View(context); + Resources resources = getWindow().getContext().getResources(); + if (bitmap != null) { + snapshot.setBackground(new BitmapDrawable(resources, bitmap)); + } + snapshot.setViewName(name); + setSharedElementState(snapshot, name, state, parentLoc); + snapshots.add(snapshot); + } + } + return snapshots; + } + + protected static void setOriginalImageViewState( + ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) { + for (int i = 0; i < originalState.size(); i++) { + ImageView imageView = originalState.keyAt(i); + Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i); + imageView.setScaleType(state.first); + imageView.setImageMatrix(state.second); + } + } + + protected Bundle captureSharedElementState() { + Bundle bundle = new Bundle(); + int[] tempLoc = new int[2]; + for (int i = 0; i < mSharedElementNames.size(); i++) { + View sharedElement = mSharedElements.get(i); + String name = mSharedElementNames.get(i); + captureSharedElementState(sharedElement, name, bundle, tempLoc); + } + return bundle; + } + + /** + * Captures placement information for Views with a shared element name for + * Activity Transitions. + * + * @param view The View to capture the placement information for. + * @param name The shared element name in the target Activity to apply the placement + * information for. + * @param transitionArgs Bundle to store shared element placement information. + * @param tempLoc A temporary int[2] for capturing the current location of views. + */ + private static void captureSharedElementState(View view, String name, Bundle transitionArgs, + int[] tempLoc) { + Bundle sharedElementBundle = new Bundle(); + view.getLocationOnScreen(tempLoc); + float scaleX = view.getScaleX(); + sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]); + int width = Math.round(view.getWidth() * scaleX); + sharedElementBundle.putInt(KEY_WIDTH, width); + + float scaleY = view.getScaleY(); + sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]); + int height = Math.round(view.getHeight() * scaleY); + sharedElementBundle.putInt(KEY_HEIGHT, height); + + sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); + + if (width > 0 && height > 0) { + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + sharedElementBundle.putParcelable(KEY_BITMAP, bitmap); + } + + if (view instanceof ImageView) { + ImageView imageView = (ImageView) view; + int scaleTypeInt = scaleTypeToInt(imageView.getScaleType()); + sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt); + if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) { + float[] matrix = new float[9]; + imageView.getImageMatrix().getValues(matrix); + sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix); + } + } + + transitionArgs.putBundle(name, sharedElementBundle); + } + + private static int scaleTypeToInt(ImageView.ScaleType scaleType) { + for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { + if (scaleType == SCALE_TYPE_VALUES[i]) { + return i; + } + } + return -1; + } + private static class FixedEpicenterCallback extends Transition.EpicenterCallback { private Rect mEpicenter; diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index ff8688d..4f335bb 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -35,7 +35,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IIntentReceiver; import android.content.IntentSender; +import android.content.IRestrictionsManager; import android.content.ReceiverCallNotAllowedException; +import android.content.RestrictionsManager; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; @@ -58,7 +60,9 @@ import android.hardware.ISerialManager; import android.hardware.SerialManager; import android.hardware.SystemSensorManager; import android.hardware.hdmi.HdmiCecManager; +import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.IHdmiCecService; +import android.hardware.hdmi.IHdmiControlService; import android.hardware.camera2.CameraManager; import android.hardware.display.DisplayManager; import android.hardware.input.InputManager; @@ -249,6 +253,8 @@ class ContextImpl extends Context { private File[] mExternalFilesDirs; @GuardedBy("mSync") private File[] mExternalCacheDirs; + @GuardedBy("mSync") + private File[] mExternalMediaDirs; private static final String[] EMPTY_FILE_LIST = {}; @@ -386,6 +392,11 @@ class ContextImpl extends Context { return new HdmiCecManager(IHdmiCecService.Stub.asInterface(b)); }}); + registerService(HDMI_CONTROL_SERVICE, new StaticServiceFetcher() { + public Object createStaticService() { + IBinder b = ServiceManager.getService(HDMI_CONTROL_SERVICE); + return new HdmiControlManager(IHdmiControlService.Stub.asInterface(b)); + }}); registerService(CLIPBOARD_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { @@ -653,6 +664,13 @@ class ContextImpl extends Context { } }); + registerService(RESTRICTIONS_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(RESTRICTIONS_SERVICE); + IRestrictionsManager service = IRestrictionsManager.Stub.asInterface(b); + return new RestrictionsManager(ctx, service); + } + }); registerService(PRINT_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE); @@ -1032,6 +1050,18 @@ class ContextImpl extends Context { } @Override + public File[] getExternalMediaDirs() { + synchronized (mSync) { + if (mExternalMediaDirs == null) { + mExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName()); + } + + // Create dirs if needed + return ensureDirsExistOrFilter(mExternalMediaDirs); + } + } + + @Override public File getFileStreamPath(String name) { return makeFilename(getFilesDir(), name); } @@ -1734,7 +1764,8 @@ class ContextImpl extends Context { arguments.setAllowFds(false); } return ActivityManagerNative.getDefault().startInstrumentation( - className, profileFile, 0, arguments, null, null, getUserId()); + className, profileFile, 0, arguments, null, null, getUserId(), + null /* ABI override */); } catch (RemoteException e) { // System has crashed, nothing we can do. } diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index bc97852..a8617b8 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -18,11 +18,7 @@ package android.app; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; import android.graphics.Matrix; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; @@ -38,7 +34,6 @@ import android.view.ViewTreeObserver; import android.widget.ImageView; import java.util.ArrayList; -import java.util.Collection; /** * This ActivityTransitionCoordinator is created by the Activity to manage @@ -56,6 +51,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private Handler mHandler; private boolean mIsCanceled; private ObjectAnimator mBackgroundAnimator; + private boolean mIsExitTransitionComplete; public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, ArrayList<String> sharedElementNames, @@ -76,6 +72,8 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } }; mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); + Bundle state = captureSharedElementState(); + mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); } } @@ -98,9 +96,8 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { break; case MSG_EXIT_TRANSITION_COMPLETE: if (!mIsCanceled) { - if (!mSharedElementTransitionStarted) { - send(resultCode, resultData); - } else { + mIsExitTransitionComplete = true; + if (mSharedElementTransitionStarted) { onRemoteExitTransitionComplete(); } } @@ -183,6 +180,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { setViewVisibility(mSharedElements, View.VISIBLE); ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState = setSharedElementState(sharedElementState, sharedElementSnapshots); + requestLayoutForSharedElements(); boolean startEnterTransition = allowOverlappingTransitions(); boolean startSharedElementTransition = true; @@ -200,6 +198,13 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { mResultReceiver = null; // all done sending messages. } + private void requestLayoutForSharedElements() { + int numSharedElements = mSharedElements.size(); + for (int i = 0; i < numSharedElements; i++) { + mSharedElements.get(i).requestLayout(); + } + } + private Transition beginTransition(boolean startEnterTransition, boolean startSharedElementTransition) { Transition sharedElementTransition = null; @@ -213,6 +218,19 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); + if (startSharedElementTransition) { + if (transition == null) { + sharedElementTransitionStarted(); + } else { + transition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionStart(Transition transition) { + transition.removeListener(this); + sharedElementTransitionStarted(); + } + }); + } + } if (transition != null) { TransitionManager.beginDelayedTransition(getDecor(), transition); if (startSharedElementTransition && !mSharedElementNames.isEmpty()) { @@ -224,6 +242,13 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { return transition; } + private void sharedElementTransitionStarted() { + mSharedElementTransitionStarted = true; + if (mIsExitTransitionComplete) { + send(MSG_EXIT_TRANSITION_COMPLETE, null); + } + } + private void startEnterTransition(Transition transition) { setViewVisibility(mTransitioningViews, View.VISIBLE); if (!mIsReturning) { @@ -310,142 +335,4 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { startEnterTransition(transition); } } - - private ArrayList<View> createSnapshots(Bundle state, Collection<String> names) { - int numSharedElements = names.size(); - if (numSharedElements == 0) { - return null; - } - ArrayList<View> snapshots = new ArrayList<View>(numSharedElements); - Context context = getWindow().getContext(); - int[] parentLoc = new int[2]; - getDecor().getLocationOnScreen(parentLoc); - for (String name: names) { - Bundle sharedElementBundle = state.getBundle(name); - if (sharedElementBundle != null) { - Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP); - View snapshot = new View(context); - Resources resources = getWindow().getContext().getResources(); - snapshot.setBackground(new BitmapDrawable(resources, bitmap)); - snapshot.setViewName(name); - setSharedElementState(snapshot, name, state, parentLoc); - snapshots.add(snapshot); - } - } - return snapshots; - } - - private static void setSharedElementState(View view, String name, Bundle transitionArgs, - int[] parentLoc) { - Bundle sharedElementBundle = transitionArgs.getBundle(name); - if (sharedElementBundle == null) { - return; - } - - if (view instanceof ImageView) { - int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1); - if (scaleTypeInt >= 0) { - ImageView imageView = (ImageView) view; - ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt]; - imageView.setScaleType(scaleType); - if (scaleType == ImageView.ScaleType.MATRIX) { - float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX); - Matrix matrix = new Matrix(); - matrix.setValues(matrixValues); - imageView.setImageMatrix(matrix); - } - } - } - - float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); - view.setTranslationZ(z); - - int x = sharedElementBundle.getInt(KEY_SCREEN_X); - int y = sharedElementBundle.getInt(KEY_SCREEN_Y); - int width = sharedElementBundle.getInt(KEY_WIDTH); - int height = sharedElementBundle.getInt(KEY_HEIGHT); - - int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); - int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); - view.measure(widthSpec, heightSpec); - - int left = x - parentLoc[0]; - int top = y - parentLoc[1]; - int right = left + width; - int bottom = top + height; - view.layout(left, top, right, bottom); - } - - private ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState( - Bundle sharedElementState, final ArrayList<View> snapshots) { - ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState = - new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>(); - if (sharedElementState != null) { - int[] tempLoc = new int[2]; - for (int i = 0; i < mSharedElementNames.size(); i++) { - View sharedElement = mSharedElements.get(i); - String name = mSharedElementNames.get(i); - Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement, - name, sharedElementState); - if (originalState != null) { - originalImageState.put((ImageView) sharedElement, originalState); - } - View parent = (View) sharedElement.getParent(); - parent.getLocationOnScreen(tempLoc); - setSharedElementState(sharedElement, name, sharedElementState, tempLoc); - sharedElement.requestLayout(); - } - } - mListener.setSharedElementStart(mSharedElementNames, mSharedElements, snapshots); - - getDecor().getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getDecor().getViewTreeObserver().removeOnPreDrawListener(this); - mListener.setSharedElementEnd(mSharedElementNames, mSharedElements, - snapshots); - mSharedElementTransitionStarted = true; - return true; - } - } - ); - return originalImageState; - } - - private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name, - Bundle transitionArgs) { - if (!(view instanceof ImageView)) { - return null; - } - Bundle bundle = transitionArgs.getBundle(name); - if (bundle == null) { - return null; - } - int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1); - if (scaleTypeInt < 0) { - return null; - } - - ImageView imageView = (ImageView) view; - ImageView.ScaleType originalScaleType = imageView.getScaleType(); - - Matrix originalMatrix = null; - if (originalScaleType == ImageView.ScaleType.MATRIX) { - originalMatrix = new Matrix(imageView.getImageMatrix()); - } - - return Pair.create(originalScaleType, originalMatrix); - } - - private static void setOriginalImageViewState( - ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) { - for (int i = 0; i < originalState.size(); i++) { - ImageView imageView = originalState.keyAt(i); - Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i); - imageView.setScaleType(state.first); - imageView.setImageMatrix(state.second); - } - } - } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index 93eb53e..a71d649 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -19,8 +19,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -29,7 +27,7 @@ import android.os.Message; import android.transition.Transition; import android.transition.TransitionManager; import android.view.View; -import android.widget.ImageView; +import android.view.ViewTreeObserver; import java.util.ArrayList; @@ -62,6 +60,10 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mIsHidden; + private boolean mExitTransitionStarted; + + private Bundle mExitSharedElementBundle; + public ExitTransitionCoordinator(Activity activity, ArrayList<String> names, ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) { super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning), @@ -102,15 +104,32 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { setViewVisibility(mSharedElements, View.VISIBLE); mIsHidden = true; break; + case MSG_SHARED_ELEMENT_DESTINATION: + mExitSharedElementBundle = resultData; + if (mExitTransitionStarted) { + startSharedElementExit(); + } + break; + } + } + + private void startSharedElementExit() { + if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) { + Transition transition = getSharedElementExitTransition(); + TransitionManager.beginDelayedTransition(getDecor(), transition); + ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, + mSharedElementNames); + setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); } } private void hideSharedElements() { setViewVisibility(mSharedElements, View.INVISIBLE); + finishIfNecessary(); } public void startExit() { - beginTransition(); + beginTransitions(); setViewVisibility(mTransitioningViews, View.INVISIBLE); } @@ -140,7 +159,30 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } } }, options); - startExit(); + Transition sharedElementTransition = mSharedElements.isEmpty() + ? null : getSharedElementTransition(); + if (sharedElementTransition == null) { + sharedElementTransitionComplete(); + } + Transition transition = mergeTransitions(sharedElementTransition, getExitTransition()); + if (transition == null) { + mExitTransitionStarted = true; + } else { + TransitionManager.beginDelayedTransition(getDecor(), transition); + setViewVisibility(mTransitioningViews, View.INVISIBLE); + getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + mExitTransitionStarted = true; + if (mExitSharedElementBundle != null) { + startSharedElementExit(); + } + notifyComplete(); + return true; + } + }); + } } private void fadeOutBackground() { @@ -162,40 +204,62 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } } - private void beginTransition() { - Transition sharedElementTransition = configureTransition(getSharedElementTransition()); - Transition viewsTransition = configureTransition(getViewsTransition()); - viewsTransition = addTargets(viewsTransition, mTransitioningViews); - if (sharedElementTransition == null || mSharedElements.isEmpty()) { - sharedElementTransitionComplete(); - sharedElementTransition = null; + private Transition getExitTransition() { + Transition viewsTransition = null; + if (!mTransitioningViews.isEmpty()) { + viewsTransition = configureTransition(getViewsTransition()); + } + if (viewsTransition == null) { + exitTransitionComplete(); } else { - sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() { + viewsTransition.addListener(new Transition.TransitionListenerAdapter() { @Override public void onTransitionEnd(Transition transition) { - sharedElementTransitionComplete(); + exitTransitionComplete(); + if (mIsHidden) { + setViewVisibility(mTransitioningViews, View.VISIBLE); + } + } + + @Override + public void onTransitionCancel(Transition transition) { + super.onTransitionCancel(transition); } }); } - if (viewsTransition == null || mTransitioningViews.isEmpty()) { - exitTransitionComplete(); - viewsTransition = null; + return viewsTransition; + } + + private Transition getSharedElementExitTransition() { + Transition sharedElementTransition = null; + if (!mSharedElements.isEmpty()) { + sharedElementTransition = configureTransition(getSharedElementTransition()); + } + if (sharedElementTransition == null) { + sharedElementTransitionComplete(); } else { - viewsTransition.addListener(new Transition.TransitionListenerAdapter() { + sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() { @Override public void onTransitionEnd(Transition transition) { - exitTransitionComplete(); + sharedElementTransitionComplete(); if (mIsHidden) { - setViewVisibility(mTransitioningViews, View.VISIBLE); + setViewVisibility(mSharedElements, View.VISIBLE); } } }); + mSharedElements.get(0).invalidate(); } + return sharedElementTransition; + } + + private void beginTransitions() { + Transition sharedElementTransition = getSharedElementExitTransition(); + Transition viewsTransition = getExitTransition(); Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); - TransitionManager.beginDelayedTransition(getDecor(), transition); - if (viewsTransition == null && sharedElementTransition != null) { - mSharedElements.get(0).requestLayout(); + mExitTransitionStarted = true; + if (transition != null) { + TransitionManager.beginDelayedTransition(getDecor(), transition); } } @@ -205,18 +269,12 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } protected boolean isReadyToNotify() { - return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; + return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady + && mExitTransitionStarted; } private void sharedElementTransitionComplete() { - Bundle bundle = new Bundle(); - int[] tempLoc = new int[2]; - for (int i = 0; i < mSharedElementNames.size(); i++) { - View sharedElement = mSharedElements.get(i); - String name = mSharedElementNames.get(i); - captureSharedElementState(sharedElement, name, bundle, tempLoc); - } - mSharedElementBundle = bundle; + mSharedElementBundle = captureSharedElementState(); notifyComplete(); } @@ -230,15 +288,23 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { mExitNotified = true; mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); mResultReceiver = null; // done talking - if (mIsReturning) { - mActivity.finish(); - mActivity.overridePendingTransition(0, 0); - } - mActivity = null; + finishIfNecessary(); } } } + private void finishIfNecessary() { + if (mIsReturning && mExitNotified && (mSharedElements.isEmpty() + || mSharedElements.get(0).getVisibility() == View.INVISIBLE)) { + mActivity.finish(); + mActivity.overridePendingTransition(0, 0); + mActivity = null; + } + if (!mIsReturning && mExitNotified) { + mActivity = null; // don't need it anymore + } + } + @Override protected Transition getViewsTransition() { if (mIsReturning) { @@ -255,58 +321,4 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { return getWindow().getSharedElementExitTransition(); } } - - /** - * Captures placement information for Views with a shared element name for - * Activity Transitions. - * - * @param view The View to capture the placement information for. - * @param name The shared element name in the target Activity to apply the placement - * information for. - * @param transitionArgs Bundle to store shared element placement information. - * @param tempLoc A temporary int[2] for capturing the current location of views. - */ - private static void captureSharedElementState(View view, String name, Bundle transitionArgs, - int[] tempLoc) { - Bundle sharedElementBundle = new Bundle(); - view.getLocationOnScreen(tempLoc); - float scaleX = view.getScaleX(); - sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]); - int width = Math.round(view.getWidth() * scaleX); - sharedElementBundle.putInt(KEY_WIDTH, width); - - float scaleY = view.getScaleY(); - sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]); - int height = Math.round(view.getHeight() * scaleY); - sharedElementBundle.putInt(KEY_HEIGHT, height); - - sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); - - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - view.draw(canvas); - sharedElementBundle.putParcelable(KEY_BITMAP, bitmap); - - if (view instanceof ImageView) { - ImageView imageView = (ImageView) view; - int scaleTypeInt = scaleTypeToInt(imageView.getScaleType()); - sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt); - if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) { - float[] matrix = new float[9]; - imageView.getImageMatrix().getValues(matrix); - sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix); - } - } - - transitionArgs.putBundle(name, sharedElementBundle); - } - - private static int scaleTypeToInt(ImageView.ScaleType scaleType) { - for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { - if (scaleType == SCALE_TYPE_VALUES[i]) { - return i; - } - } - return -1; - } } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 8434c2a..bf2d7e5 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -176,7 +176,8 @@ public interface IActivityManager extends IInterface { public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher, - IUiAutomationConnection connection, int userId) throws RemoteException; + IUiAutomationConnection connection, int userId, + String abiOverride) throws RemoteException; public void finishInstrumentation(IApplicationThread target, int resultCode, Bundle results) throws RemoteException; 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..f332c9d 100644 --- a/core/java/android/app/VoiceInteractor.java +++ b/core/java/android/app/VoiceInteractor.java @@ -30,18 +30,39 @@ 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. + * Interface for an {@link Activity} to interact with the user through voice. Use + * {@link android.app.Activity#getVoiceInteractor() Activity.getVoiceInteractor} + * to retrieve the interface, if the activity is currently involved in a voice interaction. + * + * <p>The voice interactor revolves around submitting voice interaction requests to the + * back-end voice interaction service that is working with the user. These requests are + * submitted with {@link #submitRequest}, providing a new instance of a + * {@link Request} subclass describing the type of operation to perform -- currently the + * possible requests are {@link ConfirmationRequest} and {@link CommandRequest}. + * + * <p>Once a request is submitted, the voice system will process it and evetually deliver + * the result to the request object. The application can cancel a pending request at any + * time. + * + * <p>The VoiceInteractor is integrated with Activity's state saving mechanism, so that + * if an activity is being restarted with retained state, it will retain the current + * VoiceInteractor and any outstanding requests. Because of this, you should always use + * {@link Request#getActivity() Request.getActivity} to get back to the activity of a + * request, rather than holding on to the actvitity instance yourself, either explicitly + * or implicitly through a non-static inner class. */ 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 @@ -60,6 +81,16 @@ public class VoiceInteractor { request.clear(); } break; + case MSG_ABORT_VOICE_RESULT: + request = pullRequest((IVoiceInteractorRequest)args.arg1, true); + if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request + + " result=" + args.arg1); + if (request != null) { + ((AbortVoiceRequest)request).onAbortResult((Bundle) args.arg2); + request.clear(); + } + break; case MSG_COMMAND_RESULT: request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0); if (DEBUG) Log.d(TAG, "onCommandResult: req=" @@ -94,6 +125,12 @@ public class VoiceInteractor { } @Override + public void deliverAbortVoiceResult(IVoiceInteractorRequest request, Bundle result) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO( + MSG_ABORT_VOICE_RESULT, request, result)); + } + + @Override public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, Bundle result) { mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO( @@ -110,8 +147,9 @@ public class VoiceInteractor { final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); static final int MSG_CONFIRMATION_RESULT = 1; - static final int MSG_COMMAND_RESULT = 2; - static final int MSG_CANCEL_RESULT = 3; + static final int MSG_ABORT_VOICE_RESULT = 2; + static final int MSG_COMMAND_RESULT = 3; + static final int MSG_CANCEL_RESULT = 4; public static abstract class Request { IVoiceInteractorRequest mRequestInterface; @@ -140,6 +178,12 @@ public class VoiceInteractor { public void onCancel() { } + public void onAttached(Activity activity) { + } + + public void onDetached() { + } + void clear() { mRequestInterface = null; mContext = null; @@ -180,9 +224,42 @@ public class VoiceInteractor { IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, IVoiceInteractorCallback callback) throws RemoteException { - return interactor.startConfirmation(packageName, callback, mPrompt.toString(), mExtras); + return interactor.startConfirmation(packageName, callback, mPrompt, mExtras); + } + } + + public static class AbortVoiceRequest extends Request { + final CharSequence mMessage; + final Bundle mExtras; + + /** + * Reports that the current interaction can not be complete with voice, so the + * application will need to switch to a traditional input UI. Applications should + * only use this when they need to completely bail out of the voice interaction + * and switch to a traditional UI. When the resonsponse comes back, the voice + * system has handled the request and is ready to switch; at that point the application + * can start a new non-voice activity. Be sure when starting the new activity + * to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK + * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice + * interaction task. + * + * @param message Optional message to tell user about not being able to complete + * the interaction with voice. + * @param extras Additional optional information. + */ + public AbortVoiceRequest(CharSequence message, Bundle extras) { + mMessage = message; + mExtras = extras; } - } + + public void onAbortResult(Bundle result) { + } + + IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, + IVoiceInteractorCallback callback) throws RemoteException { + return interactor.startAbortVoice(packageName, callback, mMessage, mExtras); + } + } public static class CommandRequest extends Request { final String mCommand; @@ -220,11 +297,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 +315,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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 6b486c4..b3b1d47 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -25,6 +25,7 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.RestrictionsManager; import android.os.Bundle; import android.os.Handler; import android.os.Process; @@ -2320,4 +2321,23 @@ public class DevicePolicyManager { } } + /** + * Designates a specific broadcast receiver component as the provider for + * making permission requests of a local or remote administrator of the user. + * <p/> + * Only a profile owner can designate the restrictions provider. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param receiver The component name of a BroadcastReceiver that handles the + * {@link RestrictionsManager#ACTION_REQUEST_PERMISSION} intent. If this param is null, + * it removes the restrictions provider previously assigned. + */ + public void setRestrictionsProvider(ComponentName admin, ComponentName receiver) { + if (mService != null) { + try { + mService.setRestrictionsProvider(admin, receiver); + } catch (RemoteException re) { + Log.w(TAG, "Failed to set permission provider on device policy service"); + } + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index cc6d715..7f754dc 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -121,6 +121,9 @@ interface IDevicePolicyManager { void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings); Bundle getApplicationRestrictions(in ComponentName who, in String packageName); + void setRestrictionsProvider(in ComponentName who, in ComponentName provider); + ComponentName getRestrictionsProvider(int userHandle); + void setUserRestriction(in ComponentName who, in String key, boolean enable); void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags); void clearCrossProfileIntentFilters(in ComponentName admin); diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java new file mode 100644 index 0000000..da5cb10 --- /dev/null +++ b/core/java/android/app/backup/BackupTransport.java @@ -0,0 +1,334 @@ +/* + * 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.app.backup; + +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import com.android.internal.backup.BackupConstants; +import com.android.internal.backup.IBackupTransport; + +/** + * Concrete class that provides a stable-API bridge between IBackupTransport + * and its implementations. + * + * @hide + */ +public class BackupTransport { + IBackupTransport mBinderImpl = new TransportImpl(); + /** @hide */ + public IBinder getBinder() { + return mBinderImpl.asBinder(); + } + + // ------------------------------------------------------------------------------------ + // Transport self-description and general configuration interfaces + // + + /** + * Ask the transport for the name under which it should be registered. This will + * typically be its host service's component name, but need not be. + */ + public String name() { + throw new UnsupportedOperationException("Transport name() not implemented"); + } + + /** + * Ask the transport for an Intent that can be used to launch any internal + * configuration Activity that it wishes to present. For example, the transport + * may offer a UI for allowing the user to supply login credentials for the + * transport's off-device backend. + * + * If the transport does not supply any user-facing configuration UI, it should + * return null from this method. + * + * @return An Intent that can be passed to Context.startActivity() in order to + * launch the transport's configuration UI. This method will return null + * if the transport does not offer any user-facing configuration UI. + */ + public Intent configurationIntent() { + return null; + } + + /** + * On demand, supply a one-line string that can be shown to the user that + * describes the current backend destination. For example, a transport that + * can potentially associate backup data with arbitrary user accounts should + * include the name of the currently-active account here. + * + * @return A string describing the destination to which the transport is currently + * sending data. This method should not return null. + */ + public String currentDestinationString() { + throw new UnsupportedOperationException( + "Transport currentDestinationString() not implemented"); + } + + /** + * Ask the transport where, on local device storage, to keep backup state blobs. + * This is per-transport so that mock transports used for testing can coexist with + * "live" backup services without interfering with the live bookkeeping. The + * returned string should be a name that is expected to be unambiguous among all + * available backup transports; the name of the class implementing the transport + * is a good choice. + * + * @return A unique name, suitable for use as a file or directory name, that the + * Backup Manager could use to disambiguate state files associated with + * different backup transports. + */ + public String transportDirName() { + throw new UnsupportedOperationException( + "Transport transportDirName() not implemented"); + } + + // ------------------------------------------------------------------------------------ + // Key/value incremental backup support interfaces + + /** + * Verify that this is a suitable time for a backup pass. This should return zero + * if a backup is reasonable right now, some positive value otherwise. This method + * will be called outside of the {@link #performBackup}/{@link #finishBackup} pair. + * + * <p>If this is not a suitable time for a backup, the transport should return a + * backoff delay, in milliseconds, after which the Backup Manager should try again. + * + * @return Zero if this is a suitable time for a backup pass, or a positive time delay + * in milliseconds to suggest deferring the backup pass for a while. + */ + public long requestBackupTime() { + return 0; + } + + /** + * Initialize the server side storage for this device, erasing all stored data. + * The transport may send the request immediately, or may buffer it. After + * this is called, {@link #finishBackup} will be called to ensure the request + * is sent and received successfully. + * + * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far) or + * {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure). + */ + public int initializeDevice() { + return BackupConstants.TRANSPORT_ERROR; + } + + /** + * Send one application's key/value data update to the backup destination. The + * transport may send the data immediately, or may buffer it. After this is called, + * {@link #finishBackup} will be called to ensure the data is sent and recorded successfully. + * + * @param packageInfo The identity of the application whose data is being backed up. + * This specifically includes the signature list for the package. + * @param data The data stream that resulted from invoking the application's + * BackupService.doBackup() method. This may be a pipe rather than a file on + * persistent media, so it may not be seekable. + * @param wipeAllFirst When true, <i>all</i> backed-up data for the current device/account + * must be erased prior to the storage of the data provided here. The purpose of this + * is to provide a guarantee that no stale data exists in the restore set when the + * device begins providing incremental backups. + * @return one of {@link BackupConstants#TRANSPORT_OK} (OK so far), + * {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure), or + * {@link BackupConstants#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has + * become lost due to inactivity purge or some other reason and needs re-initializing) + */ + public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd) { + return BackupConstants.TRANSPORT_ERROR; + } + + /** + * Erase the give application's data from the backup destination. This clears + * out the given package's data from the current backup set, making it as though + * the app had never yet been backed up. After this is called, {@link finishBackup} + * must be called to ensure that the operation is recorded successfully. + * + * @return the same error codes as {@link #performBackup}. + */ + public int clearBackupData(PackageInfo packageInfo) { + return BackupConstants.TRANSPORT_ERROR; + } + + /** + * Finish sending application data to the backup destination. This must be + * called after {@link #performBackup} or {@link clearBackupData} to ensure that + * all data is sent. Only when this method returns true can a backup be assumed + * to have succeeded. + * + * @return the same error codes as {@link #performBackup}. + */ + public int finishBackup() { + return BackupConstants.TRANSPORT_ERROR; + } + + // ------------------------------------------------------------------------------------ + // Key/value dataset restore interfaces + + /** + * Get the set of all backups currently available over this transport. + * + * @return Descriptions of the set of restore images available for this device, + * or null if an error occurred (the attempt should be rescheduled). + **/ + public RestoreSet[] getAvailableRestoreSets() { + return null; + } + + /** + * Get the identifying token of the backup set currently being stored from + * this device. This is used in the case of applications wishing to restore + * their last-known-good data. + * + * @return A token that can be passed to {@link #startRestore}, or 0 if there + * is no backup set available corresponding to the current device state. + */ + public long getCurrentRestoreSet() { + return 0; + } + + /** + * Start restoring application data from backup. After calling this function, + * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData} + * to walk through the actual application data. + * + * @param token A backup token as returned by {@link #getAvailableRestoreSets} + * or {@link #getCurrentRestoreSet}. + * @param packages List of applications to restore (if data is available). + * Application data will be restored in the order given. + * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far, call + * {@link #nextRestorePackage}) or {@link BackupConstants#TRANSPORT_ERROR} + * (an error occurred, the restore should be aborted and rescheduled). + */ + public int startRestore(long token, PackageInfo[] packages) { + return BackupConstants.TRANSPORT_ERROR; + } + + /** + * Get the package name of the next application with data in the backup store. + * + * @return The name of one of the packages supplied to {@link #startRestore}, + * or "" (the empty string) if no more backup data is available, + * or null if an error occurred (the restore should be aborted and rescheduled). + */ + public String nextRestorePackage() { + return null; + } + + /** + * Get the data for the application returned by {@link #nextRestorePackage}. + * @param data An open, writable file into which the backup data should be stored. + * @return the same error codes as {@link #startRestore}. + */ + public int getRestoreData(ParcelFileDescriptor outFd) { + return BackupConstants.TRANSPORT_ERROR; + } + + /** + * End a restore session (aborting any in-process data transfer as necessary), + * freeing any resources and connections used during the restore process. + */ + public void finishRestore() { + throw new UnsupportedOperationException( + "Transport finishRestore() not implemented"); + } + + /** + * Bridge between the actual IBackupTransport implementation and the stable API. If the + * binder interface needs to change, we use this layer to translate so that we can + * (if appropriate) decouple those framework-side changes from the BackupTransport + * implementations. + */ + class TransportImpl extends IBackupTransport.Stub { + + @Override + public String name() throws RemoteException { + return BackupTransport.this.name(); + } + + @Override + public Intent configurationIntent() throws RemoteException { + return BackupTransport.this.configurationIntent(); + } + + @Override + public String currentDestinationString() throws RemoteException { + return BackupTransport.this.currentDestinationString(); + } + + @Override + public String transportDirName() throws RemoteException { + return BackupTransport.this.transportDirName(); + } + + @Override + public long requestBackupTime() throws RemoteException { + return BackupTransport.this.requestBackupTime(); + } + + @Override + public int initializeDevice() throws RemoteException { + return BackupTransport.this.initializeDevice(); + } + + @Override + public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd) + throws RemoteException { + return BackupTransport.this.performBackup(packageInfo, inFd); + } + + @Override + public int clearBackupData(PackageInfo packageInfo) throws RemoteException { + return BackupTransport.this.clearBackupData(packageInfo); + } + + @Override + public int finishBackup() throws RemoteException { + return BackupTransport.this.finishBackup(); + } + + @Override + public RestoreSet[] getAvailableRestoreSets() throws RemoteException { + return BackupTransport.this.getAvailableRestoreSets(); + } + + @Override + public long getCurrentRestoreSet() throws RemoteException { + return BackupTransport.this.getCurrentRestoreSet(); + } + + @Override + public int startRestore(long token, PackageInfo[] packages) throws RemoteException { + return BackupTransport.this.startRestore(token, packages); + } + + @Override + public String nextRestorePackage() throws RemoteException { + return BackupTransport.this.nextRestorePackage(); + } + + @Override + public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException { + return BackupTransport.this.getRestoreData(outFd); + } + + @Override + public void finishRestore() throws RemoteException { + BackupTransport.this.finishRestore(); + } + } +} 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..c69e669 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -40,6 +40,7 @@ import android.os.Looper; import android.os.StatFs; import android.os.UserHandle; import android.os.UserManager; +import android.provider.MediaStore; import android.util.AttributeSet; import android.view.DisplayAdjustments; import android.view.Display; @@ -929,6 +930,40 @@ public abstract class Context { public abstract File[] getExternalCacheDirs(); /** + * Returns absolute paths to application-specific directories on all + * external storage devices where the application can place media files. + * These files are scanned and made available to other apps through + * {@link MediaStore}. + * <p> + * This is like {@link #getExternalFilesDirs} in that these files will be + * deleted when the application is uninstalled, however there are some + * important differences: + * <ul> + * <li>External files are not always available: they will disappear if the + * user mounts the external storage on a computer or removes it. + * <li>There is no security enforced with these files. + * </ul> + * <p> + * External storage devices returned here are considered a permanent part of + * the device, including both emulated external storage and physical media + * slots, such as SD cards in a battery compartment. The returned paths do + * not include transient devices, such as USB flash drives. + * <p> + * An application may store data on any or all of the returned devices. For + * example, an app may choose to store large files on the device with the + * most available space, as measured by {@link StatFs}. + * <p> + * No permissions are required to read or write to the returned paths; they + * are always accessible to the calling app. Write access outside of these + * paths on secondary external storage devices is not available. + * <p> + * Returned paths may be {@code null} if a storage device is unavailable. + * + * @see Environment#getExternalStorageState(File) + */ + public abstract File[] getExternalMediaDirs(); + + /** * Returns an array of strings naming the private files associated with * this Context's application package. * @@ -2363,6 +2398,7 @@ public abstract class Context { * * @see #getSystemService * @see android.net.wifi.passpoint.WifiPasspointManager + * @hide */ public static final String WIFI_PASSPOINT_SERVICE = "wifipasspoint"; @@ -2659,6 +2695,15 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.content.RestrictionsManager} for retrieving application restrictions + * and requesting permissions for restricted operations. + * @see #getSystemService + * @see android.content.RestrictionsManager + */ + public static final String RESTRICTIONS_SERVICE = "restrictions"; + + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.app.AppOpsManager} for tracking application operations * on the device. * diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index c66355b..dbf9122 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -237,6 +237,11 @@ public class ContextWrapper extends Context { } @Override + public File[] getExternalMediaDirs() { + return mBase.getExternalMediaDirs(); + } + + @Override public File getDir(String name, int mode) { return mBase.getDir(name, mode); } diff --git a/core/java/android/content/IRestrictionsManager.aidl b/core/java/android/content/IRestrictionsManager.aidl new file mode 100644 index 0000000..b1c0a3a --- /dev/null +++ b/core/java/android/content/IRestrictionsManager.aidl @@ -0,0 +1,30 @@ +/* + * 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.content; + +import android.os.Bundle; + +/** + * Interface used by the RestrictionsManager + * @hide + */ +interface IRestrictionsManager { + Bundle getApplicationRestrictions(in String packageName); + boolean hasRestrictionsProvider(); + void requestPermission(in String packageName, in String requestTemplate, in Bundle requestData); + void notifyPermissionResponse(in String packageName, in Bundle response); +} diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java index 3ff53bf..62f88a9 100644 --- a/core/java/android/content/RestrictionEntry.java +++ b/core/java/android/content/RestrictionEntry.java @@ -73,32 +73,38 @@ public class RestrictionEntry implements Parcelable { */ public static final int TYPE_MULTI_SELECT = 4; + /** + * A type of restriction. Use this for storing an integer value. The range of values + * is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}. + */ + public static final int TYPE_INTEGER = 5; + /** The type of restriction. */ - private int type; + private int mType; /** The unique key that identifies the restriction. */ - private String key; + private String mKey; /** The user-visible title of the restriction. */ - private String title; + private String mTitle; /** The user-visible secondary description of the restriction. */ - private String description; + private String mDescription; /** The user-visible set of choices used for single-select and multi-select lists. */ - private String [] choices; + private String [] mChoiceEntries; /** The values corresponding to the user-visible choices. The value(s) of this entry will * one or more of these, returned by {@link #getAllSelectedStrings()} and * {@link #getSelectedString()}. */ - private String [] values; + private String [] mChoiceValues; /* The chosen value, whose content depends on the type of the restriction. */ - private String currentValue; + private String mCurrentValue; /* List of selected choices in the multi-select case. */ - private String[] currentValues; + private String[] mCurrentValues; /** * Constructor for {@link #TYPE_CHOICE} type. @@ -106,9 +112,9 @@ public class RestrictionEntry implements Parcelable { * @param selectedString the current value */ public RestrictionEntry(String key, String selectedString) { - this.key = key; - this.type = TYPE_CHOICE; - this.currentValue = selectedString; + this.mKey = key; + this.mType = TYPE_CHOICE; + this.mCurrentValue = selectedString; } /** @@ -117,8 +123,8 @@ public class RestrictionEntry implements Parcelable { * @param selectedState whether this restriction is selected or not */ public RestrictionEntry(String key, boolean selectedState) { - this.key = key; - this.type = TYPE_BOOLEAN; + this.mKey = key; + this.mType = TYPE_BOOLEAN; setSelectedState(selectedState); } @@ -128,9 +134,20 @@ public class RestrictionEntry implements Parcelable { * @param selectedStrings the list of values that are currently selected */ public RestrictionEntry(String key, String[] selectedStrings) { - this.key = key; - this.type = TYPE_MULTI_SELECT; - this.currentValues = selectedStrings; + this.mKey = key; + this.mType = TYPE_MULTI_SELECT; + this.mCurrentValues = selectedStrings; + } + + /** + * Constructor for {@link #TYPE_INTEGER} type. + * @param key the unique key for this restriction + * @param selectedInt the integer value of the restriction + */ + public RestrictionEntry(String key, int selectedInt) { + mKey = key; + mType = TYPE_INTEGER; + setIntValue(selectedInt); } /** @@ -138,7 +155,7 @@ public class RestrictionEntry implements Parcelable { * @param type the type for this restriction. */ public void setType(int type) { - this.type = type; + this.mType = type; } /** @@ -146,7 +163,7 @@ public class RestrictionEntry implements Parcelable { * @return the type for this restriction */ public int getType() { - return type; + return mType; } /** @@ -155,7 +172,7 @@ public class RestrictionEntry implements Parcelable { * single string values. */ public String getSelectedString() { - return currentValue; + return mCurrentValue; } /** @@ -164,7 +181,7 @@ public class RestrictionEntry implements Parcelable { * null otherwise. */ public String[] getAllSelectedStrings() { - return currentValues; + return mCurrentValues; } /** @@ -172,7 +189,23 @@ public class RestrictionEntry implements Parcelable { * @return the current selected state of the entry. */ public boolean getSelectedState() { - return Boolean.parseBoolean(currentValue); + return Boolean.parseBoolean(mCurrentValue); + } + + /** + * Returns the value of the entry as an integer when the type is {@link #TYPE_INTEGER}. + * @return the integer value of the entry. + */ + public int getIntValue() { + return Integer.parseInt(mCurrentValue); + } + + /** + * Sets the integer value of the entry when the type is {@link #TYPE_INTEGER}. + * @param value the integer value to set. + */ + public void setIntValue(int value) { + mCurrentValue = Integer.toString(value); } /** @@ -181,7 +214,7 @@ public class RestrictionEntry implements Parcelable { * @param selectedString the string value to select. */ public void setSelectedString(String selectedString) { - currentValue = selectedString; + mCurrentValue = selectedString; } /** @@ -190,7 +223,7 @@ public class RestrictionEntry implements Parcelable { * @param state the current selected state */ public void setSelectedState(boolean state) { - currentValue = Boolean.toString(state); + mCurrentValue = Boolean.toString(state); } /** @@ -199,7 +232,7 @@ public class RestrictionEntry implements Parcelable { * @param allSelectedStrings the current list of selected values. */ public void setAllSelectedStrings(String[] allSelectedStrings) { - currentValues = allSelectedStrings; + mCurrentValues = allSelectedStrings; } /** @@ -216,7 +249,7 @@ public class RestrictionEntry implements Parcelable { * @see #getAllSelectedStrings() */ public void setChoiceValues(String[] choiceValues) { - values = choiceValues; + mChoiceValues = choiceValues; } /** @@ -227,7 +260,7 @@ public class RestrictionEntry implements Parcelable { * @see #setChoiceValues(String[]) */ public void setChoiceValues(Context context, int stringArrayResId) { - values = context.getResources().getStringArray(stringArrayResId); + mChoiceValues = context.getResources().getStringArray(stringArrayResId); } /** @@ -235,7 +268,7 @@ public class RestrictionEntry implements Parcelable { * @return the list of possible values. */ public String[] getChoiceValues() { - return values; + return mChoiceValues; } /** @@ -248,7 +281,7 @@ public class RestrictionEntry implements Parcelable { * @see #setChoiceValues(String[]) */ public void setChoiceEntries(String[] choiceEntries) { - choices = choiceEntries; + mChoiceEntries = choiceEntries; } /** Sets a list of strings that will be presented as choices to the user. This is similar to @@ -257,7 +290,7 @@ public class RestrictionEntry implements Parcelable { * @param stringArrayResId the resource id of a string array containing the possible entries. */ public void setChoiceEntries(Context context, int stringArrayResId) { - choices = context.getResources().getStringArray(stringArrayResId); + mChoiceEntries = context.getResources().getStringArray(stringArrayResId); } /** @@ -265,7 +298,7 @@ public class RestrictionEntry implements Parcelable { * @return the list of choices presented to the user. */ public String[] getChoiceEntries() { - return choices; + return mChoiceEntries; } /** @@ -273,7 +306,7 @@ public class RestrictionEntry implements Parcelable { * @return the user-visible description, null if none was set earlier. */ public String getDescription() { - return description; + return mDescription; } /** @@ -283,7 +316,7 @@ public class RestrictionEntry implements Parcelable { * @param description the user-visible description string. */ public void setDescription(String description) { - this.description = description; + this.mDescription = description; } /** @@ -291,7 +324,7 @@ public class RestrictionEntry implements Parcelable { * @return the key for the restriction. */ public String getKey() { - return key; + return mKey; } /** @@ -299,7 +332,7 @@ public class RestrictionEntry implements Parcelable { * @return the user-visible title for the entry, null if none was set earlier. */ public String getTitle() { - return title; + return mTitle; } /** @@ -307,7 +340,7 @@ public class RestrictionEntry implements Parcelable { * @param title the user-visible title for the entry. */ public void setTitle(String title) { - this.title = title; + this.mTitle = title; } private boolean equalArrays(String[] one, String[] other) { @@ -324,23 +357,23 @@ public class RestrictionEntry implements Parcelable { if (!(o instanceof RestrictionEntry)) return false; final RestrictionEntry other = (RestrictionEntry) o; // Make sure that either currentValue matches or currentValues matches. - return type == other.type && key.equals(other.key) + return mType == other.mType && mKey.equals(other.mKey) && - ((currentValues == null && other.currentValues == null - && currentValue != null && currentValue.equals(other.currentValue)) + ((mCurrentValues == null && other.mCurrentValues == null + && mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue)) || - (currentValue == null && other.currentValue == null - && currentValues != null && equalArrays(currentValues, other.currentValues))); + (mCurrentValue == null && other.mCurrentValue == null + && mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues))); } @Override public int hashCode() { int result = 17; - result = 31 * result + key.hashCode(); - if (currentValue != null) { - result = 31 * result + currentValue.hashCode(); - } else if (currentValues != null) { - for (String value : currentValues) { + result = 31 * result + mKey.hashCode(); + if (mCurrentValue != null) { + result = 31 * result + mCurrentValue.hashCode(); + } else if (mCurrentValues != null) { + for (String value : mCurrentValues) { if (value != null) { result = 31 * result + value.hashCode(); } @@ -359,14 +392,14 @@ public class RestrictionEntry implements Parcelable { } public RestrictionEntry(Parcel in) { - type = in.readInt(); - key = in.readString(); - title = in.readString(); - description = in.readString(); - choices = readArray(in); - values = readArray(in); - currentValue = in.readString(); - currentValues = readArray(in); + mType = in.readInt(); + mKey = in.readString(); + mTitle = in.readString(); + mDescription = in.readString(); + mChoiceEntries = readArray(in); + mChoiceValues = readArray(in); + mCurrentValue = in.readString(); + mCurrentValues = readArray(in); } @Override @@ -387,14 +420,14 @@ public class RestrictionEntry implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(type); - dest.writeString(key); - dest.writeString(title); - dest.writeString(description); - writeArray(dest, choices); - writeArray(dest, values); - dest.writeString(currentValue); - writeArray(dest, currentValues); + dest.writeInt(mType); + dest.writeString(mKey); + dest.writeString(mTitle); + dest.writeString(mDescription); + writeArray(dest, mChoiceEntries); + writeArray(dest, mChoiceValues); + dest.writeString(mCurrentValue); + writeArray(dest, mCurrentValues); } public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() { @@ -409,6 +442,6 @@ public class RestrictionEntry implements Parcelable { @Override public String toString() { - return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}"; + return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}"; } } diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java new file mode 100644 index 0000000..0dd0edd --- /dev/null +++ b/core/java/android/content/RestrictionsManager.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.content; + +import android.app.admin.DevicePolicyManager; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Collections; +import java.util.List; + +/** + * Provides a mechanism for apps to query restrictions imposed by an entity that + * manages the user. Apps can also send permission requests to a local or remote + * device administrator to override default app-specific restrictions or any other + * operation that needs explicit authorization from the administrator. + * <p> + * Apps can expose a set of restrictions via a runtime receiver mechanism or via + * static meta data in the manifest. + * <p> + * If the user has an active restrictions provider, dynamic requests can be made in + * addition to the statically imposed restrictions. Dynamic requests are app-specific + * and can be expressed via a predefined set of templates. + * <p> + * The RestrictionsManager forwards the dynamic requests to the active + * restrictions provider. The restrictions provider can respond back to requests by calling + * {@link #notifyPermissionResponse(String, Bundle)}, when + * a response is received from the administrator of the device or user + * The response is relayed back to the application via a protected broadcast, + * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}. + * <p> + * Static restrictions are specified by an XML file referenced by a meta-data attribute + * in the manifest. This enables applications as well as any web administration consoles + * to be able to read the template from the apk. + * <p> + * The syntax of the XML format is as follows: + * <pre> + * <restrictions> + * <restriction + * android:key="<key>" + * android:restrictionType="boolean|string|integer|multi-select|null" + * ... /> + * <restriction ... /> + * </restrictions> + * </pre> + * <p> + * The attributes for each restriction depend on the restriction type. + * + * @see RestrictionEntry + */ +public class RestrictionsManager { + + /** + * Broadcast intent delivered when a response is received for a permission + * request. The response is not available for later query, so the receiver + * must persist and/or immediately act upon the response. The application + * should not interrupt the user by coming to the foreground if it isn't + * currently in the foreground. It can post a notification instead, informing + * the user of a change in state. + * <p> + * For instance, if the user requested permission to make an in-app purchase, + * the app can post a notification that the request had been granted or denied, + * and allow the purchase to go through. + * <p> + * The broadcast Intent carries the following extra: + * {@link #EXTRA_RESPONSE_BUNDLE}. + */ + public static final String ACTION_PERMISSION_RESPONSE_RECEIVED = + "android.intent.action.PERMISSION_RESPONSE_RECEIVED"; + + /** + * Protected broadcast intent sent to the active restrictions provider. The intent + * contains the following extras:<p> + * <ul> + * <li>{@link #EXTRA_PACKAGE_NAME} : String; the package name of the application requesting + * permission.</li> + * <li>{@link #EXTRA_TEMPLATE_ID} : String; the template of the request.</li> + * <li>{@link #EXTRA_REQUEST_BUNDLE} : Bundle; contains the template-specific keys and values + * for the request. + * </ul> + * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName) + * @see #requestPermission(String, String, Bundle) + */ + public static final String ACTION_REQUEST_PERMISSION = + "android.intent.action.REQUEST_PERMISSION"; + + /** + * The package name of the application making the request. + */ + public static final String EXTRA_PACKAGE_NAME = "package_name"; + + /** + * The template id that specifies what kind of a request it is and may indicate + * how the request is to be presented to the administrator. Must be either one of + * the predefined templates or a custom one specified by the application that the + * restrictions provider is familiar with. + */ + public static final String EXTRA_TEMPLATE_ID = "template_id"; + + /** + * A bundle containing the details about the request. The contents depend on the + * template id. + * @see #EXTRA_TEMPLATE_ID + */ + public static final String EXTRA_REQUEST_BUNDLE = "request_bundle"; + + /** + * Contains a response from the administrator for specific request. + * The bundle contains the following information, at least: + * <ul> + * <li>{@link #REQUEST_KEY_ID}: The request id.</li> + * <li>{@link #REQUEST_KEY_DATA}: The request reference data.</li> + * </ul> + * <p> + * And depending on what the request template was, the bundle will contain the actual + * result of the request. For {@link #REQUEST_TEMPLATE_QUESTION}, the result will be in + * {@link #RESPONSE_KEY_BOOLEAN}, which is of type boolean; true if the administrator + * approved the request, false otherwise. + */ + public static final String EXTRA_RESPONSE_BUNDLE = "response_bundle"; + + + /** + * Request template that presents a simple question, with a possible title and icon. + * <p> + * Required keys are + * {@link #REQUEST_KEY_ID} and {@link #REQUEST_KEY_MESSAGE}. + * <p> + * Optional keys are + * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE}, + * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}. + */ + public static final String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple"; + + /** + * Key for request ID contained in the request bundle. + * <p> + * App-generated request id to identify the specific request when receiving + * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_ID = "android.req_template.req_id"; + + /** + * Key for request data contained in the request bundle. + * <p> + * Optional, typically used to identify the specific data that is being referred to, + * such as the unique identifier for a movie or book. This is not used for display + * purposes and is more like a cookie. This value is returned in the + * {@link #EXTRA_RESPONSE_BUNDLE}. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DATA = "android.req_template.data"; + + /** + * Key for request title contained in the request bundle. + * <p> + * Optional, typically used as the title of any notification or dialog presented + * to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_TITLE = "android.req_template.title"; + + /** + * Key for request message contained in the request bundle. + * <p> + * Required, shown as the actual message in a notification or dialog presented + * to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_MESSAGE = "android.req_template.mesg"; + + /** + * Key for request icon contained in the request bundle. + * <p> + * Optional, shown alongside the request message presented to the administrator + * who approves the request. + * <p> + * Type: Bitmap + */ + public static final String REQUEST_KEY_ICON = "android.req_template.icon"; + + /** + * Key for request approval button label contained in the request bundle. + * <p> + * Optional, may be shown as a label on the positive button in a dialog or + * notification presented to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept"; + + /** + * Key for request rejection button label contained in the request bundle. + * <p> + * Optional, may be shown as a label on the negative button in a dialog or + * notification presented to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DENY_LABEL = "android.req_template.reject"; + + /** + * Key for requestor's name contained in the request bundle. This value is not specified by + * the application. It is automatically inserted into the Bundle by the Restrictions Provider + * before it is sent to the administrator. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor"; + + /** + * Key for requestor's device name contained in the request bundle. This value is not specified + * by the application. It is automatically inserted into the Bundle by the Restrictions Provider + * before it is sent to the administrator. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DEVICE_NAME = "android.req_template.device"; + + /** + * Key for the response in the response bundle sent to the application, for a permission + * request. + * <p> + * Type: boolean + */ + public static final String RESPONSE_KEY_BOOLEAN = "android.req_template.response"; + + private static final String TAG = "RestrictionsManager"; + + private final Context mContext; + private final IRestrictionsManager mService; + + /** + * @hide + */ + public RestrictionsManager(Context context, IRestrictionsManager service) { + mContext = context; + mService = service; + } + + /** + * Returns any available set of application-specific restrictions applicable + * to this application. + * @return + */ + public Bundle getApplicationRestrictions() { + try { + if (mService != null) { + return mService.getApplicationRestrictions(mContext.getPackageName()); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + return null; + } + + /** + * Called by an application to check if permission requests can be made. If false, + * there is no need to request permission for an operation, unless a static + * restriction applies to that operation. + * @return + */ + public boolean hasRestrictionsProvider() { + try { + if (mService != null) { + return mService.hasRestrictionsProvider(); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + return false; + } + + /** + * Called by an application to request permission for an operation. The contents of the + * request are passed in a Bundle that contains several pieces of data depending on the + * chosen request template. + * + * @param requestTemplate The request template to use. The template could be one of the + * predefined templates specified in this class or a custom template that the specific + * Restrictions Provider might understand. For custom templates, the template name should be + * namespaced to avoid collisions with predefined templates and templates specified by + * other Restrictions Provider vendors. + * @param requestData A Bundle containing the data corresponding to the specified request + * template. The keys for the data in the bundle depend on the kind of template chosen. + */ + public void requestPermission(String requestTemplate, Bundle requestData) { + try { + if (mService != null) { + mService.requestPermission(mContext.getPackageName(), requestTemplate, requestData); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + } + + /** + * Called by the Restrictions Provider when a response is available to be + * delivered to an application. + * @param packageName + * @param response + */ + public void notifyPermissionResponse(String packageName, Bundle response) { + try { + if (mService != null) { + mService.notifyPermissionResponse(packageName, response); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + } + + /** + * Parse and return the list of restrictions defined in the manifest for the specified + * package, if any. + * @param packageName The application for which to fetch the restrictions list. + * @return The list of RestrictionEntry objects created from the XML file specified + * in the manifest, or null if none was specified. + */ + public List<RestrictionEntry> getManifestRestrictions(String packageName) { + // TODO: + return null; + } +} 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/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/hardware/hdmi/HdmiCecClient.java b/core/java/android/hardware/hdmi/HdmiCecClient.java index cd86cd8..dcb3624 100644 --- a/core/java/android/hardware/hdmi/HdmiCecClient.java +++ b/core/java/android/hardware/hdmi/HdmiCecClient.java @@ -69,44 +69,28 @@ public final class HdmiCecClient { * Send <Active Source> message. */ public void sendActiveSource() { - try { - mService.sendActiveSource(mBinder); - } catch (RemoteException e) { - Log.e(TAG, "sendActiveSource threw exception ", e); - } + Log.w(TAG, "In transition to HdmiControlManager. Will not work."); } /** * Send <Inactive Source> message. */ public void sendInactiveSource() { - try { - mService.sendInactiveSource(mBinder); - } catch (RemoteException e) { - Log.e(TAG, "sendInactiveSource threw exception ", e); - } + Log.w(TAG, "In transition to HdmiControlManager. Will not work."); } /** * Send <Text View On> message. */ public void sendTextViewOn() { - try { - mService.sendTextViewOn(mBinder); - } catch (RemoteException e) { - Log.e(TAG, "sendTextViewOn threw exception ", e); - } + Log.w(TAG, "In transition to HdmiControlManager. Will not work."); } /** * Send <Image View On> message. */ public void sendImageViewOn() { - try { - mService.sendImageViewOn(mBinder); - } catch (RemoteException e) { - Log.e(TAG, "sendImageViewOn threw exception ", e); - } + Log.w(TAG, "In transition to HdmiControlManager. Will not work."); } /** @@ -116,11 +100,7 @@ public final class HdmiCecClient { * {@link HdmiCec#ADDR_TV}. */ public void sendGiveDevicePowerStatus(int address) { - try { - mService.sendGiveDevicePowerStatus(mBinder, address); - } catch (RemoteException e) { - Log.e(TAG, "sendGiveDevicePowerStatus threw exception ", e); - } + Log.w(TAG, "In transition to HdmiControlManager. Will not work."); } /** @@ -133,11 +113,7 @@ public final class HdmiCecClient { * @return true if TV is on; otherwise false. */ public boolean isTvOn() { - try { - return mService.isTvOn(mBinder); - } catch (RemoteException e) { - Log.e(TAG, "isTvOn threw exception ", e); - } - return false; + Log.w(TAG, "In transition to HdmiControlManager. Will not work."); + return true; } } diff --git a/core/java/android/hardware/hdmi/HdmiCecManager.java b/core/java/android/hardware/hdmi/HdmiCecManager.java index 10b058c..03c46d8 100644 --- a/core/java/android/hardware/hdmi/HdmiCecManager.java +++ b/core/java/android/hardware/hdmi/HdmiCecManager.java @@ -45,15 +45,7 @@ public final class HdmiCecManager { * @return {@link HdmiCecClient} instance. {@code null} on failure. */ public HdmiCecClient getClient(int type, HdmiCecClient.Listener listener) { - if (mService == null) { - return null; - } - try { - IBinder b = mService.allocateLogicalDevice(type, getListenerWrapper(listener)); - return HdmiCecClient.create(mService, b); - } catch (RemoteException e) { - return null; - } + return HdmiCecClient.create(mService, null); } private IHdmiCecListener getListenerWrapper(final HdmiCecClient.Listener listener) { 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/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/Environment.java b/core/java/android/os/Environment.java index e98a26b..e84b695 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -191,6 +191,10 @@ public class Environment { return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName); } + public File[] buildExternalStorageAppMediaDirsForVold(String packageName) { + return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_MEDIA, packageName); + } + public File[] buildExternalStorageAppObbDirs(String packageName) { return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName); } 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/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 4963991..68b91cb 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -58,24 +58,6 @@ import java.util.concurrent.atomic.AtomicInteger; * argument of {@link android.content.Context#STORAGE_SERVICE}. */ public class StorageManager { - - /// Consts to match the password types in cryptfs.h - /** Master key is encrypted with a password. - */ - public static final int CRYPT_TYPE_PASSWORD = 0; - - /** Master key is encrypted with the default password. - */ - public static final int CRYPT_TYPE_DEFAULT = 1; - - /** Master key is encrypted with a pattern. - */ - public static final int CRYPT_TYPE_PATTERN = 2; - - /** Master key is encrypted with a pin. - */ - public static final int CRYPT_TYPE_PIN = 3; - private static final String TAG = "StorageManager"; private final ContentResolver mResolver; @@ -663,4 +645,14 @@ public class StorageManager { return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES, DEFAULT_FULL_THRESHOLD_BYTES); } + + /// Consts to match the password types in cryptfs.h + /** @hide */ + public static final int CRYPT_TYPE_PASSWORD = 0; + /** @hide */ + public static final int CRYPT_TYPE_DEFAULT = 1; + /** @hide */ + public static final int CRYPT_TYPE_PATTERN = 2; + /** @hide */ + public static final int CRYPT_TYPE_PIN = 3; } diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 5e005d0..d66fc0f 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -16,7 +16,10 @@ package android.preference; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.database.ContentObserver; import android.media.AudioManager; import android.media.Ringtone; @@ -45,11 +48,14 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba private final Context mContext; private final Handler mHandler; + private final H mUiHandler = new H(); private final Callback mCallback; private final Uri mDefaultUri; private final AudioManager mAudioManager; private final int mStreamType; private final int mMaxStreamVolume; + private final Receiver mReceiver = new Receiver(); + private final Observer mVolumeObserver; private int mOriginalStreamVolume; private Ringtone mRingtone; @@ -63,17 +69,6 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba private static final int MSG_INIT_SAMPLE = 3; private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000; - private ContentObserver mVolumeObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - if (mSeekBar != null && mAudioManager != null) { - int volume = mAudioManager.getStreamVolume(mStreamType); - mSeekBar.setProgress(volume); - } - } - }; - public SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback) { mContext = context; @@ -85,10 +80,11 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba mHandler = new Handler(thread.getLooper(), this); mCallback = callback; mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType); + mVolumeObserver = new Observer(mHandler); mContext.getContentResolver().registerContentObserver( System.getUriFor(System.VOLUME_SETTINGS[mStreamType]), false, mVolumeObserver); - + mReceiver.setListening(true); if (defaultUri == null) { if (mStreamType == AudioManager.STREAM_RING) { defaultUri = Settings.System.DEFAULT_RINGTONE_URI; @@ -103,6 +99,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } public void setSeekBar(SeekBar seekBar) { + if (mSeekBar != null) { + mSeekBar.setOnSeekBarChangeListener(null); + } mSeekBar = seekBar; mSeekBar.setOnSeekBarChangeListener(null); mSeekBar.setMax(mMaxStreamVolume); @@ -150,7 +149,11 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba mCallback.onSampleStarting(this); } if (mRingtone != null) { - mRingtone.play(); + try { + mRingtone.play(); + } catch (Throwable e) { + Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e); + } } } } @@ -172,6 +175,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba postStopSample(); mContext.getContentResolver().unregisterContentObserver(mVolumeObserver); mSeekBar.setOnSeekBarChangeListener(null); + mReceiver.setListening(false); + mHandler.getLooper().quitSafely(); } public void revertVolume() { @@ -252,4 +257,62 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba postSetVolume(mLastProgress); } } -}
\ No newline at end of file + + private final class H extends Handler { + private static final int UPDATE_SLIDER = 1; + + @Override + public void handleMessage(Message msg) { + if (msg.what == UPDATE_SLIDER) { + if (mSeekBar != null) { + mSeekBar.setProgress(msg.arg1); + mLastProgress = mSeekBar.getProgress(); + } + } + } + + public void postUpdateSlider(int volume) { + obtainMessage(UPDATE_SLIDER, volume, 0).sendToTarget(); + } + } + + private final class Observer extends ContentObserver { + public Observer(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + if (mSeekBar != null && mAudioManager != null) { + final int volume = mAudioManager.getStreamVolume(mStreamType); + mUiHandler.postUpdateSlider(volume); + } + } + } + + private final class Receiver extends BroadcastReceiver { + private boolean mListening; + + public void setListening(boolean listening) { + if (mListening == listening) return; + mListening = listening; + if (listening) { + final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION); + mContext.registerReceiver(this, filter); + } else { + mContext.unregisterReceiver(this); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + if (!AudioManager.VOLUME_CHANGED_ACTION.equals(intent.getAction())) return; + final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); + final int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); + if (mSeekBar != null && streamType == mStreamType && streamValue != -1) { + mUiHandler.postUpdateSlider(streamValue); + } + } + } +} diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index b53ea81..6db78f4 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -1152,8 +1152,6 @@ public final class ContactsContract { * address book index, which is usually the first letter of the sort key. * When this parameter is supplied, the row counts are returned in the * cursor extras bundle. - * - * @hide */ public final static class ContactCounts { @@ -1163,7 +1161,24 @@ public final class ContactsContract { * first letter of the sort key. This parameter does not affect the main * content of the cursor. * - * @hide + * <p> + * <pre> + * Example: + * Uri uri = Contacts.CONTENT_URI.buildUpon() + * .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true") + * .build(); + * Cursor cursor = getContentResolver().query(uri, + * new String[] {Contacts.DISPLAY_NAME}, + * null, null, null); + * Bundle bundle = cursor.getExtras(); + * if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES) && + * bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS)) { + * String sections[] = + * bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES); + * int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS); + * } + * </pre> + * </p> */ public static final String ADDRESS_BOOK_INDEX_EXTRAS = "address_book_index_extras"; @@ -1171,8 +1186,6 @@ public final class ContactsContract { * The array of address book index titles, which are returned in the * same order as the data in the cursor. * <p>TYPE: String[]</p> - * - * @hide */ public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "address_book_index_titles"; @@ -1180,8 +1193,6 @@ public final class ContactsContract { * The array of group counts for the corresponding group. Contains the same number * of elements as the EXTRA_ADDRESS_BOOK_INDEX_TITLES array. * <p>TYPE: int[]</p> - * - * @hide */ public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "address_book_index_counts"; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e9ffc52..bec401e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4461,6 +4461,12 @@ public final class Settings { INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF; /** + * Whether the device should wake when the wake gesture sensor detects motion. + * @hide + */ + public static final String WAKE_GESTURE_ENABLED = "wake_gesture_enabled"; + + /** * The current night mode that has been selected by the user. Owned * and controlled by UiModeManagerService. Constants are as per * UiModeManager. diff --git a/core/java/android/provider/TvContract.java b/core/java/android/provider/TvContract.java index e4f93a8..0d90a16 100644 --- a/core/java/android/provider/TvContract.java +++ b/core/java/android/provider/TvContract.java @@ -74,7 +74,7 @@ public final class TvContract { * * @hide */ - public static final String PARAM_BROWSABLE_ONLY = "browable_only"; + public static final String PARAM_BROWSABLE_ONLY = "browsable_only"; /** * Builds a URI that points to a specific channel. 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..2e9077a 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; @@ -48,9 +47,22 @@ import com.android.internal.app.IVoiceInteractorRequest; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; +import java.lang.ref.WeakReference; + import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +/** + * An active voice interaction session, providing a facility for the implementation + * to interact with the user in the voice interaction layer. This interface is no shown + * by default, but you can request that it be shown with {@link #showWindow()}, which + * will result in a later call to {@link #onCreateContentView()} in which the UI can be + * built + * + * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish} + * when done. It can also initiate voice interactions with applications by calling + * {@link #startVoiceActivity}</p>. + */ public abstract class VoiceInteractionSession implements KeyEvent.Callback { static final String TAG = "VoiceInteractionSession"; static final boolean DEBUG = true; @@ -81,11 +93,14 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { final Insets mTmpInsets = new Insets(); final int[] mTmpLocation = new int[2]; + final WeakReference<VoiceInteractionSession> mWeakRef + = new WeakReference<VoiceInteractionSession>(this); + final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() { @Override public IVoiceInteractorRequest startConfirmation(String callingPackage, - IVoiceInteractorCallback callback, String prompt, Bundle extras) { - Request request = findRequest(callback, true); + IVoiceInteractorCallback callback, CharSequence prompt, Bundle extras) { + Request request = newRequest(callback); mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION, new Caller(callingPackage, Binder.getCallingUid()), request, prompt, extras)); @@ -93,9 +108,19 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } @Override + public IVoiceInteractorRequest startAbortVoice(String callingPackage, + IVoiceInteractorCallback callback, CharSequence message, Bundle extras) { + Request request = newRequest(callback); + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_ABORT_VOICE, + new Caller(callingPackage, Binder.getCallingUid()), request, + message, extras)); + return request.mInterface; + } + + @Override public IVoiceInteractorRequest startCommand(String callingPackage, IVoiceInteractorCallback callback, String command, Bundle extras) { - Request request = findRequest(callback, true); + Request request = newRequest(callback); mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND, new Caller(callingPackage, Binder.getCallingUid()), request, command, extras)); @@ -144,29 +169,60 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() { @Override public void cancel() throws RemoteException { - mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this)); + VoiceInteractionSession session = mSession.get(); + if (session != null) { + session.mHandlerCaller.sendMessage( + session.mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this)); + } } }; final IVoiceInteractorCallback mCallback; - final HandlerCaller mHandlerCaller; - Request(IVoiceInteractorCallback callback, HandlerCaller handlerCaller) { + final WeakReference<VoiceInteractionSession> mSession; + + Request(IVoiceInteractorCallback callback, VoiceInteractionSession session) { mCallback = callback; - mHandlerCaller = handlerCaller; + mSession = session.mWeakRef; + } + + void finishRequest() { + VoiceInteractionSession session = mSession.get(); + if (session == null) { + throw new IllegalStateException("VoiceInteractionSession has been destroyed"); + } + Request req = session.removeRequest(mInterface.asBinder()); + if (req == null) { + throw new IllegalStateException("Request not active: " + this); + } else if (req != this) { + throw new IllegalStateException("Current active request " + req + + " not same as calling request " + this); + } } public void sendConfirmResult(boolean confirmed, Bundle result) { try { if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface + " confirmed=" + confirmed + " result=" + result); + finishRequest(); mCallback.deliverConfirmationResult(mInterface, confirmed, result); } catch (RemoteException e) { } } + public void sendAbortVoiceResult(Bundle result) { + try { + if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface + + " result=" + result); + finishRequest(); + mCallback.deliverAbortVoiceResult(mInterface, result); + } catch (RemoteException e) { + } + } + public void sendCommandResult(boolean complete, Bundle result) { try { if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface + " result=" + result); + finishRequest(); mCallback.deliverCommandResult(mInterface, complete, result); } catch (RemoteException e) { } @@ -175,6 +231,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { public void sendCancelResult() { try { if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface); + finishRequest(); mCallback.deliverCancel(mInterface); } catch (RemoteException e) { } @@ -192,9 +249,10 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } static final int MSG_START_CONFIRMATION = 1; - static final int MSG_START_COMMAND = 2; - static final int MSG_SUPPORTS_COMMANDS = 3; - static final int MSG_CANCEL = 4; + static final int MSG_START_ABORT_VOICE = 2; + static final int MSG_START_COMMAND = 3; + static final int MSG_SUPPORTS_COMMANDS = 4; + static final int MSG_CANCEL = 5; static final int MSG_TASK_STARTED = 100; static final int MSG_TASK_FINISHED = 101; @@ -210,9 +268,16 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface + " prompt=" + args.arg3 + " extras=" + args.arg4); - onConfirm((Caller)args.arg1, (Request)args.arg2, (String)args.arg3, + onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3, (Bundle)args.arg4); break; + case MSG_START_ABORT_VOICE: + args = (SomeArgs)msg.obj; + if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((Request) args.arg2).mInterface + + " message=" + args.arg3 + " extras=" + args.arg4); + onAbortVoice((Caller) args.arg1, (Request) args.arg2, (CharSequence) args.arg3, + (Bundle) args.arg4); + break; case MSG_START_COMMAND: args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface @@ -330,18 +395,20 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mCallbacks, true); } - Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) { + Request newRequest(IVoiceInteractorCallback callback) { + synchronized (this) { + Request req = new Request(callback, this); + mActiveRequests.put(req.mInterface.asBinder(), req); + return req; + } + } + + Request removeRequest(IBinder reqInterface) { synchronized (this) { - Request req = mActiveRequests.get(callback.asBinder()); + Request req = mActiveRequests.get(reqInterface); if (req != null) { - if (newRequest) { - throw new IllegalArgumentException("Given request callback " + callback - + " is already active"); - } - return req; + mActiveRequests.remove(req); } - req = new Request(callback, mHandlerCaller); - mActiveRequests.put(callback.asBinder(), req); return req; } } @@ -426,6 +493,27 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mTheme = theme; } + /** + * Ask that a new activity be started for voice interaction. This will create a + * new dedicated task in the activity manager for this voice interaction session; + * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK} + * will be set for you to make it a new task. + * + * <p>The newly started activity will be displayed to the user in a special way, as + * a layer under the voice interaction UI.</p> + * + * <p>As the voice activity runs, it can retrieve a {@link android.app.VoiceInteractor} + * through which it can perform voice interactions through your session. These requests + * for voice interactions will appear as callbacks on {@link #onGetSupportedCommands}, + * {@link #onConfirm}, {@link #onCommand}, and {@link #onCancel}. + * + * <p>You will receive a call to {@link #onTaskStarted} when the task starts up + * and {@link #onTaskFinished} when the last activity has finished. + * + * @param intent The Intent to start this voice interaction. The given Intent will + * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since + * this is part of a voice interaction. + */ public void startVoiceActivity(Intent intent) { if (mToken == null) { throw new IllegalStateException("Can't call before onCreate()"); @@ -440,14 +528,23 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } } + /** + * Convenience for inflating views. + */ public LayoutInflater getLayoutInflater() { return mInflater; } + /** + * Retrieve the window being used to show the session's UI. + */ public Dialog getWindow() { return mWindow; } + /** + * Finish the session. + */ public void finish() { if (mToken == null) { throw new IllegalStateException("Can't call before onCreate()"); @@ -459,6 +556,12 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } } + /** + * Initiatize a new session. + * + * @param args The arguments that were supplied to + * {@link VoiceInteractionService#startSession VoiceInteractionService.startSession}. + */ public void onCreate(Bundle args) { mTheme = mTheme != 0 ? mTheme : com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession; @@ -473,9 +576,15 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mWindow.setToken(mToken); } + /** + * Last callback to the session as it is being finished. + */ public void onDestroy() { } + /** + * Hook in which to create the session's UI. + */ public View onCreateContentView() { return null; } @@ -508,6 +617,11 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { finish(); } + /** + * Sessions automatically watch for requests that all system UI be closed (such as when + * the user presses HOME), which will appear here. The default implementation always + * calls {@link #finish}. + */ public void onCloseSystemDialogs() { finish(); } @@ -531,15 +645,98 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { outInsets.touchableRegion.setEmpty(); } + /** + * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)} + * has actually started. + * + * @param intent The original {@link Intent} supplied to + * {@link #startVoiceActivity(android.content.Intent)}. + * @param taskId Unique ID of the now running task. + */ public void onTaskStarted(Intent intent, int taskId) { } + /** + * Called when the last activity of a task initiated by + * {@link #startVoiceActivity(android.content.Intent)} has finished. The default + * implementation calls {@link #finish()} on the assumption that this represents + * the completion of a voice action. You can override the implementation if you would + * like a different behavior. + * + * @param intent The original {@link Intent} supplied to + * {@link #startVoiceActivity(android.content.Intent)}. + * @param taskId Unique ID of the finished task. + */ public void onTaskFinished(Intent intent, int taskId) { finish(); } - public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands); - public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras); + /** + * Request to query for what extended commands the session supports. + * + * @param caller Who is making the request. + * @param commands An array of commands that are being queried. + * @return Return an array of booleans indicating which of each entry in the + * command array is supported. A true entry in the array indicates the command + * is supported; false indicates it is not. The default implementation returns + * an array of all false entries. + */ + public boolean[] onGetSupportedCommands(Caller caller, String[] commands) { + return new boolean[commands.length]; + } + + /** + * Request to confirm with the user before proceeding with an unrecoverable operation, + * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest + * VoiceInteractor.ConfirmationRequest}. + * + * @param caller Who is making the request. + * @param request The active request. + * @param prompt The prompt informing the user of what will happen, as per + * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}. + * @param extras Any additional information, as per + * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}. + */ + public abstract void onConfirm(Caller caller, Request request, CharSequence prompt, + Bundle extras); + + /** + * Request to abort the voice interaction session because the voice activity can not + * complete its interaction using voice. Corresponds to + * {@link android.app.VoiceInteractor.AbortVoiceRequest + * VoiceInteractor.AbortVoiceRequest}. The default implementation just sends an empty + * confirmation back to allow the activity to exit. + * + * @param caller Who is making the request. + * @param request The active request. + * @param message The message informing the user of the problem, as per + * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}. + * @param extras Any additional information, as per + * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}. + */ + public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) { + request.sendAbortVoiceResult(null); + } + + /** + * Process an arbitrary extended command from the caller, + * corresponding to a {@link android.app.VoiceInteractor.CommandRequest + * VoiceInteractor.CommandRequest}. + * + * @param caller Who is making the request. + * @param request The active request. + * @param command The command that is being executed, as per + * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}. + * @param extras Any additional information, as per + * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}. + */ public abstract void onCommand(Caller caller, Request request, String command, Bundle extras); + + /** + * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request} + * that was previously delivered to {@link #onConfirm} or {@link #onCommand}. + * + * @param request The request that is being canceled. + */ public abstract void onCancel(Request request); } diff --git a/core/java/android/speech/tts/Markup.java b/core/java/android/speech/tts/Markup.java new file mode 100644 index 0000000..c886e5d --- /dev/null +++ b/core/java/android/speech/tts/Markup.java @@ -0,0 +1,537 @@ +package android.speech.tts; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class that provides markup to a synthesis request to control aspects of speech. + * <p> + * Markup itself is a feature agnostic data format; the {@link Utterance} class defines the currently + * available set of features and should be used to construct instances of the Markup class. + * </p> + * <p> + * A marked up sentence is a tree. Each node has a type, an optional plain text, a set of + * parameters, and a list of children. + * The <b>type</b> defines what it contains, e.g. "text", "date", "measure", etc. A Markup node + * can be either a part of sentence (often a leaf node), or node altering some property of its + * children (node with children). The top level node has to be of type "utterance" and its children + * are synthesized in order. + * The <b>plain text</b> is optional except for the top level node. If the synthesis engine does not + * support Markup at all, it should use the plain text of the top level node. If an engine does not + * recognize or support a node type, it will try to use the plain text of that node if provided. If + * the plain text is null, it will synthesize its children in order. + * <b>Parameters</b> are key-value pairs specific to each node type. In case of a date node the + * parameters may be for example "month: 7" and "day: 10". + * The <b>nested markups</b> are children and can for example be used to nest semiotic classes (a + * measure may have a node of type "decimal" as its child) or to modify some property of its + * children. See "plain text" on how they are processed if the parent of the children is unknown to + * the engine. + * <p> + */ +public final class Markup implements Parcelable { + + private String mType; + private String mPlainText; + + private Bundle mParameters = new Bundle(); + private List<Markup> mNestedMarkups = new ArrayList<Markup>(); + + private static final String TYPE = "type"; + private static final String PLAIN_TEXT = "plain_text"; + private static final String MARKUP = "markup"; + + private static final String IDENTIFIER_REGEX = "([0-9a-z_]+)"; + private static final Pattern legalIdentifierPattern = Pattern.compile(IDENTIFIER_REGEX); + + /** + * Constructs an empty markup. + */ + public Markup() {} + + /** + * Constructs a markup of the given type. + */ + public Markup(String type) { + setType(type); + } + + /** + * Returns the type of this node; can be null. + */ + public String getType() { + return mType; + } + + /** + * Sets the type of this node. can be null. May only contain [0-9a-z_]. + */ + public void setType(String type) { + if (type != null) { + Matcher matcher = legalIdentifierPattern.matcher(type); + if (!matcher.matches()) { + throw new IllegalArgumentException("Type cannot be empty and may only contain " + + "0-9, a-z and underscores."); + } + } + mType = type; + } + + /** + * Returns this node's plain text; can be null. + */ + public String getPlainText() { + return mPlainText; + } + + /** + * Sets this nodes's plain text; can be null. + */ + public void setPlainText(String plainText) { + mPlainText = plainText; + } + + /** + * Adds or modifies a parameter. + * @param key The key; may only contain [0-9a-z_] and cannot be "type" or "plain_text". + * @param value The value. + * @throws An {@link IllegalArgumentException} if the key is null or empty. + * @return this + */ + public Markup setParameter(String key, String value) { + if (key == null || key.isEmpty()) { + throw new IllegalArgumentException("Key cannot be null or empty."); + } + if (key.equals("type")) { + throw new IllegalArgumentException("Key cannot be \"type\"."); + } + if (key.equals("plain_text")) { + throw new IllegalArgumentException("Key cannot be \"plain_text\"."); + } + Matcher matcher = legalIdentifierPattern.matcher(key); + if (!matcher.matches()) { + throw new IllegalArgumentException("Key may only contain 0-9, a-z and underscores."); + } + + if (value != null) { + mParameters.putString(key, value); + } else { + removeParameter(key); + } + return this; + } + + /** + * Removes the parameter with the given key + */ + public void removeParameter(String key) { + mParameters.remove(key); + } + + /** + * Returns the value of the parameter. + * @param key The parameter key. + * @return The value of the parameter or null if the parameter is not set. + */ + public String getParameter(String key) { + return mParameters.getString(key); + } + + /** + * Returns the number of parameters that have been set. + */ + public int parametersSize() { + return mParameters.size(); + } + + /** + * Appends a child to the list of children + * @param markup The child. + * @return This instance. + * @throws {@link IllegalArgumentException} if markup is null. + */ + public Markup addNestedMarkup(Markup markup) { + if (markup == null) { + throw new IllegalArgumentException("Nested markup cannot be null"); + } + mNestedMarkups.add(markup); + return this; + } + + /** + * Removes the given node from its children. + * @param markup The child to remove. + * @return True if this instance was modified by this operation, false otherwise. + */ + public boolean removeNestedMarkup(Markup markup) { + return mNestedMarkups.remove(markup); + } + + /** + * Returns the index'th child. + * @param i The index of the child. + * @return The child. + * @throws {@link IndexOutOfBoundsException} if i < 0 or i >= nestedMarkupSize() + */ + public Markup getNestedMarkup(int i) { + return mNestedMarkups.get(i); + } + + + /** + * Returns the number of children. + */ + public int nestedMarkupSize() { + return mNestedMarkups.size(); + } + + /** + * Returns a string representation of this Markup instance. Can be deserialized back to a Markup + * instance with markupFromString(). + */ + public String toString() { + StringBuilder out = new StringBuilder(); + if (mType != null) { + out.append(TYPE + ": \"" + mType + "\""); + } + if (mPlainText != null) { + out.append(out.length() > 0 ? " " : ""); + out.append(PLAIN_TEXT + ": \"" + escapeQuotedString(mPlainText) + "\""); + } + // Sort the parameters alphabetically by key so we have a stable output. + SortedMap<String, String> sortedMap = new TreeMap<String, String>(); + for (String key : mParameters.keySet()) { + sortedMap.put(key, mParameters.getString(key)); + } + for (Map.Entry<String, String> entry : sortedMap.entrySet()) { + out.append(out.length() > 0 ? " " : ""); + out.append(entry.getKey() + ": \"" + escapeQuotedString(entry.getValue()) + "\""); + } + for (Markup m : mNestedMarkups) { + out.append(out.length() > 0 ? " " : ""); + String nestedStr = m.toString(); + if (nestedStr.isEmpty()) { + out.append(MARKUP + " {}"); + } else { + out.append(MARKUP + " { " + m.toString() + " }"); + } + } + return out.toString(); + } + + /** + * Escapes backslashes and double quotes in the plain text and parameter values before this + * instance is written to a string. + * @param str The string to escape. + * @return The escaped string. + */ + private static String escapeQuotedString(String str) { + StringBuilder out = new StringBuilder(); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c == '"') { + out.append("\\\""); + } else if (str.charAt(i) == '\\') { + out.append("\\\\"); + } else { + out.append(c); + } + } + return out.toString(); + } + + /** + * The reverse of the escape method, returning plain text and parameter values to their original + * form. + * @param str An escaped string. + * @return The unescaped string. + */ + private static String unescapeQuotedString(String str) { + StringBuilder out = new StringBuilder(); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c == '\\') { + i++; + if (i >= str.length()) { + throw new IllegalArgumentException("Unterminated escape sequence in string: " + + str); + } + c = str.charAt(i); + if (c == '\\') { + out.append("\\"); + } else if (c == '"') { + out.append("\""); + } else { + throw new IllegalArgumentException("Unsupported escape sequence: \\" + c + + " in string " + str); + } + } else { + out.append(c); + } + } + return out.toString(); + } + + /** + * Returns true if the given string consists only of whitespace. + * @param str The string to check. + * @return True if the given string consists only of whitespace. + */ + private static boolean isWhitespace(String str) { + return Pattern.matches("\\s*", str); + } + + /** + * Parses the given string, and overrides the values of this instance with those contained + * in the given string. + * @param str The string to parse; can have superfluous whitespace. + * @return An empty string on success, else the remainder of the string that could not be + * parsed. + */ + private String fromReadableString(String str) { + while (!isWhitespace(str)) { + String newStr = matchValue(str); + if (newStr == null) { + newStr = matchMarkup(str); + + if (newStr == null) { + return str; + } + } + str = newStr; + } + return ""; + } + + // Matches: key : "value" + // where key is an identifier and value can contain escaped quotes + // there may be superflouous whitespace + // The value string may contain quotes and backslashes. + private static final String OPTIONAL_WHITESPACE = "\\s*"; + private static final String VALUE_REGEX = "((\\\\.|[^\\\"])*)"; + private static final String KEY_VALUE_REGEX = + "\\A" + OPTIONAL_WHITESPACE + // start of string + IDENTIFIER_REGEX + OPTIONAL_WHITESPACE + ":" + OPTIONAL_WHITESPACE + // key: + "\"" + VALUE_REGEX + "\""; // "value" + private static final Pattern KEY_VALUE_PATTERN = Pattern.compile(KEY_VALUE_REGEX); + + /** + * Tries to match a key-value pair at the start of the string. If found, add that as a parameter + * of this instance. + * @param str The string to parse. + * @return The remainder of the string without the parsed key-value pair on success, else null. + */ + private String matchValue(String str) { + // Matches: key: "value" + Matcher matcher = KEY_VALUE_PATTERN.matcher(str); + if (!matcher.find()) { + return null; + } + String key = matcher.group(1); + String value = matcher.group(2); + + if (key == null || value == null) { + return null; + } + String unescapedValue = unescapeQuotedString(value); + if (key.equals(TYPE)) { + this.mType = unescapedValue; + } else if (key.equals(PLAIN_TEXT)) { + this.mPlainText = unescapedValue; + } else { + setParameter(key, unescapedValue); + } + + return str.substring(matcher.group(0).length()); + } + + // matches 'markup {' + private static final Pattern OPEN_MARKUP_PATTERN = + Pattern.compile("\\A" + OPTIONAL_WHITESPACE + MARKUP + OPTIONAL_WHITESPACE + "\\{"); + // matches '}' + private static final Pattern CLOSE_MARKUP_PATTERN = + Pattern.compile("\\A" + OPTIONAL_WHITESPACE + "\\}"); + + /** + * Tries to parse a Markup specification from the start of the string. If so, add that markup to + * the list of nested Markup's of this instance. + * @param str The string to parse. + * @return The remainder of the string without the parsed Markup on success, else null. + */ + private String matchMarkup(String str) { + // find and strip "markup {" + Matcher matcher = OPEN_MARKUP_PATTERN.matcher(str); + + if (!matcher.find()) { + return null; + } + String strRemainder = str.substring(matcher.group(0).length()); + // parse and strip markup contents + Markup nestedMarkup = new Markup(); + strRemainder = nestedMarkup.fromReadableString(strRemainder); + + // find and strip "}" + Matcher matcherClose = CLOSE_MARKUP_PATTERN.matcher(strRemainder); + if (!matcherClose.find()) { + return null; + } + strRemainder = strRemainder.substring(matcherClose.group(0).length()); + + // Everything parsed, add markup + this.addNestedMarkup(nestedMarkup); + + // Return remainder + return strRemainder; + } + + /** + * Returns a Markup instance from the string representation generated by toString(). + * @param string The string representation generated by toString(). + * @return The new Markup instance. + * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed. + */ + public static Markup markupFromString(String string) throws IllegalArgumentException { + Markup m = new Markup(); + if (m.fromReadableString(string).isEmpty()) { + return m; + } else { + throw new IllegalArgumentException("Cannot parse input to Markup"); + } + } + + /** + * Compares the specified object with this Markup for equality. + * @return True if the given object is a Markup instance with the same type, plain text, + * parameters and the nested markups are also equal to each other and in the same order. + */ + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !(o instanceof Markup) ) return false; + Markup m = (Markup) o; + + if (nestedMarkupSize() != this.nestedMarkupSize()) { + return false; + } + + if (!(mType == null ? m.mType == null : mType.equals(m.mType))) { + return false; + } + if (!(mPlainText == null ? m.mPlainText == null : mPlainText.equals(m.mPlainText))) { + return false; + } + if (!equalBundles(mParameters, m.mParameters)) { + return false; + } + + for (int i = 0; i < this.nestedMarkupSize(); i++) { + if (!mNestedMarkups.get(i).equals(m.mNestedMarkups.get(i))) { + return false; + } + } + + return true; + } + + /** + * Checks if two bundles are equal to each other. Used by equals(o). + */ + private boolean equalBundles(Bundle one, Bundle two) { + if (one == null || two == null) { + return false; + } + + if(one.size() != two.size()) { + return false; + } + + Set<String> valuesOne = one.keySet(); + for(String key : valuesOne) { + Object valueOne = one.get(key); + Object valueTwo = two.get(key); + if (valueOne instanceof Bundle && valueTwo instanceof Bundle && + !equalBundles((Bundle) valueOne, (Bundle) valueTwo)) { + return false; + } else if (valueOne == null) { + if (valueTwo != null || !two.containsKey(key)) { + return false; + } + } else if(!valueOne.equals(valueTwo)) { + return false; + } + } + return true; + } + + /** + * Returns an unmodifiable list of the children. + * @return An unmodifiable list of children that throws an {@link UnsupportedOperationException} + * if an attempt is made to modify it + */ + public List<Markup> getNestedMarkups() { + return Collections.unmodifiableList(mNestedMarkups); + } + + /** + * @hide + */ + public Markup(Parcel in) { + mType = in.readString(); + mPlainText = in.readString(); + mParameters = in.readBundle(); + in.readList(mNestedMarkups, Markup.class.getClassLoader()); + } + + /** + * Creates a deep copy of the given markup. + */ + public Markup(Markup markup) { + mType = markup.mType; + mPlainText = markup.mPlainText; + mParameters = markup.mParameters; + for (Markup nested : markup.getNestedMarkups()) { + addNestedMarkup(new Markup(nested)); + } + } + + /** + * @hide + */ + public int describeContents() { + return 0; + } + + /** + * @hide + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mType); + dest.writeString(mPlainText); + dest.writeBundle(mParameters); + dest.writeList(mNestedMarkups); + } + + /** + * @hide + */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Markup createFromParcel(Parcel in) { + return new Markup(in); + } + + public Markup[] newArray(int size) { + return new Markup[size]; + } + }; +} + diff --git a/core/java/android/speech/tts/SynthesisRequestV2.java b/core/java/android/speech/tts/SynthesisRequestV2.java index a1da49c..130e3f9 100644 --- a/core/java/android/speech/tts/SynthesisRequestV2.java +++ b/core/java/android/speech/tts/SynthesisRequestV2.java @@ -4,11 +4,12 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.speech.tts.TextToSpeechClient.UtteranceId; +import android.util.Log; /** * Service-side representation of a synthesis request from a V2 API client. Contains: * <ul> - * <li>The utterance to synthesize</li> + * <li>The markup object to synthesize containing the utterance.</li> * <li>The id of the utterance (String, result of {@link UtteranceId#toUniqueString()}</li> * <li>The synthesis voice name (String, result of {@link VoiceInfo#getName()})</li> * <li>Voice parameters (Bundle of parameters)</li> @@ -16,8 +17,11 @@ import android.speech.tts.TextToSpeechClient.UtteranceId; * </ul> */ public final class SynthesisRequestV2 implements Parcelable { - /** Synthesis utterance. */ - private final String mText; + + private static final String TAG = "SynthesisRequestV2"; + + /** Synthesis markup */ + private final Markup mMarkup; /** Synthesis id. */ private final String mUtteranceId; @@ -34,9 +38,9 @@ public final class SynthesisRequestV2 implements Parcelable { /** * Constructor for test purposes. */ - public SynthesisRequestV2(String text, String utteranceId, String voiceName, + public SynthesisRequestV2(Markup markup, String utteranceId, String voiceName, Bundle voiceParams, Bundle audioParams) { - this.mText = text; + this.mMarkup = markup; this.mUtteranceId = utteranceId; this.mVoiceName = voiceName; this.mVoiceParams = voiceParams; @@ -49,15 +53,18 @@ public final class SynthesisRequestV2 implements Parcelable { * @hide */ public SynthesisRequestV2(Parcel in) { - this.mText = in.readString(); + this.mMarkup = (Markup) in.readValue(Markup.class.getClassLoader()); this.mUtteranceId = in.readString(); this.mVoiceName = in.readString(); this.mVoiceParams = in.readBundle(); this.mAudioParams = in.readBundle(); } - SynthesisRequestV2(String text, String utteranceId, RequestConfig rconfig) { - this.mText = text; + /** + * Constructor to request the synthesis of a sentence. + */ + SynthesisRequestV2(Markup markup, String utteranceId, RequestConfig rconfig) { + this.mMarkup = markup; this.mUtteranceId = utteranceId; this.mVoiceName = rconfig.getVoice().getName(); this.mVoiceParams = rconfig.getVoiceParams(); @@ -71,7 +78,7 @@ public final class SynthesisRequestV2 implements Parcelable { */ @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mText); + dest.writeValue(mMarkup); dest.writeString(mUtteranceId); dest.writeString(mVoiceName); dest.writeBundle(mVoiceParams); @@ -82,7 +89,18 @@ public final class SynthesisRequestV2 implements Parcelable { * @return the text which should be synthesized. */ public String getText() { - return mText; + if (mMarkup.getPlainText() == null) { + Log.e(TAG, "Plaintext of markup is null."); + return ""; + } + return mMarkup.getPlainText(); + } + + /** + * @return the markup which should be synthesized. + */ + public Markup getMarkup() { + return mMarkup; } /** diff --git a/core/java/android/speech/tts/TextToSpeechClient.java b/core/java/android/speech/tts/TextToSpeechClient.java index 85f702b..e17b498 100644 --- a/core/java/android/speech/tts/TextToSpeechClient.java +++ b/core/java/android/speech/tts/TextToSpeechClient.java @@ -512,7 +512,6 @@ public class TextToSpeechClient { } } - /** * Connects the client to TTS service. This method returns immediately, and connects to the * service in the background. @@ -876,7 +875,7 @@ public class TextToSpeechClient { private static final String ACTION_QUEUE_SPEAK_NAME = "queueSpeak"; /** - * Speaks the string using the specified queuing strategy using current + * Speaks the string using the specified queuing strategy and the current * voice. This method is asynchronous, i.e. the method just adds the request * to the queue of TTS requests and then returns. The synthesis might not * have finished (or even started!) at the time when this method returns. @@ -887,12 +886,35 @@ public class TextToSpeechClient { * in {@link RequestCallbacks}. * @param config Synthesis request configuration. Can't be null. Has to contain a * voice. - * @param callbacks Synthesis request callbacks. If null, default request + * @param callbacks Synthesis request callbacks. If null, the default request * callbacks object will be used. */ public void queueSpeak(final String utterance, final UtteranceId utteranceId, final RequestConfig config, final RequestCallbacks callbacks) { + queueSpeak(createMarkupFromString(utterance), utteranceId, config, callbacks); + } + + /** + * Speaks the {@link Markup} (which can be constructed with {@link Utterance}) using + * the specified queuing strategy and the current voice. This method is + * asynchronous, i.e. the method just adds the request to the queue of TTS + * requests and then returns. The synthesis might not have finished (or even + * started!) at the time when this method returns. + * + * @param markup The Markup to be spoken. The written equivalent of the spoken + * text should be no longer than 1000 characters. + * @param utteranceId Unique identificator used to track the synthesis progress + * in {@link RequestCallbacks}. + * @param config Synthesis request configuration. Can't be null. Has to contain a + * voice. + * @param callbacks Synthesis request callbacks. If null, the default request + * callbacks object will be used. + */ + public void queueSpeak(final Markup markup, + final UtteranceId utteranceId, + final RequestConfig config, + final RequestCallbacks callbacks) { runAction(new Action(ACTION_QUEUE_SPEAK_NAME) { @Override public void run(ITextToSpeechService service) throws RemoteException { @@ -908,7 +930,7 @@ public class TextToSpeechClient { int queueResult = service.speakV2( getCallerIdentity(), - new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), config)); + new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config)); if (queueResult != Status.SUCCESS) { removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); } @@ -931,12 +953,37 @@ public class TextToSpeechClient { * @param outputFile File to write the generated audio data to. * @param config Synthesis request configuration. Can't be null. Have to contain a * voice. - * @param callbacks Synthesis request callbacks. If null, default request + * @param callbacks Synthesis request callbacks. If null, the default request * callbacks object will be used. */ public void queueSynthesizeToFile(final String utterance, final UtteranceId utteranceId, final File outputFile, final RequestConfig config, final RequestCallbacks callbacks) { + queueSynthesizeToFile(createMarkupFromString(utterance), utteranceId, outputFile, config, callbacks); + } + + /** + * Synthesizes the given {@link Markup} (can be constructed with {@link Utterance}) + * to a file using the specified parameters. This method is asynchronous, i.e. the + * method just adds the request to the queue of TTS requests and then returns. The + * synthesis might not have finished (or even started!) at the time when this method + * returns. + * + * @param markup The Markup that should be synthesized. The written equivalent of + * the spoken text should be no longer than 1000 characters. + * @param utteranceId Unique identificator used to track the synthesis progress + * in {@link RequestCallbacks}. + * @param outputFile File to write the generated audio data to. + * @param config Synthesis request configuration. Can't be null. Have to contain a + * voice. + * @param callbacks Synthesis request callbacks. If null, the default request + * callbacks object will be used. + */ + public void queueSynthesizeToFile( + final Markup markup, + final UtteranceId utteranceId, + final File outputFile, final RequestConfig config, + final RequestCallbacks callbacks) { runAction(new Action(ACTION_QUEUE_SYNTHESIZE_TO_FILE) { @Override public void run(ITextToSpeechService service) throws RemoteException { @@ -964,8 +1011,7 @@ public class TextToSpeechClient { int queueResult = service.synthesizeToFileDescriptorV2(getCallerIdentity(), fileDescriptor, - new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), - config)); + new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config)); fileDescriptor.close(); if (queueResult != Status.SUCCESS) { removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); @@ -981,6 +1027,13 @@ public class TextToSpeechClient { }); } + private static Markup createMarkupFromString(String str) { + return new Utterance() + .append(new Utterance.TtsText(str)) + .setNoWarningOnFallback(true) + .createMarkup(); + } + private static final String ACTION_QUEUE_SILENCE_NAME = "queueSilence"; /** diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 6b899d9..14a4024 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -352,6 +352,12 @@ public abstract class TextToSpeechService extends Service { params.putString(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS, "true"); } + String noWarning = request.getMarkup().getParameter(Utterance.KEY_NO_WARNING_ON_FALLBACK); + if (noWarning == null || noWarning.equals("false")) { + Log.w("TextToSpeechService", "The synthesis engine does not support Markup, falling " + + "back to the given plain text."); + } + // Build V1 request SynthesisRequest requestV1 = new SynthesisRequest(request.getText(), params); Locale locale = selectedVoice.getLocale(); @@ -856,14 +862,53 @@ public abstract class TextToSpeechService extends Service { } } + /** + * Estimate of the character count equivalent of a Markup instance. Calculated + * by summing the characters of all Markups of type "text". Each other node + * is counted as a single character, as the character count of other nodes + * is non-trivial to calculate and we don't want to accept arbitrarily large + * requests. + */ + private int estimateSynthesisLengthFromMarkup(Markup m) { + int size = 0; + if (m.getType() != null && + m.getType().equals("text") && + m.getParameter("text") != null) { + size += m.getParameter("text").length(); + } else if (m.getType() == null || + !m.getType().equals("utterance")) { + size += 1; + } + for (Markup nested : m.getNestedMarkups()) { + size += estimateSynthesisLengthFromMarkup(nested); + } + return size; + } + @Override public boolean isValid() { - if (mSynthesisRequest.getText() == null) { - Log.e(TAG, "null synthesis text"); + if (mSynthesisRequest.getMarkup() == null) { + Log.e(TAG, "No markup in request."); return false; } - if (mSynthesisRequest.getText().length() >= TextToSpeech.getMaxSpeechInputLength()) { - Log.w(TAG, "Text too long: " + mSynthesisRequest.getText().length() + " chars"); + String type = mSynthesisRequest.getMarkup().getType(); + if (type == null) { + Log.w(TAG, "Top level markup node should have type \"utterance\", not null"); + return false; + } else if (!type.equals("utterance")) { + Log.w(TAG, "Top level markup node should have type \"utterance\" instead of " + + "\"" + type + "\""); + return false; + } + + int estimate = estimateSynthesisLengthFromMarkup(mSynthesisRequest.getMarkup()); + if (estimate >= TextToSpeech.getMaxSpeechInputLength()) { + Log.w(TAG, "Text too long: estimated size of text was " + estimate + " chars."); + return false; + } + + if (estimate <= 0) { + Log.e(TAG, "null synthesis text"); return false; } diff --git a/core/java/android/speech/tts/Utterance.java b/core/java/android/speech/tts/Utterance.java new file mode 100644 index 0000000..0a29283 --- /dev/null +++ b/core/java/android/speech/tts/Utterance.java @@ -0,0 +1,595 @@ +package android.speech.tts; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class acts as a builder for {@link Markup} instances. + * <p> + * Each Utterance consists of a list of the semiotic classes ({@link Utterance.TtsCardinal} and + * {@link Utterance.TtsText}). + * <p>Each semiotic class can be supplied with morphosyntactic features + * (gender, animacy, multiplicity and case), it is up to the synthesis engine to use this + * information during synthesis. + * Examples where morphosyntactic features matter: + * <ul> + * <li>In French, the number one is verbalized differently based on the gender of the noun + * it modifies. "un homme" (one man) versus "une femme" (one woman). + * <li>In German the grammatical case (accusative, locative, etc) needs to be included to be + * verbalize correctly. In German you'd have the sentence "Sie haben 1 kilometer vor Ihnen" (You + * have 1 kilometer ahead of you), "1" in this case needs to become inflected to the accusative + * form ("einen") instead of the nominative form "ein". + * </p> + * <p> + * Utterance usage example: + * Markup m1 = new Utterance().append("The Eiffel Tower is") + * .append(new TtsCardinal(324)) + * .append("meters tall."); + * Markup m2 = new Utterance().append("Sie haben") + * .append(new TtsCardinal(1).setGender(Utterance.GENDER_MALE) + * .append("Tag frei."); + * </p> + */ +public class Utterance { + + /*** + * Toplevel type of markup representation. + */ + public static final String TYPE_UTTERANCE = "utterance"; + /*** + * The no_warning_on_fallback parameter can be set to "false" or "true", true indicating that + * no warning will be given when the synthesizer does not support Markup. This is used when + * the user only provides a string to the API instead of a markup. + */ + public static final String KEY_NO_WARNING_ON_FALLBACK = "no_warning_on_fallback"; + + // Gender. + public final static int GENDER_UNKNOWN = 0; + public final static int GENDER_NEUTRAL = 1; + public final static int GENDER_MALE = 2; + public final static int GENDER_FEMALE = 3; + + // Animacy. + public final static int ANIMACY_UNKNOWN = 0; + public final static int ANIMACY_ANIMATE = 1; + public final static int ANIMACY_INANIMATE = 2; + + // Multiplicity. + public final static int MULTIPLICITY_UNKNOWN = 0; + public final static int MULTIPLICITY_SINGLE = 1; + public final static int MULTIPLICITY_DUAL = 2; + public final static int MULTIPLICITY_PLURAL = 3; + + // Case. + public final static int CASE_UNKNOWN = 0; + public final static int CASE_NOMINATIVE = 1; + public final static int CASE_ACCUSATIVE = 2; + public final static int CASE_DATIVE = 3; + public final static int CASE_ABLATIVE = 4; + public final static int CASE_GENITIVE = 5; + public final static int CASE_VOCATIVE = 6; + public final static int CASE_LOCATIVE = 7; + public final static int CASE_INSTRUMENTAL = 8; + + private List<AbstractTts<? extends AbstractTts<?>>> says = + new ArrayList<AbstractTts<? extends AbstractTts<?>>>(); + Boolean mNoWarningOnFallback = null; + + /** + * Objects deriving from this class can be appended to a Utterance. This class uses generics + * so method from this class can return instances of its child classes, resulting in a better + * API (CRTP pattern). + */ + public static abstract class AbstractTts<C extends AbstractTts<C>> { + + protected Markup mMarkup = new Markup(); + + /** + * Empty constructor. + */ + protected AbstractTts() { + } + + /** + * Construct with Markup. + * @param markup + */ + protected AbstractTts(Markup markup) { + mMarkup = markup; + } + + /** + * Returns the type of this class, e.g. "cardinal" or "measure". + * @return The type. + */ + public String getType() { + return mMarkup.getType(); + } + + /** + * A fallback plain text can be provided, in case the engine does not support this class + * type, or even Markup altogether. + * @param plainText A string with the plain text. + * @return This instance. + */ + @SuppressWarnings("unchecked") + public C setPlainText(String plainText) { + mMarkup.setPlainText(plainText); + return (C) this; + } + + /** + * Returns the plain text (fallback) string. + * @return Plain text string or null if not set. + */ + public String getPlainText() { + return mMarkup.getPlainText(); + } + + /** + * Populates the plainText if not set and builds a Markup instance. + * @return The Markup object describing this instance. + */ + public Markup getMarkup() { + return new Markup(mMarkup); + } + + @SuppressWarnings("unchecked") + protected C setParameter(String key, String value) { + mMarkup.setParameter(key, value); + return (C) this; + } + + protected String getParameter(String key) { + return mMarkup.getParameter(key); + } + + @SuppressWarnings("unchecked") + protected C removeParameter(String key) { + mMarkup.removeParameter(key); + return (C) this; + } + + /** + * Returns a string representation of this instance, can be deserialized to an equal + * Utterance instance. + */ + public String toString() { + return mMarkup.toString(); + } + + /** + * Returns a generated plain text alternative for this instance if this instance isn't + * better representated by the list of it's children. + * @return Best effort plain text representation of this instance, can be null. + */ + public String generatePlainText() { + return null; + } + } + + public static abstract class AbstractTtsSemioticClass<C extends AbstractTtsSemioticClass<C>> + extends AbstractTts<C> { + // Keys. + private static final String KEY_GENDER = "gender"; + private static final String KEY_ANIMACY = "animacy"; + private static final String KEY_MULTIPLICITY = "multiplicity"; + private static final String KEY_CASE = "case"; + + protected AbstractTtsSemioticClass() { + super(); + } + + protected AbstractTtsSemioticClass(Markup markup) { + super(markup); + } + + @SuppressWarnings("unchecked") + public C setGender(int gender) { + if (gender < 0 || gender > 3) { + throw new IllegalArgumentException("Only four types of gender can be set: " + + "unknown, neutral, maculine and female."); + } + if (gender != GENDER_UNKNOWN) { + setParameter(KEY_GENDER, String.valueOf(gender)); + } else { + setParameter(KEY_GENDER, null); + } + return (C) this; + } + + public int getGender() { + String gender = mMarkup.getParameter(KEY_GENDER); + return gender != null ? Integer.valueOf(gender) : GENDER_UNKNOWN; + } + + @SuppressWarnings("unchecked") + public C setAnimacy(int animacy) { + if (animacy < 0 || animacy > 2) { + throw new IllegalArgumentException( + "Only two types of animacy can be set: unknown, animate and inanimate"); + } + if (animacy != ANIMACY_UNKNOWN) { + setParameter(KEY_ANIMACY, String.valueOf(animacy)); + } else { + setParameter(KEY_ANIMACY, null); + } + return (C) this; + } + + public int getAnimacy() { + String animacy = getParameter(KEY_ANIMACY); + return animacy != null ? Integer.valueOf(animacy) : ANIMACY_UNKNOWN; + } + + @SuppressWarnings("unchecked") + public C setMultiplicity(int multiplicity) { + if (multiplicity < 0 || multiplicity > 3) { + throw new IllegalArgumentException( + "Only four types of multiplicity can be set: unknown, single, dual and " + + "plural."); + } + if (multiplicity != MULTIPLICITY_UNKNOWN) { + setParameter(KEY_MULTIPLICITY, String.valueOf(multiplicity)); + } else { + setParameter(KEY_MULTIPLICITY, null); + } + return (C) this; + } + + public int getMultiplicity() { + String multiplicity = mMarkup.getParameter(KEY_MULTIPLICITY); + return multiplicity != null ? Integer.valueOf(multiplicity) : MULTIPLICITY_UNKNOWN; + } + + @SuppressWarnings("unchecked") + public C setCase(int grammaticalCase) { + if (grammaticalCase < 0 || grammaticalCase > 8) { + throw new IllegalArgumentException( + "Only nine types of grammatical case can be set."); + } + if (grammaticalCase != CASE_UNKNOWN) { + setParameter(KEY_CASE, String.valueOf(grammaticalCase)); + } else { + setParameter(KEY_CASE, null); + } + return (C) this; + } + + public int getCase() { + String grammaticalCase = mMarkup.getParameter(KEY_CASE); + return grammaticalCase != null ? Integer.valueOf(grammaticalCase) : CASE_UNKNOWN; + } + } + + /** + * Class that contains regular text, synthesis engine pronounces it using its regular pipeline. + * Parameters: + * <ul> + * <li>Text: the text to synthesize</li> + * </ul> + */ + public static class TtsText extends AbstractTtsSemioticClass<TtsText> { + + // The type of this node. + protected static final String TYPE_TEXT = "text"; + // The text parameter stores the text to be synthesized. + private static final String KEY_TEXT = "text"; + + /** + * Default constructor. + */ + public TtsText() { + mMarkup.setType(TYPE_TEXT); + } + + /** + * Constructor that sets the text to be synthesized. + * @param text The text to be synthesized. + */ + public TtsText(String text) { + this(); + setText(text); + } + + /** + * Constructs a TtsText with the values of the Markup, does not check if the given Markup is + * of the right type. + */ + private TtsText(Markup markup) { + super(markup); + } + + /** + * Sets the text to be synthesized. + * @return This instance. + */ + public TtsText setText(String text) { + setParameter(KEY_TEXT, text); + return this; + } + + /** + * Returns the text to be synthesized. + * @return This instance. + */ + public String getText() { + return getParameter(KEY_TEXT); + } + + /** + * Generates a best effort plain text, in this case simply the text. + */ + @Override + public String generatePlainText() { + return getText(); + } + } + + /** + * Contains a cardinal. + * Parameters: + * <ul> + * <li>integer: the integer to synthesize</li> + * </ul> + */ + public static class TtsCardinal extends AbstractTtsSemioticClass<TtsCardinal> { + + // The type of this node. + protected static final String TYPE_CARDINAL = "cardinal"; + // The parameter integer stores the integer to synthesize. + private static final String KEY_INTEGER = "integer"; + + /** + * Default constructor. + */ + public TtsCardinal() { + mMarkup.setType(TYPE_CARDINAL); + } + + /** + * Constructor that sets the integer to be synthesized. + */ + public TtsCardinal(int integer) { + this(); + setInteger(integer); + } + + /** + * Constructor that sets the integer to be synthesized. + */ + public TtsCardinal(String integer) { + this(); + setInteger(integer); + } + + /** + * Constructs a TtsText with the values of the Markup. + * Does not check if the given Markup is of the right type. + */ + private TtsCardinal(Markup markup) { + super(markup); + } + + /** + * Sets the integer. + * @return This instance. + */ + public TtsCardinal setInteger(int integer) { + return setInteger(String.valueOf(integer)); + } + + /** + * Sets the integer. + * @param integer A non-empty string of digits with an optional '-' in front. + * @return This instance. + */ + public TtsCardinal setInteger(String integer) { + if (!integer.matches("-?\\d+")) { + throw new IllegalArgumentException("Expected a cardinal: \"" + integer + "\""); + } + setParameter(KEY_INTEGER, integer); + return this; + } + + /** + * Returns the integer parameter. + */ + public String getInteger() { + return getParameter(KEY_INTEGER); + } + + /** + * Generates a best effort plain text, in this case simply the integer. + */ + @Override + public String generatePlainText() { + return getInteger(); + } + } + + /** + * Default constructor. + */ + public Utterance() {} + + /** + * Returns the plain text of a given Markup if it was set; if it's not set, recursively call the + * this same method on its children. + */ + private String constructPlainText(Markup m) { + StringBuilder plainText = new StringBuilder(); + if (m.getPlainText() != null) { + plainText.append(m.getPlainText()); + } else { + for (Markup nestedMarkup : m.getNestedMarkups()) { + String nestedPlainText = constructPlainText(nestedMarkup); + if (!nestedPlainText.isEmpty()) { + if (plainText.length() != 0) { + plainText.append(" "); + } + plainText.append(nestedPlainText); + } + } + } + return plainText.toString(); + } + + /** + * Creates a Markup instance with auto generated plain texts for the relevant nodes, in case the + * user has not provided one already. + * @return A Markup instance representing this utterance. + */ + public Markup createMarkup() { + Markup markup = new Markup(TYPE_UTTERANCE); + StringBuilder plainText = new StringBuilder(); + for (AbstractTts<? extends AbstractTts<?>> say : says) { + // Get a copy of this markup, and generate a plaintext for it if is not set. + Markup sayMarkup = say.getMarkup(); + if (sayMarkup.getPlainText() == null) { + sayMarkup.setPlainText(say.generatePlainText()); + } + if (plainText.length() != 0) { + plainText.append(" "); + } + plainText.append(constructPlainText(sayMarkup)); + markup.addNestedMarkup(sayMarkup); + } + if (mNoWarningOnFallback != null) { + markup.setParameter(KEY_NO_WARNING_ON_FALLBACK, + mNoWarningOnFallback ? "true" : "false"); + } + markup.setPlainText(plainText.toString()); + return markup; + } + + /** + * Appends an element to this Utterance instance. + * @return this instance + */ + public Utterance append(AbstractTts<? extends AbstractTts<?>> say) { + says.add(say); + return this; + } + + private Utterance append(Markup markup) { + if (markup.getType().equals(TtsText.TYPE_TEXT)) { + append(new TtsText(markup)); + } else if (markup.getType().equals(TtsCardinal.TYPE_CARDINAL)) { + append(new TtsCardinal(markup)); + } else { + // Unknown node, a class we don't know about. + if (markup.getPlainText() != null) { + append(new TtsText(markup.getPlainText())); + } else { + // No plainText specified; add its children + // seperately. In case of a new prosody node, + // we would still verbalize it correctly. + for (Markup nested : markup.getNestedMarkups()) { + append(nested); + } + } + } + return this; + } + + /** + * Returns a string representation of this Utterance instance. Can be deserialized back to an + * Utterance instance with utteranceFromString(). Can be used to store utterances to be used + * at a later time. + */ + public String toString() { + String out = "type: \"" + TYPE_UTTERANCE + "\""; + if (mNoWarningOnFallback != null) { + out += " no_warning_on_fallback: \"" + (mNoWarningOnFallback ? "true" : "false") + "\""; + } + for (AbstractTts<? extends AbstractTts<?>> say : says) { + out += " markup { " + say.getMarkup().toString() + " }"; + } + return out; + } + + /** + * Returns an Utterance instance from the string representation generated by toString(). + * @param string The string representation generated by toString(). + * @return The new Utterance instance. + * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed. + */ + static public Utterance utteranceFromString(String string) throws IllegalArgumentException { + Utterance utterance = new Utterance(); + Markup markup = Markup.markupFromString(string); + if (!markup.getType().equals(TYPE_UTTERANCE)) { + throw new IllegalArgumentException("Top level markup should be of type \"" + + TYPE_UTTERANCE + "\", but was of type \"" + + markup.getType() + "\".") ; + } + for (Markup nestedMarkup : markup.getNestedMarkups()) { + utterance.append(nestedMarkup); + } + return utterance; + } + + /** + * Appends a new TtsText with the given text. + * @param text The text to synthesize. + * @return This instance. + */ + public Utterance append(String text) { + return append(new TtsText(text)); + } + + /** + * Appends a TtsCardinal representing the given number. + * @param integer The integer to synthesize. + * @return this + */ + public Utterance append(int integer) { + return append(new TtsCardinal(integer)); + } + + /** + * Returns the n'th element in this Utterance. + * @param i The index. + * @return The n'th element in this Utterance. + * @throws {@link IndexOutOfBoundsException} - if i < 0 || i >= size() + */ + public AbstractTts<? extends AbstractTts<?>> get(int i) { + return says.get(i); + } + + /** + * Returns the number of elements in this Utterance. + * @return The number of elements in this Utterance. + */ + public int size() { + return says.size(); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !(o instanceof Utterance) ) return false; + Utterance utt = (Utterance) o; + + if (says.size() != utt.says.size()) { + return false; + } + + for (int i = 0; i < says.size(); i++) { + if (!says.get(i).getMarkup().equals(utt.says.get(i).getMarkup())) { + return false; + } + } + return true; + } + + /** + * Can be set to true or false, true indicating that the user provided only a string to the API, + * at which the system will not issue a warning if the synthesizer falls back onto the plain + * text when the synthesizer does not support Markup. + */ + public Utterance setNoWarningOnFallback(boolean noWarning) { + mNoWarningOnFallback = noWarning; + return this; + } +} diff --git a/core/java/android/tv/ITvInputClient.aidl b/core/java/android/tv/ITvInputClient.aidl index ac83356..ef89c68 100644 --- a/core/java/android/tv/ITvInputClient.aidl +++ b/core/java/android/tv/ITvInputClient.aidl @@ -17,6 +17,7 @@ package android.tv; import android.content.ComponentName; +import android.os.Bundle; import android.tv.ITvInputSession; import android.view.InputChannel; @@ -29,4 +30,6 @@ oneway interface ITvInputClient { void onSessionCreated(in String inputId, IBinder token, in InputChannel channel, int seq); void onAvailabilityChanged(in String inputId, boolean isAvailable); void onSessionReleased(int seq); + void onSessionEvent(in String name, in Bundle args, int seq); + void onVideoSizeChanged(int width, int height, int seq); } diff --git a/core/java/android/tv/ITvInputSessionCallback.aidl b/core/java/android/tv/ITvInputSessionCallback.aidl index a2bd0d7..e27b8bf 100644 --- a/core/java/android/tv/ITvInputSessionCallback.aidl +++ b/core/java/android/tv/ITvInputSessionCallback.aidl @@ -16,6 +16,7 @@ package android.tv; +import android.os.Bundle; import android.tv.ITvInputSession; /** @@ -25,4 +26,6 @@ import android.tv.ITvInputSession; */ oneway interface ITvInputSessionCallback { void onSessionCreated(ITvInputSession session); + void onSessionEvent(in String name, in Bundle args); + void onVideoSizeChanged(int width, int height); } diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java index dfa84f8..d0c2ca6 100644 --- a/core/java/android/tv/TvInputManager.java +++ b/core/java/android/tv/TvInputManager.java @@ -18,6 +18,7 @@ package android.tv; import android.graphics.Rect; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -85,6 +86,29 @@ public final class TvInputManager { */ public void onSessionReleased(Session session) { } + + /** + * This is called at the beginning of the playback of a channel and later when the size of + * the video has been changed. + * + * @param session A {@link TvInputManager.Session} associated with this callback + * @param width the width of the video + * @param height the height of the video + * @hide + */ + public void onVideoSizeChanged(Session session, int width, int height) { + } + + /** + * This is called when a custom event has been sent from this session. + * + * @param session A {@link TvInputManager.Session} associated with this callback + * @param eventType The type of the event. + * @param eventArgs Optional arguments of the event. + * @hide + */ + public void onSessionEvent(Session session, String eventType, Bundle eventArgs) { + } } private static final class SessionCallbackRecord { @@ -116,6 +140,24 @@ public final class TvInputManager { } }); } + + public void postVideoSizeChanged(final int width, final int height) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onVideoSizeChanged(mSession, width, height); + } + }); + } + + public void postSessionEvent(final String eventType, final Bundle eventArgs) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onSessionEvent(mSession, eventType, eventArgs); + } + }); + } } /** @@ -196,6 +238,30 @@ public final class TvInputManager { } @Override + public void onVideoSizeChanged(int width, int height, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postVideoSizeChanged(width, height); + } + } + + @Override + public void onSessionEvent(String eventType, Bundle eventArgs, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postSessionEvent(eventType, eventArgs); + } + } + + @Override public void onAvailabilityChanged(String inputId, boolean isAvailable) { synchronized (mTvInputListenerRecordsMap) { List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId); diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java index cb0142f..03d24db 100644 --- a/core/java/android/tv/TvInputService.java +++ b/core/java/android/tv/TvInputService.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.graphics.PixelFormat; import android.graphics.Rect; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -156,6 +157,7 @@ public abstract class TvInputService extends Service { private boolean mOverlayViewEnabled; private IBinder mWindowToken; private Rect mOverlayFrame; + private ITvInputSessionCallback mSessionCallback; public TvInputSessionImpl() { mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); @@ -188,6 +190,52 @@ public abstract class TvInputService extends Service { } /** + * Dispatches an event to the application using this session. + * + * @param eventType The type of the event. + * @param eventArgs Optional arguments of the event. + * @hide + */ + public void dispatchSessionEvent(final String eventType, final Bundle eventArgs) { + if (eventType == null) { + throw new IllegalArgumentException("eventType should not be null."); + } + mHandler.post(new Runnable() { + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "dispatchSessionEvent(" + eventType + ")"); + mSessionCallback.onSessionEvent(eventType, eventArgs); + } catch (RemoteException e) { + Log.w(TAG, "error in sending event (event=" + eventType + ")"); + } + } + }); + } + + /** + * Sends the change on the size of the video. This is expected to be called at the + * beginning of the playback and later when the size has been changed. + * + * @param width The width of the video. + * @param height The height of the video. + * @hide + */ + public void dispatchVideoSizeChanged(final int width, final int height) { + mHandler.post(new Runnable() { + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "dispatchVideoSizeChanged"); + mSessionCallback.onVideoSizeChanged(width, height); + } catch (RemoteException e) { + Log.w(TAG, "error in dispatchVideoSizeChanged"); + } + } + }); + } + + /** * Called when the session is released. */ public abstract void onRelease(); @@ -394,9 +442,7 @@ public abstract class TvInputService extends Service { mWindowManager.removeView(mOverlayView); mOverlayView = null; } - if (DEBUG) { - Log.d(TAG, "create overlay view(" + frame + ")"); - } + if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")"); mWindowToken = windowToken; mOverlayFrame = frame; if (!mOverlayViewEnabled) { @@ -431,9 +477,7 @@ public abstract class TvInputService extends Service { * @param frame A new position of the overlay view. */ void relayoutOverlayView(Rect frame) { - if (DEBUG) { - Log.d(TAG, "relayout overlay view(" + frame + ")"); - } + if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")"); mOverlayFrame = frame; if (!mOverlayViewEnabled || mOverlayView == null) { return; @@ -449,9 +493,7 @@ public abstract class TvInputService extends Service { * Removes the current overlay view. */ void removeOverlayView(boolean clearWindowToken) { - if (DEBUG) { - Log.d(TAG, "remove overlay view(" + mOverlayView + ")"); - } + if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayView + ")"); if (clearWindowToken) { mWindowToken = null; mOverlayFrame = null; @@ -498,6 +540,10 @@ public abstract class TvInputService extends Service { mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver); return Session.DISPATCH_IN_PROGRESS; } + + private void setSessionCallback(ITvInputSessionCallback callback) { + mSessionCallback = callback; + } } private final class ServiceHandler extends Handler { @@ -517,6 +563,7 @@ public abstract class TvInputService extends Service { // Failed to create a session. cb.onSessionCreated(null); } else { + sessionImpl.setSessionCallback(cb); ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this, sessionImpl, channel); cb.onSessionCreated(stub); diff --git a/core/java/android/tv/TvView.java b/core/java/android/tv/TvView.java index 59b6386..2d31701 100644 --- a/core/java/android/tv/TvView.java +++ b/core/java/android/tv/TvView.java @@ -18,6 +18,7 @@ package android.tv; import android.content.Context; import android.graphics.Rect; +import android.os.Bundle; import android.os.Handler; import android.text.TextUtils; import android.tv.TvInputManager.Session; @@ -379,5 +380,23 @@ public class TvView extends SurfaceView { mExternalCallback.onSessionReleased(session); } } + + @Override + public void onVideoSizeChanged(Session session, int width, int height) { + if (DEBUG) { + Log.d(TAG, "onVideoSizeChanged(" + width + ", " + height + ")"); + } + if (mExternalCallback != null) { + mExternalCallback.onVideoSizeChanged(session, width, height); + } + } + + @Override + public void onSessionEvent(TvInputManager.Session session, String eventType, + Bundle eventArgs) { + if (mExternalCallback != null) { + mExternalCallback.onSessionEvent(session, eventType, eventArgs); + } + } } } 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/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 34d1f0e..af16185 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -120,6 +120,7 @@ interface IWindowManager boolean isKeyguardSecure(); boolean inKeyguardRestrictedInputMode(); void dismissKeyguard(); + void keyguardGoingAway(); void closeSystemDialogs(String reason); 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 c3bf295..ac25b57 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -5351,7 +5351,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/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 20194eb..2b4677c 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -608,8 +608,15 @@ public interface WindowManagerPolicy { * Return whether the given window should forcibly hide everything * behind it. Typically returns true for the keyguard. */ - public boolean doesForceHide(WindowState win, WindowManager.LayoutParams attrs); - + public boolean doesForceHide(WindowManager.LayoutParams attrs); + + + /** + * Return whether the given window can become one that passes doesForceHide() test. + * Typically returns true for the StatusBar. + */ + public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs); + /** * Determine if a window that is behind one that is force hiding * (as determined by {@link #doesForceHide}) should actually be hidden. @@ -618,7 +625,7 @@ public interface WindowManagerPolicy { * will conflict with what you set. */ public boolean canBeForceHidden(WindowState win, WindowManager.LayoutParams attrs); - + /** * Called when the system would like to show a UI to indicate that an * application is starting. You can use this to add a @@ -1149,12 +1156,6 @@ public interface WindowManagerPolicy { public void setLastInputMethodWindowLw(WindowState ime, WindowState target); /** - * Show the recents task list app. - * @hide - */ - public void showRecentApps(); - - /** * @return The current height of the input method window. */ public int getInputMethodWindowVisibleHeightLw(); @@ -1195,4 +1196,12 @@ public interface WindowManagerPolicy { * @return True if the window is a top level one. */ public boolean isTopLevelWindow(int windowType); + + /** + * Notifies the keyguard to start fading out. + * + * @param startTime the start time of the animation in uptime milliseconds + * @param fadeoutDuration the duration of the exit animation, in milliseconds + */ + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration); } diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java index 628da3c..84f395a 100644 --- a/core/java/android/view/textservice/SpellCheckerSession.java +++ b/core/java/android/view/textservice/SpellCheckerSession.java @@ -427,8 +427,12 @@ public class SpellCheckerSession { @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { - mHandler.sendMessage( - Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results)); + synchronized (this) { + if (mHandler != null) { + mHandler.sendMessage(Message.obtain(mHandler, + MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results)); + } + } } } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index c9eb130..9a46052 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -2495,17 +2495,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** - * Positions the selector in a way that mimics keyboard focus. If the - * selector drawable supports hotspots, this manages the focus hotspot. + * Positions the selector in a way that mimics keyboard focus. */ void positionSelectorLikeFocus(int position, View sel) { + // If we're changing position, update the visibility since the selector + // is technically being detached from the previous selection. + final Drawable selector = mSelector; + final boolean manageState = selector != null && mSelectorPosition != position + && position != INVALID_POSITION; + if (manageState) { + selector.setVisible(false, false); + } + positionSelector(position, sel); - final Drawable selector = mSelector; - if (selector != null && position != INVALID_POSITION) { + if (manageState) { final Rect bounds = mSelectorRect; final float x = bounds.exactCenterX(); final float y = bounds.exactCenterY(); + selector.setVisible(getVisibility() == VISIBLE, false); selector.setHotspot(x, y); } } @@ -2520,8 +2528,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (sel instanceof SelectionBoundsAdjuster) { ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect); } - positionSelector(selectorRect.left, selectorRect.top, selectorRect.right, - selectorRect.bottom); + + // Adjust for selection padding. + selectorRect.left -= mSelectionLeftPadding; + selectorRect.top -= mSelectionTopPadding; + selectorRect.right += mSelectionRightPadding; + selectorRect.bottom += mSelectionBottomPadding; + + // Update the selector drawable. + final Drawable selector = mSelector; + if (selector != null) { + selector.setBounds(selectorRect); + } final boolean isChildViewEnabled = mIsChildViewEnabled; if (sel.isEnabled() != isChildViewEnabled) { @@ -2532,11 +2550,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } - private void positionSelector(int l, int t, int r, int b) { - mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r - + mSelectionRightPadding, b + mSelectionBottomPadding); - } - @Override protected void dispatchDraw(Canvas canvas) { int saveCount = 0; diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java index a9a5eae..acee592 100644 --- a/core/java/android/widget/ActionMenuView.java +++ b/core/java/android/widget/ActionMenuView.java @@ -570,9 +570,9 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo mMenu = new MenuBuilder(context); mMenu.setCallback(new MenuBuilderCallback()); mPresenter = new ActionMenuPresenter(context); - mPresenter.setMenuView(this); mPresenter.setCallback(new ActionMenuPresenterCallback()); mMenu.addMenuPresenter(mPresenter); + mPresenter.setMenuView(this); } return mMenu; @@ -652,6 +652,11 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo return false; } + /** @hide */ + public void setExpandedActionViewsExclusive(boolean exclusive) { + mPresenter.setExpandedActionViewsExclusive(exclusive); + } + /** * Interface responsible for receiving menu item click events if the items themselves * do not have individual item click listeners. diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 265dbcd..2c1a77c 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -24,6 +24,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.text.InputType; +import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; @@ -814,8 +815,7 @@ public class DatePicker extends FrameLayout { mSpinners.removeAllViews(); // We use numeric spinners for year and day, but textual months. Ask icu4c what // order the user's locale uses for that combination. http://b/7207103. - String pattern = ICU.getBestDateTimePattern("yyyyMMMdd", - Locale.getDefault().toString()); + String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd"); char[] order = ICU.getDateFormatOrder(pattern); final int spinnerCount = order.length; for (int i = 0; i < spinnerCount; i++) { diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java index 8c67bb7..419c582 100644 --- a/core/java/android/widget/Toolbar.java +++ b/core/java/android/widget/Toolbar.java @@ -127,6 +127,8 @@ public class Toolbar extends ViewGroup { // Clear me after use. private final ArrayList<View> mTempViews = new ArrayList<View>(); + private final int[] mTempMargins = new int[2]; + private OnMenuItemClickListener mOnMenuItemClickListener; private final ActionMenuView.OnMenuItemClickListener mMenuViewItemClickListener = @@ -220,7 +222,7 @@ public class Toolbar extends ViewGroup { final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle); if (!TextUtils.isEmpty(subtitle)) { - setSubtitle(title); + setSubtitle(subtitle); } a.recycle(); } @@ -557,6 +559,28 @@ public class Toolbar extends ViewGroup { } /** + * Sets the text color, size, style, hint color, and highlight color + * from the specified TextAppearance resource. + */ + public void setTitleTextAppearance(Context context, int resId) { + mTitleTextAppearance = resId; + if (mTitleTextView != null) { + mTitleTextView.setTextAppearance(context, resId); + } + } + + /** + * Sets the text color, size, style, hint color, and highlight color + * from the specified TextAppearance resource. + */ + public void setSubtitleTextAppearance(Context context, int resId) { + mSubtitleTextAppearance = resId; + if (mSubtitleTextView != null) { + mSubtitleTextView.setTextAppearance(context, resId); + } + } + + /** * Set the icon to use for the toolbar's navigation button. * * <p>The navigation button appears at the start of the toolbar if present. Setting an icon @@ -592,7 +616,7 @@ public class Toolbar extends ViewGroup { */ public void setNavigationContentDescription(int resId) { ensureNavButtonView(); - mNavButtonView.setContentDescription(getContext().getText(resId)); + mNavButtonView.setContentDescription(resId != 0 ? getContext().getText(resId) : null); } /** @@ -681,10 +705,23 @@ public class Toolbar extends ViewGroup { * @return The toolbar's Menu */ public Menu getMenu() { - ensureMenuView(); + ensureMenu(); return mMenuView.getMenu(); } + private void ensureMenu() { + ensureMenuView(); + if (mMenuView.peekMenu() == null) { + // Initialize a new menu for the first time. + final MenuBuilder menu = (MenuBuilder) mMenuView.getMenu(); + if (mExpandedMenuPresenter == null) { + mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); + } + mMenuView.setExpandedActionViewsExclusive(true); + menu.addMenuPresenter(mExpandedMenuPresenter); + } + } + private void ensureMenuView() { if (mMenuView == null) { mMenuView = new ActionMenuView(getContext()); @@ -906,12 +943,49 @@ public class Toolbar extends ViewGroup { child.measure(childWidthSpec, childHeightSpec); } + /** + * Returns the width + uncollapsed margins + */ + private int measureChildCollapseMargins(View child, + int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed, int[] collapsingMargins) { + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + final int leftDiff = lp.leftMargin - collapsingMargins[0]; + final int rightDiff = lp.rightMargin - collapsingMargins[1]; + final int leftMargin = Math.max(0, leftDiff); + final int rightMargin = Math.max(0, rightDiff); + final int hMargins = leftMargin + rightMargin; + collapsingMargins[0] = Math.max(0, -leftDiff); + collapsingMargins[1] = Math.max(0, -rightDiff); + + final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, + mPaddingLeft + mPaddingRight + hMargins + widthUsed, lp.width); + final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, + mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + + heightUsed, lp.height); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + return child.getMeasuredWidth() + hMargins; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = 0; int height = 0; int childState = 0; + final int[] collapsingMargins = mTempMargins; + final int marginStartIndex; + final int marginEndIndex; + if (isLayoutRtl()) { + marginStartIndex = 1; + marginEndIndex = 0; + } else { + marginStartIndex = 0; + marginEndIndex = 1; + } + // System views measure first. int navWidth = 0; @@ -934,7 +1008,9 @@ public class Toolbar extends ViewGroup { childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState()); } - width += Math.max(getContentInsetStart(), navWidth); + final int contentInsetStart = getContentInsetStart(); + width += Math.max(contentInsetStart, navWidth); + collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth); int menuWidth = 0; if (shouldLayout(mMenuView)) { @@ -946,21 +1022,21 @@ public class Toolbar extends ViewGroup { childState = combineMeasuredStates(childState, mMenuView.getMeasuredState()); } - width += Math.max(getContentInsetEnd(), menuWidth); + final int contentInsetEnd = getContentInsetEnd(); + width += Math.max(contentInsetEnd, menuWidth); + collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth); if (shouldLayout(mExpandedActionView)) { - measureChildWithMargins(mExpandedActionView, widthMeasureSpec, width, - heightMeasureSpec, 0); - width += mExpandedActionView.getMeasuredWidth() + - getHorizontalMargins(mExpandedActionView); + width += measureChildCollapseMargins(mExpandedActionView, widthMeasureSpec, width, + heightMeasureSpec, 0, collapsingMargins); height = Math.max(height, mExpandedActionView.getMeasuredHeight() + getVerticalMargins(mExpandedActionView)); childState = combineMeasuredStates(childState, mExpandedActionView.getMeasuredState()); } if (shouldLayout(mLogoView)) { - measureChildWithMargins(mLogoView, widthMeasureSpec, width, heightMeasureSpec, 0); - width += mLogoView.getMeasuredWidth() + getHorizontalMargins(mLogoView); + width += measureChildCollapseMargins(mLogoView, widthMeasureSpec, width, + heightMeasureSpec, 0, collapsingMargins); height = Math.max(height, mLogoView.getMeasuredHeight() + getVerticalMargins(mLogoView)); childState = combineMeasuredStates(childState, mLogoView.getMeasuredState()); @@ -971,17 +1047,18 @@ public class Toolbar extends ViewGroup { final int titleVertMargins = mTitleMarginTop + mTitleMarginBottom; final int titleHorizMargins = mTitleMarginStart + mTitleMarginEnd; if (shouldLayout(mTitleTextView)) { - measureChildWithMargins(mTitleTextView, widthMeasureSpec, width + titleHorizMargins, - heightMeasureSpec, titleVertMargins); + titleWidth = measureChildCollapseMargins(mTitleTextView, widthMeasureSpec, + width + titleHorizMargins, heightMeasureSpec, titleVertMargins, + collapsingMargins); titleWidth = mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView); titleHeight = mTitleTextView.getMeasuredHeight() + getVerticalMargins(mTitleTextView); childState = combineMeasuredStates(childState, mTitleTextView.getMeasuredState()); } if (shouldLayout(mSubtitleTextView)) { - measureChildWithMargins(mSubtitleTextView, widthMeasureSpec, width + titleHorizMargins, - heightMeasureSpec, titleHeight + titleVertMargins); - titleWidth = Math.max(titleWidth, mSubtitleTextView.getMeasuredWidth() + - getHorizontalMargins(mSubtitleTextView)); + titleWidth = Math.max(titleWidth, measureChildCollapseMargins(mSubtitleTextView, + widthMeasureSpec, width + titleHorizMargins, + heightMeasureSpec, titleHeight + titleVertMargins, + collapsingMargins)); titleHeight += mSubtitleTextView.getMeasuredHeight() + getVerticalMargins(mSubtitleTextView); childState = combineMeasuredStates(childState, mSubtitleTextView.getMeasuredState()); @@ -999,8 +1076,8 @@ public class Toolbar extends ViewGroup { continue; } - measureChildWithMargins(child, widthMeasureSpec, width, heightMeasureSpec, 0); - width += child.getMeasuredWidth() + getHorizontalMargins(child); + width += measureChildCollapseMargins(child, widthMeasureSpec, width, + heightMeasureSpec, 0, collapsingMargins); height = Math.max(height, child.getMeasuredHeight() + getVerticalMargins(child)); childState = combineMeasuredStates(childState, child.getMeasuredState()); } @@ -1031,46 +1108,51 @@ public class Toolbar extends ViewGroup { int left = paddingLeft; int right = width - paddingRight; + final int[] collapsingMargins = mTempMargins; + collapsingMargins[0] = collapsingMargins[1] = 0; + if (shouldLayout(mNavButtonView)) { if (isRtl) { - right = layoutChildRight(mNavButtonView, right); + right = layoutChildRight(mNavButtonView, right, collapsingMargins); } else { - left = layoutChildLeft(mNavButtonView, left); + left = layoutChildLeft(mNavButtonView, left, collapsingMargins); } } if (shouldLayout(mCollapseButtonView)) { if (isRtl) { - right = layoutChildRight(mCollapseButtonView, right); + right = layoutChildRight(mCollapseButtonView, right, collapsingMargins); } else { - left = layoutChildLeft(mCollapseButtonView, left); + left = layoutChildLeft(mCollapseButtonView, left, collapsingMargins); } } if (shouldLayout(mMenuView)) { if (isRtl) { - left = layoutChildLeft(mMenuView, left); + left = layoutChildLeft(mMenuView, left, collapsingMargins); } else { - right = layoutChildRight(mMenuView, right); + right = layoutChildRight(mMenuView, right, collapsingMargins); } } + collapsingMargins[0] = Math.max(0, getContentInsetLeft() - left); + collapsingMargins[1] = Math.max(0, getContentInsetRight() - (width - paddingRight - right)); left = Math.max(left, getContentInsetLeft()); right = Math.min(right, width - paddingRight - getContentInsetRight()); if (shouldLayout(mExpandedActionView)) { if (isRtl) { - right = layoutChildRight(mExpandedActionView, right); + right = layoutChildRight(mExpandedActionView, right, collapsingMargins); } else { - left = layoutChildLeft(mExpandedActionView, left); + left = layoutChildLeft(mExpandedActionView, left, collapsingMargins); } } if (shouldLayout(mLogoView)) { if (isRtl) { - right = layoutChildRight(mLogoView, right); + right = layoutChildRight(mLogoView, right, collapsingMargins); } else { - left = layoutChildLeft(mLogoView, left); + left = layoutChildLeft(mLogoView, left, collapsingMargins); } } @@ -1119,48 +1201,52 @@ public class Toolbar extends ViewGroup { break; } if (isRtl) { + final int rd = mTitleMarginStart - collapsingMargins[1]; + right -= Math.max(0, rd); + collapsingMargins[1] = Math.max(0, -rd); int titleRight = right; int subtitleRight = right; + if (layoutTitle) { final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams(); - titleRight -= lp.rightMargin + mTitleMarginStart; final int titleLeft = titleRight - mTitleTextView.getMeasuredWidth(); final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight(); mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom); - titleRight = titleLeft - lp.leftMargin - mTitleMarginEnd; + titleRight = titleLeft - mTitleMarginEnd; titleTop = titleBottom + lp.bottomMargin; } if (layoutSubtitle) { final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams(); - subtitleRight -= lp.rightMargin + mTitleMarginStart; titleTop += lp.topMargin; final int subtitleLeft = subtitleRight - mSubtitleTextView.getMeasuredWidth(); final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight(); mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom); - subtitleRight = subtitleRight - lp.leftMargin - mTitleMarginEnd; + subtitleRight = subtitleRight - mTitleMarginEnd; titleTop = subtitleBottom + lp.bottomMargin; } right = Math.max(titleRight, subtitleRight); } else { + final int ld = mTitleMarginStart - collapsingMargins[0]; + left += Math.max(0, ld); + collapsingMargins[0] = Math.max(0, -ld); int titleLeft = left; int subtitleLeft = left; + if (layoutTitle) { final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams(); - titleLeft += lp.leftMargin + mTitleMarginStart; final int titleRight = titleLeft + mTitleTextView.getMeasuredWidth(); final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight(); mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom); - titleLeft = titleRight + lp.rightMargin + mTitleMarginEnd; + titleLeft = titleRight + mTitleMarginEnd; titleTop = titleBottom + lp.bottomMargin; } if (layoutSubtitle) { final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams(); - subtitleLeft += lp.leftMargin + mTitleMarginStart; titleTop += lp.topMargin; final int subtitleRight = subtitleLeft + mSubtitleTextView.getMeasuredWidth(); final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight(); mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom); - subtitleLeft = subtitleRight + lp.rightMargin + mTitleMarginEnd; + subtitleLeft = subtitleRight + mTitleMarginEnd; titleTop = subtitleBottom + lp.bottomMargin; } left = Math.max(titleLeft, subtitleLeft); @@ -1173,19 +1259,19 @@ public class Toolbar extends ViewGroup { addCustomViewsWithGravity(mTempViews, Gravity.LEFT); final int leftViewsCount = mTempViews.size(); for (int i = 0; i < leftViewsCount; i++) { - left = layoutChildLeft(mTempViews.get(i), left); + left = layoutChildLeft(mTempViews.get(i), left, collapsingMargins); } addCustomViewsWithGravity(mTempViews, Gravity.RIGHT); final int rightViewsCount = mTempViews.size(); for (int i = 0; i < rightViewsCount; i++) { - right = layoutChildRight(mTempViews.get(i), right); + right = layoutChildRight(mTempViews.get(i), right, collapsingMargins); } // Centered views try to center with respect to the whole bar, but views pinned // to the left or right can push the mass of centered views to one side or the other. addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL); - final int centerViewsWidth = getViewListMeasuredWidth(mTempViews); + final int centerViewsWidth = getViewListMeasuredWidth(mTempViews, collapsingMargins); final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2; final int halfCenterViewsWidth = centerViewsWidth / 2; int centerLeft = parentCenter - halfCenterViewsWidth; @@ -1198,25 +1284,35 @@ public class Toolbar extends ViewGroup { final int centerViewsCount = mTempViews.size(); for (int i = 0; i < centerViewsCount; i++) { - centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft); + centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft, collapsingMargins); } mTempViews.clear(); } - private int getViewListMeasuredWidth(List<View> views) { + private int getViewListMeasuredWidth(List<View> views, int[] collapsingMargins) { + int collapseLeft = collapsingMargins[0]; + int collapseRight = collapsingMargins[1]; int width = 0; final int count = views.size(); for (int i = 0; i < count; i++) { final View v = views.get(i); final LayoutParams lp = (LayoutParams) v.getLayoutParams(); - width += lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin; + final int l = lp.leftMargin - collapseLeft; + final int r = lp.rightMargin - collapseRight; + final int leftMargin = Math.max(0, l); + final int rightMargin = Math.max(0, r); + collapseLeft = Math.max(0, -l); + collapseRight = Math.max(0, -r); + width += leftMargin + v.getMeasuredWidth() + rightMargin; } return width; } - private int layoutChildLeft(View child, int left) { + private int layoutChildLeft(View child, int left, int[] collapsingMargins) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - left += lp.leftMargin; + final int l = lp.leftMargin - collapsingMargins[0]; + left += Math.max(0, l); + collapsingMargins[0] = Math.max(0, -l); final int top = getChildTop(child); final int childWidth = child.getMeasuredWidth(); child.layout(left, top, left + childWidth, top + child.getMeasuredHeight()); @@ -1224,9 +1320,11 @@ public class Toolbar extends ViewGroup { return left; } - private int layoutChildRight(View child, int right) { + private int layoutChildRight(View child, int right, int[] collapsingMargins) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - right -= lp.rightMargin; + final int r = lp.rightMargin - collapsingMargins[1]; + right -= Math.max(0, r); + collapsingMargins[1] = Math.max(0, -r); final int top = getChildTop(child); final int childWidth = child.getMeasuredWidth(); child.layout(right - childWidth, top, right, top + child.getMeasuredHeight()); diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl index 737906a..2900595 100644 --- a/core/java/com/android/internal/app/IVoiceInteractor.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl @@ -26,7 +26,9 @@ import com.android.internal.app.IVoiceInteractorRequest; */ interface IVoiceInteractor { IVoiceInteractorRequest startConfirmation(String callingPackage, - IVoiceInteractorCallback callback, String prompt, in Bundle extras); + IVoiceInteractorCallback callback, CharSequence prompt, in Bundle extras); + IVoiceInteractorRequest startAbortVoice(String callingPackage, + IVoiceInteractorCallback callback, CharSequence message, in Bundle extras); IVoiceInteractorRequest startCommand(String callingPackage, IVoiceInteractorCallback callback, String command, in Bundle extras); boolean[] supportsCommands(String callingPackage, in String[] commands); diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl index c6f93e1..8dbf9d4 100644 --- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl @@ -26,6 +26,7 @@ import com.android.internal.app.IVoiceInteractorRequest; oneway interface IVoiceInteractorCallback { void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed, in Bundle result); + void deliverAbortVoiceResult(IVoiceInteractorRequest request, in Bundle result); void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result); void deliverCancel(IVoiceInteractorRequest request); } diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java index afb6f7c..6056bf2 100644 --- a/core/java/com/android/internal/app/ToolbarActionBar.java +++ b/core/java/com/android/internal/app/ToolbarActionBar.java @@ -23,37 +23,50 @@ import android.content.Context; import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.view.ActionMode; +import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; +import android.view.Window; import android.widget.SpinnerAdapter; import android.widget.Toolbar; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.widget.DecorToolbar; +import com.android.internal.widget.ToolbarWidgetWrapper; import java.util.ArrayList; -import java.util.Map; public class ToolbarActionBar extends ActionBar { private Toolbar mToolbar; - private View mCustomView; - - private int mDisplayOptions; - - private int mNavResId; - private int mIconResId; - private int mLogoResId; - private Drawable mNavDrawable; - private Drawable mIconDrawable; - private Drawable mLogoDrawable; - private int mTitleResId; - private int mSubtitleResId; - private CharSequence mTitle; - private CharSequence mSubtitle; + private DecorToolbar mDecorToolbar; + private Window.Callback mWindowCallback; private boolean mLastMenuVisibility; private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners = new ArrayList<OnMenuVisibilityListener>(); - public ToolbarActionBar(Toolbar toolbar) { + private final Runnable mMenuInvalidator = new Runnable() { + @Override + public void run() { + populateOptionsMenu(); + } + }; + + private final Toolbar.OnMenuItemClickListener mMenuClicker = + new Toolbar.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); + } + }; + + public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) { mToolbar = toolbar; + mDecorToolbar = new ToolbarWidgetWrapper(toolbar); + mWindowCallback = windowCallback; + toolbar.setOnMenuItemClickListener(mMenuClicker); + mDecorToolbar.setWindowTitle(title); } @Override @@ -63,19 +76,8 @@ public class ToolbarActionBar extends ActionBar { @Override public void setCustomView(View view, LayoutParams layoutParams) { - if (mCustomView != null) { - mToolbar.removeView(mCustomView); - } - mCustomView = view; - if (view != null) { - mToolbar.addView(view, generateLayoutParams(layoutParams)); - } - } - - private Toolbar.LayoutParams generateLayoutParams(LayoutParams lp) { - final Toolbar.LayoutParams result = new Toolbar.LayoutParams(lp); - result.gravity = lp.gravity; - return result; + view.setLayoutParams(layoutParams); + mDecorToolbar.setCustomView(view); } @Override @@ -86,48 +88,22 @@ public class ToolbarActionBar extends ActionBar { @Override public void setIcon(int resId) { - mIconResId = resId; - mIconDrawable = null; - updateToolbarLogo(); + mDecorToolbar.setIcon(resId); } @Override public void setIcon(Drawable icon) { - mIconResId = 0; - mIconDrawable = icon; - updateToolbarLogo(); + mDecorToolbar.setIcon(icon); } @Override public void setLogo(int resId) { - mLogoResId = resId; - mLogoDrawable = null; - updateToolbarLogo(); + mDecorToolbar.setLogo(resId); } @Override public void setLogo(Drawable logo) { - mLogoResId = 0; - mLogoDrawable = logo; - updateToolbarLogo(); - } - - private void updateToolbarLogo() { - Drawable drawable = null; - if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) { - final int resId; - if ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) { - resId = mLogoResId; - drawable = mLogoDrawable; - } else { - resId = mIconResId; - drawable = mIconDrawable; - } - if (resId != 0) { - drawable = mToolbar.getContext().getDrawable(resId); - } - } - mToolbar.setLogo(drawable); + mDecorToolbar.setLogo(logo); } @Override @@ -219,42 +195,22 @@ public class ToolbarActionBar extends ActionBar { @Override public void setTitle(CharSequence title) { - mTitle = title; - mTitleResId = 0; - updateToolbarTitle(); + mDecorToolbar.setTitle(title); } @Override public void setTitle(int resId) { - mTitleResId = resId; - mTitle = null; - updateToolbarTitle(); + mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null); } @Override public void setSubtitle(CharSequence subtitle) { - mSubtitle = subtitle; - mSubtitleResId = 0; - updateToolbarTitle(); + mDecorToolbar.setSubtitle(subtitle); } @Override public void setSubtitle(int resId) { - mSubtitleResId = resId; - mSubtitle = null; - updateToolbarTitle(); - } - - private void updateToolbarTitle() { - final Context context = mToolbar.getContext(); - CharSequence title = null; - CharSequence subtitle = null; - if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) { - title = mTitleResId != 0 ? context.getText(mTitleResId) : mTitle; - subtitle = mSubtitleResId != 0 ? context.getText(mSubtitleResId) : mSubtitle; - } - mToolbar.setTitle(title); - mToolbar.setSubtitle(subtitle); + mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null); } @Override @@ -264,9 +220,8 @@ public class ToolbarActionBar extends ActionBar { @Override public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) { - final int oldOptions = mDisplayOptions; - mDisplayOptions = (options & mask) | (mDisplayOptions & ~mask); - final int optionsChanged = oldOptions ^ mDisplayOptions; + mDecorToolbar.setDisplayOptions((options & mask) | + mDecorToolbar.getDisplayOptions() & ~mask); } @Override @@ -301,7 +256,7 @@ public class ToolbarActionBar extends ActionBar { @Override public View getCustomView() { - return mCustomView; + return mDecorToolbar.getCustomView(); } @Override @@ -327,7 +282,7 @@ public class ToolbarActionBar extends ActionBar { @Override public int getDisplayOptions() { - return mDisplayOptions; + return mDecorToolbar.getDisplayOptions(); } @Override @@ -425,6 +380,54 @@ public class ToolbarActionBar extends ActionBar { return mToolbar.getVisibility() == View.VISIBLE; } + @Override + public boolean openOptionsMenu() { + return mToolbar.showOverflowMenu(); + } + + @Override + public boolean invalidateOptionsMenu() { + mToolbar.removeCallbacks(mMenuInvalidator); + mToolbar.postOnAnimation(mMenuInvalidator); + return true; + } + + @Override + public boolean collapseActionView() { + if (mToolbar.hasExpandedActionView()) { + mToolbar.collapseActionView(); + return true; + } + return false; + } + + void populateOptionsMenu() { + final Menu menu = mToolbar.getMenu(); + final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null; + if (mb != null) { + mb.stopDispatchingItemsChanged(); + } + try { + menu.clear(); + if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) || + !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) { + menu.clear(); + } + } finally { + if (mb != null) { + mb.startDispatchingItemsChanged(); + } + } + } + + @Override + public boolean onMenuKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP) { + openOptionsMenu(); + } + return true; + } + public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) { mMenuVisibilityListeners.add(listener); } diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index a0c75a6..c0b5b97 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -342,7 +342,7 @@ public class WindowDecorActionBar extends ActionBar implements @Override public void setCustomView(int resId) { setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, - (ViewGroup) mDecorToolbar, false)); + mDecorToolbar.getViewGroup(), false)); } @Override @@ -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/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index 1e37fd9..d10451b 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -178,7 +178,7 @@ interface IBackupTransport { /** * Get the data for the application returned by {@link #nextRestorePackage}. * @param data An open, writable file into which the backup data should be stored. - * @return the same error codes as {@link #nextRestorePackage}. + * @return the same error codes as {@link #startRestore}. */ int getRestoreData(in ParcelFileDescriptor outFd); diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index 446ef55..f2b29ef 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -18,6 +18,7 @@ package com.android.internal.backup; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; +import android.app.backup.BackupTransport; import android.app.backup.RestoreSet; import android.content.ComponentName; import android.content.Context; @@ -47,7 +48,7 @@ import static android.system.OsConstants.*; * later restoring from there. For testing only. */ -public class LocalTransport extends IBackupTransport.Stub { +public class LocalTransport extends BackupTransport { private static final String TAG = "LocalTransport"; private static final boolean DEBUG = true; @@ -217,7 +218,7 @@ public class LocalTransport extends IBackupTransport.Stub { // Restore handling static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 }; - public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException { + public RestoreSet[] getAvailableRestoreSets() { long[] existing = new long[POSSIBLE_SETS.length + 1]; int num = 0; diff --git a/core/java/com/android/internal/backup/LocalTransportService.java b/core/java/com/android/internal/backup/LocalTransportService.java index d05699a..77ac313 100644 --- a/core/java/com/android/internal/backup/LocalTransportService.java +++ b/core/java/com/android/internal/backup/LocalTransportService.java @@ -32,6 +32,6 @@ public class LocalTransportService extends Service { @Override public IBinder onBind(Intent intent) { - return sTransport; + return sTransport.getBinder(); } } 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/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl index b78c70f..a5421f5 100644 --- a/core/java/com/android/internal/policy/IKeyguardService.aidl +++ b/core/java/com/android/internal/policy/IKeyguardService.aidl @@ -56,4 +56,13 @@ interface IKeyguardService { oneway void dispatch(in MotionEvent event); oneway void launchCamera(); oneway void onBootCompleted(); + + /** + * Notifies that the activity behind has now been drawn and it's safe to remove the wallpaper + * and keyguard flag. + * + * @param startTime the start time of the animation in uptime milliseconds + * @param fadeoutDuration the duration of the exit animation, in milliseconds + */ + oneway void startKeyguardExitAnimation(long startTime, long fadeoutDuration); } 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/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java index f90aaea..3e15c32 100644 --- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java +++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Parcelable; +import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; @@ -80,16 +81,20 @@ public class ToolbarWidgetWrapper implements DecorToolbar { public ToolbarWidgetWrapper(Toolbar toolbar) { mToolbar = toolbar; + mTitle = toolbar.getTitle(); + mSubtitle = toolbar.getSubtitle(); + mTitleSet = !TextUtils.isEmpty(mTitle); + final TypedArray a = toolbar.getContext().obtainStyledAttributes(null, R.styleable.ActionBar, R.attr.actionBarStyle, 0); final CharSequence title = a.getText(R.styleable.ActionBar_title); - if (title != null) { + if (!TextUtils.isEmpty(title)) { setTitle(title); } final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle); - if (subtitle != null) { + if (!TextUtils.isEmpty(subtitle)) { setSubtitle(subtitle); } @@ -132,6 +137,16 @@ public class ToolbarWidgetWrapper implements DecorToolbar { mToolbar.setContentInsetsRelative(contentInsetStart, contentInsetEnd); } + final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0); + if (titleTextStyle != 0) { + mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle); + } + + final int subtitleTextStyle = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0); + if (subtitleTextStyle != 0) { + mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle); + } + a.recycle(); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { 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/MaskFilter.cpp b/core/jni/android/graphics/MaskFilter.cpp index 2113330..b394905 100644 --- a/core/jni/android/graphics/MaskFilter.cpp +++ b/core/jni/android/graphics/MaskFilter.cpp @@ -21,8 +21,7 @@ public: static jlong createBlur(JNIEnv* env, jobject, jfloat radius, jint blurStyle) { SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius); - SkMaskFilter* filter = SkBlurMaskFilter::Create( - (SkBlurMaskFilter::BlurStyle)blurStyle, sigma); + SkMaskFilter* filter = SkBlurMaskFilter::Create((SkBlurStyle)blurStyle, sigma); ThrowIAE_IfNull(env, filter); return reinterpret_cast<jlong>(filter); } 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_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h index fedb1b2..a2b1ed9 100644 --- a/core/jni/android_media_AudioFormat.h +++ b/core/jni/android_media_AudioFormat.h @@ -23,6 +23,11 @@ #define ENCODING_PCM_16BIT 2 #define ENCODING_PCM_8BIT 3 #define ENCODING_PCM_FLOAT 4 +#define ENCODING_INVALID 0 +#define ENCODING_DEFAULT 1 + +#define CHANNEL_INVALID 0 +#define CHANNEL_OUT_DEFAULT 1 static inline audio_format_t audioFormatToNative(int audioFormat) { @@ -33,9 +38,58 @@ static inline audio_format_t audioFormatToNative(int audioFormat) return AUDIO_FORMAT_PCM_8_BIT; case ENCODING_PCM_FLOAT: return AUDIO_FORMAT_PCM_FLOAT; + case ENCODING_DEFAULT: + return AUDIO_FORMAT_DEFAULT; default: return AUDIO_FORMAT_INVALID; } } +static inline int audioFormatFromNative(audio_format_t nativeFormat) +{ + switch (nativeFormat) { + case AUDIO_FORMAT_PCM_16_BIT: + return ENCODING_PCM_16BIT; + case AUDIO_FORMAT_PCM_8_BIT: + return ENCODING_PCM_8BIT; + case AUDIO_FORMAT_PCM_FLOAT: + return ENCODING_PCM_FLOAT; + case AUDIO_FORMAT_DEFAULT: + return ENCODING_DEFAULT; + default: + return ENCODING_INVALID; + } +} + +static inline audio_channel_mask_t outChannelMaskToNative(int channelMask) +{ + switch (channelMask) { + case CHANNEL_OUT_DEFAULT: + case CHANNEL_INVALID: + return AUDIO_CHANNEL_NONE; + default: + return (audio_channel_mask_t)(channelMask>>2); + } +} + +static inline int outChannelMaskFromNative(audio_channel_mask_t nativeMask) +{ + switch (nativeMask) { + case AUDIO_CHANNEL_NONE: + return CHANNEL_OUT_DEFAULT; + default: + return (int)nativeMask<<2; + } +} + +static inline audio_channel_mask_t inChannelMaskToNative(int channelMask) +{ + return (audio_channel_mask_t)channelMask; +} + +static inline int inChannelMaskFromNative(audio_channel_mask_t nativeMask) +{ + return (int)nativeMask; +} + #endif // ANDROID_MEDIA_AUDIOFORMAT_H diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index a9a62f8..0f7e140 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -15,7 +15,9 @@ ** limitations under the License. */ -#define LOG_TAG "AudioSystem" +//#define LOG_NDEBUG 0 + +#define LOG_TAG "AudioSystem-JNI" #include <utils/Log.h> #include <jni.h> @@ -26,6 +28,8 @@ #include <system/audio.h> #include <system/audio_policy.h> +#include "android_media_AudioFormat.h" +#include "android_media_AudioErrors.h" // ---------------------------------------------------------------------------- @@ -33,12 +37,160 @@ using namespace android; static const char* const kClassPathName = "android/media/AudioSystem"; +static jclass gArrayListClass; +static struct { + jmethodID add; +} gArrayListMethods; + +static jclass gAudioHandleClass; +static jmethodID gAudioHandleCstor; +static struct { + jfieldID mId; +} gAudioHandleFields; + +static jclass gAudioPortClass; +static jmethodID gAudioPortCstor; +static struct { + jfieldID mHandle; + jfieldID mRole; + jfieldID mGains; + jfieldID mActiveConfig; + // other fields unused by JNI +} gAudioPortFields; + +static jclass gAudioPortConfigClass; +static jmethodID gAudioPortConfigCstor; +static struct { + jfieldID mPort; + jfieldID mSamplingRate; + jfieldID mChannelMask; + jfieldID mFormat; + jfieldID mGain; + jfieldID mConfigMask; +} gAudioPortConfigFields; + +static jclass gAudioDevicePortClass; +static jmethodID gAudioDevicePortCstor; + +static jclass gAudioDevicePortConfigClass; +static jmethodID gAudioDevicePortConfigCstor; + +static jclass gAudioMixPortClass; +static jmethodID gAudioMixPortCstor; + +static jclass gAudioMixPortConfigClass; +static jmethodID gAudioMixPortConfigCstor; + +static jclass gAudioGainClass; +static jmethodID gAudioGainCstor; + +static jclass gAudioGainConfigClass; +static jmethodID gAudioGainConfigCstor; +static struct { + jfieldID mIndex; + jfieldID mMode; + jfieldID mChannelMask; + jfieldID mValues; + jfieldID mRampDurationMs; + // other fields unused by JNI +} gAudioGainConfigFields; + +static jclass gAudioPatchClass; +static jmethodID gAudioPatchCstor; +static struct { + jfieldID mHandle; + // other fields unused by JNI +} gAudioPatchFields; + +static const char* const kEventHandlerClassPathName = + "android/media/AudioPortEventHandler"; +static jmethodID gPostEventFromNative; + enum AudioError { kAudioStatusOk = 0, kAudioStatusError = 1, kAudioStatusMediaServerDied = 100 }; +enum { + AUDIOPORT_EVENT_PORT_LIST_UPDATED = 1, + AUDIOPORT_EVENT_PATCH_LIST_UPDATED = 2, + AUDIOPORT_EVENT_SERVICE_DIED = 3, +}; + +#define MAX_PORT_GENERATION_SYNC_ATTEMPTS 5 + +// ---------------------------------------------------------------------------- +// ref-counted object for callbacks +class JNIAudioPortCallback: public AudioSystem::AudioPortCallback +{ +public: + JNIAudioPortCallback(JNIEnv* env, jobject thiz, jobject weak_thiz); + ~JNIAudioPortCallback(); + + virtual void onAudioPortListUpdate(); + virtual void onAudioPatchListUpdate(); + virtual void onServiceDied(); + +private: + void sendEvent(int event); + + jclass mClass; // Reference to AudioPortEventHandlerDelegate class + jobject mObject; // Weak ref to AudioPortEventHandlerDelegate Java object to call on +}; + +JNIAudioPortCallback::JNIAudioPortCallback(JNIEnv* env, jobject thiz, jobject weak_thiz) +{ + + // Hold onto the SoundTriggerModule class for use in calling the static method + // that posts events to the application thread. + jclass clazz = env->GetObjectClass(thiz); + if (clazz == NULL) { + ALOGE("Can't find class %s", kEventHandlerClassPathName); + return; + } + mClass = (jclass)env->NewGlobalRef(clazz); + + // We use a weak reference so the SoundTriggerModule object can be garbage collected. + // The reference is only used as a proxy for callbacks. + mObject = env->NewGlobalRef(weak_thiz); +} + +JNIAudioPortCallback::~JNIAudioPortCallback() +{ + // remove global references + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mObject); + env->DeleteGlobalRef(mClass); +} + +void JNIAudioPortCallback::sendEvent(int event) +{ + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject, + event, 0, 0, NULL); + if (env->ExceptionCheck()) { + ALOGW("An exception occurred while notifying an event."); + env->ExceptionClear(); + } +} + +void JNIAudioPortCallback::onAudioPortListUpdate() +{ + sendEvent(AUDIOPORT_EVENT_PORT_LIST_UPDATED); +} + +void JNIAudioPortCallback::onAudioPatchListUpdate() +{ + sendEvent(AUDIOPORT_EVENT_PATCH_LIST_UPDATED); +} + +void JNIAudioPortCallback::onServiceDied() +{ + sendEvent(AUDIOPORT_EVENT_SERVICE_DIED); +} + static int check_AudioSystem_Command(status_t status) { switch (status) { @@ -281,6 +433,854 @@ android_media_AudioSystem_checkAudioFlinger(JNIEnv *env, jobject clazz) return (jint) check_AudioSystem_Command(AudioSystem::checkAudioFlinger()); } + +static bool useInChannelMask(audio_port_type_t type, audio_port_role_t role) +{ + return ((type == AUDIO_PORT_TYPE_DEVICE) && (role == AUDIO_PORT_ROLE_SOURCE)) || + ((type == AUDIO_PORT_TYPE_MIX) && (role == AUDIO_PORT_ROLE_SINK)); +} + +static void convertAudioGainConfigToNative(JNIEnv *env, + struct audio_gain_config *nAudioGainConfig, + const jobject jAudioGainConfig, + bool useInMask) +{ + nAudioGainConfig->index = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mIndex); + nAudioGainConfig->mode = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode); + ALOGV("convertAudioGainConfigToNative got gain index %d", nAudioGainConfig->index); + jint jMask = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mChannelMask); + audio_channel_mask_t nMask; + if (useInMask) { + nMask = inChannelMaskToNative(jMask); + ALOGV("convertAudioGainConfigToNative IN mask java %x native %x", jMask, nMask); + } else { + nMask = outChannelMaskToNative(jMask); + ALOGV("convertAudioGainConfigToNative OUT mask java %x native %x", jMask, nMask); + } + nAudioGainConfig->channel_mask = nMask; + nAudioGainConfig->ramp_duration_ms = env->GetIntField(jAudioGainConfig, + gAudioGainConfigFields.mRampDurationMs); + jintArray jValues = (jintArray)env->GetObjectField(jAudioGainConfig, + gAudioGainConfigFields.mValues); + int *nValues = env->GetIntArrayElements(jValues, NULL); + size_t size = env->GetArrayLength(jValues); + memcpy(nAudioGainConfig->values, nValues, size * sizeof(int)); + env->DeleteLocalRef(jValues); +} + + +static jint convertAudioPortConfigToNative(JNIEnv *env, + struct audio_port_config *nAudioPortConfig, + const jobject jAudioPortConfig) +{ + jobject jAudioPort = env->GetObjectField(jAudioPortConfig, gAudioPortConfigFields.mPort); + jobject jHandle = env->GetObjectField(jAudioPort, gAudioPortFields.mHandle); + nAudioPortConfig->id = env->GetIntField(jHandle, gAudioHandleFields.mId); + nAudioPortConfig->role = (audio_port_role_t)env->GetIntField(jAudioPort, + gAudioPortFields.mRole); + if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) { + nAudioPortConfig->type = AUDIO_PORT_TYPE_DEVICE; + } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) { + nAudioPortConfig->type = AUDIO_PORT_TYPE_MIX; + } else { + env->DeleteLocalRef(jAudioPort); + env->DeleteLocalRef(jHandle); + return (jint)AUDIO_JAVA_ERROR; + } + ALOGV("convertAudioPortConfigToNative handle %d role %d type %d", + nAudioPortConfig->id, nAudioPortConfig->role, nAudioPortConfig->type); + + nAudioPortConfig->sample_rate = env->GetIntField(jAudioPortConfig, + gAudioPortConfigFields.mSamplingRate); + + bool useInMask = useInChannelMask(nAudioPortConfig->type, nAudioPortConfig->role); + audio_channel_mask_t nMask; + jint jMask = env->GetIntField(jAudioPortConfig, + gAudioPortConfigFields.mChannelMask); + if (useInMask) { + nMask = inChannelMaskToNative(jMask); + ALOGV("convertAudioPortConfigToNative IN mask java %x native %x", jMask, nMask); + } else { + nMask = outChannelMaskToNative(jMask); + ALOGV("convertAudioPortConfigToNative OUT mask java %x native %x", jMask, nMask); + } + nAudioPortConfig->channel_mask = nMask; + + jint jFormat = env->GetIntField(jAudioPortConfig, gAudioPortConfigFields.mFormat); + audio_format_t nFormat = audioFormatToNative(jFormat); + ALOGV("convertAudioPortConfigToNative format %d native %d", jFormat, nFormat); + nAudioPortConfig->format = nFormat; + jobject jGain = env->GetObjectField(jAudioPortConfig, gAudioPortConfigFields.mGain); + if (jGain != NULL) { + convertAudioGainConfigToNative(env, &nAudioPortConfig->gain, jGain, useInMask); + env->DeleteLocalRef(jGain); + } else { + ALOGV("convertAudioPortConfigToNative no gain"); + nAudioPortConfig->gain.index = -1; + } + nAudioPortConfig->config_mask = env->GetIntField(jAudioPortConfig, + gAudioPortConfigFields.mConfigMask); + + env->DeleteLocalRef(jAudioPort); + env->DeleteLocalRef(jHandle); + return (jint)AUDIO_JAVA_SUCCESS; +} + +static jint convertAudioPortConfigFromNative(JNIEnv *env, + jobject jAudioPort, + jobject *jAudioPortConfig, + const struct audio_port_config *nAudioPortConfig) +{ + jint jStatus = AUDIO_JAVA_SUCCESS; + jobject jAudioGainConfig = NULL; + jobject jAudioGain = NULL; + jintArray jGainValues; + bool audioportCreated = false; + + ALOGV("convertAudioPortConfigFromNative jAudioPort %p", jAudioPort); + + if (jAudioPort == NULL) { + jobject jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor, + nAudioPortConfig->id); + + ALOGV("convertAudioPortConfigFromNative handle %d is a %s", nAudioPortConfig->id, + nAudioPortConfig->type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix"); + + if (jHandle == NULL) { + return (jint)AUDIO_JAVA_ERROR; + } + // create dummy port and port config objects with just the correct handle + // and configuration data. The actual AudioPortConfig objects will be + // constructed by java code with correct class type (device, mix etc...) + // and reference to AudioPort instance in this client + jAudioPort = env->NewObject(gAudioPortClass, gAudioPortCstor, + jHandle, + 0, + NULL, + NULL, + NULL, + NULL); + env->DeleteLocalRef(jHandle); + if (jAudioPort == NULL) { + return (jint)AUDIO_JAVA_ERROR; + } + ALOGV("convertAudioPortConfigFromNative jAudioPort created for handle %d", + nAudioPortConfig->id); + + audioportCreated = true; + } + + bool useInMask = useInChannelMask(nAudioPortConfig->type, nAudioPortConfig->role); + + audio_channel_mask_t nMask; + jint jMask; + + int gainIndex = nAudioPortConfig->gain.index; + if (gainIndex >= 0) { + ALOGV("convertAudioPortConfigFromNative gain found with index %d mode %x", + gainIndex, nAudioPortConfig->gain.mode); + if (audioportCreated) { + ALOGV("convertAudioPortConfigFromNative creating gain"); + jAudioGain = env->NewObject(gAudioGainClass, gAudioGainCstor, + gainIndex, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0); + if (jAudioGain == NULL) { + ALOGV("convertAudioPortConfigFromNative creating gain FAILED"); + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + } else { + ALOGV("convertAudioPortConfigFromNative reading gain from port"); + jobjectArray jGains = (jobjectArray)env->GetObjectField(jAudioPort, + gAudioPortFields.mGains); + if (jGains == NULL) { + ALOGV("convertAudioPortConfigFromNative could not get gains from port"); + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + jAudioGain = env->GetObjectArrayElement(jGains, gainIndex); + env->DeleteLocalRef(jGains); + if (jAudioGain == NULL) { + ALOGV("convertAudioPortConfigFromNative could not get gain at index %d", gainIndex); + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + } + //TODO: replace popcount by audio utils function mask to count + int numValues = popcount(nAudioPortConfig->gain.channel_mask); + jGainValues = env->NewIntArray(numValues); + if (jGainValues == NULL) { + ALOGV("convertAudioPortConfigFromNative could not create gain values %d", numValues); + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + env->SetIntArrayRegion(jGainValues, 0, numValues, + nAudioPortConfig->gain.values); + + nMask = nAudioPortConfig->gain.channel_mask; + if (useInMask) { + jMask = inChannelMaskFromNative(nMask); + ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask); + } else { + jMask = outChannelMaskFromNative(nMask); + ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask); + } + + jAudioGainConfig = env->NewObject(gAudioGainConfigClass, + gAudioGainConfigCstor, + gainIndex, + jAudioGain, + nAudioPortConfig->gain.mode, + jMask, + jGainValues, + nAudioPortConfig->gain.ramp_duration_ms); + env->DeleteLocalRef(jGainValues); + if (jAudioGainConfig == NULL) { + ALOGV("convertAudioPortConfigFromNative could not create gain config"); + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + } + jclass clazz; + jmethodID methodID; + if (audioportCreated) { + clazz = gAudioPortConfigClass; + methodID = gAudioPortConfigCstor; + ALOGV("convertAudioPortConfigFromNative building a generic port config"); + } else { + if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) { + clazz = gAudioDevicePortConfigClass; + methodID = gAudioDevicePortConfigCstor; + ALOGV("convertAudioPortConfigFromNative building a device config"); + } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) { + clazz = gAudioMixPortConfigClass; + methodID = gAudioMixPortConfigCstor; + ALOGV("convertAudioPortConfigFromNative building a mix config"); + } else { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + } + nMask = nAudioPortConfig->channel_mask; + if (useInMask) { + jMask = inChannelMaskFromNative(nMask); + ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask); + } else { + jMask = outChannelMaskFromNative(nMask); + ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask); + } + + *jAudioPortConfig = env->NewObject(clazz, methodID, + jAudioPort, + nAudioPortConfig->sample_rate, + jMask, + audioFormatFromNative(nAudioPortConfig->format), + jAudioGainConfig); + if (*jAudioPortConfig == NULL) { + ALOGV("convertAudioPortConfigFromNative could not create new port config"); + jStatus = (jint)AUDIO_JAVA_ERROR; + } else { + ALOGV("convertAudioPortConfigFromNative OK"); + } + +exit: + if (audioportCreated) { + env->DeleteLocalRef(jAudioPort); + if (jAudioGain != NULL) { + env->DeleteLocalRef(jAudioGain); + } + } + if (jAudioGainConfig != NULL) { + env->DeleteLocalRef(jAudioGainConfig); + } + return jStatus; +} + +static jint convertAudioPortFromNative(JNIEnv *env, + jobject *jAudioPort, const struct audio_port *nAudioPort) +{ + jint jStatus = (jint)AUDIO_JAVA_SUCCESS; + jintArray jSamplingRates = NULL; + jintArray jChannelMasks = NULL; + jintArray jFormats = NULL; + jobjectArray jGains = NULL; + jobject jHandle = NULL; + bool useInMask; + + ALOGV("convertAudioPortFromNative id %d role %d type %d", + nAudioPort->id, nAudioPort->role, nAudioPort->type); + + jSamplingRates = env->NewIntArray(nAudioPort->num_sample_rates); + if (jSamplingRates == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + if (nAudioPort->num_sample_rates) { + env->SetIntArrayRegion(jSamplingRates, 0, nAudioPort->num_sample_rates, + (jint *)nAudioPort->sample_rates); + } + + jChannelMasks = env->NewIntArray(nAudioPort->num_channel_masks); + if (jChannelMasks == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + useInMask = useInChannelMask(nAudioPort->type, nAudioPort->role); + + jint jMask; + for (size_t j = 0; j < nAudioPort->num_channel_masks; j++) { + if (useInMask) { + jMask = inChannelMaskFromNative(nAudioPort->channel_masks[j]); + } else { + jMask = outChannelMaskFromNative(nAudioPort->channel_masks[j]); + } + env->SetIntArrayRegion(jChannelMasks, j, 1, &jMask); + } + + jFormats = env->NewIntArray(nAudioPort->num_formats); + if (jFormats == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + for (size_t j = 0; j < nAudioPort->num_formats; j++) { + jint jFormat = audioFormatFromNative(nAudioPort->formats[j]); + env->SetIntArrayRegion(jFormats, j, 1, &jFormat); + } + + jGains = env->NewObjectArray(nAudioPort->num_gains, + gAudioGainClass, NULL); + if (jGains == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + for (size_t j = 0; j < nAudioPort->num_gains; j++) { + audio_channel_mask_t nMask = nAudioPort->gains[j].channel_mask; + if (useInMask) { + jMask = inChannelMaskFromNative(nMask); + ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask); + } else { + jMask = outChannelMaskFromNative(nMask); + ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask); + } + + jobject jGain = env->NewObject(gAudioGainClass, gAudioGainCstor, + j, + nAudioPort->gains[j].mode, + jMask, + nAudioPort->gains[j].min_value, + nAudioPort->gains[j].max_value, + nAudioPort->gains[j].default_value, + nAudioPort->gains[j].step_value, + nAudioPort->gains[j].min_ramp_ms, + nAudioPort->gains[j].max_ramp_ms); + if (jGain == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + env->SetObjectArrayElement(jGains, j, jGain); + env->DeleteLocalRef(jGain); + } + + jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor, + nAudioPort->id); + if (jHandle == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + + if (nAudioPort->type == AUDIO_PORT_TYPE_DEVICE) { + ALOGV("convertAudioPortFromNative is a device %08x", nAudioPort->ext.device.type); + jstring jAddress = env->NewStringUTF(nAudioPort->ext.device.address); + *jAudioPort = env->NewObject(gAudioDevicePortClass, gAudioDevicePortCstor, + jHandle, jSamplingRates, jChannelMasks, jFormats, jGains, + nAudioPort->ext.device.type, jAddress); + env->DeleteLocalRef(jAddress); + } else if (nAudioPort->type == AUDIO_PORT_TYPE_MIX) { + ALOGV("convertAudioPortFromNative is a mix"); + *jAudioPort = env->NewObject(gAudioMixPortClass, gAudioMixPortCstor, + jHandle, nAudioPort->role, jSamplingRates, jChannelMasks, + jFormats, jGains); + } else { + ALOGE("convertAudioPortFromNative unknown nAudioPort type %d", nAudioPort->type); + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + if (*jAudioPort == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + + jobject jAudioPortConfig; + jStatus = convertAudioPortConfigFromNative(env, + *jAudioPort, + &jAudioPortConfig, + &nAudioPort->active_config); + if (jStatus != AUDIO_JAVA_SUCCESS) { + return jStatus; + } + + env->SetObjectField(*jAudioPort, gAudioPortFields.mActiveConfig, jAudioPortConfig); + +exit: + if (jSamplingRates != NULL) { + env->DeleteLocalRef(jSamplingRates); + } + if (jChannelMasks != NULL) { + env->DeleteLocalRef(jChannelMasks); + } + if (jFormats != NULL) { + env->DeleteLocalRef(jFormats); + } + if (jGains != NULL) { + env->DeleteLocalRef(jGains); + } + if (jHandle != NULL) { + env->DeleteLocalRef(jHandle); + } + + return jStatus; +} + + +static jint +android_media_AudioSystem_listAudioPorts(JNIEnv *env, jobject clazz, + jobject jPorts, jintArray jGeneration) +{ + ALOGV("listAudioPorts"); + + if (jPorts == NULL) { + ALOGE("listAudioPorts NULL AudioPort ArrayList"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jPorts, gArrayListClass)) { + ALOGE("listAudioPorts not an arraylist"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + status_t status; + unsigned int generation1; + unsigned int generation; + unsigned int numPorts; + jint *nGeneration; + struct audio_port *nPorts = NULL; + int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS; + + // get the port count and all the ports until they both return the same generation + do { + if (attempts-- < 0) { + status = TIMED_OUT; + break; + } + + numPorts = 0; + status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE, + AUDIO_PORT_TYPE_NONE, + &numPorts, + NULL, + &generation1); + if (status != NO_ERROR || numPorts == 0) { + ALOGE_IF(status != NO_ERROR, "AudioSystem::listAudioPorts error %d", status); + break; + } + nPorts = (struct audio_port *)realloc(nPorts, numPorts * sizeof(struct audio_port)); + + status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE, + AUDIO_PORT_TYPE_NONE, + &numPorts, + nPorts, + &generation); + ALOGV("listAudioPorts AudioSystem::listAudioPorts numPorts %d generation %d generation1 %d", + numPorts, generation, generation1); + } while (generation1 != generation && status == NO_ERROR); + + jint jStatus = nativeToJavaStatus(status); + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + + nGeneration = env->GetIntArrayElements(jGeneration, NULL); + if (nGeneration == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + nGeneration[0] = generation1; + env->ReleaseIntArrayElements(jGeneration, nGeneration, 0); + + for (size_t i = 0; i < numPorts; i++) { + jobject jAudioPort; + jStatus = convertAudioPortFromNative(env, &jAudioPort, &nPorts[i]); + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + env->CallBooleanMethod(jPorts, gArrayListMethods.add, jAudioPort); + } + +exit: + free(nPorts); + return jStatus; +} + +static int +android_media_AudioSystem_createAudioPatch(JNIEnv *env, jobject clazz, + jobjectArray jPatches, jobjectArray jSources, jobjectArray jSinks) +{ + status_t status; + jint jStatus; + + ALOGV("createAudioPatch"); + if (jPatches == NULL || jSources == NULL || jSinks == NULL) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + if (env->GetArrayLength(jPatches) != 1) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + jint numSources = env->GetArrayLength(jSources); + if (numSources == 0 || numSources > AUDIO_PATCH_PORTS_MAX) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + jint numSinks = env->GetArrayLength(jSinks); + if (numSinks == 0 || numSinks > AUDIO_PATCH_PORTS_MAX) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + audio_patch_handle_t handle = (audio_patch_handle_t)0; + jobject jPatch = env->GetObjectArrayElement(jPatches, 0); + jobject jPatchHandle = NULL; + if (jPatch != NULL) { + if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle); + handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId); + } + + struct audio_patch nPatch; + + nPatch.id = handle; + nPatch.num_sources = 0; + nPatch.num_sinks = 0; + jobject jSource = NULL; + jobject jSink = NULL; + + for (jint i = 0; i < numSources; i++) { + jSource = env->GetObjectArrayElement(jSources, i); + if (!env->IsInstanceOf(jSource, gAudioPortConfigClass)) { + jStatus = (jint)AUDIO_JAVA_BAD_VALUE; + goto exit; + } + jStatus = convertAudioPortConfigToNative(env, &nPatch.sources[i], jSource); + env->DeleteLocalRef(jSource); + jSource = NULL; + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + nPatch.num_sources++; + } + + for (jint i = 0; i < numSinks; i++) { + jSink = env->GetObjectArrayElement(jSinks, i); + if (!env->IsInstanceOf(jSink, gAudioPortConfigClass)) { + jStatus = (jint)AUDIO_JAVA_BAD_VALUE; + goto exit; + } + jStatus = convertAudioPortConfigToNative(env, &nPatch.sinks[i], jSink); + env->DeleteLocalRef(jSink); + jSink = NULL; + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + nPatch.num_sinks++; + } + + ALOGV("AudioSystem::createAudioPatch"); + status = AudioSystem::createAudioPatch(&nPatch, &handle); + ALOGV("AudioSystem::createAudioPatch() returned %d hande %d", status, handle); + + jStatus = nativeToJavaStatus(status); + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + + if (jPatchHandle == NULL) { + jPatchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor, + handle); + if (jPatchHandle == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor, jPatchHandle, jSources, jSinks); + if (jPatch == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + env->SetObjectArrayElement(jPatches, 0, jPatch); + } else { + env->SetIntField(jPatchHandle, gAudioHandleFields.mId, handle); + } + +exit: + if (jPatchHandle != NULL) { + env->DeleteLocalRef(jPatchHandle); + } + if (jPatch != NULL) { + env->DeleteLocalRef(jPatch); + } + if (jSource != NULL) { + env->DeleteLocalRef(jSource); + } + if (jSink != NULL) { + env->DeleteLocalRef(jSink); + } + return jStatus; +} + +static int +android_media_AudioSystem_releaseAudioPatch(JNIEnv *env, jobject clazz, + jobject jPatch) +{ + ALOGV("releaseAudioPatch"); + if (jPatch == NULL) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + audio_patch_handle_t handle = (audio_patch_handle_t)0; + jobject jPatchHandle = NULL; + if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle); + handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId); + env->DeleteLocalRef(jPatchHandle); + + ALOGV("AudioSystem::releaseAudioPatch"); + status_t status = AudioSystem::releaseAudioPatch(handle); + ALOGV("AudioSystem::releaseAudioPatch() returned %d", status); + jint jStatus = nativeToJavaStatus(status); + return status; +} + +static jint +android_media_AudioSystem_listAudioPatches(JNIEnv *env, jobject clazz, + jobject jPatches, jintArray jGeneration) +{ + ALOGV("listAudioPatches"); + if (jPatches == NULL) { + ALOGE("listAudioPatches NULL AudioPatch ArrayList"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jPatches, gArrayListClass)) { + ALOGE("listAudioPatches not an arraylist"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + status_t status; + unsigned int generation1; + unsigned int generation; + unsigned int numPatches; + jint *nGeneration; + struct audio_patch *nPatches = NULL; + jobjectArray jSources = NULL; + jobject jSource = NULL; + jobjectArray jSinks = NULL; + jobject jSink = NULL; + jobject jPatch = NULL; + int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS; + + // get the patch count and all the patches until they both return the same generation + do { + if (attempts-- < 0) { + status = TIMED_OUT; + break; + } + + numPatches = 0; + status = AudioSystem::listAudioPatches(&numPatches, + NULL, + &generation1); + if (status != NO_ERROR || numPatches == 0) { + ALOGE_IF(status != NO_ERROR, "listAudioPatches AudioSystem::listAudioPatches error %d", + status); + break; + } + nPatches = (struct audio_patch *)realloc(nPatches, numPatches * sizeof(struct audio_patch)); + + status = AudioSystem::listAudioPatches(&numPatches, + nPatches, + &generation); + ALOGV("listAudioPatches AudioSystem::listAudioPatches numPatches %d generation %d generation1 %d", + numPatches, generation, generation1); + + } while (generation1 != generation && status == NO_ERROR); + + jint jStatus = nativeToJavaStatus(status); + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + + nGeneration = env->GetIntArrayElements(jGeneration, NULL); + if (nGeneration == NULL) { + jStatus = AUDIO_JAVA_ERROR; + goto exit; + } + nGeneration[0] = generation1; + env->ReleaseIntArrayElements(jGeneration, nGeneration, 0); + + for (size_t i = 0; i < numPatches; i++) { + jobject patchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor, + nPatches[i].id); + if (patchHandle == NULL) { + jStatus = AUDIO_JAVA_ERROR; + goto exit; + } + ALOGV("listAudioPatches patch %d num_sources %d num_sinks %d", + i, nPatches[i].num_sources, nPatches[i].num_sinks); + + env->SetIntField(patchHandle, gAudioHandleFields.mId, nPatches[i].id); + + // load sources + jSources = env->NewObjectArray(nPatches[i].num_sources, + gAudioPortConfigClass, NULL); + if (jSources == NULL) { + jStatus = AUDIO_JAVA_ERROR; + goto exit; + } + + for (size_t j = 0; j < nPatches[i].num_sources; j++) { + jStatus = convertAudioPortConfigFromNative(env, + NULL, + &jSource, + &nPatches[i].sources[j]); + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + env->SetObjectArrayElement(jSources, j, jSource); + env->DeleteLocalRef(jSource); + jSource = NULL; + ALOGV("listAudioPatches patch %d source %d is a %s handle %d", + i, j, + nPatches[i].sources[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix", + nPatches[i].sources[j].id); + } + // load sinks + jSinks = env->NewObjectArray(nPatches[i].num_sinks, + gAudioPortConfigClass, NULL); + if (jSinks == NULL) { + jStatus = AUDIO_JAVA_ERROR; + goto exit; + } + + for (size_t j = 0; j < nPatches[i].num_sinks; j++) { + jStatus = convertAudioPortConfigFromNative(env, + NULL, + &jSink, + &nPatches[i].sinks[j]); + + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + env->SetObjectArrayElement(jSinks, j, jSink); + env->DeleteLocalRef(jSink); + jSink = NULL; + ALOGV("listAudioPatches patch %d sink %d is a %s handle %d", + i, j, + nPatches[i].sinks[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix", + nPatches[i].sinks[j].id); + } + + jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor, + patchHandle, jSources, jSinks); + env->DeleteLocalRef(jSources); + jSources = NULL; + env->DeleteLocalRef(jSinks); + jSinks = NULL; + if (jPatch == NULL) { + jStatus = AUDIO_JAVA_ERROR; + goto exit; + } + env->CallBooleanMethod(jPatches, gArrayListMethods.add, jPatch); + env->DeleteLocalRef(jPatch); + jPatch = NULL; + } + +exit: + if (jSources != NULL) { + env->DeleteLocalRef(jSources); + } + if (jSource != NULL) { + env->DeleteLocalRef(jSource); + } + if (jSinks != NULL) { + env->DeleteLocalRef(jSinks); + } + if (jSink != NULL) { + env->DeleteLocalRef(jSink); + } + if (jPatch != NULL) { + env->DeleteLocalRef(jPatch); + } + free(nPatches); + return jStatus; +} + +static jint +android_media_AudioSystem_setAudioPortConfig(JNIEnv *env, jobject clazz, + jobject jAudioPortConfig) +{ + ALOGV("setAudioPortConfig"); + if (jAudioPortConfig == NULL) { + return AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jAudioPortConfig, gAudioPortConfigClass)) { + return AUDIO_JAVA_BAD_VALUE; + } + struct audio_port_config nAudioPortConfig; + jint jStatus = convertAudioPortConfigToNative(env, &nAudioPortConfig, jAudioPortConfig); + if (jStatus != AUDIO_JAVA_SUCCESS) { + return jStatus; + } + status_t status = AudioSystem::setAudioPortConfig(&nAudioPortConfig); + ALOGV("AudioSystem::setAudioPortConfig() returned %d", status); + jStatus = nativeToJavaStatus(status); + return jStatus; +} + +static void +android_media_AudioSystem_eventHandlerSetup(JNIEnv *env, jobject thiz, jobject weak_this) +{ + ALOGV("eventHandlerSetup"); + + sp<JNIAudioPortCallback> callback = new JNIAudioPortCallback(env, thiz, weak_this); + + AudioSystem::setAudioPortCallback(callback); +} + +static void +android_media_AudioSystem_eventHandlerFinalize(JNIEnv *env, jobject thiz) +{ + ALOGV("eventHandlerFinalize"); + + sp<JNIAudioPortCallback> callback; + + AudioSystem::setAudioPortCallback(callback); +} + // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { @@ -309,12 +1309,123 @@ static JNINativeMethod gMethods[] = { {"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency}, {"setLowRamDevice", "(Z)I", (void *)android_media_AudioSystem_setLowRamDevice}, {"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger}, + {"listAudioPorts", "(Ljava/util/ArrayList;[I)I", + (void *)android_media_AudioSystem_listAudioPorts}, + {"createAudioPatch", "([Landroid/media/AudioPatch;[Landroid/media/AudioPortConfig;[Landroid/media/AudioPortConfig;)I", + (void *)android_media_AudioSystem_createAudioPatch}, + {"releaseAudioPatch", "(Landroid/media/AudioPatch;)I", + (void *)android_media_AudioSystem_releaseAudioPatch}, + {"listAudioPatches", "(Ljava/util/ArrayList;[I)I", + (void *)android_media_AudioSystem_listAudioPatches}, + {"setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I", + (void *)android_media_AudioSystem_setAudioPortConfig}, +}; + + +static JNINativeMethod gEventHandlerMethods[] = { + {"native_setup", + "(Ljava/lang/Object;)V", + (void *)android_media_AudioSystem_eventHandlerSetup}, + {"native_finalize", + "()V", + (void *)android_media_AudioSystem_eventHandlerFinalize}, }; int register_android_media_AudioSystem(JNIEnv *env) { + + jclass arrayListClass = env->FindClass("java/util/ArrayList"); + gArrayListClass = (jclass) env->NewGlobalRef(arrayListClass); + gArrayListMethods.add = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z"); + + jclass audioHandleClass = env->FindClass("android/media/AudioHandle"); + gAudioHandleClass = (jclass) env->NewGlobalRef(audioHandleClass); + gAudioHandleCstor = env->GetMethodID(audioHandleClass, "<init>", "(I)V"); + gAudioHandleFields.mId = env->GetFieldID(audioHandleClass, "mId", "I"); + + jclass audioPortClass = env->FindClass("android/media/AudioPort"); + gAudioPortClass = (jclass) env->NewGlobalRef(audioPortClass); + gAudioPortCstor = env->GetMethodID(audioPortClass, "<init>", + "(Landroid/media/AudioHandle;I[I[I[I[Landroid/media/AudioGain;)V"); + gAudioPortFields.mHandle = env->GetFieldID(audioPortClass, "mHandle", + "Landroid/media/AudioHandle;"); + gAudioPortFields.mRole = env->GetFieldID(audioPortClass, "mRole", "I"); + gAudioPortFields.mGains = env->GetFieldID(audioPortClass, "mGains", + "[Landroid/media/AudioGain;"); + gAudioPortFields.mActiveConfig = env->GetFieldID(audioPortClass, "mActiveConfig", + "Landroid/media/AudioPortConfig;"); + + jclass audioPortConfigClass = env->FindClass("android/media/AudioPortConfig"); + gAudioPortConfigClass = (jclass) env->NewGlobalRef(audioPortConfigClass); + gAudioPortConfigCstor = env->GetMethodID(audioPortConfigClass, "<init>", + "(Landroid/media/AudioPort;IIILandroid/media/AudioGainConfig;)V"); + gAudioPortConfigFields.mPort = env->GetFieldID(audioPortConfigClass, "mPort", + "Landroid/media/AudioPort;"); + gAudioPortConfigFields.mSamplingRate = env->GetFieldID(audioPortConfigClass, + "mSamplingRate", "I"); + gAudioPortConfigFields.mChannelMask = env->GetFieldID(audioPortConfigClass, + "mChannelMask", "I"); + gAudioPortConfigFields.mFormat = env->GetFieldID(audioPortConfigClass, "mFormat", "I"); + gAudioPortConfigFields.mGain = env->GetFieldID(audioPortConfigClass, "mGain", + "Landroid/media/AudioGainConfig;"); + gAudioPortConfigFields.mConfigMask = env->GetFieldID(audioPortConfigClass, "mConfigMask", "I"); + + jclass audioDevicePortConfigClass = env->FindClass("android/media/AudioDevicePortConfig"); + gAudioDevicePortConfigClass = (jclass) env->NewGlobalRef(audioDevicePortConfigClass); + gAudioDevicePortConfigCstor = env->GetMethodID(audioDevicePortConfigClass, "<init>", + "(Landroid/media/AudioDevicePort;IIILandroid/media/AudioGainConfig;)V"); + + jclass audioMixPortConfigClass = env->FindClass("android/media/AudioMixPortConfig"); + gAudioMixPortConfigClass = (jclass) env->NewGlobalRef(audioMixPortConfigClass); + gAudioMixPortConfigCstor = env->GetMethodID(audioMixPortConfigClass, "<init>", + "(Landroid/media/AudioMixPort;IIILandroid/media/AudioGainConfig;)V"); + + jclass audioDevicePortClass = env->FindClass("android/media/AudioDevicePort"); + gAudioDevicePortClass = (jclass) env->NewGlobalRef(audioDevicePortClass); + gAudioDevicePortCstor = env->GetMethodID(audioDevicePortClass, "<init>", + "(Landroid/media/AudioHandle;[I[I[I[Landroid/media/AudioGain;ILjava/lang/String;)V"); + + jclass audioMixPortClass = env->FindClass("android/media/AudioMixPort"); + gAudioMixPortClass = (jclass) env->NewGlobalRef(audioMixPortClass); + gAudioMixPortCstor = env->GetMethodID(audioMixPortClass, "<init>", + "(Landroid/media/AudioHandle;I[I[I[I[Landroid/media/AudioGain;)V"); + + jclass audioGainClass = env->FindClass("android/media/AudioGain"); + gAudioGainClass = (jclass) env->NewGlobalRef(audioGainClass); + gAudioGainCstor = env->GetMethodID(audioGainClass, "<init>", "(IIIIIIIII)V"); + + jclass audioGainConfigClass = env->FindClass("android/media/AudioGainConfig"); + gAudioGainConfigClass = (jclass) env->NewGlobalRef(audioGainConfigClass); + gAudioGainConfigCstor = env->GetMethodID(audioGainConfigClass, "<init>", + "(ILandroid/media/AudioGain;II[II)V"); + gAudioGainConfigFields.mIndex = env->GetFieldID(gAudioGainConfigClass, "mIndex", "I"); + gAudioGainConfigFields.mMode = env->GetFieldID(audioGainConfigClass, "mMode", "I"); + gAudioGainConfigFields.mChannelMask = env->GetFieldID(audioGainConfigClass, "mChannelMask", + "I"); + gAudioGainConfigFields.mValues = env->GetFieldID(audioGainConfigClass, "mValues", "[I"); + gAudioGainConfigFields.mRampDurationMs = env->GetFieldID(audioGainConfigClass, + "mRampDurationMs", "I"); + + jclass audioPatchClass = env->FindClass("android/media/AudioPatch"); + gAudioPatchClass = (jclass) env->NewGlobalRef(audioPatchClass); + gAudioPatchCstor = env->GetMethodID(audioPatchClass, "<init>", +"(Landroid/media/AudioHandle;[Landroid/media/AudioPortConfig;[Landroid/media/AudioPortConfig;)V"); + gAudioPatchFields.mHandle = env->GetFieldID(audioPatchClass, "mHandle", + "Landroid/media/AudioHandle;"); + + jclass eventHandlerClass = env->FindClass(kEventHandlerClassPathName); + gPostEventFromNative = env->GetStaticMethodID(eventHandlerClass, "postEventFromNative", + "(Ljava/lang/Object;IIILjava/lang/Object;)V"); + + AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback); - return AndroidRuntime::registerNativeMethods(env, + int status = AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); + + if (status == 0) { + status = AndroidRuntime::registerNativeMethods(env, + kEventHandlerClassPathName, gEventHandlerMethods, NELEM(gEventHandlerMethods)); + } + return status; } diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index c5dd06f..9141d44 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -373,7 +373,7 @@ static void android_view_GLES20Canvas_setMatrix(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong matrixPtr) { OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); - renderer->setMatrix(matrix); + renderer->setMatrix(matrix ? *matrix : SkMatrix::I()); } static void android_view_GLES20Canvas_getMatrix(JNIEnv* env, jobject clazz, @@ -387,7 +387,7 @@ static void android_view_GLES20Canvas_concatMatrix(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong matrixPtr) { OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); - renderer->concatMatrix(matrix); + renderer->concatMatrix(*matrix); } // ---------------------------------------------------------------------------- @@ -430,7 +430,7 @@ static void android_view_GLES20Canvas_drawBitmapMatrix(JNIEnv* env, jobject claz OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); - renderer->drawBitmap(bitmap, matrix, paint); + renderer->drawBitmap(bitmap, *matrix, paint); } static void android_view_GLES20Canvas_drawBitmapData(JNIEnv* env, jobject clazz, 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/AndroidManifest.xml b/core/res/AndroidManifest.xml index 14141d7..bb6a1cb 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -258,6 +258,12 @@ <protected-broadcast android:name="com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION" /> + <!-- Defined in RestrictionsManager --> + <protected-broadcast + android:name="android.intent.action.PERMISSION_RESPONSE_RECEIVED" /> + <!-- Defined in RestrictionsManager --> + <protected-broadcast android:name="android.intent.action.REQUEST_PERMISSION" /> + <!-- ====================================== --> <!-- Permissions for things that cost money --> <!-- ====================================== --> diff --git a/core/res/res/anim/input_method_exit.xml b/core/res/res/anim/input_method_exit.xml index 4c4f6a4..117774a 100644 --- a/core/res/res/anim/input_method_exit.xml +++ b/core/res/res/anim/input_method_exit.xml @@ -17,7 +17,7 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> - <translate android:fromYDelta="0" android:toYDelta="-20%" + <translate android:fromYDelta="0" android:toYDelta="10%" android:interpolator="@interpolator/accelerate_quint" android:duration="@android:integer/config_shortAnimTime"/> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml index cb47b3c..7e212be 100644 --- a/core/res/res/anim/lock_screen_behind_enter.xml +++ b/core/res/res/anim/lock_screen_behind_enter.xml @@ -18,11 +18,17 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:background="#ff000000" android:shareInterpolator="false"> + android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="60"> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:fillEnabled="true" android:fillBefore="true" - android:interpolator="@interpolator/decelerate_quint" - android:startOffset="@android:integer/config_shortAnimTime" - android:duration="@android:integer/config_shortAnimTime"/> + android:interpolator="@interpolator/linear_out_slow_in" + android:duration="@integer/config_shortAnimTime"/> + <scale + android:fromXScale="0.95" android:toXScale="1.0" + android:fromYScale="0.95" android:toYScale="1.0" + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" android:fillBefore="true" + android:interpolator="@interpolator/linear_out_slow_in" + android:duration="@integer/config_shortAnimTime" /> </set>
\ No newline at end of file diff --git a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml b/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml deleted file mode 100644 index c29fd1a..0000000 --- a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 2007, 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. -*/ ---> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:detachWallpaper="true" android:shareInterpolator="false"> - <alpha - android:fromAlpha="0.0" android:toAlpha="1.0" - android:fillEnabled="true" android:fillBefore="true" - android:interpolator="@interpolator/decelerate_quad" - android:startOffset="@android:integer/config_shortAnimTime" - android:duration="@android:integer/config_shortAnimTime"/> -</set> diff --git a/core/res/res/color/btn_default_quantum_dark.xml b/core/res/res/color/btn_default_quantum_dark.xml index f2e772d..ec0f140 100644 --- a/core/res/res/color/btn_default_quantum_dark.xml +++ b/core/res/res/color/btn_default_quantum_dark.xml @@ -15,6 +15,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" android:alpha="0.5" android:color="@color/quantum_grey_700"/> - <item android:color="@color/quantum_grey_700"/> + <item android:state_enabled="false" android:alpha="0.5" android:color="@color/button_quantum_dark"/> + <item android:color="@color/button_quantum_dark"/> </selector> diff --git a/core/res/res/color/btn_default_quantum_light.xml b/core/res/res/color/btn_default_quantum_light.xml index de1bd2c..9536d24 100644 --- a/core/res/res/color/btn_default_quantum_light.xml +++ b/core/res/res/color/btn_default_quantum_light.xml @@ -15,6 +15,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" android:alpha="0.5" android:color="@color/quantum_grey_300"/> - <item android:color="@color/quantum_grey_300"/> + <item android:state_enabled="false" android:alpha="0.5" android:color="@color/button_quantum_light"/> + <item android:color="@color/button_quantum_light"/> </selector> diff --git a/core/res/res/drawable/list_selector_quantum.xml b/core/res/res/drawable/item_background_borderless_quantum.xml index 6cd59e5..c2a1c127 100644 --- a/core/res/res/drawable/list_selector_quantum.xml +++ b/core/res/res/drawable/item_background_borderless_quantum.xml @@ -15,8 +15,5 @@ --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:tint="?attr/colorControlHighlight"> - <item android:id="@id/mask"> - <color android:color="@color/white" /> - </item> -</ripple> + android:tint="?attr/colorControlHighlight" + android:pinned="true" /> diff --git a/core/res/res/drawable/item_background_quantum.xml b/core/res/res/drawable/item_background_quantum.xml index c2a1c127..039ca51 100644 --- a/core/res/res/drawable/item_background_quantum.xml +++ b/core/res/res/drawable/item_background_quantum.xml @@ -15,5 +15,8 @@ --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:tint="?attr/colorControlHighlight" - android:pinned="true" /> + android:tint="?attr/colorControlHighlight"> + <item android:id="@id/mask"> + <color android:color="@color/white" /> + </item> +</ripple>
\ No newline at end of file diff --git a/core/res/res/layout/alert_dialog_quantum.xml b/core/res/res/layout/alert_dialog_quantum.xml index e109425..7fd22ad 100644 --- a/core/res/res/layout/alert_dialog_quantum.xml +++ b/core/res/res/layout/alert_dialog_quantum.xml @@ -23,7 +23,10 @@ android:orientation="vertical" android:background="@drawable/dialog_background_quantum" android:translationZ="@dimen/floating_window_z" - android:layout_margin="@dimen/floating_window_margin"> + android:layout_marginLeft="@dimen/floating_window_margin_left" + android:layout_marginTop="@dimen/floating_window_margin_top" + android:layout_marginRight="@dimen/floating_window_margin_right" + android:layout_marginBottom="@dimen/floating_window_margin_bottom"> <LinearLayout android:id="@+id/topPanel" android:layout_width="match_parent" @@ -44,7 +47,7 @@ android:scaleType="fitCenter" android:src="@null" /> <TextView android:id="@+id/alertTitle" - style="?android:attr/windowTitleStyle" + style="?attr/windowTitleStyle" android:singleLine="true" android:ellipsize="end" android:layout_width="match_parent" @@ -65,7 +68,7 @@ android:layout_height="wrap_content" android:clipToPadding="false"> <TextView android:id="@+id/message" - style="?android:attr/textAppearanceMedium" + style="?attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingStart="16dip" @@ -92,26 +95,24 @@ android:gravity="end" android:padding="16dip"> <LinearLayout - style="?android:attr/buttonBarStyle" + style="?attr/buttonBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layoutDirection="locale"> <Button android:id="@+id/button3" - style="?android:attr/buttonBarButtonStyle" + style="?attr/buttonBarButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginRight="8dip" android:maxLines="2" android:minHeight="@dimen/alert_dialog_button_bar_height" /> <Button android:id="@+id/button2" - style="?android:attr/buttonBarButtonStyle" + style="?attr/buttonBarButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxLines="2" android:minHeight="@dimen/alert_dialog_button_bar_height" /> <Button android:id="@+id/button1" - style="?android:attr/buttonBarButtonStyle" - android:layout_marginLeft="8dip" + style="?attr/buttonBarButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxLines="2" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 0ef23e3..489adb4 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -890,9 +890,12 @@ with the appearance of a singel button broken into segments. --> <attr name="segmentedButtonStyle" format="reference" /> - <!-- Background drawable for standalone items that need focus/pressed states. --> + <!-- Background drawable for bordered standalone items that need focus/pressed states. --> <attr name="selectableItemBackground" format="reference" /> + <!-- Background drawable for borderless standalone items that need focus/pressed states. --> + <attr name="selectableItemBackgroundBorderless" format="reference" /> + <!-- Style for buttons without an explicit border, often used in groups. --> <attr name="borderlessButtonStyle" format="reference" /> @@ -4664,11 +4667,11 @@ <!-- Drawable used to show animated touch feedback. --> <declare-styleable name="RippleDrawable"> - <!-- The tint to use for feedback ripples. This attribute is required. --> + <!-- The tint to use for ripple effects. This attribute is required. --> <attr name="tint" /> - <!-- Specifies the Porter-Duff blending mode used to apply the tint. The default vlaue is src_atop, which draws over the opaque parts of the drawable. --> + <!-- Specifies the Porter-Duff blending mode used to apply the tint. The default value is src_atop, which draws over the opaque parts of the drawable. --> <attr name="tintMode" /> - <!-- Whether to pin feedback ripples to the center of the drawable. Default value is false. --> + <!-- Whether to pin ripple effects to the center of the drawable. Default value is false. --> <attr name="pinned" format="boolean" /> </declare-styleable> @@ -6144,6 +6147,11 @@ <!-- Component name of an activity that allows the user to modify the settings for this trust agent. --> <attr name="settingsActivity" /> + <!-- Title for a preference that allows that user to launch the + activity to modify trust agent settings. --> + <attr name="title" /> + <!-- Summary for the same preference as the title. --> + <attr name="summary" /> </declare-styleable> <!-- =============================== --> diff --git a/core/res/res/values/colors_quantum.xml b/core/res/res/values/colors_quantum.xml index 556463e..976930c 100644 --- a/core/res/res/values/colors_quantum.xml +++ b/core/res/res/values/colors_quantum.xml @@ -16,8 +16,14 @@ <!-- Colors specific to Quantum themes. --> <resources> - <color name="background_quantum_dark">#ff303030</color> - <color name="background_quantum_light">@color/white</color> + <color name="background_quantum_dark">#ff414042</color> + <color name="background_quantum_light">#fff1f2f2</color> + + <color name="ripple_quantum_dark">#30ffffff</color> + <color name="ripple_quantum_light">#30000000</color> + + <color name="button_quantum_dark">#ff5a595b</color> + <color name="button_quantum_light">#ffd6d7d7</color> <color name="bright_foreground_quantum_dark">@color/white</color> <color name="bright_foreground_quantum_light">@color/black</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e9d8ccc..f6732d3 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1538,6 +1538,7 @@ --> <string-array translatable="false" name="config_globalActionsList"> <item>power</item> + <item>bugreport</item> <item>users</item> </string-array> diff --git a/core/res/res/values/dimens_quantum.xml b/core/res/res/values/dimens_quantum.xml index 2defee2..b5ba1ca 100644 --- a/core/res/res/values/dimens_quantum.xml +++ b/core/res/res/values/dimens_quantum.xml @@ -52,7 +52,10 @@ <dimen name="text_size_small_quantum">14sp</dimen> <dimen name="floating_window_z">16dp</dimen> - <dimen name="floating_window_margin">32dp</dimen> + <dimen name="floating_window_margin_left">16dp</dimen> + <dimen name="floating_window_margin_top">8dp</dimen> + <dimen name="floating_window_margin_right">16dp</dimen> + <dimen name="floating_window_margin_bottom">32dp</dimen> <!-- the amount of elevation for pressed button state--> <dimen name="button_pressed_z">2dp</dimen> diff --git a/core/res/res/values/donottranslate_quantum.xml b/core/res/res/values/donottranslate_quantum.xml index 83cc4e5..e53c40e 100644 --- a/core/res/res/values/donottranslate_quantum.xml +++ b/core/res/res/values/donottranslate_quantum.xml @@ -27,6 +27,6 @@ <string name="font_family_body_1_quantum">sans-serif</string> <string name="font_family_caption_quantum">sans-serif</string> <string name="font_family_menu_quantum">sans-serif-medium</string> - <string name="font_family_button_quantum">sans-serif</string> + <string name="font_family_button_quantum">sans-serif-medium</string> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 9c33d80..7fc2f49 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2182,6 +2182,7 @@ <public type="attr" name="layout_columnWeight" /> <public type="attr" name="translateX" /> <public type="attr" name="translateY" /> + <public type="attr" name="selectableItemBackgroundBorderless" /> <public-padding type="dimen" name="l_resource_pad" end="0x01050010" /> @@ -2256,6 +2257,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" /> @@ -2272,6 +2274,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 63f2674..59bd667 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/styles_quantum.xml b/core/res/res/values/styles_quantum.xml index ea32681..2f51048 100644 --- a/core/res/res/values/styles_quantum.xml +++ b/core/res/res/values/styles_quantum.xml @@ -404,8 +404,10 @@ please see styles_device_defaults.xml. <item name="textAppearance">?attr/textAppearanceButton</item> <item name="textColor">?attr/textColorPrimary</item> <item name="minHeight">48dip</item> - <item name="minWidth">96dip</item> - <item name="stateListAnimator">@anim/button_state_list_anim_quantum</item> + <item name="minWidth">88dip</item> + + <!-- TODO: Turn this back on when we support inset drawable outlines. --> + <!-- <item name="stateListAnimator">@anim/button_state_list_anim_quantum</item> --> </style> <!-- Small bordered ink button --> @@ -434,7 +436,6 @@ please see styles_device_defaults.xml. <item name="background">@drawable/btn_toggle_quantum</item> <item name="textOn">@string/capital_on</item> <item name="textOff">@string/capital_off</item> - <item name="textAppearance">?attr/textAppearanceSmall</item> <item name="minHeight">48dip</item> </style> @@ -468,34 +469,29 @@ please see styles_device_defaults.xml. <item name="paddingEnd">8dp</item> </style> - <style name="Widget.Quantum.CheckedTextView" parent="Widget.CheckedTextView"> - <item name="drawablePadding">4dip</item> - </style> - + <style name="Widget.Quantum.CheckedTextView" parent="Widget.CheckedTextView" /> <style name="Widget.Quantum.TextSelectHandle" parent="Widget.TextSelectHandle"/> <style name="Widget.Quantum.TextSuggestionsPopupWindow" parent="Widget.TextSuggestionsPopupWindow"/> <style name="Widget.Quantum.AbsListView" parent="Widget.AbsListView"/> <style name="Widget.Quantum.AutoCompleteTextView" parent="Widget.AutoCompleteTextView"> - <item name="dropDownSelector">@drawable/list_selector_quantum</item> + <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item> <item name="popupBackground">@drawable/popup_background_quantum</item> </style> <style name="Widget.Quantum.CompoundButton" parent="Widget.CompoundButton"/> <style name="Widget.Quantum.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox"> - <item name="background">?attr/selectableItemBackground</item> - <item name="drawablePadding">4dip</item> + <item name="background">?attr/selectableItemBackgroundBorderless</item> </style> <style name="Widget.Quantum.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton"> - <item name="background">?attr/selectableItemBackground</item> - <item name="drawablePadding">4dip</item> + <item name="background">?attr/selectableItemBackgroundBorderless</item> </style> <style name="Widget.Quantum.CompoundButton.Star" parent="Widget.CompoundButton.Star"> <item name="button">@drawable/btn_star_quantum</item> - <item name="background">?attr/selectableItemBackground</item> + <item name="background">?attr/selectableItemBackgroundBorderless</item> </style> <style name="Widget.Quantum.CompoundButton.Switch"> @@ -507,7 +503,7 @@ please see styles_device_defaults.xml. <item name="textOff"></item> <item name="switchMinWidth">4dip</item> <item name="switchPadding">4dip</item> - <item name="background">?attr/selectableItemBackground</item> + <item name="background">?attr/selectableItemBackgroundBorderless</item> </style> <style name="Widget.Quantum.EditText" parent="Widget.EditText"/> @@ -586,7 +582,7 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.PopupWindow" parent="Widget.PopupWindow"/> <style name="Widget.Quantum.PopupWindow.ActionMode"> - <item name="popupBackground">@color/black</item> + <item name="popupBackground">@drawable/popup_background_quantum</item> <item name="popupAnimationStyle">@style/Animation.PopupWindow.ActionMode</item> </style> @@ -626,7 +622,7 @@ please see styles_device_defaults.xml. <item name="paddingStart">16dip</item> <item name="paddingEnd">16dip</item> <item name="mirrorForRtl">true</item> - <item name="background">?attr/selectableItemBackground</item> + <item name="background">?attr/selectableItemBackgroundBorderless</item> </style> <style name="Widget.Quantum.RatingBar" parent="Widget.RatingBar"> @@ -653,7 +649,7 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.Spinner" parent="Widget.Spinner.DropDown"> <item name="background">@drawable/spinner_background_quantum</item> - <item name="dropDownSelector">@drawable/list_selector_quantum</item> + <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item> <item name="popupBackground">@drawable/popup_background_quantum</item> <item name="dropDownVerticalOffset">0dip</item> <item name="dropDownHorizontalOffset">0dip</item> @@ -712,7 +708,7 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.QuickContactBadgeSmall.WindowLarge" parent="Widget.QuickContactBadgeSmall.WindowLarge"/> <style name="Widget.Quantum.ListPopupWindow" parent="Widget.ListPopupWindow"> - <item name="dropDownSelector">@drawable/list_selector_quantum</item> + <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item> <item name="popupBackground">@drawable/popup_background_quantum</item> <item name="popupAnimationStyle">@style/Animation.Quantum.Popup</item> <item name="dropDownVerticalOffset">0dip</item> @@ -783,7 +779,6 @@ please see styles_device_defaults.xml. <item name="itemPadding">8dip</item> <item name="homeLayout">@layout/action_bar_home_quantum</item> <item name="gravity">center_vertical</item> - <item name="contentInsetStart">56dp</item> </style> <style name="Widget.Quantum.ActionBar.Solid"> @@ -810,7 +805,7 @@ please see styles_device_defaults.xml. </style> <style name="Widget.Quantum.MediaRouteButton"> - <item name="background">?attr/selectableItemBackground</item> + <item name="background">?attr/selectableItemBackgroundBorderless</item> <item name="externalRouteEnabledDrawable">@drawable/ic_media_route_quantum</item> <item name="minWidth">56dp</item> <item name="minHeight">48dp</item> @@ -880,12 +875,7 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.Light.ListView" parent="Widget.Quantum.ListView"/> <style name="Widget.Quantum.Light.ListView.White" parent="Widget.Quantum.ListView.White"/> <style name="Widget.Quantum.Light.PopupWindow" parent="Widget.Quantum.PopupWindow"/> - - <style name="Widget.Quantum.Light.PopupWindow.ActionMode"> - <item name="popupBackground">@color/white</item> - <item name="popupAnimationStyle">@style/Animation.PopupWindow.ActionMode</item> - </style> - + <style name="Widget.Quantum.Light.PopupWindow.ActionMode" parent="Widget.Quantum.PopupWindow.ActionMode"/> <style name="Widget.Quantum.Light.ProgressBar" parent="Widget.Quantum.ProgressBar"/> <style name="Widget.Quantum.Light.ProgressBar.Horizontal" parent="Widget.Quantum.ProgressBar.Horizontal"/> <style name="Widget.Quantum.Light.ProgressBar.Small" parent="Widget.Quantum.ProgressBar.Small"/> @@ -895,7 +885,6 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.Light.ProgressBar.Small.Inverse" parent="Widget.Quantum.ProgressBar.Small.Inverse"/> <style name="Widget.Quantum.Light.ProgressBar.Large.Inverse" parent="Widget.Quantum.ProgressBar.Large.Inverse"/> <style name="Widget.Quantum.Light.SeekBar" parent="Widget.Quantum.SeekBar"/> - <style name="Widget.Quantum.Light.RatingBar" parent="Widget.Quantum.RatingBar" /> <style name="Widget.Quantum.Light.RatingBar.Indicator" parent="Widget.RatingBar.Indicator"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6e1629b..6cd7cd2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1679,7 +1679,6 @@ <java-symbol type="anim" name="push_down_out" /> <java-symbol type="anim" name="push_up_in" /> <java-symbol type="anim" name="push_up_out" /> - <java-symbol type="anim" name="lock_screen_wallpaper_behind_enter" /> <java-symbol type="anim" name="lock_screen_behind_enter" /> <java-symbol type="bool" name="config_alwaysUseCdmaRssi" /> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 41f4ff8..648660b 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -124,6 +124,7 @@ please see themes_device_defaults.xml. <item name="buttonStyleToggle">@android:style/Widget.Button.Toggle</item> <item name="selectableItemBackground">@android:drawable/item_background</item> + <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackground</item> <item name="borderlessButtonStyle">?android:attr/buttonStyle</item> <item name="homeAsUpIndicator">@android:drawable/ic_ab_back_holo_dark</item> @@ -1032,6 +1033,7 @@ please see themes_device_defaults.xml. <item name="mediaRouteButtonStyle">@android:style/Widget.Holo.MediaRouteButton</item> <item name="selectableItemBackground">@android:drawable/item_background_holo_dark</item> + <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackground</item> <item name="borderlessButtonStyle">@android:style/Widget.Holo.Button.Borderless</item> <item name="homeAsUpIndicator">@android:drawable/ic_ab_back_holo_dark</item> @@ -1372,6 +1374,7 @@ please see themes_device_defaults.xml. <item name="mediaRouteButtonStyle">@android:style/Widget.Holo.Light.MediaRouteButton</item> <item name="selectableItemBackground">@android:drawable/item_background_holo_light</item> + <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackground</item> <item name="borderlessButtonStyle">@android:style/Widget.Holo.Light.Button.Borderless</item> <item name="homeAsUpIndicator">@android:drawable/ic_ab_back_holo_light</item> diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml index 484c694..cdbd771 100644 --- a/core/res/res/values/themes_quantum.xml +++ b/core/res/res/values/themes_quantum.xml @@ -101,6 +101,7 @@ please see themes_device_defaults.xml. <item name="mediaRouteButtonStyle">@style/Widget.Quantum.MediaRouteButton</item> <item name="selectableItemBackground">@drawable/item_background_quantum</item> + <item name="selectableItemBackgroundBorderless">@drawable/item_background_borderless_quantum</item> <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless</item> <item name="homeAsUpIndicator">@drawable/ic_ab_back_quantum</item> @@ -125,8 +126,7 @@ please see themes_device_defaults.xml. <item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum_anim</item> <item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item> - <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item> - + <item name="listChoiceBackgroundIndicator">?attr/selectableItemBackground</item> <item name="activatedBackgroundIndicator">@drawable/activated_background_quantum</item> <item name="listDividerAlertDialog">@drawable/list_divider_quantum</item> @@ -308,7 +308,7 @@ please see themes_device_defaults.xml. <item name="actionModePopupWindowStyle">@style/Widget.Quantum.PopupWindow.ActionMode</item> <item name="actionBarWidgetTheme">@style/ThemeOverlay.Quantum.ActionBarWidget</item> <item name="actionBarTheme">@null</item> - <item name="actionBarItemBackground">@drawable/item_background_quantum</item> + <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item> <item name="actionModeCutDrawable">@drawable/ic_menu_cut_quantum</item> <item name="actionModeCopyDrawable">@drawable/ic_menu_copy_quantum</item> @@ -378,8 +378,8 @@ please see themes_device_defaults.xml. <item name="colorControlNormal">?attr/textColorSecondary</item> <item name="colorControlActivated">?attr/colorPrimary</item> - <item name="colorControlHighlight">#30ffffff</item> + <item name="colorControlHighlight">@color/ripple_quantum_dark</item> <item name="colorButtonNormal">@color/btn_default_quantum_dark</item> </style> @@ -446,6 +446,7 @@ please see themes_device_defaults.xml. <item name="mediaRouteButtonStyle">@style/Widget.Quantum.Light.MediaRouteButton</item> <item name="selectableItemBackground">@drawable/item_background_quantum</item> + <item name="selectableItemBackgroundBorderless">@drawable/item_background_borderless_quantum</item> <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item> <item name="homeAsUpIndicator">@drawable/ic_ab_back_quantum</item> @@ -470,8 +471,7 @@ please see themes_device_defaults.xml. <item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum_anim</item> <item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item> - <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item> - + <item name="listChoiceBackgroundIndicator">?attr/selectableItemBackground</item> <item name="activatedBackgroundIndicator">@drawable/activated_background_quantum</item> <item name="expandableListPreferredItemPaddingLeft">40dip</item> @@ -655,7 +655,7 @@ please see themes_device_defaults.xml. <item name="actionModePopupWindowStyle">@style/Widget.Quantum.Light.PopupWindow.ActionMode</item> <item name="actionBarWidgetTheme">@style/ThemeOverlay.Quantum.ActionBarWidget</item> <item name="actionBarTheme">@null</item> - <item name="actionBarItemBackground">@drawable/item_background_quantum</item> + <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item> <item name="actionModeCutDrawable">@drawable/ic_menu_cut_quantum</item> <item name="actionModeCopyDrawable">@drawable/ic_menu_copy_quantum</item> @@ -668,7 +668,7 @@ please see themes_device_defaults.xml. <item name="dividerVertical">?attr/listDivider</item> <item name="dividerHorizontal">?attr/listDivider</item> <item name="buttonBarStyle">@style/Widget.Quantum.Light.ButtonBar</item> - <item name="buttonBarButtonStyle">@style/Widget.Quantum.Light.Button.Borderless.Small</item> + <item name="buttonBarButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item> <item name="segmentedButtonStyle">@style/Widget.Quantum.Light.SegmentedButton</item> <!-- SearchView attributes --> @@ -721,8 +721,8 @@ please see themes_device_defaults.xml. <item name="colorControlNormal">?attr/textColorSecondary</item> <item name="colorControlActivated">?attr/colorPrimary</item> - <item name="colorControlHighlight">#30000000</item> + <item name="colorControlHighlight">@color/ripple_quantum_light</item> <item name="colorButtonNormal">@color/btn_default_quantum_light</item> </style> @@ -770,7 +770,8 @@ please see themes_device_defaults.xml. <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_light</item> <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_light</item> - <item name="colorButtonNormal">@color/quantum_grey_100</item> + <item name="colorControlHighlight">@color/ripple_quantum_light</item> + <item name="colorButtonNormal">@color/btn_default_quantum_light</item> </style> <!-- Theme overlay that replaces colors with their dark versions but preserves @@ -805,7 +806,8 @@ please see themes_device_defaults.xml. <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_dark</item> <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_dark</item> - <item name="colorButtonNormal">@color/quantum_grey_700</item> + <item name="colorControlHighlight">@color/ripple_quantum_dark</item> + <item name="colorButtonNormal">@color/btn_default_quantum_dark</item> </style> <!-- Theme overlay that replaces the activated control color (which by default @@ -913,6 +915,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 +947,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. --> @@ -986,7 +1004,7 @@ please see themes_device_defaults.xml. <item name="colorBackgroundCacheHint">@null</item> <item name="buttonBarStyle">@style/Widget.Quantum.ButtonBar.AlertDialog</item> - <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless.Small</item> + <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless</item> <item name="textAppearance">@style/TextAppearance.Quantum</item> <item name="textAppearanceInverse">@style/TextAppearance.Quantum.Inverse</item> @@ -1105,7 +1123,7 @@ please see themes_device_defaults.xml. <item name="colorBackgroundCacheHint">@null</item> <item name="buttonBarStyle">@style/Widget.Quantum.Light.ButtonBar.AlertDialog</item> - <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless.Small</item> + <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item> <item name="textAppearance">@style/TextAppearance.Quantum</item> <item name="textAppearanceInverse">@style/TextAppearance.Quantum.Inverse</item> 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/data/keyboards/Vendor_2378_Product_1008.kl b/data/keyboards/Vendor_2378_Product_1008.kl new file mode 100644 index 0000000..478da03 --- /dev/null +++ b/data/keyboards/Vendor_2378_Product_1008.kl @@ -0,0 +1,35 @@ +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# OnLive, Inc. OnLive Wireless Controller, USB adapter + +key 304 BUTTON_A +key 305 BUTTON_B +key 307 BUTTON_X +key 308 BUTTON_Y +key 310 BUTTON_L1 +key 311 BUTTON_R1 +key 315 BUTTON_START +key 314 BUTTON_SELECT +key 317 BUTTON_THUMBL +key 318 BUTTON_THUMBR + +axis 0x00 X +axis 0x01 Y +axis 0x03 Z +axis 0x04 RZ +axis 0x05 RTRIGGER +axis 0x02 LTRIGGER +axis 0x10 HAT_X +axis 0x11 HAT_Y diff --git a/docs/html/distribute/engage/app-updates.jd b/docs/html/distribute/engage/app-updates.jd index 6b751b9..2b7cd2c 100644 --- a/docs/html/distribute/engage/app-updates.jd +++ b/docs/html/distribute/engage/app-updates.jd @@ -36,12 +36,12 @@ page.image=/images/gp-your-user-0.jpg "18x6," data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="related-resources"> +<div class="headerLine"> + <h2 id="related-resources"> Related Resources - </h1> + </h2> + - <hr> </div> <div class="resource-widget resource-flow-layout col-13" data-query= diff --git a/docs/html/distribute/engage/community.jd b/docs/html/distribute/engage/community.jd index 035058a..e202d54 100644 --- a/docs/html/distribute/engage/community.jd +++ b/docs/html/distribute/engage/community.jd @@ -29,12 +29,14 @@ page.image=/images/gp-engage-9.jpg Learn more about how to <a href="{@docRoot}distribute/users/build-community.html">build and manage a community</a>. </p> -<div class="headerLine clearfloat"> - <h1 id="related-resources"> +<p style="clear:both"> +</p> +<div class="headerLine"> + <h2 id="related-resources"> Related Resources - </h1> + </h2> + - <hr> </div> <div class="resource-widget resource-flow-layout col-13" data-query= diff --git a/docs/html/distribute/engage/deep-linking.jd b/docs/html/distribute/engage/deep-linking.jd index cd62f9d..50728c5 100644 --- a/docs/html/distribute/engage/deep-linking.jd +++ b/docs/html/distribute/engage/deep-linking.jd @@ -11,7 +11,7 @@ page.image=/images/gp-listing-4.jpg </p> <div class="headerLine"> -<h1>Deep Linking from Google+ Posts</h1><hr> +<h2>Deep Linking from Google+ Posts</h2> </div> <p> @@ -43,8 +43,8 @@ page.image=/images/gp-listing-4.jpg </div> -<div class="headerLine clearfloat"> -<h1>Deep Linking from Google Search — App Indexing</h1><hr> +<div class="headerLine"> +<h2>Deep Linking from Google Search — App Indexing</h2> </div> <p> @@ -64,12 +64,12 @@ page.image=/images/gp-listing-4.jpg <img src="{@docRoot}images/gp-listing-4.jpg" style="padding-top:1em;"> </div> -<div class="headerLine clearfloat"> - <h1 id="related-resources"> +<div class="headerLine"> + <h2 id="related-resources"> Related Resources - </h1> + </h2> + - <hr> </div> <div class="resource-widget resource-flow-layout col-13" data-query= diff --git a/docs/html/distribute/engage/easy-signin.jd b/docs/html/distribute/engage/easy-signin.jd index 92c3ffc..d066181 100644 --- a/docs/html/distribute/engage/easy-signin.jd +++ b/docs/html/distribute/engage/easy-signin.jd @@ -37,11 +37,11 @@ page.image=/images/google/gps-googleplus.png </p> <div class="headerLine"> - <h1> + <h2> And Spreading the Word a Snap - </h1> + </h2> + - <hr> </div> @@ -85,12 +85,14 @@ page.image=/images/google/gps-googleplus.png </li> </ul> - <div class="headerLine clearfloat"> - <h1 id="related-resources"> +<p style="clear:both"> +</p> + <div class="headerLine"> + <h2 id="related-resources"> Related Resources - </h1> + </h2> + - <hr> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/engage/game-services.jd b/docs/html/distribute/engage/game-services.jd index 5153435..1c77d2d 100644 --- a/docs/html/distribute/engage/game-services.jd +++ b/docs/html/distribute/engage/game-services.jd @@ -75,12 +75,12 @@ page.image=/images/google/gps-play_games_logo.png Game Developer Best Practices</a>. </p> -<div class="headerLine clearfloat"> - <h1 id="related-resources"> +<div class="headerLine"> + <h2 id="related-resources"> Related Resources - </h1> + </h2> + - <hr> </div> <div class="resource-widget resource-flow-layout col-13" data-query= diff --git a/docs/html/distribute/engage/gcm.jd b/docs/html/distribute/engage/gcm.jd index d793124e..7d9b6bb 100644 --- a/docs/html/distribute/engage/gcm.jd +++ b/docs/html/distribute/engage/gcm.jd @@ -35,12 +35,12 @@ page.image=/images/gcm/gcm-logo.png free and there are no quotas. </p> -<div class="headerLine clearfloat"> - <h1 id="related-resources"> +<div class="headerLine"> + <h2 id="related-resources"> Related Resources - </h1> + </h2> + - <hr> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/engage/notifications.jd b/docs/html/distribute/engage/notifications.jd index fecfb45..1aa0637 100644 --- a/docs/html/distribute/engage/notifications.jd +++ b/docs/html/distribute/engage/notifications.jd @@ -40,17 +40,15 @@ page.image=/design/media/notifications_pattern_anatomy.png </p> - <div class="sidebox" style="width:326px;float:left;margin-left:0"> <p><strong>Tip:</strong> Use notifications sparingly — be sure any information presented is useful. Give users the option to turn notifications off. </p> - </div> - <div class="headerLine clearfloat"> - <h1 id="related-resources"> + <div class="headerLine"> + <h2 id="related-resources"> Related Resources - </h1><hr> + </h2> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/engage/video.jd b/docs/html/distribute/engage/video.jd index 1a30f3a..c5a4997 100644 --- a/docs/html/distribute/engage/video.jd +++ b/docs/html/distribute/engage/video.jd @@ -23,12 +23,12 @@ page.image=/images/gp-engage-smule.jpg <img src="{@docRoot}images/gp-engage-smule.jpg"> </div> -<div class="headerLine clearfloat"> - <h1 id="related-resources"> +<div class="headerLine"> + <h2 id="related-resources"> Related Resources - </h1> + </h2> + - <hr> </div> <div class="resource-widget resource-flow-layout col-13" data-query= diff --git a/docs/html/distribute/engage/widgets.jd b/docs/html/distribute/engage/widgets.jd index b17af08..286adea 100644 --- a/docs/html/distribute/engage/widgets.jd +++ b/docs/html/distribute/engage/widgets.jd @@ -28,10 +28,13 @@ page.image=/images/gp-engage-0.jpg or upcoming deadlines. Widgets should serve as more than a launcher icon.</p> </div> -<div class="headerLine clearfloat"> - <h1 id="related-resources"> +<p style="clear:both"> +</p> + +<div class="headerLine"> + <h2 id="related-resources"> Related Resources - </h1><hr> + </h2> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/essentials/best-practices/apps.jd b/docs/html/distribute/essentials/best-practices/apps.jd index 055a349..bbac727 100644 --- a/docs/html/distribute/essentials/best-practices/apps.jd +++ b/docs/html/distribute/essentials/best-practices/apps.jd @@ -17,7 +17,7 @@ page.metaDescription=Essential tips for launching successful apps in Google Play <p>The following best practices have enabled developers worldwide to build great, successful apps for Google Play.</p> <div class="headerLine"> -<h1 id="essentials">Get the Essentials Right</h1><hr> +<h2 id="essentials">Get the Essentials Right</h2> </div> <h3>1. Make it Android</h3> @@ -82,11 +82,11 @@ page.metaDescription=Essential tips for launching successful apps in Google Play </ul> <div class="headerLine"> - <h1 id="users"> + <h2 id="users"> Get Users - </h1> + </h2> + - <hr> </div> <h3> @@ -150,11 +150,11 @@ page.metaDescription=Essential tips for launching successful apps in Google Play </ul> <div class="headerLine"> - <h1 id="engage"> + <h2 id="engage"> Engage and Retain - </h1> + </h2> + - <hr> </div> <h3> @@ -211,11 +211,11 @@ page.metaDescription=Essential tips for launching successful apps in Google Play </ul> <div class="headerLine"> - <h1 id="beyond"> + <h2 id="beyond"> Beyond the Basics - </h1> + </h2> + - <hr> </div> <ul> @@ -249,7 +249,7 @@ page.metaDescription=Essential tips for launching successful apps in Google Play </ul> <div class="headerLine"> -<h1 id="related-resources">Related Resources</h1><hr> +<h2 id="related-resources">Related Resources</h2> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/essentials/best-practices/games.jd b/docs/html/distribute/essentials/best-practices/games.jd index ac1df44..c4ce66e 100644 --- a/docs/html/distribute/essentials/best-practices/games.jd +++ b/docs/html/distribute/essentials/best-practices/games.jd @@ -20,11 +20,11 @@ page.metaDescription=Essential tips for launching successful games in Google Pla </p> <div class="headerLine"> - <h1 id="users"> + <h2 id="users"> Get Users - </h1> + </h2> + - <hr> </div> <h3> @@ -111,11 +111,11 @@ page.metaDescription=Essential tips for launching successful games in Google Pla </ul> <div class="headerLine"> - <h1 id="engage"> + <h2 id="engage"> Engage and Retain - </h1> + </h2> + - <hr> </div> <h3> @@ -213,11 +213,11 @@ page.metaDescription=Essential tips for launching successful games in Google Pla </ul> <div class="headerLine"> - <h1 id="beyond"> + <h2 id="beyond"> Beyond the Basics - </h1> + </h2> + - <hr> </div> <ul> @@ -249,7 +249,7 @@ page.metaDescription=Essential tips for launching successful games in Google Pla </ul> <div class="headerLine"> -<h1 id="related-resources">Related Resources</h1><hr> +<h2 id="related-resources">Related Resources</h2> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/essentials/gpfe-guidelines.jd b/docs/html/distribute/essentials/gpfe-guidelines.jd index 799009f..734bddc 100644 --- a/docs/html/distribute/essentials/gpfe-guidelines.jd +++ b/docs/html/distribute/essentials/gpfe-guidelines.jd @@ -50,12 +50,12 @@ Xnonavpage=true Distribution Agreement</a>. </p> -<div class="headerLine clearfloat"> - <h1 id="basic-reqts"> +<div class="headerLine"> + <h2 id="basic-reqts"> Basic Requirements - </h1> + </h2> + - <hr> </div> <p> @@ -108,11 +108,11 @@ Xnonavpage=true </ul> <div class="headerLine"> - <h1 id="monetizing-ads"> + <h2 id="monetizing-ads"> Monetizing and Ads - </h1> + </h2> + - <hr> </div> <p> @@ -195,11 +195,11 @@ Xnonavpage=true </ul> <div class="headerLine"> - <h1 id="e-value"> + <h2 id="e-value"> Educational Value - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -299,11 +299,11 @@ Xnonavpage=true </p> <div class="headerLine"> - <h1 id="quality"> + <h2 id="quality"> App Quality - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -410,11 +410,11 @@ Xnonavpage=true </ul> <div class="headerLine"> - <h1 id="test-environment"> + <h2 id="test-environment"> Test Environment - </h1> + </h2> + - <hr> </div> <p> @@ -487,7 +487,7 @@ Xnonavpage=true </ul> <div class="headerLine"> -<h1>Related Resources</h1><hr> +<h2>Related Resources</h2> </div> <div class="dynamic-grid"> diff --git a/docs/html/distribute/essentials/optimizing-your-app.jd b/docs/html/distribute/essentials/optimizing-your-app.jd index 3fe91b28..696ef53 100644 --- a/docs/html/distribute/essentials/optimizing-your-app.jd +++ b/docs/html/distribute/essentials/optimizing-your-app.jd @@ -53,11 +53,11 @@ page.image=/distribute/images/gp-optimize-card.jpg </p> <div class="headerLine"> - <h1 id="listen-to-your-users"> + <h2 id="listen-to-your-users"> Listen to Your Users - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -147,11 +147,11 @@ page.image=/distribute/images/gp-optimize-card.jpg </p> <div class="headerLine" id="measuring-analyzing-responding"> - <h1> + <h2> Measuring, Analyzing, and Responding to User Behavior - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -260,11 +260,11 @@ who can help automate, measure, and optimize your mobile marketing. </p> <div class="headerLine"> - <h1 id="improve-stability"> + <h2 id="improve-stability"> Improve Stability and Eliminate Bugs - </h1> + </h2> + - <hr> </div> <p> @@ -298,11 +298,11 @@ who can help automate, measure, and optimize your mobile marketing. </p> <div class="headerLine"> - <h1 id="improve-ui"> + <h2 id="improve-ui"> Improve UI Responsiveness - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -352,11 +352,11 @@ who can help automate, measure, and optimize your mobile marketing. </p> <div class="headerLine"> - <h1 id="improve-usability"> + <h2 id="improve-usability"> Improve Usability - </h1> + </h2> + - <hr> </div> <div class="sidebox-wrapper" style="float:right;"> @@ -403,11 +403,11 @@ who can help automate, measure, and optimize your mobile marketing. </p> <div class="headerLine"> - <h1 id="professional-appearance"> + <h2 id="professional-appearance"> Professional Appearance and Aesthetics - </h1> + </h2> + - <hr> </div> <p> @@ -439,11 +439,11 @@ who can help automate, measure, and optimize your mobile marketing. </p> <div class="headerLine"> - <h1 id="deliver-features"> + <h2 id="deliver-features"> Deliver the Right Set of Features - </h1> + </h2> + - <hr> </div> <p> @@ -464,11 +464,11 @@ who can help automate, measure, and optimize your mobile marketing. </p> <div class="headerLine"> - <h1 id="integrate"> + <h2 id="integrate"> Integrate with the System and Third-Party apps - </h1> + </h2> + - <hr> </div> <p> @@ -502,7 +502,7 @@ who can help automate, measure, and optimize your mobile marketing. </p> <div class="headerLine"> -<h1 id="related-resources">Related Resources</h1><hr> +<h2 id="related-resources">Related Resources</h2> </div> <div class="resource-widget resource-flow-layout col-13" data-query="collection:distribute/essentials/optimizing, tag:addia" diff --git a/docs/html/distribute/essentials/quality/core.jd b/docs/html/distribute/essentials/quality/core.jd index 558b030..c301f8c 100644 --- a/docs/html/distribute/essentials/quality/core.jd +++ b/docs/html/distribute/essentials/quality/core.jd @@ -63,12 +63,12 @@ page.image=/distribute/images/core-quality-guidelines.jpg Guidelines</a>. </p> -<div class="headerLine clearfloat"> - <h1 id="ux"> +<div class="headerLine"> + <h2 id="ux"> Visual Design and User Interaction - </h1> + </h2> + - <hr> </div> <p> @@ -244,12 +244,12 @@ data-sortorder="-timestamp" data-cardsizes="9x3,9x3,6x3,6x3,6x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="fn"> +<div class="headerLine"> + <h2 id="fn"> Functionality - </h1> + </h2> + - <hr> </div> <p> @@ -518,12 +518,12 @@ data-maxresults="6"> data-sortorder="-timestamp" data-cardsizes="6x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="ps"> +<div class="headerLine"> + <h2 id="ps"> Performance and Stability - </h1> + </h2> + - <hr> </div> <p> @@ -679,12 +679,12 @@ data-sortorder="-timestamp" data-cardsizes="6x3" data-maxresults="6"> data-cardsizes="6x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="listing"> +<div class="headerLine"> + <h2 id="listing"> Google Play - </h1> + </h2> + - <hr> </div> <p> @@ -828,12 +828,12 @@ data-cardsizes="6x3" data-maxresults="6"> data-cardsizes="6x3,6x3,6x3,6x3,6x3,6x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="test-environment"> +<div class="headerLine"> + <h2 id="test-environment"> Setting Up a Test Environment - </h1> + </h2> + - <hr> </div> <p> @@ -863,12 +863,12 @@ data-cardsizes="6x3,6x3,6x3,6x3,6x3,6x3" data-maxresults="6"> increase the number or complexity of tests and quality criteria. </p> -<div class="headerLine clearfloat"> - <h1 id="tests"> +<div class="headerLine"> + <h2 id="tests"> Test Procedures - </h1> + </h2> + - <hr> </div> <p> diff --git a/docs/html/distribute/essentials/quality/tablets.jd b/docs/html/distribute/essentials/quality/tablets.jd index 7dfab48..966c462 100644 --- a/docs/html/distribute/essentials/quality/tablets.jd +++ b/docs/html/distribute/essentials/quality/tablets.jd @@ -52,7 +52,7 @@ Xnonavpage=true help you address each recommendation included. </p> -<div class="headerLine"><h1 id="core-app-quality">1. Test for Basic Tablet App Quality</h1><hr></div> +<div class="headerLine"><h2 id="core-app-quality">1. Test for Basic Tablet App Quality</h2></div> <p>The first step in delivering a great tablet app experience is making sure that it meets the <em>core app quality criteria</em> for all of the devices @@ -78,8 +78,8 @@ Before publishing, also ensure that your app passes the basic technical checks a Tips page</a>.</p> -<div class="headerLine clearfloat"> -<h1 id="optimize-layouts">2. Optimize Layouts for Larger Screens</h1><hr></div> +<div class="headerLine"> +<h2 id="optimize-layouts">2. Optimize Layouts for Larger Screens</h2></div> <p> Android makes it easy to develop an app that runs well on a wide range of @@ -167,7 +167,7 @@ multi-pane UI for tablets (see next section).</li> data-maxResults="6"></div> -<div class="headerLine clearfloat"><h1 id="use-extra-space">3. Take Advantage of Extra Screen Area</h1><hr></div> +<div class="headerLine"><h2 id="use-extra-space">3. Take Advantage of Extra Screen Area</h2></div> <div style="width:340px;float:right;margin:1.5em;margin-bottom:0;margin-top:0;"> <img src="{@docRoot}images/training/app-navigation-multiple-sizes-multipane-good.png" @@ -227,7 +227,7 @@ different layouts in the appropriate screen size buckets (such as data-cardSizes="6x3,6x3,6x3" data-maxResults="6"></div> -<div class="headerLine clearfloat"><h1 id="use-tablet-icons">4. Use Assets Designed for Tablet Screens</h1><hr></div> +<div class="headerLine"><h2 id="use-tablet-icons">4. Use Assets Designed for Tablet Screens</h2></div> <div><img src="{@docRoot}design/media/devices_displays_density@2x.png"></div> @@ -316,8 +316,8 @@ it will request the {@code xxhdpi} version of the launcher icon.</li> data-cardSizes="9x3" data-maxResults="6"></div> -<div class="headerLine clearfloat"><h1 id="adjust-font-sizes">5. -Adjust Font Sizes and Touch Targets</h1><hr></div> +<div class="headerLine"><h2 id="adjust-font-sizes">5. +Adjust Font Sizes and Touch Targets</h2></div> <p>To make sure your app is easy to use on tablets, take some time to adjust the font sizes and touch targets in your tablet UI, for all of the screen @@ -353,7 +353,7 @@ or just centering the icon within the transparent button.</li> data-cardSizes="9x3,9x3,6x3,6x3,6x3" data-maxResults="6"></div> -<div class="headerLine clearfloat"><h1 id="adjust-widgets">6. Adjust Sizes of Home Screen Widgets</h1><hr></div> +<div class="headerLine"><h2 id="adjust-widgets">6. Adjust Sizes of Home Screen Widgets</h2></div> <p>If your app includes a home screen widget, here are a few points to consider to ensure a great user experience on tablet screens: </p> @@ -380,7 +380,7 @@ possible.</li> data-maxResults="6"></div> -<div class="headerLine clearfloat"><h1 id="offer-full-feature-set">7. Full Feature Set for Tablet Users</h1><hr></div> +<div class="headerLine"><h2 id="offer-full-feature-set">7. Full Feature Set for Tablet Users</h2></div> <div class="centered-full-image" style="width:600px;margin:1.5em"><img src="{@docRoot}images/gp-tablets-full-feature-set.png" alt="Tablet feature sets"></div> @@ -415,7 +415,7 @@ some recommendations:</p> </li> </ul> -<div class="headerLine clearfloat"><h1 id="android-versions">8. Target Android Versions Properly</h1><hr></div> +<div class="headerLine"><h2 id="android-versions">8. Target Android Versions Properly</h2></div> <p> To ensure the broadest possible distribution to tablets, make sure that your @@ -466,7 +466,7 @@ some recommendations:</p> data-cardSizes="6x3" data-maxResults="6"></div> -<div class="headerLine clearfloat"><h1 id="hardware-requirements">9. Declare Hardware Feature Dependencies Properly</h1><hr></div> +<div class="headerLine"><h2 id="hardware-requirements">9. Declare Hardware Feature Dependencies Properly</h2></div> <p> Handsets and tablets typically offer slightly different hardware support for @@ -536,7 +536,7 @@ permissions, make sure to explicitly declare a corresponding data-cardSizes="9x3" data-maxResults="6"></div> -<div class="headerLine clearfloat"><h1 id="support-screens">10. Declare Support for Tablet Screens</h1><hr></div> +<div class="headerLine"><h2 id="support-screens">10. Declare Support for Tablet Screens</h2></div> <p>To ensure that you can distribute your app to a broad range of tablets, your app should declare support for tablet screen sizes in its manifest file, as follows:</p> @@ -569,7 +569,7 @@ element in your app.</p> data-maxResults="6"></div> -<div class="headerLine clearfloat"><h1 id="google-play">11. Showcase Your Tablet UI in Google Play</h1><hr></div> +<div class="headerLine"><h2 id="google-play">11. Showcase Your Tablet UI in Google Play</h2></div> <p> After you've done the work to create an rich, optimized UI for your tablet @@ -697,12 +697,12 @@ element in your app.</p> data-cardSizes="9x3,9x3,9x3,9x3" data-maxResults="6"></div> -<div class="headerLine clearfloat"> - <h1 id="google-play-best-practices"> +<div class="headerLine"> + <h2 id="google-play-best-practices"> 12. Follow Best Practices for Publishing in Google Play - </h1> + </h2> + - <hr> </div> <p> @@ -807,12 +807,12 @@ appropriate.</p> data-maxResults="6"></div> -<div class="headerLine clearfloat"> - <h1 id="test-environment"> +<div class="headerLine"> + <h2 id="test-environment"> Setting Up a Test Environment for Tablets - </h1> + </h2> + - <hr> </div> <p> @@ -858,7 +858,7 @@ listed platform versions, screen configurations, and hardware feature configurat </tr> </table> -<div class="headerLine clearfloat"><h1 id="related-resources">Related Resources</h1><hr></div> +<div class="headerLine"><h2 id="related-resources">Related Resources</h2></div> <div class="resource-widget resource-flow-layout col-13" data-query="collection:distribute/essentials/tabletguidelines" diff --git a/docs/html/distribute/googleplay/about.jd b/docs/html/distribute/googleplay/about.jd index cf0c6d2..c7c91ac 100644 --- a/docs/html/distribute/googleplay/about.jd +++ b/docs/html/distribute/googleplay/about.jd @@ -58,11 +58,11 @@ page.image=/distribute/images/about-play.jpg </p> <div class="headerLine"> - <h1 id="ratings-reviews"> + <h2 id="ratings-reviews"> User Ratings and Reviews - </h1> + </h2> + - <hr> </div> <p> @@ -83,11 +83,11 @@ page.image=/distribute/images/about-play.jpg </div> <div class="headerLine"> - <h1 id="category-browsing"> + <h2 id="category-browsing"> Category Browsing - </h1> + </h2> + - <hr> </div> <p> @@ -98,11 +98,11 @@ page.image=/distribute/images/about-play.jpg </p> <div class="headerLine"> - <h1 id="search"> + <h2 id="search"> Search - </h1> + </h2> + - <hr> </div> <p> @@ -113,11 +113,11 @@ page.image=/distribute/images/about-play.jpg </p> <div class="headerLine"> - <h1 id="top-charts-and-lists"> + <h2 id="top-charts-and-lists"> Top Charts and Lists - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -195,11 +195,11 @@ page.image=/distribute/images/about-play.jpg </table> <div class="headerLine"> - <h1 id="featured-staff-picks"> + <h2 id="featured-staff-picks"> Featured, Staff Picks, Collections, and Badges - </h1> + </h2> + - <hr> </div> <p> @@ -313,11 +313,11 @@ page.image=/distribute/images/about-play.jpg </p> <div class="headerLine"> - <h1 id="product-detail-pages"> + <h2 id="product-detail-pages"> Store Listing Pages - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -357,8 +357,11 @@ page.image=/distribute/images/about-play.jpg Products</a> to find out how. </p> -<div class="headerLine clearfloat"> -<h1>Related Resources</h1><hr> +<p style="clear:both"> +</p> + +<div class="headerLine"> +<h2>Related Resources</h2> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/googleplay/developer-console.jd b/docs/html/distribute/googleplay/developer-console.jd index 6263431..f5b3ac6 100644 --- a/docs/html/distribute/googleplay/developer-console.jd +++ b/docs/html/distribute/googleplay/developer-console.jd @@ -44,12 +44,12 @@ Xnonavpage=true verification by email, you can sign in to your Google Play Developer Console. </p> -<div class="headerLine clearfloat"> - <h1 id="allapps"> +<div class="headerLine"> + <h2 id="allapps"> All Applications - </h1> + </h2> + - <hr> </div> <p> @@ -61,12 +61,12 @@ Xnonavpage=true <img src="{@docRoot}images/gp-dc-home.png" class="border-img"> </div> -<div class="headerLine clearfloat" style="margin-top:-6px"> - <h1 id="account-details"> +<div class="headerLine" style="margin-top:-6px"> + <h2 id="account-details"> Your Account Details - </h1> + </h2> + - <hr> </div> <p> @@ -112,12 +112,12 @@ Xnonavpage=true Google Play licensing. </p> -<div class="headerLine clearfloat"> - <h1 id="merchant-account"> +<div class="headerLine"> + <h2 id="merchant-account"> Linking Your Merchant Account - </h1> + </h2> + - <hr> </div> <p> @@ -127,12 +127,12 @@ Xnonavpage=true from sales. </p> -<div class="headerLine clearfloat"> - <h1 id="multiple-user-accounts"> +<div class="headerLine"> + <h2 id="multiple-user-accounts"> Multiple User Accounts - </h1> + </h2> + - <hr> </div> <p> @@ -157,12 +157,12 @@ Xnonavpage=true up multiple accounts</a> now. </p> -<div class="headerLine clearfloat"> - <h1 id="store-listing-details"> +<div class="headerLine"> + <h2 id="store-listing-details"> Store Listing Details - </h1> + </h2> + - <hr> </div> <p> @@ -181,12 +181,12 @@ Xnonavpage=true <img src="{@docRoot}images/gp-dc-details.png" class="frame"> </div> -<div class="headerLine clearfloat"> - <h1 id="upload-instantly-publish"> +<div class="headerLine"> + <h2 id="upload-instantly-publish"> Upload and Instantly Publish - </h1> + </h2> + - <hr> </div> <p> @@ -212,12 +212,12 @@ Xnonavpage=true time. </p> -<div class="headerLine clearfloat"> - <h1 id="alpha-beta"> +<div class="headerLine"> + <h2 id="alpha-beta"> Alpha and Beta Testing - </h1> + </h2> + - <hr> </div> <p> @@ -270,12 +270,12 @@ Xnonavpage=true Checklist</a>. </p> -<div class="headerLine clearfloat"> - <h1 id="staged-rollouts"> +<div class="headerLine"> + <h2 id="staged-rollouts"> Staged Rollouts - </h1> + </h2> + - <hr> </div> <p> @@ -303,12 +303,12 @@ Xnonavpage=true updates. </p> -<div class="headerLine clearfloat"> - <h1 id="multiple-apk"> +<div class="headerLine"> + <h2 id="multiple-apk"> Multiple APK Support - </h1> + </h2> + - <hr> </div> <p> @@ -335,12 +335,12 @@ Xnonavpage=true of the normal app installation. </p> -<div class="headerLine clearfloat"> - <h1 id="selling-pricing-your-products"> +<div class="headerLine"> + <h2 id="selling-pricing-your-products"> Selling and Pricing Your Products - </h1> + </h2> + - <hr> </div> <div class="figure-right"> @@ -407,12 +407,12 @@ Xnonavpage=true Expand into New Markets</a>. </p> -<div class="headerLine clearfloat"> - <h1 id="in-app-products"> +<div class="headerLine"> + <h2 id="in-app-products"> In-app Products - </h1> + </h2> + - <hr> </div> <p> @@ -448,12 +448,12 @@ Xnonavpage=true monetization models </p> -<div class="headerLine clearfloat"> - <h1 id="distribution-controls"> +<div class="headerLine"> + <h2 id="distribution-controls"> Distribution Controls - </h1> + </h2> + - <hr> </div> <p> @@ -518,12 +518,12 @@ Xnonavpage=true exclude specific devices if needed. </p> -<div class="headerLine clearfloat"> - <h1 id="reviews-reports"> +<div class="headerLine"> + <h2 id="reviews-reports"> User Reviews and Crash Reports - </h1> + </h2> + - <hr> </div> <div class="figure-right" style="width:500px;"> @@ -548,12 +548,12 @@ Xnonavpage=true devices. </p> -<div class="headerLine clearfloat"> - <h1 id="app-stats"> +<div class="headerLine"> + <h2 id="app-stats"> App Statistics - </h1> + </h2> + - <hr> </div> <div class="figure" style="width:500px"> @@ -587,9 +587,12 @@ Xnonavpage=true on data inside a dimension by adding specific points to the timeline. </p> +<p style="clear:both"> +</p> + <div class="dynamic-grid"> -<div class="headerLine clearfloat"> -<h1 id="related-resources">Related Resources</h1><hr/> +<div class="headerLine"> +<h2 id="related-resources">Related Resources</h2> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/googleplay/edu/about.jd b/docs/html/distribute/googleplay/edu/about.jd index 3944909..1c003cf 100644 --- a/docs/html/distribute/googleplay/edu/about.jd +++ b/docs/html/distribute/googleplay/edu/about.jd @@ -106,8 +106,10 @@ Xnonavpage=true </div> </div> -<div class="headerLine clearfloat"> -<h1>Related Resources</h1><hr> +<p style="clear:both"> +</p> +<div class="headerLine"> +<h2>Related Resources</h2> </div> <div class="dynamic-grid"> diff --git a/docs/html/distribute/googleplay/edu/faq.jd b/docs/html/distribute/googleplay/edu/faq.jd index 0866da5..36e2064 100644 --- a/docs/html/distribute/googleplay/edu/faq.jd +++ b/docs/html/distribute/googleplay/edu/faq.jd @@ -58,11 +58,11 @@ page.image=/distribute/images/gpfe-faq.jpg </p> <div class="headerLine"> - <h1 id="business-model-and-monetization"> + <h2 id="business-model-and-monetization"> Business Model and Monetization - </h1> + </h2> + - <hr> </div> <p> @@ -143,11 +143,11 @@ page.image=/distribute/images/gpfe-faq.jpg </p> <div class="headerLine"> - <h1 id="free-trials"> + <h2 id="free-trials"> Free Trials - </h1> + </h2> + - <hr> </div> <p> @@ -172,11 +172,11 @@ page.image=/distribute/images/gpfe-faq.jpg </p> <div class="headerLine"> - <h1 id="discovery"> + <h2 id="discovery"> Discovery - </h1> + </h2> + - <hr> </div> <p> @@ -211,11 +211,11 @@ page.image=/distribute/images/gpfe-faq.jpg </p> <div class="headerLine"> - <h1 id="app-review-process"> + <h2 id="app-review-process"> App Review Process - </h1> + </h2> + - <hr> </div> <p> @@ -255,11 +255,11 @@ page.image=/distribute/images/gpfe-faq.jpg </p> <div class="headerLine"> - <h1 id="app-features"> + <h2 id="app-features"> App Features - </h1> + </h2> + - <hr> </div> <p> @@ -310,11 +310,11 @@ page.image=/distribute/images/gpfe-faq.jpg </p> <div class="headerLine"> - <h1 id="marketing-and-roi"> + <h2 id="marketing-and-roi"> Marketing and ROI - </h1> + </h2> + - <hr> </div> <p> @@ -364,11 +364,11 @@ page.image=/distribute/images/gpfe-faq.jpg </p> <div class="headerLine"> - <h1 id="devices"> + <h2 id="devices"> Devices - </h1> + </h2> + - <hr> </div> <p> @@ -393,11 +393,11 @@ page.image=/distribute/images/gpfe-faq.jpg </p> <div class="headerLine"> - <h1 id="accounts"> + <h2 id="accounts"> Accounts - </h1> + </h2> + - <hr> </div> <p> @@ -423,7 +423,7 @@ page.image=/distribute/images/gpfe-faq.jpg schools in the program use Google Accounts and Google Apps for Education, offering Google Single Sign-on is ideal. </p> -<div class="headerLine"><h1 id="related-resources">Related Resources</h1><hr></div> +<div class="headerLine"><h2 id="related-resources">Related Resources</h2></div> <div class="resource-widget resource-flow-layout col-13" data-query="collection:distribute/toolsreference/gpfefaq" diff --git a/docs/html/distribute/googleplay/edu/start.jd b/docs/html/distribute/googleplay/edu/start.jd index 260ae85..4886b5a 100644 --- a/docs/html/distribute/googleplay/edu/start.jd +++ b/docs/html/distribute/googleplay/edu/start.jd @@ -32,12 +32,12 @@ page.metaDescription=Join Google Play for Education in just a few simple steps. "border:1px solid #ddd;padding:0px;width:100%;"> </div> -<div class="headerLine clearfloat"> - <h1 id="register"> +<div class="headerLine"> + <h2 id="register"> Register for a Publisher Account - </h1> + </h2> + - <hr> </div> <p> @@ -49,11 +49,11 @@ page.metaDescription=Join Google Play for Education in just a few simple steps. </p> <div class="headerLine"> - <h1 id="prepare"> + <h2 id="prepare"> Prepare Your Apps - </h1> + </h2> + - <hr> </div> <div class="figure-right"> @@ -138,11 +138,11 @@ page.metaDescription=Join Google Play for Education in just a few simple steps. </p> <div class="headerLine"> - <h1 id="publish"> + <h2 id="publish"> Publish Your Apps - </h1> + </h2> + - <hr> </div> <p> @@ -298,7 +298,7 @@ page.metaDescription=Join Google Play for Education in just a few simple steps. </li> </ul> <div class="headerLine"> -<h1 id="related-resources">Related Resources</h1><hr> +<h2 id="related-resources">Related Resources</h2> </div> <div class="dynamic-grid"> diff --git a/docs/html/distribute/googleplay/start.jd b/docs/html/distribute/googleplay/start.jd index 6dc397b..2ba5880 100644 --- a/docs/html/distribute/googleplay/start.jd +++ b/docs/html/distribute/googleplay/start.jd @@ -33,11 +33,11 @@ page.image=/distribute/images/getting-started.jpg </p> <div class="headerLine"> - <h1> + <h2> Register for a Publisher Account - </h1> + </h2> + - <hr> </div> <div class="sidebox-wrapper" style="float:right;"> @@ -92,11 +92,11 @@ page.image=/distribute/images/getting-started.jpg </ol> <div class="headerLine"> - <h1 id="merchant-account"> + <h2 id="merchant-account"> Set Up a Google Wallet Merchant Account - </h1> + </h2> + - <hr> </div> <div class="figure" style="width:200px;"> @@ -135,11 +135,11 @@ page.image=/distribute/images/getting-started.jpg </p> <div class="headerLine"> - <h1> + <h2> Explore the Developer Console - </h1> + </h2> + - <hr> </div> <p> @@ -153,7 +153,7 @@ page.image=/distribute/images/getting-started.jpg </div> <div class="headerLine"> -<h1 id="related-resources">Related Resources</h1><hr /> +<h2 id="related-resources">Related Resources</h2><hr /> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/monetize/ads.jd b/docs/html/distribute/monetize/ads.jd index 40120c3..bcb1e52 100644 --- a/docs/html/distribute/monetize/ads.jd +++ b/docs/html/distribute/monetize/ads.jd @@ -104,7 +104,7 @@ page.image=/distribute/images/advertising.png DoubleClick for Publishers Small Business</a>. </p> -<div class="headerLine"><h1 id="related-resources">Related resources</h1><hr></div> +<div class="headerLine"><h2 id="related-resources">Related resources</h2></div> <div class="resource-widget resource-flow-layout col-13" data-query="collection:distribute/monetize/advertising" diff --git a/docs/html/distribute/monetize/ecommerce.jd b/docs/html/distribute/monetize/ecommerce.jd index b3f790d..65e2b20 100644 --- a/docs/html/distribute/monetize/ecommerce.jd +++ b/docs/html/distribute/monetize/ecommerce.jd @@ -44,12 +44,14 @@ page.tags="monetizing", "physical goods", "wallet" Account</a>. </p> -<div class="headerLine clearfloat"> - <h1 id="related-resources"> +<p style="clear:both"> +</p> +<div class="headerLine"> + <h2 id="related-resources"> Related Resources - </h1> + </h2> + - <hr> </div> <div class="resource-widget resource-flow-layout col-13" data-query="collection:distribute/monetize/ecommerce" diff --git a/docs/html/distribute/monetize/freemium.jd b/docs/html/distribute/monetize/freemium.jd index ec86d19..0d33054 100644 --- a/docs/html/distribute/monetize/freemium.jd +++ b/docs/html/distribute/monetize/freemium.jd @@ -66,11 +66,11 @@ page.tags="in-app", "billing", "iap", "monetizing" </p> <div class="headerLine"> - <h1 id="related-resources"> + <h2 id="related-resources"> Related Resources - </h1> + </h2> + - <hr> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/monetize/payments.jd b/docs/html/distribute/monetize/payments.jd index 37b4d44..55c289f 100644 --- a/docs/html/distribute/monetize/payments.jd +++ b/docs/html/distribute/monetize/payments.jd @@ -17,11 +17,11 @@ page.tags="google play", "payments", "gift card" </p> <div class="headerLine"> - <h1 id="dcb"> + <h2 id="dcb"> Direct Carrier Billing - </h1> + </h2> + - <hr> </div> <p> @@ -33,10 +33,10 @@ page.tags="google play", "payments", "gift card" </p> <div class="headerLine"> - <h1 id="credit"> + <h2 id="credit"> Credit Cards - </h1> - <hr> + </h2> + </div> <p> @@ -47,12 +47,12 @@ page.tags="google play", "payments", "gift card" setup process. </p> -<div class="headerLine clearfloat"> - <h1 id="gift-cards"> +<div class="headerLine"> + <h2 id="gift-cards"> Google Play Gift Cards - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -67,12 +67,14 @@ page.tags="google play", "payments", "gift card" "_android">here</a>. </p> -<div class="headerLine clearfloat"> - <h1 id="balance"> +<p style="clear:both"> +</p> +<div class="headerLine"> + <h2 id="balance"> Google Play Balance - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -94,7 +96,9 @@ page.tags="google play", "payments", "gift card" network, and other factors. </p> -<div class="headerLine clearfloat"><h1 id="related-resources">Related Resources</h1><hr></div> +<p style="clear:both"> +</p> +<div class="headerLine"><h2 id="related-resources">Related Resources</h2></div> <div class="resource-widget resource-flow-layout col-13" data-query="collection:distribute/monetize/paymentmethods" diff --git a/docs/html/distribute/monetize/premium.jd b/docs/html/distribute/monetize/premium.jd index b66cd03..c8823d1 100644 --- a/docs/html/distribute/monetize/premium.jd +++ b/docs/html/distribute/monetize/premium.jd @@ -35,11 +35,13 @@ page.tags="monetizing", "paid" <a href="{@docRoot}distribute/monetize/ads.html">advertising</a> models. </p> -<div class="headerLine clearfloat"> - <h1> +<p style="clear:both"> +</p> +<div class="headerLine"> + <h2> Related Resources - </h1> - <hr> + </h2> + </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/monetize/subscriptions.jd b/docs/html/distribute/monetize/subscriptions.jd index a838e30..6a4d9b1 100644 --- a/docs/html/distribute/monetize/subscriptions.jd +++ b/docs/html/distribute/monetize/subscriptions.jd @@ -61,7 +61,9 @@ page.tags="in-app", "iap", "monetizing", "free", "trials" </p> </div> -<div class="headerLine clearfloat"><h1 id="related-resources">Related Resources</h1><hr></div> +<p style="clear:both"> +</p> +<div class="headerLine"><h2 id="related-resources">Related Resources</h2></div> <div class="resource-widget resource-flow-layout col-13" data-query="collection:distribute/monetize/subscriptions" diff --git a/docs/html/distribute/tools/launch-checklist.jd b/docs/html/distribute/tools/launch-checklist.jd index 3b0dd55..3f6b1a6 100644 --- a/docs/html/distribute/tools/launch-checklist.jd +++ b/docs/html/distribute/tools/launch-checklist.jd @@ -62,11 +62,11 @@ src="{@docRoot}distribute/images/launch-checklist.jpg"></div> </p> <div class="headerLine"> - <h1 id="understand-publishing"> + <h2 id="understand-publishing"> 1. Understand the Publishing Process - </h1> + </h2> + - <hr> </div> <p> @@ -95,12 +95,12 @@ data-sortorder="-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="understand-policies"> +<div class="headerLine"> + <h2 id="understand-policies"> 2. Understand Google Play Policies and Agreements - </h1> + </h2> + - <hr> </div> <p> @@ -119,12 +119,12 @@ data-maxresults="6"> "-timestamp" data-cardsizes="6x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="test-quality"> +<div class="headerLine"> + <h2 id="test-quality"> 3. Test for Quality - </h1> + </h2> + - <hr> </div> <p> @@ -163,12 +163,12 @@ data-maxresults="6"> "-timestamp" data-cardsizes="6x3,6x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="determine-rating"> +<div class="headerLine"> + <h2 id="determine-rating"> 4. Determine your App’s Content Rating - </h1> + </h2> + - <hr> </div> <p> @@ -221,12 +221,12 @@ data-maxresults="6"> "-timestamp" data-cardsizes="9x3,6x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="determine-country"> +<div class="headerLine"> + <h2 id="determine-country"> 5. Determine Country Distribution - </h1> + </h2> + - <hr> </div> <p> @@ -289,12 +289,12 @@ data-maxresults="6"> "-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="confirm-size"> +<div class="headerLine"> + <h2 id="confirm-size"> 6. Confirm the App's Overall Size - </h1> + </h2> + - <hr> </div> <p> @@ -344,12 +344,12 @@ data-maxresults="6"> "-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="confirm-platform"> +<div class="headerLine"> + <h2 id="confirm-platform"> 7. Confirm the App's Platform and Screen Compatibility Ranges - </h1> + </h2> + - <hr> </div> <p> @@ -396,12 +396,12 @@ data-maxresults="6"> "-timestamp" data-cardsizes="6x3,6x3,6x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="decide-price"> +<div class="headerLine"> + <h2 id="decide-price"> 8. Decide Whether your App will be Free or Priced - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -459,12 +459,12 @@ data-maxresults="6"> "-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="consider-billing"> +<div class="headerLine"> + <h2 id="consider-billing"> 9. Consider using In-app Billing - </h1> + </h2> + - <hr> </div> <p> @@ -495,12 +495,12 @@ data-sortorder="-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="set-prices"> +<div class="headerLine"> + <h2 id="set-prices"> 10. Set Prices for your Products - </h1> + </h2> + - <hr> </div> <p> @@ -526,12 +526,12 @@ data-maxresults="6"> "-timestamp" data-cardsizes="9x3,9x3,9x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="start-localization"> +<div class="headerLine"> + <h2 id="start-localization"> 11. Start Localization - </h1> + </h2> + - <hr> </div> <p> @@ -609,12 +609,12 @@ data-sortorder="-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="prepare-graphics"> +<div class="headerLine"> + <h2 id="prepare-graphics"> 12. Prepare Promotional Graphics, Screenshots, and Videos - </h1> + </h2> + - <hr> </div> <p> @@ -668,12 +668,12 @@ data-maxresults="6"> "-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="build-upload"> +<div class="headerLine"> + <h2 id="build-upload"> 13. Build and Upload the Release-ready APK - </h1> + </h2> + - <hr> </div> <p> @@ -709,12 +709,12 @@ data-maxresults="6"> data-cardSizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxResults="6"></div>--> -<div class="headerLine clearfloat"> - <h1 id="plan-beta"> +<div class="headerLine"> + <h2 id="plan-beta"> 14. Plan a Beta Release - </h1> + </h2> + - <hr> </div> <div class="sidebox-wrapper" style="float:right;"> @@ -765,11 +765,11 @@ See how you can facilitate testing with Google Play.</td> </table> --> <div class="headerLine"> - <h1 id="complete-details"> + <h2 id="complete-details"> 15. Complete the Apps’ Store Listing - </h1> + </h2> + - <hr> </div> <p> @@ -825,12 +825,12 @@ data-sortorder="-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="use-badges"> +<div class="headerLine"> + <h2 id="use-badges"> 16. Use Google Play Badges and Links in your Promotional Campaigns - </h1> + </h2> + - <hr> </div> <p> @@ -862,12 +862,12 @@ data-maxresults="6"> "-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="final-checks"> +<div class="headerLine"> + <h2 id="final-checks"> 17. Final Checks and Publishing - </h1> + </h2> + - <hr> </div> <p> @@ -969,12 +969,12 @@ data-sortorder="-timestamp" data-cardsizes="6x3,6x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="support-users"> +<div class="headerLine"> + <h2 id="support-users"> 18. Support Users after Launch - </h1> + </h2> + - <hr> </div> <p> diff --git a/docs/html/distribute/tools/localization-checklist.jd b/docs/html/distribute/tools/localization-checklist.jd index 7a638ed..569ed02 100644 --- a/docs/html/distribute/tools/localization-checklist.jd +++ b/docs/html/distribute/tools/localization-checklist.jd @@ -41,11 +41,11 @@ page.image=/distribute/images/localization-checklist.jpg </p> <div class="headerLine"> - <h1 id="identify-languages"> + <h2 id="identify-languages"> 1. Identify target languages and locales - </h1> + </h2> + - <hr> </div> <p> @@ -93,12 +93,12 @@ page.image=/distribute/images/localization-checklist.jpg data-sortorder="-timestamp" data-cardsizes="9x3," data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="design"> +<div class="headerLine"> + <h2 id="design"> 2. Design for localization - </h1> + </h2> + - <hr> </div> <p> @@ -233,12 +233,12 @@ data-sortorder="-timestamp" data-cardsizes="9x3," data-maxresults="6"> data-cardsizes="9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="manage-strings"> +<div class="headerLine"> + <h2 id="manage-strings"> 3. Manage strings for localization - </h1> + </h2> + - <hr> </div> <p> @@ -408,12 +408,12 @@ data-cardsizes="9x3" data-maxresults="6"> data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="translate-strings"> +<div class="headerLine"> + <h2 id="translate-strings"> 4. Translate UI strings and other resources - </h1> + </h2> + - <hr> </div> <p> @@ -579,12 +579,12 @@ data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6"> data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="test"> +<div class="headerLine"> + <h2 id="test"> 5. Test your localized app - </h1> + </h2> + - <hr> </div> <p> @@ -699,12 +699,12 @@ data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6"> data-maxResults="6"></div> --> </p> -<div class="headerLine clearfloat"> - <h1 id="prepare-launch"> +<div class="headerLine"> + <h2 id="prepare-launch"> 6. Prepare for international launch - </h1> + </h2> + - <hr> </div> <p> @@ -897,12 +897,12 @@ data-sortorder="-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6"> </div> -<div class="headerLine clearfloat"> - <h1 id="support-users"> +<div class="headerLine"> + <h2 id="support-users"> 7. Support international users after launch - </h1> + </h2> + - <hr> </div> <p> diff --git a/docs/html/distribute/tools/open-distribution.jd b/docs/html/distribute/tools/open-distribution.jd index f804af2..e28102d 100644 --- a/docs/html/distribute/tools/open-distribution.jd +++ b/docs/html/distribute/tools/open-distribution.jd @@ -26,11 +26,11 @@ page.image=/distribute/images/alt-distribution.jpg </p> <div class="headerLine"> - <h1> + <h2> Distributing Through an App Marketplace - </h1> + </h2> + - <hr> </div> <p> @@ -55,11 +55,11 @@ page.image=/distribute/images/alt-distribution.jpg </p> <div class="headerLine"> - <h1> + <h2> Distributing Your Apps by Email - </h1> + </h2> + - <hr> </div> <div class="figure" style="width:300px;"> @@ -95,11 +95,11 @@ page.image=/distribute/images/alt-distribution.jpg </p> <div class="headerLine"> - <h1> + <h2> Distributing Through a Website - </h1> + </h2> + - <hr> </div> <p> @@ -119,12 +119,12 @@ page.image=/distribute/images/alt-distribution.jpg sources</a>. </p> -<div class="headerLine clearfloat"> - <h1> +<div class="headerLine"> + <h2> User Opt-In for Apps from Unknown Sources - </h1> + </h2> + - <hr> </div> <div class="figure" style="width:325px;"> diff --git a/docs/html/distribute/tools/promote/badge-files.jd b/docs/html/distribute/tools/promote/badge-files.jd index cce3632..b481802 100644 --- a/docs/html/distribute/tools/promote/badge-files.jd +++ b/docs/html/distribute/tools/promote/badge-files.jd @@ -12,7 +12,7 @@ table tr td {border:0} <p>The following links provide the Adobe® Illustrator® (.ai) file for the two Google Play badges.</p> -<hr> + <img src="{@docRoot}images/brand/en_generic_rgb_wo_60.png" alt="Get It On Google Play"> <div style="clear:left"> </div> @@ -137,7 +137,7 @@ two Google Play badges.</p> -<hr> + <img src="{@docRoot}images/brand/en_app_rgb_wo_60.png" alt="Android App On Google Play"> <div style="clear:left"> </div> diff --git a/docs/html/distribute/tools/promote/device-art.jd b/docs/html/distribute/tools/promote/device-art.jd index b0b5f84..a204ea1 100644 --- a/docs/html/distribute/tools/promote/device-art.jd +++ b/docs/html/distribute/tools/promote/device-art.jd @@ -12,7 +12,7 @@ Xnonavpage=true <p class="note"><strong>Note</strong>: Do <em>not</em> use graphics created here in your 1024x500 feature image or screenshots for your Google Play app listing.</p> -<hr> + <div class="supported-browser"> @@ -28,7 +28,7 @@ feature image or screenshots for your Google Play app listing.</p> </div> </div> -<hr> + <div class="layout-content-row"> <div class="layout-content-col span-3"> diff --git a/docs/html/distribute/users/build-buzz.jd b/docs/html/distribute/users/build-buzz.jd index b76498e..412589f 100644 --- a/docs/html/distribute/users/build-buzz.jd +++ b/docs/html/distribute/users/build-buzz.jd @@ -65,11 +65,11 @@ page.tags="users, growth, promotion" </p> <div class="headerLine"> - <h1 id="link-to-your-apps"> + <h2 id="link-to-your-apps"> Link to Your Apps in Google Play - </h1> + </h2> + - <hr> </div> <p> @@ -101,12 +101,12 @@ page.tags="users, growth, promotion" </li> </ul> -<div class="headerLine clearfloat"> - <h1 id="use-the-google-play-badge"> +<div class="headerLine"> + <h2 id="use-the-google-play-badge"> Use the Google Play Badge - </h1> + </h2> + - <hr> </div> <div class="figure" style="margin:0 3em;"> @@ -126,12 +126,12 @@ page.tags="users, growth, promotion" also easy to make and available in multiple languages. </p> -<div class="headerLine clearfloat"> - <h1 id="cross-promote-from-your-other-apps"> +<div class="headerLine"> + <h2 id="cross-promote-from-your-other-apps"> Cross-Promote from Your Other Apps - </h1> + </h2> + - <hr> </div> <div class="figure-right"> @@ -158,11 +158,11 @@ page.tags="users, growth, promotion" </p> <div class="headerLine"> - <h1 id="hold-a-contest"> + <h2 id="hold-a-contest"> Hold a Contest - </h1> + </h2> + - <hr> </div> <p> @@ -180,11 +180,11 @@ page.tags="users, growth, promotion" </p> <div class="headerLine"> - <h1 id="leverage-pr"> + <h2 id="leverage-pr"> Leverage PR - </h1> + </h2> + - <hr> </div> <p> @@ -196,11 +196,11 @@ page.tags="users, growth, promotion" </p> <div class="headerLine"> - <h1 id="use-social-media"> + <h2 id="use-social-media"> Use Social Media - </h1> + </h2> + - <hr> </div> <p> @@ -220,12 +220,12 @@ page.tags="users, growth, promotion" review your apps. A review on the right blog is a great promotion. </p> -<div class="headerLine clearfloat"> - <h1 id="publish-youtube-videos"> +<div class="headerLine"> + <h2 id="publish-youtube-videos"> Publish YouTube Videos - </h1> + </h2> + - <hr> </div> <div class="center-img" style="padding-top:1em;"> @@ -240,11 +240,11 @@ page.tags="users, growth, promotion" </p> <div class="headerLine"> - <h1 id="advertise"> + <h2 id="advertise"> Advertise - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -262,12 +262,12 @@ page.tags="users, growth, promotion" Sign up for an AdMob account</a> to get started. </p> -<div class="headerLine clearfloat"> - <h1 id="maximize-your-marketing-spend"> +<div class="headerLine"> + <h2 id="maximize-your-marketing-spend"> Maximize your Marketing Spend - </h1> + </h2> + - <hr> </div> <div class="figure" style="margin: 0 3em;"> @@ -285,12 +285,12 @@ page.tags="users, growth, promotion" platforms simultaneously helps you maximize your return on investment. </p> -<div class="headerLine clearfloat"> - <h1 id="related-resources"> +<div class="headerLine"> + <h2 id="related-resources"> Related Resources - </h1> + </h2> + - <hr> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/users/build-community.jd b/docs/html/distribute/users/build-community.jd index 1623939..5cdabea 100644 --- a/docs/html/distribute/users/build-community.jd +++ b/docs/html/distribute/users/build-community.jd @@ -43,11 +43,11 @@ page.tags="users, growth, community" </p> <div class="headerLine"> - <h1 id="starting-your-community"> + <h2 id="starting-your-community"> Starting Your Community - </h1> + </h2> + - <hr> </div> <p> @@ -116,11 +116,11 @@ page.tags="users, growth, community" </p> <div class="headerLine"> - <h1 id="tools-to-build-your-community"> + <h2 id="tools-to-build-your-community"> Tools to Build Your Community - </h1> + </h2> + - <hr> </div> <p> @@ -158,11 +158,11 @@ page.tags="users, growth, community" </p> <div class="headerLine"> - <h1 id="managing-your-community"> + <h2 id="managing-your-community"> Managing Your Community - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -189,11 +189,11 @@ page.tags="users, growth, community" versions or new apps to make them feel special. </p> -<div class="headerLine clearfloat"> - <h1 id="related-resources"> +<div class="headerLine"> + <h2 id="related-resources"> Related Resources - </h1> - <hr> + </h2> + </div> <div class="resource-widget resource-flow-layout col-13" data-query="collection:distribute/users/buildcommunity" diff --git a/docs/html/distribute/users/expand-to-new-markets.jd b/docs/html/distribute/users/expand-to-new-markets.jd index cc94a92..df1a0fb 100644 --- a/docs/html/distribute/users/expand-to-new-markets.jd +++ b/docs/html/distribute/users/expand-to-new-markets.jd @@ -77,11 +77,11 @@ page.tags="users, growth, global" </p> <div class="headerLine"> - <h1 id="localize-your-product"> + <h2 id="localize-your-product"> Localize Your Product - </h1> + </h2> + - <hr> </div> <div class="sidebox-wrapper" style="float:right;"> @@ -124,20 +124,16 @@ page.tags="users, growth, global" high-quality professional translations at competitive prices. </p> -<div style="float:left; width:48%; padding:8px;"> - <img src="{@docRoot}images/gp-listing-3.jpg"> -</div> +<img src="{@docRoot}images/gp-listing-3.jpg" style="padding:8px 0"> -<div style="width:48%; padding:8px; float:left"> - <img src="{@docRoot}images/gp-expand-2.jpg"> -</div> +<img src="{@docRoot}images/gp-expand-2.jpg" style="padding:8px 0"> -<div class="headerLine clearfloat"> - <h1 id="testing-and-support"> +<div class="headerLine"> + <h2 id="testing-and-support"> Testing and Support - </h1> + </h2> + - <hr> </div> <p> @@ -162,11 +158,11 @@ page.tags="users, growth, global" </p> <div class="headerLine"> - <h1 id="localize-your-google-play-listing"> + <h2 id="localize-your-google-play-listing"> Localize Your Google Play Store Listing - </h1> + </h2> + - <hr> </div> <div class="sidebox-wrapper" style="float:right;"> @@ -295,11 +291,11 @@ page.tags="users, growth, global" </p> <div class="headerLine"> - <h1 id="marketing"> + <h2 id="marketing"> Marketing - </h1> + </h2> + - <hr> </div> <div class="figure"> @@ -316,7 +312,7 @@ page.tags="users, growth, global" for each language you support. </p> -<div class="headerLine clearfloat"><h1 id="related-resources">Related Resources</h1><hr></div> +<div class="headerLine"><h2 id="related-resources">Related Resources</h2></div> <div class="resource-widget resource-flow-layout col-13" data-query="collection:distribute/getusers/expandnewmarkets" diff --git a/docs/html/distribute/users/know-your-user.jd b/docs/html/distribute/users/know-your-user.jd index fb91a05..1fbcb9c 100644 --- a/docs/html/distribute/users/know-your-user.jd +++ b/docs/html/distribute/users/know-your-user.jd @@ -15,13 +15,9 @@ page.tags="users, growth, global" when they use your app, and how often they return to it. </p> -<div class="headerLine"> - <h1 id="read-ratings-comments"> + <h2 id="read-ratings-comments"> Read Ratings Comments - </h1> - - <hr> -</div> + </h2> <p> The most obvious way to get to know your users is by reading review comments @@ -46,11 +42,11 @@ page.tags="users, growth, global" </div> <div class="headerLine"> - <h1 id="start-community"> + <h2 id="start-community"> Start a Community - </h1> + </h2> + - <hr> </div> <p> @@ -68,11 +64,11 @@ page.tags="users, growth, global" </p> <div class="headerLine"> - <h1 id="create-survey"> + <h2 id="create-survey"> Create a Survey - </h1> + </h2> + - <hr> </div> <p> @@ -92,11 +88,11 @@ page.tags="users, growth, global" </p> <div class="headerLine"> - <h1 id="add-analytics"> + <h2 id="add-analytics"> Add Analytics to your Apps - </h1> + </h2> + - <hr> </div> <p> @@ -121,11 +117,11 @@ page.tags="users, growth, global" </div> <div class="headerLine"> - <h1 id="use-google"> + <h2 id="use-google"> Use Google+ - </h1> + </h2> + - <hr> </div> <p> @@ -142,8 +138,8 @@ page.tags="users, growth, global" <img src="{@docRoot}images/gp-your-user-2.jpg"> </div> -<div class="headerLine clearfloat"> -<h1 id="related-resources">Related Resources</h1><hr> +<div class="headerLine"> +<h2 id="related-resources">Related Resources</h2> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/distribute/users/your-listing.jd b/docs/html/distribute/users/your-listing.jd index cc72fff..f869950 100644 --- a/docs/html/distribute/users/your-listing.jd +++ b/docs/html/distribute/users/your-listing.jd @@ -13,11 +13,11 @@ page.tags="listing, google play, users, growth" </p> <div class="headerLine"> - <h1 id="graphics-imagery"> + <h2 id="graphics-imagery"> Graphics & Imagery Tips - </h1> + </h2> + - <hr> </div> <p> @@ -94,12 +94,12 @@ page.tags="listing, google play, users, growth" Featured-Image Guidelines</a>. </p> -<div class="headerLine clearfloat"> - <h1 id="localization-tips"> +<div class="headerLine"> + <h2 id="localization-tips"> Localization Tips - </h1> + </h2> + - <hr> </div> <div class="sidebox-wrapper" style="float:right;"> @@ -125,16 +125,14 @@ page.tags="listing, google play, users, growth" the growing international audience</a>. </p> -<div style="float:left;"> <img src="{@docRoot}images/gp-listing-3.jpg"> -</div> -<div class="headerLine clearfloat"> - <h1 id="keyword-tips"> +<div class="headerLine"> + <h2 id="keyword-tips"> Keyword Tips - </h1> + </h2> + - <hr> </div> <p> @@ -146,11 +144,11 @@ page.tags="listing, google play, users, growth" </p> <div class="headerLine"> - <h1 id="app-indexing"> + <h2 id="app-indexing"> Sign Up for App Indexing - </h1> + </h2> + - <hr> </div> <p> @@ -164,11 +162,11 @@ page.tags="listing, google play, users, growth" </div> <div class="headerLine"> - <h1 id="education-app"> + <h2 id="education-app"> Education App Tips - </h1> + </h2> + - <hr> </div> <p> @@ -181,7 +179,7 @@ page.tags="listing, google play, users, growth" </p> <div class="headerLine"> -<h1 id="related-resources">Related Resources</h1><hr> +<h2 id="related-resources">Related Resources</h2> </div> <div class="resource-widget resource-flow-layout col-13" diff --git a/docs/html/google/play/billing/billing_admin.jd b/docs/html/google/play/billing/billing_admin.jd index 46ad905..5904b03 100644 --- a/docs/html/google/play/billing/billing_admin.jd +++ b/docs/html/google/play/billing/billing_admin.jd @@ -66,7 +66,8 @@ storing and delivering the digital content that you sell in your applications.</ </p> </div> -<p>You can create a product list for any published application or any draft application that's been +<p>You can create a product list for any published application, or any +application in the alpha or beta channels, that's been uploaded and saved to the Developer Console. However, you must have a Google Wallet merchant account and the application's manifest must include the <code>com.android.vending.BILLING</code> permission. If an application's manifest does not include this permission, you will be able to edit @@ -75,6 +76,13 @@ information about this permission, see <a href="{@docRoot}google/play/billing/billing_integrate.html#billing-permission">Updating Your Application's Manifest</a>.</p> +<p class="note"><strong>Note:</strong> Previously you could test an app by +uploading an unpublished "draft" version. This functionality is no longer +supported; instead, you must publish it to the alpha or beta distribution +channel. For more information, see <a +href="{@docRoot}google/play/billing/billing_testing.html#draft_apps">Draft Apps +are No Longer Supported</a>. + <p>In addition, an application package can have only one product list. If you create a product list for an application, and you use the <a href="{@docRoot}google/play/publishing/multiple-apks.html">multiple APK feature</a> to distribute diff --git a/docs/html/google/play/billing/billing_testing.jd b/docs/html/google/play/billing/billing_testing.jd index df6c657..8a49433 100644 --- a/docs/html/google/play/billing/billing_testing.jd +++ b/docs/html/google/play/billing/billing_testing.jd @@ -8,8 +8,9 @@ parent.link=index.html <h2>In this document</h2> <ol> <li><a href="#testing-purchases">Testing In-app Purchases</a></li> - <li><a href="#billing-testing-static">Testing with static responses</a></li> + <li><a href="#billing-testing-static">Testing with Static Responses</a></li> <li><a href="#billing-testing-real">Setting Up for Test Purchases</a></li> + <li><a href="#draft_apps">Draft Apps are No Longer Supported</a></li> </ol> <h2>See also</h2> <ol> @@ -79,8 +80,7 @@ method).</p> <p>First, upload and publish in-app products that you want testers to be able to purchase. You can upload and publish in-app products in the Developer Console. Note that you can upload and publish your in-app items before you publish the -APK itself. For example, you can publish your in-app items while your APK is -still a draft. </p> +APK itself.</p> <p>Next, create license test accounts for authorized users. In the Developer Console, go to <strong>Settings</strong> > <strong>Account details</strong>, @@ -149,11 +149,12 @@ license accounts in your alpha and beta distribution groups, those users will only be able to make test purchases. </p> -<h2 id="billing-testing-static">Testing with static responses</h2> +<h2 id="billing-testing-static">Testing with Static Responses</h2> <p>We recommend that you first test your In-app Billing implementation using static responses from Google Play. This enables you to verify that your application is handling the primary Google -Play responses correctly and that your application is able to verify signatures correctly.</p> +Play responses correctly and that your application is able to verify signatures correctly. You can do this +even if the app hasn't been published yet.</p> <p>To test your implementation with static responses, you make an In-app Billing request using a special item that has a reserved product ID. Each reserved product ID returns a specific static @@ -173,6 +174,12 @@ the Developer Console to perform static response tests with the reserved product install your application on a device, log into the device, and make billing requests using the reserved product IDs.</p> +<p class="note"><strong>Note:</strong> Previously you could test an app by +uploading an unpublished "draft" version. This functionality is no longer +supported. However, you can test your app with static responses even before you +upload it to the Google Play store. For more information, see <a +href="#draft_apps">Draft Apps are No Longer Supported</a>. + <p>There are four reserved product IDs for testing static In-app Billing responses:</p> <ul> @@ -205,67 +212,12 @@ Pricing</a>.</p> </li> </ul> -<p>In some cases, the reserved items may return signed static responses, which lets you test -signature verification in your application. To test signature verification with the special reserved -product IDs, you may need to set up <a -href="{@docRoot}google/play/billing/billing_admin.html#billing-testing-setup">test accounts</a> or -upload your application as a unpublished draft application. Table 1 shows you the conditions under -which static responses are signed.</p> - -<p class="table-caption" id="static-responses-table"><strong>Table 1.</strong> -Conditions under which static responses are signed.</p> - -<table> -<tr> -<th>Application ever been published?</th> -<th>Draft application uploaded and unpublished?</th> -<th>User who is running the application</th> -<th>Static response signature</th> -</tr> - -<tr> -<td>No</td> -<td>No</td> -<td>Any</td> -<td>Unsigned</td> -</tr> - -<tr> -<td>No</td> -<td>No</td> -<td>Developer</td> -<td>Signed</td> -</tr> - -<tr> -<td>Yes</td> -<td>No</td> -<td>Any</td> -<td>Unsigned</td> -</tr> - -<tr> -<td>Yes</td> -<td>No</td> -<td>Developer</td> -<td>Signed</td> -</tr> - -<tr> -<td>Yes</td> -<td>No</td> -<td>Test account</td> -<td>Signed</td> -</tr> - -<tr> -<td>Yes</td> -<td>Yes</td> -<td>Any</td> -<td>Signed</td> -</tr> - -</table> +<p>In some cases, the reserved items may return signed static responses, which +lets you test signature verification in your application. The reserved items +only return signed responses if the user running the application has a <a +href="{@docRoot}distribute/googleplay/start.html">developer</a> or <a +href="{@docRoot}google/play/billing/billing_admin.html#billing-testing-setup">test +account.</a> <p>To make an In-app Billing request with a reserved product ID, you simply construct a normal <code>REQUEST_PURCHASE</code> request, but instead of using a real product ID from your @@ -310,9 +262,11 @@ purchases. Testing real in-app purchases enables you to test the end-to-end In-a experience, including the actual purchases from Google Play and the actual checkout flow that users will experience in your application.</p> -<p class="note"><strong>Note</strong>: You do not need to publish your application to do end-to-end -testing. You only need to upload your application as a draft application to perform end-to-end -testing.</p> +<p class="note"><strong>Note:</strong> You can do end-to-end testing of your app + by publishing it to an <a + href="{@docRoot}distribute/googleplay/developer-console.html#alpha-beta">alpha + distribution channel</a>. This allows you to publish the app to the Google + Play store, but limit its availability to just the testers you designate. </p> <p>To test your In-app Billing implementation with actual in-app purchases, you will need to register at least one test account on the Google Play Developer Console. You cannot use your @@ -327,14 +281,16 @@ application does not need to be published, but the item does need to be publishe <p>To test your In-app Billing implementation with actual purchases, follow these steps:</p> <ol> - <li><strong>Upload your application as a draft application to the Developer Console.</strong> - <p>You do not need to publish your application to perform end-to-end testing with real product - IDs; you only need to upload your application as a draft application. However, you must sign - your application with your release key before you upload it as a draft application. Also, the - version number of the uploaded application must match the version number of the application you - load to your device for testing. To learn how to upload an application to Google Play, see - <a href="http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=113469">Uploading - applications</a>.</p> + <li><strong>Upload your application to the <a + href="{@docRoot}distribute/googleplay/developer-console.html#alpha-beta">alpha + distribution channel</a> with the Developer Console.</strong> + + <p class="note"><strong>Note:</strong> Previously you could test an app by + uploading an unpublished "draft" version. This functionality is no longer + supported; instead, you must publish it to the alpha or beta distribution + channel. For more information, see <a href="#draft_apps">Draft Apps are No + Longer Supported</a>. + </li> <li><strong>Add items to the application's product list.</strong> <p>Make sure that you publish the items (the application can remain unpublished). See <a @@ -370,3 +326,24 @@ href="{@docRoot}tools/publishing/app-signing.html">signing</a>, and <a href="{@docRoot}distribute/tools/launch-checklist.html">publishing on Google Play</a>. </p> +<h2 id="draft_apps">Draft Apps are No Longer Supported</h2> + +<p>Previously, you could publish a "draft" version of your app for testing. This +functionality is no longer supported. Instead, there are two ways you can test +how a pre-release app functions on the Google Play store:</p> + +<ul> + + <li>You can publish an app to the <a + href="{@docRoot}distribute/googleplay/developer-console.html#alpha-beta">alpha + or beta distribution channels</a>. This makes the app available on the Google + Play store, but only to the testers you put on a "whitelist".</li> + + <li>In a few cases, you can test Google Play functionality with an unpublished + app. For example, you can test an unpublished app's in-app billing support by + using <a + href="{@docRoot}google/play/billing/billing_testing.html#billing-testing-static">static + responses</a>, special reserved product IDs that always return a specific + result (like "purchased" or "refunded").</li> + +</ul> diff --git a/docs/html/google/play/billing/v2/billing_integrate.jd b/docs/html/google/play/billing/v2/billing_integrate.jd index ca41e0b..5eb17d5 100644 --- a/docs/html/google/play/billing/v2/billing_integrate.jd +++ b/docs/html/google/play/billing/v2/billing_integrate.jd @@ -208,6 +208,14 @@ following:</p> a draft to the Google Play Developer Console. You also need to create a product list for the in-app items that are available for purchase in the sample application. The following instructions show you how to do this.</p> + +<p class="caution"><strong>Caution:</strong> Draft applications are no longer +supported. To test an application, publish it in the <a +href="{@docRoot}distribute/googleplay/developer-console.html#alpha-beta">alpha +or beta channels</a>. For more information, see <a +href="{@docRoot}google/play/billing/billing_testing.html#draft_apps">Draft Apps +are No Longer Supported</a>.</p> + <ol> <li><strong>Upload the release version of the sample application to Google Play.</strong> <p>Do not publish the sample application; leave it as an unpublished draft application. The @@ -928,10 +936,12 @@ public class BillingReceiver extends BroadcastReceiver { // Intent actions that we receive in the BillingReceiver from Google Play. // These are defined by Google Play and cannot be changed. // The sample application defines these in the Consts.java file. - public static final String ACTION_NOTIFY = "com.android.vending.billing.IN_APP_NOTIFY"; - public static final String ACTION_RESPONSE_CODE = "com.android.vending.billing.RESPONSE_CODE"; + public static final String ACTION_NOTIFY = + "com.android.vending.billing.IN_APP_NOTIFY"; + public static final String ACTION_RESPONSE_CODE = + "com.android.vending.billing.RESPONSE_CODE"; public static final String ACTION_PURCHASE_STATE_CHANGED = - "com.android.vending.billing.PURCHASE_STATE_CHANGED"; + "com.android.vending.billing.PURCHASE_STATE_CHANGED"; // The intent extras that are passed in an intent from Google Play. // These are defined by Google Play and cannot be changed. @@ -962,7 +972,8 @@ public class BillingReceiver extends BroadcastReceiver { Log.w(TAG, "unexpected action: " + action); } } - // Perform other processing here, such as forwarding intent messages to your local service. + // Perform other processing here, such as forwarding intent messages + // to your local service. } </pre> diff --git a/docs/html/google/play/expansion-files.jd b/docs/html/google/play/expansion-files.jd index e90f8fa..601ea48 100644 --- a/docs/html/google/play/expansion-files.jd +++ b/docs/html/google/play/expansion-files.jd @@ -527,17 +527,21 @@ are:</p> <!-- Required to download files from Google Play --> <uses-permission android:name="android.permission.INTERNET" /> - <!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) --> + <!-- Required to keep CPU alive while downloading files + (NOT to keep screen awake) --> <uses-permission android:name="android.permission.WAKE_LOCK" /> - <!-- Required to poll the state of the network connection and respond to changes --> - <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <!-- Required to poll the state of the network connection + and respond to changes --> + <uses-permission + android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- Required to check whether Wi-Fi is enabled --> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <!-- Required to read and write the expansion files on shared storage --> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission + android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest> </pre> @@ -650,8 +654,8 @@ public class SampleAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { try { - DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, - SampleDownloaderService.class); + DownloaderClientMarshaller.startDownloadServiceIfRequired(context, + intent, SampleDownloaderService.class); } catch (NameNotFoundException e) { e.printStackTrace(); } @@ -693,16 +697,19 @@ versionCode)}</li> <p>For example, the sample app provided in the Apk Expansion package calls the following method in the activity's {@link android.app.Activity#onCreate onCreate()} method to check whether the expansion files already exist on the device:</p> + <pre> boolean expansionFilesDelivered() { for (XAPKFile xf : xAPKS) { - String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsBase, xf.mFileVersion); + String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsBase, + xf.mFileVersion); if (!Helpers.doesFileExist(this, fileName, xf.mFileSize, false)) return false; } return true; } </pre> + <p>In this case, each {@code XAPKFile} object holds the version number and file size of a known expansion file and a boolean as to whether it's the main expansion file. (See the sample application's {@code SampleDownloaderActivity} class for details.)</p> @@ -740,6 +747,7 @@ the Downloader Library begins the download and you should update your activity U display the download progress (see the next step). If the response <em>is</em> {@code NO_DOWNLOAD_REQUIRED}, then the files are available and your application can start.</p> <p>For example:</p> + <pre> @Override public void onCreate(Bundle savedInstanceState) { @@ -754,11 +762,14 @@ public void onCreate(Bundle savedInstanceState) { notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); // Start the download service (if required) - int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, + int startResult = + DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, SampleDownloaderService.class); - // If download has started, initialize this activity to show download progress + // If download has started, initialize this activity to show + // download progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { - // This is where you do set up to display the download progress (next step) + // This is where you do set up to display the download + // progress (next step) ... return; } // If the download wasn't necessary, fall through to start the app @@ -766,6 +777,7 @@ public void onCreate(Bundle savedInstanceState) { startApp(); // Expansion files are available, start the app } </pre> + </li> <li>When the {@code startDownloadServiceIfRequired()} method returns anything <em>other than</em> {@code NO_DOWNLOAD_REQUIRED}, create an instance of {@code IStub} by @@ -783,9 +795,11 @@ android.app.Activity#onCreate onCreate()} method, after {@code startDownloadServ starts the download. </p> <p>For example, in the previous code sample for {@link android.app.Activity#onCreate onCreate()}, you can respond to the {@code startDownloadServiceIfRequired()} result like this:</p> + <pre> // Start the download service (if required) - int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, + int startResult = + DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, SampleDownloaderService.class); // If download has started, initialize activity to show progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { @@ -892,7 +906,8 @@ others. By default, this flag is <em>not</em> enabled, so the user must be on Wi expansion files. You might want to provide a user preference to enable downloads over the cellular network. In which case, you can call: <pre> -mRemoteService.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR); +mRemoteService + .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR); </pre> </dd> </dl> @@ -975,10 +990,12 @@ to both your expansion files:</p> // The shared path to all app expansion files private final static String EXP_PATH = "/Android/obb/"; -static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) { +static String[] getAPKExpansionFiles(Context ctx, int mainVersion, + int patchVersion) { String packageName = ctx.getPackageName(); Vector<String> ret = new Vector<String>(); - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + if (Environment.getExternalStorageState() + .equals(Environment.MEDIA_MOUNTED)) { // Build the full path to the app's expansion files File root = Environment.getExternalStorageDirectory(); File expPath = new File(root.toString() + EXP_PATH + packageName); @@ -1102,7 +1119,8 @@ following:</p> <pre> // Get a ZipResourceFile representing a merger of both the main and patch files -ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext, +ZipResourceFile expansionFile = + APKExpansionSupport.getAPKExpansionZipFile(appContext, mainVersion, patchVersion); // Get an input stream for a known file inside the expansion file ZIPs @@ -1190,28 +1208,18 @@ android.content.Context#getExternalFilesDir getExternalFilesDir()}.</li> opens, it's important that you test this process to be sure your application can successfully query for the URLs, download the files, and save them to the device.</p> -<p>To test your application's implementation of the manual download procedure, you must upload -your application to Google Play as a "draft" to make your expansion files available for -download:</p> - -<ol> - <li>Upload your APK and corresponding expansion files using the Google Play Developer -Console.</li> - <li>Fill in the necessary application details (title, screenshots, etc.). You can come back and -finalize these details before publishing your application. - <p>Click the <strong>Save</strong> button. <em>Do not click Publish.</em> This saves -the application as a draft, such that your application is not published for Google Play users, -but the expansion files are available for you to test the download process.</p></li> - <li>Install the application on your test device using the Eclipse tools or <a -href="{@docRoot}tools/help/adb.html">{@code adb}</a>.</li> - <li>Launch the app.</li> -</ol> - -<p>If everything works as expected, your application should begin downloading the expansion +<p>To test your application's implementation of the manual download procedure, +you can publish it to the alpha or beta channel, so it will only be available to +authorized testers. +If everything works as expected, your application should begin downloading the expansion files as soon as the main activity starts.</p> - - +<p class="note"><strong>Note:</strong> Previously you could test an app by +uploading an unpublished "draft" version. This functionality is no longer +supported; instead, you must publish it to the alpha or beta distribution +channel. For more information, see <a +href="{@docRoot}google/play/billing/billing_testing.html#draft_apps">Draft Apps +are No Longer Supported</a>. <h2 id="Updating">Updating Your Application</h2> diff --git a/docs/html/google/play/licensing/licensing-reference.jd b/docs/html/google/play/licensing/licensing-reference.jd index 7bfa61a..d4ca79a 100644 --- a/docs/html/google/play/licensing/licensing-reference.jd +++ b/docs/html/google/play/licensing/licensing-reference.jd @@ -151,7 +151,8 @@ returned by the Google Play server in a license response.</p> <tr> <td>{@code LICENSED}</td> <td>The application is licensed to the user. The user has purchased the -application or the application only exists as a draft.</td> +application, or is authorized to download and install the alpha or beta version +of the application.</td> <td>Yes</td> <td><code>VT</code>, <code>GT</code>, <code>GR</code></td> <td><em>Allow access according to {@code Policy} constraints.</em></td> @@ -233,16 +234,14 @@ implementation.</p> href="{@docRoot}google/play/licensing/setting-up.html#test-env"> Setting Up The Testing Environment</a>, the response code can be manually overridden for the application developer and any registered test users via the -Google Play Developer Console. -<br/><br/> -Additionally, as noted above, applications that are in draft mode (in other -words, applications that have been uploaded but have <em>never</em> been -published) will return {@code LICENSED} for all users, even if not listed as a test -user. Since the application has never been offered for download, it is assumed -that any users running it must have obtained it from an authorized channel for -testing purposes.</p> - +Google Play Developer Console.</p> +<p class="note"><strong>Note:</strong> Previously you could test an app by +uploading an unpublished "draft" version. This functionality is no longer +supported; instead, you must publish it to the alpha or beta distribution +channel. For more information, see <a +href="{@docRoot}google/play/billing/billing_testing.html#draft_apps">Draft Apps +are No Longer Supported</a>. <h2 id="extras">Server Response Extras</h2> @@ -430,8 +429,8 @@ public boolean allowAccess() { } } else if (mLastResponse == LicenseResponse.RETRY && ts < mLastResponseTime + MILLIS_PER_MINUTE) { - // Only allow access if we are within the retry period or we haven't used up our - // max retries. + // Only allow access if we are within the retry period + // or we haven't used up our max retries. return (ts <= mRetryUntil || mRetryCount <= mMaxRetries); } return false; diff --git a/docs/html/google/play/licensing/overview.jd b/docs/html/google/play/licensing/overview.jd index 4e1a9c9..a2d5379 100644 --- a/docs/html/google/play/licensing/overview.jd +++ b/docs/html/google/play/licensing/overview.jd @@ -38,12 +38,11 @@ the licensing server and receives the result. The Google Play application sends the result to your application, which can allow or disallow further use of the application as needed.</p> -<p class="note"><strong>Note:</strong> If a paid application has been uploaded -to Google Play, but saved only as a draft application (the app is -unpublished), the licensing server considers all users to be licensed users of -the application (because it's not even possible to purchase the app). This -exception is necessary in order for you to perform testing of your licensing -implementation.</p> +<p class="note"><strong>Note:</strong> If a version of an app is in the alpha or +beta channel, all users who are authorized to download and install that app are +considered to be licensed users of the app. For more information, see <a +href="{@docRoot}distribute/googleplay/developer-console.html#alpha-beta">Alpha +and Beta Testing</a>.</p> <div class="figure" style="width:469px"> <img src="{@docRoot}images/licensing_arch.png" alt=""/> @@ -52,6 +51,12 @@ license check through the License Verification Library and the Google Play client, which handles communication with the Google Play server.</p> </div> +<p class="note"><strong>Note:</strong> Previously you could test an app by +uploading an unpublished "draft" version. This functionality is no longer +supported; instead, you must publish it to the alpha or beta distribution +channel. For more information, see <a +href="{@docRoot}google/play/billing/billing_testing.html#draft_apps">Draft Apps +are No Longer Supported</a>. <p>To properly identify the user and determine the license status, the licensing server requires information about the application and user—your application and the Google Play client work 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/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index f82acc3..b2d61ec 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -1153,6 +1153,10 @@ public class GradientDrawable extends Drawable { // Extract the theme attributes, if any. st.mAttrPadding = a.extractThemeAttrs(); + if (st.mPadding == null) { + st.mPadding = new Rect(); + } + final Rect pad = st.mPadding; pad.set(a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_left, pad.left), a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_top, pad.top), @@ -1424,8 +1428,6 @@ public class GradientDrawable extends Drawable { } final static class GradientState extends ConstantState { - public final Rect mPadding = new Rect(); - public int mChangingConfigurations; public int mShape = RECTANGLE; public int mGradient = LINEAR_GRADIENT; @@ -1442,6 +1444,7 @@ public class GradientDrawable extends Drawable { public float mStrokeDashGap; public float mRadius; // use this if mRadiusArray is null public float[] mRadiusArray; + public Rect mPadding; public int mWidth = -1; public int mHeight = -1; public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO; @@ -1491,7 +1494,7 @@ public class GradientDrawable extends Drawable { mRadiusArray = state.mRadiusArray.clone(); } if (state.mPadding != null) { - mPadding.set(state.mPadding); + mPadding = new Rect(state.mPadding); } mWidth = state.mWidth; mHeight = state.mHeight; diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 2e47d3a..75cb0a0 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -304,7 +304,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { * @param right The right padding of the new layer. * @param bottom The bottom padding of the new layer. */ - private void addLayer(Drawable layer, int[] themeAttrs, int id, int left, int top, int right, + void addLayer(Drawable layer, int[] themeAttrs, int id, int left, int top, int right, int bottom) { final LayerState st = mLayerState; final int N = st.mChildren != null ? st.mChildren.length : 0; diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java index 24e8de6..ada741b 100644 --- a/graphics/java/android/graphics/drawable/Ripple.java +++ b/graphics/java/android/graphics/drawable/Ripple.java @@ -25,9 +25,10 @@ import android.graphics.CanvasProperty; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; +import android.util.MathUtils; import android.view.HardwareCanvas; import android.view.RenderNodeAnimator; -import android.view.animation.AccelerateInterpolator; +import android.view.animation.LinearInterpolator; import java.util.ArrayList; @@ -35,7 +36,7 @@ import java.util.ArrayList; * Draws a Quantum Paper ripple. */ class Ripple { - private static final TimeInterpolator INTERPOLATOR = new AccelerateInterpolator(); + private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); private static final float GLOBAL_SPEED = 1.0f; private static final float WAVE_TOUCH_DOWN_ACCELERATION = 512.0f * GLOBAL_SPEED; @@ -47,17 +48,23 @@ class Ripple { private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<>(); private final ArrayList<RenderNodeAnimator> mPendingAnimations = new ArrayList<>(); - private final Drawable mOwner; + private final RippleDrawable mOwner; /** Bounds used for computing max radius. */ private final Rect mBounds; /** Full-opacity color for drawing this ripple. */ - private final int mColor; + private int mColor; /** Maximum ripple radius. */ private float mOuterRadius; + /** Screen density used to adjust pixel-based velocities. */ + private float mDensity; + + private float mStartingX; + private float mStartingY; + // Hardware rendering properties. private CanvasProperty<Paint> mPropPaint; private CanvasProperty<Float> mPropRadius; @@ -78,13 +85,13 @@ class Ripple { // Software rendering properties. private float mOuterOpacity = 0; private float mOpacity = 1; - private float mRadius = 0; private float mOuterX; private float mOuterY; - private float mX; - private float mY; - private boolean mFinished; + // Values used to tween between the start and end positions. + private float mTweenRadius = 0; + private float mTweenX = 0; + private float mTweenY = 0; /** Whether we should be drawing hardware animations. */ private boolean mHardwareAnimating; @@ -92,28 +99,42 @@ class Ripple { /** Whether we can use hardware acceleration for the exit animation. */ private boolean mCanUseHardware; + /** Whether we have an explicit maximum radius. */ + private boolean mHasMaxRadius; + /** * Creates a new ripple. */ - public Ripple(Drawable owner, Rect bounds, int color) { + public Ripple(RippleDrawable owner, Rect bounds, float startingX, float startingY) { mOwner = owner; mBounds = bounds; + mStartingX = startingX; + mStartingY = startingY; + } + + public void setup(int maxRadius, int color, float density) { mColor = color | 0xFF000000; - final float halfWidth = bounds.width() / 2.0f; - final float halfHeight = bounds.height() / 2.0f; - mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight); + if (maxRadius != RippleDrawable.RADIUS_AUTO) { + mHasMaxRadius = true; + mOuterRadius = maxRadius; + } else { + final float halfWidth = mBounds.width() / 2.0f; + final float halfHeight = mBounds.height() / 2.0f; + mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight); + } + mOuterX = 0; mOuterY = 0; + mDensity = density; } - public void setRadius(float r) { - mRadius = r; - invalidateSelf(); - } - - public float getRadius() { - return mRadius; + public void onHotspotBoundsChanged() { + if (!mHasMaxRadius) { + final float halfWidth = mBounds.width() / 2.0f; + final float halfHeight = mBounds.height() / 2.0f; + mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight); + } } public void setOpacity(float a) { @@ -134,29 +155,31 @@ class Ripple { return mOuterOpacity; } - public void setX(float x) { - mX = x; + public void setRadiusGravity(float r) { + mTweenRadius = r; invalidateSelf(); } - public float getX() { - return mX; + public float getRadiusGravity() { + return mTweenRadius; } - public void setY(float y) { - mY = y; + public void setXGravity(float x) { + mTweenX = x; invalidateSelf(); } - public float getY() { - return mY; + public float getXGravity() { + return mTweenX; } - /** - * Returns whether this ripple has finished exiting. - */ - public boolean isFinished() { - return mFinished; + public void setYGravity(float y) { + mTweenY = y; + invalidateSelf(); + } + + public float getYGravity() { + return mTweenY; } /** @@ -204,28 +227,27 @@ class Ripple { } private boolean drawSoftware(Canvas c, Paint p) { - final float radius = mRadius; - final float opacity = mOpacity; - final float outerOpacity = mOuterOpacity; + boolean hasContent = false; // Cache the paint alpha so we can restore it later. final int paintAlpha = p.getAlpha(); - final int alpha = (int) (255 * opacity + 0.5f); - final int outerAlpha = (int) (255 * outerOpacity + 0.5f); - - boolean hasContent = false; - if (outerAlpha > 0 && alpha > 0) { - p.setAlpha(Math.min(alpha, outerAlpha)); + final int outerAlpha = (int) (255 * mOuterOpacity + 0.5f); + if (outerAlpha > 0 && mOuterRadius > 0) { + p.setAlpha(outerAlpha); p.setStyle(Style.FILL); c.drawCircle(mOuterX, mOuterY, mOuterRadius, p); hasContent = true; } - if (opacity > 0 && radius > 0) { + final int alpha = (int) (255 * mOpacity + 0.5f); + final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); + if (alpha > 0 && radius > 0) { + final float x = MathUtils.lerp(mStartingX - mBounds.exactCenterX(), mOuterX, mTweenX); + final float y = MathUtils.lerp(mStartingY - mBounds.exactCenterY(), mOuterY, mTweenY); p.setAlpha(alpha); p.setStyle(Style.FILL); - c.drawCircle(mX, mY, radius, p); + c.drawCircle(x, y, radius, p); hasContent = true; } @@ -235,45 +257,49 @@ class Ripple { } /** - * Returns the maximum bounds for this ripple. + * Returns the maximum bounds of the ripple relative to the ripple center. */ public void getBounds(Rect bounds) { final int outerX = (int) mOuterX; final int outerY = (int) mOuterY; final int r = (int) mOuterRadius; bounds.set(outerX - r, outerY - r, outerX + r, outerY + r); - - final int x = (int) mX; - final int y = (int) mY; - bounds.union(x - r, y - r, x + r, y + r); } /** - * Starts the enter animation at the specified absolute coordinates. + * Specifies the starting position relative to the drawable bounds. No-op if + * the ripple has already entered. */ - public void enter(float x, float y) { - mX = x - mBounds.exactCenterX(); - mY = y - mBounds.exactCenterY(); + public void move(float x, float y) { + mStartingX = x; + mStartingY = y; + } + /** + * Starts the enter animation. + */ + public void enter() { final int radiusDuration = (int) - (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION) + 0.5); + (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5); final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_VELOCITY); - final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radius", 0, mOuterRadius); + final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", 1); radius.setAutoCancel(true); radius.setDuration(radiusDuration); + radius.setInterpolator(LINEAR_INTERPOLATOR); - final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "x", mOuterX); + final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1); cX.setAutoCancel(true); cX.setDuration(radiusDuration); - final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "y", mOuterY); + final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", 1); cY.setAutoCancel(true); cY.setDuration(radiusDuration); final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1); outer.setAutoCancel(true); outer.setDuration(outerDuration); + outer.setInterpolator(LINEAR_INTERPOLATOR); mAnimRadius = radius; mAnimOuterOpacity = outer; @@ -295,15 +321,16 @@ class Ripple { public void exit() { cancelSoftwareAnimations(); + final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); final float remaining; if (mAnimRadius != null && mAnimRadius.isRunning()) { - remaining = mOuterRadius - mRadius; + remaining = mOuterRadius - radius; } else { remaining = mOuterRadius; } final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION - + WAVE_TOUCH_DOWN_ACCELERATION)) + 0.5); + + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5); final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f); // Determine at what time the inner and outer opacity intersect. @@ -325,6 +352,8 @@ class Ripple { int inflectionOpacity) { mPendingAnimations.clear(); + final float startX = MathUtils.lerp(mStartingX - mBounds.exactCenterX(), mOuterX, mTweenX); + final float startY = MathUtils.lerp(mStartingY - mBounds.exactCenterY(), mOuterY, mTweenY); final Paint outerPaint = new Paint(); outerPaint.setAntiAlias(true); outerPaint.setColor(mColor); @@ -335,58 +364,69 @@ class Ripple { mPropOuterX = CanvasProperty.createFloat(mOuterX); mPropOuterY = CanvasProperty.createFloat(mOuterY); + final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); final Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(mColor); paint.setAlpha((int) (255 * mOpacity + 0.5f)); paint.setStyle(Style.FILL); mPropPaint = CanvasProperty.createPaint(paint); - mPropRadius = CanvasProperty.createFloat(mRadius); - mPropX = CanvasProperty.createFloat(mX); - mPropY = CanvasProperty.createFloat(mY); + mPropRadius = CanvasProperty.createFloat(startRadius); + mPropX = CanvasProperty.createFloat(startX); + mPropY = CanvasProperty.createFloat(startY); - final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mOuterRadius); - radius.setDuration(radiusDuration); + final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius); + radiusAnim.setDuration(radiusDuration); + radiusAnim.setInterpolator(LINEAR_INTERPOLATOR); - final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mOuterX); - x.setDuration(radiusDuration); + final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX); + xAnim.setDuration(radiusDuration); + xAnim.setInterpolator(LINEAR_INTERPOLATOR); - final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mOuterY); - y.setDuration(radiusDuration); + final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY); + yAnim.setDuration(radiusDuration); + yAnim.setInterpolator(LINEAR_INTERPOLATOR); - final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint, + final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint, RenderNodeAnimator.PAINT_ALPHA, 0); - opacity.setDuration(opacityDuration); - opacity.addListener(mAnimationListener); + opacityAnim.setDuration(opacityDuration); + opacityAnim.setInterpolator(LINEAR_INTERPOLATOR); - final RenderNodeAnimator outerOpacity; + final RenderNodeAnimator outerOpacityAnim; if (outerInflection > 0) { // Outer opacity continues to increase for a bit. - outerOpacity = new RenderNodeAnimator( + outerOpacityAnim = new RenderNodeAnimator( mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity); - outerOpacity.setDuration(outerInflection); + outerOpacityAnim.setDuration(outerInflection); + outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR); // Chain the outer opacity exit animation. final int outerDuration = opacityDuration - outerInflection; if (outerDuration > 0) { - final RenderNodeAnimator outerFadeOut = new RenderNodeAnimator( + final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator( mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0); - outerFadeOut.setDuration(outerDuration); - outerFadeOut.setStartDelay(outerInflection); - - mPendingAnimations.add(outerFadeOut); + outerFadeOutAnim.setDuration(outerDuration); + outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR); + outerFadeOutAnim.setStartDelay(outerInflection); + outerFadeOutAnim.addListener(mAnimationListener); + + mPendingAnimations.add(outerFadeOutAnim); + } else { + outerOpacityAnim.addListener(mAnimationListener); } } else { - outerOpacity = new RenderNodeAnimator( + outerOpacityAnim = new RenderNodeAnimator( mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0); - outerOpacity.setDuration(opacityDuration); + outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR); + outerOpacityAnim.setDuration(opacityDuration); + outerOpacityAnim.addListener(mAnimationListener); } - mPendingAnimations.add(radius); - mPendingAnimations.add(opacity); - mPendingAnimations.add(outerOpacity); - mPendingAnimations.add(x); - mPendingAnimations.add(y); + mPendingAnimations.add(radiusAnim); + mPendingAnimations.add(opacityAnim); + mPendingAnimations.add(outerOpacityAnim); + mPendingAnimations.add(xAnim); + mPendingAnimations.add(yAnim); mHardwareAnimating = true; @@ -394,65 +434,80 @@ class Ripple { } private void exitSoftware(int radiusDuration, int opacityDuration, int outerInflection, - float inflectionOpacity) { - final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radius", mOuterRadius); - radius.setAutoCancel(true); - radius.setDuration(radiusDuration); - - final ObjectAnimator x = ObjectAnimator.ofFloat(this, "x", mOuterX); - x.setAutoCancel(true); - x.setDuration(radiusDuration); - - final ObjectAnimator y = ObjectAnimator.ofFloat(this, "y", mOuterY); - y.setAutoCancel(true); - y.setDuration(radiusDuration); - - final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, "opacity", 0); - opacity.setAutoCancel(true); - opacity.setDuration(opacityDuration); - opacity.addListener(mAnimationListener); - - final ObjectAnimator outerOpacity; + int inflectionOpacity) { + final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1); + radiusAnim.setAutoCancel(true); + radiusAnim.setDuration(radiusDuration); + radiusAnim.setInterpolator(LINEAR_INTERPOLATOR); + + final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1); + xAnim.setAutoCancel(true); + xAnim.setDuration(radiusDuration); + xAnim.setInterpolator(LINEAR_INTERPOLATOR); + + final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1); + yAnim.setAutoCancel(true); + yAnim.setDuration(radiusDuration); + yAnim.setInterpolator(LINEAR_INTERPOLATOR); + + final ObjectAnimator opacityAnim = ObjectAnimator.ofFloat(this, "opacity", 0); + opacityAnim.setAutoCancel(true); + opacityAnim.setDuration(opacityDuration); + opacityAnim.setInterpolator(LINEAR_INTERPOLATOR); + + final ObjectAnimator outerOpacityAnim; if (outerInflection > 0) { // Outer opacity continues to increase for a bit. - outerOpacity = ObjectAnimator.ofFloat(this, "outerOpacity", inflectionOpacity); - outerOpacity.setDuration(outerInflection); + outerOpacityAnim = ObjectAnimator.ofFloat(this, + "outerOpacity", inflectionOpacity / 255.0f); + outerOpacityAnim.setAutoCancel(true); + outerOpacityAnim.setDuration(outerInflection); + outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR); // Chain the outer opacity exit animation. final int outerDuration = opacityDuration - outerInflection; - outerOpacity.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - final ObjectAnimator outerFadeOut = ObjectAnimator.ofFloat(Ripple.this, - "outerOpacity", 0); - outerFadeOut.setDuration(outerDuration); - - mAnimOuterOpacity = outerFadeOut; - - outerFadeOut.start(); - } - - @Override - public void onAnimationCancel(Animator animation) { - animation.removeListener(this); - } - }); + if (outerDuration > 0) { + outerOpacityAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(Ripple.this, + "outerOpacity", 0); + outerFadeOutAnim.setAutoCancel(true); + outerFadeOutAnim.setDuration(outerDuration); + outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR); + outerFadeOutAnim.addListener(mAnimationListener); + + mAnimOuterOpacity = outerFadeOutAnim; + + outerFadeOutAnim.start(); + } + + @Override + public void onAnimationCancel(Animator animation) { + animation.removeListener(this); + } + }); + } else { + outerOpacityAnim.addListener(mAnimationListener); + } } else { - outerOpacity = ObjectAnimator.ofFloat(this, "outerOpacity", 0); - outerOpacity.setDuration(opacityDuration); + outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0); + outerOpacityAnim.setAutoCancel(true); + outerOpacityAnim.setDuration(opacityDuration); + outerOpacityAnim.addListener(mAnimationListener); } - mAnimRadius = radius; - mAnimOpacity = opacity; - mAnimOuterOpacity = outerOpacity; - mAnimX = opacity; - mAnimY = opacity; - - radius.start(); - opacity.start(); - outerOpacity.start(); - x.start(); - y.start(); + mAnimRadius = radiusAnim; + mAnimOpacity = opacityAnim; + mAnimOuterOpacity = outerOpacityAnim; + mAnimX = opacityAnim; + mAnimY = opacityAnim; + + radiusAnim.start(); + opacityAnim.start(); + outerOpacityAnim.start(); + xAnim.start(); + yAnim.start(); } /** @@ -498,6 +553,11 @@ class Ripple { runningAnimations.clear(); } + private void removeSelf() { + // The owner will invalidate itself. + mOwner.removeRipple(this); + } + private void invalidateSelf() { mOwner.invalidateSelf(); } @@ -505,12 +565,7 @@ class Ripple { private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - mFinished = true; - } - - @Override - public void onAnimationCancel(Animator animation) { - mFinished = true; + removeSelf(); } }; } diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 1bd7cac..9d7a8b6 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -25,17 +25,14 @@ import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.PointF; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; -import android.util.SparseArray; import com.android.internal.R; -import com.android.org.bouncycastle.util.Arrays; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -71,10 +68,17 @@ import java.io.IOException; public class RippleDrawable extends LayerDrawable { private static final String LOG_TAG = RippleDrawable.class.getSimpleName(); private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN); - private static final PorterDuffXfermode DST_ATOP = new PorterDuffXfermode(Mode.DST_ATOP); private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP); private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER); + /** + * Constant for automatically determining the maximum ripple radius. + * + * @see #setMaxRadius(int) + * @hide + */ + public static final int RADIUS_AUTO = -1; + /** The maximum number of ripples supported. */ private static final int MAX_RIPPLES = 10; @@ -91,17 +95,8 @@ public class RippleDrawable extends LayerDrawable { private final RippleState mState; - /** - * Lazily-created map of pending hotspot locations. These may be modified by - * calls to {@link #setHotspot(float, float)}. - */ - private SparseArray<PointF> mPendingHotspots; - - /** - * Lazily-created map of active hotspot locations. These may be modified by - * calls to {@link #setHotspot(float, float)}. - */ - private SparseArray<Ripple> mActiveHotspots; + /** The current hotspot. May be actively animating or pending entry. */ + private Ripple mHotspot; /** * Lazily-created array of actively animating ripples. Inactive ripples are @@ -122,18 +117,46 @@ public class RippleDrawable extends LayerDrawable { /** Whether bounds are being overridden. */ private boolean mOverrideBounds; + /** Whether the hotspot is currently active (e.g. focused or pressed). */ + private boolean mActive; + RippleDrawable() { + this(null, null); + } + + /** + * Creates a new ripple drawable with the specified content and mask + * drawables. + * + * @param content The content drawable, may be {@code null} + * @param mask The mask drawable, may be {@code null} + */ + public RippleDrawable(Drawable content, Drawable mask) { this(new RippleState(null, null, null), null, null); + + if (content != null) { + addLayer(content, null, 0, 0, 0, 0, 0); + } + + if (mask != null) { + addLayer(content, null, android.R.id.mask, 0, 0, 0, 0); + } + + ensurePadding(); } @Override public void setAlpha(int alpha) { - + super.setAlpha(alpha); + + // TODO: Should we support this? } @Override public void setColorFilter(ColorFilter cf) { - + super.setColorFilter(cf); + + // TODO: Should we support this? } @Override @@ -146,20 +169,18 @@ public class RippleDrawable extends LayerDrawable { protected boolean onStateChange(int[] stateSet) { super.onStateChange(stateSet); - final boolean pressed = Arrays.contains(stateSet, R.attr.state_pressed); - if (!pressed) { - removeHotspot(R.attr.state_pressed); - } else { - activateHotspot(R.attr.state_pressed); - } - - final boolean focused = Arrays.contains(stateSet, R.attr.state_focused); - if (!focused) { - removeHotspot(R.attr.state_focused); - } else { - activateHotspot(R.attr.state_focused); + boolean active = false; + final int N = stateSet.length; + for (int i = 0; i < N; i++) { + if (stateSet[i] == R.attr.state_focused + || stateSet[i] == R.attr.state_pressed) { + active = true; + break; + } } + setActive(active); + // Update the paint color. Only applicable when animated in software. if (mRipplePaint != null && mState.mTint != null) { final ColorStateList stateList = mState.mTint; final int newColor = stateList.getColorForState(stateSet, 0); @@ -174,12 +195,25 @@ public class RippleDrawable extends LayerDrawable { return false; } + private void setActive(boolean active) { + if (mActive != active) { + mActive = active; + + if (active) { + activateHotspot(); + } else { + removeHotspot(); + } + } + } + @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); if (!mOverrideBounds) { mHotspotBounds.set(bounds); + onHotspotBoundsChanged(); } invalidateSelf(); @@ -272,7 +306,7 @@ public class RippleDrawable extends LayerDrawable { /** * Initializes the constant state from the values in the typed array. */ - private void updateStateFromTypedArray(TypedArray a) { + private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { final RippleState state = mState; // Extract the theme attributes, if any. @@ -289,6 +323,12 @@ public class RippleDrawable extends LayerDrawable { } mState.mPinned = a.getBoolean(R.styleable.RippleDrawable_pinned, mState.mPinned); + + // If we're not waiting on a theme, verify required attributes. + if (state.mTouchThemeAttrs == null && mState.mTint == null) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <ripple> requires a valid tint attribute"); + } } /** @@ -314,8 +354,13 @@ public class RippleDrawable extends LayerDrawable { final TypedArray a = t.resolveAttributes(state.mTouchThemeAttrs, R.styleable.RippleDrawable); - updateStateFromTypedArray(a); - a.recycle(); + try { + updateStateFromTypedArray(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } } @Override @@ -330,36 +375,15 @@ public class RippleDrawable extends LayerDrawable { y = mHotspotBounds.exactCenterY(); } - // TODO: We should only have a single pending/active hotspot. - final int id = R.attr.state_pressed; - final int[] stateSet = getState(); - if (!Arrays.contains(stateSet, id)) { - // The hotspot is not active, so just modify the pending location. - getOrCreatePendingHotspot(id).set(x, y); - return; - } - - if (mAnimatingRipplesCount >= MAX_RIPPLES) { - // This should never happen unless the user is tapping like a maniac - // or there is a bug that's preventing ripples from being removed. - Log.d(LOG_TAG, "Max ripple count exceeded", new RuntimeException()); - return; - } + if (mHotspot == null) { + mHotspot = new Ripple(this, mHotspotBounds, x, y); - if (mActiveHotspots == null) { - mActiveHotspots = new SparseArray<Ripple>(); - mAnimatingRipples = new Ripple[MAX_RIPPLES]; - } - - final Ripple ripple = mActiveHotspots.get(id); - if (ripple != null) { - // The hotspot is active, but we can't move it because it's probably - // busy animating the center position. - return; + if (mActive) { + activateHotspot(); + } + } else { + mHotspot.move(x, y); } - - // The hotspot needs to be made active. - createActiveHotspot(id, x, y); } private boolean circleContains(Rect bounds, float x, float y) { @@ -374,74 +398,44 @@ public class RippleDrawable extends LayerDrawable { return pointRadius < boundsRadius; } - private PointF getOrCreatePendingHotspot(int id) { - final PointF p; - if (mPendingHotspots == null) { - mPendingHotspots = new SparseArray<>(2); - p = null; - } else { - p = mPendingHotspots.get(id); - } - - if (p == null) { - final PointF newPoint = new PointF(); - mPendingHotspots.put(id, newPoint); - return newPoint; - } else { - return p; - } - } - /** - * Moves a hotspot from pending to active. + * Creates an active hotspot at the specified location. */ - private void activateHotspot(int id) { - final SparseArray<PointF> pendingHotspots = mPendingHotspots; - if (pendingHotspots != null) { - final int index = pendingHotspots.indexOfKey(id); - if (index >= 0) { - final PointF hotspot = pendingHotspots.valueAt(index); - pendingHotspots.removeAt(index); - createActiveHotspot(id, hotspot.x, hotspot.y); - } + private void activateHotspot() { + if (mAnimatingRipplesCount >= MAX_RIPPLES) { + // This should never happen unless the user is tapping like a maniac + // or there is a bug that's preventing ripples from being removed. + Log.d(LOG_TAG, "Max ripple count exceeded", new RuntimeException()); + return; + } + + if (mHotspot == null) { + final float x = mHotspotBounds.exactCenterX(); + final float y = mHotspotBounds.exactCenterY(); + mHotspot = new Ripple(this, mHotspotBounds, x, y); } - } - /** - * Creates an active hotspot at the specified location. - */ - private void createActiveHotspot(int id, float x, float y) { final int color = mState.mTint.getColorForState(getState(), Color.TRANSPARENT); - final Ripple newRipple = new Ripple(this, mHotspotBounds, color); - newRipple.enter(x, y); + mHotspot.setup(mState.mMaxRadius, color, mDensity); + mHotspot.enter(); if (mAnimatingRipples == null) { mAnimatingRipples = new Ripple[MAX_RIPPLES]; } - mAnimatingRipples[mAnimatingRipplesCount++] = newRipple; - - if (mActiveHotspots == null) { - mActiveHotspots = new SparseArray<Ripple>(); - } - mActiveHotspots.put(id, newRipple); + mAnimatingRipples[mAnimatingRipplesCount++] = mHotspot; } - private void removeHotspot(int id) { - if (mActiveHotspots == null) { - return; - } - - final Ripple ripple = mActiveHotspots.get(id); - if (ripple != null) { - ripple.exit(); - - mActiveHotspots.remove(id); + private void removeHotspot() { + if (mHotspot != null) { + mHotspot.exit(); + mHotspot = null; } } private void clearHotspots() { - if (mActiveHotspots != null) { - mActiveHotspots.clear(); + if (mHotspot != null) { + mHotspot.cancel(); + mHotspot = null; } final int count = mAnimatingRipplesCount; @@ -459,73 +453,113 @@ public class RippleDrawable extends LayerDrawable { public void setHotspotBounds(int left, int top, int right, int bottom) { mOverrideBounds = true; mHotspotBounds.set(left, top, right, bottom); + + onHotspotBoundsChanged(); + } + + /** + * Notifies all the animating ripples that the hotspot bounds have changed. + */ + private void onHotspotBoundsChanged() { + final int count = mAnimatingRipplesCount; + final Ripple[] ripples = mAnimatingRipples; + for (int i = 0; i < count; i++) { + ripples[i].onHotspotBoundsChanged(); + } } @Override public void draw(Canvas canvas) { - final int N = mLayerState.mNum; - final Rect bounds = getBounds(); - final ChildDrawable[] array = mLayerState.mChildren; - final boolean maskOnly = mState.mMask != null && N == 1; - - int restoreToCount = drawRippleLayer(canvas, maskOnly); - - if (restoreToCount >= 0) { - // We have a ripple layer that contains ripples. If we also have an - // explicit mask drawable, apply it now using DST_IN blending. - if (mState.mMask != null) { - canvas.saveLayer(bounds.left, bounds.top, bounds.right, - bounds.bottom, getMaskingPaint(DST_IN)); - mState.mMask.draw(canvas); - canvas.restoreToCount(restoreToCount); - restoreToCount = -1; + final Rect bounds = isProjected() ? getDirtyBounds() : getBounds(); + + // Draw the content into a layer first. + final int contentLayer = drawContentLayer(canvas, bounds, SRC_OVER); + + // Next, draw the ripples into a layer. + final int rippleLayer = drawRippleLayer(canvas, bounds, mState.mTintXfermode); + + // If we have ripples, draw the masking layer. + if (rippleLayer >= 0) { + drawMaskingLayer(canvas, bounds, DST_IN); + } + + // Composite the layers if needed. + if (contentLayer >= 0) { + canvas.restoreToCount(contentLayer); + } else if (rippleLayer >= 0) { + canvas.restoreToCount(rippleLayer); + } + } + + /** + * Removes a ripple from the animating ripple list. + * + * @param ripple the ripple to remove + */ + void removeRipple(Ripple ripple) { + // Ripple ripple ripple ripple. Ripple ripple. + final Ripple[] ripples = mAnimatingRipples; + final int count = mAnimatingRipplesCount; + final int index = getRippleIndex(ripple); + if (index >= 0) { + for (int i = index + 1; i < count; i++) { + ripples[i - 1] = ripples[i]; } + ripples[count - 1] = null; + mAnimatingRipplesCount--; + invalidateSelf(); + } + } - // If there's more content, we need an extra masking layer to merge - // the ripples over the content. - if (!maskOnly) { - final PorterDuffXfermode xfermode = mState.getTintXfermodeInverse(); - final int count = canvas.saveLayer(bounds.left, bounds.top, - bounds.right, bounds.bottom, getMaskingPaint(xfermode)); - if (restoreToCount < 0) { - restoreToCount = count; - } + private int getRippleIndex(Ripple ripple) { + final Ripple[] ripples = mAnimatingRipples; + final int count = mAnimatingRipplesCount; + for (int i = 0; i < count; i++) { + if (ripples[i] == ripple) { + return i; } } + return -1; + } + + private int drawContentLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { + final int count = mLayerState.mNum; + if (count == 0 || (mState.mMask != null && count == 1)) { + return -1; + } + + final Paint maskingPaint = getMaskingPaint(mode); + final int restoreToCount = canvas.saveLayer(bounds.left, bounds.top, + bounds.right, bounds.bottom, maskingPaint); // Draw everything except the mask. - for (int i = 0; i < N; i++) { + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < count; i++) { if (array[i].mId != R.id.mask) { array[i].mDrawable.draw(canvas); } } - // Composite the layers if needed. - if (restoreToCount >= 0) { - canvas.restoreToCount(restoreToCount); - } + return restoreToCount; } - private int drawRippleLayer(Canvas canvas, boolean maskOnly) { + private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { final int count = mAnimatingRipplesCount; if (count == 0) { return -1; } - final Ripple[] ripples = mAnimatingRipples; - final boolean projected = isProjected(); - final Rect layerBounds = projected ? getDirtyBounds() : getBounds(); - // Separate the ripple color and alpha channel. The alpha will be // applied when we merge the ripples down to the canvas. - final int rippleColor; + final int rippleARGB; if (mState.mTint != null) { - rippleColor = mState.mTint.getColorForState(getState(), Color.TRANSPARENT); + rippleARGB = mState.mTint.getColorForState(getState(), Color.TRANSPARENT); } else { - rippleColor = Color.TRANSPARENT; + rippleARGB = Color.TRANSPARENT; } - final int rippleAlpha = Color.alpha(rippleColor); + final int rippleAlpha = Color.alpha(rippleARGB); + final int rippleColor = rippleARGB | (0xFF << 24); if (mRipplePaint == null) { mRipplePaint = new Paint(); mRipplePaint.setAntiAlias(true); @@ -536,36 +570,20 @@ public class RippleDrawable extends LayerDrawable { boolean drewRipples = false; int restoreToCount = -1; int restoreTranslate = -1; - int animatingCount = 0; // Draw ripples and update the animating ripples array. + final Ripple[] ripples = mAnimatingRipples; for (int i = 0; i < count; i++) { final Ripple ripple = ripples[i]; - // Mark and skip finished ripples. - if (ripple.isFinished()) { - ripples[i] = null; - continue; - } - // If we're masking the ripple layer, make sure we have a layer // first. This will merge SRC_OVER (directly) onto the canvas. if (restoreToCount < 0) { - // If we're projecting or we only have a mask, we want to treat the - // underlying canvas as our content and merge the ripple layer down - // using the tint xfermode. - final PorterDuffXfermode xfermode; - if (projected || maskOnly) { - xfermode = mState.getTintXfermode(); - } else { - xfermode = SRC_OVER; - } - - final Paint layerPaint = getMaskingPaint(xfermode); - layerPaint.setAlpha(rippleAlpha); - restoreToCount = canvas.saveLayer(layerBounds.left, layerBounds.top, - layerBounds.right, layerBounds.bottom, layerPaint); - layerPaint.setAlpha(255); + final Paint maskingPaint = getMaskingPaint(mode); + maskingPaint.setAlpha(rippleAlpha); + restoreToCount = canvas.saveLayer(bounds.left, bounds.top, + bounds.right, bounds.bottom, maskingPaint); + maskingPaint.setAlpha(255); restoreTranslate = canvas.save(); // Translate the canvas to the current hotspot bounds. @@ -573,13 +591,8 @@ public class RippleDrawable extends LayerDrawable { } drewRipples |= ripple.draw(canvas, ripplePaint); - - ripples[animatingCount] = ripples[i]; - animatingCount++; } - mAnimatingRipplesCount = animatingCount; - // Always restore the translation. if (restoreTranslate >= 0) { canvas.restoreToCount(restoreTranslate); @@ -594,6 +607,20 @@ public class RippleDrawable extends LayerDrawable { return restoreToCount; } + private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { + final Drawable mask = mState.mMask; + if (mask == null) { + return -1; + } + + final int restoreToCount = canvas.saveLayer(bounds.left, bounds.top, + bounds.right, bounds.bottom, getMaskingPaint(mode)); + + mask.draw(canvas); + + return restoreToCount; + } + private Paint getMaskingPaint(PorterDuffXfermode xfermode) { if (mMaskingPaint == null) { mMaskingPaint = new Paint(); @@ -634,8 +661,8 @@ public class RippleDrawable extends LayerDrawable { int[] mTouchThemeAttrs; ColorStateList mTint = null; PorterDuffXfermode mTintXfermode = SRC_ATOP; - PorterDuffXfermode mTintXfermodeInverse = DST_ATOP; Drawable mMask; + int mMaxRadius = RADIUS_AUTO; boolean mPinned = false; public RippleState(RippleState orig, RippleDrawable owner, Resources res) { @@ -645,14 +672,12 @@ public class RippleDrawable extends LayerDrawable { mTouchThemeAttrs = orig.mTouchThemeAttrs; mTint = orig.mTint; mTintXfermode = orig.mTintXfermode; - mTintXfermodeInverse = orig.mTintXfermodeInverse; + mMaxRadius = orig.mMaxRadius; mPinned = orig.mPinned; } } public void setTintMode(Mode mode) { - final Mode invertedMode = RippleState.invertPorterDuffMode(mode); - mTintXfermodeInverse = new PorterDuffXfermode(invertedMode); mTintXfermode = new PorterDuffXfermode(mode); } @@ -660,10 +685,6 @@ public class RippleDrawable extends LayerDrawable { return mTintXfermode; } - public PorterDuffXfermode getTintXfermodeInverse() { - return mTintXfermodeInverse; - } - @Override public boolean canApplyTheme() { return mTouchThemeAttrs != null || super.canApplyTheme(); @@ -683,33 +704,36 @@ public class RippleDrawable extends LayerDrawable { public Drawable newDrawable(Resources res, Theme theme) { return new RippleDrawable(this, res, theme); } + } - /** - * Inverts SRC and DST in PorterDuff blending modes. - */ - private static Mode invertPorterDuffMode(Mode src) { - switch (src) { - case SRC_ATOP: - return Mode.DST_ATOP; - case SRC_IN: - return Mode.DST_IN; - case SRC_OUT: - return Mode.DST_OUT; - case SRC_OVER: - return Mode.DST_OVER; - case DST_ATOP: - return Mode.SRC_ATOP; - case DST_IN: - return Mode.SRC_IN; - case DST_OUT: - return Mode.SRC_OUT; - case DST_OVER: - return Mode.SRC_OVER; - default: - // Everything else is agnostic to SRC versus DST. - return src; - } + /** + * Sets the maximum ripple radius in pixels. The default value of + * {@link #RADIUS_AUTO} defines the radius as the distance from the center + * of the drawable bounds (or hotspot bounds, if specified) to a corner. + * + * @param maxRadius the maximum ripple radius in pixels or + * {@link #RADIUS_AUTO} to automatically determine the maximum + * radius based on the bounds + * @see #getMaxRadius() + * @see #setHotspotBounds(int, int, int, int) + * @hide + */ + public void setMaxRadius(int maxRadius) { + if (maxRadius != RADIUS_AUTO && maxRadius < 0) { + throw new IllegalArgumentException("maxRadius must be RADIUS_AUTO or >= 0"); } + + mState.mMaxRadius = maxRadius; + } + + /** + * @return the maximum ripple radius in pixels, or {@link #RADIUS_AUTO} if + * the radius is determined automatically + * @see #setMaxRadius(int) + * @hide + */ + public int getMaxRadius() { + return mState.mMaxRadius; } private RippleDrawable(RippleState state, Resources res, Theme theme) { 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/DisplayList.cpp b/libs/hwui/DisplayList.cpp index 96c6292..f418c9b 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -80,10 +80,6 @@ void DisplayListData::cleanupResources() { delete paths.itemAt(i); } - for (size_t i = 0; i < matrices.size(); i++) { - delete matrices.itemAt(i); - } - bitmapResources.clear(); ownedBitmapResources.clear(); patchResources.clear(); @@ -91,7 +87,6 @@ void DisplayListData::cleanupResources() { paints.clear(); regions.clear(); paths.clear(); - matrices.clear(); layers.clear(); } diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index 11e78b0..7b7dc16 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -125,7 +125,6 @@ public: Vector<const SkPath*> paths; SortedVector<const SkPath*> sourcePaths; Vector<const SkRegion*> regions; - Vector<const SkMatrix*> matrices; Vector<Layer*> layers; uint32_t functorCount; bool hasDrawOps; diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index ea3e7a8..02f686f 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -472,7 +472,7 @@ private: class SetMatrixOp : public StateOp { public: - SetMatrixOp(const SkMatrix* matrix) + SetMatrixOp(const SkMatrix& matrix) : mMatrix(matrix) {} virtual void applyState(OpenGLRenderer& renderer, int saveCount) const { @@ -480,22 +480,22 @@ public: } virtual void output(int level, uint32_t logFlags) const { - if (mMatrix) { - OP_LOG("SetMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(mMatrix)); - } else { + if (mMatrix.isIdentity()) { OP_LOGS("SetMatrix (reset)"); + } else { + OP_LOG("SetMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(&mMatrix)); } } virtual const char* name() { return "SetMatrix"; } private: - const SkMatrix* mMatrix; + const SkMatrix mMatrix; }; class ConcatMatrixOp : public StateOp { public: - ConcatMatrixOp(const SkMatrix* matrix) + ConcatMatrixOp(const SkMatrix& matrix) : mMatrix(matrix) {} virtual void applyState(OpenGLRenderer& renderer, int saveCount) const { @@ -503,13 +503,13 @@ public: } virtual void output(int level, uint32_t logFlags) const { - OP_LOG("ConcatMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(mMatrix)); + OP_LOG("ConcatMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(&mMatrix)); } virtual const char* name() { return "ConcatMatrix"; } private: - const SkMatrix* mMatrix; + const SkMatrix mMatrix; }; class ClipOp : public StateOp { @@ -746,10 +746,10 @@ protected: class DrawBitmapMatrixOp : public DrawBoundedOp { public: - DrawBitmapMatrixOp(const SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint) + DrawBitmapMatrixOp(const SkBitmap* bitmap, const SkMatrix& matrix, const SkPaint* paint) : DrawBoundedOp(paint), mBitmap(bitmap), mMatrix(matrix) { mLocalBounds.set(0, 0, bitmap->width(), bitmap->height()); - const mat4 transform(*matrix); + const mat4 transform(matrix); transform.mapRect(mLocalBounds); } @@ -758,7 +758,7 @@ public: } virtual void output(int level, uint32_t logFlags) const { - OP_LOG("Draw bitmap %p matrix " SK_MATRIX_STRING, mBitmap, SK_MATRIX_ARGS(mMatrix)); + OP_LOG("Draw bitmap %p matrix " SK_MATRIX_STRING, mBitmap, SK_MATRIX_ARGS(&mMatrix)); } virtual const char* name() { return "DrawBitmapMatrix"; } @@ -770,7 +770,7 @@ public: private: const SkBitmap* mBitmap; - const SkMatrix* mMatrix; + const SkMatrix mMatrix; }; class DrawBitmapRectOp : public DrawBoundedOp { diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 229afdf..0e47c6e 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -141,14 +141,12 @@ void DisplayListRenderer::skew(float sx, float sy) { StatefulBaseRenderer::skew(sx, sy); } -void DisplayListRenderer::setMatrix(const SkMatrix* matrix) { - matrix = refMatrix(matrix); +void DisplayListRenderer::setMatrix(const SkMatrix& matrix) { addStateOp(new (alloc()) SetMatrixOp(matrix)); StatefulBaseRenderer::setMatrix(matrix); } -void DisplayListRenderer::concatMatrix(const SkMatrix* matrix) { - matrix = refMatrix(matrix); +void DisplayListRenderer::concatMatrix(const SkMatrix& matrix) { addStateOp(new (alloc()) ConcatMatrixOp(matrix)); StatefulBaseRenderer::concatMatrix(matrix); } @@ -203,10 +201,9 @@ status_t DisplayListRenderer::drawBitmap(const SkBitmap* bitmap, float left, flo return DrawGlInfo::kStatusDone; } -status_t DisplayListRenderer::drawBitmap(const SkBitmap* bitmap, const SkMatrix* matrix, +status_t DisplayListRenderer::drawBitmap(const SkBitmap* bitmap, const SkMatrix& matrix, const SkPaint* paint) { bitmap = refBitmap(bitmap); - matrix = refMatrix(matrix); paint = refPaint(paint); addDrawOp(new (alloc()) DrawBitmapMatrixOp(bitmap, matrix, paint)); diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index f0ae00f..195b00b 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -86,8 +86,8 @@ public: virtual void scale(float sx, float sy); virtual void skew(float sx, float sy); - virtual void setMatrix(const SkMatrix* matrix); - virtual void concatMatrix(const SkMatrix* matrix); + virtual void setMatrix(const SkMatrix& matrix); + virtual void concatMatrix(const SkMatrix& matrix); // Clip virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op); @@ -106,7 +106,7 @@ public: // Bitmap-based virtual status_t drawBitmap(const SkBitmap* bitmap, float left, float top, const SkPaint* paint); - virtual status_t drawBitmap(const SkBitmap* bitmap, const SkMatrix* matrix, + virtual status_t drawBitmap(const SkBitmap* bitmap, const SkMatrix& matrix, const SkPaint* paint); virtual status_t drawBitmap(const SkBitmap* bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, @@ -233,17 +233,6 @@ private: return regionCopy; } - inline const SkMatrix* refMatrix(const SkMatrix* matrix) { - if (matrix) { - // Copying the matrix is cheap and prevents against the user changing - // the original matrix before the operation that uses it - const SkMatrix* copy = new SkMatrix(*matrix); - mDisplayListData->matrices.add(copy); - return copy; - } - return matrix; - } - inline Layer* refLayer(Layer* layer) { mDisplayListData->layers.add(layer); mCaches.resourceCache.incrementRefcount(layer); 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/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 71836dd..cd09f86 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -2042,10 +2042,10 @@ status_t OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, float left, float to return DrawGlInfo::kStatusDrew; } -status_t OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, const SkMatrix* matrix, +status_t OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, const SkMatrix& matrix, const SkPaint* paint) { Rect r(0.0f, 0.0f, bitmap->width(), bitmap->height()); - const mat4 transform(*matrix); + const mat4 transform(matrix); transform.mapRect(r); if (quickRejectSetupScissor(r.left, r.top, r.right, r.bottom)) { diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index fc27947..0f953a5 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -170,7 +170,7 @@ public: const SkPaint* paint); status_t drawBitmaps(const SkBitmap* bitmap, AssetAtlas::Entry* entry, int bitmapCount, TextureVertex* vertices, bool pureTranslate, const Rect& bounds, const SkPaint* paint); - virtual status_t drawBitmap(const SkBitmap* bitmap, const SkMatrix* matrix, + virtual status_t drawBitmap(const SkBitmap* bitmap, const SkMatrix& matrix, const SkPaint* paint); virtual status_t drawBitmap(const SkBitmap* bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index 5a49f38..d9c06d3 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -346,7 +346,7 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) { float left, top, offset; uint32_t width, height; - PathCache::computePathBounds(t->path, t->paint, left, top, offset, width, height); + PathCache::computePathBounds(t->path, &t->paint, left, top, offset, width, height); PathTexture* texture = t->texture; texture->left = left; @@ -357,7 +357,7 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) { if (width <= mMaxTextureSize && height <= mMaxTextureSize) { SkBitmap* bitmap = new SkBitmap(); - drawPath(t->path, t->paint, *bitmap, left, top, offset, width, height); + drawPath(t->path, &t->paint, *bitmap, left, top, offset, width, height); t->setResult(bitmap); } else { texture->width = 0; diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h index 847853a..bcfb367 100644 --- a/libs/hwui/PathCache.h +++ b/libs/hwui/PathCache.h @@ -293,7 +293,7 @@ private: class PathTask: public Task<SkBitmap*> { public: PathTask(const SkPath* path, const SkPaint* paint, PathTexture* texture): - path(path), paint(paint), texture(texture) { + path(path), paint(*paint), texture(texture) { } ~PathTask() { @@ -301,7 +301,8 @@ private: } const SkPath* path; - const SkPaint* paint; + //copied, since input paint may not be immutable + const SkPaint paint; PathTexture* texture; }; 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..baf372a 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(); @@ -212,9 +223,9 @@ void RenderNode::setViewProperties(OpenGLRenderer& renderer, T& handler) { renderer.translate(properties().getLeft(), properties().getTop()); } if (properties().getStaticMatrix()) { - renderer.concatMatrix(properties().getStaticMatrix()); + renderer.concatMatrix(*properties().getStaticMatrix()); } else if (properties().getAnimationMatrix()) { - renderer.concatMatrix(properties().getAnimationMatrix()); + renderer.concatMatrix(*properties().getAnimationMatrix()); } if (properties().hasTransformMatrix()) { if (properties().isTransformTranslateOnly()) { 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/Renderer.h b/libs/hwui/Renderer.h index 23cab0e..320895c 100644 --- a/libs/hwui/Renderer.h +++ b/libs/hwui/Renderer.h @@ -170,8 +170,8 @@ public: virtual void scale(float sx, float sy) = 0; virtual void skew(float sx, float sy) = 0; - virtual void setMatrix(const SkMatrix* matrix) = 0; - virtual void concatMatrix(const SkMatrix* matrix) = 0; + virtual void setMatrix(const SkMatrix& matrix) = 0; + virtual void concatMatrix(const SkMatrix& matrix) = 0; // clip virtual const Rect& getLocalClipBounds() const = 0; @@ -193,7 +193,7 @@ public: // Bitmap-based virtual status_t drawBitmap(const SkBitmap* bitmap, float left, float top, const SkPaint* paint) = 0; - virtual status_t drawBitmap(const SkBitmap* bitmap, const SkMatrix* matrix, + virtual status_t drawBitmap(const SkBitmap* bitmap, const SkMatrix& matrix, const SkPaint* paint) = 0; virtual status_t drawBitmap(const SkBitmap* bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, diff --git a/libs/hwui/StatefulBaseRenderer.cpp b/libs/hwui/StatefulBaseRenderer.cpp index 90039e9..fae25a6 100644 --- a/libs/hwui/StatefulBaseRenderer.cpp +++ b/libs/hwui/StatefulBaseRenderer.cpp @@ -121,20 +121,16 @@ void StatefulBaseRenderer::skew(float sx, float sy) { mSnapshot->transform->skew(sx, sy); } -void StatefulBaseRenderer::setMatrix(const SkMatrix* matrix) { - if (matrix) { - mSnapshot->transform->load(*matrix); - } else { - mSnapshot->transform->loadIdentity(); - } +void StatefulBaseRenderer::setMatrix(const SkMatrix& matrix) { + mSnapshot->transform->load(matrix); } void StatefulBaseRenderer::setMatrix(const Matrix4& matrix) { mSnapshot->transform->load(matrix); } -void StatefulBaseRenderer::concatMatrix(const SkMatrix* matrix) { - mat4 transform(*matrix); +void StatefulBaseRenderer::concatMatrix(const SkMatrix& matrix) { + mat4 transform(matrix); mSnapshot->transform->multiply(transform); } diff --git a/libs/hwui/StatefulBaseRenderer.h b/libs/hwui/StatefulBaseRenderer.h index 057006b..f38c752 100644 --- a/libs/hwui/StatefulBaseRenderer.h +++ b/libs/hwui/StatefulBaseRenderer.h @@ -75,9 +75,9 @@ public: virtual void scale(float sx, float sy); virtual void skew(float sx, float sy); - virtual void setMatrix(const SkMatrix* matrix); + virtual void setMatrix(const SkMatrix& matrix); void setMatrix(const Matrix4& matrix); // internal only convenience method - virtual void concatMatrix(const SkMatrix* matrix); + virtual void concatMatrix(const SkMatrix& matrix); void concatMatrix(const Matrix4& matrix); // internal only convenience method // Clip 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/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 47a8ce5..a0ff074 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -41,6 +41,7 @@ import android.view.KeyEvent; import java.util.HashMap; import java.util.ArrayList; + /** * AudioManager provides access to volume and ringer mode control. * <p> @@ -61,6 +62,7 @@ public class AudioManager { private final boolean mUseVolumeKeySounds; private final Binder mToken = new Binder(); private static String TAG = "AudioManager"; + AudioPortEventHandler mAudioPortEventHandler; /** * Broadcast intent, a hint for applications that audio is about to become @@ -337,6 +339,12 @@ public class AudioManager { public static final int FLAG_BLUETOOTH_ABS_VOLUME = 1 << 6; /** + * Adjusting the volume was prevented due to silent mode, display a hint in the UI. + * @hide + */ + public static final int FLAG_SHOW_SILENT_HINT = 1 << 7; + + /** * Ringer mode that will be silent and will not vibrate. (This overrides the * vibrate setting.) * @@ -438,6 +446,7 @@ public class AudioManager { com.android.internal.R.bool.config_useMasterVolume); mUseVolumeKeySounds = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useVolumeKeySounds); + mAudioPortEventHandler = new AudioPortEventHandler(this); } private static IAudioService getService() @@ -3007,7 +3016,7 @@ public class AudioManager { * @hide */ public int listAudioPorts(ArrayList<AudioPort> ports) { - return ERROR_INVALID_OPERATION; + return updateAudioPortCache(ports, null); } /** @@ -3016,7 +3025,17 @@ public class AudioManager { * @hide */ public int listAudioDevicePorts(ArrayList<AudioPort> devices) { - return ERROR_INVALID_OPERATION; + ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); + int status = updateAudioPortCache(ports, null); + if (status == SUCCESS) { + devices.clear(); + for (int i = 0; i < ports.size(); i++) { + if (ports.get(i) instanceof AudioDevicePort) { + devices.add(ports.get(i)); + } + } + } + return status; } /** @@ -3045,7 +3064,7 @@ public class AudioManager { public int createAudioPatch(AudioPatch[] patch, AudioPortConfig[] sources, AudioPortConfig[] sinks) { - return ERROR_INVALID_OPERATION; + return AudioSystem.createAudioPatch(patch, sources, sinks); } /** @@ -3060,7 +3079,7 @@ public class AudioManager { * @hide */ public int releaseAudioPatch(AudioPatch patch) { - return ERROR_INVALID_OPERATION; + return AudioSystem.releaseAudioPatch(patch); } /** @@ -3069,7 +3088,7 @@ public class AudioManager { * @hide */ public int listAudioPatches(ArrayList<AudioPatch> patches) { - return ERROR_INVALID_OPERATION; + return updateAudioPortCache(null, patches); } /** @@ -3078,7 +3097,14 @@ public class AudioManager { * @hide */ public int setAudioPortGain(AudioPort port, AudioGainConfig gain) { - return ERROR_INVALID_OPERATION; + if (port == null || gain == null) { + return ERROR_BAD_VALUE; + } + AudioPortConfig activeConfig = port.activeConfig(); + AudioPortConfig config = new AudioPortConfig(port, activeConfig.samplingRate(), + activeConfig.channelMask(), activeConfig.format(), gain); + config.mConfigMask = AudioPortConfig.GAIN; + return AudioSystem.setAudioPortConfig(config); } /** @@ -3106,16 +3132,128 @@ public class AudioManager { } /** - * Register an audio port update listener. + * Register an audio port list update listener. * @hide */ public void registerAudioPortUpdateListener(OnAudioPortUpdateListener l) { + mAudioPortEventHandler.registerListener(l); } /** - * Unregister an audio port update listener. + * Unregister an audio port list update listener. * @hide */ public void unregisterAudioPortUpdateListener(OnAudioPortUpdateListener l) { + mAudioPortEventHandler.unregisterListener(l); + } + + // + // AudioPort implementation + // + + static final int AUDIOPORT_GENERATION_INIT = 0; + Integer mAudioPortGeneration = new Integer(AUDIOPORT_GENERATION_INIT); + ArrayList<AudioPort> mAudioPortsCached = new ArrayList<AudioPort>(); + ArrayList<AudioPatch> mAudioPatchesCached = new ArrayList<AudioPatch>(); + + int resetAudioPortGeneration() { + int generation; + synchronized (mAudioPortGeneration) { + generation = mAudioPortGeneration; + mAudioPortGeneration = AUDIOPORT_GENERATION_INIT; + } + return generation; + } + + int updateAudioPortCache(ArrayList<AudioPort> ports, ArrayList<AudioPatch> patches) { + synchronized (mAudioPortGeneration) { + + if (mAudioPortGeneration == AUDIOPORT_GENERATION_INIT) { + int[] patchGeneration = new int[1]; + int[] portGeneration = new int[1]; + int status; + ArrayList<AudioPort> newPorts = new ArrayList<AudioPort>(); + ArrayList<AudioPatch> newPatches = new ArrayList<AudioPatch>(); + + do { + newPorts.clear(); + status = AudioSystem.listAudioPorts(newPorts, portGeneration); + Log.i(TAG, "updateAudioPortCache AudioSystem.listAudioPorts() status: "+ + status+" num ports: "+ newPorts.size() +" portGeneration: "+portGeneration[0]); + if (status != SUCCESS) { + return status; + } + newPatches.clear(); + status = AudioSystem.listAudioPatches(newPatches, patchGeneration); + Log.i(TAG, "updateAudioPortCache AudioSystem.listAudioPatches() status: "+ + status+" num patches: "+ newPatches.size() +" patchGeneration: "+patchGeneration[0]); + if (status != SUCCESS) { + return status; + } + } while (patchGeneration[0] != portGeneration[0]); + + for (int i = 0; i < newPatches.size(); i++) { + for (int j = 0; j < newPatches.get(i).sources().length; j++) { + AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sources()[j], newPorts); + if (portCfg == null) { + return ERROR; + } + newPatches.get(i).sources()[j] = portCfg; + } + for (int j = 0; j < newPatches.get(i).sinks().length; j++) { + AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sinks()[j], newPorts); + if (portCfg == null) { + return ERROR; + } + newPatches.get(i).sinks()[j] = portCfg; + } + } + + mAudioPortsCached = newPorts; + mAudioPatchesCached = newPatches; + mAudioPortGeneration = portGeneration[0]; + } + if (ports != null) { + ports.clear(); + ports.addAll(mAudioPortsCached); + } + if (patches != null) { + patches.clear(); + patches.addAll(mAudioPatchesCached); + } + } + return SUCCESS; + } + + AudioPortConfig updatePortConfig(AudioPortConfig portCfg, ArrayList<AudioPort> ports) { + AudioPort port = portCfg.port(); + int k; + for (k = 0; k < ports.size(); k++) { + // compare handles because the port returned by JNI is not of the correct + // subclass + if (ports.get(k).handle().equals(port.handle())) { + Log.i(TAG, "updatePortConfig match found for port handle: "+ + port.handle().id()+" port: "+ k); + port = ports.get(k); + break; + } + } + if (k == ports.size()) { + // this hould never happen + Log.e(TAG, "updatePortConfig port not found for handle: "+port.handle().id()); + return null; + } + AudioGainConfig gainCfg = portCfg.gain(); + if (gainCfg != null) { + AudioGain gain = port.gain(gainCfg.index()); + gainCfg = gain.buildConfig(gainCfg.mode(), + gainCfg.channelMask(), + gainCfg.values(), + gainCfg.rampDurationMs()); + } + return port.buildConfig(portCfg.samplingRate(), + portCfg.channelMask(), + portCfg.format(), + gainCfg); } } diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java index 9aeddef..fbd5022 100644 --- a/media/java/android/media/AudioPort.java +++ b/media/java/android/media/AudioPort.java @@ -133,6 +133,9 @@ public class AudioPort { * Get the gain descriptor at a given index */ AudioGain gain(int index) { + if (index < mGains.length) { + return null; + } return mGains[index]; } diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java new file mode 100644 index 0000000..cd9a4de --- /dev/null +++ b/media/java/android/media/AudioPortEventHandler.java @@ -0,0 +1,168 @@ +/* + * 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.media; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import java.util.ArrayList; +import java.lang.ref.WeakReference; + +/** + * The AudioPortEventHandler handles AudioManager.OnAudioPortUpdateListener callbacks + * posted from JNI + * @hide + */ + +class AudioPortEventHandler { + private final Handler mHandler; + private ArrayList<AudioManager.OnAudioPortUpdateListener> mListeners; + private AudioManager mAudioManager; + + private static String TAG = "AudioPortEventHandler"; + + private static final int AUDIOPORT_EVENT_PORT_LIST_UPDATED = 1; + private static final int AUDIOPORT_EVENT_PATCH_LIST_UPDATED = 2; + private static final int AUDIOPORT_EVENT_SERVICE_DIED = 3; + private static final int AUDIOPORT_EVENT_NEW_LISTENER = 4; + + AudioPortEventHandler(AudioManager audioManager) { + mAudioManager = audioManager; + mListeners = new ArrayList<AudioManager.OnAudioPortUpdateListener>(); + + // find the looper for our new event handler + Looper looper = Looper.myLooper(); + if (looper == null) { + throw new IllegalArgumentException("Calling thread not associated with a looper"); + } + + mHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + Log.i(TAG, "handleMessage: "+msg.what); + ArrayList<AudioManager.OnAudioPortUpdateListener> listeners; + synchronized (this) { + if (msg.what == AUDIOPORT_EVENT_NEW_LISTENER) { + listeners = new ArrayList<AudioManager.OnAudioPortUpdateListener>(); + if (mListeners.contains(msg.obj)) { + listeners.add((AudioManager.OnAudioPortUpdateListener)msg.obj); + } + } else { + listeners = mListeners; + } + } + if (listeners.isEmpty()) { + return; + } + // reset audio port cache if the event corresponds to a change coming + // from audio policy service or if mediaserver process died. + if (msg.what == AUDIOPORT_EVENT_PORT_LIST_UPDATED || + msg.what == AUDIOPORT_EVENT_PATCH_LIST_UPDATED || + msg.what == AUDIOPORT_EVENT_SERVICE_DIED) { + mAudioManager.resetAudioPortGeneration(); + } + ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); + ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>(); + if (msg.what != AUDIOPORT_EVENT_SERVICE_DIED) { + int status = mAudioManager.updateAudioPortCache(ports, patches); + if (status != AudioManager.SUCCESS) { + return; + } + } + + switch (msg.what) { + case AUDIOPORT_EVENT_NEW_LISTENER: + case AUDIOPORT_EVENT_PORT_LIST_UPDATED: + AudioPort[] portList = ports.toArray(new AudioPort[0]); + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).OnAudioPortListUpdate(portList); + } + if (msg.what == AUDIOPORT_EVENT_PORT_LIST_UPDATED) { + break; + } + // FALL THROUGH + + case AUDIOPORT_EVENT_PATCH_LIST_UPDATED: + AudioPatch[] patchList = patches.toArray(new AudioPatch[0]); + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).OnAudioPatchListUpdate(patchList); + } + break; + + case AUDIOPORT_EVENT_SERVICE_DIED: + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).OnServiceDied(); + } + break; + + default: + break; + } + } + }; + + native_setup(new WeakReference<AudioPortEventHandler>(this)); + } + private native void native_setup(Object module_this); + + @Override + protected void finalize() { + native_finalize(); + } + private native void native_finalize(); + + void registerListener(AudioManager.OnAudioPortUpdateListener l) { + synchronized (this) { + mListeners.add(l); + } + if (mHandler != null) { + Message m = mHandler.obtainMessage(AUDIOPORT_EVENT_NEW_LISTENER, 0, 0, l); + mHandler.sendMessage(m); + } + } + + void unregisterListener(AudioManager.OnAudioPortUpdateListener l) { + synchronized (this) { + mListeners.remove(l); + } + } + + Handler handler() { + return mHandler; + } + + @SuppressWarnings("unused") + private static void postEventFromNative(Object module_ref, + int what, int arg1, int arg2, Object obj) { + AudioPortEventHandler eventHandler = + (AudioPortEventHandler)((WeakReference)module_ref).get(); + if (eventHandler == null) { + return; + } + + if (eventHandler != null) { + Handler handler = eventHandler.handler(); + if (handler != null) { + Message m = handler.obtainMessage(what, arg1, arg2, obj); + handler.sendMessage(m); + } + } + } + +} diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 98a7c65..74f39b7 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -115,6 +115,9 @@ public class AudioService extends IAudioService.Stub { /** Allow volume changes to set ringer mode to silent? */ private static final boolean VOLUME_SETS_RINGER_MODE_SILENT = false; + /** In silent mode, are volume adjustments (raises) prevented? */ + private static final boolean PREVENT_VOLUME_ADJUSTMENT_IF_SILENT = true; + /** How long to delay before persisting a change in volume/ringer mode. */ private static final int PERSIST_DELAY = 500; @@ -126,6 +129,11 @@ public class AudioService extends IAudioService.Stub { */ public static final int PLAY_SOUND_DELAY = 300; + /** + * Only used in the result from {@link #checkForRingerModeChange(int, int, int)} + */ + private static final int FLAG_ADJUST_VOLUME = 1; + private final Context mContext; private final ContentResolver mContentResolver; private final AppOpsManager mAppOps; @@ -942,7 +950,12 @@ public class AudioService extends IAudioService.Stub { } // Check if the ringer mode changes with this volume adjustment. If // it does, it will handle adjusting the volume, so we won't below - adjustVolume = checkForRingerModeChange(aliasIndex, direction, step); + final int result = checkForRingerModeChange(aliasIndex, direction, step); + adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0; + // If suppressing a volume adjustment in silent mode, display the UI hint + if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) { + flags |= AudioManager.FLAG_SHOW_SILENT_HINT; + } } int oldIndex = mStreamStates[streamType].getIndex(device); @@ -2564,8 +2577,8 @@ public class AudioService extends IAudioService.Stub { * adjusting volume. If so, this will set the proper ringer mode and volume * indices on the stream states. */ - private boolean checkForRingerModeChange(int oldIndex, int direction, int step) { - boolean adjustVolumeIndex = true; + private int checkForRingerModeChange(int oldIndex, int direction, int step) { + int result = FLAG_ADJUST_VOLUME; int ringerMode = getRingerMode(); switch (ringerMode) { @@ -2604,17 +2617,21 @@ public class AudioService extends IAudioService.Stub { } else if (direction == AudioManager.ADJUST_RAISE) { ringerMode = RINGER_MODE_NORMAL; } - adjustVolumeIndex = false; + result &= ~FLAG_ADJUST_VOLUME; break; case RINGER_MODE_SILENT: if (direction == AudioManager.ADJUST_RAISE) { - if (mHasVibrator) { - ringerMode = RINGER_MODE_VIBRATE; + if (PREVENT_VOLUME_ADJUSTMENT_IF_SILENT) { + result |= AudioManager.FLAG_SHOW_SILENT_HINT; } else { - ringerMode = RINGER_MODE_NORMAL; + if (mHasVibrator) { + ringerMode = RINGER_MODE_VIBRATE; + } else { + ringerMode = RINGER_MODE_NORMAL; + } } } - adjustVolumeIndex = false; + result &= ~FLAG_ADJUST_VOLUME; break; default: Log.e(TAG, "checkForRingerModeChange() wrong ringer mode: "+ringerMode); @@ -2625,7 +2642,7 @@ public class AudioService extends IAudioService.Stub { mPrevVolDirection = direction; - return adjustVolumeIndex; + return result; } @Override @@ -4141,8 +4158,9 @@ public class AudioService extends IAudioService.Stub { (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE))) { setBluetoothA2dpOnInt(true); } - boolean isUsb = ((device & AudioSystem.DEVICE_OUT_ALL_USB) != 0) || - ((device & AudioSystem.DEVICE_IN_ALL_USB) != 0); + boolean isUsb = ((device & ~AudioSystem.DEVICE_OUT_ALL_USB) == 0) || + (((device & AudioSystem.DEVICE_BIT_IN) != 0) && + ((device & ~AudioSystem.DEVICE_IN_ALL_USB) == 0)); handleDeviceConnection((state == 1), device, (isUsb ? name : "")); if (state != 0) { if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) || @@ -4159,7 +4177,7 @@ public class AudioService extends IAudioService.Stub { MUSIC_ACTIVE_POLL_PERIOD_MS); } } - if (!isUsb) { + if (!isUsb && (device != AudioSystem.DEVICE_IN_WIRED_HEADSET)) { sendDeviceConnectionIntent(device, state, name); } } @@ -4175,7 +4193,8 @@ public class AudioService extends IAudioService.Stub { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - int device; + int outDevice; + int inDevice; int state; if (action.equals(Intent.ACTION_DOCK_EVENT)) { @@ -4210,7 +4229,8 @@ public class AudioService extends IAudioService.Stub { } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); - device = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO; + outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO; + inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; String address = null; BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); @@ -4224,10 +4244,10 @@ public class AudioService extends IAudioService.Stub { switch (btClass.getDeviceClass()) { case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: - device = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET; + outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET; break; case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: - device = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT; + outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT; break; } } @@ -4237,7 +4257,9 @@ public class AudioService extends IAudioService.Stub { } boolean connected = (state == BluetoothProfile.STATE_CONNECTED); - if (handleDeviceConnection(connected, device, address)) { + boolean success = handleDeviceConnection(connected, outDevice, address) && + handleDeviceConnection(connected, inDevice, address); + if (success) { synchronized (mScoClients) { if (connected) { mBluetoothHeadsetDevice = btDevice; @@ -4257,8 +4279,8 @@ public class AudioService extends IAudioService.Stub { : "card=" + alsaCard + ";device=" + alsaDevice); // Playback Device - device = AudioSystem.DEVICE_OUT_USB_ACCESSORY; - setWiredDeviceConnectionState(device, state, params); + outDevice = AudioSystem.DEVICE_OUT_USB_ACCESSORY; + setWiredDeviceConnectionState(outDevice, state, params); } else if (action.equals(Intent.ACTION_USB_AUDIO_DEVICE_PLUG)) { state = intent.getIntExtra("state", 0); @@ -4273,14 +4295,14 @@ public class AudioService extends IAudioService.Stub { // Playback Device if (hasPlayback) { - device = AudioSystem.DEVICE_OUT_USB_DEVICE; - setWiredDeviceConnectionState(device, state, params); + outDevice = AudioSystem.DEVICE_OUT_USB_DEVICE; + setWiredDeviceConnectionState(outDevice, state, params); } // Capture Device if (hasCapture) { - device = AudioSystem.DEVICE_IN_USB_DEVICE; - setWiredDeviceConnectionState(device, state, params); + inDevice = AudioSystem.DEVICE_IN_USB_DEVICE; + setWiredDeviceConnectionState(inDevice, state, params); } } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { boolean broadcast = false; diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 5abf4b6..c8d64ce 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -16,6 +16,7 @@ package android.media; +import java.util.ArrayList; /* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET * TO UPDATE THE CORRESPONDING NATIVE GLUE AND AudioManager.java. @@ -459,4 +460,12 @@ public class AudioSystem public static native int setLowRamDevice(boolean isLowRamDevice); public static native int checkAudioFlinger(); + + public static native int listAudioPorts(ArrayList<AudioPort> ports, int[] generation); + public static native int createAudioPatch(AudioPatch[] patch, + AudioPortConfig[] sources, AudioPortConfig[] sinks); + public static native int releaseAudioPatch(AudioPatch patch); + public static native int listAudioPatches(ArrayList<AudioPatch> patches, int[] generation); + public static native int setAudioPortConfig(AudioPortConfig config); } + diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index c7b3fc9..f258063 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -744,12 +744,40 @@ final public class MediaCodec { setParameters(keys, values); } + /** + * Sets the codec listener for actionable MediaCodec events. + * <p>Call this method with a null listener to stop receiving event notifications. + * + * @param cb The listener that will run. + * + * @hide + */ public void setNotificationCallback(NotificationCallback cb) { mNotificationCallback = cb; } - public interface NotificationCallback { - void onCodecNotify(MediaCodec codec); + /** + * MediaCodec listener interface. Used to notify the user of MediaCodec + * when there are available input and/or output buffers, a change in + * configuration or when a codec error happened. + * + * @hide + */ + public static abstract class NotificationCallback { + /** + * Called on the listener to notify that there is an actionable + * MediaCodec event. The application should call {@link #dequeueOutputBuffer} + * to receive the configuration change event, codec error or an + * available output buffer. It should also call {@link #dequeueInputBuffer} + * to receive any available input buffer. For best performance, it + * is recommended to exhaust both available input and output buffers in + * the handling of a single callback, by calling the dequeue methods + * repeatedly with a zero timeout until {@link #INFO_TRY_AGAIN_LATER} is returned. + * + * @param codec the MediaCodec instance that has an actionable event. + * + */ + public abstract void onCodecNotify(MediaCodec codec); } private void postEventFromNative( diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java index ff73a10..5dc8e1b 100644 --- a/media/java/android/media/MediaMetadata.java +++ b/media/java/android/media/MediaMetadata.java @@ -23,6 +23,8 @@ import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; +import java.util.Set; + /** * Contains metadata about an item, such as the title, artist, etc. */ @@ -301,6 +303,15 @@ public final class MediaMetadata implements Parcelable { } /** + * Returns a Set containing the Strings used as keys in this metadata. + * + * @return a Set of String keys + */ + public Set<String> keySet() { + return mBundle.keySet(); + } + + /** * Helper for getting the String key used by {@link MediaMetadata} from the * integer key that {@link MediaMetadataEditor} uses. * diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 26ae3cc..0caea5f 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -27,7 +27,6 @@ import android.graphics.RectF; import android.media.session.MediaSessionLegacyHelper; import android.media.session.PlaybackState; import android.media.session.MediaSession; -import android.media.session.TransportPerformer; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -584,7 +583,7 @@ public class RemoteControlClient // USE_SESSIONS if (mSession != null && mMetadataBuilder != null) { - mSession.getTransportPerformer().setMetadata(mMetadataBuilder.build()); + mSession.setMetadata(mMetadataBuilder.build()); } mApplied = true; } @@ -702,7 +701,7 @@ public class RemoteControlClient mSessionPlaybackState.setState(pbState, hasPosition ? mPlaybackPositionMs : PlaybackState.PLAYBACK_POSITION_UNKNOWN, playbackSpeed); - mSession.getTransportPerformer().setPlaybackState(mSessionPlaybackState); + mSession.setPlaybackState(mSessionPlaybackState); } } } @@ -789,7 +788,7 @@ public class RemoteControlClient if (mSession != null) { mSessionPlaybackState.setActions(PlaybackState .getActionsFromRccControlFlags(transportControlFlags)); - mSession.getTransportPerformer().setPlaybackState(mSessionPlaybackState); + mSession.setPlaybackState(mSessionPlaybackState); } } } @@ -1317,7 +1316,8 @@ public class RemoteControlClient } // USE_SESSIONS - private TransportPerformer.Listener mTransportListener = new TransportPerformer.Listener() { + private MediaSession.TransportControlsCallback mTransportListener + = new MediaSession.TransportControlsCallback() { @Override public void onSeekTo(long pos) { @@ -1325,7 +1325,7 @@ public class RemoteControlClient } @Override - public void onRate(Rating rating) { + public void onSetRating(Rating rating) { if ((mTransportControlFlags & FLAG_KEY_MEDIA_RATING) != 0) { if (mEventHandler != null) { mEventHandler.sendMessage(mEventHandler.obtainMessage( diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl index 5ddb6db..9ce0692 100644 --- a/media/java/android/media/session/ISessionController.aidl +++ b/media/java/android/media/session/ISessionController.aidl @@ -30,7 +30,7 @@ import android.view.KeyEvent; */ interface ISessionController { void sendCommand(String command, in Bundle extras, in ResultReceiver cb); - void sendMediaButton(in KeyEvent mediaButton); + boolean sendMediaButton(in KeyEvent mediaButton); void registerCallbackListener(in ISessionControllerCallback cb); void unregisterCallbackListener(in ISessionControllerCallback cb); boolean isTransportControlEnabled(); diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index 642ac2f..caff1ad 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -17,6 +17,7 @@ package android.media.session; import android.media.MediaMetadata; +import android.media.Rating; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -45,8 +46,8 @@ public final class MediaController { private static final String TAG = "SessionController"; private static final int MSG_EVENT = 1; - private static final int MESSAGE_PLAYBACK_STATE = 2; - private static final int MESSAGE_METADATA = 3; + private static final int MSG_UPDATE_PLAYBACK_STATE = 2; + private static final int MSG_UPDATE_METADATA = 3; private static final int MSG_ROUTE = 4; private final ISessionController mSessionBinder; @@ -57,10 +58,11 @@ public final class MediaController { private boolean mCbRegistered = false; - private TransportController mTransportController; + private TransportControls mTransportController; private MediaController(ISessionController sessionBinder) { mSessionBinder = sessionBinder; + mTransportController = new TransportControls(); } /** @@ -70,9 +72,6 @@ public final class MediaController { MediaController controller = new MediaController(sessionBinder); try { controller.mSessionBinder.registerCallbackListener(controller.mCbStub); - if (controller.mSessionBinder.isTransportControlEnabled()) { - controller.mTransportController = new TransportController(sessionBinder); - } } catch (RemoteException e) { Log.wtf(TAG, "MediaController created with expired token", e); controller = null; @@ -93,33 +92,84 @@ public final class MediaController { } /** - * Get a TransportController if the session supports it. If it is not - * supported null will be returned. + * Get a {@link TransportControls} instance for this session. * - * @return A TransportController or null + * @return A controls instance */ - public TransportController getTransportController() { + public TransportControls getTransportControls() { return mTransportController; } /** - * Send the specified media button to the session. Only media keys can be - * sent using this method. + * Send the specified media button event to the session. Only media keys can + * be sent by this method, other keys will be ignored. + * + * @param keyEvent The media button event to dispatch. + * @return true if the event was sent to the session, false otherwise. + */ + public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) { + if (keyEvent == null) { + throw new IllegalArgumentException("KeyEvent may not be null"); + } + if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) { + return false; + } + try { + return mSessionBinder.sendMediaButton(keyEvent); + } catch (RemoteException e) { + // System is dead. =( + } + return false; + } + + /** + * Get the current playback state for this session. + * + * @return The current PlaybackState or null + */ + public PlaybackState getPlaybackState() { + try { + return mSessionBinder.getPlaybackState(); + } catch (RemoteException e) { + Log.wtf(TAG, "Error calling getPlaybackState.", e); + return null; + } + } + + /** + * Get the current metadata for this session. * - * @param keycode The media button keycode, such as - * {@link KeyEvent#KEYCODE_MEDIA_PLAY}. + * @return The current MediaMetadata or null. */ - public void sendMediaButton(int keycode) { - if (!KeyEvent.isMediaKey(keycode)) { - throw new IllegalArgumentException("May only send media buttons through " - + "sendMediaButton"); + public MediaMetadata getMetadata() { + try { + return mSessionBinder.getMetadata(); + } catch (RemoteException e) { + Log.wtf(TAG, "Error calling getMetadata.", e); + return null; } - // TODO do something better than key down/up events - KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, keycode); + } + + /** + * Get the rating type supported by the session. One of: + * <ul> + * <li>{@link Rating#RATING_NONE}</li> + * <li>{@link Rating#RATING_HEART}</li> + * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li> + * <li>{@link Rating#RATING_3_STARS}</li> + * <li>{@link Rating#RATING_4_STARS}</li> + * <li>{@link Rating#RATING_5_STARS}</li> + * <li>{@link Rating#RATING_PERCENTAGE}</li> + * </ul> + * + * @return The supported rating type + */ + public int getRatingType() { try { - mSessionBinder.sendMediaButton(event); + return mSessionBinder.getRatingType(); } catch (RemoteException e) { - Log.d(TAG, "Dead object in sendMediaButton", e); + Log.wtf(TAG, "Error calling getRatingType.", e); + return Rating.RATING_NONE; } } @@ -171,7 +221,7 @@ public final class MediaController { * @param params Any parameters to include with the command * @param cb The callback to receive the result on */ - public void sendCommand(String command, Bundle params, ResultReceiver cb) { + public void sendControlCommand(String command, Bundle params, ResultReceiver cb) { if (TextUtils.isEmpty(command)) { throw new IllegalArgumentException("command cannot be null or empty"); } @@ -254,18 +304,10 @@ public final class MediaController { return null; } - private void postEvent(String event, Bundle extras) { - synchronized (mLock) { - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - mCallbacks.get(i).post(MSG_EVENT, event, extras); - } - } - } - - private void postRouteChanged(RouteInfo route) { + private final void postMessage(int what, Object obj, Bundle data) { synchronized (mLock) { for (int i = mCallbacks.size() - 1; i >= 0; i--) { - mCallbacks.get(i).post(MSG_ROUTE, route, null); + mCallbacks.get(i).post(what, obj, data); } } } @@ -282,7 +324,7 @@ public final class MediaController { * * @param event */ - public void onEvent(String event, Bundle extras) { + public void onSessionEvent(String event, Bundle extras) { } /** @@ -293,6 +335,143 @@ public final class MediaController { */ public void onRouteChanged(RouteInfo route) { } + + /** + * Override to handle changes in playback state. + * + * @param state The new playback state of the session + */ + public void onPlaybackStateChanged(PlaybackState state) { + } + + /** + * Override to handle changes to the current metadata. + * + * @see MediaMetadata + * @param metadata The current metadata for the session or null + */ + public void onMetadataChanged(MediaMetadata metadata) { + } + } + + /** + * Interface for controlling media playback on a session. This allows an app + * to send media transport commands to the session. + */ + public final class TransportControls { + private static final String TAG = "TransportController"; + + private TransportControls() { + } + + /** + * Request that the player start its playback at its current position. + */ + public void play() { + try { + mSessionBinder.play(); + } catch (RemoteException e) { + Log.wtf(TAG, "Error calling play.", e); + } + } + + /** + * Request that the player pause its playback and stay at its current + * position. + */ + public void pause() { + try { + mSessionBinder.pause(); + } catch (RemoteException e) { + Log.wtf(TAG, "Error calling pause.", e); + } + } + + /** + * Request that the player stop its playback; it may clear its state in + * whatever way is appropriate. + */ + public void stop() { + try { + mSessionBinder.stop(); + } catch (RemoteException e) { + Log.wtf(TAG, "Error calling stop.", e); + } + } + + /** + * Move to a new location in the media stream. + * + * @param pos Position to move to, in milliseconds. + */ + public void seekTo(long pos) { + try { + mSessionBinder.seekTo(pos); + } catch (RemoteException e) { + Log.wtf(TAG, "Error calling seekTo.", e); + } + } + + /** + * Start fast forwarding. If playback is already fast forwarding this + * may increase the rate. + */ + public void fastForward() { + try { + mSessionBinder.fastForward(); + } catch (RemoteException e) { + Log.wtf(TAG, "Error calling fastForward.", e); + } + } + + /** + * Skip to the next item. + */ + public void skipToNext() { + try { + mSessionBinder.next(); + } catch (RemoteException e) { + Log.wtf(TAG, "Error calling next.", e); + } + } + + /** + * Start rewinding. If playback is already rewinding this may increase + * the rate. + */ + public void rewind() { + try { + mSessionBinder.rewind(); + } catch (RemoteException e) { + Log.wtf(TAG, "Error calling rewind.", e); + } + } + + /** + * Skip to the previous item. + */ + public void skipToPrevious() { + try { + mSessionBinder.previous(); + } catch (RemoteException e) { + Log.wtf(TAG, "Error calling previous.", e); + } + } + + /** + * Rate the current content. This will cause the rating to be set for + * the current user. The Rating type must match the type returned by + * {@link #getRatingType()}. + * + * @param rating The rating to set for the current content + */ + public void setRating(Rating rating) { + try { + mSessionBinder.rate(rating); + } catch (RemoteException e) { + Log.wtf(TAG, "Error calling rate.", e); + } + } } private final static class CallbackStub extends ISessionControllerCallback.Stub { @@ -306,7 +485,7 @@ public final class MediaController { public void onEvent(String event, Bundle extras) { MediaController controller = mController.get(); if (controller != null) { - controller.postEvent(event, extras); + controller.postMessage(MSG_EVENT, event, extras); } } @@ -314,7 +493,7 @@ public final class MediaController { public void onRouteChanged(RouteInfo route) { MediaController controller = mController.get(); if (controller != null) { - controller.postRouteChanged(route); + controller.postMessage(MSG_ROUTE, route, null); } } @@ -322,10 +501,7 @@ public final class MediaController { public void onPlaybackStateChanged(PlaybackState state) { MediaController controller = mController.get(); if (controller != null) { - TransportController tc = controller.getTransportController(); - if (tc != null) { - tc.postPlaybackStateChanged(state); - } + controller.postMessage(MSG_UPDATE_PLAYBACK_STATE, state, null); } } @@ -333,10 +509,7 @@ public final class MediaController { public void onMetadataChanged(MediaMetadata metadata) { MediaController controller = mController.get(); if (controller != null) { - TransportController tc = controller.getTransportController(); - if (tc != null) { - tc.postMetadataChanged(metadata); - } + controller.postMessage(MSG_UPDATE_METADATA, metadata, null); } } @@ -354,10 +527,17 @@ public final class MediaController { public void handleMessage(Message msg) { switch (msg.what) { case MSG_EVENT: - mCallback.onEvent((String) msg.obj, msg.getData()); + mCallback.onSessionEvent((String) msg.obj, msg.getData()); break; case MSG_ROUTE: mCallback.onRouteChanged((RouteInfo) msg.obj); + break; + case MSG_UPDATE_PLAYBACK_STATE: + mCallback.onPlaybackStateChanged((PlaybackState) msg.obj); + break; + case MSG_UPDATE_METADATA: + mCallback.onMetadataChanged((MediaMetadata) msg.obj); + break; } } diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 539dc3c..90ccf68 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -16,10 +16,12 @@ package android.media.session; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Intent; -import android.media.AudioAttributes; import android.media.AudioManager; +import android.media.MediaMetadata; import android.media.Rating; import android.media.session.ISessionController; import android.media.session.ISession; @@ -49,15 +51,13 @@ import java.util.List; * <p> * A MediaSession is created by calling * {@link MediaSessionManager#createSession(String)}. Once a session is created - * apps that have the MEDIA_CONTENT_CONTROL permission can interact with the - * session through - * {@link MediaSessionManager#getActiveSessions(android.content.ComponentName)}. - * The owner of the session may also use {@link #getSessionToken()} to allow - * apps without this permission to create a {@link MediaController} to interact - * with this session. + * the owner of the session may use {@link #getSessionToken()} to allow apps to + * create a {@link MediaController} to interact with this session. * <p> - * To receive commands, media keys, and other events a Callback must be set with - * {@link #addCallback(Callback)}. + * To receive commands, media keys, and other events a {@link Callback} must be + * set with {@link #addCallback(Callback)}. To receive transport control + * commands a {@link TransportControlsCallback} must be set with + * {@link #addTransportControlsCallback}. * <p> * When an app is finished performing playback it must call {@link #release()} * to clean up the session and notify any controllers. @@ -74,9 +74,10 @@ public final class MediaSession { public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0; /** - * Set this flag on the session to indicate that it handles commands through - * the {@link TransportPerformer}. The performer can be retrieved by calling - * {@link #getTransportPerformer()}. + * Set this flag on the session to indicate that it handles transport + * control commands through a {@link TransportControlsCallback}. The + * callback can be retrieved by calling + * {@link #addTransportControlsCallback}. */ public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1; @@ -123,12 +124,6 @@ public final class MediaSession { */ public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5; - private static final int MSG_MEDIA_BUTTON = 1; - private static final int MSG_COMMAND = 2; - private static final int MSG_ROUTE_CHANGE = 3; - private static final int MSG_ROUTE_CONNECTED = 4; - private static final int MSG_ROUTE_DISCONNECTED = 5; - private static final String KEY_COMMAND = "command"; private static final String KEY_EXTRAS = "extras"; private static final String KEY_CALLBACK = "callback"; @@ -139,12 +134,14 @@ public final class MediaSession { private final ISession mBinder; private final CallbackStub mCbStub; - private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>(); + private final ArrayList<CallbackMessageHandler> mCallbacks + = new ArrayList<CallbackMessageHandler>(); + private final ArrayList<TransportMessageHandler> mTransportCallbacks + = new ArrayList<TransportMessageHandler>(); // TODO route interfaces private final ArrayMap<String, RouteInterface.EventListener> mInterfaceListeners = new ArrayMap<String, RouteInterface.EventListener>(); - private TransportPerformer mPerformer; private Route mRoute; private boolean mActive = false;; @@ -162,11 +159,12 @@ public final class MediaSession { throw new RuntimeException("Dead object in MediaSessionController constructor: ", e); } mSessionToken = new MediaSessionToken(controllerBinder); - mPerformer = new TransportPerformer(mBinder); } /** - * Set the callback to receive updates on. + * Add a callback to receive updates on for the MediaSession. This includes + * media button and volume events. The caller's thread will be used to post + * events. * * @param callback The callback object */ @@ -193,7 +191,8 @@ public final class MediaSession { if (handler == null) { handler = new Handler(); } - MessageHandler msgHandler = new MessageHandler(handler.getLooper(), callback); + CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(), + callback); mCallbacks.add(msgHandler); } } @@ -210,18 +209,6 @@ public final class MediaSession { } /** - * Retrieves the {@link TransportPerformer} for this session. To receive - * commands through the performer you must also set the - * {@link #FLAG_HANDLES_TRANSPORT_CONTROLS} flag using - * {@link #setFlags(int)}. - * - * @return The performer associated with this session. - */ - public TransportPerformer getTransportPerformer() { - return mPerformer; - } - - /** * Set an intent for launching UI for this Session. This can be used as a * quick link to an ongoing media screen. * @@ -246,7 +233,7 @@ public final class MediaSession { /** * Set the stream this session is playing on. This will affect the system's - * volume handling for this session. If {@link #useRemotePlayback} was + * volume handling for this session. If {@link #setPlaybackToRemote} was * previously called it will stop receiving volume commands and the system * will begin sending volume changes to the appropriate stream. * <p> @@ -254,21 +241,21 @@ public final class MediaSession { * * @param stream The {@link AudioManager} stream this session is playing on. */ - public void useLocalPlayback(int stream) { + public void setPlaybackToLocal(int stream) { // TODO } /** * Configure this session to use remote volume handling. This must be called * to receive volume button events, otherwise the system will adjust the - * current stream volume for this session. If {@link #useLocalPlayback} was - * previously called that stream will stop receiving volume changes for this - * session. + * current stream volume for this session. If {@link #setPlaybackToLocal} + * was previously called that stream will stop receiving volume changes for + * this session. * * @param volumeProvider The provider that will handle volume changes. May * not be null. */ - public void useRemotePlayback(RemoteVolumeProvider volumeProvider) { + public void setPlaybackToRemote(RemoteVolumeProvider volumeProvider) { if (volumeProvider == null) { throw new IllegalArgumentException("volumeProvider may not be null!"); } @@ -312,7 +299,7 @@ public final class MediaSession { * @param event The name of the event to send * @param extras Any extras included with the event */ - public void sendEvent(String event, Bundle extras) { + public void sendSessionEvent(String event, Bundle extras) { if (TextUtils.isEmpty(event)) { throw new IllegalArgumentException("event cannot be null or empty"); } @@ -432,12 +419,160 @@ public final class MediaSession { return true; } - private MessageHandler getHandlerForCallbackLocked(Callback cb) { + /** + * Add a callback to receive transport controls on, such as play, rewind, or + * fast forward. + * + * @param callback The callback object + */ + public void addTransportControlsCallback(@NonNull TransportControlsCallback callback) { + addTransportControlsCallback(callback, null); + } + + /** + * Add a callback to receive transport controls on, such as play, rewind, or + * fast forward. The updates will be posted to the specified handler. If no + * handler is provided they will be posted to the caller's thread. + * + * @param callback The callback to receive updates on + * @param handler The handler to post the updates on + */ + public void addTransportControlsCallback(@NonNull TransportControlsCallback callback, + @Nullable Handler handler) { + if (callback == null) { + throw new IllegalArgumentException("Callback cannot be null"); + } + synchronized (mLock) { + if (getTransportControlsHandlerForCallbackLocked(callback) != null) { + Log.w(TAG, "Callback is already added, ignoring"); + return; + } + if (handler == null) { + handler = new Handler(); + } + TransportMessageHandler msgHandler = new TransportMessageHandler(handler.getLooper(), + callback); + mTransportCallbacks.add(msgHandler); + } + } + + /** + * Stop receiving transport controls on the specified callback. If an update + * has already been posted you may still receive it after this call returns. + * + * @param callback The callback to stop receiving updates on + */ + public void removeTransportControlsCallback(@NonNull TransportControlsCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("Callback cannot be null"); + } + synchronized (mLock) { + removeTransportControlsCallbackLocked(callback); + } + } + + /** + * Update the current playback state. + * + * @param state The current state of playback + */ + public void setPlaybackState(PlaybackState state) { + try { + mBinder.setPlaybackState(state); + } catch (RemoteException e) { + Log.wtf(TAG, "Dead object in setPlaybackState.", e); + } + } + + /** + * Update the current metadata. New metadata can be created using + * {@link android.media.MediaMetadata.Builder}. + * + * @param metadata The new metadata + */ + public void setMetadata(MediaMetadata metadata) { + try { + mBinder.setMetadata(metadata); + } catch (RemoteException e) { + Log.wtf(TAG, "Dead object in setPlaybackState.", e); + } + } + + private void dispatchPlay() { + postToTransportCallbacks(TransportMessageHandler.MSG_PLAY); + } + + private void dispatchPause() { + postToTransportCallbacks(TransportMessageHandler.MSG_PAUSE); + } + + private void dispatchStop() { + postToTransportCallbacks(TransportMessageHandler.MSG_STOP); + } + + private void dispatchNext() { + postToTransportCallbacks(TransportMessageHandler.MSG_NEXT); + } + + private void dispatchPrevious() { + postToTransportCallbacks(TransportMessageHandler.MSG_PREVIOUS); + } + + private void dispatchFastForward() { + postToTransportCallbacks(TransportMessageHandler.MSG_FAST_FORWARD); + } + + private void dispatchRewind() { + postToTransportCallbacks(TransportMessageHandler.MSG_REWIND); + } + + private void dispatchSeekTo(long pos) { + postToTransportCallbacks(TransportMessageHandler.MSG_SEEK_TO, pos); + } + + private void dispatchRate(Rating rating) { + postToTransportCallbacks(TransportMessageHandler.MSG_RATE, rating); + } + + private TransportMessageHandler getTransportControlsHandlerForCallbackLocked( + TransportControlsCallback callback) { + for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) { + TransportMessageHandler handler = mTransportCallbacks.get(i); + if (callback == handler.mCallback) { + return handler; + } + } + return null; + } + + private boolean removeTransportControlsCallbackLocked(TransportControlsCallback callback) { + for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) { + if (callback == mTransportCallbacks.get(i).mCallback) { + mTransportCallbacks.remove(i); + return true; + } + } + return false; + } + + private void postToTransportCallbacks(int what, Object obj) { + synchronized (mLock) { + for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) { + mTransportCallbacks.get(i).post(what, obj); + } + } + } + + private void postToTransportCallbacks(int what) { + postToTransportCallbacks(what, null); + } + + private CallbackMessageHandler getHandlerForCallbackLocked(Callback cb) { if (cb == null) { throw new IllegalArgumentException("Callback cannot be null"); } for (int i = mCallbacks.size() - 1; i >= 0; i--) { - MessageHandler handler = mCallbacks.get(i); + CallbackMessageHandler handler = mCallbacks.get(i); if (cb == handler.mCallback) { return handler; } @@ -450,7 +585,7 @@ public final class MediaSession { throw new IllegalArgumentException("Callback cannot be null"); } for (int i = mCallbacks.size() - 1; i >= 0; i--) { - MessageHandler handler = mCallbacks.get(i); + CallbackMessageHandler handler = mCallbacks.get(i); if (cb == handler.mCallback) { mCallbacks.remove(i); return true; @@ -463,7 +598,7 @@ public final class MediaSession { Command cmd = new Command(command, extras, resultCb); synchronized (mLock) { for (int i = mCallbacks.size() - 1; i >= 0; i--) { - mCallbacks.get(i).post(MSG_COMMAND, cmd); + mCallbacks.get(i).post(CallbackMessageHandler.MSG_COMMAND, cmd); } } } @@ -471,7 +606,7 @@ public final class MediaSession { private void postMediaButton(Intent mediaButtonIntent) { synchronized (mLock) { for (int i = mCallbacks.size() - 1; i >= 0; i--) { - mCallbacks.get(i).post(MSG_MEDIA_BUTTON, mediaButtonIntent); + mCallbacks.get(i).post(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent); } } } @@ -479,7 +614,7 @@ public final class MediaSession { private void postRequestRouteChange(RouteInfo route) { synchronized (mLock) { for (int i = mCallbacks.size() - 1; i >= 0; i--) { - mCallbacks.get(i).post(MSG_ROUTE_CHANGE, route); + mCallbacks.get(i).post(CallbackMessageHandler.MSG_ROUTE_CHANGE, route); } } } @@ -488,7 +623,7 @@ public final class MediaSession { synchronized (mLock) { mRoute = new Route(route, options, this); for (int i = mCallbacks.size() - 1; i >= 0; i--) { - mCallbacks.get(i).post(MSG_ROUTE_CONNECTED, mRoute); + mCallbacks.get(i).post(CallbackMessageHandler.MSG_ROUTE_CONNECTED, mRoute); } } } @@ -497,16 +632,16 @@ public final class MediaSession { synchronized (mLock) { if (mRoute != null && TextUtils.equals(mRoute.getRouteInfo().getId(), route.getId())) { for (int i = mCallbacks.size() - 1; i >= 0; i--) { - mCallbacks.get(i).post(MSG_ROUTE_DISCONNECTED, mRoute, reason); + mCallbacks.get(i).post(CallbackMessageHandler.MSG_ROUTE_DISCONNECTED, mRoute, + reason); } } } } /** - * Receives commands or updates from controllers and routes. An app can - * specify what commands and buttons it supports by setting them on the - * MediaSession. + * Receives generic commands or updates from controllers and the system. + * Callbacks may be registered using {@link #addCallback}. */ public abstract static class Callback { @@ -525,7 +660,7 @@ public final class MediaSession { * @param mediaButtonIntent an intent containing the KeyEvent as an * extra */ - public void onMediaButton(Intent mediaButtonIntent) { + public void onMediaButtonEvent(Intent mediaButtonIntent) { } /** @@ -536,7 +671,7 @@ public final class MediaSession { * @param command * @param extras optional */ - public void onCommand(String command, Bundle extras, ResultReceiver cb) { + public void onControlCommand(String command, Bundle extras, ResultReceiver cb) { } /** @@ -582,6 +717,82 @@ public final class MediaSession { } /** + * Receives transport control commands. Callbacks may be registered using + * {@link #addTransportControlsCallback}. + */ + public static abstract class TransportControlsCallback { + + /** + * Override to handle requests to begin playback. + */ + public void onPlay() { + } + + /** + * Override to handle requests to pause playback. + */ + public void onPause() { + } + + /** + * Override to handle requests to skip to the next media item. + */ + public void onSkipToNext() { + } + + /** + * Override to handle requests to skip to the previous media item. + */ + public void onSkipToPrevious() { + } + + /** + * Override to handle requests to fast forward. + */ + public void onFastForward() { + } + + /** + * Override to handle requests to rewind. + */ + public void onRewind() { + } + + /** + * Override to handle requests to stop playback. + */ + public void onStop() { + } + + /** + * Override to handle requests to seek to a specific position in ms. + * + * @param pos New position to move to, in milliseconds. + */ + public void onSeekTo(long pos) { + } + + /** + * Override to handle the item being rated. + * + * @param rating + */ + public void onSetRating(Rating rating) { + } + + /** + * Report that audio focus has changed on the app. This only happens if + * you have indicated you have started playing with + * {@link #setPlaybackState}. + * + * @param focusChange The type of focus change, TBD. + * @hide + */ + public void onRouteFocusChange(int focusChange) { + } + } + + /** * @hide */ public static class CallbackStub extends ISessionCallback.Stub { @@ -643,10 +854,7 @@ public final class MediaSession { public void onPlay() throws RemoteException { MediaSession session = mMediaSession.get(); if (session != null) { - TransportPerformer tp = session.getTransportPerformer(); - if (tp != null) { - tp.onPlay(); - } + session.dispatchPlay(); } } @@ -654,10 +862,7 @@ public final class MediaSession { public void onPause() throws RemoteException { MediaSession session = mMediaSession.get(); if (session != null) { - TransportPerformer tp = session.getTransportPerformer(); - if (tp != null) { - tp.onPause(); - } + session.dispatchPause(); } } @@ -665,10 +870,7 @@ public final class MediaSession { public void onStop() throws RemoteException { MediaSession session = mMediaSession.get(); if (session != null) { - TransportPerformer tp = session.getTransportPerformer(); - if (tp != null) { - tp.onStop(); - } + session.dispatchStop(); } } @@ -676,10 +878,7 @@ public final class MediaSession { public void onNext() throws RemoteException { MediaSession session = mMediaSession.get(); if (session != null) { - TransportPerformer tp = session.getTransportPerformer(); - if (tp != null) { - tp.onNext(); - } + session.dispatchNext(); } } @@ -687,10 +886,7 @@ public final class MediaSession { public void onPrevious() throws RemoteException { MediaSession session = mMediaSession.get(); if (session != null) { - TransportPerformer tp = session.getTransportPerformer(); - if (tp != null) { - tp.onPrevious(); - } + session.dispatchPrevious(); } } @@ -698,10 +894,7 @@ public final class MediaSession { public void onFastForward() throws RemoteException { MediaSession session = mMediaSession.get(); if (session != null) { - TransportPerformer tp = session.getTransportPerformer(); - if (tp != null) { - tp.onFastForward(); - } + session.dispatchFastForward(); } } @@ -709,10 +902,7 @@ public final class MediaSession { public void onRewind() throws RemoteException { MediaSession session = mMediaSession.get(); if (session != null) { - TransportPerformer tp = session.getTransportPerformer(); - if (tp != null) { - tp.onRewind(); - } + session.dispatchRewind(); } } @@ -720,10 +910,7 @@ public final class MediaSession { public void onSeekTo(long pos) throws RemoteException { MediaSession session = mMediaSession.get(); if (session != null) { - TransportPerformer tp = session.getTransportPerformer(); - if (tp != null) { - tp.onSeekTo(pos); - } + session.dispatchSeekTo(pos); } } @@ -731,10 +918,7 @@ public final class MediaSession { public void onRate(Rating rating) throws RemoteException { MediaSession session = mMediaSession.get(); if (session != null) { - TransportPerformer tp = session.getTransportPerformer(); - if (tp != null) { - tp.onRate(rating); - } + session.dispatchRate(rating); } } @@ -760,10 +944,16 @@ public final class MediaSession { } - private class MessageHandler extends Handler { + private class CallbackMessageHandler extends Handler { + private static final int MSG_MEDIA_BUTTON = 1; + private static final int MSG_COMMAND = 2; + private static final int MSG_ROUTE_CHANGE = 3; + private static final int MSG_ROUTE_CONNECTED = 4; + private static final int MSG_ROUTE_DISCONNECTED = 5; + private MediaSession.Callback mCallback; - public MessageHandler(Looper looper, MediaSession.Callback callback) { + public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) { super(looper, null, true); mCallback = callback; } @@ -776,11 +966,11 @@ public final class MediaSession { } switch (msg.what) { case MSG_MEDIA_BUTTON: - mCallback.onMediaButton((Intent) msg.obj); + mCallback.onMediaButtonEvent((Intent) msg.obj); break; case MSG_COMMAND: Command cmd = (Command) msg.obj; - mCallback.onCommand(cmd.command, cmd.extras, cmd.stub); + mCallback.onControlCommand(cmd.command, cmd.extras, cmd.stub); break; case MSG_ROUTE_CHANGE: mCallback.onRequestRouteChange((RouteInfo) msg.obj); @@ -815,4 +1005,64 @@ public final class MediaSession { this.stub = stub; } } + + private class TransportMessageHandler extends Handler { + private static final int MSG_PLAY = 1; + private static final int MSG_PAUSE = 2; + private static final int MSG_STOP = 3; + private static final int MSG_NEXT = 4; + private static final int MSG_PREVIOUS = 5; + private static final int MSG_FAST_FORWARD = 6; + private static final int MSG_REWIND = 7; + private static final int MSG_SEEK_TO = 8; + private static final int MSG_RATE = 9; + + private TransportControlsCallback mCallback; + + public TransportMessageHandler(Looper looper, TransportControlsCallback cb) { + super(looper); + mCallback = cb; + } + + public void post(int what, Object obj) { + obtainMessage(what, obj).sendToTarget(); + } + + public void post(int what) { + post(what, null); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_PLAY: + mCallback.onPlay(); + break; + case MSG_PAUSE: + mCallback.onPause(); + break; + case MSG_STOP: + mCallback.onStop(); + break; + case MSG_NEXT: + mCallback.onSkipToNext(); + break; + case MSG_PREVIOUS: + mCallback.onSkipToPrevious(); + break; + case MSG_FAST_FORWARD: + mCallback.onFastForward(); + break; + case MSG_REWIND: + mCallback.onRewind(); + break; + case MSG_SEEK_TO: + mCallback.onSeekTo((Long) msg.obj); + break; + case MSG_RATE: + mCallback.onSetRating((Rating) msg.obj); + break; + } + } + } } diff --git a/media/java/android/media/session/MediaSessionInfo.java b/media/java/android/media/session/MediaSessionInfo.java index 3d8d33f..f701211 100644 --- a/media/java/android/media/session/MediaSessionInfo.java +++ b/media/java/android/media/session/MediaSessionInfo.java @@ -20,6 +20,8 @@ import android.os.Parcelable; /** * Information about a media session, including the owner's package name. + * + * @hide */ public final class MediaSessionInfo implements Parcelable { private final String mId; diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java index 249b9c4..c303e77 100644 --- a/media/java/android/media/session/MediaSessionLegacyHelper.java +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -76,13 +76,13 @@ public class MediaSessionLegacyHelper { } } - public void addRccListener(PendingIntent pi, TransportPerformer.Listener listener) { + public void addRccListener(PendingIntent pi, + MediaSession.TransportControlsCallback listener) { if (pi == null) { Log.w(TAG, "Pending intent was null, can't add rcc listener."); return; } SessionHolder holder = getHolder(pi, true); - TransportPerformer performer = holder.mSession.getTransportPerformer(); if (holder.mRccListener != null) { if (holder.mRccListener == listener) { if (DEBUG) { @@ -92,9 +92,9 @@ public class MediaSessionLegacyHelper { return; } // Otherwise it changed so we need to switch to the new one - performer.removeListener(holder.mRccListener); + holder.mSession.removeTransportControlsCallback(holder.mRccListener); } - performer.addListener(listener, mHandler); + holder.mSession.addTransportControlsCallback(listener, mHandler); holder.mRccListener = listener; holder.mFlags |= MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS; holder.mSession.setFlags(holder.mFlags); @@ -110,7 +110,7 @@ public class MediaSessionLegacyHelper { } SessionHolder holder = getHolder(pi, false); if (holder != null && holder.mRccListener != null) { - holder.mSession.getTransportPerformer().removeListener(holder.mRccListener); + holder.mSession.removeTransportControlsCallback(holder.mRccListener); holder.mRccListener = null; holder.mFlags &= ~MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS; holder.mSession.setFlags(holder.mFlags); @@ -141,7 +141,7 @@ public class MediaSessionLegacyHelper { // set this flag holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS; holder.mSession.setFlags(holder.mFlags); - holder.mSession.getTransportPerformer().addListener(holder.mMediaButtonListener, mHandler); + holder.mSession.addTransportControlsCallback(holder.mMediaButtonListener, mHandler); holder.mMediaButtonReceiver = new MediaButtonReceiver(pi, context); holder.mSession.addCallback(holder.mMediaButtonReceiver, mHandler); @@ -156,7 +156,7 @@ public class MediaSessionLegacyHelper { } SessionHolder holder = getHolder(pi, false); if (holder != null && holder.mMediaButtonListener != null) { - holder.mSession.getTransportPerformer().removeListener(holder.mMediaButtonListener); + holder.mSession.removeTransportControlsCallback(holder.mMediaButtonListener); holder.mFlags &= ~MediaSession.FLAG_HANDLES_MEDIA_BUTTONS; holder.mSession.setFlags(holder.mFlags); holder.mMediaButtonListener = null; @@ -201,12 +201,12 @@ public class MediaSessionLegacyHelper { } @Override - public void onMediaButton(Intent mediaButtonIntent) { + public void onMediaButtonEvent(Intent mediaButtonIntent) { MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent); } } - private static final class MediaButtonListener extends TransportPerformer.Listener { + private static final class MediaButtonListener extends MediaSession.TransportControlsCallback { private final PendingIntent mPendingIntent; private final Context mContext; @@ -226,12 +226,12 @@ public class MediaSessionLegacyHelper { } @Override - public void onNext() { + public void onSkipToNext() { sendKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT); } @Override - public void onPrevious() { + public void onSkipToPrevious() { sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS); } @@ -272,7 +272,7 @@ public class MediaSessionLegacyHelper { public final PendingIntent mPi; public MediaButtonListener mMediaButtonListener; public MediaButtonReceiver mMediaButtonReceiver; - public TransportPerformer.Listener mRccListener; + public MediaSession.TransportControlsCallback mRccListener; public int mFlags; public SessionHolder(MediaSession session, PendingIntent pi) { diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 0589a7d..8d5e338 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -106,6 +106,7 @@ public final class MediaSessionManager { * @param notificationListener The enabled notification listener component. * May be null. * @return A list of controllers for ongoing sessions + * @hide */ public List<MediaController> getActiveSessions(ComponentName notificationListener) { return getActiveSessionsForUser(notificationListener, UserHandle.myUserId()); diff --git a/media/java/android/media/session/MediaSessionToken.java b/media/java/android/media/session/MediaSessionToken.java index f5569a4..86f5662 100644 --- a/media/java/android/media/session/MediaSessionToken.java +++ b/media/java/android/media/session/MediaSessionToken.java @@ -20,7 +20,12 @@ import android.media.session.ISessionController; import android.os.Parcel; import android.os.Parcelable; -public class MediaSessionToken implements Parcelable { +/** + * Represents an ongoing session. This may be passed to apps by the session + * owner to allow them to create a {@link MediaController} to communicate with + * the session. + */ +public final class MediaSessionToken implements Parcelable { private ISessionController mBinder; /** diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java index 7ef38eaa..e09ac3f 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/java/android/media/session/PlaybackState.java @@ -22,7 +22,7 @@ import android.os.SystemClock; /** * Playback state for a {@link MediaSession}. This includes a state like - * {@link PlaybackState#PLAYSTATE_PLAYING}, the current playback position, + * {@link PlaybackState#STATE_PLAYING}, the current playback position, * and the current control capabilities. */ public final class PlaybackState implements Parcelable { @@ -59,28 +59,28 @@ public final class PlaybackState implements Parcelable { * * @see #setActions */ - public static final long ACTION_PREVIOUS_ITEM = 1 << 4; + public static final long ACTION_SKIP_TO_PREVIOUS = 1 << 4; /** * Indicates this performer supports the next command. * * @see #setActions */ - public static final long ACTION_NEXT_ITEM = 1 << 5; + public static final long ACTION_SKIP_TO_NEXT = 1 << 5; /** * Indicates this performer supports the fast forward command. * * @see #setActions */ - public static final long ACTION_FASTFORWARD = 1 << 6; + public static final long ACTION_FAST_FORWARD = 1 << 6; /** * Indicates this performer supports the set rating command. * * @see #setActions */ - public static final long ACTION_RATING = 1 << 7; + public static final long ACTION_SET_RATING = 1 << 7; /** * Indicates this performer supports the seek to command. @@ -102,42 +102,42 @@ public final class PlaybackState implements Parcelable { * * @see #setState */ - public final static int PLAYSTATE_NONE = 0; + public final static int STATE_NONE = 0; /** * State indicating this item is currently stopped. * * @see #setState */ - public final static int PLAYSTATE_STOPPED = 1; + public final static int STATE_STOPPED = 1; /** * State indicating this item is currently paused. * * @see #setState */ - public final static int PLAYSTATE_PAUSED = 2; + public final static int STATE_PAUSED = 2; /** * State indicating this item is currently playing. * * @see #setState */ - public final static int PLAYSTATE_PLAYING = 3; + public final static int STATE_PLAYING = 3; /** * State indicating this item is currently fast forwarding. * * @see #setState */ - public final static int PLAYSTATE_FAST_FORWARDING = 4; + public final static int STATE_FAST_FORWARDING = 4; /** * State indicating this item is currently rewinding. * * @see #setState */ - public final static int PLAYSTATE_REWINDING = 5; + public final static int STATE_REWINDING = 5; /** * State indicating this item is currently buffering and will begin playing @@ -145,7 +145,7 @@ public final class PlaybackState implements Parcelable { * * @see #setState */ - public final static int PLAYSTATE_BUFFERING = 6; + public final static int STATE_BUFFERING = 6; /** * State indicating this item is currently in an error state. The error @@ -153,30 +153,30 @@ public final class PlaybackState implements Parcelable { * * @see #setState */ - public final static int PLAYSTATE_ERROR = 7; + public final static int STATE_ERROR = 7; /** * State indicating the class doing playback is currently connecting to a * route. Depending on the implementation you may return to the previous - * state when the connection finishes or enter {@link #PLAYSTATE_NONE}. If - * the connection failed {@link #PLAYSTATE_ERROR} should be used. + * state when the connection finishes or enter {@link #STATE_NONE}. If + * the connection failed {@link #STATE_ERROR} should be used. * @hide */ - public final static int PLAYSTATE_CONNECTING = 8; + public final static int STATE_CONNECTING = 8; /** * State indicating the player is currently skipping to the previous item. * * @see #setState */ - public final static int PLAYSTATE_SKIPPING_BACKWARDS = 9; + public final static int STATE_SKIPPING_TO_PREVIOUS = 9; /** * State indicating the player is currently skipping to the next item. * * @see #setState */ - public final static int PLAYSTATE_SKIPPING_FORWARDS = 10; + public final static int STATE_SKIPPING_TO_NEXT = 10; /** * Use this value for the position to indicate the position is not known. @@ -188,7 +188,7 @@ public final class PlaybackState implements Parcelable { private long mBufferPosition; private float mRate; private long mActions; - private String mErrorMessage; + private CharSequence mErrorMessage; private long mUpdateTime; /** @@ -221,7 +221,7 @@ public final class PlaybackState implements Parcelable { mUpdateTime = in.readLong(); mBufferPosition = in.readLong(); mActions = in.readLong(); - mErrorMessage = in.readString(); + mErrorMessage = in.readCharSequence(); } @@ -252,20 +252,20 @@ public final class PlaybackState implements Parcelable { dest.writeLong(mUpdateTime); dest.writeLong(mBufferPosition); dest.writeLong(mActions); - dest.writeString(mErrorMessage); + dest.writeCharSequence(mErrorMessage); } /** * Get the current state of playback. One of the following: * <ul> - * <li> {@link PlaybackState#PLAYSTATE_NONE}</li> - * <li> {@link PlaybackState#PLAYSTATE_STOPPED}</li> - * <li> {@link PlaybackState#PLAYSTATE_PLAYING}</li> - * <li> {@link PlaybackState#PLAYSTATE_PAUSED}</li> - * <li> {@link PlaybackState#PLAYSTATE_FAST_FORWARDING}</li> - * <li> {@link PlaybackState#PLAYSTATE_REWINDING}</li> - * <li> {@link PlaybackState#PLAYSTATE_BUFFERING}</li> - * <li> {@link PlaybackState#PLAYSTATE_ERROR}</li> + * <li> {@link PlaybackState#STATE_NONE}</li> + * <li> {@link PlaybackState#STATE_STOPPED}</li> + * <li> {@link PlaybackState#STATE_PLAYING}</li> + * <li> {@link PlaybackState#STATE_PAUSED}</li> + * <li> {@link PlaybackState#STATE_FAST_FORWARDING}</li> + * <li> {@link PlaybackState#STATE_REWINDING}</li> + * <li> {@link PlaybackState#STATE_BUFFERING}</li> + * <li> {@link PlaybackState#STATE_ERROR}</li> */ public int getState() { return mState; @@ -283,25 +283,25 @@ public final class PlaybackState implements Parcelable { * <p> * The state must be one of the following: * <ul> - * <li> {@link PlaybackState#PLAYSTATE_NONE}</li> - * <li> {@link PlaybackState#PLAYSTATE_STOPPED}</li> - * <li> {@link PlaybackState#PLAYSTATE_PLAYING}</li> - * <li> {@link PlaybackState#PLAYSTATE_PAUSED}</li> - * <li> {@link PlaybackState#PLAYSTATE_FAST_FORWARDING}</li> - * <li> {@link PlaybackState#PLAYSTATE_REWINDING}</li> - * <li> {@link PlaybackState#PLAYSTATE_BUFFERING}</li> - * <li> {@link PlaybackState#PLAYSTATE_ERROR}</li> + * <li> {@link PlaybackState#STATE_NONE}</li> + * <li> {@link PlaybackState#STATE_STOPPED}</li> + * <li> {@link PlaybackState#STATE_PLAYING}</li> + * <li> {@link PlaybackState#STATE_PAUSED}</li> + * <li> {@link PlaybackState#STATE_FAST_FORWARDING}</li> + * <li> {@link PlaybackState#STATE_REWINDING}</li> + * <li> {@link PlaybackState#STATE_BUFFERING}</li> + * <li> {@link PlaybackState#STATE_ERROR}</li> * </ul> * * @param state The current state of playback. * @param position The position in the current track in ms. - * @param rate The current rate of playback as a multiple of normal + * @param playbackRate The current rate of playback as a multiple of normal * playback. */ - public void setState(int state, long position, float rate) { + public void setState(int state, long position, float playbackRate) { this.mState = state; this.mPosition = position; - this.mRate = rate; + this.mRate = playbackRate; mUpdateTime = SystemClock.elapsedRealtime(); } @@ -337,7 +337,7 @@ public final class PlaybackState implements Parcelable { * * @return The current rate of playback. */ - public float getRate() { + public float getPlaybackRate() { return mRate; } @@ -345,15 +345,15 @@ public final class PlaybackState implements Parcelable { * Get the current actions available on this session. This should use a * bitmask of the available actions. * <ul> - * <li> {@link PlaybackState#ACTION_PREVIOUS_ITEM}</li> + * <li> {@link PlaybackState#ACTION_SKIP_TO_PREVIOUS}</li> * <li> {@link PlaybackState#ACTION_REWIND}</li> * <li> {@link PlaybackState#ACTION_PLAY}</li> * <li> {@link PlaybackState#ACTION_PAUSE}</li> * <li> {@link PlaybackState#ACTION_STOP}</li> - * <li> {@link PlaybackState#ACTION_FASTFORWARD}</li> - * <li> {@link PlaybackState#ACTION_NEXT_ITEM}</li> + * <li> {@link PlaybackState#ACTION_FAST_FORWARD}</li> + * <li> {@link PlaybackState#ACTION_SKIP_TO_NEXT}</li> * <li> {@link PlaybackState#ACTION_SEEK_TO}</li> - * <li> {@link PlaybackState#ACTION_RATING}</li> + * <li> {@link PlaybackState#ACTION_SET_RATING}</li> * </ul> */ public long getActions() { @@ -364,15 +364,15 @@ public final class PlaybackState implements Parcelable { * Set the current capabilities available on this session. This should use a * bitmask of the available capabilities. * <ul> - * <li> {@link PlaybackState#ACTION_PREVIOUS_ITEM}</li> + * <li> {@link PlaybackState#ACTION_SKIP_TO_PREVIOUS}</li> * <li> {@link PlaybackState#ACTION_REWIND}</li> * <li> {@link PlaybackState#ACTION_PLAY}</li> * <li> {@link PlaybackState#ACTION_PAUSE}</li> * <li> {@link PlaybackState#ACTION_STOP}</li> - * <li> {@link PlaybackState#ACTION_FASTFORWARD}</li> - * <li> {@link PlaybackState#ACTION_NEXT_ITEM}</li> + * <li> {@link PlaybackState#ACTION_FAST_FORWARD}</li> + * <li> {@link PlaybackState#ACTION_SKIP_TO_NEXT}</li> * <li> {@link PlaybackState#ACTION_SEEK_TO}</li> - * <li> {@link PlaybackState#ACTION_RATING}</li> + * <li> {@link PlaybackState#ACTION_SET_RATING}</li> * </ul> */ public void setActions(long capabilities) { @@ -381,9 +381,9 @@ public final class PlaybackState implements Parcelable { /** * Get a user readable error message. This should be set when the state is - * {@link PlaybackState#PLAYSTATE_ERROR}. + * {@link PlaybackState#STATE_ERROR}. */ - public String getErrorMessage() { + public CharSequence getErrorMessage() { return mErrorMessage; } @@ -400,9 +400,9 @@ public final class PlaybackState implements Parcelable { /** * Set a user readable error message. This should be set when the state is - * {@link PlaybackState#PLAYSTATE_ERROR}. + * {@link PlaybackState#STATE_ERROR}. */ - public void setErrorMessage(String errorMessage) { + public void setErrorMessage(CharSequence errorMessage) { mErrorMessage = errorMessage; } @@ -417,23 +417,23 @@ public final class PlaybackState implements Parcelable { public static int getStateFromRccState(int rccState) { switch (rccState) { case RemoteControlClient.PLAYSTATE_BUFFERING: - return PLAYSTATE_BUFFERING; + return STATE_BUFFERING; case RemoteControlClient.PLAYSTATE_ERROR: - return PLAYSTATE_ERROR; + return STATE_ERROR; case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: - return PLAYSTATE_FAST_FORWARDING; + return STATE_FAST_FORWARDING; case RemoteControlClient.PLAYSTATE_NONE: - return PLAYSTATE_NONE; + return STATE_NONE; case RemoteControlClient.PLAYSTATE_PAUSED: - return PLAYSTATE_PAUSED; + return STATE_PAUSED; case RemoteControlClient.PLAYSTATE_PLAYING: - return PLAYSTATE_PLAYING; + return STATE_PLAYING; case RemoteControlClient.PLAYSTATE_REWINDING: - return PLAYSTATE_REWINDING; + return STATE_REWINDING; case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: - return PLAYSTATE_SKIPPING_BACKWARDS; + return STATE_SKIPPING_TO_PREVIOUS; case RemoteControlClient.PLAYSTATE_STOPPED: - return PLAYSTATE_STOPPED; + return STATE_STOPPED; default: return -1; } @@ -457,7 +457,7 @@ public final class PlaybackState implements Parcelable { private static long getActionForRccFlag(int flag) { switch (flag) { case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS: - return ACTION_PREVIOUS_ITEM; + return ACTION_SKIP_TO_PREVIOUS; case RemoteControlClient.FLAG_KEY_MEDIA_REWIND: return ACTION_REWIND; case RemoteControlClient.FLAG_KEY_MEDIA_PLAY: @@ -469,13 +469,13 @@ public final class PlaybackState implements Parcelable { case RemoteControlClient.FLAG_KEY_MEDIA_STOP: return ACTION_STOP; case RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD: - return ACTION_FASTFORWARD; + return ACTION_FAST_FORWARD; case RemoteControlClient.FLAG_KEY_MEDIA_NEXT: - return ACTION_NEXT_ITEM; + return ACTION_SKIP_TO_NEXT; case RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE: return ACTION_SEEK_TO; case RemoteControlClient.FLAG_KEY_MEDIA_RATING: - return ACTION_RATING; + return ACTION_SET_RATING; } return 0; } diff --git a/media/java/android/media/session/RemoteVolumeProvider.java b/media/java/android/media/session/RemoteVolumeProvider.java index 9526cc8..47f672f 100644 --- a/media/java/android/media/session/RemoteVolumeProvider.java +++ b/media/java/android/media/session/RemoteVolumeProvider.java @@ -1,35 +1,61 @@ +/* + * 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.media.session; /** * Handles requests to adjust or set the volume on a session. This is also used * to push volume updates back to the session after a request has been handled. * You can set a volume provider on a session by calling - * {@link MediaSession#useRemotePlayback}. + * {@link MediaSession#setPlaybackToRemote}. */ public abstract class RemoteVolumeProvider { /** - * Handles relative volume changes via {@link #onAdjustVolume(int)}. + * The volume is fixed and can not be modified. Requests to change volume + * should be ignored. + */ + public static final int VOLUME_CONTROL_FIXED = 0; + + /** + * The volume control uses relative adjustment via + * {@link #onAdjustVolumeBy(int)}. Attempts to set the volume to a specific + * value should be ignored. */ - public static final int FLAG_VOLUME_RELATIVE = 1 << 0; + public static final int VOLUME_CONTROL_RELATIVE = 1; /** - * Handles setting the volume via {@link #onSetVolume(int)}. + * The volume control uses an absolute value. It may be adjusted using + * {@link #onAdjustVolumeBy(int)} or set directly using + * {@link #onSetVolumeTo(int)}. */ - public static final int FLAG_VOLUME_ABSOLUTE = 1 << 1; + public static final int VOLUME_CONTROL_ABSOLUTE = 2; - private final int mFlags; + private final int mControlType; private final int mMaxVolume; /** * Create a new volume provider for handling volume events. You must specify - * the type of events and the maximum volume that can be used. + * the type of volume control and the maximum volume that can be used. * - * @param flags The flags to use with this provider. + * @param volumeControl The method for controlling volume that is used by + * this provider. * @param maxVolume The maximum allowed volume. */ - public RemoteVolumeProvider(int flags, int maxVolume) { - mFlags = flags; + public RemoteVolumeProvider(int volumeControl, int maxVolume) { + mControlType = volumeControl; mMaxVolume = maxVolume; } @@ -38,15 +64,15 @@ public abstract class RemoteVolumeProvider { * * @return The current volume. */ - public abstract int getCurrentVolume(); + public abstract int onGetCurrentVolume(); /** - * Get the flags that were set for this volume provider. + * Get the volume control type that this volume provider uses. * - * @return The flags for this volume provider + * @return The volume control type for this volume provider */ - public final int getFlags() { - return mFlags; + public final int getVolumeControl() { + return mControlType; } /** @@ -59,7 +85,7 @@ public abstract class RemoteVolumeProvider { } /** - * Notify the system that the remove playback's volume has been changed. + * Notify the system that the remote playback's volume has been changed. */ public final void notifyVolumeChanged() { // TODO @@ -70,7 +96,7 @@ public abstract class RemoteVolumeProvider { * * @param volume The volume to set the output to. */ - public void onSetVolume(int volume) { + public void onSetVolumeTo(int volume) { } /** @@ -79,6 +105,6 @@ public abstract class RemoteVolumeProvider { * * @param delta The amount to change the volume */ - public void onAdjustVolume(int delta) { + public void onAdjustVolumeBy(int delta) { } }
\ No newline at end of file diff --git a/media/java/android/media/session/TransportController.java b/media/java/android/media/session/TransportController.java deleted file mode 100644 index 090489b..0000000 --- a/media/java/android/media/session/TransportController.java +++ /dev/null @@ -1,343 +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.media.session; - -import android.media.MediaMetadata; -import android.media.Rating; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.util.Log; - -import java.util.ArrayList; - -/** - * Interface for controlling media playback on a session. This allows an app to - * request changes in playback, retrieve the current playback state and - * metadata, and listen for changes to the playback state and metadata. - */ -public final class TransportController { - private static final String TAG = "TransportController"; - - private final Object mLock = new Object(); - private final ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>(); - private final ISessionController mBinder; - - /** - * @hide - */ - public TransportController(ISessionController binder) { - mBinder = binder; - } - - /** - * Start listening to changes in playback state. - */ - public void addStateListener(TransportStateListener listener) { - addStateListener(listener, null); - } - - public void addStateListener(TransportStateListener listener, Handler handler) { - if (listener == null) { - throw new IllegalArgumentException("Listener cannot be null"); - } - synchronized (mLock) { - if (getHandlerForListenerLocked(listener) != null) { - Log.w(TAG, "Listener is already added, ignoring"); - return; - } - if (handler == null) { - handler = new Handler(); - } - - MessageHandler msgHandler = new MessageHandler(handler.getLooper(), listener); - mListeners.add(msgHandler); - } - } - - /** - * Stop listening to changes in playback state. - */ - public void removeStateListener(TransportStateListener listener) { - if (listener == null) { - throw new IllegalArgumentException("Listener cannot be null"); - } - synchronized (mLock) { - removeStateListenerLocked(listener); - } - } - - /** - * Request that the player start its playback at its current position. - */ - public void play() { - try { - mBinder.play(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling play.", e); - } - } - - /** - * Request that the player pause its playback and stay at its current - * position. - */ - public void pause() { - try { - mBinder.pause(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling pause.", e); - } - } - - /** - * Request that the player stop its playback; it may clear its state in - * whatever way is appropriate. - */ - public void stop() { - try { - mBinder.stop(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling stop.", e); - } - } - - /** - * Move to a new location in the media stream. - * - * @param pos Position to move to, in milliseconds. - */ - public void seekTo(long pos) { - try { - mBinder.seekTo(pos); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling seekTo.", e); - } - } - - /** - * Start fast forwarding. If playback is already fast forwarding this may - * increase the rate. - */ - public void fastForward() { - try { - mBinder.fastForward(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling fastForward.", e); - } - } - - /** - * Skip to the next item. - */ - public void next() { - try { - mBinder.next(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling next.", e); - } - } - - /** - * Start rewinding. If playback is already rewinding this may increase the - * rate. - */ - public void rewind() { - try { - mBinder.rewind(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling rewind.", e); - } - } - - /** - * Skip to the previous item. - */ - public void previous() { - try { - mBinder.previous(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling previous.", e); - } - } - - /** - * Rate the current content. This will cause the rating to be set for the - * current user. The Rating type must match the type returned by - * {@link #getRatingType()}. - * - * @param rating The rating to set for the current content - */ - public void rate(Rating rating) { - try { - mBinder.rate(rating); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling rate.", e); - } - } - - /** - * Get the rating type supported by the session. One of: - * <ul> - * <li>{@link Rating#RATING_NONE}</li> - * <li>{@link Rating#RATING_HEART}</li> - * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li> - * <li>{@link Rating#RATING_3_STARS}</li> - * <li>{@link Rating#RATING_4_STARS}</li> - * <li>{@link Rating#RATING_5_STARS}</li> - * <li>{@link Rating#RATING_PERCENTAGE}</li> - * </ul> - * - * @return The supported rating type - */ - public int getRatingType() { - try { - return mBinder.getRatingType(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getRatingType.", e); - return Rating.RATING_NONE; - } - } - - /** - * Get the current playback state for this session. - * - * @return The current PlaybackState or null - */ - public PlaybackState getPlaybackState() { - try { - return mBinder.getPlaybackState(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getPlaybackState.", e); - return null; - } - } - - /** - * Get the current metadata for this session. - * - * @return The current MediaMetadata or null. - */ - public MediaMetadata getMetadata() { - try { - return mBinder.getMetadata(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getMetadata.", e); - return null; - } - } - - /** - * @hide - */ - public final void postPlaybackStateChanged(PlaybackState state) { - synchronized (mLock) { - for (int i = mListeners.size() - 1; i >= 0; i--) { - mListeners.get(i).post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state); - } - } - } - - /** - * @hide - */ - public final void postMetadataChanged(MediaMetadata metadata) { - synchronized (mLock) { - for (int i = mListeners.size() - 1; i >= 0; i--) { - mListeners.get(i).post(MessageHandler.MSG_UPDATE_METADATA, - metadata); - } - } - } - - private MessageHandler getHandlerForListenerLocked(TransportStateListener listener) { - for (int i = mListeners.size() - 1; i >= 0; i--) { - MessageHandler handler = mListeners.get(i); - if (listener == handler.mListener) { - return handler; - } - } - return null; - } - - private boolean removeStateListenerLocked(TransportStateListener listener) { - for (int i = mListeners.size() - 1; i >= 0; i--) { - if (listener == mListeners.get(i).mListener) { - mListeners.remove(i); - return true; - } - } - return false; - } - - /** - * Register using {@link #addStateListener} to receive updates when there - * are playback changes on the session. - */ - public static abstract class TransportStateListener { - private MessageHandler mHandler; - /** - * Override to handle changes in playback state. - * - * @param state The new playback state of the session - */ - public void onPlaybackStateChanged(PlaybackState state) { - } - - /** - * Override to handle changes to the current metadata. - * - * @see MediaMetadata - * @param metadata The current metadata for the session or null - */ - public void onMetadataChanged(MediaMetadata metadata) { - } - - private void setHandler(Handler handler) { - mHandler = new MessageHandler(handler.getLooper(), this); - } - } - - private static class MessageHandler extends Handler { - private static final int MSG_UPDATE_PLAYBACK_STATE = 1; - private static final int MSG_UPDATE_METADATA = 2; - - private TransportStateListener mListener; - - public MessageHandler(Looper looper, TransportStateListener cb) { - super(looper, null, true); - mListener = cb; - } - - public void post(int msg, Object obj) { - obtainMessage(msg, obj).sendToTarget(); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_PLAYBACK_STATE: - mListener.onPlaybackStateChanged((PlaybackState) msg.obj); - break; - case MSG_UPDATE_METADATA: - mListener.onMetadataChanged((MediaMetadata) msg.obj); - break; - } - } - } - -} diff --git a/media/java/android/media/session/TransportPerformer.java b/media/java/android/media/session/TransportPerformer.java deleted file mode 100644 index 1588d8f..0000000 --- a/media/java/android/media/session/TransportPerformer.java +++ /dev/null @@ -1,351 +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.media.session; - -import android.media.AudioManager; -import android.media.MediaMetadata; -import android.media.Rating; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.util.Log; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; - -/** - * Allows broadcasting of playback changes. - */ -public final class TransportPerformer { - private static final String TAG = "TransportPerformer"; - private final Object mLock = new Object(); - private final ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>(); - - private ISession mBinder; - - /** - * @hide - */ - public TransportPerformer(ISession binder) { - mBinder = binder; - } - - /** - * Add a listener to receive updates on. - * - * @param listener The callback object - */ - public void addListener(Listener listener) { - addListener(listener, null); - } - - /** - * Add a listener to receive updates on. The updates will be posted to the - * specified handler. If no handler is provided they will be posted to the - * caller's thread. - * - * @param listener The listener to receive updates on - * @param handler The handler to post the updates on - */ - public void addListener(Listener listener, Handler handler) { - if (listener == null) { - throw new IllegalArgumentException("Listener cannot be null"); - } - synchronized (mLock) { - if (getHandlerForListenerLocked(listener) != null) { - Log.w(TAG, "Listener is already added, ignoring"); - } - if (handler == null) { - handler = new Handler(); - } - MessageHandler msgHandler = new MessageHandler(handler.getLooper(), listener); - mListeners.add(msgHandler); - } - } - - /** - * Stop receiving updates on the specified handler. If an update has already - * been posted you may still receive it after this call returns. - * - * @param listener The listener to stop receiving updates on - */ - public void removeListener(Listener listener) { - if (listener == null) { - throw new IllegalArgumentException("Listener cannot be null"); - } - synchronized (mLock) { - removeListenerLocked(listener); - } - } - - /** - * Update the current playback state. - * - * @param state The current state of playback - */ - public final void setPlaybackState(PlaybackState state) { - try { - mBinder.setPlaybackState(state); - } catch (RemoteException e) { - Log.wtf(TAG, "Dead object in setPlaybackState.", e); - } - } - - /** - * Update the current metadata. New metadata can be created using - * {@link MediaMetadata.Builder}. - * - * @param metadata The new metadata - */ - public final void setMetadata(MediaMetadata metadata) { - try { - mBinder.setMetadata(metadata); - } catch (RemoteException e) { - Log.wtf(TAG, "Dead object in setPlaybackState.", e); - } - } - - /** - * @hide - */ - public final void onPlay() { - post(MessageHandler.MESSAGE_PLAY); - } - - /** - * @hide - */ - public final void onPause() { - post(MessageHandler.MESSAGE_PAUSE); - } - - /** - * @hide - */ - public final void onStop() { - post(MessageHandler.MESSAGE_STOP); - } - - /** - * @hide - */ - public final void onNext() { - post(MessageHandler.MESSAGE_NEXT); - } - - /** - * @hide - */ - public final void onPrevious() { - post(MessageHandler.MESSAGE_PREVIOUS); - } - - /** - * @hide - */ - public final void onFastForward() { - post(MessageHandler.MESSAGE_FAST_FORWARD); - } - - /** - * @hide - */ - public final void onRewind() { - post(MessageHandler.MESSAGE_REWIND); - } - - /** - * @hide - */ - public final void onSeekTo(long pos) { - post(MessageHandler.MESSAGE_SEEK_TO, pos); - } - - /** - * @hide - */ - public final void onRate(Rating rating) { - post(MessageHandler.MESSAGE_RATE, rating); - } - - private MessageHandler getHandlerForListenerLocked(Listener listener) { - for (int i = mListeners.size() - 1; i >= 0; i--) { - MessageHandler handler = mListeners.get(i); - if (listener == handler.mListener) { - return handler; - } - } - return null; - } - - private boolean removeListenerLocked(Listener listener) { - for (int i = mListeners.size() - 1; i >= 0; i--) { - if (listener == mListeners.get(i).mListener) { - mListeners.remove(i); - return true; - } - } - return false; - } - - private void post(int what, Object obj) { - synchronized (mLock) { - for (int i = mListeners.size() - 1; i >= 0; i--) { - mListeners.get(i).post(what, obj); - } - } - } - - private void post(int what) { - post(what, null); - } - - /** - * Extend Listener to handle transport controls. Listeners can be registered - * using {@link #addListener}. - */ - public static abstract class Listener { - - /** - * Override to handle requests to begin playback. - */ - public void onPlay() { - } - - /** - * Override to handle requests to pause playback. - */ - public void onPause() { - } - - /** - * Override to handle requests to skip to the next media item. - */ - public void onNext() { - } - - /** - * Override to handle requests to skip to the previous media item. - */ - public void onPrevious() { - } - - /** - * Override to handle requests to fast forward. - */ - public void onFastForward() { - } - - /** - * Override to handle requests to rewind. - */ - public void onRewind() { - } - - /** - * Override to handle requests to stop playback. - */ - public void onStop() { - } - - /** - * Override to handle requests to seek to a specific position in ms. - * - * @param pos New position to move to, in milliseconds. - */ - public void onSeekTo(long pos) { - } - - /** - * Override to handle the item being rated. - * - * @param rating - */ - public void onRate(Rating rating) { - } - - /** - * Report that audio focus has changed on the app. This only happens if - * you have indicated you have started playing with - * {@link #setPlaybackState}. - * - * @param focusChange The type of focus change, TBD. - */ - public void onRouteFocusChange(int focusChange) { - } - } - - private class MessageHandler extends Handler { - private static final int MESSAGE_PLAY = 1; - private static final int MESSAGE_PAUSE = 2; - private static final int MESSAGE_STOP = 3; - private static final int MESSAGE_NEXT = 4; - private static final int MESSAGE_PREVIOUS = 5; - private static final int MESSAGE_FAST_FORWARD = 6; - private static final int MESSAGE_REWIND = 7; - private static final int MESSAGE_SEEK_TO = 8; - private static final int MESSAGE_RATE = 9; - - private Listener mListener; - - public MessageHandler(Looper looper, Listener cb) { - super(looper); - mListener = cb; - } - - public void post(int what, Object obj) { - obtainMessage(what, obj).sendToTarget(); - } - - public void post(int what) { - post(what, null); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_PLAY: - mListener.onPlay(); - break; - case MESSAGE_PAUSE: - mListener.onPause(); - break; - case MESSAGE_STOP: - mListener.onStop(); - break; - case MESSAGE_NEXT: - mListener.onNext(); - break; - case MESSAGE_PREVIOUS: - mListener.onPrevious(); - break; - case MESSAGE_FAST_FORWARD: - mListener.onFastForward(); - break; - case MESSAGE_REWIND: - mListener.onRewind(); - break; - case MESSAGE_SEEK_TO: - mListener.onSeekTo((Long) msg.obj); - break; - case MESSAGE_RATE: - mListener.onRate((Rating) msg.obj); - break; - } - } - } -} diff --git a/media/lib/signer/Android.mk b/media/lib/signer/Android.mk index bca643a..b0d3177 100644 --- a/media/lib/signer/Android.mk +++ b/media/lib/signer/Android.mk @@ -23,7 +23,8 @@ LOCAL_MODULE:= com.android.mediadrm.signer LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := \ - $(call all-java-files-under, java) + $(call all-java-files-under, java) \ + $(call all-aidl-files-under, java) include $(BUILD_JAVA_LIBRARY) diff --git a/media/lib/signer/com.android.mediadrm.signer.xml b/media/lib/signer/com.android.mediadrm.signer.xml index b5b1f09..fd3a115 100644 --- a/media/lib/signer/com.android.mediadrm.signer.xml +++ b/media/lib/signer/com.android.mediadrm.signer.xml @@ -15,6 +15,6 @@ --> <permissions> - <library name="com.android.media.drm.signer" - file="/system/framework/com.android.media.drm.signer.jar" /> + <library name="com.android.mediadrm.signer" + file="/system/framework/com.android.mediadrm.signer.jar" /> </permissions> 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 fe51215..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", @@ -1032,11 +1049,14 @@ public class CameraMetadataTest extends junit.framework.TestCase { float[] redOut = mMetadata.get(CaptureResult.TONEMAP_CURVE_RED); float[] greenOut = mMetadata.get(CaptureResult.TONEMAP_CURVE_GREEN); float[] blueOut = mMetadata.get(CaptureResult.TONEMAP_CURVE_BLUE); - assertTrue("Input and output tonemap curve should match", Arrays.equals(red, redOut)); - assertTrue("Input and output tonemap curve should match", Arrays.equals(green, greenOut)); - assertTrue("Input and output tonemap curve should match", Arrays.equals(blue, blueOut)); + assertArrayEquals(red, redOut); + assertArrayEquals(green, greenOut); + assertArrayEquals(blue, blueOut); TonemapCurve tcOut = mMetadata.get(CaptureResult.TONEMAP_CURVE); - assertTrue("Input and output tonemap curve should match", tcIn.equals(tcOut)); + assertEquals(tcIn, tcOut); + mMetadata.set(CaptureResult.TONEMAP_CURVE_GREEN, null); + // If any of channel has null curve, return a null TonemapCurve + assertNull(mMetadata.get(CaptureResult.TONEMAP_CURVE)); } /** 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/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index bf97fc0..0e025a9 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -192,4 +192,7 @@ <!-- Default for Settings.Global.DEVICE_NAME $1=BRAND $2=MODEL--> <string name="def_device_name">%1$s %2$s</string> + <!-- Default for Settings.Secure.WAKE_GESTURE_ENABLED --> + <bool name="def_wake_gesture_enabled">true</bool> + </resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index 286921e..c4a54b7 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -70,7 +70,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion' // is properly propagated through your change. Not doing so will result in a loss of user // settings. - private static final int DATABASE_VERSION = 103; + private static final int DATABASE_VERSION = 104; private Context mContext; private int mUserHandle; @@ -1660,6 +1660,23 @@ public class DatabaseHelper extends SQLiteOpenHelper { } upgradeVersion = 103; } + + if (upgradeVersion == 103) { + db.beginTransaction(); + SQLiteStatement stmt = null; + try { + stmt = db.compileStatement("INSERT OR REPLACE INTO secure(name,value)" + + " VALUES(?,?);"); + loadBooleanSetting(stmt, Settings.Secure.WAKE_GESTURE_ENABLED, + R.bool.def_wake_gesture_enabled); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + if (stmt != null) stmt.close(); + } + upgradeVersion = 104; + } + // *** Remember to update DATABASE_VERSION above! if (upgradeVersion != currentVersion) { @@ -2222,6 +2239,9 @@ public class DatabaseHelper extends SQLiteOpenHelper { loadBooleanSetting(stmt, Settings.Secure.INSTALL_NON_MARKET_APPS, R.bool.def_install_non_market_apps); + loadBooleanSetting(stmt, Settings.Secure.WAKE_GESTURE_ENABLED, + R.bool.def_wake_gesture_enabled); + } finally { if (stmt != null) stmt.close(); } diff --git a/packages/SystemUI/res/drawable/heads_up_scrim.xml b/packages/SystemUI/res/drawable/heads_up_scrim.xml new file mode 100644 index 0000000..59000fc --- /dev/null +++ b/packages/SystemUI/res/drawable/heads_up_scrim.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2014 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <gradient + android:type="linear" + android:angle="-90" + android:startColor="#55000000" + android:endColor="#00000000" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_qs_airplane_off.xml b/packages/SystemUI/res/drawable/ic_qs_airplane_off.xml index 9f0ec67..c68238f 100644 --- a/packages/SystemUI/res/drawable/ic_qs_airplane_off.xml +++ b/packages/SystemUI/res/drawable/ic_qs_airplane_off.xml @@ -19,17 +19,10 @@ Copyright (C) 2014 The Android Open Source Project android:height="64dp"/> <viewport - android:viewportWidth="24.0" - android:viewportHeight="24.0"/> + android:viewportWidth="48.0" + android:viewportHeight="48.0"/> <path - android:fill="#00000000" - android:stroke="#CCCCCC" - android:strokeWidth="1.0" - android:pathData="M10.2,9.0"/> - <path - android:fill="#00000000" - android:stroke="#CCCCCC" - android:strokeWidth="1.0" - android:pathData="M21.0,16.0l0.0,-2.0l-8.0,-5.0L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5L10.0,9.0l-8.0,5.0l0.0,2.0l8.0,-2.5L10.0,19.0l-2.0,1.5L8.0,22.0l3.5,-1.0l3.5,1.0l0.0,-1.5L13.0,19.0l0.0,-5.5L21.0,16.0z"/> + android:fill="#4DFFFFFF" + android:pathData="M26.0,18.0L26.0,7.0c0.0,-1.7 -1.3,-3.0 -3.0,-3.0c-1.7,0.0 -3.0,1.3 -3.0,3.0l0.0,7.4L35.7,30.0l6.3,2.0l0.0,-4.0L26.0,18.0zM6.0,10.5l10.0,10.0L4.0,28.0l0.0,4.0l16.0,-5.0l0.0,11.0l-4.0,3.0l0.0,3.0l7.0,-2.0l7.0,2.0l0.0,-3.0l-4.0,-3.0l0.0,-7.5L37.5,42.0l2.5,-2.5L8.5,8.0L6.0,10.5z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_airplane_on.xml b/packages/SystemUI/res/drawable/ic_qs_airplane_on.xml index 95c20bb..c1e3c7e 100644 --- a/packages/SystemUI/res/drawable/ic_qs_airplane_on.xml +++ b/packages/SystemUI/res/drawable/ic_qs_airplane_on.xml @@ -19,13 +19,13 @@ Copyright (C) 2014 The Android Open Source Project android:height="64dp"/> <viewport - android:viewportWidth="24.0" - android:viewportHeight="24.0"/> + android:viewportWidth="48.0" + android:viewportHeight="48.0"/> <path android:fill="#FFFFFFFF" - android:pathData="M10.2,9.0"/> + android:pathData="M20.4,18.0"/> <path android:fill="#FFFFFFFF" - android:pathData="M21.0,16.0l0.0,-2.0l-8.0,-5.0L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5L10.0,9.0l-8.0,5.0l0.0,2.0l8.0,-2.5L10.0,19.0l-2.0,1.5L8.0,22.0l3.5,-1.0l3.5,1.0l0.0,-1.5L13.0,19.0l0.0,-5.5L21.0,16.0z"/> + android:pathData="M42.0,32.0l0.0,-4.0L26.0,18.0L26.0,7.0c0.0,-1.7 -1.3,-3.0 -3.0,-3.0c-1.7,0.0 -3.0,1.3 -3.0,3.0l0.0,11.0L4.0,28.0l0.0,4.0l16.0,-5.0l0.0,11.0l-4.0,3.0l0.0,3.0l7.0,-2.0l7.0,2.0l0.0,-3.0l-4.0,-3.0L26.0,27.0L42.0,32.0z"/> </vector> 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_bluetooth_connected.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml index 61a7777..3957d02 100644 --- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml +++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml @@ -19,10 +19,10 @@ Copyright (C) 2014 The Android Open Source Project android:height="64dp"/> <viewport - android:viewportWidth="24.0" - android:viewportHeight="24.0"/> + android:viewportWidth="48.0" + android:viewportHeight="48.0"/> <path android:fill="#FFFFFFFF" - android:pathData="M17.7,7.7L12.0,2.0l-1.0,0.0l0.0,7.6L6.4,5.0L5.0,6.4l5.6,5.6L5.0,17.6L6.4,19.0l4.6,-4.6L11.0,22.0l1.0,0.0l5.7,-5.7L13.4,12.0L17.7,7.7zM13.0,5.8l1.9,1.9L13.0,9.6L13.0,5.8zM14.9,16.3L13.0,18.2l0.0,-3.8L14.9,16.3z"/> + android:pathData="M14.0,24.0l-4.0,-4.0l-4.0,4.0l4.0,4.0L14.0,24.0zM35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6zM38.0,20.0l-4.0,4.0l4.0,4.0l4.0,-4.0L38.0,20.0z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_zen_off.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.xml index 73886ec..e4038f9 100644 --- a/packages/SystemUI/res/drawable/ic_qs_zen_off.xml +++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.xml @@ -19,12 +19,10 @@ Copyright (C) 2014 The Android Open Source Project android:height="64dp"/> <viewport - android:viewportWidth="24.0" - android:viewportHeight="24.0"/> + android:viewportWidth="48.0" + android:viewportHeight="48.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="#FFFFFFFF" + android:pathData="M28.5,24.0l4.6,4.6c0.6,-1.4 0.9,-3.0 0.9,-4.7c0.0,-1.6 -0.3,-3.2 -0.9,-4.6L28.5,24.0zM39.1,13.4L36.5,16.0c1.3,2.4 2.0,5.1 2.0,8.0s-0.7,5.6 -2.0,8.0l2.4,2.4c1.9,-3.1 3.1,-6.7 3.1,-10.6C42.0,20.0 40.9,16.5 39.1,13.4zM31.4,15.4L20.0,4.0l-2.0,0.0l0.0,15.2L8.8,10.0L6.0,12.8L17.2,24.0L6.0,35.2L8.8,38.0l9.2,-9.2L18.0,44.0l2.0,0.0l11.4,-11.4L22.8,24.0L31.4,15.4zM22.0,11.7l3.8,3.8L22.0,19.2L22.0,11.7zM25.8,32.6L22.0,36.3l0.0,-7.5L25.8,32.6z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml index 7ac1cb9..00c5af8 100644 --- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml +++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml @@ -19,12 +19,10 @@ Copyright (C) 2014 The Android Open Source Project android:height="64dp"/> <viewport - android:viewportWidth="24.0" - android:viewportHeight="24.0"/> + android:viewportWidth="48.0" + android:viewportHeight="48.0"/> <path - android:fill="#00000000" - android:stroke="#CCCCCC" - android:strokeWidth="1.0" - android:pathData="M17.7,7.7L12.0,2.0l-1.0,0.0l0.0,7.6L6.4,5.0L5.0,6.4l5.6,5.6L5.0,17.6L6.4,19.0l4.6,-4.6L11.0,22.0l1.0,0.0l5.7,-5.7L13.4,12.0L17.7,7.7zM13.0,5.8l1.9,1.9L13.0,9.6L13.0,5.8zM14.9,16.3L13.0,18.2l0.0,-3.8L14.9,16.3z"/> + android:fill="#4DFFFFFF" + android:pathData="M26.0,11.8l3.8,3.8l-3.2,3.2l2.8,2.8l6.0,-6.0L24.0,4.2l-2.0,0.0l0.0,10.1l4.0,4.0L26.0,11.8zM10.8,8.2L8.0,11.0l13.2,13.2L10.0,35.3l2.8,2.8L22.0,29.0l0.0,15.2l2.0,0.0l8.6,-8.6l4.6,4.6l2.8,-2.8L10.8,8.2zM26.0,36.5L26.0,29.0l3.8,3.8L26.0,36.5z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml index 61a7777..2b14f33 100644 --- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml +++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml @@ -19,10 +19,10 @@ Copyright (C) 2014 The Android Open Source Project android:height="64dp"/> <viewport - android:viewportWidth="24.0" - android:viewportHeight="24.0"/> + android:viewportWidth="48.0" + android:viewportHeight="48.0"/> <path android:fill="#FFFFFFFF" - android:pathData="M17.7,7.7L12.0,2.0l-1.0,0.0l0.0,7.6L6.4,5.0L5.0,6.4l5.6,5.6L5.0,17.6L6.4,19.0l4.6,-4.6L11.0,22.0l1.0,0.0l5.7,-5.7L13.4,12.0L17.7,7.7zM13.0,5.8l1.9,1.9L13.0,9.6L13.0,5.8zM14.9,16.3L13.0,18.2l0.0,-3.8L14.9,16.3z"/> + android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_off.xml b/packages/SystemUI/res/drawable/ic_qs_cast_off.xml index 130c639..2a9541e 100644 --- a/packages/SystemUI/res/drawable/ic_qs_cast_off.xml +++ b/packages/SystemUI/res/drawable/ic_qs_cast_off.xml @@ -19,12 +19,10 @@ Copyright (C) 2014 The Android Open Source Project android:height="64dp"/> <viewport - android:viewportWidth="24.0" - android:viewportHeight="24.0"/> + android:viewportWidth="48.0" + android:viewportHeight="48.0"/> <path - android:fill="#00000000" - android:stroke="#CCCCCC" - android:strokeWidth="1.0" - android:pathData="M21.0,3.0L3.0,3.0C1.9,3.0 1.0,3.9 1.0,5.0l0.0,3.0l2.0,0.0L3.0,5.0l18.0,0.0l0.0,14.0l-7.0,0.0l0.0,2.0l7.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L23.0,5.0C23.0,3.9 22.1,3.0 21.0,3.0zM1.0,18.0l0.0,3.0l3.0,0.0C4.0,19.3 2.7,18.0 1.0,18.0zM1.0,14.0l0.0,2.0c2.8,0.0 5.0,2.2 5.0,5.0l2.0,0.0C8.0,17.1 4.9,14.0 1.0,14.0zM1.0,10.0l0.0,2.0c5.0,0.0 9.0,4.0 9.0,9.0l2.0,0.0C12.0,14.9 7.1,10.0 1.0,10.0z"/> + android:fill="#4DFFFFFF" + android:pathData="M42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0zM2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml index 6c82b1c..8dacdc9 100644 --- a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml +++ b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml @@ -19,10 +19,10 @@ Copyright (C) 2014 The Android Open Source Project android:height="64dp"/> <viewport - android:viewportWidth="24.0" - android:viewportHeight="24.0"/> + android:viewportWidth="48.0" + android:viewportHeight="48.0"/> <path android:fill="#FFFFFFFF" - android:pathData="M21.0,3.0L3.0,3.0C1.9,3.0 1.0,3.9 1.0,5.0l0.0,3.0l2.0,0.0L3.0,5.0l18.0,0.0l0.0,14.0l-7.0,0.0l0.0,2.0l7.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L23.0,5.0C23.0,3.9 22.1,3.0 21.0,3.0zM1.0,18.0l0.0,3.0l3.0,0.0C4.0,19.3 2.7,18.0 1.0,18.0zM1.0,14.0l0.0,2.0c2.8,0.0 5.0,2.2 5.0,5.0l2.0,0.0C8.0,17.1 4.9,14.0 1.0,14.0zM1.0,10.0l0.0,2.0c5.0,0.0 9.0,4.0 9.0,9.0l2.0,0.0C12.0,14.9 7.1,10.0 1.0,10.0z"/> + android:pathData="M42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0zM2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.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..4887b32 100644 --- a/packages/SystemUI/res/drawable/ic_qs_zen_on.xml +++ b/packages/SystemUI/res/drawable/ic_qs_zen_on.xml @@ -19,10 +19,10 @@ Copyright (C) 2014 The Android Open Source Project android:height="64dp"/> <viewport - android:viewportWidth="24.0" - android:viewportHeight="24.0"/> + android:viewportWidth="48.0" + android:viewportHeight="48.0"/> <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="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.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..477c36b 100644 --- a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml +++ b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml @@ -19,12 +19,10 @@ Copyright (C) 2014 The Android Open Source Project android:height="32dp"/> <viewport - android:viewportWidth="24.0" - android:viewportHeight="24.0"/> + android:viewportWidth="48.0" + android:viewportHeight="48.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="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.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..0a43a7b 100644 --- a/packages/SystemUI/res/drawable/ic_vol_zen_on.xml +++ b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml @@ -19,10 +19,10 @@ Copyright (C) 2014 The Android Open Source Project android:height="32dp"/> <viewport - android:viewportWidth="24.0" - android:viewportHeight="24.0"/> + android:viewportWidth="48.0" + android:viewportHeight="48.0"/> <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="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.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..5992470 100644 --- a/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml +++ b/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml @@ -15,14 +15,14 @@ Copyright (C) 2014 The Android Open Source Project --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size - android:width="19dp" - android:height="19dp"/> + android:width="18dp" + android:height="18dp"/> <viewport - android:viewportWidth="24.0" - android:viewportHeight="24.0"/> + android:viewportWidth="48.0" + android:viewportHeight="48.0"/> <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="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.0z"/> </vector> diff --git a/packages/SystemUI/res/layout/heads_up.xml b/packages/SystemUI/res/layout/heads_up.xml index 236fdc3..0e2b6d6 100644 --- a/packages/SystemUI/res/layout/heads_up.xml +++ b/packages/SystemUI/res/layout/heads_up.xml @@ -17,13 +17,14 @@ <com.android.systemui.statusbar.policy.HeadsUpNotificationView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="match_parent" - android:layout_width="match_parent"> + android:layout_width="match_parent" + android:background="@drawable/heads_up_scrim"> <FrameLayout android:layout_height="wrap_content" - android:layout_marginStart="@dimen/notification_side_padding" - android:layout_marginEnd="@dimen/notification_side_padding" - android:elevation="16dp" + android:paddingStart="@dimen/notification_side_padding" + android:paddingEnd="@dimen/notification_side_padding" + android:elevation="8dp" android:id="@+id/content_holder" style="@style/NotificationsQuickSettings" /> 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/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml new file mode 100644 index 0000000..5afa967 --- /dev/null +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/qs_panel_background" + android:translationZ="@dimen/volume_panel_z" + android:layout_margin="@dimen/volume_panel_z"> + + <include layout="@layout/volume_panel" /> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_panel_item.xml b/packages/SystemUI/res/layout/volume_panel_item.xml index 98cb8f4..4a2a0c0 100644 --- a/packages/SystemUI/res/layout/volume_panel_item.xml +++ b/packages/SystemUI/res/layout/volume_panel_item.xml @@ -15,28 +15,34 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="80dip" - android:orientation="horizontal" - android:layout_marginTop="8dip" - android:layout_marginBottom="8dip" - android:gravity="start|center_vertical"> + android:layout_width="match_parent" + android:layout_height="80dip" + android:orientation="horizontal" + android:layout_marginTop="8dip" + android:layout_marginBottom="8dip" + android:gravity="start|center_vertical"> <ImageView - android:id="@+id/stream_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingLeft="16dip" - android:background="?android:attr/selectableItemBackground" - android:contentDescription="@null" /> - - <SeekBar - style="?android:attr/seekBarStyle" - android:id="@+id/seekbar" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:padding="16dip" - android:layout_marginEnd="16dip" /> - + android:id="@+id/stream_icon" + style="@style/BorderlessButton.Tiny" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="16dip" + android:contentDescription="@null" /> + <FrameLayout + android:id="@+id/seekbar_container" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"> + <SeekBar + style="?android:attr/seekBarStyle" + android:id="@+id/seekbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="16dip" + android:paddingBottom="16dip" + android:paddingStart="11dip" + android:paddingEnd="11dip" + android:layout_marginEnd="16dip" /> + </FrameLayout> </LinearLayout> 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..7460c73 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> @@ -293,6 +305,7 @@ keyguard_clock_height_fraction_* for the difference between min and max.--> <dimen name="keyguard_clock_notifications_margin_min">22dp</dimen> <dimen name="keyguard_clock_notifications_margin_max">36dp</dimen> + <dimen name="heads_up_window_height">250dp</dimen> <!-- The minimum amount the user needs to swipe to go to the camera / phone. --> <dimen name="keyguard_min_swipe_amount">75dp</dimen> @@ -302,4 +315,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..373f11f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -290,6 +290,8 @@ <string name="accessibility_desc_off">Off.</string> <!-- Content description of an item that is connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_desc_connected">Connected.</string> + <!-- Content description of an item that is connecting for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_desc_connecting">Connecting.</string> <!-- Content description of the data connection type GPRS for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_data_connection_gprs">GPRS</string> @@ -522,7 +524,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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 1b12cb0..d153d09 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -51,12 +51,12 @@ public class SwipeHelper implements Gefingerpoken { private int MAX_DISMISS_VELOCITY = 2000; // dp/sec private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms - public static float ALPHA_FADE_START = 0f; // fraction of thumbnail width + public static float SWIPE_PROGRESS_FADE_START = 0f; // fraction of thumbnail width // where fade starts - static final float ALPHA_FADE_END = 0.5f; // fraction of thumbnail width - // beyond which alpha->0 - private float mMinAlpha = 0f; - private float mMaxAlpha = 1f; + static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width + // beyond which swipe progress->0 + private float mMinSwipeProgress = 0f; + private float mMaxSwipeProgress = 1f; private float mPagingTouchSlop; private Callback mCallback; @@ -137,36 +137,39 @@ public class SwipeHelper implements Gefingerpoken { v.getMeasuredHeight(); } - public void setMinAlpha(float minAlpha) { - mMinAlpha = minAlpha; + public void setMinSwipeProgress(float minSwipeProgress) { + mMinSwipeProgress = minSwipeProgress; } - public void setMaxAlpha(float maxAlpha) { - mMaxAlpha = maxAlpha; + public void setMaxSwipeProgress(float maxSwipeProgress) { + mMaxSwipeProgress = maxSwipeProgress; } - private float getAlphaForOffset(View view) { + private float getSwipeProgressForOffset(View view) { float viewSize = getSize(view); - final float fadeSize = ALPHA_FADE_END * viewSize; + final float fadeSize = SWIPE_PROGRESS_FADE_END * viewSize; float result = 1.0f; float pos = getTranslation(view); - if (pos >= viewSize * ALPHA_FADE_START) { - result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize; - } else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) { - result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize; + if (pos >= viewSize * SWIPE_PROGRESS_FADE_START) { + result = 1.0f - (pos - viewSize * SWIPE_PROGRESS_FADE_START) / fadeSize; + } else if (pos < viewSize * (1.0f - SWIPE_PROGRESS_FADE_START)) { + result = 1.0f + (viewSize * SWIPE_PROGRESS_FADE_START + pos) / fadeSize; } - return Math.min(Math.max(mMinAlpha, result), mMaxAlpha); + return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress); } - private void updateAlphaFromOffset(View animView, boolean dismissable) { - if (FADE_OUT_DURING_SWIPE && dismissable) { - float alpha = getAlphaForOffset(animView); - if (alpha != 0f && alpha != 1f) { - animView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } else { - animView.setLayerType(View.LAYER_TYPE_NONE, null); + private void updateSwipeProgressFromOffset(View animView, boolean dismissable) { + float swipeProgress = getSwipeProgressForOffset(animView); + if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) { + if (FADE_OUT_DURING_SWIPE && dismissable) { + float alpha = swipeProgress; + if (alpha != 0f && alpha != 1f) { + animView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } else { + animView.setLayerType(View.LAYER_TYPE_NONE, null); + } + animView.setAlpha(getSwipeProgressForOffset(animView)); } - animView.setAlpha(getAlphaForOffset(animView)); } invalidateGlobalRegion(animView); } @@ -307,7 +310,7 @@ public class SwipeHelper implements Gefingerpoken { }); anim.addUpdateListener(new AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { - updateAlphaFromOffset(animView, canAnimViewBeDismissed); + updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed); } }); anim.start(); @@ -321,12 +324,12 @@ public class SwipeHelper implements Gefingerpoken { anim.setDuration(duration); anim.addUpdateListener(new AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { - updateAlphaFromOffset(animView, canAnimViewBeDismissed); + updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed); } }); anim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animator) { - updateAlphaFromOffset(animView, canAnimViewBeDismissed); + updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed); mCallback.onChildSnappedBack(animView); } }); @@ -365,7 +368,7 @@ public class SwipeHelper implements Gefingerpoken { } setTranslation(mCurrAnimView, delta); - updateAlphaFromOffset(mCurrAnimView, mCanCurrViewBeDimissed); + updateSwipeProgressFromOffset(mCurrAnimView, mCanCurrViewBeDimissed); } break; case MotionEvent.ACTION_UP: @@ -415,5 +418,12 @@ public class SwipeHelper implements Gefingerpoken { void onDragCancelled(View v); void onChildSnappedBack(View animView); + + /** + * Updates the swipe progress on a child. + * + * @return if true, prevents the default alpha fading. + */ + boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 41c0e78..b280ab7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -202,6 +202,12 @@ public class KeyguardService extends Service { checkPermission(); mKeyguardViewMediator.onBootCompleted(); } + + @Override + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { + checkPermission(); + mKeyguardViewMediator.startKeyguardExitAnimation(startTime, fadeoutDuration); + } }; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index b2872fa..f7b4994 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -46,7 +46,8 @@ import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.view.ViewGroup; -import android.view.WindowManager; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicy; import com.android.internal.policy.IKeyguardExitCallback; @@ -137,6 +138,7 @@ public class KeyguardViewMediator extends SystemUI { private static final int SET_OCCLUDED = 12; private static final int KEYGUARD_TIMEOUT = 13; private static final int DISMISS = 17; + private static final int START_KEYGUARD_EXIT_ANIM = 18; /** * The default amount of time we stay awake (used for all key input) @@ -180,6 +182,9 @@ public class KeyguardViewMediator extends SystemUI { /** High level access to the power manager for WakeLocks */ private PowerManager mPM; + /** High level access to the window manager for dismissing keyguard animation */ + private IWindowManager mWM; + /** UserManager for querying number of users */ private UserManager mUserManager; @@ -440,6 +445,7 @@ public class KeyguardViewMediator extends SystemUI { private void setup() { mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mWM = WindowManagerGlobal.getWindowManagerService(); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard"); mShowKeyguardWakeLock.setReferenceCounted(false); @@ -1076,6 +1082,10 @@ public class KeyguardViewMediator extends SystemUI { case DISMISS: handleDismiss(); break; + case START_KEYGUARD_EXIT_ANIM: + StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj; + handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration); + break; } } }; @@ -1207,6 +1217,19 @@ public class KeyguardViewMediator extends SystemUI { private void handleHide() { synchronized (KeyguardViewMediator.this) { if (DEBUG) Log.d(TAG, "handleHide"); + try { + + // Don't actually hide the Keyguard at the moment, wait for window manager until + // it tells us it's safe to do so with startKeyguardExitAnimation. + mWM.keyguardGoingAway(); + } catch (RemoteException e) { + Log.e(TAG, "Error while calling WindowManager", e); + } + } + } + + private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) { + synchronized (KeyguardViewMediator.this) { // only play "unlock" noises if not on a call (since the incall UI // disables the keyguard) @@ -1214,7 +1237,7 @@ public class KeyguardViewMediator extends SystemUI { playSounds(false); } - mStatusBarKeyguardViewManager.hide(); + mStatusBarKeyguardViewManager.hide(startTime, fadeoutDuration); mShowing = false; mKeyguardDonePending = false; updateActivityLockScreenState(); @@ -1324,7 +1347,24 @@ public class KeyguardViewMediator extends SystemUI { return mStatusBarKeyguardViewManager; } + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { + Message msg = mHandler.obtainMessage(START_KEYGUARD_EXIT_ANIM, + new StartKeyguardExitAnimParams(startTime, fadeoutDuration)); + mHandler.sendMessage(msg); + } + public ViewMediatorCallback getViewMediatorCallback() { return mViewMediatorCallback; } + + private static class StartKeyguardExitAnimParams { + + long startTime; + long fadeoutDuration; + + private StartKeyguardExitAnimParams(long startTime, long fadeoutDuration) { + this.startTime = startTime; + this.fadeoutDuration = fadeoutDuration; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 626fc0d..2bf369a 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(); } } @@ -143,6 +152,7 @@ public class QSPanel extends ViewGroup { } private void handleShowDetail(TileRecord r, boolean show) { + if (r == null) return; AnimatorListener listener = null; if (show) { if (mDetailRecord != null) return; @@ -156,6 +166,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 +206,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 +233,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 +251,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 +283,8 @@ public class QSPanel extends ViewGroup { mDetailRecord = null; }; }; + + public interface Callback { + void onShowingDetail(boolean showingDetail); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 7335ab4..d220e1a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles; import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback; import android.content.Intent; import android.provider.Settings; +import android.text.TextUtils; import com.android.systemui.R; import com.android.systemui.qs.QSTile; @@ -70,18 +71,27 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> { final boolean supported = mController.isBluetoothSupported(); final boolean enabled = mController.isBluetoothEnabled(); final boolean connected = mController.isBluetoothConnected(); + final boolean connecting = mController.isBluetoothConnecting(); state.visible = supported; state.value = enabled; final String stateContentDescription; if (enabled) { + state.label = null; if (connected) { state.iconId = R.drawable.ic_qs_bluetooth_connected; stateContentDescription = mContext.getString(R.string.accessibility_desc_connected); + state.label = mController.getLastDeviceName(); + } else if (connecting) { + state.iconId = R.drawable.ic_qs_bluetooth_connecting; + stateContentDescription = mContext.getString(R.string.accessibility_desc_connecting); + state.label = mController.getLastDeviceName(); } else { state.iconId = R.drawable.ic_qs_bluetooth_on; stateContentDescription = mContext.getString(R.string.accessibility_desc_on); } - state.label = mContext.getString(R.string.quick_settings_bluetooth_label); + if (TextUtils.isEmpty(state.label)) { + state.label = mContext.getString(R.string.quick_settings_bluetooth_label); + } } else { state.iconId = R.drawable.ic_qs_bluetooth_off; state.label = mContext.getString(R.string.quick_settings_bluetooth_off_label); @@ -91,9 +101,9 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> { R.string.accessibility_quick_settings_bluetooth, stateContentDescription); } - private final BluetoothStateChangeCallback mCallback = new BluetoothStateChangeCallback() { + private final BluetoothController.Callback mCallback = new BluetoothController.Callback() { @Override - public void onBluetoothStateChange(boolean on) { + public void onBluetoothStateChange(boolean enabled, boolean connecting) { refreshState(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java index 0759b8e..72a3341 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java @@ -64,7 +64,7 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView } public void setMinSwipeAlpha(float minAlpha) { - mSwipeHelper.setMinAlpha(minAlpha); + mSwipeHelper.setMinSwipeProgress(minAlpha); } private int scrollPositionOfMostRecent() { @@ -221,6 +221,11 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView public void onChildSnappedBack(View animView) { } + @Override + public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { + return false; + } + public View getChildAtPosition(MotionEvent ev) { final float x = ev.getX() + getScrollX(); final float y = ev.getY() + getScrollY(); diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java index c2dde6a..1213375 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java @@ -65,7 +65,7 @@ public class RecentsVerticalScrollView extends ScrollView } public void setMinSwipeAlpha(float minAlpha) { - mSwipeHelper.setMinAlpha(minAlpha); + mSwipeHelper.setMinSwipeProgress(minAlpha); } private int scrollPositionOfMostRecent() { @@ -229,6 +229,11 @@ public class RecentsVerticalScrollView extends ScrollView public void onChildSnappedBack(View animView) { } + @Override + public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { + return false; + } + public View getChildAtPosition(MotionEvent ev) { final float x = ev.getX() + getScrollX(); final float y = ev.getY() + getScrollY(); 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 21b41c7..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(); @@ -228,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 { @@ -250,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; @@ -332,8 +337,7 @@ public abstract class BaseStatusBar extends SystemUI implements mHandler.post(new Runnable() { @Override public void run() { - mNotificationData.updateRanking(currentRanking); - updateNotifications(); + updateRankingInternal(currentRanking); } }); } @@ -479,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(); } @@ -1049,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 @@ -1069,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; @@ -1094,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; } }); } @@ -1275,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) { @@ -1282,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/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index b7a7b0a..3aaace4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -17,10 +17,14 @@ package com.android.systemui.statusbar.phone; import android.content.Context; +import android.os.SystemClock; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardViewBase; @@ -43,6 +47,8 @@ public class KeyguardBouncer { private StatusBarWindowManager mWindowManager; private KeyguardViewBase mKeyguardView; private ViewGroup mRoot; + private Interpolator mFadeOutInterpolator = new LinearInterpolator(); + private boolean mFadingOut; public KeyguardBouncer(Context context, ViewMediatorCallback callback, LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager, @@ -86,6 +92,29 @@ public class KeyguardBouncer { } } + public void animateHide(long delay, long duration) { + if (isShowing()) { + mFadingOut = true; + mKeyguardView.animate() + .alpha(0) + .withLayer() + + // Make it disappear faster, as the focus should be on the activity behind. + .setDuration(duration / 3) + .setInterpolator(mFadeOutInterpolator) + .setStartDelay(delay) + .withEndAction(new Runnable() { + @Override + public void run() { + mFadingOut = false; + hide(true /* destroyView */); + } + }); + } else { + hide(true /* destroyView */); + } + } + /** * Reset the state of the view. */ @@ -110,7 +139,7 @@ public class KeyguardBouncer { } public boolean isShowing() { - return mRoot != null && mRoot.getVisibility() == View.VISIBLE; + return mRoot != null && mRoot.getVisibility() == View.VISIBLE && !mFadingOut; } public void prepare() { 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..03d164b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -45,7 +45,10 @@ import java.util.ArrayList; public class NotificationPanelView extends PanelView implements ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, - View.OnClickListener, KeyguardPageSwipeHelper.Callback { + View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener, + KeyguardPageSwipeHelper.Callback { + + private static float EXPANSION_RUBBER_BAND_EXTRA_FACTOR = 0.6f; private KeyguardPageSwipeHelper mPageSwiper; PhoneStatusBar mStatusBar; @@ -70,6 +73,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; @@ -83,6 +87,7 @@ public class NotificationPanelView extends PanelView implements private int mQsPeekHeight; private float mNotificationTranslation; private int mStackScrollerIntrinsicPadding; + private boolean mStackScrollerOverscrolling; private boolean mQsExpansionEnabled = true; private ValueAnimator mQsExpansionAnimator; private FlingAnimationUtils mFlingAnimationUtils; @@ -136,6 +141,7 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller = (NotificationStackScrollLayout) findViewById(R.id.notification_stack_scroller); mNotificationStackScroller.setOnHeightChangedListener(this); + mNotificationStackScroller.setOverscrollTopChangedListener(this); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), android.R.interpolator.fast_out_slow_in); mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); @@ -165,9 +171,13 @@ 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); + if (!mStackScrollerOverscrolling) { + setQsExpansion(mQsMinExpansionHeight); + } positionClockAndNotifications(); mNotificationStackScroller.setStackHeight(getExpandedHeight()); } @@ -180,7 +190,10 @@ public class NotificationPanelView extends PanelView implements private void positionClockAndNotifications() { boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending(); if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { - mStackScrollerIntrinsicPadding = mHeader.getBottom() + mQsPeekHeight + int bottom = mStackScrollerOverscrolling + ? mHeader.getCollapsedHeight() + : mHeader.getBottom(); + mStackScrollerIntrinsicPadding = bottom + mQsPeekHeight + mNotificationTopPadding; mTopPaddingAdjustment = 0; } else { @@ -484,6 +497,16 @@ public class NotificationPanelView extends PanelView implements } } + + @Override + public void onOverscrollTopChanged(float amount) { + cancelAnimation(); + float rounded = amount >= 1f ? amount : 0f; + mStackScrollerOverscrolling = rounded != 0f; + setQsExpansion(mQsMinExpansionHeight + rounded); + updateQsState(); + } + private void onQsExpansionStarted() { onQsExpansionStarted(0); } @@ -511,9 +534,10 @@ public class NotificationPanelView extends PanelView implements } private void updateQsState() { - mHeader.setExpanded(mQsExpanded); + boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling; + mHeader.setExpanded(expandVisually, mStackScrollerOverscrolling); mNotificationStackScroller.setEnabled(!mQsExpanded); - mQsPanel.setVisibility(mQsExpanded ? View.VISIBLE : View.INVISIBLE); + mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE); mQsContainer.setVisibility(mKeyguardShowing && !mQsExpanded ? View.INVISIBLE : View.VISIBLE); @@ -522,7 +546,8 @@ public class NotificationPanelView extends PanelView implements private void setQsExpansion(float height) { height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); - if (height > mQsMinExpansionHeight && !mQsExpanded) { + mQsFullyExpanded = height == mQsMaxExpansionHeight; + if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) { setQsExpanded(true); } else if (height <= mQsMinExpansionHeight && mQsExpanded) { setQsExpanded(false); @@ -530,7 +555,9 @@ public class NotificationPanelView extends PanelView implements mQsExpansionHeight = height; mHeader.setExpansion(height - mQsPeekHeight); setQsTranslation(height); - setQsStackScrollerPadding(height); + if (!mStackScrollerOverscrolling) { + setQsStackScrollerPadding(height); + } mStatusBar.userActivity(); } @@ -701,8 +728,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/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index c5a9b85..220b691 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -200,8 +200,10 @@ public abstract class PanelView extends FrameLayout { case MotionEvent.ACTION_CANCEL: mTrackingPointer = -1; trackMovement(event); - boolean expand = flingWithCurrentVelocity(); + float vel = getCurrentVelocity(); + boolean expand = flingExpands(vel); onTrackingStopped(expand); + fling(vel, expand); if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; @@ -323,18 +325,15 @@ public abstract class PanelView extends FrameLayout { } /** - * @return whether the panel will be expanded after the animation + * @param vel the current velocity of the motion + * @return whether a fling should expands the panel; contracts otherwise */ - private boolean flingWithCurrentVelocity() { - float vel = getCurrentVelocity(); - boolean expand; + private boolean flingExpands(float vel) { if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { - expand = getExpandedFraction() > 0.5f; + return getExpandedFraction() > 0.5f; } else { - expand = vel > 0; + return vel > 0; } - fling(vel, expand); - return expand; } protected void fling(float vel, boolean expand) { @@ -342,6 +341,7 @@ public abstract class PanelView extends FrameLayout { float target = expand ? getMaxPanelHeight() : 0.0f; if (target == mExpandedHeight) { onExpandingFinished(); + mBar.panelExpansionChanged(this, mExpandedFraction); return; } ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, target); @@ -430,7 +430,7 @@ public abstract class PanelView extends FrameLayout { public void setExpandedHeightInternal(float h) { float fh = getMaxPanelHeight(); - mExpandedHeight = Math.min(fh, h); + mExpandedHeight = Math.max(0, Math.min(fh, h)); float overExpansion = h - fh; overExpansion = Math.max(0, overExpansion); if (overExpansion != mOverExpansion) { @@ -442,7 +442,7 @@ public abstract class PanelView extends FrameLayout { } onHeightUpdated(mExpandedHeight); - mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh); + mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : mExpandedHeight / fh); } protected void onOverExpansionChanged(float 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 0e5b7e1..f6e6fa8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -980,8 +980,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } private void addHeadsUpView() { + int headsUpHeight = mContext.getResources() + .getDimensionPixelSize(R.dimen.heads_up_window_height); WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT, headsUpHeight, WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar! WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS @@ -1050,7 +1052,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; @@ -1114,6 +1116,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); @@ -2348,16 +2357,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() { @@ -2418,7 +2435,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 { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 6156fc3..1264d75 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -19,16 +19,12 @@ package com.android.systemui.statusbar.phone; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; -import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.view.View; import android.view.ViewTreeObserver; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; -import android.view.animation.LinearInterpolator; /** * Controls both the scrim behind the notifications and in front of the notifications (when a @@ -53,6 +49,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { private boolean mAnimateChange; private boolean mUpdatePending; private boolean mExpanding; + private boolean mAnimateKeyguardFadingOut; + private long mDurationOverride = -1; + private long mAnimationDelay; + private Runnable mOnAnimationFinished; + private boolean mAnimationStarted; private final Interpolator mInterpolator = new DecelerateInterpolator(); @@ -87,14 +88,26 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { scheduleUpdate(); } + public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished) { + mAnimateKeyguardFadingOut = true; + mDurationOverride = duration; + mAnimationDelay = delay; + mAnimateChange = true; + mOnAnimationFinished = onAnimationFinished; + scheduleUpdate(); + } + private void scheduleUpdate() { if (mUpdatePending) return; + + // Make sure that a frame gets scheduled. + mScrimBehind.invalidate(); mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this); mUpdatePending = true; } private void updateScrims() { - if (!mKeyguardShowing) { + if (!mKeyguardShowing || mAnimateKeyguardFadingOut) { updateScrimNormal(); setScrimInFrontColor(0); } else { @@ -170,8 +183,20 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { } }); anim.setInterpolator(mInterpolator); - anim.setDuration(ANIMATION_DURATION); + anim.setStartDelay(mAnimationDelay); + anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION); + anim.addListener(new AnimatorListenerAdapter() { + + @Override + public void onAnimationEnd(Animator animation) { + if (mOnAnimationFinished != null) { + mOnAnimationFinished.run(); + mOnAnimationFinished = null; + } + } + }); anim.start(); + mAnimationStarted = true; } private int getBackgroundAlpha(View scrim) { @@ -188,6 +213,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); mUpdatePending = false; updateScrims(); + mAnimateKeyguardFadingOut = false; + mDurationOverride = -1; + mAnimationDelay = 0; + + // Make sure that we always call the listener even if we didn't start an animation. + if (!mAnimationStarted && mOnAnimationFinished != null) { + mOnAnimationFinished.run(); + mOnAnimationFinished = null; + } + mAnimationStarted = false; return true; } } 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..13d3291 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java @@ -44,6 +44,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL private static final float EXPANSION_RUBBERBAND_FACTOR = 0.35f; private boolean mExpanded; + private boolean mOverscrolled; private boolean mKeyguardShowing; private View mBackground; @@ -125,10 +126,12 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL return mExpandedHeight; } - public void setExpanded(boolean expanded) { + public void setExpanded(boolean expanded, boolean overscrolled) { boolean changed = expanded != mExpanded; + boolean overscrollChanged = overscrolled != mOverscrolled; mExpanded = expanded; - if (changed) { + mOverscrolled = overscrolled; + if (changed || overscrollChanged) { updateHeights(); updateVisibilities(); updateSystemIconsLayoutParams(); @@ -136,7 +139,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL updateZTranslation(); updateClickTargets(); if (mQSPanel != null) { - mQSPanel.setExpanded(expanded); + mQSPanel.setExpanded(expanded && !overscrolled); } } } @@ -184,13 +187,13 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL mDateTime.setVisibility(onKeyguardAndCollapsed ? View.INVISIBLE : View.VISIBLE); mKeyguardCarrierText.setVisibility(onKeyguardAndCollapsed ? View.VISIBLE : View.GONE); mDate.setVisibility(mExpanded ? View.VISIBLE : View.GONE); - mSettingsButton.setVisibility(mExpanded ? View.VISIBLE : View.GONE); + mSettingsButton.setVisibility(mExpanded && !mOverscrolled ? View.VISIBLE : View.GONE); mBrightnessContainer.setVisibility(mExpanded ? View.VISIBLE : View.GONE); if (mStatusIcons != null) { - mStatusIcons.setVisibility(!mExpanded ? View.VISIBLE : View.GONE); + mStatusIcons.setVisibility(!mExpanded || mOverscrolled ? View.VISIBLE : View.GONE); } if (mSignalCluster != null) { - mSignalCluster.setVisibility(!mExpanded ? View.VISIBLE : View.GONE); + mSignalCluster.setVisibility(!mExpanded || mOverscrolled ? View.VISIBLE : View.GONE); } } @@ -293,5 +296,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/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index d5551b8..e3145a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone; import android.content.Context; import android.os.Bundle; import android.os.RemoteException; +import android.os.SystemClock; import android.util.Slog; import android.view.KeyEvent; import android.view.View; @@ -183,11 +184,23 @@ public class StatusBarKeyguardViewManager { /** * Hides the keyguard view */ - public void hide() { + public void hide(long startTime, long fadeoutDuration) { mShowing = false; mPhoneStatusBar.hideKeyguard(); + mStatusBarWindowManager.setKeyguardFadingAway(true); mStatusBarWindowManager.setKeyguardShowing(false); - mBouncer.hide(true /* destroyView */); + long uptimeMillis = SystemClock.uptimeMillis(); + long delay = startTime - uptimeMillis; + if (delay < 0) { + delay = 0; + } + mBouncer.animateHide(delay, fadeoutDuration); + mScrimController.animateKeyguardFadingOut(delay, fadeoutDuration, new Runnable() { + @Override + public void run() { + mStatusBarWindowManager.setKeyguardFadingAway(false); + } + }); mViewMediatorCallback.keyguardGone(); updateStates(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java index b7bf6cd..fe57cef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -124,7 +124,8 @@ public class StatusBarWindowManager { } private void applyHeight(State state) { - boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded; + boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded + || state.keyguardFadingAway; if (expanded) { mLp.height = ViewGroup.LayoutParams.MATCH_PARENT; } else { @@ -201,6 +202,11 @@ public class StatusBarWindowManager { apply(mCurrentState); } + public void setKeyguardFadingAway(boolean keyguardFadingAway) { + mCurrentState.keyguardFadingAway = keyguardFadingAway; + apply(mCurrentState); + } + /** * @param state The {@link StatusBarState} of the status bar. */ @@ -217,6 +223,7 @@ public class StatusBarWindowManager { boolean statusBarFocusable; long keyguardUserActivityTimeout; boolean bouncerShowing; + boolean keyguardFadingAway; /** * The {@link BaseStatusBar} state from the status bar. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java index f4145cd..8e9fb30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java @@ -16,14 +16,18 @@ package com.android.systemui.statusbar.policy; -import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback; - public interface BluetoothController { - void addStateChangedCallback(BluetoothStateChangeCallback callback); - void removeStateChangedCallback(BluetoothStateChangeCallback callback); + void addStateChangedCallback(Callback callback); + void removeStateChangedCallback(Callback callback); boolean isBluetoothSupported(); boolean isBluetoothEnabled(); boolean isBluetoothConnected(); + boolean isBluetoothConnecting(); + String getLastDeviceName(); void setBluetoothEnabled(boolean enabled); + + public interface Callback { + void onBluetoothStateChange(boolean enabled, boolean connecting); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index 5a19881..117bf61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.policy; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; @@ -31,14 +30,13 @@ import java.util.Set; public class BluetoothControllerImpl extends BroadcastReceiver implements BluetoothController { private static final String TAG = "StatusBar.BluetoothController"; + private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); + private final Set<BluetoothDevice> mBondedDevices = new HashSet<BluetoothDevice>(); private final BluetoothAdapter mAdapter; - private boolean mEnabled = false; - - private Set<BluetoothDevice> mBondedDevices = new HashSet<BluetoothDevice>(); - - private ArrayList<BluetoothStateChangeCallback> mChangeCallbacks = - new ArrayList<BluetoothStateChangeCallback>(); + private boolean mEnabled; + private boolean mConnecting; + private BluetoothDevice mLastDevice; public BluetoothControllerImpl(Context context) { mAdapter = BluetoothAdapter.getDefaultAdapter(); @@ -57,14 +55,14 @@ public class BluetoothControllerImpl extends BroadcastReceiver implements Blueto updateBondedBluetoothDevices(); } - public void addStateChangedCallback(BluetoothStateChangeCallback cb) { - mChangeCallbacks.add(cb); + public void addStateChangedCallback(Callback cb) { + mCallbacks.add(cb); fireCallback(cb); } @Override - public void removeStateChangedCallback(BluetoothStateChangeCallback cb) { - mChangeCallbacks.remove(cb); + public void removeStateChangedCallback(Callback cb) { + mCallbacks.remove(cb); } @Override @@ -79,6 +77,12 @@ public class BluetoothControllerImpl extends BroadcastReceiver implements Blueto } @Override + public boolean isBluetoothConnecting() { + return mAdapter != null + && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING; + } + + @Override public void setBluetoothEnabled(boolean enabled) { if (mAdapter != null) { if (enabled) { @@ -99,6 +103,13 @@ public class BluetoothControllerImpl extends BroadcastReceiver implements Blueto } @Override + public String getLastDeviceName() { + return mLastDevice != null ? mLastDevice.getName() + : mBondedDevices.size() == 1 ? mBondedDevices.iterator().next().getName() + : null; + } + + @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); @@ -106,6 +117,11 @@ public class BluetoothControllerImpl extends BroadcastReceiver implements Blueto handleAdapterStateChange( intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)); } + if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { + mConnecting = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1) + == BluetoothAdapter.STATE_CONNECTING; + mLastDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + } fireCallbacks(); updateBondedBluetoothDevices(); } @@ -131,12 +147,12 @@ public class BluetoothControllerImpl extends BroadcastReceiver implements Blueto } private void fireCallbacks() { - for (BluetoothStateChangeCallback cb : mChangeCallbacks) { + for (Callback cb : mCallbacks) { fireCallback(cb); } } - private void fireCallback(BluetoothStateChangeCallback cb) { - cb.onBluetoothStateChange(mEnabled); + private void fireCallback(Callback cb) { + cb.onBluetoothStateChange(mEnabled, mConnecting); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java index cadb44a..f978833 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java @@ -20,6 +20,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.text.format.DateFormat; import android.util.AttributeSet; import android.widget.TextView; @@ -29,8 +30,6 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; -import libcore.icu.ICU; - public class DateView extends TextView { private static final String TAG = "DateView"; @@ -87,7 +86,7 @@ public class DateView extends TextView { if (mDateFormat == null) { final String dateFormat = getContext().getString(R.string.system_ui_date_pattern); final Locale l = Locale.getDefault(); - final String fmt = ICU.getBestDateTimePattern(dateFormat, l.toString()); + final String fmt = DateFormat.getBestDateTimePattern(l, dateFormat); mDateFormat = new SimpleDateFormat(fmt, l); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java index 9271e71..ac26da2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java @@ -47,7 +47,7 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. int[] mTmpTwoArray = new int[2]; private final int mTouchSensitivityDelay; - private final float mMaxAlpha = 0.95f; + private final float mMaxAlpha = 1f; private SwipeHelper mSwipeHelper; private EdgeSwipeHelper mEdgeSwipeHelper; @@ -114,7 +114,7 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. float pagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop(); float touchSlop = viewConfiguration.getScaledTouchSlop(); mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); - mSwipeHelper.setMaxAlpha(mMaxAlpha); + mSwipeHelper.setMaxSwipeProgress(mMaxAlpha); mEdgeSwipeHelper = new EdgeSwipeHelper(touchSlop); int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); @@ -184,7 +184,13 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); Outline o = new Outline(); - o.setRect(0, 0, mContentHolder.getWidth(), mContentHolder.getHeight()); + + // Apply padding to shadow. + int outlineLeft = mContentHolder.getPaddingLeft(); + int outlineTop = mContentHolder.getPaddingTop(); + o.setRect(outlineLeft, outlineTop, + mContentHolder.getWidth() - outlineLeft - mContentHolder.getPaddingRight(), + mContentHolder.getHeight() - outlineTop - mContentHolder.getPaddingBottom()); mContentHolder.setOutline(o); } @@ -246,6 +252,12 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. } @Override + public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { + getBackground().setAlpha((int) (255 * swipeProgress)); + return false; + } + + @Override public View getChildAtPosition(MotionEvent ev) { return mContentHolder; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 58176b9..6892b85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -18,13 +18,10 @@ package com.android.systemui.statusbar.stack; import android.content.Context; import android.content.res.Configuration; - import android.graphics.Canvas; import android.graphics.Paint; - import android.util.AttributeSet; import android.util.Log; - import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; @@ -40,8 +37,8 @@ import com.android.systemui.SwipeHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.SpeedBumpView; -import com.android.systemui.statusbar.stack.StackScrollState.ViewState; import com.android.systemui.statusbar.policy.ScrollAdapter; +import com.android.systemui.statusbar.stack.StackScrollState.ViewState; import java.util.ArrayList; @@ -121,6 +118,7 @@ public class NotificationStackScrollLayout extends ViewGroup private float mOverScrolledBottomPixels; private OnChildLocationsChangedListener mListener; + private OnOverscrollTopChangedListener mOverscrollTopChangedListener; private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; private boolean mNeedsAnimation; private boolean mTopPaddingNeedsAnimation; @@ -449,6 +447,11 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @Override + public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { + return false; + } + public void onBeginDrag(View v) { setSwipingInProgress(true); mAmbientState.onBeginDrag(v); @@ -870,9 +873,24 @@ public class NotificationStackScrollLayout extends ViewGroup setOverScrolledPixels(amount / RUBBER_BAND_FACTOR, onTop); mAmbientState.setOverScrollAmount(amount, onTop); requestChildrenUpdate(); + if (onTop) { + float scrollAmount = mOwnScrollY < 0 ? -mOwnScrollY : 0; + notifyOverscrollTopListener(scrollAmount + amount); + } } } + private void notifyOverscrollTopListener(float amount) { + if (mOverscrollTopChangedListener != null) { + mOverscrollTopChangedListener.onOverscrollTopChanged(amount); + } + } + + public void setOverscrollTopChangedListener( + OnOverscrollTopChangedListener overscrollTopChangedListener) { + mOverscrollTopChangedListener = overscrollTopChangedListener; + } + public float getCurrentOverScrollAmount(boolean top) { return mAmbientState.getOverScrollAmount(top); } @@ -908,6 +926,12 @@ public class NotificationStackScrollLayout extends ViewGroup onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); invalidateParentIfNeeded(); updateChildren(); + float overScrollTop = getCurrentOverScrollAmount(true); + if (mOwnScrollY < 0) { + notifyOverscrollTopListener(-mOwnScrollY + overScrollTop); + } else { + notifyOverscrollTopListener(overScrollTop); + } } } else { customScrollTo(scrollY); @@ -1591,6 +1615,13 @@ public class NotificationStackScrollLayout extends ViewGroup public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout); } + /** + * A listener that gets notified when the overscroll at the top has changed. + */ + public interface OnOverscrollTopChangedListener { + public void onOverscrollTopChanged(float amount); + } + static class AnimationEvent { static AnimationFilter[] FILTERS = new AnimationFilter[] { 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 06f4c2e..898b46e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java @@ -16,6 +16,8 @@ package com.android.systemui.volume; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.app.AlertDialog; import android.app.Dialog; import android.content.BroadcastReceiver; @@ -25,12 +27,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; @@ -39,11 +44,15 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.view.ViewStub; import android.view.Window; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; @@ -76,6 +85,8 @@ 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 float ICON_PULSE_SCALE = 1.3f; private static final int MSG_VOLUME_CHANGED = 0; private static final int MSG_FREE_RESOURCES = 1; @@ -100,9 +111,11 @@ public class VolumePanel extends Handler { protected final Context mContext; private final AudioManager mAudioManager; private final ZenModeController mZenController; + private final Interpolator mFastOutSlowInInterpolator; 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; @@ -214,6 +227,7 @@ public class VolumePanel extends Handler { ViewGroup group; ImageView icon; SeekBar seekbarView; + View seekbarContainer; int iconRes; int iconMuteRes; } @@ -267,6 +281,8 @@ public class VolumePanel extends Handler { mParent = parent; mZenController = zenController; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.fast_out_slow_in); // For now, only show master volume if master volume is supported final Resources res = context.getResources(); @@ -278,7 +294,6 @@ public class VolumePanel extends Handler { } } if (LOGD) Log.d(mTag, String.format("new VolumePanel hasParent=%s", parent != null)); - final int layoutId = com.android.systemui.R.layout.volume_panel; if (parent == null) { // dialog mode mDialog = new Dialog(context) { @@ -301,17 +316,18 @@ 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); + mDialog.setContentView(com.android.systemui.R.layout.volume_dialog); mDialog.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { @@ -321,6 +337,8 @@ public class VolumePanel extends Handler { }); mDialog.create(); + // temporary workaround, until we support window-level shadows + mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(0x00000000)); mView = window.findViewById(R.id.content); mView.setOnTouchListener(new View.OnTouchListener() { @@ -334,7 +352,8 @@ public class VolumePanel extends Handler { } else { // embedded mode mDialog = null; - mView = LayoutInflater.from(mContext).inflate(layoutId, parent, true); + mView = LayoutInflater.from(mContext).inflate( + com.android.systemui.R.layout.volume_panel, parent, true); } mPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.visible_panel); mSliderPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.slider_panel); @@ -444,6 +463,26 @@ public class VolumePanel extends Handler { sc.iconRes = streamRes.iconRes; sc.iconMuteRes = streamRes.iconMuteRes; sc.icon.setImageResource(sc.iconRes); + sc.icon.setClickable(isNotificationOrRing(streamType)); + if (sc.icon.isClickable()) { + sc.icon.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + resetTimeout(); + toggle(sc); + } + }); + sc.icon.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + resetTimeout(); + longToggle(sc); + return true; + } + }); + } + sc.seekbarContainer = + sc.group.findViewById(com.android.systemui.R.id.seekbar_container); sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar); final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO || streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0; @@ -454,6 +493,26 @@ public class VolumePanel extends Handler { } } + private void toggle(StreamControl sc) { + if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL) { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_VIBRATE); + postVolumeChanged(sc.streamType, AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); + } else { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); + postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND); + } + } + + private void longToggle(StreamControl sc) { + if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); + postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND); + } else { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); + postVolumeChanged(sc.streamType, AudioManager.FLAG_SHOW_UI); // disable the slider + } + } + private void reorderSliders(int activeStreamType) { mSliderPanel.removeAllViews(); @@ -477,21 +536,62 @@ public class VolumePanel extends Handler { // Force reloading the image resource sc.icon.setImageDrawable(null); sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes); - if (((sc.streamType == AudioManager.STREAM_RING) || - (sc.streamType == AudioManager.STREAM_NOTIFICATION)) && + if (isNotificationOrRing(sc.streamType) && mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) { sc.icon.setImageResource(com.android.systemui.R.drawable.ic_ringer_vibrate); } + updateSliderEnabled(sc, muted, false); + } + + private void updateSliderEnabled(final StreamControl sc, boolean muted, boolean fixedVolume) { + final boolean wasEnabled = sc.seekbarView.isEnabled(); if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) { // never disable touch interactions for remote playback, the muting is not tied to // the state of the phone. sc.seekbarView.setEnabled(true); - } else if ((sc.streamType != mAudioManager.getMasterStreamType() && muted) || + } else if (fixedVolume || + (sc.streamType != mAudioManager.getMasterStreamType() && muted) || (sConfirmSafeVolumeDialog != null)) { sc.seekbarView.setEnabled(false); + } else if (isNotificationOrRing(sc.streamType) + && mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) { + sc.seekbarView.setEnabled(false); } else { sc.seekbarView.setEnabled(true); } + // pulse the ringer icon when the disabled slider is touched in silent mode + if (sc.icon.isClickable() && wasEnabled != sc.seekbarView.isEnabled()) { + if (sc.seekbarView.isEnabled()) { + sc.seekbarContainer.setOnTouchListener(null); + } else { + sc.seekbarContainer.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + resetTimeout(); + pulseIcon(sc.icon); + return false; + } + }); + } + } + } + + private void pulseIcon(final ImageView icon) { + if (icon.getScaleX() != 1) return; // already running + icon.animate().cancel(); + icon.animate().scaleX(ICON_PULSE_SCALE).scaleY(ICON_PULSE_SCALE) + .setInterpolator(mFastOutSlowInInterpolator) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + icon.animate().scaleX(1).scaleY(1).setListener(null); + } + }); + } + + private static boolean isNotificationOrRing(int streamType) { + return streamType == AudioManager.STREAM_RING + || streamType == AudioManager.STREAM_NOTIFICATION; } public void setZenModePanelCallback(ZenModePanel.Callback callback) { @@ -513,6 +613,7 @@ public class VolumePanel extends Handler { @Override public void onInteraction() { + resetTimeout(); if (mZenPanelCallback != null) { mZenPanelCallback.onInteraction(); } @@ -521,6 +622,8 @@ public class VolumePanel extends Handler { } mZenPanel.setVisibility(View.VISIBLE); mZenPanelDivider.setVisibility(View.VISIBLE); + mTimeoutDelay = TIMEOUT_DELAY_EXPANDED; + resetTimeout(); } private void collapse() { @@ -529,6 +632,8 @@ public class VolumePanel extends Handler { mZenPanel.setVisibility(View.GONE); } mZenPanelDivider.setVisibility(View.GONE); + mTimeoutDelay = TIMEOUT_DELAY; + resetTimeout(); } public void updateStates() { @@ -541,8 +646,7 @@ public class VolumePanel extends Handler { private void updateZenMode(boolean zen) { if (mZenModeCapable) { - final boolean show = mActiveStreamType == AudioManager.STREAM_NOTIFICATION - || mActiveStreamType == AudioManager.STREAM_RING; + final boolean show = isNotificationOrRing(mActiveStreamType); mExpandButton.setVisibility(show ? View.VISIBLE : View.GONE); mExpandDivider.setVisibility(show ? View.VISIBLE : View.GONE); mExpandButton.setImageResource(zen ? com.android.systemui.R.drawable.ic_vol_zen_on @@ -555,7 +659,7 @@ public class VolumePanel extends Handler { public void postZenModeChanged(boolean zen) { removeMessages(MSG_ZEN_MODE_CHANGED); - obtainMessage(MSG_ZEN_MODE_CHANGED, zen ? 1 : 0).sendToTarget(); + obtainMessage(MSG_ZEN_MODE_CHANGED, zen ? 1 : 0, 0).sendToTarget(); } public void postVolumeChanged(int streamType, int flags) { @@ -633,7 +737,7 @@ public class VolumePanel extends Handler { public void postLayoutDirection(int layoutDirection) { removeMessages(MSG_LAYOUT_DIRECTION); - obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection).sendToTarget(); + obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection, 0).sendToTarget(); } /** @@ -769,15 +873,8 @@ public class VolumePanel extends Handler { } sc.seekbarView.setProgress(index); - if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) || - (streamType != mAudioManager.getMasterStreamType() && - streamType != AudioService.STREAM_REMOTE_MUSIC && - isMuted(streamType)) || - sConfirmSafeVolumeDialog != null) { - sc.seekbarView.setEnabled(false); - } else { - sc.seekbarView.setEnabled(true); - } + updateSliderEnabled(sc, isMuted(streamType), + (flags & AudioManager.FLAG_FIXED_VOLUME) != 0); } if (!isShowing()) { @@ -801,6 +898,11 @@ public class VolumePanel extends Handler { mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) { sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY); } + + // Pulse the slider icon if an adjustment was suppressed due to silent mode. + if (sc != null && (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) { + pulseIcon(sc.icon); + } } private boolean isShowing() { @@ -1035,8 +1137,8 @@ public class VolumePanel extends Handler { if (isShowing()) { if (mDialog != null) { mDialog.dismiss(); + mActiveStreamType = -1; } - mActiveStreamType = -1; } synchronized (sConfirmSafeVolumeLock) { if (sConfirmSafeVolumeDialog != null) { @@ -1082,7 +1184,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 +1236,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 { @@ -1147,7 +1254,7 @@ public class VolumePanel extends Handler { private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { public void onZenChanged(boolean zen) { - updateZenMode(zen); + postZenModeChanged(zen); } }; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index 77d267e..c338563 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -45,6 +45,7 @@ public class ZenModePanel extends LinearLayout { private static final int[] MINUTES = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 }; public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); + private final Context mContext; private final LayoutInflater mInflater; private final HashSet<RadioButton> mRadioButtons = new HashSet<RadioButton>(); private final H mHandler = new H(); @@ -56,6 +57,7 @@ public class ZenModePanel extends LinearLayout { public ZenModePanel(Context context, AttributeSet attrs) { super(context, attrs); + mContext = context; mInflater = LayoutInflater.from(new ContextThemeWrapper(context, R.style.QSWhiteTheme)); } diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java index 673ce0b..762d3df 100644 --- a/policy/src/com/android/internal/policy/impl/GlobalActions.java +++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java @@ -185,7 +185,8 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac // If we only have 1 item and it's a simple press action, just do this action. if (mAdapter.getCount() == 1 - && mAdapter.getItem(0) instanceof SinglePressAction) { + && mAdapter.getItem(0) instanceof SinglePressAction + && !(mAdapter.getItem(0) instanceof LongPressAction)) { ((SinglePressAction) mAdapter.getItem(0)).onPress(); } else { WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes(); @@ -262,7 +263,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac continue; } if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { - mItems.add(getPowerAction()); + mItems.add(new PowerAction()); } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { mItems.add(mAirplaneModeOn); } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey) @@ -300,7 +301,11 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { - return mAdapter.getItem(position).onLongPress(); + final Action action = mAdapter.getItem(position); + if (action instanceof LongPressAction) { + return ((LongPressAction) action).onLongPress(); + } + return false; } }); dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); @@ -310,29 +315,33 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac return dialog; } - private Action getPowerAction() { - return new SinglePressAction( - com.android.internal.R.drawable.ic_lock_power_off, - R.string.global_action_power_off) { + private final class PowerAction extends SinglePressAction implements LongPressAction { + private PowerAction() { + super(com.android.internal.R.drawable.ic_lock_power_off, + R.string.global_action_power_off); + } - public void onPress() { - // shutdown by making sure radio and power are handled accordingly. - mWindowManagerFuncs.shutdown(true); - } + @Override + public boolean onLongPress() { + mWindowManagerFuncs.rebootSafeMode(true); + return true; + } - public boolean onLongPress() { - mWindowManagerFuncs.rebootSafeMode(true); - return true; - } + @Override + public boolean showDuringKeyguard() { + return true; + } - public boolean showDuringKeyguard() { - return true; - } + @Override + public boolean showBeforeProvisioning() { + return true; + } - public boolean showBeforeProvisioning() { - return true; - } - }; + @Override + public void onPress() { + // shutdown by making sure radio and power are handled accordingly. + mWindowManagerFuncs.shutdown(false /* confirm */); + } } private Action getBugReportAction() { @@ -367,10 +376,6 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac dialog.show(); } - public boolean onLongPress() { - return false; - } - public boolean showDuringKeyguard() { return true; } @@ -393,11 +398,6 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac } @Override - public boolean onLongPress() { - return false; - } - - @Override public boolean showDuringKeyguard() { return true; } @@ -583,8 +583,6 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac void onPress(); - public boolean onLongPress(); - /** * @return whether this action should appear in the dialog when the keygaurd * is showing. @@ -601,6 +599,13 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac } /** + * An action that also supports long press. + */ + private interface LongPressAction extends Action { + boolean onLongPress(); + } + + /** * A single press action maintains no state, just responds to a press * and takes an action. */ @@ -637,10 +642,6 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac abstract public void onPress(); - public boolean onLongPress() { - return false; - } - public View create( Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { View v = inflater.inflate(R.layout.global_actions_item, parent, false); @@ -769,10 +770,6 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac changeStateFromPress(nowOn); } - public boolean onLongPress() { - return false; - } - public boolean isEnabled() { return !mState.inTransition(); } @@ -862,10 +859,6 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac public void onPress() { } - public boolean onLongPress() { - return false; - } - public boolean showDuringKeyguard() { return true; } 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 3d53725..0d39586 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -289,6 +289,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mDemoHdmiRotation; boolean mDemoHdmiRotationLock; + boolean mWakeGestureEnabledSetting; + MyWakeGestureListener mWakeGestureListener; + // Default display does not rotate, apps that require non-default orientation will have to // have the orientation emulated. private boolean mForceDefaultOrientation = false; @@ -483,7 +486,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_DISABLE_POINTER_LOCATION = 2; private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3; private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4; - private static final int MSG_DISPATCH_SHOW_RECENTS = 5; private class PolicyHandler extends Handler { @Override @@ -501,9 +503,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK: dispatchMediaKeyRepeatWithWakeLock((KeyEvent)msg.obj); break; - case MSG_DISPATCH_SHOW_RECENTS: - showRecentApps(false); - break; } } } @@ -529,6 +528,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR), false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.WAKE_GESTURE_ENABLED), false, this, + UserHandle.USER_ALL); resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.ACCELEROMETER_ROTATION), false, this, UserHandle.USER_ALL); @@ -559,6 +561,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + class MyWakeGestureListener extends WakeGestureListener { + MyWakeGestureListener(Context context, Handler handler) { + super(context, handler); + } + + @Override + public void onWakeUp() { + synchronized (mLock) { + if (shouldEnableWakeGestureLp()) { + mPowerManager.wakeUp(SystemClock.uptimeMillis()); + } + } + } + } + class MyOrientationListener extends WindowOrientationListener { MyOrientationListener(Context context, Handler handler) { super(context, handler); @@ -860,6 +877,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManager = windowManager; mWindowManagerFuncs = windowManagerFuncs; mHandler = new PolicyHandler(); + mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler); mOrientationListener = new MyOrientationListener(mContext, mHandler); try { mOrientationListener.setCurrentRotation(windowManager.getRotation()); @@ -1146,6 +1164,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT, UserHandle.USER_CURRENT); + // Configure wake gesture. + boolean wakeGestureEnabledSetting = Settings.Secure.getIntForUser(resolver, + Settings.Secure.WAKE_GESTURE_ENABLED, 0, + UserHandle.USER_CURRENT) != 0; + if (mWakeGestureEnabledSetting != wakeGestureEnabledSetting) { + mWakeGestureEnabledSetting = wakeGestureEnabledSetting; + updateWakeGestureListenerLp(); + } + // Configure rotation lock. int userRotation = Settings.System.getIntForUser(resolver, Settings.System.USER_ROTATION, Surface.ROTATION_0, @@ -1193,6 +1220,20 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private void updateWakeGestureListenerLp() { + if (shouldEnableWakeGestureLp()) { + mWakeGestureListener.requestWakeUpTrigger(); + } else { + mWakeGestureListener.cancelWakeUpTrigger(); + } + } + + private boolean shouldEnableWakeGestureLp() { + return mWakeGestureEnabledSetting && !mScreenOnEarly + && (!mLidControlsSleep || mLidState != LID_CLOSED) + && mWakeGestureListener.isSupported(); + } + private void enablePointerLocation() { if (mPointerLocationView == null) { mPointerLocationView = new PointerLocationView(mContext); @@ -1576,11 +1617,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - public boolean doesForceHide(WindowState win, WindowManager.LayoutParams attrs) { + public boolean doesForceHide(WindowManager.LayoutParams attrs) { return (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0; } @Override + public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs) { + return attrs.type == TYPE_STATUS_BAR; + } + + @Override public boolean canBeForceHidden(WindowState win, WindowManager.LayoutParams attrs) { switch (attrs.type) { case TYPE_STATUS_BAR: @@ -1913,9 +1959,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public Animation createForceHideEnterAnimation(boolean onWallpaper) { - return AnimationUtils.loadAnimation(mContext, onWallpaper - ? com.android.internal.R.anim.lock_screen_wallpaper_behind_enter - : com.android.internal.R.anim.lock_screen_behind_enter); + return AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.lock_screen_behind_enter); } private static void awakenDreams() { @@ -2467,12 +2512,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - @Override - public void showRecentApps() { - mHandler.removeMessages(MSG_DISPATCH_SHOW_RECENTS); - mHandler.sendEmptyMessage(MSG_DISPATCH_SHOW_RECENTS); - } - private void showRecentApps(boolean triggeredFromAltTab) { mPreloadedRecentApps = false; // preloading no longer needs to be canceled try { @@ -3353,7 +3392,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; @@ -4411,6 +4455,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardDelegate.onScreenTurnedOff(why); } synchronized (mLock) { + updateWakeGestureListenerLp(); updateOrientationListenerLp(); updateLockScreenTimeout(); } @@ -4427,6 +4472,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { synchronized (mLock) { mScreenOnEarly = true; + updateWakeGestureListenerLp(); updateOrientationListenerLp(); updateLockScreenTimeout(); } @@ -4570,6 +4616,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + @Override + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { + if (mKeyguardDelegate != null) { + mKeyguardDelegate.startKeyguardExitAnimation(startTime, fadeoutDuration); + } + } + void sendCloseSystemWindows() { sendCloseSystemWindows(mContext, null); } @@ -5015,6 +5068,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { PowerManager.GO_TO_SLEEP_REASON_USER, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); } + + synchronized (mLock) { + updateWakeGestureListenerLp(); + } } void updateRotation(boolean alwaysSendConfiguration) { @@ -5475,6 +5532,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print(prefix); pw.print("mLastFocusNeedsMenu="); pw.println(mLastFocusNeedsMenu); } + pw.print(prefix); pw.print("mWakeGestureEnabledSetting="); + pw.println(mWakeGestureEnabledSetting); + pw.print(prefix); pw.print("mSupportAutoRotation="); pw.println(mSupportAutoRotation); pw.print(prefix); pw.print("mUiMode="); pw.print(mUiMode); pw.print(" mDockMode="); pw.print(mDockMode); @@ -5617,10 +5677,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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); + if (mWakeGestureListener != null) { + mWakeGestureListener.dump(pw, prefix); + } if (mOrientationListener != null) { mOrientationListener.dump(pw, prefix); } diff --git a/policy/src/com/android/internal/policy/impl/WakeGestureListener.java b/policy/src/com/android/internal/policy/impl/WakeGestureListener.java new file mode 100644 index 0000000..9396c2c --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/WakeGestureListener.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import android.os.Handler; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.hardware.TriggerEvent; +import android.hardware.TriggerEventListener; + +import java.io.PrintWriter; + +/** + * Watches for wake gesture sensor events then invokes the listener. + */ +public abstract class WakeGestureListener { + private static final String TAG = "WakeGestureListener"; + + private final SensorManager mSensorManager; + private final Handler mHandler; + + private final Object mLock = new Object(); + + private boolean mTriggerRequested; + private Sensor mSensor; + + public WakeGestureListener(Context context, Handler handler) { + mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + mHandler = handler; + + mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_WAKE_GESTURE); + } + + public abstract void onWakeUp(); + + public boolean isSupported() { + synchronized (mLock) { + return mSensor != null; + } + } + + public void requestWakeUpTrigger() { + synchronized (mLock) { + if (mSensor != null && !mTriggerRequested) { + mTriggerRequested = true; + mSensorManager.requestTriggerSensor(mListener, mSensor); + } + } + } + + public void cancelWakeUpTrigger() { + synchronized (mLock) { + if (mSensor != null && mTriggerRequested) { + mTriggerRequested = false; + mSensorManager.cancelTriggerSensor(mListener, mSensor); + } + } + } + + public void dump(PrintWriter pw, String prefix) { + synchronized (mLock) { + pw.println(prefix + TAG); + prefix += " "; + pw.println(prefix + "mTriggerRequested=" + mTriggerRequested); + pw.println(prefix + "mSensor=" + mSensor); + } + } + + private final TriggerEventListener mListener = new TriggerEventListener() { + @Override + public void onTrigger(TriggerEvent event) { + synchronized (mLock) { + mTriggerRequested = false; + mHandler.post(mWakeUpRunnable); + } + } + }; + + private final Runnable mWakeUpRunnable = new Runnable() { + @Override + public void run() { + onWakeUp(); + } + }; +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java index 966924b..63a5850 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java @@ -274,6 +274,12 @@ public class KeyguardServiceDelegate { mKeyguardState.currentUser = newUserId; } + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { + if (mKeyguardService != null) { + mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration); + } + } + private static final View createScrim(Context context) { View view = new View(context); diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java index 7cb48fa..5096bd3 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java @@ -190,6 +190,14 @@ public class KeyguardServiceWrapper implements IKeyguardService { } } + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { + try { + mService.startKeyguardExitAnimation(startTime, fadeoutDuration); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + public void showAssistant() { // Not used by PhoneWindowManager } diff --git a/services/Android.mk b/services/Android.mk index 5fcef64..b4de903 100644 --- a/services/Android.mk +++ b/services/Android.mk @@ -25,6 +25,7 @@ services := \ backup \ devicepolicy \ print \ + restrictions \ usb \ voiceinteraction 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/DockObserver.java b/services/core/java/com/android/server/DockObserver.java index 4a8bf72..af38664 100644 --- a/services/core/java/com/android/server/DockObserver.java +++ b/services/core/java/com/android/server/DockObserver.java @@ -19,10 +19,12 @@ package com.android.server; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.media.AudioManager; import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; +import android.os.Binder; import android.os.Handler; import android.os.Message; import android.os.PowerManager; @@ -33,62 +35,62 @@ import android.provider.Settings; import android.util.Log; import android.util.Slog; +import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.FileReader; +import java.io.PrintWriter; /** - * <p>DockObserver monitors for a docking station. + * DockObserver monitors for a docking station. */ -final class DockObserver extends UEventObserver { - private static final String TAG = DockObserver.class.getSimpleName(); +final class DockObserver extends SystemService { + private static final String TAG = "DockObserver"; private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock"; private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state"; private static final int MSG_DOCK_STATE_CHANGED = 0; - private final Object mLock = new Object(); + private final PowerManager mPowerManager; + private final PowerManager.WakeLock mWakeLock; - private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; - private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + private final Object mLock = new Object(); private boolean mSystemReady; - private final Context mContext; - private final PowerManager mPowerManager; - private final PowerManager.WakeLock mWakeLock; + private int mActualDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + + private int mReportedDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + + private boolean mUpdatesStopped; public DockObserver(Context context) { - mContext = context; + super(context); - mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); init(); // set initial status - startObserving(DOCK_UEVENT_MATCH); + + mObserver.startObserving(DOCK_UEVENT_MATCH); } @Override - public void onUEvent(UEventObserver.UEvent event) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, "Dock UEVENT: " + event.toString()); - } + public void onStart() { + publishBinderService(TAG, new BinderService()); + } - synchronized (mLock) { - try { - int newState = Integer.parseInt(event.get("SWITCH_STATE")); - if (newState != mDockState) { - mPreviousDockState = mDockState; - mDockState = newState; - if (mSystemReady) { - // Wake up immediately when docked or undocked. - mPowerManager.wakeUp(SystemClock.uptimeMillis()); - - updateLocked(); - } + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_ACTIVITY_MANAGER_READY) { + synchronized (mLock) { + mSystemReady = true; + + // don't bother broadcasting undocked here + if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { + updateLocked(); } - } catch (NumberFormatException e) { - Slog.e(TAG, "Could not parse switch state from event " + event); } } } @@ -100,8 +102,8 @@ final class DockObserver extends UEventObserver { FileReader file = new FileReader(DOCK_STATE_PATH); try { int len = file.read(buffer, 0, 1024); - mDockState = Integer.valueOf((new String(buffer, 0, len)).trim()); - mPreviousDockState = mDockState; + setActualDockStateLocked(Integer.valueOf((new String(buffer, 0, len)).trim())); + mPreviousDockState = mActualDockState; } finally { file.close(); } @@ -113,13 +115,21 @@ final class DockObserver extends UEventObserver { } } - void systemReady() { - synchronized (mLock) { - // don't bother broadcasting undocked here - if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { + private void setActualDockStateLocked(int newState) { + mActualDockState = newState; + if (!mUpdatesStopped) { + setDockStateLocked(newState); + } + } + + private void setDockStateLocked(int newState) { + if (newState != mReportedDockState) { + mReportedDockState = newState; + if (mSystemReady) { + // Wake up immediately when docked or undocked. + mPowerManager.wakeUp(SystemClock.uptimeMillis()); updateLocked(); } - mSystemReady = true; } } @@ -130,10 +140,13 @@ final class DockObserver extends UEventObserver { private void handleDockStateChange() { synchronized (mLock) { - Slog.i(TAG, "Dock state changed: " + mDockState); + Slog.i(TAG, "Dock state changed from " + mPreviousDockState + " to " + + mReportedDockState); + final int previousDockState = mPreviousDockState; + mPreviousDockState = mReportedDockState; // Skip the dock intent if not yet provisioned. - final ContentResolver cr = mContext.getContentResolver(); + final ContentResolver cr = getContext().getContentResolver(); if (Settings.Global.getInt(cr, Settings.Global.DEVICE_PROVISIONED, 0) == 0) { Slog.i(TAG, "Device not provisioned, skipping dock broadcast"); @@ -143,27 +156,27 @@ final class DockObserver extends UEventObserver { // Pack up the values and broadcast them to everyone Intent intent = new Intent(Intent.ACTION_DOCK_EVENT); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState); + intent.putExtra(Intent.EXTRA_DOCK_STATE, mReportedDockState); // Play a sound to provide feedback to confirm dock connection. // Particularly useful for flaky contact pins... if (Settings.Global.getInt(cr, Settings.Global.DOCK_SOUNDS_ENABLED, 1) == 1) { String whichSound = null; - if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { - if ((mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) || - (mPreviousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || - (mPreviousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { + if (mReportedDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { + if ((previousDockState == Intent.EXTRA_DOCK_STATE_DESK) || + (previousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || + (previousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { whichSound = Settings.Global.DESK_UNDOCK_SOUND; - } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) { + } else if (previousDockState == Intent.EXTRA_DOCK_STATE_CAR) { whichSound = Settings.Global.CAR_UNDOCK_SOUND; } } else { - if ((mDockState == Intent.EXTRA_DOCK_STATE_DESK) || - (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || - (mDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { + if ((mReportedDockState == Intent.EXTRA_DOCK_STATE_DESK) || + (mReportedDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || + (mReportedDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { whichSound = Settings.Global.DESK_DOCK_SOUND; - } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) { + } else if (mReportedDockState == Intent.EXTRA_DOCK_STATE_CAR) { whichSound = Settings.Global.CAR_DOCK_SOUND; } } @@ -173,7 +186,8 @@ final class DockObserver extends UEventObserver { if (soundPath != null) { final Uri soundUri = Uri.parse("file://" + soundPath); if (soundUri != null) { - final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); + final Ringtone sfx = RingtoneManager.getRingtone( + getContext(), soundUri); if (sfx != null) { sfx.setStreamType(AudioManager.STREAM_SYSTEM); sfx.play(); @@ -186,7 +200,7 @@ final class DockObserver extends UEventObserver { // Send the dock event intent. // There are many components in the system watching for this so as to // adjust audio routing, screen orientation, etc. - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL); // Release the wake lock that was acquired when the message was posted. mWakeLock.release(); @@ -203,4 +217,71 @@ final class DockObserver extends UEventObserver { } } }; + + private final UEventObserver mObserver = new UEventObserver() { + @Override + public void onUEvent(UEventObserver.UEvent event) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Slog.v(TAG, "Dock UEVENT: " + event.toString()); + } + + try { + synchronized (mLock) { + setActualDockStateLocked(Integer.parseInt(event.get("SWITCH_STATE"))); + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Could not parse switch state from event " + event); + } + } + }; + + private final class BinderService extends Binder { + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump dock observer service from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + if (args == null || args.length == 0 || "-a".equals(args[0])) { + pw.println("Current Dock Observer Service state:"); + if (mUpdatesStopped) { + pw.println(" (UPDATES STOPPED -- use 'reset' to restart)"); + } + pw.println(" reported state: " + mReportedDockState); + pw.println(" previous state: " + mPreviousDockState); + pw.println(" actual state: " + mActualDockState); + } else if (args.length == 3 && "set".equals(args[0])) { + String key = args[1]; + String value = args[2]; + try { + if ("state".equals(key)) { + mUpdatesStopped = true; + setDockStateLocked(Integer.parseInt(value)); + } else { + pw.println("Unknown set option: " + key); + } + } catch (NumberFormatException ex) { + pw.println("Bad value: " + value); + } + } else if (args.length == 1 && "reset".equals(args[0])) { + mUpdatesStopped = false; + setDockStateLocked(mActualDockState); + } else { + pw.println("Dump current dock state, or:"); + pw.println(" set state <value>"); + pw.println(" reset"); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index fb69c86..3a8f767 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -1681,6 +1681,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurMethodId = null; unbindCurrentMethodLocked(true, false); } + // Here is not the perfect place to reset the switching controller. Ideally + // mSwitchingController and mSettings should be able to share the same state. + // TODO: Make sure that mSwitchingController and mSettings are sharing the + // the same enabled IMEs list. + mSwitchingController.resetCircularListLocked(mContext); } /* package */ void setInputMethodLocked(String id, int subtypeId) { @@ -2650,7 +2655,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub setInputMethodEnabledLocked(defaultImiId, true); } } - + // Here is not the perfect place to reset the switching controller. Ideally + // mSwitchingController and mSettings should be able to share the same state. + // TODO: Make sure that mSwitchingController and mSettings are sharing the + // the same enabled IMEs list. mSwitchingController.resetCircularListLocked(mContext); } diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index d5f045e..d31fb60 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -2372,6 +2372,18 @@ class MountService extends IMountService.Stub } } + voldPath = maybeTranslatePathForVold(appPath, + userEnv.buildExternalStorageAppMediaDirs(callingPkg), + userEnv.buildExternalStorageAppMediaDirsForVold(callingPkg)); + if (voldPath != null) { + try { + mConnector.execute("volume", "mkdirs", voldPath); + return 0; + } catch (NativeDaemonConnectorException e) { + return e.getCode(); + } + } + throw new SecurityException("Invalid mkdirs path: " + appPath); } 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/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java index cd8c13f..d8ee8a1 100644 --- a/services/core/java/com/android/server/WiredAccessoryManager.java +++ b/services/core/java/com/android/server/WiredAccessoryManager.java @@ -246,7 +246,8 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { private void setDeviceStateLocked(int headset, int headsetState, int prevHeadsetState, String headsetName) { if ((headsetState & headset) != (prevHeadsetState & headset)) { - int device; + int outDevice = 0; + int inDevice = 0; int state; if ((headsetState & headset) != 0) { @@ -256,15 +257,16 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { } if (headset == BIT_HEADSET) { - device = AudioManager.DEVICE_OUT_WIRED_HEADSET; + outDevice = AudioManager.DEVICE_OUT_WIRED_HEADSET; + inDevice = AudioManager.DEVICE_IN_WIRED_HEADSET; } else if (headset == BIT_HEADSET_NO_MIC){ - device = AudioManager.DEVICE_OUT_WIRED_HEADPHONE; + outDevice = AudioManager.DEVICE_OUT_WIRED_HEADPHONE; } else if (headset == BIT_USB_HEADSET_ANLG) { - device = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET; + outDevice = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET; } else if (headset == BIT_USB_HEADSET_DGTL) { - device = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET; + outDevice = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET; } else if (headset == BIT_HDMI_AUDIO) { - device = AudioManager.DEVICE_OUT_HDMI; + outDevice = AudioManager.DEVICE_OUT_HDMI; } else { Slog.e(TAG, "setDeviceState() invalid headset type: "+headset); return; @@ -273,7 +275,12 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { if (LOG) Slog.v(TAG, "device "+headsetName+((state == 1) ? " connected" : " disconnected")); - mAudioManager.setWiredDeviceConnectionState(device, state, headsetName); + if (outDevice != 0) { + mAudioManager.setWiredDeviceConnectionState(outDevice, state, headsetName); + } + if (inDevice != 0) { + mAudioManager.setWiredDeviceConnectionState(inDevice, state, headsetName); + } } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index af0d443..816e022 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) { @@ -308,7 +309,7 @@ public final class ActiveServices { } ServiceRecord r = res.record; NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked( - callingUid, r.packageName, service, service.getFlags(), null); + callingUid, r.packageName, service, service.getFlags(), null, r.userId); if (unscheduleServiceRestartLocked(r, callingUid, false)) { if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 4b0187f..61ba6e0 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2834,7 +2834,7 @@ public final class ActivityManagerService extends ActivityManagerNative return app; } - startProcessLocked(app, hostingType, hostingNameStr); + startProcessLocked(app, hostingType, hostingNameStr, null /* ABI override */); return (app.pid != 0) ? app : null; } @@ -2843,7 +2843,7 @@ public final class ActivityManagerService extends ActivityManagerNative } private final void startProcessLocked(ProcessRecord app, - String hostingType, String hostingNameStr) { + String hostingType, String hostingNameStr, String abiOverride) { if (app.pid > 0 && app.pid != MY_PID) { synchronized (mPidsSelfLocked) { mPidsSelfLocked.remove(app.pid); @@ -2928,7 +2928,7 @@ public final class ActivityManagerService extends ActivityManagerNative debugFlags |= Zygote.DEBUG_ENABLE_ASSERT; } - String requiredAbi = app.info.cpuAbi; + String requiredAbi = (abiOverride != null) ? abiOverride : app.info.cpuAbi; if (requiredAbi == null) { requiredAbi = Build.SUPPORTED_ABIS[0]; } @@ -5020,7 +5020,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.persistent && !app.isolated) { if (!callerWillRestart) { - addAppLocked(app.info, false); + addAppLocked(app.info, false, null /* ABI override */); } else { needRestart = true; } @@ -5133,7 +5133,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.deathRecipient = adr; } catch (RemoteException e) { app.resetPackageList(mProcessStats); - startProcessLocked(app, "link fail", processName); + startProcessLocked(app, "link fail", processName, null /* ABI override */); return false; } @@ -5226,7 +5226,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.resetPackageList(mProcessStats); app.unlinkDeathRecipient(); - startProcessLocked(app, "bind fail", processName); + startProcessLocked(app, "bind fail", processName, null /* ABI override */); return false; } @@ -5377,7 +5377,7 @@ public final class ActivityManagerService extends ActivityManagerNative for (int ip=0; ip<NP; ip++) { if (DEBUG_PROCESSES) Slog.v(TAG, "Starting process on hold: " + procs.get(ip)); - startProcessLocked(procs.get(ip), "on-hold", null); + startProcessLocked(procs.get(ip), "on-hold", null, null /* ABI override */); } } @@ -6381,7 +6381,7 @@ public final class ActivityManagerService extends ActivityManagerNative * Like checkGrantUriPermissionLocked, but takes an Intent. */ NeededUriGrants checkGrantUriPermissionFromIntentLocked(int callingUid, - String targetPkg, Intent intent, int mode, NeededUriGrants needed) { + String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId) { if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking URI perm to data=" + (intent != null ? intent.getData() : null) + " clip=" + (intent != null ? intent.getClipData() : null) @@ -6400,11 +6400,28 @@ public final class ActivityManagerService extends ActivityManagerNative if (data == null && clip == null) { return null; } - + final IPackageManager pm = AppGlobals.getPackageManager(); + int targetUid; + if (needed != null) { + targetUid = needed.targetUid; + } else { + try { + targetUid = pm.getPackageUid(targetPkg, targetUserId); + } catch (RemoteException ex) { + return null; + } + if (targetUid < 0) { + if (DEBUG_URI_PERMISSION) { + Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg + + " on user " + targetUserId); + } + return null; + } + } if (data != null) { GrantUri grantUri = GrantUri.resolve(UserHandle.getUserId(callingUid), data); - int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, grantUri, mode, - needed != null ? needed.targetUid : -1); + targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, grantUri, mode, + targetUid); if (targetUid > 0) { if (needed == null) { needed = new NeededUriGrants(targetPkg, targetUid, mode); @@ -6416,10 +6433,9 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<clip.getItemCount(); i++) { Uri uri = clip.getItemAt(i).getUri(); if (uri != null) { - int targetUid = -1; GrantUri grantUri = GrantUri.resolve(UserHandle.getUserId(callingUid), uri); targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, grantUri, mode, - needed != null ? needed.targetUid : -1); + targetUid); if (targetUid > 0) { if (needed == null) { needed = new NeededUriGrants(targetPkg, targetUid, mode); @@ -6430,7 +6446,7 @@ public final class ActivityManagerService extends ActivityManagerNative Intent clipIntent = clip.getItemAt(i).getIntent(); if (clipIntent != null) { NeededUriGrants newNeeded = checkGrantUriPermissionFromIntentLocked( - callingUid, targetPkg, clipIntent, mode, needed); + callingUid, targetPkg, clipIntent, mode, needed, targetUserId); if (newNeeded != null) { needed = newNeeded; } @@ -6457,9 +6473,9 @@ public final class ActivityManagerService extends ActivityManagerNative } void grantUriPermissionFromIntentLocked(int callingUid, - String targetPkg, Intent intent, UriPermissionOwner owner) { + String targetPkg, Intent intent, UriPermissionOwner owner, int targetUserId) { NeededUriGrants needed = checkGrantUriPermissionFromIntentLocked(callingUid, targetPkg, - intent, intent != null ? intent.getFlags() : 0, null); + intent, intent != null ? intent.getFlags() : 0, null, targetUserId); if (needed == null) { return; } @@ -8550,7 +8566,8 @@ public final class ActivityManagerService extends ActivityManagerNative return new ProcessRecord(stats, info, proc, uid); } - final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) { + final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated, + String abiOverride) { ProcessRecord app; if (!isolated) { app = getProcessRecordLocked(info.processName, info.uid, true); @@ -8585,7 +8602,8 @@ public final class ActivityManagerService extends ActivityManagerNative } if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) { mPersistentStartingProcesses.add(app); - startProcessLocked(app, "added application", app.processName); + startProcessLocked(app, "added application", app.processName, + abiOverride); } return app; @@ -9802,7 +9820,7 @@ public final class ActivityManagerService extends ActivityManagerNative = (ApplicationInfo)apps.get(i); if (info != null && !info.packageName.equals("android")) { - addAppLocked(info, false); + addAppLocked(info, false, null /* ABI override */); } } } @@ -12982,7 +13000,7 @@ public final class ActivityManagerService extends ActivityManagerNative // We have components that still need to be running in the // process, so re-launch it. mProcessNames.put(app.processName, app.uid, app); - startProcessLocked(app, "restart", app.processName); + startProcessLocked(app, "restart", app.processName, null /* ABI override */); } else if (app.pid > 0 && app.pid != MY_PID) { // Goodbye! boolean removed; @@ -14296,7 +14314,7 @@ public final class ActivityManagerService extends ActivityManagerNative public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection, - int userId) { + int userId, String abiOverride) { enforceNotIsolatedCaller("startInstrumentation"); userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "startInstrumentation", null); @@ -14345,7 +14363,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Instrumentation can kill and relaunch even persistent processes forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false, userId, "start instr"); - ProcessRecord app = addAppLocked(ai, false); + ProcessRecord app = addAppLocked(ai, false, abiOverride); app.instrumentationClass = className; app.instrumentationInfo = ai; app.instrumentationProfileFile = profileFile; @@ -16309,7 +16327,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.persistent) { if (app.persistent) { - addAppLocked(app.info, false); + addAppLocked(app.info, false, null /* ABI override */); } } } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index b429b93..32722bc 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -635,7 +635,7 @@ final class ActivityRecord { final void deliverNewIntentLocked(int callingUid, Intent intent) { // The activity now gets access to the data associated with this Intent. service.grantUriPermissionFromIntentLocked(callingUid, packageName, - intent, getUriPermissionsLocked()); + intent, getUriPermissionsLocked(), userId); // We want to immediately deliver the intent to the activity if // it is currently the top resumed activity... however, if the // device is sleeping, then all activities are stopped, so in that diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 8f60b03..16ad153 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -30,10 +30,6 @@ import static com.android.server.am.ActivityManagerService.DEBUG_USER_LEAVING; import static com.android.server.am.ActivityManagerService.DEBUG_VISBILITY; import static com.android.server.am.ActivityManagerService.VALIDATE_TOKENS; -import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; - import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_APP; import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE; @@ -1067,6 +1063,40 @@ final class ActivityStack { } } + /** + * Determine if home should be visible below the passed record. + * @param record activity we are querying for. + * @return true if home is visible below the passed activity, false otherwise. + */ + boolean isActivityOverHome(ActivityRecord record) { + // Start at record and go down, look for either home or a visible fullscreen activity. + final TaskRecord recordTask = record.task; + for (int taskNdx = mTaskHistory.indexOf(recordTask); taskNdx >= 0; --taskNdx) { + TaskRecord task = mTaskHistory.get(taskNdx); + final ArrayList<ActivityRecord> activities = task.mActivities; + final int startNdx = + task == recordTask ? activities.indexOf(record) : activities.size() - 1; + for (int activityNdx = startNdx; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (r.isHomeActivity()) { + return true; + } + if (!r.finishing && r.fullscreen) { + // Passed activity is over a fullscreen activity. + return false; + } + } + if (task.mOnTopOfHome) { + // Got to the bottom of a task on top of home without finding a visible fullscreen + // activity. Home is visible. + return true; + } + } + // Got to the bottom of this stack and still don't know. If this is over the home stack + // then record is over home. May not work if we ever get more than two layers. + return mStackSupervisor.isFrontStack(this); + } + private void setVisibile(ActivityRecord r, boolean visible) { r.visible = visible; mWindowManager.setAppVisibility(r.appToken, visible); @@ -1096,8 +1126,7 @@ final class ActivityStack { for (int i = mStacks.indexOf(this) + 1; i < mStacks.size(); i++) { final ArrayList<TaskRecord> tasks = mStacks.get(i).getAllTasks(); for (int taskNdx = 0; taskNdx < tasks.size(); taskNdx++) { - final TaskRecord task = tasks.get(taskNdx); - final ArrayList<ActivityRecord> activities = task.mActivities; + final ArrayList<ActivityRecord> activities = tasks.get(taskNdx).mActivities; for (int activityNdx = 0; activityNdx < activities.size(); activityNdx++) { final ActivityRecord r = activities.get(activityNdx); @@ -1108,7 +1137,7 @@ final class ActivityStack { // - Full Screen Activity OR // - On top of Home and our stack is NOT home if (!r.finishing && r.visible && (r.fullscreen || - (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()))) { + (!isHomeStack() && r.frontOfTask && tasks.get(taskNdx).mOnTopOfHome))) { return false; } } @@ -1236,7 +1265,7 @@ final class ActivityStack { // At this point, nothing else needs to be shown if (DEBUG_VISBILITY) Slog.v(TAG, "Fullscreen: at " + r); behindFullscreen = true; - } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) { + } else if (!isHomeStack() && r.frontOfTask && task.mOnTopOfHome) { if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r); behindFullscreen = true; } @@ -1390,7 +1419,6 @@ final class ActivityStack { final boolean userLeaving = mStackSupervisor.mUserLeaving; mStackSupervisor.mUserLeaving = false; - final TaskRecord prevTask = prev != null ? prev.task : null; if (next == null) { // There are no more activities! Let's just start up the // Launcher... @@ -1398,10 +1426,7 @@ final class ActivityStack { if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: No more activities go home"); if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); // Only resume home if on home display - final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ? - HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo(); - return isOnHomeDisplay() && - mStackSupervisor.resumeHomeStackTask(returnTaskType, prev); + return isOnHomeDisplay() && mStackSupervisor.resumeHomeActivity(prev); } next.delayedResume = false; @@ -1420,24 +1445,22 @@ final class ActivityStack { } final TaskRecord nextTask = next.task; + final TaskRecord prevTask = prev != null ? prev.task : null; if (prevTask != null && prevTask.stack == this && - prevTask.isOverHomeStack() && prev.finishing && prev.frontOfTask) { + prevTask.mOnTopOfHome && prev.finishing && prev.frontOfTask) { if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); if (prevTask == nextTask) { prevTask.setFrontOfTask(); } else if (prevTask != topTask()) { - // This task is going away but it was supposed to return to the home stack. + // This task is going away but it was supposed to return to the home task. // Now the task above it has to return to the home task instead. final int taskNdx = mTaskHistory.indexOf(prevTask) + 1; - mTaskHistory.get(taskNdx).setTaskToReturnTo(HOME_ACTIVITY_TYPE); + mTaskHistory.get(taskNdx).mOnTopOfHome = true; } else { if (DEBUG_STATES && isOnHomeDisplay()) Slog.d(TAG, "resumeTopActivityLocked: Launching home next"); // Only resume home if on home display - final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ? - HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo(); - return isOnHomeDisplay() && - mStackSupervisor.resumeHomeStackTask(returnTaskType, prev); + return isOnHomeDisplay() && mStackSupervisor.resumeHomeActivity(prev); } } @@ -1808,11 +1831,10 @@ final class ActivityStack { ActivityStack lastStack = mStackSupervisor.getLastStack(); final boolean fromHome = lastStack.isHomeStack(); if (!isHomeStack() && (fromHome || topTask() != task)) { - task.setTaskToReturnTo(fromHome ? - lastStack.topTask().taskType : APPLICATION_ACTIVITY_TYPE); + task.mOnTopOfHome = fromHome; } } else { - task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE); + task.mOnTopOfHome = false; } mTaskHistory.remove(task); @@ -2331,7 +2353,7 @@ final class ActivityStack { if (callingUid > 0) { mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, - data, r.getUriPermissionsLocked()); + data, r.getUriPermissionsLocked(), r.userId); } if (DEBUG_RESULTS) Slog.v(TAG, "Send activity result to " + r @@ -2357,8 +2379,8 @@ final class ActivityStack { ActivityRecord next = topRunningActivityLocked(null); if (next != r) { final TaskRecord task = r.task; - if (r.frontOfTask && task == topTask() && task.isOverHomeStack()) { - mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo()); + if (r.frontOfTask && task == topTask() && task.mOnTopOfHome) { + mStackSupervisor.moveHomeToTop(); } } ActivityRecord top = mStackSupervisor.topRunningActivityLocked(); @@ -2514,10 +2536,13 @@ final class ActivityStack { if (DEBUG_RESULTS) Slog.v(TAG, "Adding result to " + resultTo + " who=" + r.resultWho + " req=" + r.requestCode + " res=" + resultCode + " data=" + resultData); + if (resultTo.userId != r.userId) { + resultData.prepareToLeaveUser(r.userId); + } if (r.info.applicationInfo.uid > 0) { mService.grantUriPermissionFromIntentLocked(r.info.applicationInfo.uid, resultTo.packageName, resultData, - resultTo.getUriPermissionsLocked()); + resultTo.getUriPermissionsLocked(), resultTo.userId); } resultTo.addResultLocked(r, r.resultWho, r.requestCode, resultCode, resultData); @@ -2842,9 +2867,8 @@ final class ActivityStack { if (task != null && task.removeActivity(r)) { if (DEBUG_STACK) Slog.i(TAG, "removeActivityFromHistoryLocked: last activity removed from " + this); - if (mStackSupervisor.isFrontStack(this) && task == topTask() && - task.isOverHomeStack()) { - mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo()); + if (mStackSupervisor.isFrontStack(this) && task == topTask() && task.mOnTopOfHome) { + mStackSupervisor.moveHomeToTop(); } removeTask(task); } @@ -3159,13 +3183,12 @@ final class ActivityStack { } } - void moveHomeStackTaskToTop(int homeStackTaskType) { + void moveHomeTaskToTop() { final int top = mTaskHistory.size() - 1; for (int taskNdx = top; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); - if (task.taskType == homeStackTaskType) { - if (DEBUG_TASKS || DEBUG_STACK) - Slog.d(TAG, "moveHomeStackTaskToTop: moving " + task); + if (task.isHomeTask()) { + if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG, "moveHomeTaskToTop: moving " + task); mTaskHistory.remove(taskNdx); mTaskHistory.add(top, task); updateTaskMovement(task, true); @@ -3277,12 +3300,12 @@ final class ActivityStack { int numTasks = mTaskHistory.size(); for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); - if (task.isOverHomeStack()) { + if (task.mOnTopOfHome) { break; } if (taskNdx == 1) { // Set the last task before tr to go to home. - task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + task.mOnTopOfHome = true; } } @@ -3303,10 +3326,9 @@ final class ActivityStack { } final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null; - if (task == tr && tr.isOverHomeStack() || numTasks <= 1 && isOnHomeDisplay()) { - final int taskToReturnTo = tr.getTaskToReturnTo(); - tr.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE); - return mStackSupervisor.resumeHomeStackTask(taskToReturnTo, null); + if (task == tr && tr.mOnTopOfHome || numTasks <= 1 && isOnHomeDisplay()) { + tr.mOnTopOfHome = false; + return mStackSupervisor.resumeHomeActivity(null); } mStackSupervisor.resumeTopActivitiesLocked(); @@ -3747,11 +3769,8 @@ final class ActivityStack { final int taskNdx = mTaskHistory.indexOf(task); final int topTaskNdx = mTaskHistory.size() - 1; - if (task.isOverHomeStack() && taskNdx < topTaskNdx) { - final TaskRecord nextTask = mTaskHistory.get(taskNdx + 1); - if (!nextTask.isOverHomeStack()) { - nextTask.setTaskToReturnTo(HOME_ACTIVITY_TYPE); - } + if (task.mOnTopOfHome && taskNdx < topTaskNdx) { + mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true; } mTaskHistory.remove(task); updateTaskMovement(task, true); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index f52f796..dc4ad48 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -31,9 +31,6 @@ import static com.android.server.am.ActivityManagerService.DEBUG_TASKS; import static com.android.server.am.ActivityManagerService.DEBUG_USER_LEAVING; import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG; import static com.android.server.am.ActivityManagerService.TAG; -import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; import android.app.Activity; import android.app.ActivityManager; @@ -217,6 +214,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 @@ -296,6 +297,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(); } } @@ -347,27 +351,18 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - void moveHomeStackTaskToTop(int homeStackTaskType) { - if (homeStackTaskType == RECENTS_ACTIVITY_TYPE) { - mWindowManager.showRecentApps(); - return; - } + void moveHomeToTop() { moveHomeStack(true); - mHomeStack.moveHomeStackTaskToTop(homeStackTaskType); + mHomeStack.moveHomeTaskToTop(); } - boolean resumeHomeStackTask(int homeStackTaskType, ActivityRecord prev) { - if (homeStackTaskType == RECENTS_ACTIVITY_TYPE) { - mWindowManager.showRecentApps(); - return false; - } - moveHomeStackTaskToTop(homeStackTaskType); + boolean resumeHomeActivity(ActivityRecord prev) { + moveHomeToTop(); if (prev != null) { - prev.task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE); + prev.task.mOnTopOfHome = false; } - ActivityRecord r = mHomeStack.topRunningActivityLocked(null); - if (r != null && (r.isHomeActivity() || r.isRecentsActivity())) { + if (r != null && r.isHomeActivity()) { mService.setFocusedActivityLocked(r); return resumeTopActivitiesLocked(mHomeStack, prev, null); } @@ -721,7 +716,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } void startHomeActivity(Intent intent, ActivityInfo aInfo) { - moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE); + moveHomeToTop(); startActivityLocked(null, intent, null, aInfo, null, null, null, null, 0, 0, 0, null, 0, null, false, null, null); } @@ -1412,7 +1407,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()) { @@ -1656,7 +1654,7 @@ public final class ActivityStackSupervisor implements DisplayListener { (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { // Caller wants to appear on home activity. - intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + intentActivity.task.mOnTopOfHome = true; } options = null; } @@ -1841,11 +1839,6 @@ public final class ActivityStackSupervisor implements DisplayListener { newTaskInfo != null ? newTaskInfo : r.info, newTaskIntent != null ? newTaskIntent : intent, voiceSession, voiceInteractor, true), null, true); - if (sourceRecord == null) { - // Launched from a service or notification or task that is finishing. - r.task.setTaskToReturnTo(isFrontStack(mHomeStack) ? - mHomeStack.topTask().taskType : RECENTS_ACTIVITY_TYPE); - } if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " + r.task); } else { @@ -1857,7 +1850,7 @@ public final class ActivityStackSupervisor implements DisplayListener { == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) { // Caller wants to appear on home activity, so before starting // their own activity we will bring home to the front. - r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + r.task.mOnTopOfHome = r.task.stack.isOnHomeDisplay(); } } } else if (sourceRecord != null) { @@ -1930,7 +1923,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, - intent, r.getUriPermissionsLocked()); + intent, r.getUriPermissionsLocked(), r.userId); if (newTask) { EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId); @@ -2208,7 +2201,7 @@ public final class ActivityStackSupervisor implements DisplayListener { if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) { // Caller wants the home activity moved with it. To accomplish this, // we'll just indicate that this task returns to the home task. - task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + task.mOnTopOfHome = true; } task.stack.moveTaskToFrontLocked(task, null, options); if (DEBUG_STACK) Slog.d(TAG, "findTaskToMoveToFront: moved to front of stack=" @@ -2319,7 +2312,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } mWindowManager.addTask(taskId, stackId, false); } - resumeHomeStackTask(HOME_ACTIVITY_TYPE, null); + resumeHomeActivity(null); } void moveTaskToStack(int taskId, int stackId, boolean toTop) { @@ -2581,7 +2574,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } } else { // Stack was moved to another display while user was swapped out. - resumeHomeStackTask(HOME_ACTIVITY_TYPE, null); + resumeHomeActivity(null); } return homeInFront; } @@ -3496,4 +3489,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/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index c07bc1e..ce83ae6 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -17,9 +17,6 @@ package com.android.server.am; import static com.android.server.am.ActivityManagerService.TAG; -import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE; import android.app.Activity; @@ -57,6 +54,7 @@ final class TaskRecord extends ThumbnailHolder { private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode"; private static final String ATTR_USERID = "user_id"; private static final String ATTR_TASKTYPE = "task_type"; + private static final String ATTR_ONTOPOFHOME = "on_top_of_home"; private static final String ATTR_LASTDESCRIPTION = "last_description"; private static final String ATTR_LASTTIMEMOVED = "last_time_moved"; @@ -106,11 +104,9 @@ final class TaskRecord extends ThumbnailHolder { /** True if persistable, has changed, and has not yet been persisted */ boolean needsPersisting = false; - - /** Indication of what to run next when task exits. Use ActivityRecord types. - * ActivityRecord.APPLICATION_ACTIVITY_TYPE indicates to resume the task below this one in the - * task stack. */ - private int mTaskToReturnTo = APPLICATION_ACTIVITY_TYPE; + /** Launch the home activity when leaving this task. Will be false for tasks that are not on + * Display.DEFAULT_DISPLAY. */ + boolean mOnTopOfHome = false; final ActivityManagerService mService; @@ -127,8 +123,9 @@ final class TaskRecord extends ThumbnailHolder { TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent, String _affinity, ComponentName _realActivity, ComponentName _origActivity, - boolean _rootWasReset, boolean _askedCompatMode, int _taskType, int _userId, - String _lastDescription, ArrayList<ActivityRecord> activities, long lastTimeMoved) { + boolean _rootWasReset, boolean _askedCompatMode, int _taskType, boolean _onTopOfHome, + int _userId, String _lastDescription, ArrayList<ActivityRecord> activities, + long lastTimeMoved) { mService = service; taskId = _taskId; intent = _intent; @@ -141,7 +138,7 @@ final class TaskRecord extends ThumbnailHolder { rootWasReset = _rootWasReset; askedCompatMode = _askedCompatMode; taskType = _taskType; - mTaskToReturnTo = HOME_ACTIVITY_TYPE; + mOnTopOfHome = _onTopOfHome; userId = _userId; lastDescription = _lastDescription; mActivities = activities; @@ -209,14 +206,6 @@ final class TaskRecord extends ThumbnailHolder { } } - void setTaskToReturnTo(int taskToReturnTo) { - mTaskToReturnTo = taskToReturnTo; - } - - int getTaskToReturnTo() { - return mTaskToReturnTo; - } - void disposeThumbnail() { super.disposeThumbnail(); for (int i=mActivities.size()-1; i>=0; i--) { @@ -488,15 +477,11 @@ final class TaskRecord extends ThumbnailHolder { } boolean isHomeTask() { - return taskType == HOME_ACTIVITY_TYPE; + return taskType == ActivityRecord.HOME_ACTIVITY_TYPE; } boolean isApplicationTask() { - return taskType == APPLICATION_ACTIVITY_TYPE; - } - - boolean isOverHomeStack() { - return mTaskToReturnTo == HOME_ACTIVITY_TYPE || mTaskToReturnTo == RECENTS_ACTIVITY_TYPE; + return taskType == ActivityRecord.APPLICATION_ACTIVITY_TYPE; } public TaskAccessInfo getTaskAccessInfoLocked() { @@ -638,6 +623,7 @@ final class TaskRecord extends ThumbnailHolder { out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode)); out.attribute(null, ATTR_USERID, String.valueOf(userId)); out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType)); + out.attribute(null, ATTR_ONTOPOFHOME, String.valueOf(mOnTopOfHome)); out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved)); if (lastDescription != null) { out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString()); @@ -683,6 +669,7 @@ final class TaskRecord extends ThumbnailHolder { boolean rootHasReset = false; boolean askedCompatMode = false; int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE; + boolean onTopOfHome = true; int userId = 0; String lastDescription = null; long lastTimeOnTop = 0; @@ -710,6 +697,8 @@ final class TaskRecord extends ThumbnailHolder { userId = Integer.valueOf(attrValue); } else if (ATTR_TASKTYPE.equals(attrName)) { taskType = Integer.valueOf(attrValue); + } else if (ATTR_ONTOPOFHOME.equals(attrName)) { + onTopOfHome = Boolean.valueOf(attrValue); } else if (ATTR_LASTDESCRIPTION.equals(attrName)) { lastDescription = attrValue; } else if (ATTR_LASTTIMEMOVED.equals(attrName)) { @@ -747,7 +736,8 @@ final class TaskRecord extends ThumbnailHolder { final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent, affinityIntent, affinity, realActivity, origActivity, rootHasReset, - askedCompatMode, taskType, userId, lastDescription, activities, lastTimeOnTop); + askedCompatMode, taskType, onTopOfHome, userId, lastDescription, activities, + lastTimeOnTop); for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) { final ActivityRecord r = activities.get(activityNdx); @@ -766,7 +756,7 @@ final class TaskRecord extends ThumbnailHolder { pw.print(" userId="); pw.print(userId); pw.print(" taskType="); pw.print(taskType); pw.print(" numFullscreen="); pw.print(numFullscreen); - pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo); + pw.print(" mOnTopOfHome="); pw.println(mOnTopOfHome); } if (affinity != null) { pw.print(prefix); pw.print("affinity="); pw.println(affinity); diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java index d46cc7b..579f89f 100644 --- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java @@ -115,12 +115,12 @@ final class DeviceDiscoveryAction extends FeatureAction { @Override public void onPollingFinished(List<Integer> ackedAddress) { if (ackedAddress.isEmpty()) { - Slog.i(TAG, "No device is detected."); + Slog.v(TAG, "No device is detected."); finish(); return; } - Slog.i(TAG, "Device detected: " + ackedAddress); + Slog.v(TAG, "Device detected: " + ackedAddress); allocateDevices(ackedAddress); startPhysicalAddressStage(); } @@ -136,6 +136,7 @@ final class DeviceDiscoveryAction extends FeatureAction { } private void startPhysicalAddressStage() { + Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size()); mProcessedDeviceCount = 0; mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS; @@ -158,6 +159,7 @@ final class DeviceDiscoveryAction extends FeatureAction { } private void startOsdNameStage() { + Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size()); mProcessedDeviceCount = 0; mState = STATE_WAITING_FOR_OSD_NAME; @@ -176,6 +178,8 @@ final class DeviceDiscoveryAction extends FeatureAction { } private void startVendorIdStage() { + Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size()); + mProcessedDeviceCount = 0; mState = STATE_WAITING_FOR_VENDOR_ID; @@ -301,11 +305,14 @@ final class DeviceDiscoveryAction extends FeatureAction { } private void wrapUpAndFinish() { + Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------"); ArrayList<HdmiCecDeviceInfo> result = new ArrayList<>(); for (DeviceInfo info : mDevices) { HdmiCecDeviceInfo cecDeviceInfo = info.toHdmiCecDeviceInfo(); + Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo); result.add(cecDeviceInfo); } + Slog.v(TAG, "--------------------------------------------"); mCallback.onDeviceDiscoveryDone(result); finish(); } @@ -355,6 +362,7 @@ final class DeviceDiscoveryAction extends FeatureAction { return; } + Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount); removeDevice(mProcessedDeviceCount); checkAndProceedStage(); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index e0a01f0..d0b716d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -121,10 +121,11 @@ final class HdmiCecController { * * @param deviceTypes array of device types */ - void initializeLocalDevices(int[] deviceTypes) { + void initializeLocalDevices(int[] deviceTypes, + HdmiCecLocalDevice.AddressAllocationCallback callback) { assertRunOnServiceThread(); for (int type : deviceTypes) { - HdmiCecLocalDevice device = HdmiCecLocalDevice.create(this, type); + HdmiCecLocalDevice device = HdmiCecLocalDevice.create(this, type, callback); if (device == null) { continue; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index e65e5fa..aac2a15 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -29,23 +29,41 @@ abstract class HdmiCecLocalDevice { protected final HdmiCecController mController; protected final int mDeviceType; + protected final AddressAllocationCallback mAllocationCallback; protected int mAddress; protected int mPreferredAddress; protected HdmiCecDeviceInfo mDeviceInfo; - protected HdmiCecLocalDevice(HdmiCecController controller, int deviceType) { + /** + * Callback interface to notify newly allocated logical address of the given + * local device. + */ + interface AddressAllocationCallback { + /** + * Called when a logical address of the given device is allocated. + * + * @param deviceType original device type + * @param logicalAddress newly allocated logical address + */ + void onAddressAllocated(int deviceType, int logicalAddress); + } + + protected HdmiCecLocalDevice(HdmiCecController controller, int deviceType, + AddressAllocationCallback callback) { mController = controller; mDeviceType = deviceType; + mAllocationCallback = callback; mAddress = HdmiCec.ADDR_UNREGISTERED; } // Factory method that returns HdmiCecLocalDevice of corresponding type. - static HdmiCecLocalDevice create(HdmiCecController controller, int deviceType) { + static HdmiCecLocalDevice create(HdmiCecController controller, int deviceType, + AddressAllocationCallback callback) { switch (deviceType) { case HdmiCec.DEVICE_TV: - return new HdmiCecLocalDeviceTv(controller); + return new HdmiCecLocalDeviceTv(controller, callback); case HdmiCec.DEVICE_PLAYBACK: - return new HdmiCecLocalDevicePlayback(controller); + return new HdmiCecLocalDevicePlayback(controller, callback); default: return null; } @@ -53,6 +71,12 @@ abstract class HdmiCecLocalDevice { abstract void init(); + /** + * Called when a logical address of the local device is allocated. + * Note that internal variables are updated before it's called. + */ + protected abstract void onAddressAllocated(int logicalAddress); + protected void allocateAddress(int type) { mController.allocateLogicalAddress(type, mPreferredAddress, new AllocateLogicalAddressCallback() { @@ -66,6 +90,10 @@ abstract class HdmiCecLocalDevice { mController.addDeviceInfo(deviceInfo); mController.addLogicalAddress(logicalAddress); + onAddressAllocated(logicalAddress); + if (mAllocationCallback != null) { + mAllocationCallback.onAddressAllocated(deviceType, logicalAddress); + } } }); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index a953467..3347725 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -23,13 +23,17 @@ import android.hardware.hdmi.HdmiCec; */ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { - HdmiCecLocalDevicePlayback(HdmiCecController controller) { - super(controller, HdmiCec.DEVICE_PLAYBACK); + HdmiCecLocalDevicePlayback(HdmiCecController controller, AddressAllocationCallback callback) { + super(controller, HdmiCec.DEVICE_PLAYBACK, callback); } @Override void init() { allocateAddress(mDeviceType); + } + + @Override + protected void onAddressAllocated(int logicalAddress) { mController.sendCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( mAddress, mController.getPhysicalAddress(), mDeviceType)); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 01ea685..93761ab 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -23,14 +23,17 @@ import android.hardware.hdmi.HdmiCec; */ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { - HdmiCecLocalDeviceTv(HdmiCecController controller) { - super(controller, HdmiCec.DEVICE_TV); + HdmiCecLocalDeviceTv(HdmiCecController controller, AddressAllocationCallback callback) { + super(controller, HdmiCec.DEVICE_TV, callback); } @Override void init() { allocateAddress(mDeviceType); + } + @Override + protected void onAddressAllocated(int logicalAddress) { // TODO: vendor-specific initialization here. mController.sendCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( diff --git a/services/core/java/com/android/server/hdmi/HdmiCecService.java b/services/core/java/com/android/server/hdmi/HdmiCecService.java index 0a4c719..98dc72f 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecService.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecService.java @@ -75,13 +75,8 @@ public final class HdmiCecService extends SystemService { @Override public void onStart() { - mNativePtr = nativeInit(this); - if (mNativePtr != 0) { - // TODO: Consider using a dedicated, configurable identifier for OSD name, maybe from - // Settings. It should be ASCII only, not a very long one (limited to 15 chars). - setOsdNameLocked(Build.MODEL); - publishBinderService(Context.HDMI_CEC_SERVICE, new BinderService()); - } + // Stop publishing the service. Soon to be deprecated. + Log.w(TAG, "In transition to HdmiControlService. May not work."); } /** diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 072b97f..d775733 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -30,10 +30,12 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.Slog; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.server.SystemService; import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; +import com.android.server.hdmi.HdmiCecLocalDevice.AddressAllocationCallback; import java.util.ArrayList; import java.util.Iterator; @@ -130,7 +132,22 @@ public final class HdmiControlService extends SystemService { mIoThread.start(); mCecController = HdmiCecController.create(this); if (mCecController != null) { - mCecController.initializeLocalDevices(mLocalDevices); + mCecController.initializeLocalDevices(mLocalDevices, new AddressAllocationCallback() { + private final SparseIntArray mAllocated = new SparseIntArray(); + + @Override + public void onAddressAllocated(int deviceType, int logicalAddress) { + mAllocated.append(deviceType, logicalAddress); + // TODO: get HdmiLCecLocalDevice and call onAddressAllocated here. + + // Once all logical allocation is done, launch device discovery + // action if one of local device is TV. + int tvAddress = mAllocated.get(HdmiCec.DEVICE_TV, -1); + if (mLocalDevices.length == mAllocated.size() && tvAddress != -1) { + launchDeviceDiscovery(tvAddress); + } + } + }); } else { Slog.i(TAG, "Device does not support HDMI-CEC."); } @@ -140,8 +157,7 @@ public final class HdmiControlService extends SystemService { Slog.i(TAG, "Device does not support MHL-control."); } - // TODO: Publish the BinderService - // publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); + publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); } /** @@ -287,6 +303,9 @@ public final class HdmiControlService extends SystemService { case HdmiCec.MESSAGE_TERMINATE_ARC: handleTerminateArc(message); return true; + case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS: + handleReportPhysicalAddress(message); + return true; // TODO: Add remaining system information query such as // <Give Device Power Status> and <Request Active Source> handler. default: @@ -297,7 +316,7 @@ public final class HdmiControlService extends SystemService { /** * Called when a new hotplug event is issued. * - * @param port hdmi port number where hot plug event issued. + * @param portNo hdmi port number where hot plug event issued. * @param connected whether to be plugged in or not */ void onHotplug(int portNo, boolean connected) { @@ -315,6 +334,22 @@ public final class HdmiControlService extends SystemService { mCecController.pollDevices(callback, retryCount); } + private void handleReportPhysicalAddress(HdmiCecMessage message) { + // At first, try to consume it. + if (dispatchMessageToAction(message)) { + return; + } + + // Ignore if [Device Discovery Action] is on going ignore message. + if (hasAction(DeviceDiscoveryAction.class)) { + Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> " + + "because Device Discovery Action is on-going:" + message); + return; + } + + // TODO: start new device action. + } + private void handleInitiateArc(HdmiCecMessage message){ // In case where <Initiate Arc> is started by <Request ARC Initiation> // need to clean up RequestArcInitiationAction. @@ -428,12 +463,12 @@ public final class HdmiControlService extends SystemService { // Launch device discovery sequence. // It starts with clearing the existing device info list. // Note that it assumes that logical address of all local devices is already allocated. - void launchDeviceDiscovery() { + private void launchDeviceDiscovery(int sourceAddress) { // At first, clear all existing device infos. mCecController.clearDeviceInfoList(); // TODO: check whether TV is one of local devices. - DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, HdmiCec.ADDR_TV, + DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress, new DeviceDiscoveryCallback() { @Override public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 030e3ed..c909a54 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -66,13 +66,13 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { * These are the playback states that count as currently active. */ private static final int[] ACTIVE_STATES = { - PlaybackState.PLAYSTATE_FAST_FORWARDING, - PlaybackState.PLAYSTATE_REWINDING, - PlaybackState.PLAYSTATE_SKIPPING_BACKWARDS, - PlaybackState.PLAYSTATE_SKIPPING_FORWARDS, - PlaybackState.PLAYSTATE_BUFFERING, - PlaybackState.PLAYSTATE_CONNECTING, - PlaybackState.PLAYSTATE_PLAYING }; + PlaybackState.STATE_FAST_FORWARDING, + PlaybackState.STATE_REWINDING, + PlaybackState.STATE_SKIPPING_TO_PREVIOUS, + PlaybackState.STATE_SKIPPING_TO_NEXT, + PlaybackState.STATE_BUFFERING, + PlaybackState.STATE_CONNECTING, + PlaybackState.STATE_PLAYING }; /** * The length of time a session will still be considered active after @@ -301,7 +301,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { if (isActiveState(state)) { return true; } - if (state == mPlaybackState.PLAYSTATE_PAUSED) { + if (state == mPlaybackState.STATE_PAUSED) { long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime; if (inactiveTime < ACTIVE_BUFFER) { return true; @@ -509,12 +509,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } PlaybackState result = null; if (state != null) { - if (state.getState() == PlaybackState.PLAYSTATE_PLAYING - || state.getState() == PlaybackState.PLAYSTATE_FAST_FORWARDING - || state.getState() == PlaybackState.PLAYSTATE_REWINDING) { + if (state.getState() == PlaybackState.STATE_PLAYING + || state.getState() == PlaybackState.STATE_FAST_FORWARDING + || state.getState() == PlaybackState.STATE_REWINDING) { long updateTime = state.getLastPositionUpdateTime(); if (updateTime > 0) { - long position = (long) (state.getRate() + long position = (long) (state.getPlaybackRate() * (SystemClock.elapsedRealtime() - updateTime)) + state.getPosition(); if (duration >= 0 && position > duration) { position = duration; @@ -522,7 +522,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { position = 0; } result = new PlaybackState(state); - result.setState(state.getState(), position, state.getRate()); + result.setState(state.getState(), position, state.getPlaybackRate()); } } } @@ -588,7 +588,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { public void setPlaybackState(PlaybackState state) { int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState(); int newState = state == null ? 0 : state.getState(); - if (isActiveState(oldState) && newState == PlaybackState.PLAYSTATE_PAUSED) { + if (isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) { mLastActiveTime = SystemClock.elapsedRealtime(); } mPlaybackState = state; @@ -649,14 +649,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { mCb = cb; } - public void sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) { + public boolean sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) { Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); try { mCb.onMediaButton(mediaButtonIntent, sequenceId, cb); + return true; } catch (RemoteException e) { Slog.e(TAG, "Remote failure in sendMediaRequest.", e); } + return false; } public void sendCommand(String command, Bundle extras, ResultReceiver cb) { @@ -788,8 +790,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public void sendMediaButton(KeyEvent mediaButtonIntent) { - mSessionCb.sendMediaButton(mediaButtonIntent, 0, null); + public boolean sendMediaButton(KeyEvent mediaButtonIntent) { + return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null); } @Override diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java index 7ba9212..56236f8 100644 --- a/services/core/java/com/android/server/media/MediaSessionStack.java +++ b/services/core/java/com/android/server/media/MediaSessionStack.java @@ -33,18 +33,18 @@ public class MediaSessionStack { * bump priority regardless of the old state. */ private static final int[] ALWAYS_PRIORITY_STATES = { - PlaybackState.PLAYSTATE_FAST_FORWARDING, - PlaybackState.PLAYSTATE_REWINDING, - PlaybackState.PLAYSTATE_SKIPPING_BACKWARDS, - PlaybackState.PLAYSTATE_SKIPPING_FORWARDS }; + PlaybackState.STATE_FAST_FORWARDING, + PlaybackState.STATE_REWINDING, + PlaybackState.STATE_SKIPPING_TO_PREVIOUS, + PlaybackState.STATE_SKIPPING_TO_NEXT }; /** * These are states that usually indicate the user took an action if they * were entered from a non-priority state. */ private static final int[] TRANSITION_PRIORITY_STATES = { - PlaybackState.PLAYSTATE_BUFFERING, - PlaybackState.PLAYSTATE_CONNECTING, - PlaybackState.PLAYSTATE_PLAYING }; + PlaybackState.STATE_BUFFERING, + PlaybackState.STATE_CONNECTING, + PlaybackState.STATE_PLAYING }; private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>(); 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 009943f..5081bf7 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..4ac2dcc 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -28,14 +28,14 @@ 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; /** * This {@link NotificationSignalExtractor} attempts to validate * people references. Also elevates the priority of real people. + * + * {@hide} */ public class ValidateNotificationPeople implements NotificationSignalExtractor { private static final String TAG = "ValidateNotificationPeople"; @@ -49,9 +49,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; @@ -138,7 +149,8 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { }; } - private String[] getExtraPeople(Bundle extras) { + // VisibleForTesting + public static String[] getExtraPeople(Bundle extras) { Object people = extras.get(Notification.EXTRA_PEOPLE); if (people instanceof String[]) { return (String[]) people; 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/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 5e3325c..25ebfc0 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -40,6 +40,7 @@ import android.util.Log; import android.util.Slog; import com.android.internal.content.PackageMonitor; +import com.android.server.SystemService; import java.util.ArrayList; import java.util.List; @@ -48,358 +49,374 @@ import java.util.List; * Service that manages requests and callbacks for launchers that support * managed profiles. */ -public class LauncherAppsService extends ILauncherApps.Stub { - private static final boolean DEBUG = false; - private static final String TAG = "LauncherAppsService"; - private final Context mContext; - private final PackageManager mPm; - private final UserManager mUm; - private final PackageCallbackList<IOnAppsChangedListener> mListeners - = new PackageCallbackList<IOnAppsChangedListener>(); - private MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); +public class LauncherAppsService extends SystemService { + + private final LauncherAppsImpl mLauncherAppsImpl; public LauncherAppsService(Context context) { - mContext = context; - mPm = mContext.getPackageManager(); - mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + super(context); + mLauncherAppsImpl = new LauncherAppsImpl(context); } - /* - * @see android.content.pm.ILauncherApps#addOnAppsChangedListener( - * android.content.pm.IOnAppsChangedListener) - */ @Override - public void addOnAppsChangedListener(IOnAppsChangedListener listener) throws RemoteException { - synchronized (mListeners) { - if (DEBUG) { - Log.d(TAG, "Adding listener from " + Binder.getCallingUserHandle()); - } - if (mListeners.getRegisteredCallbackCount() == 0) { + public void onStart() { + publishBinderService(Context.LAUNCHER_APPS_SERVICE, mLauncherAppsImpl); + } + + class LauncherAppsImpl extends ILauncherApps.Stub { + private static final boolean DEBUG = false; + private static final String TAG = "LauncherAppsService"; + private final Context mContext; + private final PackageManager mPm; + private final UserManager mUm; + private final PackageCallbackList<IOnAppsChangedListener> mListeners + = new PackageCallbackList<IOnAppsChangedListener>(); + + private MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); + + public LauncherAppsImpl(Context context) { + mContext = context; + mPm = mContext.getPackageManager(); + mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + } + + /* + * @see android.content.pm.ILauncherApps#addOnAppsChangedListener( + * android.content.pm.IOnAppsChangedListener) + */ + @Override + public void addOnAppsChangedListener(IOnAppsChangedListener listener) throws RemoteException { + synchronized (mListeners) { if (DEBUG) { - Log.d(TAG, "Starting package monitoring"); + Log.d(TAG, "Adding listener from " + Binder.getCallingUserHandle()); } - startWatchingPackageBroadcasts(); + if (mListeners.getRegisteredCallbackCount() == 0) { + if (DEBUG) { + Log.d(TAG, "Starting package monitoring"); + } + startWatchingPackageBroadcasts(); + } + mListeners.unregister(listener); + mListeners.register(listener, Binder.getCallingUserHandle()); } - mListeners.unregister(listener); - mListeners.register(listener, Binder.getCallingUserHandle()); } - } - /* - * @see android.content.pm.ILauncherApps#removeOnAppsChangedListener( - * android.content.pm.IOnAppsChangedListener) - */ - @Override - public void removeOnAppsChangedListener(IOnAppsChangedListener listener) - throws RemoteException { - synchronized (mListeners) { - if (DEBUG) { - Log.d(TAG, "Removing listener from " + Binder.getCallingUserHandle()); - } - mListeners.unregister(listener); - if (mListeners.getRegisteredCallbackCount() == 0) { - stopWatchingPackageBroadcasts(); + /* + * @see android.content.pm.ILauncherApps#removeOnAppsChangedListener( + * android.content.pm.IOnAppsChangedListener) + */ + @Override + public void removeOnAppsChangedListener(IOnAppsChangedListener listener) + throws RemoteException { + synchronized (mListeners) { + if (DEBUG) { + Log.d(TAG, "Removing listener from " + Binder.getCallingUserHandle()); + } + mListeners.unregister(listener); + if (mListeners.getRegisteredCallbackCount() == 0) { + stopWatchingPackageBroadcasts(); + } } } - } - - /** - * Register a receiver to watch for package broadcasts - */ - private void startWatchingPackageBroadcasts() { - mPackageMonitor.register(mContext, null, UserHandle.ALL, true); - } - /** - * Unregister package broadcast receiver - */ - private void stopWatchingPackageBroadcasts() { - if (DEBUG) { - Log.d(TAG, "Stopped watching for packages"); + /** + * Register a receiver to watch for package broadcasts + */ + private void startWatchingPackageBroadcasts() { + mPackageMonitor.register(mContext, null, UserHandle.ALL, true); } - mPackageMonitor.unregister(); - } - void checkCallbackCount() { - synchronized (mListeners) { + /** + * Unregister package broadcast receiver + */ + private void stopWatchingPackageBroadcasts() { if (DEBUG) { - Log.d(TAG, "Callback count = " + mListeners.getRegisteredCallbackCount()); - } - if (mListeners.getRegisteredCallbackCount() == 0) { - stopWatchingPackageBroadcasts(); + Log.d(TAG, "Stopped watching for packages"); } + mPackageMonitor.unregister(); } - } - /** - * Checks if the caller is in the same group as the userToCheck. - */ - private void ensureInUserProfiles(UserHandle userToCheck, String message) { - final int callingUserId = UserHandle.getCallingUserId(); - final int targetUserId = userToCheck.getIdentifier(); - - if (targetUserId == callingUserId) return; - - long ident = Binder.clearCallingIdentity(); - try { - UserInfo callingUserInfo = mUm.getUserInfo(callingUserId); - UserInfo targetUserInfo = mUm.getUserInfo(targetUserId); - if (targetUserInfo == null - || targetUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID - || targetUserInfo.profileGroupId != callingUserInfo.profileGroupId) { - throw new SecurityException(message); + void checkCallbackCount() { + synchronized (mListeners) { + if (DEBUG) { + Log.d(TAG, "Callback count = " + mListeners.getRegisteredCallbackCount()); + } + if (mListeners.getRegisteredCallbackCount() == 0) { + stopWatchingPackageBroadcasts(); + } } - } finally { - Binder.restoreCallingIdentity(ident); } - } - /** - * Checks if the user is enabled. - */ - private boolean isUserEnabled(UserHandle user) { - long ident = Binder.clearCallingIdentity(); - try { - UserInfo targetUserInfo = mUm.getUserInfo(user.getIdentifier()); - return targetUserInfo != null && targetUserInfo.isEnabled(); - } finally { - Binder.restoreCallingIdentity(ident); - } - } + /** + * Checks if the caller is in the same group as the userToCheck. + */ + private void ensureInUserProfiles(UserHandle userToCheck, String message) { + final int callingUserId = UserHandle.getCallingUserId(); + final int targetUserId = userToCheck.getIdentifier(); - @Override - public List<ResolveInfo> getLauncherActivities(String packageName, UserHandle user) - throws RemoteException { - ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user); - if (!isUserEnabled(user)) { - return new ArrayList<ResolveInfo>(); - } + if (targetUserId == callingUserId) return; - final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); - mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); - mainIntent.setPackage(packageName); - long ident = Binder.clearCallingIdentity(); - try { - List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, 0, - user.getIdentifier()); - return apps; - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - @Override - public ResolveInfo resolveActivity(Intent intent, UserHandle user) - throws RemoteException { - ensureInUserProfiles(user, "Cannot resolve activity for unrelated profile " + user); - if (!isUserEnabled(user)) { - return null; + long ident = Binder.clearCallingIdentity(); + try { + UserInfo callingUserInfo = mUm.getUserInfo(callingUserId); + UserInfo targetUserInfo = mUm.getUserInfo(targetUserId); + if (targetUserInfo == null + || targetUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID + || targetUserInfo.profileGroupId != callingUserInfo.profileGroupId) { + throw new SecurityException(message); + } + } finally { + Binder.restoreCallingIdentity(ident); + } } - long ident = Binder.clearCallingIdentity(); - try { - ResolveInfo app = mPm.resolveActivityAsUser(intent, 0, user.getIdentifier()); - return app; - } finally { - Binder.restoreCallingIdentity(ident); + /** + * Checks if the user is enabled. + */ + private boolean isUserEnabled(UserHandle user) { + long ident = Binder.clearCallingIdentity(); + try { + UserInfo targetUserInfo = mUm.getUserInfo(user.getIdentifier()); + return targetUserInfo != null && targetUserInfo.isEnabled(); + } finally { + Binder.restoreCallingIdentity(ident); + } } - } - @Override - public boolean isPackageEnabled(String packageName, UserHandle user) - throws RemoteException { - ensureInUserProfiles(user, "Cannot check package for unrelated profile " + user); - if (!isUserEnabled(user)) { - return false; - } + @Override + public List<ResolveInfo> getLauncherActivities(String packageName, UserHandle user) + throws RemoteException { + ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user); + if (!isUserEnabled(user)) { + return new ArrayList<ResolveInfo>(); + } - long ident = Binder.clearCallingIdentity(); - try { - IPackageManager pm = AppGlobals.getPackageManager(); - PackageInfo info = pm.getPackageInfo(packageName, 0, user.getIdentifier()); - return info != null && info.applicationInfo.enabled; - } finally { - Binder.restoreCallingIdentity(ident); + final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + mainIntent.setPackage(packageName); + long ident = Binder.clearCallingIdentity(); + try { + List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, 0, + user.getIdentifier()); + return apps; + } finally { + Binder.restoreCallingIdentity(ident); + } } - } - @Override - public boolean isActivityEnabled(ComponentName component, UserHandle user) - throws RemoteException { - ensureInUserProfiles(user, "Cannot check component for unrelated profile " + user); - if (!isUserEnabled(user)) { - return false; - } + @Override + public ResolveInfo resolveActivity(Intent intent, UserHandle user) + throws RemoteException { + ensureInUserProfiles(user, "Cannot resolve activity for unrelated profile " + user); + if (!isUserEnabled(user)) { + return null; + } - long ident = Binder.clearCallingIdentity(); - try { - IPackageManager pm = AppGlobals.getPackageManager(); - ActivityInfo info = pm.getActivityInfo(component, 0, user.getIdentifier()); - return info != null && info.isEnabled(); - } finally { - Binder.restoreCallingIdentity(ident); + long ident = Binder.clearCallingIdentity(); + try { + ResolveInfo app = mPm.resolveActivityAsUser(intent, 0, user.getIdentifier()); + return app; + } finally { + Binder.restoreCallingIdentity(ident); + } } - } - @Override - public void startActivityAsUser(ComponentName component, Rect sourceBounds, - Bundle opts, UserHandle user) throws RemoteException { - ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user); - if (!isUserEnabled(user)) { - throw new IllegalStateException("Cannot start activity for disabled profile " + user); - } + @Override + public boolean isPackageEnabled(String packageName, UserHandle user) + throws RemoteException { + ensureInUserProfiles(user, "Cannot check package for unrelated profile " + user); + if (!isUserEnabled(user)) { + return false; + } - Intent launchIntent = new Intent(Intent.ACTION_MAIN); - launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); - launchIntent.setComponent(component); - launchIntent.setSourceBounds(sourceBounds); - launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - long ident = Binder.clearCallingIdentity(); - try { - mContext.startActivityAsUser(launchIntent, opts, user); - } finally { - Binder.restoreCallingIdentity(ident); + long ident = Binder.clearCallingIdentity(); + try { + IPackageManager pm = AppGlobals.getPackageManager(); + PackageInfo info = pm.getPackageInfo(packageName, 0, user.getIdentifier()); + return info != null && info.applicationInfo.enabled; + } finally { + Binder.restoreCallingIdentity(ident); + } } - } - private class MyPackageMonitor extends PackageMonitor { - - /** Checks if user is a profile of or same as listeningUser. - * and the user is enabled. */ - private boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser, - String debugMsg) { - if (user.getIdentifier() == listeningUser.getIdentifier()) { - if (DEBUG) Log.d(TAG, "Delivering msg to same user " + debugMsg); - return true; + @Override + public boolean isActivityEnabled(ComponentName component, UserHandle user) + throws RemoteException { + ensureInUserProfiles(user, "Cannot check component for unrelated profile " + user); + if (!isUserEnabled(user)) { + return false; } + long ident = Binder.clearCallingIdentity(); try { - UserInfo userInfo = mUm.getUserInfo(user.getIdentifier()); - UserInfo listeningUserInfo = mUm.getUserInfo(listeningUser.getIdentifier()); - if (userInfo == null || listeningUserInfo == null - || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID - || userInfo.profileGroupId != listeningUserInfo.profileGroupId - || !userInfo.isEnabled()) { - if (DEBUG) { - Log.d(TAG, "Not delivering msg from " + user + " to " + listeningUser + ":" - + debugMsg); - } - return false; - } else { - if (DEBUG) { - Log.d(TAG, "Delivering msg from " + user + " to " + listeningUser + ":" - + debugMsg); - } - return true; - } + IPackageManager pm = AppGlobals.getPackageManager(); + ActivityInfo info = pm.getActivityInfo(component, 0, user.getIdentifier()); + return info != null && info.isEnabled(); } finally { Binder.restoreCallingIdentity(ident); } } @Override - public void onPackageAdded(String packageName, int uid) { - UserHandle user = new UserHandle(getChangingUserId()); - final int n = mListeners.beginBroadcast(); - for (int i = 0; i < n; i++) { - IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onPackageAdded")) continue; - try { - listener.onPackageAdded(user, packageName); - } catch (RemoteException re) { - Slog.d(TAG, "Callback failed ", re); - } + public void startActivityAsUser(ComponentName component, Rect sourceBounds, + Bundle opts, UserHandle user) throws RemoteException { + ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user); + if (!isUserEnabled(user)) { + throw new IllegalStateException("Cannot start activity for disabled profile " + user); } - mListeners.finishBroadcast(); - super.onPackageAdded(packageName, uid); + Intent launchIntent = new Intent(Intent.ACTION_MAIN); + launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); + launchIntent.setComponent(component); + launchIntent.setSourceBounds(sourceBounds); + launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + long ident = Binder.clearCallingIdentity(); + try { + mContext.startActivityAsUser(launchIntent, opts, user); + } finally { + Binder.restoreCallingIdentity(ident); + } } - @Override - public void onPackageRemoved(String packageName, int uid) { - UserHandle user = new UserHandle(getChangingUserId()); - final int n = mListeners.beginBroadcast(); - for (int i = 0; i < n; i++) { - IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onPackageRemoved")) continue; + private class MyPackageMonitor extends PackageMonitor { + + /** Checks if user is a profile of or same as listeningUser. + * and the user is enabled. */ + private boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser, + String debugMsg) { + if (user.getIdentifier() == listeningUser.getIdentifier()) { + if (DEBUG) Log.d(TAG, "Delivering msg to same user " + debugMsg); + return true; + } + long ident = Binder.clearCallingIdentity(); try { - listener.onPackageRemoved(user, packageName); - } catch (RemoteException re) { - Slog.d(TAG, "Callback failed ", re); + UserInfo userInfo = mUm.getUserInfo(user.getIdentifier()); + UserInfo listeningUserInfo = mUm.getUserInfo(listeningUser.getIdentifier()); + if (userInfo == null || listeningUserInfo == null + || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID + || userInfo.profileGroupId != listeningUserInfo.profileGroupId + || !userInfo.isEnabled()) { + if (DEBUG) { + Log.d(TAG, "Not delivering msg from " + user + " to " + listeningUser + ":" + + debugMsg); + } + return false; + } else { + if (DEBUG) { + Log.d(TAG, "Delivering msg from " + user + " to " + listeningUser + ":" + + debugMsg); + } + return true; + } + } finally { + Binder.restoreCallingIdentity(ident); } } - mListeners.finishBroadcast(); - super.onPackageRemoved(packageName, uid); - } - - @Override - public void onPackageModified(String packageName) { - UserHandle user = new UserHandle(getChangingUserId()); - final int n = mListeners.beginBroadcast(); - for (int i = 0; i < n; i++) { - IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onPackageModified")) continue; - try { - listener.onPackageChanged(user, packageName); - } catch (RemoteException re) { - Slog.d(TAG, "Callback failed ", re); + @Override + public void onPackageAdded(String packageName, int uid) { + UserHandle user = new UserHandle(getChangingUserId()); + final int n = mListeners.beginBroadcast(); + for (int i = 0; i < n; i++) { + IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); + UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, listeningUser, "onPackageAdded")) continue; + try { + listener.onPackageAdded(user, packageName); + } catch (RemoteException re) { + Slog.d(TAG, "Callback failed ", re); + } } + mListeners.finishBroadcast(); + + super.onPackageAdded(packageName, uid); } - mListeners.finishBroadcast(); - super.onPackageModified(packageName); - } + @Override + public void onPackageRemoved(String packageName, int uid) { + UserHandle user = new UserHandle(getChangingUserId()); + final int n = mListeners.beginBroadcast(); + for (int i = 0; i < n; i++) { + IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); + UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, listeningUser, "onPackageRemoved")) continue; + try { + listener.onPackageRemoved(user, packageName); + } catch (RemoteException re) { + Slog.d(TAG, "Callback failed ", re); + } + } + mListeners.finishBroadcast(); - @Override - public void onPackagesAvailable(String[] packages) { - UserHandle user = new UserHandle(getChangingUserId()); - final int n = mListeners.beginBroadcast(); - for (int i = 0; i < n; i++) { - IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onPackagesAvailable")) continue; - try { - listener.onPackagesAvailable(user, packages, isReplacing()); - } catch (RemoteException re) { - Slog.d(TAG, "Callback failed ", re); + super.onPackageRemoved(packageName, uid); + } + + @Override + public void onPackageModified(String packageName) { + UserHandle user = new UserHandle(getChangingUserId()); + final int n = mListeners.beginBroadcast(); + for (int i = 0; i < n; i++) { + IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); + UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, listeningUser, "onPackageModified")) continue; + try { + listener.onPackageChanged(user, packageName); + } catch (RemoteException re) { + Slog.d(TAG, "Callback failed ", re); + } } + mListeners.finishBroadcast(); + + super.onPackageModified(packageName); } - mListeners.finishBroadcast(); - super.onPackagesAvailable(packages); - } + @Override + public void onPackagesAvailable(String[] packages) { + UserHandle user = new UserHandle(getChangingUserId()); + final int n = mListeners.beginBroadcast(); + for (int i = 0; i < n; i++) { + IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); + UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, listeningUser, "onPackagesAvailable")) continue; + try { + listener.onPackagesAvailable(user, packages, isReplacing()); + } catch (RemoteException re) { + Slog.d(TAG, "Callback failed ", re); + } + } + mListeners.finishBroadcast(); - @Override - public void onPackagesUnavailable(String[] packages) { - UserHandle user = new UserHandle(getChangingUserId()); - final int n = mListeners.beginBroadcast(); - for (int i = 0; i < n; i++) { - IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onPackagesUnavailable")) continue; - try { - listener.onPackagesUnavailable(user, packages, isReplacing()); - } catch (RemoteException re) { - Slog.d(TAG, "Callback failed ", re); + super.onPackagesAvailable(packages); + } + + @Override + public void onPackagesUnavailable(String[] packages) { + UserHandle user = new UserHandle(getChangingUserId()); + final int n = mListeners.beginBroadcast(); + for (int i = 0; i < n; i++) { + IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); + UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, listeningUser, "onPackagesUnavailable")) continue; + try { + listener.onPackagesUnavailable(user, packages, isReplacing()); + } catch (RemoteException re) { + Slog.d(TAG, "Callback failed ", re); + } } + mListeners.finishBroadcast(); + + super.onPackagesUnavailable(packages); } - mListeners.finishBroadcast(); - super.onPackagesUnavailable(packages); } - } - - class PackageCallbackList<T extends IInterface> extends RemoteCallbackList<T> { - @Override - public void onCallbackDied(T callback, Object cookie) { - checkCallbackCount(); + class PackageCallbackList<T extends IInterface> extends RemoteCallbackList<T> { + @Override + public void onCallbackDied(T callback, Object cookie) { + checkCallbackCount(); + } } } -} +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index d70c725..c78249b 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -52,25 +52,49 @@ public final class SELinuxMMAC { private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false; // Signature seinfo values read from policy. - private static HashMap<Signature, Policy> sSigSeinfo = - new HashMap<Signature, Policy>(); + private static HashMap<Signature, Policy> sSigSeinfo = new HashMap<Signature, Policy>(); // Default seinfo read from policy. private static String sDefaultSeinfo = null; - // Locations of potential install policy files. - private static final File[] INSTALL_POLICY_FILE = { - new File(Environment.getDataDirectory(), "security/mac_permissions.xml"), - new File(Environment.getRootDirectory(), "etc/security/mac_permissions.xml"), - null}; + // Data policy override version file. + private static final String DATA_VERSION_FILE = + Environment.getDataDirectory() + "/security/current/selinux_version"; - // Location of seapp_contexts policy file. - private static final String SEAPP_CONTEXTS_FILE = "/seapp_contexts"; + // Base policy version file. + private static final String BASE_VERSION_FILE = "/selinux_version"; + + // Whether override security policies should be loaded. + private static final boolean USE_OVERRIDE_POLICY = useOverridePolicy(); + + // Data override mac_permissions.xml policy file. + private static final String DATA_MAC_PERMISSIONS = + Environment.getDataDirectory() + "/security/current/mac_permissions.xml"; + + // Base mac_permissions.xml policy file. + private static final String BASE_MAC_PERMISSIONS = + Environment.getRootDirectory() + "/etc/security/mac_permissions.xml"; + + // Determine which mac_permissions.xml file to use. + private static final String MAC_PERMISSIONS = USE_OVERRIDE_POLICY ? + DATA_MAC_PERMISSIONS : BASE_MAC_PERMISSIONS; + + // Data override seapp_contexts policy file. + private static final String DATA_SEAPP_CONTEXTS = + Environment.getDataDirectory() + "/security/current/seapp_contexts"; + + // Base seapp_contexts policy file. + private static final String BASE_SEAPP_CONTEXTS = "/seapp_contexts"; + + // Determine which seapp_contexts file to use. + private static final String SEAPP_CONTEXTS = USE_OVERRIDE_POLICY ? + DATA_SEAPP_CONTEXTS : BASE_SEAPP_CONTEXTS; // Stores the hash of the last used seapp_contexts file. private static final String SEAPP_HASH_FILE = Environment.getDataDirectory().toString() + "/system/seapp_hash"; + // Signature policy stanzas static class Policy { private String seinfo; @@ -112,51 +136,17 @@ public final class SELinuxMMAC { sDefaultSeinfo = null; } - /** - * Parses an MMAC install policy from a predefined list of locations. - * @return boolean indicating whether an install policy was correctly parsed. - */ public static boolean readInstallPolicy() { - - return readInstallPolicy(INSTALL_POLICY_FILE); - } - - /** - * Parses an MMAC install policy given as an argument. - * @param policyFile object representing the path of the policy. - * @return boolean indicating whether the install policy was correctly parsed. - */ - public static boolean readInstallPolicy(File policyFile) { - - return readInstallPolicy(new File[]{policyFile,null}); - } - - private static boolean readInstallPolicy(File[] policyFiles) { // Temp structures to hold the rules while we parse the xml file. // We add all the rules together once we know there's no structural problems. HashMap<Signature, Policy> sigSeinfo = new HashMap<Signature, Policy>(); String defaultSeinfo = null; FileReader policyFile = null; - int i = 0; - while (policyFile == null && policyFiles != null && policyFiles[i] != null) { - try { - policyFile = new FileReader(policyFiles[i]); - break; - } catch (FileNotFoundException e) { - Slog.d(TAG,"Couldn't find install policy " + policyFiles[i].getPath()); - } - i++; - } - - if (policyFile == null) { - Slog.d(TAG, "No policy file found. All seinfo values will be null."); - return false; - } - - Slog.d(TAG, "Using install policy file " + policyFiles[i].getPath()); - try { + policyFile = new FileReader(MAC_PERMISSIONS); + Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS); + XmlPullParser parser = Xml.newPullParser(); parser.setInput(policyFile); @@ -199,20 +189,14 @@ public final class SELinuxMMAC { XmlUtils.skipCurrentTag(parser); } } - } catch (XmlPullParserException e) { - // An error outside of a stanza means a structural problem - // with the xml file. So ignore it. - Slog.w(TAG, "Got exception parsing ", e); + } catch (XmlPullParserException xpe) { + Slog.w(TAG, "Got exception parsing " + MAC_PERMISSIONS, xpe); return false; - } catch (IOException e) { - Slog.w(TAG, "Got exception parsing ", e); + } catch (IOException ioe) { + Slog.w(TAG, "Got exception parsing " + MAC_PERMISSIONS, ioe); return false; } finally { - try { - policyFile.close(); - } catch (IOException e) { - //omit - } + IoUtils.closeQuietly(policyFile); } flushInstallPolicy(); @@ -412,7 +396,7 @@ public final class SELinuxMMAC { // Any error with the seapp_contexts file should be fatal byte[] currentHash = null; try { - currentHash = returnHash(SEAPP_CONTEXTS_FILE); + currentHash = returnHash(SEAPP_CONTEXTS); } catch (IOException ioe) { Slog.e(TAG, "Error with hashing seapp_contexts.", ioe); return false; @@ -434,7 +418,7 @@ public final class SELinuxMMAC { */ public static void setRestoreconDone() { try { - final byte[] currentHash = returnHash(SEAPP_CONTEXTS_FILE); + final byte[] currentHash = returnHash(SEAPP_CONTEXTS); dumpHash(new File(SEAPP_HASH_FILE), currentHash); } catch (IOException ioe) { Slog.e(TAG, "Error with saving hash to " + SEAPP_HASH_FILE, ioe); @@ -485,4 +469,21 @@ public final class SELinuxMMAC { throw new RuntimeException(nsae); // impossible } } + + private static boolean useOverridePolicy() { + try { + final String overrideVersion = IoUtils.readFileAsString(DATA_VERSION_FILE); + final String baseVersion = IoUtils.readFileAsString(BASE_VERSION_FILE); + if (overrideVersion.equals(baseVersion)) { + return true; + } + Slog.e(TAG, "Override policy version '" + overrideVersion + "' doesn't match " + + "base version '" + baseVersion + "'. Skipping override policy files."); + } catch (FileNotFoundException fnfe) { + // Override version file doesn't have to exist so silently ignore. + } catch (IOException ioe) { + Slog.w(TAG, "Skipping override policy files.", ioe); + } + return false; + } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 69aeae6..3483fae 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2940,6 +2940,26 @@ final class Settings { file.delete(); file = getUserPackagesStateBackupFile(userId); file.delete(); + removeCrossProfileIntentFiltersToUserLPr(userId); + } + + void removeCrossProfileIntentFiltersToUserLPr(int targetUserId) { + for (int i = 0; i < mCrossProfileIntentResolvers.size(); i++) { + int sourceUserId = mCrossProfileIntentResolvers.keyAt(i); + CrossProfileIntentResolver cpir = mCrossProfileIntentResolvers.get(sourceUserId); + boolean needsWriting = false; + HashSet<CrossProfileIntentFilter> cpifs = + new HashSet<CrossProfileIntentFilter>(cpir.filterSet()); + for (CrossProfileIntentFilter cpif : cpifs) { + if (cpif.getTargetUserId() == targetUserId) { + needsWriting = true; + cpir.removeFilter(cpif); + } + } + if (needsWriting) { + writePackageRestrictionsLPr(sourceUserId); + } + } } // This should be called (at least) whenever an application is removed diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 1bf40e0..7162683 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -107,6 +107,7 @@ public class UserManagerService extends IUserManager.Stub { private static final String ATTR_TYPE_STRING_ARRAY = "sa"; private static final String ATTR_TYPE_STRING = "s"; private static final String ATTR_TYPE_BOOLEAN = "b"; + private static final String ATTR_TYPE_INTEGER = "i"; private static final String USER_INFO_DIR = "system" + File.separator + "users"; private static final String USER_LIST_FILENAME = "userlist.xml"; @@ -1532,16 +1533,18 @@ public class UserManagerService extends IUserManager.Stub { String [] valueStrings = new String[values.size()]; values.toArray(valueStrings); restrictions.putStringArray(key, valueStrings); - } else if (ATTR_TYPE_BOOLEAN.equals(valType)) { - restrictions.putBoolean(key, Boolean.parseBoolean( - parser.nextText().trim())); } else { String value = parser.nextText().trim(); - restrictions.putString(key, value); + if (ATTR_TYPE_BOOLEAN.equals(valType)) { + restrictions.putBoolean(key, Boolean.parseBoolean(value)); + } else if (ATTR_TYPE_INTEGER.equals(valType)) { + restrictions.putInt(key, Integer.parseInt(value)); + } else { + restrictions.putString(key, value); + } } } } - } catch (IOException ioe) { } catch (XmlPullParserException pe) { } finally { @@ -1581,6 +1584,9 @@ public class UserManagerService extends IUserManager.Stub { if (value instanceof Boolean) { serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN); serializer.text(value.toString()); + } else if (value instanceof Integer) { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_INTEGER); + serializer.text(value.toString()); } else if (value == null || value instanceof String) { serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING); serializer.text(value != null ? (String) value : ""); diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 6c38a4c..e52f218 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -33,6 +33,7 @@ import android.database.Cursor; import android.graphics.Rect; import android.net.Uri; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -338,6 +339,41 @@ public final class TvInputManagerService extends SystemService { channels[0].dispose(); } } + + @Override + public void onVideoSizeChanged(int width, int height) throws RemoteException { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onVideoSizeChanged(" + width + ", " + height + ")"); + } + if (sessionState.mSession == null || sessionState.mClient == null) { + return; + } + try { + sessionState.mClient.onVideoSizeChanged(width, height, sessionState.mSeq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onSessionEvent"); + } + } + } + + @Override + public void onSessionEvent(String eventType, Bundle eventArgs) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")"); + } + if (sessionState.mSession == null || sessionState.mClient == null) { + return; + } + try { + sessionState.mClient.onSessionEvent(eventType, eventArgs, + sessionState.mSeq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onSessionEvent"); + } + } + } }; // Create a session. When failed, send a null token immediately. diff --git a/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java b/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java index 3c960c7..55dd4ab 100644 --- a/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java +++ b/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java @@ -40,12 +40,20 @@ public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver { private static final String fileContextsPath = "file_contexts"; private static final String propertyContextsPath = "property_contexts"; private static final String seappContextsPath = "seapp_contexts"; + private static final String versionPath = "selinux_version"; + private static final String macPermissionsPath = "mac_permissions.xml"; public SELinuxPolicyInstallReceiver() { super("/data/security/bundle", "sepolicy_bundle", "metadata/", "version"); } private void backupContexts(File contexts) { + new File(contexts, versionPath).renameTo( + new File(contexts, versionPath + "_backup")); + + new File(contexts, macPermissionsPath).renameTo( + new File(contexts, macPermissionsPath + "_backup")); + new File(contexts, seappContextsPath).renameTo( new File(contexts, seappContextsPath + "_backup")); @@ -60,6 +68,8 @@ public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver { } private void copyUpdate(File contexts) { + new File(updateDir, versionPath).renameTo(new File(contexts, versionPath)); + new File(updateDir, macPermissionsPath).renameTo(new File(contexts, macPermissionsPath)); new File(updateDir, seappContextsPath).renameTo(new File(contexts, seappContextsPath)); new File(updateDir, propertyContextsPath).renameTo(new File(contexts, propertyContextsPath)); new File(updateDir, fileContextsPath).renameTo(new File(contexts, fileContextsPath)); @@ -75,11 +85,13 @@ public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver { } private int[] readChunkLengths(BufferedInputStream bundle) throws IOException { - int[] chunks = new int[4]; + int[] chunks = new int[6]; chunks[0] = readInt(bundle); chunks[1] = readInt(bundle); chunks[2] = readInt(bundle); chunks[3] = readInt(bundle); + chunks[4] = readInt(bundle); + chunks[5] = readInt(bundle); return chunks; } @@ -94,10 +106,12 @@ public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver { BufferedInputStream stream = new BufferedInputStream(new FileInputStream(updateContent)); try { int[] chunkLengths = readChunkLengths(stream); - installFile(new File(updateDir, seappContextsPath), stream, chunkLengths[0]); - installFile(new File(updateDir, propertyContextsPath), stream, chunkLengths[1]); - installFile(new File(updateDir, fileContextsPath), stream, chunkLengths[2]); - installFile(new File(updateDir, sepolicyPath), stream, chunkLengths[3]); + installFile(new File(updateDir, versionPath), stream, chunkLengths[0]); + installFile(new File(updateDir, macPermissionsPath), stream, chunkLengths[1]); + installFile(new File(updateDir, seappContextsPath), stream, chunkLengths[2]); + installFile(new File(updateDir, propertyContextsPath), stream, chunkLengths[3]); + installFile(new File(updateDir, fileContextsPath), stream, chunkLengths[4]); + installFile(new File(updateDir, sepolicyPath), stream, chunkLengths[5]); } finally { IoUtils.closeQuietly(stream); } 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/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 266527d..008d2fc 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -36,6 +36,7 @@ import android.util.TimeUtils; import android.view.Display; import android.view.SurfaceControl; import android.view.WindowManagerPolicy; +import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import com.android.server.wm.WindowManagerService.LayoutFields; @@ -50,6 +51,9 @@ import java.util.ArrayList; public class WindowAnimator { private static final String TAG = "WindowAnimator"; + /** How long to give statusbar to clear the private keyguard flag when animating out */ + private static final long KEYGUARD_ANIM_TIMEOUT_MS = 1000; + final WindowManagerService mService; final Context mContext; final WindowManagerPolicy mPolicy; @@ -82,6 +86,8 @@ public class WindowAnimator { boolean mInitialized = false; + boolean mKeyguardGoingAway; + // forceHiding states. static final int KEYGUARD_NOT_SHOWN = 0; static final int KEYGUARD_ANIMATING_IN = 1; @@ -213,6 +219,29 @@ public class WindowAnimator { final WindowList windows = mService.getWindowListLocked(displayId); ArrayList<WindowStateAnimator> unForceHiding = null; boolean wallpaperInUnForceHiding = false; + + if (mKeyguardGoingAway) { + for (int i = windows.size() - 1; i >= 0; i--) { + WindowState win = windows.get(i); + if (!mPolicy.isKeyguardHostWindow(win.mAttrs)) { + continue; + } + final WindowStateAnimator winAnimator = win.mWinAnimator; + if (mPolicy.doesForceHide(win.mAttrs)) { + if (!winAnimator.mAnimating) { + // Create a new animation to delay until keyguard is gone on its own. + winAnimator.mAnimation = new AlphaAnimation(1.0f, 1.0f); + winAnimator.mAnimation.setDuration(KEYGUARD_ANIM_TIMEOUT_MS); + winAnimator.mAnimationIsEntrance = false; + } + } else { + mKeyguardGoingAway = false; + winAnimator.clearAnimation(); + } + break; + } + } + mForceHiding = KEYGUARD_NOT_SHOWN; for (int i = windows.size() - 1; i >= 0; i--) { @@ -239,7 +268,7 @@ public class WindowAnimator { } } - if (mPolicy.doesForceHide(win, win.mAttrs)) { + if (mPolicy.doesForceHide(win.mAttrs)) { if (!wasAnimating && nowAnimating) { if (WindowManagerService.DEBUG_ANIM || WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, @@ -252,6 +281,11 @@ public class WindowAnimator { getPendingLayoutChanges(displayId)); } mService.mFocusMayChange = true; + } else if (mKeyguardGoingAway && !nowAnimating) { + // Timeout!! + Slog.e(TAG, "Timeout waiting for animation to startup"); + mPolicy.startKeyguardExitAnimation(0, 0); + mKeyguardGoingAway = false; } if (win.isReadyForDisplay()) { if (nowAnimating) { @@ -265,7 +299,7 @@ public class WindowAnimator { } } if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, - "Force hide " + mForceHiding + "Force hide " + forceHidingToString() + " hasSurface=" + win.mHasSurface + " policyVis=" + win.mPolicyVisibility + " destroying=" + win.mDestroying @@ -349,12 +383,20 @@ public class WindowAnimator { // If we have windows that are being show due to them no longer // being force-hidden, apply the appropriate animation to them. if (unForceHiding != null) { + boolean startKeyguardExit = true; for (int i=unForceHiding.size()-1; i>=0; i--) { Animation a = mPolicy.createForceHideEnterAnimation(wallpaperInUnForceHiding); if (a != null) { final WindowStateAnimator winAnimator = unForceHiding.get(i); winAnimator.setAnimation(a); winAnimator.mAnimationIsEntrance = true; + if (startKeyguardExit) { + // Do one time only. + mPolicy.startKeyguardExitAnimation(mCurrentTime + a.getStartOffset(), + a.getDuration()); + mKeyguardGoingAway = false; + startKeyguardExit = false; + } } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index d40f562..05502cf 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5139,6 +5139,18 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void keyguardGoingAway() { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires DISABLE_KEYGUARD permission"); + } + synchronized (mWindowMap) { + mAnimator.mKeyguardGoingAway = true; + requestTraversalLocked(); + } + } + + @Override public void closeSystemDialogs(String reason) { synchronized(mWindowMap) { final int numDisplays = mDisplayContents.size(); @@ -7163,9 +7175,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int TAP_OUTSIDE_STACK = 31; public static final int NOTIFY_ACTIVITY_DRAWN = 32; - public static final int REMOVE_STARTING_TIMEOUT = 33; - - public static final int SHOW_DISPLAY_MASK = 34; + public static final int SHOW_DISPLAY_MASK = 33; @Override public void handleMessage(Message msg) { @@ -10283,10 +10293,6 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.lockNow(options); } - public void showRecentApps() { - mPolicy.showRecentApps(); - } - @Override public boolean isSafeModeEnabled() { return mSafeMode; diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp index c6de676..a734026 100644 --- a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp +++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp @@ -240,13 +240,6 @@ void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) { var = env->GetMethodID(clazz, methodName, methodDescriptor); \ LOG_FATAL_IF(! var, "Unable to find method " methodName); -// TODO: replace above code with following once -// replace old HdmiCecService with HdmiControlService -#undef HDMI_CEC_HARDWARE_MODULE_ID -#define HDMI_CEC_HARDWARE_MODULE_ID "hdmi_cec_module" -#undef HDMI_CEC_HARDWARE_INTERFACE -#define HDMI_CEC_HARDWARE_INTERFACE "hdmi_cec_module_hw_if" - static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj, jobject messageQueueObj) { int err; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d78fb13..a52396e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -130,6 +130,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final boolean DBG = false; + private static final String ATTR_PERMISSION_PROVIDER = "permission-provider"; + final Context mContext; final UserManager mUserManager; final PowerManager.WakeLock mWakeLock; @@ -190,6 +192,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // This is the list of component allowed to start lock task mode. final List<ComponentName> mLockTaskComponents = new ArrayList<ComponentName>(); + ComponentName mRestrictionsProvider; + public DevicePolicyData(int userHandle) { mUserHandle = userHandle; } @@ -944,6 +948,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.startDocument(null, true); out.startTag(null, "policies"); + if (policy.mRestrictionsProvider != null) { + out.attribute(null, ATTR_PERMISSION_PROVIDER, + policy.mRestrictionsProvider.flattenToString()); + } final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { @@ -1039,6 +1047,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throw new XmlPullParserException( "Settings do not start with policies tag: found " + tag); } + + // Extract the permission provider component name if available + String permissionProvider = parser.getAttributeValue(null, ATTR_PERMISSION_PROVIDER); + if (permissionProvider != null) { + policy.mRestrictionsProvider = ComponentName.unflattenFromString(permissionProvider); + } + type = parser.next(); int outerDepth = parser.getDepth(); policy.mLockTaskComponents.clear(); @@ -3303,6 +3318,32 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @Override + public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + + int userHandle = UserHandle.getCallingUserId(); + DevicePolicyData userData = getUserData(userHandle); + userData.mRestrictionsProvider = permissionProvider; + saveSettingsLocked(userHandle); + } + } + + @Override + public ComponentName getRestrictionsProvider(int userHandle) { + synchronized (this) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the system can query the permission provider"); + } + DevicePolicyData userData = getUserData(userHandle); + return userData != null ? userData.mRestrictionsProvider : null; + } + } + public void addCrossProfileIntentFilter(ComponentName who, IntentFilter filter, int flags) { int callingUserId = UserHandle.getCallingUserId(); synchronized (this) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 0f24ff6..9174c0c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -80,6 +80,7 @@ import com.android.server.pm.PackageManagerService; import com.android.server.pm.UserManagerService; import com.android.server.power.PowerManagerService; import com.android.server.power.ShutdownThread; +import com.android.server.restrictions.RestrictionsManagerService; import com.android.server.search.SearchManagerService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; @@ -330,7 +331,6 @@ public final class SystemServer { IPackageManager pm = null; WindowManagerService wm = null; BluetoothManagerService bluetooth = null; - DockObserver dock = null; UsbService usb = null; SerialService serial = null; RecognitionManagerService recognition = null; @@ -789,13 +789,7 @@ public final class SystemServer { } if (!disableNonCoreServices) { - try { - Slog.i(TAG, "Dock Observer"); - // Listen for dock station changes - dock = new DockObserver(context); - } catch (Throwable e) { - reportWtf("starting DockObserver", e); - } + mSystemServiceManager.startService(DockObserver.class); } if (!disableMedia) { @@ -948,6 +942,12 @@ public final class SystemServer { } try { + mSystemServiceManager.startService(RestrictionsManagerService.class); + } catch (Throwable e) { + reportWtf("starting RestrictionsManagerService", e); + } + + try { mSystemServiceManager.startService(MediaSessionService.class); } catch (Throwable e) { reportWtf("starting MediaSessionService", e); @@ -998,8 +998,7 @@ public final class SystemServer { try { Slog.i(TAG, "LauncherAppsService"); - LauncherAppsService las = new LauncherAppsService(context); - ServiceManager.addService(Context.LAUNCHER_APPS_SERVICE, las); + mSystemServiceManager.startService(LauncherAppsService.class); } catch (Throwable t) { reportWtf("starting LauncherAppsService", t); } @@ -1085,7 +1084,6 @@ public final class SystemServer { final NetworkPolicyManagerService networkPolicyF = networkPolicy; final ConnectivityService connectivityF = connectivity; final NetworkScoreService networkScoreF = networkScore; - final DockObserver dockF = dock; final WallpaperManagerService wallpaperF = wallpaper; final InputMethodManagerService immF = imm; final RecognitionManagerService recognitionF = recognition; @@ -1159,11 +1157,6 @@ public final class SystemServer { reportWtf("making Connectivity Service ready", e); } try { - if (dockF != null) dockF.systemReady(); - } catch (Throwable e) { - reportWtf("making Dock Service ready", e); - } - try { if (recognitionF != null) recognitionF.systemReady(); } catch (Throwable e) { reportWtf("making Recognition Service ready", e); diff --git a/services/restrictions/Android.mk b/services/restrictions/Android.mk new file mode 100644 index 0000000..fcf8626 --- /dev/null +++ b/services/restrictions/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := services.restrictions + +LOCAL_SRC_FILES += \ + $(call all-java-files-under,java) + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java new file mode 100644 index 0000000..e1f77b3 --- /dev/null +++ b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java @@ -0,0 +1,171 @@ +/* + * 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.restrictions; + +import android.Manifest; +import android.app.AppGlobals; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.admin.IDevicePolicyManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IRestrictionsManager; +import android.content.RestrictionsManager; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.IUserManager; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; + +import com.android.internal.util.ArrayUtils; +import com.android.server.SystemService; + +/** + * SystemService wrapper for the RestrictionsManager implementation. Publishes the + * Context.RESTRICTIONS_SERVICE. + */ + +public final class RestrictionsManagerService extends SystemService { + private final RestrictionsManagerImpl mRestrictionsManagerImpl; + + public RestrictionsManagerService(Context context) { + super(context); + mRestrictionsManagerImpl = new RestrictionsManagerImpl(context); + } + + @Override + public void onStart() { + publishBinderService(Context.RESTRICTIONS_SERVICE, mRestrictionsManagerImpl); + } + + class RestrictionsManagerImpl extends IRestrictionsManager.Stub { + private final Context mContext; + private final IUserManager mUm; + private final IDevicePolicyManager mDpm; + + public RestrictionsManagerImpl(Context context) { + mContext = context; + mUm = (IUserManager) getBinderService(Context.USER_SERVICE); + mDpm = (IDevicePolicyManager) getBinderService(Context.DEVICE_POLICY_SERVICE); + } + + @Override + public Bundle getApplicationRestrictions(String packageName) throws RemoteException { + return mUm.getApplicationRestrictions(packageName); + } + + @Override + public boolean hasRestrictionsProvider() throws RemoteException { + int userHandle = UserHandle.getCallingUserId(); + if (mDpm != null) { + long ident = Binder.clearCallingIdentity(); + try { + return mDpm.getRestrictionsProvider(userHandle) != null; + } finally { + Binder.restoreCallingIdentity(ident); + } + } else { + return false; + } + } + + @Override + public void requestPermission(String packageName, String requestTemplate, + Bundle requestData) throws RemoteException { + int callingUid = Binder.getCallingUid(); + int userHandle = UserHandle.getUserId(callingUid); + if (mDpm != null) { + long ident = Binder.clearCallingIdentity(); + try { + ComponentName restrictionsProvider = + mDpm.getRestrictionsProvider(userHandle); + // Check if there is a restrictions provider + if (restrictionsProvider == null) { + throw new IllegalStateException( + "Cannot request permission without a restrictions provider registered"); + } + // Check that the packageName matches the caller. + enforceCallerMatchesPackage(callingUid, packageName, "Package name does not" + + " match caller "); + // Prepare and broadcast the intent to the provider + Intent intent = new Intent(RestrictionsManager.ACTION_REQUEST_PERMISSION); + intent.setComponent(restrictionsProvider); + intent.putExtra(RestrictionsManager.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(RestrictionsManager.EXTRA_TEMPLATE_ID, requestTemplate); + intent.putExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE, requestData); + mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + private void enforceCallerMatchesPackage(int callingUid, String packageName, + String message) { + try { + String[] pkgs = AppGlobals.getPackageManager().getPackagesForUid(callingUid); + if (pkgs != null) { + if (!ArrayUtils.contains(pkgs, packageName)) { + throw new SecurityException(message + callingUid); + } + } + } catch (RemoteException re) { + // Shouldn't happen + } + } + + @Override + public void notifyPermissionResponse(String packageName, Bundle response) + throws RemoteException { + // Check caller + int callingUid = Binder.getCallingUid(); + int userHandle = UserHandle.getUserId(callingUid); + if (mDpm != null) { + long ident = Binder.clearCallingIdentity(); + try { + ComponentName permProvider = mDpm.getRestrictionsProvider(userHandle); + if (permProvider == null) { + throw new SecurityException("No restrictions provider registered for user"); + } + enforceCallerMatchesPackage(callingUid, permProvider.getPackageName(), + "Restrictions provider does not match caller "); + + // Post the response to target package + Intent responseIntent = new Intent( + RestrictionsManager.ACTION_PERMISSION_RESPONSE_RECEIVED); + responseIntent.setPackage(packageName); + responseIntent.putExtra(RestrictionsManager.EXTRA_RESPONSE_BUNDLE, response); + mContext.sendBroadcastAsUser(responseIntent, new UserHandle(userHandle)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + } +} diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 7848b1d..636dd4d 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -35,7 +35,8 @@ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> - + <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> + <application> <uses-library android:name="android.test.runner" /> @@ -53,6 +54,15 @@ </intent-filter> </service> + <receiver android:name="com.android.server.devicepolicy.ApplicationRestrictionsTest$AdminReceiver" + android:permission="android.permission.BIND_DEVICE_ADMIN"> + <meta-data android:name="android.app.device_admin" + android:resource="@xml/device_admin_sample" /> + <intent-filter> + <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> + </intent-filter> + </receiver> + </application> <instrumentation diff --git a/services/tests/servicestests/res/xml/device_admin_sample.xml b/services/tests/servicestests/res/xml/device_admin_sample.xml new file mode 100644 index 0000000..032debb --- /dev/null +++ b/services/tests/servicestests/res/xml/device_admin_sample.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<device-admin xmlns:android="http://schemas.android.com/apk/res/android"> + <uses-policies> + <limit-password /> + <watch-login /> + <reset-password /> + <force-lock /> + <wipe-data /> + <expire-password /> + <encrypted-storage /> + <disable-camera /> + <disable-keyguard-features /> + </uses-policies> +</device-admin> diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java new file mode 100644 index 0000000..8e8e4e6 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java @@ -0,0 +1,135 @@ +/* + * 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.devicepolicy; + +import android.app.admin.DeviceAdminReceiver; +import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.UserHandle; +import android.provider.Settings; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +/** + * Tests for application restrictions persisting via profile owner: + * make -j FrameworksServicesTests + * runtest --path frameworks/base/services/tests/servicestests/ \ + * src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java + */ +public class ApplicationRestrictionsTest extends AndroidTestCase { + + static DevicePolicyManager sDpm; + static ComponentName sAdminReceiver; + private static final String RESTRICTED_APP = "com.example.restrictedApp"; + static boolean sAddBack = false; + + public static class AdminReceiver extends DeviceAdminReceiver { + + @Override + public void onDisabled(Context context, Intent intent) { + if (sAddBack) { + sDpm.setActiveAdmin(sAdminReceiver, false); + sAddBack = false; + } + + super.onDisabled(context, intent); + } + } + + @Override + public void setUp() { + final Context context = getContext(); + sAdminReceiver = new ComponentName(mContext.getPackageName(), + AdminReceiver.class.getName()); + sDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); + Settings.Secure.putInt(context.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0); + sDpm.setProfileOwner(context.getPackageName(), "Test", UserHandle.myUserId()); + Settings.Secure.putInt(context.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 1); + // Remove the admin if already registered. It's async, so add it back + // when the admin gets a broadcast. Otherwise add it back right away. + if (sDpm.isAdminActive(sAdminReceiver)) { + sAddBack = true; + sDpm.removeActiveAdmin(sAdminReceiver); + } else { + sDpm.setActiveAdmin(sAdminReceiver, false); + } + } + + @Override + public void tearDown() { + Settings.Secure.putInt(getContext().getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0); + sDpm.removeActiveAdmin(sAdminReceiver); + Settings.Secure.putInt(getContext().getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 1); + } + + public void testSettingRestrictions() { + Bundle restrictions = new Bundle(); + restrictions.putString("KEY_STRING", "Foo"); + assertNotNull(sDpm.getApplicationRestrictions(sAdminReceiver, RESTRICTED_APP)); + sDpm.setApplicationRestrictions(sAdminReceiver, RESTRICTED_APP, restrictions); + Bundle returned = sDpm.getApplicationRestrictions(sAdminReceiver, RESTRICTED_APP); + assertNotNull(returned); + assertEquals(returned.size(), 1); + assertEquals(returned.get("KEY_STRING"), "Foo"); + sDpm.setApplicationRestrictions(sAdminReceiver, RESTRICTED_APP, new Bundle()); + returned = sDpm.getApplicationRestrictions(sAdminReceiver, RESTRICTED_APP); + assertEquals(returned.size(), 0); + } + + public void testRestrictionTypes() { + Bundle restrictions = new Bundle(); + restrictions.putString("KEY_STRING", "Foo"); + restrictions.putInt("KEY_INT", 7); + restrictions.putBoolean("KEY_BOOLEAN", true); + restrictions.putBoolean("KEY_BOOLEAN_2", false); + restrictions.putString("KEY_STRING_2", "Bar"); + restrictions.putStringArray("KEY_STR_ARRAY", new String[] { "Foo", "Bar" }); + sDpm.setApplicationRestrictions(sAdminReceiver, RESTRICTED_APP, restrictions); + Bundle returned = sDpm.getApplicationRestrictions(sAdminReceiver, RESTRICTED_APP); + assertTrue(returned.getBoolean("KEY_BOOLEAN")); + assertFalse(returned.getBoolean("KEY_BOOLEAN_2")); + assertFalse(returned.getBoolean("KEY_BOOLEAN_3")); + assertEquals(returned.getInt("KEY_INT"), 7); + assertTrue(returned.get("KEY_BOOLEAN") instanceof Boolean); + assertTrue(returned.get("KEY_INT") instanceof Integer); + assertEquals(returned.get("KEY_STRING"), "Foo"); + assertEquals(returned.get("KEY_STRING_2"), "Bar"); + assertTrue(returned.getStringArray("KEY_STR_ARRAY") instanceof String[]); + sDpm.setApplicationRestrictions(sAdminReceiver, RESTRICTED_APP, new Bundle()); + } + + public void testTextEscaping() { + String fancyText = "<This contains XML/> <JSON> " + + "{ \"One\": { \"OneOne\": \"11\", \"OneTwo\": \"12\" }, \"Two\": \"2\" } <JSON/>"; + Bundle restrictions = new Bundle(); + restrictions.putString("KEY_FANCY_TEXT", fancyText); + sDpm.setApplicationRestrictions(sAdminReceiver, RESTRICTED_APP, restrictions); + Bundle returned = sDpm.getApplicationRestrictions(sAdminReceiver, RESTRICTED_APP); + assertEquals(returned.getString("KEY_FANCY_TEXT"), fancyText); + } +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/servicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java new file mode 100644 index 0000000..a6fdee9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java @@ -0,0 +1,152 @@ +/* + * 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.os.Bundle; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.SpannableString; + +import java.util.ArrayList; +import java.util.Arrays; + +public class ValidateNotificationPeopleTest extends AndroidTestCase { + + @SmallTest + public void testNoExtra() throws Exception { + Bundle bundle = new Bundle(); + String[] result = ValidateNotificationPeople.getExtraPeople(bundle); + assertNull("lack of extra should return null", result); + } + + @SmallTest + public void testSingleString() throws Exception { + String[] expected = { "foobar" }; + Bundle bundle = new Bundle(); + bundle.putString(Notification.EXTRA_PEOPLE, expected[0]); + String[] result = ValidateNotificationPeople.getExtraPeople(bundle); + assertStringArrayEquals("string should be in result[0]", expected, result); + } + + @SmallTest + public void testSingleCharArray() throws Exception { + String[] expected = { "foobar" }; + Bundle bundle = new Bundle(); + bundle.putCharArray(Notification.EXTRA_PEOPLE, expected[0].toCharArray()); + String[] result = ValidateNotificationPeople.getExtraPeople(bundle); + assertStringArrayEquals("char[] should be in result[0]", expected, result); + } + + @SmallTest + public void testSingleCharSequence() throws Exception { + String[] expected = { "foobar" }; + Bundle bundle = new Bundle(); + bundle.putCharSequence(Notification.EXTRA_PEOPLE, new SpannableString(expected[0])); + String[] result = ValidateNotificationPeople.getExtraPeople(bundle); + assertStringArrayEquals("charSequence should be in result[0]", expected, result); + } + + @SmallTest + public void testStringArraySingle() throws Exception { + Bundle bundle = new Bundle(); + String[] expected = { "foobar" }; + bundle.putStringArray(Notification.EXTRA_PEOPLE, expected); + String[] result = ValidateNotificationPeople.getExtraPeople(bundle); + assertStringArrayEquals("wrapped string should be in result[0]", expected, result); + } + + @SmallTest + public void testStringArrayMultiple() throws Exception { + Bundle bundle = new Bundle(); + String[] expected = { "foo", "bar", "baz" }; + bundle.putStringArray(Notification.EXTRA_PEOPLE, expected); + String[] result = ValidateNotificationPeople.getExtraPeople(bundle); + assertStringArrayEquals("testStringArrayMultiple", expected, result); + } + + @SmallTest + public void testStringArrayNulls() throws Exception { + Bundle bundle = new Bundle(); + String[] expected = { "foo", null, "baz" }; + bundle.putStringArray(Notification.EXTRA_PEOPLE, expected); + String[] result = ValidateNotificationPeople.getExtraPeople(bundle); + assertStringArrayEquals("testStringArrayNulls", expected, result); + } + + @SmallTest + public void testCharSequenceArrayMultiple() throws Exception { + Bundle bundle = new Bundle(); + String[] expected = { "foo", "bar", "baz" }; + CharSequence[] charSeqArray = new CharSequence[expected.length]; + for (int i = 0; i < expected.length; i++) { + charSeqArray[i] = new SpannableString(expected[i]); + } + bundle.putCharSequenceArray(Notification.EXTRA_PEOPLE, charSeqArray); + String[] result = ValidateNotificationPeople.getExtraPeople(bundle); + assertStringArrayEquals("testCharSequenceArrayMultiple", expected, result); + } + + @SmallTest + public void testMixedCharSequenceArrayList() throws Exception { + Bundle bundle = new Bundle(); + String[] expected = { "foo", "bar", "baz" }; + CharSequence[] charSeqArray = new CharSequence[expected.length]; + for (int i = 0; i < expected.length; i++) { + if (i % 2 == 0) { + charSeqArray[i] = expected[i]; + } else { + charSeqArray[i] = new SpannableString(expected[i]); + } + } + bundle.putCharSequenceArray(Notification.EXTRA_PEOPLE, charSeqArray); + String[] result = ValidateNotificationPeople.getExtraPeople(bundle); + assertStringArrayEquals("testMixedCharSequenceArrayList", expected, result); + } + + @SmallTest + public void testStringArrayList() throws Exception { + Bundle bundle = new Bundle(); + String[] expected = { "foo", null, "baz" }; + final ArrayList<String> stringArrayList = new ArrayList<String>(expected.length); + for (int i = 0; i < expected.length; i++) { + stringArrayList.add(expected[i]); + } + bundle.putStringArrayList(Notification.EXTRA_PEOPLE, stringArrayList); + String[] result = ValidateNotificationPeople.getExtraPeople(bundle); + assertStringArrayEquals("testStringArrayList", expected, result); + } + + @SmallTest + public void testCharSequenceArrayList() throws Exception { + Bundle bundle = new Bundle(); + String[] expected = { "foo", "bar", "baz" }; + final ArrayList<CharSequence> stringArrayList = + new ArrayList<CharSequence>(expected.length); + for (int i = 0; i < expected.length; i++) { + stringArrayList.add(new SpannableString(expected[i])); + } + bundle.putCharSequenceArrayList(Notification.EXTRA_PEOPLE, stringArrayList); + String[] result = ValidateNotificationPeople.getExtraPeople(bundle); + assertStringArrayEquals("testCharSequenceArrayList", expected, result); + } + + private void assertStringArrayEquals(String message, String[] expected, String[] result) { + String expectedString = Arrays.toString(expected); + String resultString = Arrays.toString(result); + assertEquals(message + ": arrays differ", expectedString, resultString); + } +} diff --git a/telecomm/java/android/telecomm/CallServiceAdapter.java b/telecomm/java/android/telecomm/CallServiceAdapter.java index d5bb989..dafc310 100644 --- a/telecomm/java/android/telecomm/CallServiceAdapter.java +++ b/telecomm/java/android/telecomm/CallServiceAdapter.java @@ -156,5 +156,17 @@ public final class CallServiceAdapter { } } + /** + * Asks Telecomm to start or stop a ringback tone for a call. + * + * @param callId The unique ID of the call whose ringback is being changed. + * @param ringback Whether Telecomm should start playing a ringback tone. + */ + public void setRequestingRingback(String callId, boolean ringback) { + try { + mAdapter.setRequestingRingback(callId, ringback); + } catch (RemoteException e) { + } + } } diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java index 88de17a..8cce8e6 100644 --- a/telecomm/java/android/telecomm/Connection.java +++ b/telecomm/java/android/telecomm/Connection.java @@ -33,6 +33,7 @@ public abstract class Connection { void onHandleChanged(Connection c, Uri newHandle); void onSignalChanged(Connection c, Bundle details); void onDisconnected(Connection c, int cause, String message); + void onRequestingRingback(Connection c, boolean ringback); void onDestroyed(Connection c); } @@ -60,6 +61,10 @@ public abstract class Connection { /** {@inheritDoc} */ @Override public void onDestroyed(Connection c) {} + + /** {@inheritDoc} */ + @Override + public void onRequestingRingback(Connection c, boolean ringback) {} } public final class State { @@ -77,6 +82,7 @@ public abstract class Connection { private int mState = State.NEW; private CallAudioState mCallAudioState; private Uri mHandle; + private boolean mRequestingRingback = false; /** * Create a new Connection. @@ -268,6 +274,14 @@ public abstract class Connection { } /** + * @return Whether this connection is requesting that the system play a ringback tone + * on its behalf. + */ + public boolean isRequestingRingback() { + return mRequestingRingback; + } + + /** * Sets the value of the {@link #getHandle()} property and notifies listeners. * * @param handle The new handle. @@ -286,6 +300,7 @@ public abstract class Connection { * communicate). */ protected void setActive() { + setRequestingRingback(false); setState(State.ACTIVE); } @@ -329,6 +344,21 @@ public abstract class Connection { } /** + * Requests that the framework play a ringback tone. This is to be invoked by implementations + * that do not play a ringback tone themselves in the call's audio stream. + * + * @param ringback Whether the ringback tone is to be played. + */ + protected void setRequestingRingback(boolean ringback) { + if (mRequestingRingback != ringback) { + mRequestingRingback = ringback; + for (Listener l : mListeners) { + l.onRequestingRingback(this, ringback); + } + } + } + + /** * Notifies this Connection and listeners that the {@link #getCallAudioState()} property * has a new value. * @@ -336,7 +366,7 @@ public abstract class Connection { */ protected void onSetAudioState(CallAudioState state) { // TODO: Enforce super called - this.mCallAudioState = state; + mCallAudioState = state; for (Listener l : mListeners) { l.onAudioStateChanged(this, state); } @@ -356,6 +386,21 @@ public abstract class Connection { } /** + * Notifies this Connection of an internal state change. This method is called before the + * state is actually changed. Overriding implementations must call + * {@code super.onSetState(state)}. + * + * @param state The new state, a {@link Connection.State} member. + */ + protected void onSetState(int state) { + // TODO: Enforce super called + this.mState = state; + for (Listener l : mListeners) { + l.onStateChanged(this, state); + } + } + + /** * Notifies this Connection of a request to play a DTMF tone. * * @param c A DTMF character. @@ -401,9 +446,6 @@ public abstract class Connection { private void setState(int state) { Log.d(this, "setState: %s", stateToString(state)); - this.mState = state; - for (Listener l : mListeners) { - l.onStateChanged(this, state); - } + onSetState(state); } } diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java index 492b08e..8d02842 100644 --- a/telecomm/java/android/telecomm/ConnectionService.java +++ b/telecomm/java/android/telecomm/ConnectionService.java @@ -89,6 +89,13 @@ public abstract class ConnectionService extends CallService { public void onDestroyed(Connection c) { removeConnection(c); } + + @Override + public void onRequestingRingback(Connection c, boolean ringback) { + String id = mIdByConnection.get(c); + Log.d(this, "Adapter onRingback %b", ringback); + getAdapter().setRequestingRingback(id, ringback); + } }; @Override 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/ICallServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl index dfdaa75..6d36494 100644 --- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl +++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl @@ -43,4 +43,6 @@ oneway interface ICallServiceAdapter { void setDisconnected(String callId, int disconnectCause, String disconnectMessage); void setOnHold(String callId); + + void setRequestingRingback(String callId, boolean ringing); } 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 2483419..ffa9a4e 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1988,9 +1988,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; } @@ -1999,9 +2000,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/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index c162bf2..a54936b 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -608,4 +608,9 @@ public class MockContext extends Context { public File[] getExternalCacheDirs() { throw new UnsupportedOperationException(); } + + @Override + public File[] getExternalMediaDirs() { + throw new UnsupportedOperationException(); + } } diff --git a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java index 158f5e4..ee407ad 100644 --- a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java +++ b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java @@ -99,10 +99,10 @@ public class OnePlayerActivity extends Activity { switch (v.getId()) { case R.id.play_button: Log.d(TAG, "Play button pressed, in state " + mPlaybackState); - if (mPlaybackState == PlaybackState.PLAYSTATE_PAUSED - || mPlaybackState == PlaybackState.PLAYSTATE_STOPPED) { + if (mPlaybackState == PlaybackState.STATE_PAUSED + || mPlaybackState == PlaybackState.STATE_STOPPED) { mPlayer.play(); - } else if (mPlaybackState == PlaybackState.PLAYSTATE_PLAYING) { + } else if (mPlaybackState == PlaybackState.STATE_PLAYING) { mPlayer.pause(); } break; @@ -126,31 +126,31 @@ public class OnePlayerActivity extends Activity { boolean enableControls = true; StringBuilder statusBuilder = new StringBuilder(); switch (mPlaybackState) { - case PlaybackState.PLAYSTATE_PLAYING: + case PlaybackState.STATE_PLAYING: statusBuilder.append("playing"); mPlayButton.setText("Pause"); enablePlay = true; break; - case PlaybackState.PLAYSTATE_PAUSED: + case PlaybackState.STATE_PAUSED: statusBuilder.append("paused"); mPlayButton.setText("Play"); enablePlay = true; break; - case PlaybackState.PLAYSTATE_STOPPED: + case PlaybackState.STATE_STOPPED: statusBuilder.append("ended"); mPlayButton.setText("Play"); enablePlay = true; break; - case PlaybackState.PLAYSTATE_ERROR: + case PlaybackState.STATE_ERROR: statusBuilder.append("error: ").append(state.getErrorMessage()); break; - case PlaybackState.PLAYSTATE_BUFFERING: + case PlaybackState.STATE_BUFFERING: statusBuilder.append("buffering"); break; - case PlaybackState.PLAYSTATE_NONE: + case PlaybackState.STATE_NONE: statusBuilder.append("none"); break; - case PlaybackState.PLAYSTATE_CONNECTING: + case PlaybackState.STATE_CONNECTING: statusBuilder.append("connecting"); enableControls = false; break; diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java index 9f7bb26..145b389 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java @@ -21,7 +21,6 @@ import android.media.session.MediaController; import android.media.session.RouteInfo; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; -import android.media.session.TransportController; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -42,12 +41,11 @@ public class PlayerController { protected MediaController mController; protected IPlayerService mBinder; - protected TransportController mTransportControls; + protected MediaController.TransportControls mTransportControls; private final Intent mServiceIntent; private Context mContext; private Listener mListener; - private TransportListener mTransportListener = new TransportListener(); private SessionCallback mControllerCb; private MediaSessionManager mManager; private Handler mHandler = new Handler(); @@ -161,16 +159,13 @@ public class PlayerController { return; } mController.addCallback(mControllerCb, mHandler); - mTransportControls = mController.getTransportController(); - if (mTransportControls != null) { - mTransportControls.addStateListener(mTransportListener); - } + mTransportControls = mController.getTransportControls(); Log.d(TAG, "Ready to use PlayerService"); if (mListener != null) { mListener.onConnectionStateChange(STATE_CONNECTED); if (mTransportControls != null) { - mListener.onPlaybackStateChange(mTransportControls.getPlaybackState()); + mListener.onPlaybackStateChange(mController.getPlaybackState()); } } } @@ -181,9 +176,7 @@ public class PlayerController { public void onRouteChanged(RouteInfo route) { // TODO } - } - private class TransportListener extends TransportController.TransportStateListener { @Override public void onPlaybackStateChanged(PlaybackState state) { if (state == null) { diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerService.java b/tests/OneMedia/src/com/android/onemedia/PlayerService.java index 0ad6dd1..934f4ef 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerService.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerService.java @@ -103,11 +103,11 @@ public class PlayerService extends Service { @Override public void onPlayStateChanged(PlaybackState state) { switch (state.getState()) { - case PlaybackState.PLAYSTATE_PLAYING: + case PlaybackState.STATE_PLAYING: onPlaybackStarted(); break; - case PlaybackState.PLAYSTATE_STOPPED: - case PlaybackState.PLAYSTATE_ERROR: + case PlaybackState.STATE_STOPPED: + case PlaybackState.STATE_ERROR: onPlaybackEnded(); break; } diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java index 94d0851..c1fa74f 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java @@ -25,7 +25,6 @@ import android.media.session.MediaSession; import android.media.session.MediaSessionManager; import android.media.session.MediaSessionToken; import android.media.session.PlaybackState; -import android.media.session.TransportPerformer; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; @@ -45,7 +44,6 @@ public class PlayerSession { protected Renderer mRenderer; protected MediaSession.Callback mCallback; protected Renderer.Listener mRenderListener; - protected TransportPerformer mPerformer; protected PlaybackState mPlaybackState; protected Listener mListener; @@ -84,9 +82,8 @@ public class PlayerSession { Log.d(TAG, "Creating session for package " + mContext.getBasePackageName()); mSession = man.createSession("OneMedia"); mSession.addCallback(mCallback); - mPerformer = mSession.getTransportPerformer(); - mPerformer.addListener(new TransportListener()); - mPerformer.setPlaybackState(mPlaybackState); + mSession.addTransportControlsCallback(new TransportListener()); + mSession.setPlaybackState(mPlaybackState); mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); mSession.setRouteOptions(mRouteOptions); mSession.setActive(true); @@ -120,10 +117,10 @@ public class PlayerSession { } private void updateState(int newState) { - float rate = newState == PlaybackState.PLAYSTATE_PLAYING ? 1 : 0; + float rate = newState == PlaybackState.STATE_PLAYING ? 1 : 0; long position = mRenderer == null ? -1 : mRenderer.getSeekPosition(); mPlaybackState.setState(newState, position, rate); - mPerformer.setPlaybackState(mPlaybackState); + mSession.setPlaybackState(mPlaybackState); } public interface Listener { @@ -135,11 +132,11 @@ public class PlayerSession { @Override public void onError(int type, int extra, Bundle extras, Throwable error) { Log.d(TAG, "Sending onError with type " + type + " and extra " + extra); - mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, -1, 0); + mPlaybackState.setState(PlaybackState.STATE_ERROR, -1, 0); if (error != null) { mPlaybackState.setErrorMessage(error.getLocalizedMessage()); } - mPerformer.setPlaybackState(mPlaybackState); + mSession.setPlaybackState(mPlaybackState); if (mListener != null) { mListener.onPlayStateChanged(mPlaybackState); } @@ -157,27 +154,27 @@ public class PlayerSession { switch (newState) { case Renderer.STATE_ENDED: case Renderer.STATE_STOPPED: - mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED, position, 0); + mPlaybackState.setState(PlaybackState.STATE_STOPPED, position, 0); break; case Renderer.STATE_INIT: case Renderer.STATE_PREPARING: - mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING, position, 0); + mPlaybackState.setState(PlaybackState.STATE_BUFFERING, position, 0); break; case Renderer.STATE_ERROR: - mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, position, 0); + mPlaybackState.setState(PlaybackState.STATE_ERROR, position, 0); break; case Renderer.STATE_PAUSED: - mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED, position, 0); + mPlaybackState.setState(PlaybackState.STATE_PAUSED, position, 0); break; case Renderer.STATE_PLAYING: - mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING, position, 1); + mPlaybackState.setState(PlaybackState.STATE_PLAYING, position, 1); break; default: - mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, position, 0); + mPlaybackState.setState(PlaybackState.STATE_ERROR, position, 0); mPlaybackState.setErrorMessage("unkown state"); break; } - mPerformer.setPlaybackState(mPlaybackState); + mSession.setPlaybackState(mPlaybackState); if (mListener != null) { mListener.onPlayStateChanged(mPlaybackState); } @@ -191,8 +188,8 @@ public class PlayerSession { public void onFocusLost() { Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED); long position = mRenderer == null ? -1 : mRenderer.getSeekPosition(); - mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED, position, 0); - mPerformer.setPlaybackState(mPlaybackState); + mPlaybackState.setState(PlaybackState.STATE_PAUSED, position, 0); + mSession.setPlaybackState(mPlaybackState); if (mListener != null) { mListener.onPlayStateChanged(mPlaybackState); } @@ -206,7 +203,7 @@ public class PlayerSession { private class SessionCb extends MediaSession.Callback { @Override - public void onMediaButton(Intent mediaRequestIntent) { + public void onMediaButtonEvent(Intent mediaRequestIntent) { if (Intent.ACTION_MEDIA_BUTTON.equals(mediaRequestIntent.getAction())) { KeyEvent event = (KeyEvent) mediaRequestIntent .getParcelableExtra(Intent.EXTRA_KEY_EVENT); @@ -233,12 +230,12 @@ public class PlayerSession { mRoute = null; mRenderer = new LocalRenderer(mContext, null); mRenderer.registerListener(mRenderListener); - updateState(PlaybackState.PLAYSTATE_NONE); + updateState(PlaybackState.STATE_NONE); } else { // Use remote route mSession.connect(route, mRouteOptions.get(0)); mRenderer = null; - updateState(PlaybackState.PLAYSTATE_CONNECTING); + updateState(PlaybackState.STATE_CONNECTING); } } @@ -249,7 +246,7 @@ public class PlayerSession { mRouteControls.addListener(mRouteListener); Log.d(TAG, "Connected to route, registering listener"); mRenderer = new OneMRPRenderer(mRouteControls); - updateState(PlaybackState.PLAYSTATE_NONE); + updateState(PlaybackState.STATE_NONE); } @Override @@ -258,7 +255,7 @@ public class PlayerSession { } } - private class TransportListener extends TransportPerformer.Listener { + private class TransportListener extends MediaSession.TransportControlsCallback { @Override public void onPlay() { mRenderer.onPlay(); diff --git a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java index 6537d49..f2d691c 100644 --- a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java +++ b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java @@ -149,7 +149,7 @@ public class OneMediaRouteProvider extends RouteProviderService { public void onError(int type, int extra, Bundle extras, Throwable error) { Log.d(TAG, "Sending onError with type " + type + " and extra " + extra); if (mControls != null) { - mControls.sendPlaybackChangeEvent(PlaybackState.PLAYSTATE_ERROR); + mControls.sendPlaybackChangeEvent(PlaybackState.STATE_ERROR); } } @@ -165,23 +165,23 @@ public class OneMediaRouteProvider extends RouteProviderService { switch (newState) { case Renderer.STATE_ENDED: case Renderer.STATE_STOPPED: - mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED, position, 0); + mPlaybackState.setState(PlaybackState.STATE_STOPPED, position, 0); break; case Renderer.STATE_INIT: case Renderer.STATE_PREPARING: - mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING, position, 0); + mPlaybackState.setState(PlaybackState.STATE_BUFFERING, position, 0); break; case Renderer.STATE_ERROR: - mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, position, 0); + mPlaybackState.setState(PlaybackState.STATE_ERROR, position, 0); break; case Renderer.STATE_PAUSED: - mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED, position, 0); + mPlaybackState.setState(PlaybackState.STATE_PAUSED, position, 0); break; case Renderer.STATE_PLAYING: - mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING, position, 1); + mPlaybackState.setState(PlaybackState.STATE_PLAYING, position, 1); break; default: - mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, position, 0); + mPlaybackState.setState(PlaybackState.STATE_ERROR, position, 0); mPlaybackState.setErrorMessage("unkown state"); break; } @@ -196,7 +196,7 @@ public class OneMediaRouteProvider extends RouteProviderService { @Override public void onFocusLost() { Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED); - mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED, mRenderer.getSeekPosition(), 0); + mPlaybackState.setState(PlaybackState.STATE_PAUSED, mRenderer.getSeekPosition(), 0); mRenderer.onPause(); } diff --git a/tests/TtsTests/src/com/android/speech/tts/AbstractTtsSemioticClassTest.java b/tests/TtsTests/src/com/android/speech/tts/AbstractTtsSemioticClassTest.java new file mode 100644 index 0000000..31484f4 --- /dev/null +++ b/tests/TtsTests/src/com/android/speech/tts/AbstractTtsSemioticClassTest.java @@ -0,0 +1,189 @@ +/* + * 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.speech.tts; + +import android.test.InstrumentationTestCase; + +import android.speech.tts.Markup; +import android.speech.tts.Utterance; +import android.speech.tts.Utterance.AbstractTtsSemioticClass; + +public class AbstractTtsSemioticClassTest extends InstrumentationTestCase { + + public static class TtsMock extends AbstractTtsSemioticClass<TtsMock> { + public TtsMock() { + super(); + } + + public TtsMock(Markup markup) { + super(); + } + + public void setType(String type) { + mMarkup.setType(type); + } + } + + public void testFluentAPI() { + new TtsMock() + .setPlainText("a plaintext") // from AbstractTts + .setGender(Utterance.GENDER_MALE) // from AbstractTtsSemioticClass + .setType("test"); // from TtsMock + } + + public void testDefaultConstructor() { + new TtsMock(); + } + + public void testMarkupConstructor() { + Markup markup = new Markup(); + new TtsMock(markup); + } + + public void testGetType() { + TtsMock t = new TtsMock(); + t.setType("type1"); + assertEquals("type1", t.getType()); + t.setType(null); + assertEquals(null, t.getType()); + t.setType("type2"); + assertEquals("type2", t.getType()); + } + + + public void testDefaultGender() { + assertEquals(Utterance.GENDER_UNKNOWN, new TtsMock().getGender()); + } + + public void testSetGender() { + assertEquals(Utterance.GENDER_MALE, + new TtsMock().setGender(Utterance.GENDER_MALE).getGender()); + } + + public void testSetGenderNegative() { + try { + new TtsMock().setGender(-1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testSetGenderOutOfBounds() { + try { + new TtsMock().setGender(4); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testDefaultAnimacy() { + assertEquals(Utterance.ANIMACY_UNKNOWN, new TtsMock().getAnimacy()); + } + + public void testSetAnimacy() { + assertEquals(Utterance.ANIMACY_ANIMATE, + new TtsMock().setAnimacy(Utterance.ANIMACY_ANIMATE).getAnimacy()); + } + + public void testSetAnimacyNegative() { + try { + new TtsMock().setAnimacy(-1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testSetAnimacyOutOfBounds() { + try { + new TtsMock().setAnimacy(4); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testDefaultMultiplicity() { + assertEquals(Utterance.MULTIPLICITY_UNKNOWN, new TtsMock().getMultiplicity()); + } + + public void testSetMultiplicity() { + assertEquals(Utterance.MULTIPLICITY_DUAL, + new TtsMock().setMultiplicity(Utterance.MULTIPLICITY_DUAL).getMultiplicity()); + } + + public void testSetMultiplicityNegative() { + try { + new TtsMock().setMultiplicity(-1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testSetMultiplicityOutOfBounds() { + try { + new TtsMock().setMultiplicity(4); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testDefaultCase() { + assertEquals(Utterance.CASE_UNKNOWN, new TtsMock().getCase()); + } + + public void testSetCase() { + assertEquals(Utterance.CASE_VOCATIVE, + new TtsMock().setCase(Utterance.CASE_VOCATIVE).getCase()); + } + + public void testSetCaseNegative() { + try { + new TtsMock().setCase(-1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testSetCaseOutOfBounds() { + try { + new TtsMock().setCase(9); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testToString() { + TtsMock t = new TtsMock() + .setAnimacy(Utterance.ANIMACY_INANIMATE) + .setCase(Utterance.CASE_INSTRUMENTAL) + .setGender(Utterance.GENDER_FEMALE) + .setMultiplicity(Utterance.MULTIPLICITY_PLURAL); + String str = + "animacy: \"2\" " + + "case: \"8\" " + + "gender: \"3\" " + + "multiplicity: \"3\""; + assertEquals(str, t.toString()); + } + + public void testToStringSetToUnkown() { + TtsMock t = new TtsMock() + .setAnimacy(Utterance.ANIMACY_INANIMATE) + .setCase(Utterance.CASE_INSTRUMENTAL) + .setGender(Utterance.GENDER_FEMALE) + .setMultiplicity(Utterance.MULTIPLICITY_PLURAL) + // set back to unknown + .setAnimacy(Utterance.ANIMACY_UNKNOWN) + .setCase(Utterance.CASE_UNKNOWN) + .setGender(Utterance.GENDER_UNKNOWN) + .setMultiplicity(Utterance.MULTIPLICITY_UNKNOWN); + String str = ""; + assertEquals(str, t.toString()); + } + +} diff --git a/tests/TtsTests/src/com/android/speech/tts/AbstractTtsTest.java b/tests/TtsTests/src/com/android/speech/tts/AbstractTtsTest.java new file mode 100644 index 0000000..281c97f --- /dev/null +++ b/tests/TtsTests/src/com/android/speech/tts/AbstractTtsTest.java @@ -0,0 +1,106 @@ +/* + * 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.speech.tts; + +import android.test.InstrumentationTestCase; + +import android.speech.tts.Markup; +import android.speech.tts.Utterance.AbstractTts; + +public class AbstractTtsTest extends InstrumentationTestCase { + + public static class TtsMock extends AbstractTts<TtsMock> { + public TtsMock() { + super(); + } + + public TtsMock(Markup markup) { + super(); + } + + public void setType(String type) { + mMarkup.setType(type); + } + + @Override + public TtsMock setParameter(String key, String value) { + return super.setParameter(key, value); + } + + @Override + public TtsMock removeParameter(String key) { + return super.removeParameter(key); + } + } + + public void testDefaultConstructor() { + new TtsMock(); + } + + public void testMarkupConstructor() { + Markup markup = new Markup(); + new TtsMock(markup); + } + + public void testGetType() { + TtsMock t = new TtsMock(); + t.setType("type1"); + assertEquals("type1", t.getType()); + t.setType(null); + assertEquals(null, t.getType()); + t.setType("type2"); + assertEquals("type2", t.getType()); + } + + public void testGeneratePlainText() { + assertNull(new TtsMock().generatePlainText()); + } + + public void testToString() { + TtsMock t = new TtsMock(); + t.setType("a_type"); + t.setPlainText("a plaintext"); + t.setParameter("key1", "value1"); + t.setParameter("aaa", "value2"); + String str = + "type: \"a_type\" " + + "plain_text: \"a plaintext\" " + + "aaa: \"value2\" " + + "key1: \"value1\""; + assertEquals(str, t.toString()); + } + + public void testRemoveParameter() { + TtsMock t = new TtsMock(); + t.setParameter("key1", "value 1"); + t.setParameter("aaa", "value a"); + t.removeParameter("key1"); + String str = + "aaa: \"value a\""; + assertEquals(str, t.toString()); + } + + public void testRemoveParameterBySettingNull() { + TtsMock t = new TtsMock(); + t.setParameter("key1", "value 1"); + t.setParameter("aaa", "value a"); + t.setParameter("aaa", null); + String str = + "key1: \"value 1\""; + assertEquals(str, t.toString()); + } +} diff --git a/tests/TtsTests/src/com/android/speech/tts/MarkupTest.java b/tests/TtsTests/src/com/android/speech/tts/MarkupTest.java new file mode 100644 index 0000000..7ef93ce --- /dev/null +++ b/tests/TtsTests/src/com/android/speech/tts/MarkupTest.java @@ -0,0 +1,510 @@ +/* + * 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.speech.tts; + +import junit.framework.Assert; +import android.os.Parcel; +import android.test.InstrumentationTestCase; + +import android.speech.tts.Markup; + +public class MarkupTest extends InstrumentationTestCase { + + public void testEmptyMarkup() { + Markup markup = new Markup(); + assertNull(markup.getType()); + assertNull(markup.getPlainText()); + assertEquals(0, markup.parametersSize()); + assertEquals(0, markup.nestedMarkupSize()); + } + + public void testGetSetType() { + Markup markup = new Markup(); + markup.setType("one"); + assertEquals("one", markup.getType()); + markup.setType(null); + assertNull(markup.getType()); + markup.setType("two"); + assertEquals("two", markup.getType()); + } + + public void testGetSetPlainText() { + Markup markup = new Markup(); + markup.setPlainText("one"); + assertEquals("one", markup.getPlainText()); + markup.setPlainText(null); + assertNull(markup.getPlainText()); + markup.setPlainText("two"); + assertEquals("two", markup.getPlainText()); + } + + public void testParametersSize1() { + Markup markup = new Markup(); + markup.addNestedMarkup(new Markup()); + assertEquals(1, markup.nestedMarkupSize()); + } + + public void testParametersSize2() { + Markup markup = new Markup(); + markup.addNestedMarkup(new Markup()); + markup.addNestedMarkup(new Markup()); + assertEquals(2, markup.nestedMarkupSize()); + } + + public void testRemoveParameter() { + Markup m = new Markup("type"); + m.setParameter("key1", "value1"); + m.setParameter("key2", "value2"); + m.setParameter("key3", "value3"); + assertEquals(3, m.parametersSize()); + m.removeParameter("key1"); + assertEquals(2, m.parametersSize()); + m.removeParameter("key3"); + assertEquals(1, m.parametersSize()); + assertNull(m.getParameter("key1")); + assertEquals("value2", m.getParameter("key2")); + assertNull(m.getParameter("key3")); + } + + public void testEmptyEqual() { + Markup m1 = new Markup(); + Markup m2 = new Markup(); + assertTrue(m1.equals(m2)); + } + + public void testFilledEqual() { + Markup m1 = new Markup(); + m1.setType("type"); + m1.setPlainText("plain text"); + m1.setParameter("key1", "value1"); + m1.addNestedMarkup(new Markup()); + Markup m2 = new Markup(); + m2.setType("type"); + m2.setPlainText("plain text"); + m2.setParameter("key1", "value1"); + m2.addNestedMarkup(new Markup()); + assertTrue(m1.equals(m2)); + } + + public void testDifferentTypeEqual() { + Markup m1 = new Markup(); + m1.setType("type1"); + Markup m2 = new Markup(); + m2.setType("type2"); + assertFalse(m1.equals(m2)); + } + + public void testDifferentPlainTextEqual() { + Markup m1 = new Markup(); + m1.setPlainText("plainText1"); + Markup m2 = new Markup(); + m2.setPlainText("plainText2"); + assertFalse(m1.equals(m2)); + } + + public void testDifferentParamEqual() { + Markup m1 = new Markup(); + m1.setParameter("test", "value1"); + Markup m2 = new Markup(); + m2.setParameter("test", "value2"); + assertFalse(m1.equals(m2)); + } + + public void testDifferentParameterKeyEqual() { + Markup m1 = new Markup(); + m1.setParameter("test1", "value"); + Markup m2 = new Markup(); + m2.setParameter("test2", "value"); + assertFalse(m1.equals(m2)); + } + + public void testDifferentParameterValueEqual() { + Markup m1 = new Markup(); + m1.setParameter("test", "value1"); + Markup m2 = new Markup(); + m2.setParameter("test", "value2"); + assertFalse(m1.equals(m2)); + } + + public void testDifferentNestedMarkupEqual() { + Markup m1 = new Markup(); + Markup nested = new Markup(); + nested.setParameter("key", "value"); + m1.addNestedMarkup(nested); + Markup m2 = new Markup(); + m2.addNestedMarkup(new Markup()); + assertFalse(m1.equals(m2)); + } + + public void testEmptyToFromString() { + Markup m1 = new Markup(); + String str = m1.toString(); + assertEquals("", str); + + Markup m2 = Markup.markupFromString(str); + assertEquals(m1, m2); + } + + public void testTypeToFromString() { + Markup m1 = new Markup("atype"); + String str = m1.toString(); + assertEquals("type: \"atype\"", str); + Markup m2 = Markup.markupFromString(str); + assertEquals(m1, m2); + } + + public void testPlainTextToFromString() { + Markup m1 = new Markup(); + m1.setPlainText("some_plainText"); + String str = m1.toString(); + assertEquals("plain_text: \"some_plainText\"", str); + + Markup m2 = Markup.markupFromString(str); + assertEquals(m1, m2); + } + + public void testParameterToFromString() { + Markup m1 = new Markup("cardinal"); + m1.setParameter("integer", "-22"); + String str = m1.toString(); + assertEquals("type: \"cardinal\" integer: \"-22\"", str); + Markup m2 = Markup.markupFromString(str); + assertEquals(m1, m2); + } + + // Parameters should be ordered alphabettically, so the output is stable. + public void testParameterOrderToFromString() { + Markup m1 = new Markup("cardinal"); + m1.setParameter("ccc", "-"); + m1.setParameter("aaa", "-"); + m1.setParameter("aa", "-"); + m1.setParameter("bbb", "-"); + String str = m1.toString(); + assertEquals( + "type: \"cardinal\" " + + "aa: \"-\" " + + "aaa: \"-\" " + + "bbb: \"-\" " + + "ccc: \"-\"", + str); + Markup m2 = Markup.markupFromString(str); + assertEquals(m1, m2); + } + + public void testEmptyNestedToFromString() { + Markup m1 = new Markup("atype"); + m1.addNestedMarkup(new Markup()); + String str = m1.toString(); + assertEquals("type: \"atype\" markup {}", str); + Markup m2 = Markup.markupFromString(str); + assertEquals(m1, m2); + } + + public void testNestedWithTypeToFromString() { + Markup m1 = new Markup("atype"); + m1.addNestedMarkup(new Markup("nested_type")); + String str = m1.toString(); + assertEquals( + "type: \"atype\" " + + "markup { type: \"nested_type\" }", + str); + Markup m2 = Markup.markupFromString(str); + assertEquals(m1, m2); + } + + public void testRemoveNestedMarkup() { + Markup m = new Markup("atype"); + Markup m1 = new Markup("nested_type1"); + Markup m2 = new Markup("nested_type2"); + Markup m3 = new Markup("nested_type3"); + m.addNestedMarkup(m1); + m.addNestedMarkup(m2); + m.addNestedMarkup(m3); + m.removeNestedMarkup(m1); + m.removeNestedMarkup(m3); + String str = m.toString(); + assertEquals( + "type: \"atype\" " + + "markup { type: \"nested_type2\" }", + str); + Markup mFromString = Markup.markupFromString(str); + assertEquals(m, mFromString); + } + + public void testLotsofNestingToFromString() { + Markup m1 = new Markup("top") + .addNestedMarkup(new Markup("top_child1") + .addNestedMarkup(new Markup("top_child1_child1")) + .addNestedMarkup(new Markup("top_child1_child2"))) + .addNestedMarkup(new Markup("top_child2") + .addNestedMarkup(new Markup("top_child2_child2")) + .addNestedMarkup(new Markup("top_child2_child2"))); + + String str = m1.toString(); + assertEquals( + "type: \"top\" " + + "markup { " + + "type: \"top_child1\" " + + "markup { type: \"top_child1_child1\" } " + + "markup { type: \"top_child1_child2\" } " + + "} " + + "markup { " + + "type: \"top_child2\" " + + "markup { type: \"top_child2_child2\" } " + + "markup { type: \"top_child2_child2\" } " + + "}", + str); + Markup m2 = Markup.markupFromString(str); + assertEquals(m1, m2); + } + + public void testFilledToFromString() { + Markup m1 = new Markup("measure"); + m1.setPlainText("fifty-five amps"); + m1.setParameter("unit", "meter"); + m1.addNestedMarkup(new Markup("cardinal").setParameter("integer", "55")); + String str = m1.toString(); + assertEquals( + "type: \"measure\" " + + "plain_text: \"fifty-five amps\" " + + "unit: \"meter\" " + + "markup { type: \"cardinal\" integer: \"55\" }", + str); + + Markup m2 = Markup.markupFromString(str); + assertEquals(m1, m2); + } + + public void testErrorFromString() { + String str = "type: \"atype\" markup {mistake}"; + try { + Markup.markupFromString(str); + Assert.fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testEscapeQuotes() { + Markup m1 = new Markup("text") + .setParameter("something_unknown", "\"this\" is \"a sentence \" with quotes\""); + String str = m1.toString(); + assertEquals( + "type: \"text\" " + + "something_unknown: \"\\\"this\\\" is \\\"a sentence \\\" with quotes\\\"\"", + str); + + Markup m2 = Markup.markupFromString(str); + assertEquals(m1.toString(), m2.toString()); + assertEquals(m1, m2); + } + + public void testEscapeSlashes1() { + Markup m1 = new Markup("text") + .setParameter("something_unknown", "\\ \\\\ \t \n \""); + String str = m1.toString(); + assertEquals( + "type: \"text\" " + + "something_unknown: \"\\\\ \\\\\\\\ \t \n \\\"\"", + str); + + Markup m2 = Markup.markupFromString(str); + assertEquals(m1.toString(), m2.toString()); + assertEquals(m1, m2); + } + + public void testEscapeSlashes2() { + Markup m1 = new Markup("text") + .setParameter("something_unknown", "\\\"\\\"\\\\\"\"\\\\\\\"\"\""); + String str = m1.toString(); + assertEquals( + "type: \"text\" " + + "something_unknown: \"\\\\\\\"\\\\\\\"\\\\\\\\\\\"\\\"\\\\\\\\\\\\\\\"\\\"\\\"\"", + str); + + Markup m2 = Markup.markupFromString(str); + assertEquals(m1.toString(), m2.toString()); + assertEquals(m1, m2); + } + + public void testBadInput1() { + String str = "type: \"text\" text: \"\\\""; + try { + Markup.markupFromString(str); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testBadInput2() { + String str = "type: \"text\" text: \"\\a\""; + try { + Markup.markupFromString(str); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testValidParameterKey() { + Markup m = new Markup(); + m.setParameter("ke9__yk_88ey_za7_", "test"); + } + + public void testInValidParameterKeyEmpty() { + Markup m = new Markup(); + try { + m.setParameter("", "test"); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testInValidParameterKeyDollar() { + Markup m = new Markup(); + try { + m.setParameter("ke9y$k88ey7", "test"); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testInValidParameterKeySpace() { + Markup m = new Markup(); + try { + m.setParameter("ke9yk88ey7 ", "test"); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testValidType() { + new Markup("_this_is_1_valid_type_222"); + } + + public void testInValidTypeAmpersand() { + try { + new Markup("abcde1234&"); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testInValidTypeSpace() { + try { + new Markup(" "); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testSimpleParcelable() { + Markup markup = new Markup(); + + Parcel parcel = Parcel.obtain(); + markup.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + Markup fromParcel = (Markup) Markup.CREATOR.createFromParcel(parcel); + + assertFalse(markup == fromParcel); + assertEquals(markup, fromParcel); + } + + public void testTypeParcelable() { + Markup markup = new Markup("text"); + + Parcel parcel = Parcel.obtain(); + markup.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + Markup fromParcel = (Markup) Markup.CREATOR.createFromParcel(parcel); + + assertFalse(markup == fromParcel); + assertEquals(markup, fromParcel); + } + + public void testPlainTextsParcelable() { + Markup markup = new Markup(); + markup.setPlainText("plainText"); + + Parcel parcel = Parcel.obtain(); + markup.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + Markup fromParcel = (Markup) Markup.CREATOR.createFromParcel(parcel); + + assertFalse(markup == fromParcel); + assertEquals(markup, fromParcel); + } + + public void testParametersParcelable() { + Markup markup = new Markup(); + markup.setParameter("key1", "value1"); + markup.setParameter("key2", "value2"); + markup.setParameter("key3", "value3"); + + Parcel parcel = Parcel.obtain(); + markup.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + Markup fromParcel = (Markup) Markup.CREATOR.createFromParcel(parcel); + + assertFalse(markup == fromParcel); + assertEquals(markup, fromParcel); + } + + public void testNestedParcelable() { + Markup markup = new Markup(); + markup.addNestedMarkup(new Markup("first")); + markup.addNestedMarkup(new Markup("second")); + markup.addNestedMarkup(new Markup("third")); + + Parcel parcel = Parcel.obtain(); + markup.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + Markup fromParcel = (Markup) Markup.CREATOR.createFromParcel(parcel); + + assertFalse(markup == fromParcel); + assertEquals(markup, fromParcel); + } + + public void testAllFieldsParcelable() { + Markup markup = new Markup("text"); + markup.setPlainText("plain text"); + markup.setParameter("key1", "value1"); + markup.setParameter("key2", "value2"); + markup.setParameter("key3", "value3"); + markup.addNestedMarkup(new Markup("first")); + markup.addNestedMarkup(new Markup("second")); + markup.addNestedMarkup(new Markup("third")); + + Parcel parcel = Parcel.obtain(); + markup.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + Markup fromParcel = (Markup) Markup.CREATOR.createFromParcel(parcel); + + assertFalse(markup == fromParcel); + assertEquals(markup, fromParcel); + } + + public void testKeyCannotBeType() { + try { + new Markup().setParameter("type", "vale"); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testKeyCannotBePlainText() { + try { + new Markup().setParameter("plain_text", "value"); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } +} diff --git a/tests/TtsTests/src/com/android/speech/tts/TtsCardinalTest.java b/tests/TtsTests/src/com/android/speech/tts/TtsCardinalTest.java new file mode 100644 index 0000000..c34f4ac --- /dev/null +++ b/tests/TtsTests/src/com/android/speech/tts/TtsCardinalTest.java @@ -0,0 +1,119 @@ +/* + * 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.speech.tts; + +import junit.framework.Assert; +import android.test.InstrumentationTestCase; +import android.test.MoreAsserts; + +import android.speech.tts.Markup; +import android.speech.tts.Utterance; +import android.speech.tts.Utterance.TtsCardinal; +import android.speech.tts.Utterance.TtsText; + +public class TtsCardinalTest extends InstrumentationTestCase { + + public void testConstruct() { + assertNotNull(new TtsCardinal(0)); + } + + public void testFluentAPI() { + new TtsCardinal() + .setPlainText("a plaintext") // from AbstractTts + .setGender(Utterance.GENDER_MALE) // from AbstractTtsSemioticClass + .setInteger("-10001"); // from TtsText + } + + public void testZero() { + assertEquals("0", new TtsCardinal(0).getInteger()); + } + + public void testThirtyOne() { + assertEquals("31", new TtsCardinal(31).getInteger()); + } + + public void testMarkupZero() { + TtsCardinal c = new TtsCardinal(0); + Markup m = c.getMarkup(); + assertEquals("0", m.getParameter("integer")); + } + + public void testMarkupThirtyOne() { + TtsCardinal c = new TtsCardinal(31); + Markup m = c.getMarkup(); + assertEquals("31", m.getParameter("integer")); + } + + public void testMarkupThirtyOneString() { + TtsCardinal c = new TtsCardinal("31"); + Markup m = c.getMarkup(); + assertEquals("31", m.getParameter("integer")); + } + + public void testMarkupNegativeThirtyOne() { + TtsCardinal c = new TtsCardinal(-31); + Markup m = c.getMarkup(); + assertEquals("-31", m.getParameter("integer")); + } + + public void testMarkupMinusZero() { + TtsCardinal c = new TtsCardinal("-0"); + Markup m = c.getMarkup(); + assertEquals("-0", m.getParameter("integer")); + } + + public void testMarkupNegativeThirtyOneString() { + TtsCardinal c = new TtsCardinal("-31"); + Markup m = c.getMarkup(); + assertEquals("-31", m.getParameter("integer")); + } + + public void testOnlyLetters() { + try { + new TtsCardinal("abc"); + Assert.fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testOnlyMinus() { + try { + new TtsCardinal("-"); + Assert.fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testNegativeLetters() { + try { + new TtsCardinal("-abc"); + Assert.fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testLetterNumberMix() { + try { + new TtsCardinal("-0a1b2c"); + Assert.fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void letterNumberMix2() { + try { + new TtsCardinal("-a0b1c2"); + Assert.fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } +} diff --git a/tests/TtsTests/src/com/android/speech/tts/TtsTextTest.java b/tests/TtsTests/src/com/android/speech/tts/TtsTextTest.java new file mode 100644 index 0000000..35fd453 --- /dev/null +++ b/tests/TtsTests/src/com/android/speech/tts/TtsTextTest.java @@ -0,0 +1,74 @@ +/* + * 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.speech.tts; + +import android.test.InstrumentationTestCase; + +import android.speech.tts.Markup; +import android.speech.tts.Utterance; +import android.speech.tts.Utterance.TtsText; + +public class TtsTextTest extends InstrumentationTestCase { + + public void testConstruct() { + assertNotNull(new TtsText()); + } + + public void testFluentAPI() { + new TtsText() + .setPlainText("a plaintext") // from AbstractTts + .setGender(Utterance.GENDER_MALE) // from AbstractTtsSemioticClass + .setText("text"); // from TtsText + } + + public void testConstructEmptyString() { + assertTrue(new TtsText("").getText().isEmpty()); + } + + public void testConstructString() { + assertEquals("this is a test.", new TtsText("this is a test.").getText()); + } + + public void testSetText() { + assertEquals("This is a test.", new TtsText().setText("This is a test.").getText()); + } + + public void testEmptyMarkup() { + TtsText t = new TtsText(); + Markup m = t.getMarkup(); + assertEquals("text", m.getType()); + assertNull(m.getPlainText()); + assertEquals(0, m.nestedMarkupSize()); + } + + public void testConstructStringMarkup() { + TtsText t = new TtsText("test"); + Markup m = t.getMarkup(); + assertEquals("text", m.getType()); + assertEquals("test", m.getParameter("text")); + assertEquals(0, m.nestedMarkupSize()); + } + + public void testSetStringMarkup() { + TtsText t = new TtsText(); + t.setText("test"); + Markup m = t.getMarkup(); + assertEquals("text", m.getType()); + assertEquals("test", m.getParameter("text")); + assertEquals(0, m.nestedMarkupSize()); + } +} diff --git a/tests/TtsTests/src/com/android/speech/tts/UtteranceTest.java b/tests/TtsTests/src/com/android/speech/tts/UtteranceTest.java new file mode 100644 index 0000000..8014dd1 --- /dev/null +++ b/tests/TtsTests/src/com/android/speech/tts/UtteranceTest.java @@ -0,0 +1,248 @@ +/* + * 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.speech.tts; + +import android.speech.tts.Markup; +import android.speech.tts.Utterance; +import android.speech.tts.Utterance.TtsCardinal; +import android.speech.tts.Utterance.TtsText; + +import android.test.InstrumentationTestCase; + +public class UtteranceTest extends InstrumentationTestCase { + + public void testEmptyUtterance() { + Utterance utt = new Utterance(); + assertEquals(0, utt.size()); + } + + public void testSizeCardinal() { + Utterance utt = new Utterance() + .append(new TtsCardinal(42)); + assertEquals(1, utt.size()); + } + + public void testSizeCardinalString() { + Utterance utt = new Utterance() + .append(new TtsCardinal(42)) + .append(new TtsText("is the answer")); + assertEquals(2, utt.size()); + } + + public void testMarkupEmpty() { + Markup m = new Utterance().createMarkup(); + assertEquals("utterance", m.getType()); + assertEquals("", m.getPlainText()); + } + + public void testMarkupCardinal() { + Utterance utt = new Utterance() + .append(new TtsCardinal(42)); + Markup markup = utt.createMarkup(); + assertEquals("utterance", markup.getType()); + assertEquals("42", markup.getPlainText()); + assertEquals("42", markup.getNestedMarkup(0).getParameter("integer")); + assertEquals("42", markup.getNestedMarkup(0).getPlainText()); + } + + public void testMarkupCardinalString() { + Utterance utt = new Utterance() + .append(new TtsCardinal(42)) + .append(new TtsText("is not just a number.")); + Markup markup = utt.createMarkup(); + assertEquals("utterance", markup.getType()); + assertEquals("42 is not just a number.", markup.getPlainText()); + assertEquals("cardinal", markup.getNestedMarkup(0).getType()); + assertEquals("42", markup.getNestedMarkup(0).getParameter("integer")); + assertEquals("42", markup.getNestedMarkup(0).getPlainText()); + assertEquals("text", markup.getNestedMarkup(1).getType()); + assertEquals("is not just a number.", markup.getNestedMarkup(1).getParameter("text")); + assertEquals("is not just a number.", markup.getNestedMarkup(1).getPlainText()); + } + + public void testTextCardinalToFromString() { + Utterance utt = new Utterance() + .append(new TtsCardinal(55)) + .append(new TtsText("this is a text.")); + String str = utt.toString(); + assertEquals( + "type: \"utterance\" " + + "markup { " + + "type: \"cardinal\" " + + "integer: \"55\" " + + "} " + + "markup { " + + "type: \"text\" " + + "text: \"this is a text.\" " + + "}" + , str); + + Utterance utt_new = Utterance.utteranceFromString(str); + assertEquals(str, utt_new.toString()); + } + + public void testNotUtteranceFromString() { + String str = + "type: \"this_is_not_an_utterance\" " + + "markup { " + + "type: \"cardinal\" " + + "plain_text: \"55\" " + + "integer: \"55\" " + + "}"; + try { + Utterance.utteranceFromString(str); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + public void testFromMarkup() { + String markup_str = + "type: \"utterance\" " + + "markup { " + + "type: \"cardinal\" " + + "plain_text: \"55\" " + + "integer: \"55\" " + + "} " + + "markup { " + + "type: \"text\" " + + "plain_text: \"this is a text.\" " + + "text: \"this is a text.\" " + + "}"; + Utterance utt = Utterance.utteranceFromString(markup_str); + assertEquals(markup_str, utt.toString()); + } + + public void testsetPlainText() { + Utterance utt = new Utterance() + .append(new TtsCardinal(-100).setPlainText("minus one hundred")); + assertEquals("minus one hundred", utt.get(0).getPlainText()); + } + + public void testRemoveTextThroughSet() { + Utterance utt = new Utterance() + .append(new TtsText().setText("test").setText(null)); + assertNull(((TtsText) utt.get(0)).getText()); + } + + public void testUnknownNodeWithPlainText() { + String str = + "type: \"utterance\" " + + "markup { " + + "type: \"some_future_feature\" " + + "plain_text: \"biep bob bob\" " + + "bombom: \"lorum ipsum\" " + + "}"; + Utterance utt = Utterance.utteranceFromString(str); + assertNotNull(utt); + assertEquals("text", utt.get(0).getType()); + assertEquals("biep bob bob", ((TtsText) utt.get(0)).getText()); + } + + public void testUnknownNodeWithNoPlainTexts() { + String str = + "type: \"utterance\" " + + "markup { " + + "type: \"some_future_feature\" " + + "bombom: \"lorum ipsum\" " + + "markup { type: \"cardinal\" integer: \"10\" } " + + "markup { type: \"text\" text: \"pears\" } " + + "}"; + Utterance utt = Utterance.utteranceFromString(str); + assertEquals( + "type: \"utterance\" " + + "markup { type: \"cardinal\" integer: \"10\" } " + + "markup { type: \"text\" text: \"pears\" }", utt.toString()); + } + + public void testCreateWarningOnFallbackTrue() { + Utterance utt = new Utterance() + .append(new TtsText("test")) + .setNoWarningOnFallback(true); + assertEquals( + "type: \"utterance\" " + + "no_warning_on_fallback: \"true\" " + + "markup { " + + "type: \"text\" " + + "text: \"test\" " + + "}", utt.toString()); + } + + public void testCreateWarningOnFallbackFalse() { + Utterance utt = new Utterance() + .append(new TtsText("test")) + .setNoWarningOnFallback(false); + assertEquals( + "type: \"utterance\" " + + "no_warning_on_fallback: \"false\" " + + "markup { " + + "type: \"text\" " + + "text: \"test\" " + + "}", utt.toString()); + } + + public void testCreatePlainTexts() { + Utterance utt = new Utterance() + .append(new TtsText("test")) + .append(new TtsCardinal(-55)); + assertEquals( + "type: \"utterance\" " + + "plain_text: \"test -55\" " + + "markup { type: \"text\" plain_text: \"test\" text: \"test\" } " + + "markup { type: \"cardinal\" plain_text: \"-55\" integer: \"-55\" }", + utt.createMarkup().toString() + ); + } + + public void testDontOverwritePlainTexts() { + Utterance utt = new Utterance() + .append(new TtsText("test").setPlainText("else")) + .append(new TtsCardinal(-55).setPlainText("44")); + assertEquals( + "type: \"utterance\" " + + "plain_text: \"else 44\" " + + "markup { type: \"text\" plain_text: \"else\" text: \"test\" } " + + "markup { type: \"cardinal\" plain_text: \"44\" integer: \"-55\" }", + utt.createMarkup().toString() + ); + } + + public void test99BottlesOnWallMarkup() { + Utterance utt = new Utterance() + .append("there are") + .append(99) + .append("bottles on the wall."); + assertEquals( + "type: \"utterance\" " + + "plain_text: \"there are 99 bottles on the wall.\" " + + "markup { type: \"text\" plain_text: \"there are\" text: \"there are\" } " + + "markup { type: \"cardinal\" plain_text: \"99\" integer: \"99\" } " + + "markup { type: \"text\" plain_text: \"bottles on the wall.\" text: \"bottles on the wall.\" }", + utt.createMarkup().toString()); + assertEquals("99", utt.createMarkup().getNestedMarkup(1).getPlainText()); + Markup markup = new Markup(utt.createMarkup()); + assertEquals("99", markup.getNestedMarkup(1).getPlainText()); + } + + public void testWhat() { + Utterance utt = new Utterance() + .append("there are") + .append(99) + .append("bottles on the wall."); + Markup m = utt.createMarkup(); + m.getNestedMarkup(1).getPlainText().equals("99"); + } +} 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/res/layout/test_interaction.xml b/tests/VoiceInteraction/res/layout/test_interaction.xml index 2abf651..4c0c67a 100644 --- a/tests/VoiceInteraction/res/layout/test_interaction.xml +++ b/tests/VoiceInteraction/res/layout/test_interaction.xml @@ -18,6 +18,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" + android:padding="8dp" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -29,9 +30,16 @@ android:layout_width="match_parent" android:layout_height="0px" android:layout_weight="1" - android:layout_marginTop="10dp" - android:textSize="12sp" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="#ffffffff" /> + <Button android:id="@+id/abort" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="@string/abortVoice" + /> + </LinearLayout> diff --git a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml index 563fa44..142d781 100644 --- a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml +++ b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml @@ -14,26 +14,49 @@ limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="match_parent" - android:orientation="vertical" - android:background="#ffffffff" - android:fitsSystemWindows="true" - > - - <TextView android:id="@+id/text" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="32dp" - /> - - <Button android:id="@+id/start" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/start" - /> - -</LinearLayout> - - + android:fitsSystemWindows="true"> + + <FrameLayout android:layout_width="fill_parent" + android:layout_height="match_parent" + android:padding="8dp"> + + <LinearLayout android:id="@+id/content" + android:layout_width="fill_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="#ffffffff" + android:elevation="8dp" + > + + <TextView android:id="@+id/text" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/start" + /> + <Button android:id="@+id/confirm" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/confirm" + /> + <Button android:id="@+id/abort" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/abort" + /> + </LinearLayout> + + </LinearLayout> + </FrameLayout> +</FrameLayout> diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml index 12edb31..70baa52 100644 --- a/tests/VoiceInteraction/res/values/strings.xml +++ b/tests/VoiceInteraction/res/values/strings.xml @@ -16,7 +16,10 @@ <resources> - <string name="start">Start!</string> + <string name="start">Start</string> + <string name="confirm">Confirm</string> + <string name="abort">Abort</string> + <string name="abortVoice">Abort Voice</string> </resources> diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java index a3af284..c24a088 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java @@ -33,9 +33,17 @@ public class MainInteractionSession extends VoiceInteractionSession View mContentView; TextView mText; Button mStartButton; + Button mConfirmButton; + Button mAbortButton; + static final int STATE_IDLE = 0; + static final int STATE_LAUNCHING = 1; + static final int STATE_CONFIRM = 2; + static final int STATE_COMMAND = 3; + static final int STATE_ABORT_VOICE = 4; + + int mState = STATE_IDLE; Request mPendingRequest; - boolean mPendingConfirm; MainInteractionSession(Context context) { super(context); @@ -54,21 +62,39 @@ public class MainInteractionSession extends VoiceInteractionSession mText = (TextView)mContentView.findViewById(R.id.text); mStartButton = (Button)mContentView.findViewById(R.id.start); mStartButton.setOnClickListener(this); + mConfirmButton = (Button)mContentView.findViewById(R.id.confirm); + mConfirmButton.setOnClickListener(this); + mAbortButton = (Button)mContentView.findViewById(R.id.abort); + mAbortButton.setOnClickListener(this); + updateState(); return mContentView; } + void updateState() { + mStartButton.setEnabled(mState == STATE_IDLE); + mConfirmButton.setEnabled(mState == STATE_CONFIRM || mState == STATE_COMMAND); + mAbortButton.setEnabled(mState == STATE_ABORT_VOICE); + } + public void onClick(View v) { - if (mPendingRequest == null) { - mStartButton.setEnabled(false); + if (v == mStartButton) { + mState = STATE_LAUNCHING; + updateState(); startVoiceActivity(mStartIntent); - } else { - if (mPendingConfirm) { + } else if (v == mConfirmButton) { + if (mState == STATE_CONFIRM) { mPendingRequest.sendConfirmResult(true, null); } else { mPendingRequest.sendCommandResult(true, null); } mPendingRequest = null; - mStartButton.setText("Start"); + mState = STATE_IDLE; + updateState(); + } else if (v == mAbortButton) { + mPendingRequest.sendAbortVoiceResult(null); + mPendingRequest = null; + mState = STATE_IDLE; + updateState(); } } @@ -78,23 +104,32 @@ public class MainInteractionSession extends VoiceInteractionSession } @Override - public void onConfirm(Caller caller, Request request, String prompt, Bundle extras) { + public void onConfirm(Caller caller, Request request, CharSequence prompt, Bundle extras) { Log.i(TAG, "onConfirm: prompt=" + prompt + " extras=" + extras); mText.setText(prompt); - mStartButton.setEnabled(true); mStartButton.setText("Confirm"); mPendingRequest = request; - mPendingConfirm = true; + mState = STATE_CONFIRM; + updateState(); + } + + @Override + public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) { + Log.i(TAG, "onAbortVoice: message=" + message + " extras=" + extras); + mText.setText(message); + mPendingRequest = request; + mState = STATE_ABORT_VOICE; + updateState(); } @Override public void onCommand(Caller caller, Request request, String command, Bundle extras) { Log.i(TAG, "onCommand: command=" + command + " extras=" + extras); mText.setText("Command: " + command); - mStartButton.setEnabled(true); mStartButton.setText("Finish Command"); mPendingRequest = request; - mPendingConfirm = false; + mState = STATE_COMMAND; + updateState(); } @Override diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java index 9c772ff..3ae6a36 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java @@ -20,11 +20,16 @@ import android.app.Activity; import android.app.VoiceInteractor; import android.os.Bundle; import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; -public class TestInteractionActivity extends Activity { +public class TestInteractionActivity extends Activity implements View.OnClickListener { static final String TAG = "TestInteractionActivity"; VoiceInteractor mInteractor; + Button mAbortButton; @Override public void onCreate(Bundle savedInstanceState) { @@ -37,6 +42,13 @@ public class TestInteractionActivity extends Activity { } setContentView(R.layout.test_interaction); + mAbortButton = (Button)findViewById(R.id.abort); + mAbortButton.setOnClickListener(this); + + // 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( @@ -62,6 +74,26 @@ public class TestInteractionActivity extends Activity { } @Override + public void onClick(View v) { + if (v == mAbortButton) { + VoiceInteractor.AbortVoiceRequest req = new VoiceInteractor.AbortVoiceRequest( + "Dammit, we suck :(", null) { + @Override + public void onCancel() { + Log.i(TAG, "Canceled!"); + } + + @Override + public void onAbortResult(Bundle result) { + Log.i(TAG, "Abort result: result=" + result); + getActivity().finish(); + } + }; + mInteractor.submitRequest(req); + } + } + + @Override public void onDestroy() { super.onDestroy(); } diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java index cdbbe46..610c867 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java @@ -79,13 +79,6 @@ public class BitmapShader_Delegate extends Shader_Delegate { return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate(long native_shader, long native_bitmap, - int shaderTileModeX, int shaderTileModeY) { - // pass, not needed. - return 0; - } - // ---- Private delegate/helper methods ---- private BitmapShader_Delegate(java.awt.image.BufferedImage image, diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java index fae8aef..59ddcc6 100644 --- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java @@ -78,19 +78,6 @@ public class ComposeShader_Delegate extends Shader_Delegate { return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(long native_shader, long native_skiaShaderA, - long native_skiaShaderB, long native_mode) { - // pass, not needed. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(long native_shader, long native_skiaShaderA, - long native_skiaShaderB, int porterDuffMode) { - // pass, not needed. - return 0; - } // ---- Private delegate/helper methods ---- diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java index 5e7543a..9ea4538 100644 --- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -146,6 +146,8 @@ public class FontFamily_Delegate { @LayoutlibDelegate /*package*/ static void nUnrefFamily(long nativePtr) { + // Removing the java reference for the object doesn't mean that it's freed for garbage + // collection. Typeface_Delegate may still hold a reference for it. sManager.removeJavaReferenceFor(nativePtr); } diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java index ac77377..55c4b98 100644 --- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java @@ -71,22 +71,6 @@ public final class LinearGradient_Delegate extends Gradient_Delegate { tileMode); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(LinearGradient thisGradient, - long native_shader, float x0, float y0, float x1, float y1, - int colors[], float positions[], int tileMode) { - // nothing to be done here. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(LinearGradient thisGradient, - long native_shader, float x0, float y0, float x1, float y1, - int color0, int color1, int tileMode) { - // nothing to be done here. - return 0; - } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java index 4f16dcf..80179ee 100644 --- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java @@ -68,20 +68,6 @@ public class RadialGradient_Delegate extends Gradient_Delegate { tileMode); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(long native_shader, float x, float y, float radius, - int colors[], float positions[], int tileMode) { - // nothing to be done here. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(long native_shader, float x, float y, float radius, - int color0, int color1, int tileMode) { - // nothing to be done here. - return 0; - } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java index 70a0a43..14e9960 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java @@ -76,13 +76,12 @@ public abstract class Shader_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static void nativeDestructor(long native_shader, long native_skiaShader) { + /*package*/ static void nativeDestructor(long native_shader) { sManager.removeJavaReferenceFor(native_shader); } @LayoutlibDelegate - /*package*/ static void nativeSetLocalMatrix(long native_shader, long native_skiaShader, - long matrix_instance) { + /*package*/ static void nativeSetLocalMatrix(long native_shader, long matrix_instance) { // get the delegate from the native int. Shader_Delegate shaderDelegate = sManager.getDelegate(native_shader); if (shaderDelegate == null) { diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java index f2b3e8d..95a57a9 100644 --- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java @@ -62,20 +62,6 @@ public class SweepGradient_Delegate extends Gradient_Delegate { return nativeCreate1(x, y, new int[] { color0, color1 }, null /*positions*/); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(long native_shader, float cx, float cy, - int[] colors, float[] positions) { - // nothing to be done here. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(long native_shader, float cx, float cy, - int color0, int color1) { - // nothing to be done here. - return 0; - } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java index ed8f3b4..9746b48 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -54,7 +54,7 @@ public final class Typeface_Delegate { // ---- delegate data ---- - private final long[] mFontFamilies; // the reference to FontFamily_Delegate. + private final FontFamily_Delegate[] mFontFamilies; // the reference to FontFamily_Delegate. private int mStyle; private static long sDefaultTypeface; @@ -71,8 +71,7 @@ public final class Typeface_Delegate { public List<Font> getFonts(boolean compact) { List<Font> fonts = new ArrayList<Font>(mFontFamilies.length); - for (long fontFamily : mFontFamilies) { - FontFamily_Delegate ffd = FontFamily_Delegate.getDelegate(fontFamily); + for (FontFamily_Delegate ffd : mFontFamilies) { if (ffd != null) { Font font = ffd.getFont(mStyle, compact); if (font != null) { @@ -122,7 +121,11 @@ public final class Typeface_Delegate { @LayoutlibDelegate /*package*/ static synchronized long nativeCreateFromArray(long[] familyArray) { - Typeface_Delegate delegate = new Typeface_Delegate(familyArray, Typeface.NORMAL); + FontFamily_Delegate[] fontFamilies = new FontFamily_Delegate[familyArray.length]; + for (int i = 0; i < familyArray.length; i++) { + fontFamilies[i] = FontFamily_Delegate.getDelegate(familyArray[i]); + } + Typeface_Delegate delegate = new Typeface_Delegate(fontFamilies, Typeface.NORMAL); return sManager.addNewDelegate(delegate); } @@ -153,9 +156,8 @@ public final class Typeface_Delegate { // ---- Private delegate/helper methods ---- - private Typeface_Delegate(long[] fontFamilies, int style) { + private Typeface_Delegate(FontFamily_Delegate[] fontFamilies, int style) { mFontFamilies = fontFamilies; mStyle = style; } - } diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index e1a9719..3bf2b20 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -439,6 +439,10 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public void keyguardGoingAway() throws RemoteException { + } + + @Override public void lockNow(Bundle options) { // TODO Auto-generated method stub } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index d31239b..5c51c63 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -1466,4 +1466,10 @@ public final class BridgeContext extends Context { // pass return new File[0]; } + + @Override + public File[] getExternalMediaDirs() { + // pass + return new File[0]; + } } diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java index 58b0d61..f6d7f55 100644 --- a/wifi/java/android/net/wifi/ScanResult.java +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -90,6 +90,19 @@ public class ScanResult implements Parcelable { */ public final static int UNSPECIFIED = -1; + /** information element from beacon + * @hide + */ + public static class InformationElement { + public int id; + public byte[] bytes; + } + + /** information elements found in the beacon + * @hide + */ + public InformationElement informationElements[]; + /** {@hide} */ public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency, long tsf) { @@ -199,6 +212,16 @@ public class ScanResult implements Parcelable { } else { dest.writeInt(0); } + if (informationElements != null) { + dest.writeInt(informationElements.length); + for (int i = 0; i < informationElements.length; i++) { + dest.writeInt(informationElements[i].id); + dest.writeInt(informationElements[i].bytes.length); + dest.writeByteArray(informationElements[i].bytes); + } + } else { + dest.writeInt(0); + } } /** Implement the Parcelable interface {@hide} */ @@ -223,6 +246,17 @@ public class ScanResult implements Parcelable { if (in.readInt() == 1) { sr.passpoint = WifiPasspointInfo.CREATOR.createFromParcel(in); } + int n = in.readInt(); + if (n != 0) { + sr.informationElements = new InformationElement[n]; + for (int i = 0; i < n; i++) { + sr.informationElements[i] = new InformationElement(); + sr.informationElements[i].id = in.readInt(); + int len = in.readInt(); + sr.informationElements[i].bytes = new byte[len]; + in.readByteArray(sr.informationElements[i].bytes); + } + } return sr; } @@ -230,5 +264,4 @@ public class ScanResult implements Parcelable { return new ScanResult[size]; } }; - } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 1157de7..0faaeba 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. @@ -312,6 +324,24 @@ public class WifiConfiguration implements Parcelable { /** * @hide + * Uid of app creating the configuration + */ + public int creatorUid; + + /** + * @hide + * Uid of last app issuing a connection related command + */ + public int lastConnectUid; + + /** + * @hide + * Uid of last app modifying the configuration + */ + public int lastUpdateUid; + + /** + * @hide * BSSID list on which this configuration was seen. * TODO: prevent this list to grow infinitely, age-out the results */ @@ -429,10 +459,17 @@ public class WifiConfiguration implements Parcelable { /** @hide * if this is set, the WifiConfiguration cannot use linkages so as to bump * it's relative priority. + * - status between and 128 indicate various level of blacklisting depending + * on the severity or frequency of the connection error + * - deleted status indicates that the user is deleting the configuration, and so + * although it may have been self added we will not re-self-add it, ignore it, + * not return it to applications, and not connect to it * */ public static final int AUTO_JOIN_TEMPORARY_DISABLED = 1; /** @hide */ - public static final int AUTO_JOIN_DISABLED_ON_AUTH_FAILURE = 2; + public static final int AUTO_JOIN_DISABLED_ON_AUTH_FAILURE = 128; + /** @hide */ + public static final int AUTO_JOIN_DELETED = 200; /** * @hide @@ -441,11 +478,23 @@ public class WifiConfiguration implements Parcelable { /** * Set if the configuration was self added by the framework + * This boolean is cleared if we get a connect/save/ update or + * any wifiManager command that indicate the user interacted with the configuration + * since we will now consider that the configuration belong to him. * @hide */ public boolean selfAdded; /** + * Set if the configuration was self added by the framework + * This boolean is set once and never cleared. It is used + * so as we never loose track of who created the + * configuration in the first place. + * @hide + */ + public boolean didSelfAdd; + + /** * @hide * Indicate that a WifiConfiguration is temporary and should not be saved * nor considered by AutoJoin. @@ -484,6 +533,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; @@ -499,6 +550,7 @@ public class WifiConfiguration implements Parcelable { enterpriseConfig = new WifiEnterpriseConfig(); autoJoinStatus = AUTO_JOIN_ENABLED; selfAdded = false; + didSelfAdd = false; ephemeral = false; mIpConfiguration = new IpConfiguration(); } @@ -565,7 +617,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++) { @@ -635,6 +688,10 @@ public class WifiConfiguration implements Parcelable { sbuf.append(mIpConfiguration.toString()); + if (selfAdded) sbuf.append("selfAdded"); + if (creatorUid != 0) sbuf.append("uid=" + Integer.toString(creatorUid)); + + return sbuf.toString(); } @@ -868,8 +925,11 @@ public class WifiConfiguration implements Parcelable { networkId = source.networkId; status = source.status; disableReason = source.disableReason; + disableReason = source.disableReason; SSID = source.SSID; BSSID = source.BSSID; + FQDN = source.FQDN; + naiRealm = source.naiRealm; preSharedKey = source.preSharedKey; wepKeys = new String[4]; @@ -916,6 +976,10 @@ public class WifiConfiguration implements Parcelable { } lastFailure = source.lastFailure; + didSelfAdd = source.didSelfAdd; + lastConnectUid = source.lastConnectUid; + lastUpdateUid = source.lastUpdateUid; + creatorUid = source.creatorUid; } } @@ -932,6 +996,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); @@ -953,6 +1019,10 @@ public class WifiConfiguration implements Parcelable { dest.writeString(defaultGwMacAddress); dest.writeInt(autoJoinStatus); dest.writeInt(selfAdded ? 1 : 0); + dest.writeInt(didSelfAdd ? 1 : 0); + dest.writeInt(creatorUid); + dest.writeInt(lastConnectUid); + dest.writeInt(lastUpdateUid); /* TODO: should we write the cache results to the parcel? if (scanResultCache != null) { @@ -976,6 +1046,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(); @@ -996,6 +1068,10 @@ public class WifiConfiguration implements Parcelable { config.defaultGwMacAddress = in.readString(); config.autoJoinStatus = in.readInt(); config.selfAdded = in.readInt() != 0; + config.didSelfAdd = in.readInt() != 0; + config.creatorUid = in.readInt(); + config.lastConnectUid = in.readInt(); + config.lastUpdateUid = in.readInt(); /* TODO: should we write the cache results to the parcel? boolean done = false; diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 9ea7027..3b65ca8 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -40,6 +40,7 @@ import java.util.concurrent.CountDownLatch; * Get an instance of this class by calling * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context * .WIFI_SCANNING_SERVICE)}. + * @hide */ public class WifiScanner { @@ -72,16 +73,14 @@ public class WifiScanner { public static final int REASON_INVALID_LISTENER = -2; /** Invalid request */ public static final int REASON_INVALID_REQUEST = -3; - /** Request conflicts with other scans that may be going on */ - public static final int REASON_CONFLICTING_REQUEST = -4; /** * Generic action callback invocation interface * @hide */ public static interface ActionListener { - public void onSuccess(Object result); - public void onFailure(int reason, Object exception); + public void onSuccess(); + public void onFailure(int reason, String description); } /** @@ -193,58 +192,6 @@ public class WifiScanner { } - /** information element from beacon */ - public static class InformationElement { - public int id; - public byte[] bytes; - } - - /** scan result with information elements from beacons */ - public static class FullScanResult implements Parcelable { - public ScanResult result; - public InformationElement informationElements[]; - - /** Implement the Parcelable interface {@hide} */ - public int describeContents() { - return 0; - } - - /** Implement the Parcelable interface {@hide} */ - public void writeToParcel(Parcel dest, int flags) { - result.writeToParcel(dest, flags); - dest.writeInt(informationElements.length); - for (int i = 0; i < informationElements.length; i++) { - dest.writeInt(informationElements[i].id); - dest.writeInt(informationElements[i].bytes.length); - dest.writeByteArray(informationElements[i].bytes); - } - } - - /** Implement the Parcelable interface {@hide} */ - public static final Creator<FullScanResult> CREATOR = - new Creator<FullScanResult>() { - public FullScanResult createFromParcel(Parcel in) { - FullScanResult result = new FullScanResult(); - result.result = ScanResult.CREATOR.createFromParcel(in); - int n = in.readInt(); - result.informationElements = new InformationElement[n]; - for (int i = 0; i < n; i++) { - result.informationElements[i] = new InformationElement(); - result.informationElements[i].id = in.readInt(); - int len = in.readInt(); - result.informationElements[i].bytes = new byte[len]; - in.readByteArray(result.informationElements[i].bytes); - } - - return result; - } - - public FullScanResult[] newArray(int size) { - return new FullScanResult[size]; - } - }; - } - /** @hide */ public static class ParcelableScanResults implements Parcelable { public ScanResult mResults[]; @@ -305,7 +252,7 @@ public class WifiScanner { /** * reports full scan result for each access point found in scan */ - public void onFullResult(FullScanResult fullScanResult); + public void onFullResult(ScanResult fullScanResult); } /** @hide */ @@ -336,13 +283,12 @@ public class WifiScanner { } /** * retrieves currently available scan results - * @param flush {@code true} means flush all results - * @param listener specifies which scan to cancel; must be same object as passed in {@link - * #startBackgroundScan} */ - public void retrieveScanResults(boolean flush, ScanListener listener) { + public ScanResult[] getScanResults() { validateChannel(); - sAsyncChannel.sendMessage(CMD_GET_SCAN_RESULTS, 0, getListenerKey(listener)); + Message reply = sAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0); + ScanResult[] results = (ScanResult[]) reply.obj; + return results; } /** specifies information about an access point of interest */ @@ -490,7 +436,7 @@ public class WifiScanner { } /** interface to receive hotlist events on; use this on {@link #setHotlist} */ - public static interface HotlistListener extends ActionListener { + public static interface HotspotListener extends ActionListener { /** indicates that access points were found by on going scans * @param results list of scan results, one for each access point visible currently */ @@ -550,10 +496,10 @@ public class WifiScanner { * @param hotspots access points of interest * @param apLostThreshold number of scans needed to indicate that AP is lost * @param listener object provided to report events on; this object must be unique and must - * also be provided on {@link #resetHotlist} + * also be provided on {@link #stopTrackingHotspots} */ - public void setHotlist(HotspotInfo[] hotspots, - int apLostThreshold, HotlistListener listener) { + public void startTrackingHotspots(HotspotInfo[] hotspots, + int apLostThreshold, HotspotListener listener) { validateChannel(); HotlistSettings settings = new HotlistSettings(); settings.hotspotInfos = hotspots; @@ -562,9 +508,9 @@ public class WifiScanner { /** * remove tracking of interesting access points - * @param listener same object provided in {@link #setHotlist} + * @param listener same object provided in {@link #startTrackingHotspots} */ - public void resetHotlist(HotlistListener listener) { + public void stopTrackingHotspots(HotspotListener listener) { validateChannel(); sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener)); } @@ -769,10 +715,10 @@ public class WifiScanner { switch (msg.what) { /* ActionListeners grouped together */ case CMD_OP_SUCCEEDED : - ((ActionListener) listener).onSuccess(msg.obj); + ((ActionListener) listener).onSuccess(); break; case CMD_OP_FAILED : - ((ActionListener) listener).onFailure(msg.arg1, msg.obj); + ((ActionListener) listener).onFailure(msg.arg1, (String)msg.obj); removeListener(msg.arg2); break; case CMD_SCAN_RESULT : @@ -780,14 +726,14 @@ public class WifiScanner { ((ParcelableScanResults) msg.obj).getResults()); return; case CMD_FULL_SCAN_RESULT : - FullScanResult result = (FullScanResult) msg.obj; + ScanResult result = (ScanResult) msg.obj; ((ScanListener) listener).onFullResult(result); return; case CMD_PERIOD_CHANGED: ((ScanListener) listener).onPeriodChanged(msg.arg1); return; case CMD_AP_FOUND: - ((HotlistListener) listener).onFound( + ((HotspotListener) listener).onFound( ((ParcelableScanResults) msg.obj).getResults()); return; case CMD_WIFI_CHANGE_DETECTED: diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java index 090ac56..54ac71e 100644 --- a/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java +++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java @@ -20,75 +20,91 @@ import android.net.wifi.WifiEnterpriseConfig; import android.os.Parcelable; import android.os.Parcel; +import java.util.ArrayList; import java.util.Collection; -import java.util.Set; +import java.util.List; import java.util.Iterator; import java.util.Map; +import java.util.Set; + /** * A class representing a Wi-Fi Passpoint credential. + * @hide */ public class WifiPasspointCredential implements Parcelable { private final static String TAG = "PasspointCredential"; - private String mWifiTreePath; - private String mWifiSPFQDN; + private final static boolean DBG = true; + + /** Wi-Fi nodes**/ + private String mWifiSpFqdn; + + /** PerProviderSubscription nodes **/ private String mCredentialName; - private String mUpdateIdentifier; + + /** SubscriptionUpdate nodes **/ + private String mSubscriptionUpdateInterval; private String mSubscriptionUpdateMethod; - private WifiEnterpriseConfig mEnterpriseConfig; + private String mSubscriptionUpdateRestriction; + private String mSubscriptionUpdateURI; + private String mSubscriptionUpdateUsername; + private String mSubscriptionUpdatePassword; + + /** HomeSP nodes **/ + private String mHomeSpFqdn; + private String mFriendlyName; + private Collection<WifiPasspointDmTree.HomeOIList> mHomeOIList; + private Collection<WifiPasspointDmTree.OtherHomePartners> mOtherHomePartnerList; + + /** SubscriptionParameters nodes**/ + private String mCreationDate; + private String mExpirationDate; + + /** Credential nodes **/ private String mType; private String mInnerMethod; private String mCertType; private String mCertSha256Fingerprint; + private String mUpdateIdentifier; private String mUsername; private String mPasswd; + private String mRealm; private String mImsi; private String mMcc; private String mMnc; private String mCaRootCert; - private String mRealm; - private int mPriority; //User preferred priority; The smaller, the higher - private boolean mUserPreferred = false; - private String mHomeSpFqdn; - private String mFriendlyName; - private String mOtherhomepartnerFqdn; private String mClientCert; - private String mCreationDate; - private String mExpirationDate; - - private String mSubscriptionDMAccUsername; - private String mSubscriptionDMAccPassword; - private String mSubscriptionUpdateInterval; + private boolean mCheckAaaServerCertStatus; - private String mPolicyUpdateURI; + /** Policy nodes **/ + private String mPolicyUpdateUri; private String mPolicyUpdateInterval; - private String mPolicyDMAccUsername; - private String mPolicyDMAccPassword; + private String mPolicyUpdateUsername; + private String mPolicyUpdatePassword; private String mPolicyUpdateRestriction; private String mPolicyUpdateMethod; - private Collection<WifiPasspointDmTree.PreferredRoamingPartnerList> mPreferredRoamingPartnerList; - private Collection<WifiPasspointDmTree.HomeOIList> mHomeOIList; private Collection<WifiPasspointDmTree.MinBackhaulThresholdNetwork> mMinBackhaulThresholdNetwork; - private Collection<WifiPasspointDmTree.RequiredProtoPortTuple> mRequiredProtoPortTuple; private Collection<WifiPasspointDmTree.SPExclusionList> mSpExclusionList; + private Collection<WifiPasspointDmTree.RequiredProtoPortTuple> mRequiredProtoPortTuple; private String mMaxBssLoad; - private boolean mIsMachineRemediation; - - private String mAAACertURL; - private String mAAASha256Fingerprint; + /** CrednetialPriority node **/ + private int mCrednetialPriority; - private String mSubscriptionUpdateRestriction; - private String mSubscriptionUpdateURI; + /** AAAServerTrustRoot nodes **/ + private String mAaaCertUrl; + private String mAaaSha256Fingerprint; - private boolean mCheckAaaServerCertStatus; + /** Others **/ + private boolean mIsMachineRemediation; + private boolean mUserPreferred = false; + private String mWifiTreePath; + private WifiEnterpriseConfig mEnterpriseConfig; /** @hide */ - public WifiPasspointCredential() { - - } + public WifiPasspointCredential() {} /** * Constructor @@ -113,84 +129,6 @@ public class WifiPasspointCredential implements Parcelable { public WifiPasspointCredential(String type, String caroot, String clientcert, - WifiPasspointDmTree.SpFqdn sp, - WifiPasspointDmTree.CredentialInfo credinfo) { - - if (credinfo == null) { - return; - } - - mType = type; - mCaRootCert = caroot; - mClientCert = clientcert; - - mWifiSPFQDN = sp.nodeName; - mUpdateIdentifier = sp.perProviderSubscription.UpdateIdentifier; - - mCredentialName = credinfo.nodeName; - Set set = credinfo.homeSP.otherHomePartners.entrySet(); - Iterator i = set.iterator(); - if (i.hasNext()) { - Map.Entry entry3 = (Map.Entry) i.next(); - WifiPasspointDmTree.OtherHomePartners ohp = (WifiPasspointDmTree.OtherHomePartners) entry3.getValue(); - mOtherhomepartnerFqdn = ohp.FQDN; - } - - set = credinfo.aAAServerTrustRoot.entrySet(); - i = set.iterator(); - if (i.hasNext()) { - Map.Entry entry3 = (Map.Entry) i.next(); - WifiPasspointDmTree.AAAServerTrustRoot aaa = (WifiPasspointDmTree.AAAServerTrustRoot) entry3.getValue(); - mAAACertURL = aaa.CertURL; - mAAASha256Fingerprint = aaa.CertSHA256Fingerprint; - } - - mCertType = credinfo.credential.digitalCertificate.CertificateType; - mCertSha256Fingerprint = credinfo.credential.digitalCertificate.CertSHA256Fingerprint; - mUsername = credinfo.credential.usernamePassword.Username; - mPasswd = credinfo.credential.usernamePassword.Password; - mIsMachineRemediation = credinfo.credential.usernamePassword.MachineManaged; - mInnerMethod = credinfo.credential.usernamePassword.eAPMethod.InnerMethod; - mImsi = credinfo.credential.sim.IMSI; - mCreationDate = credinfo.credential.CreationDate; - mExpirationDate = credinfo.credential.ExpirationDate; - mRealm = credinfo.credential.Realm; - - if (credinfo.credentialPriority == null) { - credinfo.credentialPriority = "128"; - } - mPriority = Integer.parseInt(credinfo.credentialPriority); - - mHomeSpFqdn = credinfo.homeSP.FQDN; - - mSubscriptionUpdateInterval = credinfo.subscriptionUpdate.UpdateInterval; - mSubscriptionUpdateMethod = credinfo.subscriptionUpdate.UpdateMethod; - mSubscriptionUpdateRestriction = credinfo.subscriptionUpdate.Restriction; - mSubscriptionUpdateURI = credinfo.subscriptionUpdate.URI; - mSubscriptionDMAccUsername = credinfo.subscriptionUpdate.usernamePassword.Username; - mSubscriptionDMAccPassword = credinfo.subscriptionUpdate.usernamePassword.Password; - - mPolicyUpdateURI = credinfo.policy.policyUpdate.URI; - mPolicyUpdateInterval = credinfo.policy.policyUpdate.UpdateInterval; - mPolicyDMAccUsername = credinfo.policy.policyUpdate.usernamePassword.Username; - mPolicyDMAccPassword = credinfo.policy.policyUpdate.usernamePassword.Password; - mPolicyUpdateRestriction = credinfo.policy.policyUpdate.Restriction; - mPolicyUpdateMethod = credinfo.policy.policyUpdate.UpdateMethod; - mPreferredRoamingPartnerList = credinfo.policy.preferredRoamingPartnerList.values(); - mMinBackhaulThresholdNetwork = credinfo.policy.minBackhaulThreshold.values(); - mRequiredProtoPortTuple = credinfo.policy.requiredProtoPortTuple.values(); - mMaxBssLoad = credinfo.policy.maximumBSSLoadValue; - mSpExclusionList = credinfo.policy.sPExclusionList.values(); - - mHomeOIList = credinfo.homeSP.homeOIList.values(); - mFriendlyName = credinfo.homeSP.FriendlyName; - mCheckAaaServerCertStatus = credinfo.credential.CheckAAAServerCertStatus; - } - - /** @hide */ - public WifiPasspointCredential(String type, - String caroot, - String clientcert, String mcc, String mnc, WifiPasspointDmTree.SpFqdn sp, @@ -204,25 +142,19 @@ public class WifiPasspointCredential implements Parcelable { mCaRootCert = caroot; mClientCert = clientcert; - mWifiSPFQDN = sp.nodeName; + mWifiSpFqdn = sp.nodeName; mUpdateIdentifier = sp.perProviderSubscription.UpdateIdentifier; mCredentialName = credinfo.nodeName; - Set set = credinfo.homeSP.otherHomePartners.entrySet(); - Iterator i = set.iterator(); - if (i.hasNext()) { - Map.Entry entry3 = (Map.Entry) i.next(); - WifiPasspointDmTree.OtherHomePartners ohp = (WifiPasspointDmTree.OtherHomePartners) entry3.getValue(); - mOtherhomepartnerFqdn = ohp.FQDN; - } + mOtherHomePartnerList = credinfo.homeSP.otherHomePartners.values(); - set = credinfo.aAAServerTrustRoot.entrySet(); - i = set.iterator(); + Set set = credinfo.aAAServerTrustRoot.entrySet(); + Iterator i = set.iterator(); if (i.hasNext()) { Map.Entry entry3 = (Map.Entry) i.next(); WifiPasspointDmTree.AAAServerTrustRoot aaa = (WifiPasspointDmTree.AAAServerTrustRoot) entry3.getValue(); - mAAACertURL = aaa.CertURL; - mAAASha256Fingerprint = aaa.CertSHA256Fingerprint; + mAaaCertUrl = aaa.CertURL; + mAaaSha256Fingerprint = aaa.CertSHA256Fingerprint; } mCertType = credinfo.credential.digitalCertificate.CertificateType; @@ -239,22 +171,24 @@ public class WifiPasspointCredential implements Parcelable { mRealm = credinfo.credential.Realm; if (credinfo.credentialPriority == null) { - credinfo.credentialPriority = "128"; + mCrednetialPriority = 128; + } else { + mCrednetialPriority = Integer.parseInt(credinfo.credentialPriority); } - mPriority = Integer.parseInt(credinfo.credentialPriority); mHomeSpFqdn = credinfo.homeSP.FQDN; + mSubscriptionUpdateInterval = credinfo.subscriptionUpdate.UpdateInterval; mSubscriptionUpdateMethod = credinfo.subscriptionUpdate.UpdateMethod; mSubscriptionUpdateRestriction = credinfo.subscriptionUpdate.Restriction; mSubscriptionUpdateURI = credinfo.subscriptionUpdate.URI; - mSubscriptionDMAccUsername = credinfo.subscriptionUpdate.usernamePassword.Username; - mSubscriptionDMAccPassword = credinfo.subscriptionUpdate.usernamePassword.Password; + mSubscriptionUpdateUsername = credinfo.subscriptionUpdate.usernamePassword.Username; + mSubscriptionUpdatePassword = credinfo.subscriptionUpdate.usernamePassword.Password; - mPolicyUpdateURI = credinfo.policy.policyUpdate.URI; + mPolicyUpdateUri = credinfo.policy.policyUpdate.URI; mPolicyUpdateInterval = credinfo.policy.policyUpdate.UpdateInterval; - mPolicyDMAccUsername = credinfo.policy.policyUpdate.usernamePassword.Username; - mPolicyDMAccPassword = credinfo.policy.policyUpdate.usernamePassword.Password; + mPolicyUpdateUsername = credinfo.policy.policyUpdate.usernamePassword.Username; + mPolicyUpdatePassword = credinfo.policy.policyUpdate.usernamePassword.Password; mPolicyUpdateRestriction = credinfo.policy.policyUpdate.Restriction; mPolicyUpdateMethod = credinfo.policy.policyUpdate.UpdateMethod; mPreferredRoamingPartnerList = credinfo.policy.preferredRoamingPartnerList.values(); @@ -265,6 +199,7 @@ public class WifiPasspointCredential implements Parcelable { mHomeOIList = credinfo.homeSP.homeOIList.values(); mFriendlyName = credinfo.homeSP.FriendlyName; + mCheckAaaServerCertStatus = credinfo.credential.CheckAAAServerCertStatus; } /** @hide */ @@ -283,8 +218,8 @@ public class WifiPasspointCredential implements Parcelable { } /** @hide */ - public String getWifiSPFQDN() { - return mWifiSPFQDN; + public String getWifiSpFqdn() { + return mWifiSpFqdn; } /** @hide */ @@ -293,7 +228,7 @@ public class WifiPasspointCredential implements Parcelable { } /** @hide */ - public String getEapMethodStr() { + public String getType() { return mType; } @@ -383,14 +318,14 @@ public class WifiPasspointCredential implements Parcelable { return 0; } - return mPriority; + return mCrednetialPriority; } /** * Get the fully qualified domain name (FQDN) of this Passpoint credential. * @return FQDN */ - public String getFqdn() { + public String getHomeSpFqdn() { return mHomeSpFqdn; } @@ -404,23 +339,23 @@ public class WifiPasspointCredential implements Parcelable { /** @hide */ - public String getOtherhomepartners() { - return mOtherhomepartnerFqdn; + public Collection<WifiPasspointDmTree.OtherHomePartners> getOtherHomePartnerList() { + return mOtherHomePartnerList; } /** @hide */ - public String getSubscriptionDMAccUsername() { - return mSubscriptionDMAccUsername; + public String getSubscriptionUpdateUsername() { + return mSubscriptionUpdateUsername; } /** @hide */ - public String getSubscriptionDMAccPassword() { - return mSubscriptionDMAccPassword; + public String getSubscriptionUpdatePassword() { + return mSubscriptionUpdatePassword; } /** @hide */ - public String getPolicyUpdateURI() { - return mPolicyUpdateURI; + public String getPolicyUpdateUri() { + return mPolicyUpdateUri; } /** @hide */ @@ -429,13 +364,13 @@ public class WifiPasspointCredential implements Parcelable { } /** @hide */ - public String getPolicyDMAccUsername() { - return mPolicyDMAccUsername; + public String getPolicyUpdateUsername() { + return mPolicyUpdateUsername; } /** @hide */ - public String getPolicyDMAccPassword() { - return mPolicyDMAccPassword; + public String getPolicyUpdatePassword() { + return mPolicyUpdatePassword; } /** @hide */ @@ -464,12 +399,12 @@ public class WifiPasspointCredential implements Parcelable { } /** @hide */ - public Collection<WifiPasspointDmTree.PreferredRoamingPartnerList> getPrpList() { + public Collection<WifiPasspointDmTree.PreferredRoamingPartnerList> getPreferredRoamingPartnerList() { return mPreferredRoamingPartnerList; } /** @hide */ - public Collection<WifiPasspointDmTree.HomeOIList> getHomeOIList() { + public Collection<WifiPasspointDmTree.HomeOIList> getHomeOiList() { return mHomeOIList; } @@ -494,13 +429,13 @@ public class WifiPasspointCredential implements Parcelable { } /** @hide */ - public String getAAACertURL() { - return mAAACertURL; + public String getAaaCertUrl() { + return mAaaCertUrl; } /** @hide */ - public String getAAASha256Fingerprint() { - return mAAASha256Fingerprint; + public String getAaaSha256Fingerprint() { + return mAaaSha256Fingerprint; } /** @hide */ @@ -577,72 +512,75 @@ public class WifiPasspointCredential implements Parcelable { StringBuffer sb = new StringBuffer(); String none = "<none>"; - sb.append(", UpdateIdentifier: ") - .append(mUpdateIdentifier == null ? none : mUpdateIdentifier). - append(", SubscriptionUpdateMethod: ") - .append(mSubscriptionUpdateMethod == null ? none : mSubscriptionUpdateMethod). - append(", Type: ").append(mType == null ? none : mType). - append(", Username: ").append(mUsername == null ? none : mUsername). - append(", Passwd: ").append(mPasswd == null ? none : mPasswd). - append(", SubDMAccUsername: ") - .append(mSubscriptionDMAccUsername == null ? none : mSubscriptionDMAccUsername). - append(", SubDMAccPassword: ") - .append(mSubscriptionDMAccPassword == null ? none : mSubscriptionDMAccPassword). - append(", PolDMAccUsername: ") - .append(mPolicyDMAccUsername == null ? none : mPolicyDMAccUsername). - append(", PolDMAccPassword: ") - .append(mPolicyDMAccPassword == null ? none : mPolicyDMAccPassword). - append(", Imsi: ").append(mImsi == null ? none : mImsi). - append(", Mcc: ").append(mMcc == null ? none : mMcc). - append(", Mnc: ").append(mMnc == null ? none : mMnc). - append(", CaRootCert: ").append(mCaRootCert == null ? none : mCaRootCert). - append(", Realm: ").append(mRealm == null ? none : mRealm). - append(", Priority: ").append(mPriority). - append(", Fqdn: ").append(mHomeSpFqdn == null ? none : mHomeSpFqdn). - append(", Otherhomepartners: ") - .append(mOtherhomepartnerFqdn == null ? none : mOtherhomepartnerFqdn). - append(", ExpirationDate: ") - .append(mExpirationDate == null ? none : mExpirationDate). - append(", MaxBssLoad: ").append(mMaxBssLoad == null ? none : mMaxBssLoad). - append(", SPExclusionList: ").append(mSpExclusionList); - - if (mPreferredRoamingPartnerList != null) { - sb.append("PreferredRoamingPartnerList:"); - for (WifiPasspointDmTree.PreferredRoamingPartnerList prpListItem : mPreferredRoamingPartnerList) { - sb.append("[fqdnmatch:").append(prpListItem.FQDN_Match). - append(", priority:").append(prpListItem.Priority). - append(", country:").append(prpListItem.Country).append("]"); + if (!DBG) { + sb.append(none); + } else { + sb.append(", UpdateIdentifier: ") + .append(mUpdateIdentifier == null ? none : mUpdateIdentifier) + .append(", SubscriptionUpdateMethod: ") + .append(mSubscriptionUpdateMethod == null ? none : mSubscriptionUpdateMethod) + .append(", Type: ").append(mType == null ? none : mType) + .append(", Username: ").append(mUsername == null ? none : mUsername) + .append(", Passwd: ").append(mPasswd == null ? none : mPasswd) + .append(", SubDMAccUsername: ") + .append(mSubscriptionUpdateUsername == null ? none : mSubscriptionUpdateUsername) + .append(", SubDMAccPassword: ") + .append(mSubscriptionUpdatePassword == null ? none : mSubscriptionUpdatePassword) + .append(", PolDMAccUsername: ") + .append(mPolicyUpdateUsername == null ? none : mPolicyUpdateUsername) + .append(", PolDMAccPassword: ") + .append(mPolicyUpdatePassword == null ? none : mPolicyUpdatePassword) + .append(", Imsi: ").append(mImsi == null ? none : mImsi) + .append(", Mcc: ").append(mMcc == null ? none : mMcc) + .append(", Mnc: ").append(mMnc == null ? none : mMnc) + .append(", CaRootCert: ").append(mCaRootCert == null ? none : mCaRootCert) + .append(", Realm: ").append(mRealm == null ? none : mRealm) + .append(", Priority: ").append(mCrednetialPriority) + .append(", Fqdn: ").append(mHomeSpFqdn == null ? none : mHomeSpFqdn) + .append(", Otherhomepartners: ") + .append(mOtherHomePartnerList == null ? none : mOtherHomePartnerList) + .append(", ExpirationDate: ") + .append(mExpirationDate == null ? none : mExpirationDate) + .append(", MaxBssLoad: ").append(mMaxBssLoad == null ? none : mMaxBssLoad) + .append(", SPExclusionList: ").append(mSpExclusionList); + + if (mPreferredRoamingPartnerList != null) { + sb.append("PreferredRoamingPartnerList:"); + for (WifiPasspointDmTree.PreferredRoamingPartnerList prpListItem : mPreferredRoamingPartnerList) { + sb.append("[fqdnmatch:").append(prpListItem.FQDN_Match). + append(", priority:").append(prpListItem.Priority). + append(", country:").append(prpListItem.Country).append("]"); + } } - } - if (mHomeOIList != null) { - sb.append("HomeOIList:"); - for (WifiPasspointDmTree.HomeOIList HomeOIListItem : mHomeOIList) { - sb.append("[HomeOI:").append(HomeOIListItem.HomeOI). - append(", HomeOIRequired:").append(HomeOIListItem.HomeOIRequired). - append("]"); + if (mHomeOIList != null) { + sb.append("HomeOIList:"); + for (WifiPasspointDmTree.HomeOIList HomeOIListItem : mHomeOIList) { + sb.append("[HomeOI:").append(HomeOIListItem.HomeOI). + append(", HomeOIRequired:").append(HomeOIListItem.HomeOIRequired). + append("]"); + } } - } - if (mMinBackhaulThresholdNetwork != null) { - sb.append("BackHaulThreshold:"); - for (WifiPasspointDmTree.MinBackhaulThresholdNetwork BhtListItem : mMinBackhaulThresholdNetwork) { - sb.append("[networkType:").append(BhtListItem.NetworkType). - append(", dlBandwidth:").append(BhtListItem.DLBandwidth). - append(", ulBandwidth:").append(BhtListItem.ULBandwidth). - append("]"); + if (mMinBackhaulThresholdNetwork != null) { + sb.append("BackHaulThreshold:"); + for (WifiPasspointDmTree.MinBackhaulThresholdNetwork BhtListItem : mMinBackhaulThresholdNetwork) { + sb.append("[networkType:").append(BhtListItem.NetworkType). + append(", dlBandwidth:").append(BhtListItem.DLBandwidth). + append(", ulBandwidth:").append(BhtListItem.ULBandwidth). + append("]"); + } } - } - if (mRequiredProtoPortTuple != null) { - sb.append("WifiMORequiredProtoPortTupleList:"); - for (WifiPasspointDmTree.RequiredProtoPortTuple RpptListItem : mRequiredProtoPortTuple) { - sb.append("[IPProtocol:").append(RpptListItem.IPProtocol). - append(", PortNumber:").append(RpptListItem.PortNumber). - append("]"); + if (mRequiredProtoPortTuple != null) { + sb.append("WifiMORequiredProtoPortTupleList:"); + for (WifiPasspointDmTree.RequiredProtoPortTuple RpptListItem : mRequiredProtoPortTuple) { + sb.append("[IPProtocol:").append(RpptListItem.IPProtocol). + append(", PortNumber:").append(RpptListItem.PortNumber). + append("]"); + } } } - return sb.toString(); } @@ -653,19 +591,22 @@ public class WifiPasspointCredential implements Parcelable { /** Implement the Parcelable interface {@hide} */ public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mWifiSpFqdn); + dest.writeString(mCredentialName); dest.writeString(mType); - dest.writeString(mUsername); - dest.writeString(mPasswd); - dest.writeString(mImsi); - dest.writeString(mMcc); - dest.writeString(mMnc); - dest.writeString(mCaRootCert); - dest.writeString(mRealm); - dest.writeInt(mPriority); + dest.writeInt(mCrednetialPriority); dest.writeString(mHomeSpFqdn); - dest.writeString(mOtherhomepartnerFqdn); - dest.writeString(mClientCert); - dest.writeString(mExpirationDate); + dest.writeString(mRealm); + } + + /** Implement the Parcelable interface {@hide} */ + public void readFromParcel(Parcel in) { + mWifiSpFqdn = in.readString(); + mCredentialName = in.readString(); + mType = in.readString(); + mCrednetialPriority = in.readInt(); + mHomeSpFqdn = in.readString(); + mRealm = in.readString(); } /** Implement the Parcelable interface {@hide} */ @@ -673,19 +614,12 @@ public class WifiPasspointCredential implements Parcelable { new Creator<WifiPasspointCredential>() { public WifiPasspointCredential createFromParcel(Parcel in) { WifiPasspointCredential pc = new WifiPasspointCredential(); + pc.mWifiSpFqdn = in.readString(); + pc.mCredentialName = in.readString(); pc.mType = in.readString(); - pc.mUsername = in.readString(); - pc.mPasswd = in.readString(); - pc.mImsi = in.readString(); - pc.mMcc = in.readString(); - pc.mMnc = in.readString(); - pc.mCaRootCert = in.readString(); - pc.mRealm = in.readString(); - pc.mPriority = in.readInt(); + pc.mCrednetialPriority = in.readInt(); pc.mHomeSpFqdn = in.readString(); - pc.mOtherhomepartnerFqdn = in.readString(); - pc.mClientCert = in.readString(); - pc.mExpirationDate = in.readString(); + pc.mRealm = in.readString(); return pc; } @@ -698,9 +632,9 @@ public class WifiPasspointCredential implements Parcelable { public int compareTo(WifiPasspointCredential another) { //The smaller the higher - if (mPriority < another.mPriority) { + if (mCrednetialPriority < another.mCrednetialPriority) { return -1; - } else if (mPriority == another.mPriority) { + } else if (mCrednetialPriority == another.mCrednetialPriority) { return this.mType.compareTo(another.mType); } else { return 1; diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointDmTree.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointDmTree.java index 9ff1973..bbf5fc6 100644 --- a/wifi/java/android/net/wifi/passpoint/WifiPasspointDmTree.java +++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointDmTree.java @@ -25,19 +25,17 @@ import java.util.HashMap; /** * Required Mobile Device Management Tree Structure * - * +----------+ - * | ./(Root) | - * +----+-----+ - * | - * +---------+ | +---------+ +---------+ - * | DevInfo |-----------+---------| Wi-Fi |---|SP FQDN* | - * +---------+ | +---------+ +---------+ - * +---------+ | - * |DevDetail|-----------+ - * +---------+ - * - * For example, - * ./Wi-Fi/wi-fi.org/PerproviderSubscription/Cred01/Policy/PreferredRoamingPartnerList/Roa01/FQDN_Math + * +----------+ + * | ./(Root) | + * +----+-----+ + * | + * +---------+ | +---------+ +---------+ + * | DevInfo |-----------+---------| Wi-Fi |--|SP FQDN* | + * +---------+ | +---------+ +---------+ + * +---------+ | | + * |DevDetail|-----------+ +-----------------------+ + * +---------+ |PerproviderSubscription|--<X>+ + * +-----------------------+ * * This class contains all nodes start from Wi-Fi * @hide diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java index ebaa8c9..55acbad 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 { diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointPolicy.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointPolicy.java index 5f76562..9fccf0a 100644 --- a/wifi/java/android/net/wifi/passpoint/WifiPasspointPolicy.java +++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointPolicy.java @@ -35,7 +35,7 @@ public class WifiPasspointPolicy implements Parcelable { public static final int UNRESTRICTED = 2; private String mName; - private int mSubscriptionPriority; + private int mCredentialPriority; private int mRoamingPriority; private String mBssid; private String mSsid; @@ -44,11 +44,13 @@ public class WifiPasspointPolicy implements Parcelable { private boolean mIsHomeSp; /** @hide */ - public WifiPasspointPolicy(String name, int priority, String ssid, + public WifiPasspointPolicy(String name, String ssid, String bssid, WifiPasspointCredential pc, int restriction, boolean ishomesp) { mName = name; - mSubscriptionPriority = priority; + if (pc != null) { + mCredentialPriority = pc.getPriority(); + } //PerProviderSubscription/<X+>/Policy/PreferredRoamingPartnerList/<X+>/Priority mRoamingPriority = 128; //default priority value of 128 mSsid = ssid; @@ -102,8 +104,8 @@ public class WifiPasspointPolicy implements Parcelable { } /** @hide */ - public void setSubscriptionPriority(int priority) { - mSubscriptionPriority = priority; + public void setCredentialPriority(int priority) { + mCredentialPriority = priority; } /** @hide */ @@ -111,8 +113,8 @@ public class WifiPasspointPolicy implements Parcelable { mRoamingPriority = priority; } - public int getSubscriptionPriority() { - return mSubscriptionPriority; + public int getCredentialPriority() { + return mCredentialPriority; } public int getRoamingPriority() { @@ -132,11 +134,11 @@ public class WifiPasspointPolicy implements Parcelable { return -1; } else if ((this.mIsHomeSp == true && another.getHomeSp() == true)) { Log.d(TAG, "both HomeSP"); - //if both home sp, compare subscription priority - if (this.mSubscriptionPriority < another.getSubscriptionPriority()) { + //if both home sp, compare credential priority + if (this.mCredentialPriority < another.getCredentialPriority()) { Log.d(TAG, "this priority is higher"); return -1; - } else if (this.mSubscriptionPriority == another.getSubscriptionPriority()) { + } else if (this.mCredentialPriority == another.getCredentialPriority()) { Log.d(TAG, "both priorities equal"); //if priority still the same, compare name(ssid) if (this.mName.compareTo(another.mName) != 0) { @@ -192,7 +194,7 @@ public class WifiPasspointPolicy implements Parcelable { @Override /** @hide */ public String toString() { - return "PasspointPolicy: name=" + mName + " SubscriptionPriority=" + mSubscriptionPriority + + return "PasspointPolicy: name=" + mName + " CredentialPriority=" + mCredentialPriority + " mRoamingPriority" + mRoamingPriority + " ssid=" + mSsid + " restriction=" + mRestriction + " ishomesp=" + mIsHomeSp + " Credential=" + mCredential; |
