diff options
408 files changed, 17398 insertions, 11454 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 02563372..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 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 BluetoothLeScanFilter implements android.os.Parcelable { + 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"; @@ -7083,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(); @@ -7782,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(); @@ -7799,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); @@ -7807,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[]); @@ -8199,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(); @@ -8210,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 { @@ -11476,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 { @@ -12724,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 { @@ -14295,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(); @@ -14345,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(); @@ -14677,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; @@ -15814,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; @@ -15875,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 } } @@ -17289,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); @@ -17716,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"; @@ -20575,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; @@ -20703,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); @@ -20730,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[]); @@ -20772,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; @@ -20813,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); @@ -21395,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; @@ -26391,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); @@ -26427,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); @@ -26581,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(); @@ -26643,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(); @@ -26748,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(); } @@ -26822,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); @@ -27583,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); } @@ -27648,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(); @@ -27656,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(); @@ -27664,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); } @@ -27673,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); } @@ -27683,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); } @@ -27763,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); @@ -28762,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(); @@ -38141,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/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java index 2673031..4503726 100644 --- a/cmds/bu/src/com/android/commands/bu/Backup.java +++ b/cmds/bu/src/com/android/commands/bu/Backup.java @@ -20,6 +20,7 @@ import android.app.backup.IBackupManager; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; +import android.system.OsConstants; import android.util.Log; import java.io.IOException; @@ -50,13 +51,11 @@ public final class Backup { return; } - int socketFd = Integer.parseInt(nextArg()); - String arg = nextArg(); if (arg.equals("backup")) { - doFullBackup(socketFd); + doFullBackup(OsConstants.STDOUT_FILENO); } else if (arg.equals("restore")) { - doFullRestore(socketFd); + doFullRestore(OsConstants.STDIN_FILENO); } else { Log.e(TAG, "Invalid operation '" + arg + "'"); } diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index b7c2c22..47047b8 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -39,6 +39,7 @@ import android.content.pm.VerificationParams; import android.content.res.AssetManager; import android.content.res.Resources; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.IUserManager; import android.os.Process; @@ -57,7 +58,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.WeakHashMap; - import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; @@ -823,6 +823,7 @@ public final class Pm { byte[] tag = null; String originatingUriString = null; String referrer = null; + String abi = null; while ((opt=nextOption()) != null) { if (opt.equals("-l")) { @@ -893,12 +894,34 @@ public final class Pm { System.err.println("Error: must supply argument for --referrer"); return; } + } else if (opt.equals("--abi")) { + abi = nextOptionData(); + if (abi == null) { + System.err.println("Error: must supply argument for --abi"); + return; + } } else { System.err.println("Error: Unknown option: " + opt); return; } } + 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) { + System.err.println("Error: abi " + abi + " not supported on this device."); + return; + } + } + final ContainerEncryptionParams encryptionParams; if (algo != null || iv != null || key != null || macAlgo != null || macKey != null || tag != null) { @@ -976,8 +999,9 @@ public final class Pm { VerificationParams verificationParams = new VerificationParams(verificationURI, originatingURI, referrerURI, VerificationParams.NO_UID, null); - mPm.installPackageWithVerificationAndEncryptionEtc(apkURI, null, obs, installFlags, - installerPackageName, verificationParams, encryptionParams); + mPm.installPackageWithVerificationEncryptionAndAbiOverrideEtc(apkURI, null, + obs, installFlags, installerPackageName, verificationParams, + encryptionParams, abi); synchronized (obs) { while (!obs.finished) { 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/ContextImpl.java b/core/java/android/app/ContextImpl.java index 1bd5abf..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; @@ -251,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 = {}; @@ -660,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); @@ -1039,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); } 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..85e970c 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 eventually 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 activity 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 response 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 index da5cb10..46f082e 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -22,7 +22,6 @@ import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import com.android.internal.backup.BackupConstants; import com.android.internal.backup.IBackupTransport; /** @@ -32,6 +31,13 @@ import com.android.internal.backup.IBackupTransport; * @hide */ public class BackupTransport { + public static final int TRANSPORT_OK = 0; + public static final int TRANSPORT_ERROR = 1; + public static final int TRANSPORT_NOT_INITIALIZED = 2; + public static final int TRANSPORT_PACKAGE_REJECTED = 3; + public static final int AGENT_ERROR = 4; + public static final int AGENT_UNKNOWN = 5; + IBackupTransport mBinderImpl = new TransportImpl(); /** @hide */ public IBinder getBinder() { @@ -99,10 +105,50 @@ public class BackupTransport { } // ------------------------------------------------------------------------------------ + // Device-level operations common to both key/value and full-data storage + + /** + * 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 BackupTransport#TRANSPORT_OK} (OK so far) or + * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure). + */ + public int initializeDevice() { + return BackupTransport.TRANSPORT_ERROR; + } + + /** + * Erase the given 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 BackupTransport.TRANSPORT_ERROR; + } + + /** + * Finish sending application data to the backup destination. This must be + * called after {@link #performBackup}, {@link #performFullBackup}, or {@link clearBackupData} + * to ensure that all data is sent and the operation properly finalized. Only when this + * method returns true can a backup be assumed to have succeeded. + * + * @return the same error codes as {@link #performBackup} or {@link #performFullBackup}. + */ + public int finishBackup() { + return BackupTransport.TRANSPORT_ERROR; + } + + // ------------------------------------------------------------------------------------ // Key/value incremental backup support interfaces /** - * Verify that this is a suitable time for a backup pass. This should return zero + * Verify that this is a suitable time for a key/value 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. * @@ -117,19 +163,6 @@ public class BackupTransport { } /** - * 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. @@ -143,37 +176,13 @@ public class BackupTransport { * 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 + * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far), + * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or + * {@link BackupTransport#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; + return BackupTransport.TRANSPORT_ERROR; } // ------------------------------------------------------------------------------------ @@ -210,12 +219,12 @@ public class BackupTransport { * 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} + * @return One of {@link BackupTransport#TRANSPORT_OK} (OK so far, call + * {@link #nextRestorePackage}) or {@link BackupTransport#TRANSPORT_ERROR} * (an error occurred, the restore should be aborted and rescheduled). */ public int startRestore(long token, PackageInfo[] packages) { - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } /** @@ -235,7 +244,7 @@ public class BackupTransport { * @return the same error codes as {@link #startRestore}. */ public int getRestoreData(ParcelFileDescriptor outFd) { - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } /** @@ -247,6 +256,78 @@ public class BackupTransport { "Transport finishRestore() not implemented"); } + // ------------------------------------------------------------------------------------ + // Full backup interfaces + + /** + * Verify that this is a suitable time for a full-data 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 #performFullBackup}/{@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. + * + * @see #requestBackupTime() + */ + public long requestFullBackupTime() { + return 0; + } + + /** + * Begin the process of sending an application's full-data archive to the backend. + * The description of the package whose data will be delivered is provided, as well as + * the socket file descriptor on which the transport will receive the data itself. + * + * <p>If the package is not eligible for backup, the transport should return + * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED}. In this case the system will + * simply proceed with the next candidate if any, or finish the full backup operation + * if all apps have been processed. + * + * <p>After the transport returns {@link BackupTransport#TRANSPORT_OK} from this + * method, the OS will proceed to call {@link #sendBackupData()} one or more times + * to deliver the application's data as a streamed tarball. The transport should not + * read() from the socket except as instructed to via the {@link #sendBackupData(int)} + * method. + * + * <p>After all data has been delivered to the transport, the system will call + * {@link #finishBackup()}. At this point the transport should commit the data to + * its datastore, if appropriate, and close the socket that had been provided in + * {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}. + * + * @param targetPackage The package whose data is to follow. + * @param socket The socket file descriptor through which the data will be provided. + * If the transport returns {@link #TRANSPORT_PACKAGE_REJECTED} here, it must still + * close this file descriptor now; otherwise it should be cached for use during + * succeeding calls to {@link #sendBackupData(int)}, and closed in response to + * {@link #finishBackup()}. + * @return TRANSPORT_PACKAGE_REJECTED to indicate that the stated application is not + * to be backed up; TRANSPORT_OK to indicate that the OS may proceed with delivering + * backup data; TRANSPORT_ERROR to indicate a fatal error condition that precludes + * performing a backup at this time. + */ + public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) { + return BackupTransport.TRANSPORT_PACKAGE_REJECTED; + } + + /** + * Tells the transport to read {@code numBytes} bytes of data from the socket file + * descriptor provided in the {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)} + * call, and deliver those bytes to the datastore. + * + * @param numBytes The number of bytes of tarball data available to be read from the + * socket. + * @return TRANSPORT_OK on successful processing of the data; TRANSPORT_ERROR to + * indicate a fatal error situation. If an error is returned, the system will + * call finishBackup() and stop attempting backups until after a backoff and retry + * interval. + */ + public int sendBackupData(int numBytes) { + return BackupTransport.TRANSPORT_ERROR; + } + /** * 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 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 2ff85c6..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. * @@ -2660,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/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 44a6a5d..70668e1 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -433,6 +433,13 @@ interface IPackageManager { in VerificationParams verificationParams, in ContainerEncryptionParams encryptionParams); + void installPackageWithVerificationEncryptionAndAbiOverrideEtc(in Uri packageURI, + in IPackageInstallObserver observer, in IPackageInstallObserver2 observer2, + int flags, in String installerPackageName, + in VerificationParams verificationParams, + in ContainerEncryptionParams encryptionParams, + in String packageAbiOverride); + int installExistingPackageAsUser(String packageName, int userId); void verifyPendingInstall(int id, int verificationCode); 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/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/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java index a71a74d..723eda1 100644 --- a/core/java/android/hardware/hdmi/HdmiCec.java +++ b/core/java/android/hardware/hdmi/HdmiCec.java @@ -194,6 +194,8 @@ public final class HdmiCec { DEVICE_RECORDER, // ADDR_RECORDER_3 DEVICE_TUNER, // ADDR_TUNER_4 DEVICE_PLAYBACK, // ADDR_PLAYBACK_3 + DEVICE_RESERVED, + DEVICE_RESERVED, DEVICE_TV, // ADDR_SPECIFIC_USE }; @@ -210,6 +212,8 @@ public final class HdmiCec { "Recorder_3", "Tuner_4", "Playback_3", + "Reserved_1", + "Reserved_2", "Secondary_TV", }; diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 06d8e4a..857e335 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -69,6 +69,7 @@ class IInputMethodWrapper extends IInputMethod.Stub private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80; final WeakReference<AbstractInputMethodService> mTarget; + final Context mContext; final HandlerCaller mCaller; final WeakReference<InputMethod> mInputMethod; final int mTargetSdkVersion; @@ -111,8 +112,8 @@ class IInputMethodWrapper extends IInputMethod.Stub public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) { mTarget = new WeakReference<AbstractInputMethodService>(context); - mCaller = new HandlerCaller(context.getApplicationContext(), null, - this, true /*asyncHandler*/); + mContext = context.getApplicationContext(); + mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/); mInputMethod = new WeakReference<InputMethod>(inputMethod); mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; } @@ -186,7 +187,7 @@ class IInputMethodWrapper extends IInputMethod.Stub case DO_CREATE_SESSION: { SomeArgs args = (SomeArgs)msg.obj; inputMethod.createSession(new InputMethodSessionCallbackWrapper( - mCaller.mContext, (InputChannel)args.arg1, + mContext, (InputChannel)args.arg1, (IInputSessionCallback)args.arg2)); args.recycle(); return; diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index 38a65c5..795117e 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -115,6 +115,10 @@ public class SoftInputWindow extends Dialog { getWindow().setAttributes(lp); } + public int getGravity() { + return getWindow().getAttributes().gravity; + } + private void updateWidthHeight(WindowManager.LayoutParams lp) { if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) { lp.width = WindowManager.LayoutParams.MATCH_PARENT; diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 2f2aba3..a48a388 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -40,6 +40,7 @@ import android.util.ArrayMap; import android.util.Log; import com.android.internal.telephony.ITelephony; +import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.Protocol; import java.net.InetAddress; @@ -807,11 +808,34 @@ public class ConnectivityManager { * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api. */ public int startUsingNetworkFeature(int networkType, String feature) { - try { - return mService.startUsingNetworkFeature(networkType, feature, - new Binder()); - } catch (RemoteException e) { - return -1; + NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature); + if (netCap == null) { + Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " + + feature); + return PhoneConstants.APN_REQUEST_FAILED; + } + + NetworkRequest request = null; + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l != null) { + Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest); + renewRequestLocked(l); + if (l.currentNetwork != null) { + return PhoneConstants.APN_ALREADY_ACTIVE; + } else { + return PhoneConstants.APN_REQUEST_STARTED; + } + } + + request = requestNetworkForFeatureLocked(netCap); + } + if (request != null) { + Log.d(TAG, "starting startUsingNeworkFeature for request " + request); + return PhoneConstants.APN_REQUEST_STARTED; + } else { + Log.d(TAG, " request Failed"); + return PhoneConstants.APN_REQUEST_FAILED; } } @@ -831,11 +855,172 @@ public class ConnectivityManager { * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api. */ public int stopUsingNetworkFeature(int networkType, String feature) { - try { - return mService.stopUsingNetworkFeature(networkType, feature); - } catch (RemoteException e) { + NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature); + if (netCap == null) { + Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " + + feature); return -1; } + + NetworkRequest request = removeRequestForFeature(netCap); + if (request != null) { + Log.d(TAG, "stopUsingNetworkFeature for " + networkType + ", " + feature); + releaseNetworkRequest(request); + } + return 1; + } + + private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) { + if (networkType == TYPE_MOBILE) { + int cap = -1; + if ("enableMMS".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_MMS; + } else if ("enableSUPL".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_SUPL; + } else if ("enableDUN".equals(feature) || "enableDUNAlways".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_DUN; + } else if ("enableHIPRI".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_INTERNET; + } else if ("enableFOTA".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_FOTA; + } else if ("enableIMS".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_IMS; + } else if ("enableCBS".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_CBS; + } else { + return null; + } + NetworkCapabilities netCap = new NetworkCapabilities(); + netCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + netCap.addNetworkCapability(cap); + return netCap; + } else if (networkType == TYPE_WIFI) { + if ("p2p".equals(feature)) { + NetworkCapabilities netCap = new NetworkCapabilities(); + netCap.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + netCap.addNetworkCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P); + return netCap; + } + } + return null; + } + + private int legacyTypeForNetworkCapabilities(NetworkCapabilities netCap) { + if (netCap == null) return TYPE_NONE; + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) { + return TYPE_MOBILE_CBS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) { + return TYPE_MOBILE_IMS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) { + return TYPE_MOBILE_FOTA; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) { + return TYPE_MOBILE_DUN; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) { + return TYPE_MOBILE_SUPL; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) { + return TYPE_MOBILE_MMS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + return TYPE_MOBILE_HIPRI; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)) { + return TYPE_WIFI_P2P; + } + return TYPE_NONE; + } + + private static class LegacyRequest { + NetworkCapabilities networkCapabilities; + NetworkRequest networkRequest; + int expireSequenceNumber; + Network currentNetwork; + int delay = -1; + NetworkCallbackListener networkCallbackListener = new NetworkCallbackListener() { + @Override + public void onAvailable(NetworkRequest request, Network network) { + currentNetwork = network; + Log.d(TAG, "startUsingNetworkFeature got Network:" + network); + network.bindProcessForHostResolution(); + } + @Override + public void onLost(NetworkRequest request, Network network) { + if (network.equals(currentNetwork)) { + currentNetwork = null; + network.unbindProcessForHostResolution(); + } + Log.d(TAG, "startUsingNetworkFeature lost Network:" + network); + } + }; + } + + private HashMap<NetworkCapabilities, LegacyRequest> sLegacyRequests = + new HashMap<NetworkCapabilities, LegacyRequest>(); + + private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) { + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l != null) return l.networkRequest; + } + return null; + } + + private void renewRequestLocked(LegacyRequest l) { + l.expireSequenceNumber++; + Log.d(TAG, "renewing request to seqNum " + l.expireSequenceNumber); + sendExpireMsgForFeature(l.networkCapabilities, l.expireSequenceNumber, l.delay); + } + + private void expireRequest(NetworkCapabilities netCap, int sequenceNum) { + int ourSeqNum = -1; + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l == null) return; + ourSeqNum = l.expireSequenceNumber; + if (l.expireSequenceNumber == sequenceNum) { + releaseNetworkRequest(l.networkRequest); + sLegacyRequests.remove(netCap); + } + } + Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum); + } + + private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) { + int delay = -1; + int type = legacyTypeForNetworkCapabilities(netCap); + try { + delay = mService.getRestoreDefaultNetworkDelay(type); + } catch (RemoteException e) {} + LegacyRequest l = new LegacyRequest(); + l.networkCapabilities = netCap; + l.delay = delay; + l.expireSequenceNumber = 0; + l.networkRequest = sendRequestForNetwork(netCap, l.networkCallbackListener, 0, + REQUEST, type); + if (l.networkRequest == null) return null; + sLegacyRequests.put(netCap, l); + sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay); + return l.networkRequest; + } + + private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) { + if (delay >= 0) { + Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay); + Message msg = sCallbackHandler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap); + sCallbackHandler.sendMessageDelayed(msg, delay); + } + } + + private NetworkRequest removeRequestForFeature(NetworkCapabilities netCap) { + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.remove(netCap); + if (l == null) return null; + return l.networkRequest; + } } /** @@ -1782,8 +1967,10 @@ public class ConnectivityManager { public static final int CALLBACK_RELEASED = BASE + 8; /** @hide */ public static final int CALLBACK_EXIT = BASE + 9; + /** @hide obj = NetworkCapabilities, arg1 = seq number */ + private static final int EXPIRE_LEGACY_REQUEST = BASE + 10; - private static class CallbackHandler extends Handler { + private class CallbackHandler extends Handler { private final HashMap<NetworkRequest, NetworkCallbackListener>mCallbackMap; private final AtomicInteger mRefCount; private static final String TAG = "ConnectivityManager.CallbackHandler"; @@ -1903,6 +2090,10 @@ public class ConnectivityManager { getLooper().quit(); break; } + case EXPIRE_LEGACY_REQUEST: { + expireRequest((NetworkCapabilities)message.obj, message.arg1); + break; + } } } @@ -1954,8 +2145,9 @@ public class ConnectivityManager { private final static int LISTEN = 1; private final static int REQUEST = 2; - private NetworkRequest somethingForNetwork(NetworkCapabilities need, - NetworkCallbackListener networkCallbackListener, int timeoutSec, int action) { + private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, + NetworkCallbackListener networkCallbackListener, int timeoutSec, int action, + int legacyType) { NetworkRequest networkRequest = null; if (networkCallbackListener == null) { throw new IllegalArgumentException("null NetworkCallbackListener"); @@ -1968,7 +2160,7 @@ public class ConnectivityManager { new Binder()); } else { networkRequest = mService.requestNetwork(need, new Messenger(sCallbackHandler), - timeoutSec, new Binder()); + timeoutSec, new Binder(), legacyType); } if (networkRequest != null) { synchronized(sNetworkCallbackListener) { @@ -1998,7 +2190,7 @@ public class ConnectivityManager { */ public NetworkRequest requestNetwork(NetworkCapabilities need, NetworkCallbackListener networkCallbackListener) { - return somethingForNetwork(need, networkCallbackListener, 0, REQUEST); + return sendRequestForNetwork(need, networkCallbackListener, 0, REQUEST, TYPE_NONE); } /** @@ -2021,7 +2213,8 @@ public class ConnectivityManager { */ public NetworkRequest requestNetwork(NetworkCapabilities need, NetworkCallbackListener networkCallbackListener, int timeoutSec) { - return somethingForNetwork(need, networkCallbackListener, timeoutSec, REQUEST); + return sendRequestForNetwork(need, networkCallbackListener, timeoutSec, REQUEST, + TYPE_NONE); } /** @@ -2099,7 +2292,7 @@ public class ConnectivityManager { */ public NetworkRequest listenForNetwork(NetworkCapabilities need, NetworkCallbackListener networkCallbackListener) { - return somethingForNetwork(need, networkCallbackListener, 0, LISTEN); + return sendRequestForNetwork(need, networkCallbackListener, 0, LISTEN, TYPE_NONE); } /** diff --git a/core/java/android/net/ConnectivityServiceProtocol.java b/core/java/android/net/ConnectivityServiceProtocol.java deleted file mode 100644 index 74096b4..0000000 --- a/core/java/android/net/ConnectivityServiceProtocol.java +++ /dev/null @@ -1,70 +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.net; - -import static com.android.internal.util.Protocol.BASE_CONNECTIVITY_SERVICE; - -/** - * Describes the Internal protocols used to communicate with ConnectivityService. - * @hide - */ -public class ConnectivityServiceProtocol { - - private static final int BASE = BASE_CONNECTIVITY_SERVICE; - - private ConnectivityServiceProtocol() {} - - /** - * This is a contract between ConnectivityService and various bearers. - * A NetworkFactory is an abstract entity that creates NetworkAgent objects. - * The bearers register with ConnectivityService using - * ConnectivityManager.registerNetworkFactory, where they pass in a Messenger - * to be used to deliver the following Messages. - */ - public static class NetworkFactoryProtocol { - private NetworkFactoryProtocol() {} - /** - * Pass a network request to the bearer. If the bearer believes it can - * satisfy the request it should connect to the network and create a - * NetworkAgent. Once the NetworkAgent is fully functional it will - * register itself with ConnectivityService using registerNetworkAgent. - * If the bearer cannot immediately satisfy the request (no network, - * user disabled the radio, lower-scored network) it should remember - * any NetworkRequests it may be able to satisfy in the future. It may - * disregard any that it will never be able to service, for example - * those requiring a different bearer. - * msg.obj = NetworkRequest - * msg.arg1 = score - the score of the any network currently satisfying this - * request. If this bearer knows in advance it cannot - * exceed this score it should not try to connect, holding the request - * for the future. - * Note that subsequent events may give a different (lower - * or higher) score for this request, transmitted to each - * NetworkFactory through additional CMD_REQUEST_NETWORK msgs - * with the same NetworkRequest but an updated score. - * Also, network conditions may change for this bearer - * allowing for a better score in the future. - */ - public static final int CMD_REQUEST_NETWORK = BASE; - - /** - * Cancel a network request - * msg.obj = NetworkRequest - */ - public static final int CMD_CANCEL_REQUEST = BASE + 1; - } -} diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index baec36a..5f1ff3e 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -158,7 +158,7 @@ interface IConnectivityManager in NetworkCapabilities nc, int score); NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, - in Messenger messenger, int timeoutSec, in IBinder binder); + in Messenger messenger, int timeoutSec, in IBinder binder, int legacy); NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities, in PendingIntent operation); @@ -170,4 +170,6 @@ interface IConnectivityManager in PendingIntent operation); void releaseNetworkRequest(in NetworkRequest networkRequest); + + int getRestoreDefaultNetworkDelay(int networkType); } diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 1c18ba5..7e8b1f1 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -24,85 +24,39 @@ import android.os.Messenger; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; -import android.util.SparseArray; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; +import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; /** - * A Utility class for handling NetworkRequests. - * - * Created by bearer-specific code to handle tracking requests, scores, - * network data and handle communicating with ConnectivityService. Two - * abstract methods: connect and disconnect are used to act on the - * underlying bearer code. Connect is called when we have a NetworkRequest - * and our score is better than the current handling network's score, while - * disconnect is used when ConnectivityService requests a disconnect. + * A Utility class for handling for communicating between bearer-specific + * code and ConnectivityService. * * A bearer may have more than one NetworkAgent if it can simultaneously * support separate networks (IMS / Internet / MMS Apns on cellular, or - * perhaps connections with different SSID or P2P for Wi-Fi). The bearer - * code should pass its NetworkAgents the NetworkRequests each NetworkAgent - * can handle, demultiplexing for different network types. The bearer code - * can also filter out requests it can never handle. + * perhaps connections with different SSID or P2P for Wi-Fi). * - * Each NetworkAgent needs to be given a score and NetworkCapabilities for - * their potential network. While disconnected, the NetworkAgent will check - * each time its score changes or a NetworkRequest changes to see if - * the NetworkAgent can provide a higher scored network for a NetworkRequest - * that the NetworkAgent's NetworkCapabilties can satisfy. This condition will - * trigger a connect request via connect(). After connection, connection data - * should be given to the NetworkAgent by the bearer, including LinkProperties - * NetworkCapabilties and NetworkInfo. After that the NetworkAgent will register - * with ConnectivityService and forward the data on. * @hide */ public abstract class NetworkAgent extends Handler { - private final SparseArray<NetworkRequestAndScore> mNetworkRequests = new SparseArray<>(); - private boolean mConnectionRequested = false; - - private AsyncChannel mAsyncChannel; + private volatile AsyncChannel mAsyncChannel; private final String LOG_TAG; private static final boolean DBG = true; private static final boolean VDBG = true; - // TODO - this class shouldn't cache data or it runs the risk of getting out of sync - // Make the API require each of these when any is updated so we have the data we need, - // without caching. - private LinkProperties mLinkProperties; - private NetworkInfo mNetworkInfo; - private NetworkCapabilities mNetworkCapabilities; - private int mNetworkScore; - private boolean mRegistered = false; private final Context mContext; - private AtomicBoolean mHasRequests = new AtomicBoolean(false); - - // TODO - add a name member for logging purposes. - - protected final Object mLockObj = new Object(); - + private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>(); private static final int BASE = Protocol.BASE_NETWORK_AGENT; /** - * Sent by self to queue up a new/modified request. - * obj = NetworkRequestAndScore - */ - private static final int CMD_ADD_REQUEST = BASE + 1; - - /** - * Sent by self to queue up the removal of a request. - * obj = NetworkRequest - */ - private static final int CMD_REMOVE_REQUEST = BASE + 2; - - /** * Sent by ConnectivityService to the NetworkAgent to inform it of * suspected connectivity problems on its network. The NetworkAgent * should take steps to verify and correct connectivity. */ - public static final int CMD_SUSPECT_BAD = BASE + 3; + public static final int CMD_SUSPECT_BAD = BASE; /** * Sent by the NetworkAgent (note the EVENT vs CMD prefix) to @@ -110,84 +64,63 @@ public abstract class NetworkAgent extends Handler { * Sent when the NetworkInfo changes, mainly due to change of state. * obj = NetworkInfo */ - public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 4; + public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1; /** * Sent by the NetworkAgent to ConnectivityService to pass the current * NetworkCapabilties. * obj = NetworkCapabilities */ - public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 5; + public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2; /** * Sent by the NetworkAgent to ConnectivityService to pass the current * NetworkProperties. * obj = NetworkProperties */ - public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 6; + public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3; /** * Sent by the NetworkAgent to ConnectivityService to pass the current * network score. - * arg1 = network score int + * obj = network score Integer */ - public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 7; + public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4; - public NetworkAgent(Looper looper, Context context, String logTag) { + public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, + NetworkCapabilities nc, LinkProperties lp, int score) { super(looper); LOG_TAG = logTag; mContext = context; - } - - /** - * When conditions are right, register with ConnectivityService. - * Connditions include having a well defined network and a request - * that justifies it. The NetworkAgent will remain registered until - * disconnected. - * TODO - this should have all data passed in rather than caching - */ - private void registerSelf() { - synchronized(mLockObj) { - if (!mRegistered && mConnectionRequested && - mNetworkInfo != null && mNetworkInfo.isConnected() && - mNetworkCapabilities != null && - mLinkProperties != null && - mNetworkScore != 0) { - if (DBG) log("Registering NetworkAgent"); - mRegistered = true; - ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(mNetworkInfo), - new LinkProperties(mLinkProperties), - new NetworkCapabilities(mNetworkCapabilities), mNetworkScore); - } else if (DBG && !mRegistered) { - String err = "Not registering due to "; - if (mConnectionRequested == false) err += "no Connect requested "; - if (mNetworkInfo == null) err += "null NetworkInfo "; - if (mNetworkInfo != null && mNetworkInfo.isConnected() == false) { - err += "NetworkInfo disconnected "; - } - if (mLinkProperties == null) err += "null LinkProperties "; - if (mNetworkCapabilities == null) err += "null NetworkCapabilities "; - if (mNetworkScore == 0) err += "null NetworkScore"; - log(err); - } + if (ni == null || nc == null || lp == null) { + throw new IllegalArgumentException(); } + + if (DBG) log("Registering NetworkAgent"); + ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni), + new LinkProperties(lp), new NetworkCapabilities(nc), score); } @Override public void handleMessage(Message msg) { switch (msg.what) { case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { - synchronized (mLockObj) { - if (mAsyncChannel != null) { - log("Received new connection while already connected!"); - } else { - if (DBG) log("NetworkAgent fully connected"); - mAsyncChannel = new AsyncChannel(); - mAsyncChannel.connected(null, this, msg.replyTo); - mAsyncChannel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, - AsyncChannel.STATUS_SUCCESSFUL); + if (mAsyncChannel != null) { + log("Received new connection while already connected!"); + } else { + if (DBG) log("NetworkAgent fully connected"); + AsyncChannel ac = new AsyncChannel(); + ac.connected(null, this, msg.replyTo); + ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, + AsyncChannel.STATUS_SUCCESSFUL); + synchronized (mPreConnectedQueue) { + mAsyncChannel = ac; + for (Message m : mPreConnectedQueue) { + ac.sendMessage(m); + } + mPreConnectedQueue.clear(); } } break; @@ -199,213 +132,69 @@ public abstract class NetworkAgent extends Handler { } case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { if (DBG) log("NetworkAgent channel lost"); - disconnect(); - clear(); + // let the client know CS is done with us. + unwanted(); + synchronized (mPreConnectedQueue) { + mAsyncChannel = null; + } break; } case CMD_SUSPECT_BAD: { log("Unhandled Message " + msg); break; } - case CMD_ADD_REQUEST: { - handleAddRequest(msg); - break; - } - case CMD_REMOVE_REQUEST: { - handleRemoveRequest(msg); - break; - } - } - } - - private void clear() { - synchronized(mLockObj) { - mNetworkRequests.clear(); - mHasRequests.set(false); - mConnectionRequested = false; - mAsyncChannel = null; - mRegistered = false; - } - } - - private static class NetworkRequestAndScore { - NetworkRequest req; - int score; - - NetworkRequestAndScore(NetworkRequest networkRequest, int score) { - req = networkRequest; - this.score = score; } } - private void handleAddRequest(Message msg) { - NetworkRequestAndScore n = (NetworkRequestAndScore)msg.obj; - // replaces old request, updating score - mNetworkRequests.put(n.req.requestId, n); - mHasRequests.set(true); - evalScores(); - } - - private void handleRemoveRequest(Message msg) { - NetworkRequest networkRequest = (NetworkRequest)msg.obj; - - if (mNetworkRequests.get(networkRequest.requestId) != null) { - mNetworkRequests.remove(networkRequest.requestId); - if (mNetworkRequests.size() == 0) mHasRequests.set(false); - evalScores(); - } - } - - /** - * Called to go through our list of requests and see if we're - * good enough to try connecting, or if we have gotten worse and - * need to disconnect. - * - * Once we are registered, does nothing: we disconnect when requested via - * CMD_CHANNEL_DISCONNECTED, generated by either a loss of connection - * between modules (bearer or ConnectivityService dies) or more commonly - * when the NetworkInfo reports to ConnectivityService it is disconnected. - */ - private void evalScores() { - synchronized(mLockObj) { - if (mRegistered) { - if (VDBG) log("evalScores - already connected - size=" + mNetworkRequests.size()); - // already trying - return; - } - if (VDBG) log("evalScores!"); - for (int i=0; i < mNetworkRequests.size(); i++) { - int score = mNetworkRequests.valueAt(i).score; - if (VDBG) log(" checking request Min " + score + " vs my score " + mNetworkScore); - if (score < mNetworkScore) { - // have a request that has a lower scored network servicing it - // (or no network) than we could provide, so let's connect! - mConnectionRequested = true; - connect(); - return; - } - } - // Our score is not high enough to satisfy any current request. - // This can happen if our score goes down after a connection is - // requested but before we actually connect. In this case, disconnect - // rather than continue trying - there's no point connecting if we know - // we'll just be torn down as soon as we do. - if (mConnectionRequested) { - mConnectionRequested = false; - disconnect(); + private void queueOrSendMessage(int what, Object obj) { + synchronized (mPreConnectedQueue) { + if (mAsyncChannel != null) { + mAsyncChannel.sendMessage(what, obj); + } else { + Message msg = Message.obtain(); + msg.what = what; + msg.obj = obj; + mPreConnectedQueue.add(msg); } } } - public void addNetworkRequest(NetworkRequest networkRequest, int score) { - if (DBG) log("adding NetworkRequest " + networkRequest + " with score " + score); - sendMessage(obtainMessage(CMD_ADD_REQUEST, - new NetworkRequestAndScore(networkRequest, score))); - } - - public void removeNetworkRequest(NetworkRequest networkRequest) { - if (DBG) log("removing NetworkRequest " + networkRequest); - sendMessage(obtainMessage(CMD_REMOVE_REQUEST, networkRequest)); - } - /** * Called by the bearer code when it has new LinkProperties data. - * If we're a registered NetworkAgent, this new data will get forwarded on, - * otherwise we store a copy in anticipation of registering. This call - * may also prompt registration if it causes the NetworkAgent to meet - * the conditions (fully configured, connected, satisfys a request and - * has sufficient score). */ public void sendLinkProperties(LinkProperties linkProperties) { - linkProperties = new LinkProperties(linkProperties); - synchronized(mLockObj) { - mLinkProperties = linkProperties; - if (mAsyncChannel != null) { - mAsyncChannel.sendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, linkProperties); - } else { - registerSelf(); - } - } + queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties)); } /** * Called by the bearer code when it has new NetworkInfo data. - * If we're a registered NetworkAgent, this new data will get forwarded on, - * otherwise we store a copy in anticipation of registering. This call - * may also prompt registration if it causes the NetworkAgent to meet - * the conditions (fully configured, connected, satisfys a request and - * has sufficient score). */ public void sendNetworkInfo(NetworkInfo networkInfo) { - networkInfo = new NetworkInfo(networkInfo); - synchronized(mLockObj) { - mNetworkInfo = networkInfo; - if (mAsyncChannel != null) { - mAsyncChannel.sendMessage(EVENT_NETWORK_INFO_CHANGED, networkInfo); - } else { - registerSelf(); - } - } + queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo)); } /** * Called by the bearer code when it has new NetworkCapabilities data. - * If we're a registered NetworkAgent, this new data will get forwarded on, - * otherwise we store a copy in anticipation of registering. This call - * may also prompt registration if it causes the NetworkAgent to meet - * the conditions (fully configured, connected, satisfys a request and - * has sufficient score). - * Note that if these capabilities make the network non-useful, - * ConnectivityServce will tear this network down. */ public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) { - networkCapabilities = new NetworkCapabilities(networkCapabilities); - synchronized(mLockObj) { - mNetworkCapabilities = networkCapabilities; - if (mAsyncChannel != null) { - mAsyncChannel.sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, networkCapabilities); - } else { - registerSelf(); - } - } - } - - public NetworkCapabilities getNetworkCapabilities() { - synchronized(mLockObj) { - return new NetworkCapabilities(mNetworkCapabilities); - } + queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, + new NetworkCapabilities(networkCapabilities)); } /** * Called by the bearer code when it has a new score for this network. - * If we're a registered NetworkAgent, this new data will get forwarded on, - * otherwise we store a copy. */ - public synchronized void sendNetworkScore(int score) { - synchronized(mLockObj) { - mNetworkScore = score; - evalScores(); - if (mAsyncChannel != null) { - mAsyncChannel.sendMessage(EVENT_NETWORK_SCORE_CHANGED, mNetworkScore); - } else { - registerSelf(); - } - } - } - - public boolean hasRequests() { - return mHasRequests.get(); + public void sendNetworkScore(int score) { + queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new Integer(score)); } - public boolean isConnectionRequested() { - synchronized(mLockObj) { - return mConnectionRequested; - } - } - - - abstract protected void connect(); - abstract protected void disconnect(); + /** + * Called when ConnectivityService has indicated they no longer want this network. + * The parent factory should (previously) have received indication of the change + * as well, either canceling NetworkRequests or altering their score such that this + * network won't be immediately requested again. + */ + abstract protected void unwanted(); protected void log(String s) { Log.d(LOG_TAG, "NetworkAgent: " + s); diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java new file mode 100644 index 0000000..a20e8e7 --- /dev/null +++ b/core/java/android/net/NetworkFactory.java @@ -0,0 +1,275 @@ +/* + * 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.net; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.util.AsyncChannel; +import com.android.internal.util.Protocol; + +/** + * A NetworkFactory is an entity that creates NetworkAgent objects. + * The bearers register with ConnectivityService using {@link #register} and + * their factory will start receiving scored NetworkRequests. NetworkRequests + * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by + * overridden function. All of these can be dynamic - changing NetworkCapabilities + * or score forces re-evaluation of all current requests. + * + * If any requests pass the filter some overrideable functions will be called. + * If the bearer only cares about very simple start/stopNetwork callbacks, those + * functions can be overridden. If the bearer needs more interaction, it can + * override addNetworkRequest and removeNetworkRequest which will give it each + * request that passes their current filters. + * @hide + **/ +public class NetworkFactory extends Handler { + private static final boolean DBG = true; + + private static final int BASE = Protocol.BASE_NETWORK_FACTORY; + /** + * Pass a network request to the bearer. If the bearer believes it can + * satisfy the request it should connect to the network and create a + * NetworkAgent. Once the NetworkAgent is fully functional it will + * register itself with ConnectivityService using registerNetworkAgent. + * If the bearer cannot immediately satisfy the request (no network, + * user disabled the radio, lower-scored network) it should remember + * any NetworkRequests it may be able to satisfy in the future. It may + * disregard any that it will never be able to service, for example + * those requiring a different bearer. + * msg.obj = NetworkRequest + * msg.arg1 = score - the score of the any network currently satisfying this + * request. If this bearer knows in advance it cannot + * exceed this score it should not try to connect, holding the request + * for the future. + * Note that subsequent events may give a different (lower + * or higher) score for this request, transmitted to each + * NetworkFactory through additional CMD_REQUEST_NETWORK msgs + * with the same NetworkRequest but an updated score. + * Also, network conditions may change for this bearer + * allowing for a better score in the future. + */ + public static final int CMD_REQUEST_NETWORK = BASE; + + /** + * Cancel a network request + * msg.obj = NetworkRequest + */ + public static final int CMD_CANCEL_REQUEST = BASE + 1; + + /** + * Internally used to set our best-guess score. + * msg.arg1 = new score + */ + private static final int CMD_SET_SCORE = BASE + 2; + + /** + * Internally used to set our current filter for coarse bandwidth changes with + * technology changes. + * msg.obj = new filter + */ + private static final int CMD_SET_FILTER = BASE + 3; + + private final Context mContext; + private final String LOG_TAG; + + private final SparseArray<NetworkRequestInfo> mNetworkRequests = + new SparseArray<NetworkRequestInfo>(); + + private int mScore; + private NetworkCapabilities mCapabilityFilter; + + private int mRefCount = 0; + private Messenger mMessenger = null; + + public NetworkFactory(Looper looper, Context context, String logTag, + NetworkCapabilities filter) { + super(looper); + LOG_TAG = logTag; + mContext = context; + mCapabilityFilter = filter; + } + + public void register() { + if (DBG) log("Registering NetworkFactory"); + if (mMessenger == null) { + mMessenger = new Messenger(this); + ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, LOG_TAG); + } + } + + public void unregister() { + if (DBG) log("Unregistering NetworkFactory"); + if (mMessenger != null) { + ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger); + mMessenger = null; + } + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case CMD_REQUEST_NETWORK: { + handleAddRequest((NetworkRequest)msg.obj, msg.arg1); + break; + } + case CMD_CANCEL_REQUEST: { + handleRemoveRequest((NetworkRequest) msg.obj); + break; + } + case CMD_SET_SCORE: { + handleSetScore(msg.arg1); + break; + } + case CMD_SET_FILTER: { + handleSetFilter((NetworkCapabilities) msg.obj); + break; + } + } + } + + private class NetworkRequestInfo { + public final NetworkRequest request; + public int score; + public boolean requested; // do we have a request outstanding, limited by score + + public NetworkRequestInfo(NetworkRequest request, int score) { + this.request = request; + this.score = score; + this.requested = false; + } + } + + private void handleAddRequest(NetworkRequest request, int score) { + NetworkRequestInfo n = mNetworkRequests.get(request.requestId); + if (n == null) { + n = new NetworkRequestInfo(request, score); + mNetworkRequests.put(n.request.requestId, n); + } else { + n.score = score; + } + if (DBG) log("got request " + request + " with score " + score); + if (DBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter); + + evalRequest(n); + } + + private void handleRemoveRequest(NetworkRequest request) { + NetworkRequestInfo n = mNetworkRequests.get(request.requestId); + if (n != null && n.requested) { + mNetworkRequests.remove(request.requestId); + releaseNetworkFor(n.request); + } + } + + private void handleSetScore(int score) { + mScore = score; + evalRequests(); + } + + private void handleSetFilter(NetworkCapabilities netCap) { + mCapabilityFilter = netCap; + evalRequests(); + } + + /** + * Overridable function to provide complex filtering. + * Called for every request every time a new NetworkRequest is seen + * and whenever the filterScore or filterNetworkCapabilities change. + * + * acceptRequest can be overriden to provide complex filter behavior + * for the incoming requests + * + * For output, this class will call {@link #needNetworkFor} and + * {@link #releaseNetworkFor} for every request that passes the filters. + * If you don't need to see every request, you can leave the base + * implementations of those two functions and instead override + * {@link #startNetwork} and {@link #stopNetwork}. + * + * If you want to see every score fluctuation on every request, set + * your score filter to a very high number and watch {@link #needNetworkFor}. + * + * @return {@code true} to accept the request. + */ + public boolean acceptRequest(NetworkRequest request, int score) { + return true; + } + + private void evalRequest(NetworkRequestInfo n) { + if (n.requested == false && n.score < mScore && + n.request.networkCapabilities.satisfiedByNetworkCapabilities( + mCapabilityFilter) && acceptRequest(n.request, n.score)) { + needNetworkFor(n.request, n.score); + n.requested = true; + } else if (n.requested == true && + (n.score > mScore || n.request.networkCapabilities.satisfiedByNetworkCapabilities( + mCapabilityFilter) == false || acceptRequest(n.request, n.score) == false)) { + releaseNetworkFor(n.request); + n.requested = false; + } + } + + private void evalRequests() { + for (int i = 0; i < mNetworkRequests.size(); i++) { + NetworkRequestInfo n = mNetworkRequests.valueAt(i); + + evalRequest(n); + } + } + + // override to do simple mode (request independent) + protected void startNetwork() { } + protected void stopNetwork() { } + + // override to do fancier stuff + protected void needNetworkFor(NetworkRequest networkRequest, int score) { + if (++mRefCount == 1) startNetwork(); + } + + protected void releaseNetworkFor(NetworkRequest networkRequest) { + if (--mRefCount == 0) stopNetwork(); + } + + + public void addNetworkRequest(NetworkRequest networkRequest, int score) { + sendMessage(obtainMessage(CMD_REQUEST_NETWORK, + new NetworkRequestInfo(networkRequest, score))); + } + + public void removeNetworkRequest(NetworkRequest networkRequest) { + sendMessage(obtainMessage(CMD_CANCEL_REQUEST, networkRequest)); + } + + public void setScoreFilter(int score) { + sendMessage(obtainMessage(CMD_SET_SCORE, score, 0)); + } + + public void setCapabilityFilter(NetworkCapabilities netCap) { + sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap))); + } + + protected void log(String s) { + Log.d(LOG_TAG, s); + } +} diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index 9e656ee..d279412 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -188,6 +188,15 @@ public class NetworkInfo implements Parcelable { } /** + * @hide + */ + public void setType(int type) { + synchronized (this) { + mNetworkType = type; + } + } + + /** * Return a network-type-specific integer describing the subtype * of the network. * @return the network subtype @@ -198,7 +207,10 @@ public class NetworkInfo implements Parcelable { } } - void setSubtype(int subtype, String subtypeName) { + /** + * @hide + */ + public void setSubtype(int subtype, String subtypeName) { synchronized (this) { mSubtype = subtype; mSubtypeName = subtypeName; diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 480cb05..47377e9 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -47,19 +47,19 @@ public class NetworkRequest implements Parcelable { public final int requestId; /** - * Set for legacy requests and the default. + * Set for legacy requests and the default. Set to TYPE_NONE for none. * Causes CONNECTIVITY_ACTION broadcasts to be sent. * @hide */ - public final boolean needsBroadcasts; + public final int legacyType; /** * @hide */ - public NetworkRequest(NetworkCapabilities nc, boolean needsBroadcasts, int rId) { + public NetworkRequest(NetworkCapabilities nc, int legacyType, int rId) { requestId = rId; networkCapabilities = nc; - this.needsBroadcasts = needsBroadcasts; + this.legacyType = legacyType; } /** @@ -68,7 +68,7 @@ public class NetworkRequest implements Parcelable { public NetworkRequest(NetworkRequest that) { networkCapabilities = new NetworkCapabilities(that.networkCapabilities); requestId = that.requestId; - needsBroadcasts = that.needsBroadcasts; + this.legacyType = that.legacyType; } // implement the Parcelable interface @@ -77,16 +77,16 @@ public class NetworkRequest implements Parcelable { } public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(networkCapabilities, flags); - dest.writeInt(needsBroadcasts ? 1 : 0); + dest.writeInt(legacyType); dest.writeInt(requestId); } public static final Creator<NetworkRequest> CREATOR = new Creator<NetworkRequest>() { public NetworkRequest createFromParcel(Parcel in) { NetworkCapabilities nc = (NetworkCapabilities)in.readParcelable(null); - boolean needsBroadcasts = (in.readInt() == 1); + int legacyType = in.readInt(); int requestId = in.readInt(); - NetworkRequest result = new NetworkRequest(nc, needsBroadcasts, requestId); + NetworkRequest result = new NetworkRequest(nc, legacyType, requestId); return result; } public NetworkRequest[] newArray(int size) { @@ -95,14 +95,14 @@ public class NetworkRequest implements Parcelable { }; public String toString() { - return "NetworkRequest [ id=" + requestId + ", needsBroadcasts=" + needsBroadcasts + + return "NetworkRequest [ id=" + requestId + ", legacyType=" + legacyType + ", " + networkCapabilities.toString() + " ]"; } public boolean equals(Object obj) { if (obj instanceof NetworkRequest == false) return false; NetworkRequest that = (NetworkRequest)obj; - return (that.needsBroadcasts == this.needsBroadcasts && + return (that.legacyType == this.legacyType && that.requestId == this.requestId && ((that.networkCapabilities == null && this.networkCapabilities == null) || (that.networkCapabilities != null && @@ -110,7 +110,7 @@ public class NetworkRequest implements Parcelable { } public int hashCode() { - return requestId + (needsBroadcasts ? 1013 : 2026) + + return requestId + (legacyType * 1013) + (networkCapabilities.hashCode() * 1051); } } 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/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/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/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 424d860..5056097 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -75,22 +75,10 @@ class GLES20Canvas extends HardwareCanvas { // Constructors /////////////////////////////////////////////////////////////////////////// - /** - * Creates a canvas to render directly on screen. - */ - GLES20Canvas(boolean translucent) { - this(false, translucent); - } - - protected GLES20Canvas(boolean record, boolean translucent) { - mOpaque = !translucent; - - if (record) { - mRenderer = nCreateDisplayListRenderer(); - } else { - mRenderer = nCreateRenderer(); - } - + // TODO: Merge with GLES20RecordingCanvas + protected GLES20Canvas() { + mOpaque = false; + mRenderer = nCreateDisplayListRenderer(); setupFinalizer(); } @@ -102,7 +90,6 @@ class GLES20Canvas extends HardwareCanvas { } } - private static native long nCreateRenderer(); private static native long nCreateDisplayListRenderer(); private static native void nResetDisplayListRenderer(long renderer); private static native void nDestroyRenderer(long renderer); @@ -131,36 +118,6 @@ class GLES20Canvas extends HardwareCanvas { private static native void nSetProperty(String name, String value); /////////////////////////////////////////////////////////////////////////// - // Hardware layers - /////////////////////////////////////////////////////////////////////////// - - @Override - void pushLayerUpdate(HardwareLayer layer) { - nPushLayerUpdate(mRenderer, layer.getLayer()); - } - - @Override - void cancelLayerUpdate(HardwareLayer layer) { - nCancelLayerUpdate(mRenderer, layer.getLayer()); - } - - @Override - void flushLayerUpdates() { - nFlushLayerUpdates(mRenderer); - } - - @Override - void clearLayerUpdates() { - nClearLayerUpdates(mRenderer); - } - - static native boolean nCopyLayer(long layerId, long bitmap); - private static native void nClearLayerUpdates(long renderer); - private static native void nFlushLayerUpdates(long renderer); - private static native void nPushLayerUpdate(long renderer, long layer); - private static native void nCancelLayerUpdate(long renderer, long layer); - - /////////////////////////////////////////////////////////////////////////// // Canvas management /////////////////////////////////////////////////////////////////////////// @@ -234,20 +191,6 @@ class GLES20Canvas extends HardwareCanvas { private static native void nFinish(long renderer); - /** - * Returns the size of the stencil buffer required by the underlying - * implementation. - * - * @return The minimum number of bits the stencil buffer must. Always >= 0. - * - * @hide - */ - public static int getStencilSize() { - return nGetStencilSize(); - } - - private static native int nGetStencilSize(); - /////////////////////////////////////////////////////////////////////////// // Functor /////////////////////////////////////////////////////////////////////////// @@ -284,49 +227,6 @@ class GLES20Canvas extends HardwareCanvas { */ static final int FLUSH_CACHES_FULL = 2; - /** - * Flush caches to reclaim as much memory as possible. The amount of memory - * to reclaim is indicate by the level parameter. - * - * The level can be one of {@link #FLUSH_CACHES_MODERATE} or - * {@link #FLUSH_CACHES_FULL}. - * - * @param level Hint about the amount of memory to reclaim - */ - static void flushCaches(int level) { - nFlushCaches(level); - } - - private static native void nFlushCaches(int level); - - /** - * Release all resources associated with the underlying caches. This should - * only be called after a full flushCaches(). - * - * @hide - */ - static void terminateCaches() { - nTerminateCaches(); - } - - private static native void nTerminateCaches(); - - static boolean initCaches() { - return nInitCaches(); - } - - private static native boolean nInitCaches(); - - /////////////////////////////////////////////////////////////////////////// - // Atlas - /////////////////////////////////////////////////////////////////////////// - - static void initAtlas(GraphicBuffer buffer, long[] map) { - nInitAtlas(buffer, map, map.length); - } - - private static native void nInitAtlas(GraphicBuffer buffer, long[] map, int count); - /////////////////////////////////////////////////////////////////////////// // Display list /////////////////////////////////////////////////////////////////////////// @@ -899,12 +799,6 @@ class GLES20Canvas extends HardwareCanvas { private static native void nDrawPath(long renderer, long path, long paint); private static native void nDrawRects(long renderer, long region, long paint); - void drawRects(float[] rects, int count, Paint paint) { - nDrawRects(mRenderer, rects, count, paint.mNativePaint); - } - - private static native void nDrawRects(long renderer, float[] rects, int count, long paint); - @Override public void drawPicture(Picture picture) { if (picture.createdFromStream) { diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index a94ec3a..b2961e5 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -36,7 +36,7 @@ class GLES20RecordingCanvas extends GLES20Canvas { RenderNode mNode; private GLES20RecordingCanvas() { - super(true, true); + super(); } static GLES20RecordingCanvas obtain(@NonNull RenderNode node) { diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java deleted file mode 100644 index 64a4c41..0000000 --- a/core/java/android/view/GLRenderer.java +++ /dev/null @@ -1,1527 +0,0 @@ -/* - * Copyright (C) 2013 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.view; - -import static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_SIZE; -import static javax.microedition.khronos.egl.EGL10.EGL_BAD_NATIVE_WINDOW; -import static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE; -import static javax.microedition.khronos.egl.EGL10.EGL_CONFIG_CAVEAT; -import static javax.microedition.khronos.egl.EGL10.EGL_DEFAULT_DISPLAY; -import static javax.microedition.khronos.egl.EGL10.EGL_DEPTH_SIZE; -import static javax.microedition.khronos.egl.EGL10.EGL_DRAW; -import static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE; -import static javax.microedition.khronos.egl.EGL10.EGL_HEIGHT; -import static javax.microedition.khronos.egl.EGL10.EGL_NONE; -import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT; -import static javax.microedition.khronos.egl.EGL10.EGL_NO_DISPLAY; -import static javax.microedition.khronos.egl.EGL10.EGL_NO_SURFACE; -import static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE; -import static javax.microedition.khronos.egl.EGL10.EGL_RENDERABLE_TYPE; -import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLES; -import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLE_BUFFERS; -import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE; -import static javax.microedition.khronos.egl.EGL10.EGL_SUCCESS; -import static javax.microedition.khronos.egl.EGL10.EGL_SURFACE_TYPE; -import static javax.microedition.khronos.egl.EGL10.EGL_WIDTH; -import static javax.microedition.khronos.egl.EGL10.EGL_WINDOW_BIT; - -import android.content.ComponentCallbacks2; -import android.graphics.Bitmap; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.SurfaceTexture; -import android.opengl.EGL14; -import android.opengl.GLUtils; -import android.opengl.ManagedEGLContext; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.Trace; -import android.util.DisplayMetrics; -import android.util.Log; -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; -import java.util.concurrent.locks.ReentrantLock; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGL11; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; -import javax.microedition.khronos.opengles.GL; - -/** - * Hardware renderer using OpenGL - * - * @hide - */ -public class GLRenderer extends HardwareRenderer { - static final int SURFACE_STATE_ERROR = 0; - static final int SURFACE_STATE_SUCCESS = 1; - static final int SURFACE_STATE_UPDATED = 2; - - static final int FUNCTOR_PROCESS_DELAY = 4; - - /** - * Number of frames to profile. - */ - private static final int PROFILE_MAX_FRAMES = 128; - - /** - * Number of floats per profiled frame. - */ - private static final int PROFILE_FRAME_DATA_COUNT = 3; - - private static final int PROFILE_DRAW_MARGIN = 0; - private static final int PROFILE_DRAW_WIDTH = 3; - private static final int[] PROFILE_DRAW_COLORS = { 0xcf3e66cc, 0xcfdc3912, 0xcfe69800 }; - private static final int PROFILE_DRAW_CURRENT_FRAME_COLOR = 0xcf5faa4d; - private static final int PROFILE_DRAW_THRESHOLD_COLOR = 0xff5faa4d; - private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2; - private static final int PROFILE_DRAW_DP_PER_MS = 7; - - private static final String[] VISUALIZERS = { - PROFILE_PROPERTY_VISUALIZE_BARS, - }; - - private static final String[] OVERDRAW = { - OVERDRAW_PROPERTY_SHOW, - }; - private static final int GL_VERSION = 2; - - static EGL10 sEgl; - static EGLDisplay sEglDisplay; - static EGLConfig sEglConfig; - static final Object[] sEglLock = new Object[0]; - int mWidth = -1, mHeight = -1; - - static final ThreadLocal<ManagedEGLContext> sEglContextStorage - = new ThreadLocal<ManagedEGLContext>(); - - EGLContext mEglContext; - Thread mEglThread; - - EGLSurface mEglSurface; - - GL mGl; - HardwareCanvas mCanvas; - - String mName; - - long mFrameCount; - Paint mDebugPaint; - - static boolean sDirtyRegions; - static final boolean sDirtyRegionsRequested; - static { - String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true"); - //noinspection PointlessBooleanExpression,ConstantConditions - sDirtyRegions = "true".equalsIgnoreCase(dirtyProperty); - sDirtyRegionsRequested = sDirtyRegions; - } - - boolean mDirtyRegionsEnabled; - boolean mUpdateDirtyRegions; - - boolean mProfileEnabled; - int mProfileVisualizerType = -1; - float[] mProfileData; - ReentrantLock mProfileLock; - int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT; - - GraphDataProvider mDebugDataProvider; - float[][] mProfileShapes; - Paint mProfilePaint; - - boolean mDebugDirtyRegions; - int mDebugOverdraw = -1; - - final boolean mTranslucent; - - private boolean mDestroyed; - - private final Rect mRedrawClip = new Rect(); - - private final int[] mSurfaceSize = new int[2]; - - private long mDrawDelta = Long.MAX_VALUE; - - private GLES20Canvas mGlCanvas; - - private DisplayMetrics mDisplayMetrics; - - private static EGLSurface sPbuffer; - private static final Object[] sPbufferLock = new Object[0]; - - private List<HardwareLayer> mAttachedLayers = new ArrayList<HardwareLayer>(); - - private static class GLRendererEglContext extends ManagedEGLContext { - final Handler mHandler = new Handler(); - - public GLRendererEglContext(EGLContext context) { - super(context); - } - - @Override - public void onTerminate(final EGLContext eglContext) { - // Make sure we do this on the correct thread. - if (mHandler.getLooper() != Looper.myLooper()) { - mHandler.post(new Runnable() { - @Override - public void run() { - onTerminate(eglContext); - } - }); - return; - } - - synchronized (sEglLock) { - if (sEgl == null) return; - - if (EGLImpl.getInitCount(sEglDisplay) == 1) { - usePbufferSurface(eglContext); - GLES20Canvas.terminateCaches(); - - sEgl.eglDestroyContext(sEglDisplay, eglContext); - sEglContextStorage.set(null); - sEglContextStorage.remove(); - - sEgl.eglDestroySurface(sEglDisplay, sPbuffer); - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT); - - sEgl.eglReleaseThread(); - sEgl.eglTerminate(sEglDisplay); - - sEgl = null; - sEglDisplay = null; - sEglConfig = null; - sPbuffer = null; - } - } - } - } - - HardwareCanvas createCanvas() { - return mGlCanvas = new GLES20Canvas(mTranslucent); - } - - ManagedEGLContext createManagedContext(EGLContext eglContext) { - return new GLRendererEglContext(mEglContext); - } - - int[] getConfig(boolean dirtyRegions) { - //noinspection PointlessBooleanExpression,ConstantConditions - final int stencilSize = GLES20Canvas.getStencilSize(); - final int swapBehavior = dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; - - return new int[] { - EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, 0, - EGL_CONFIG_CAVEAT, EGL_NONE, - EGL_STENCIL_SIZE, stencilSize, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior, - EGL_NONE - }; - } - - void initCaches() { - if (GLES20Canvas.initCaches()) { - // Caches were (re)initialized, rebind atlas - initAtlas(); - } - } - - void initAtlas() { - IBinder binder = ServiceManager.getService("assetatlas"); - if (binder == null) return; - - IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder); - try { - if (atlas.isCompatible(android.os.Process.myPpid())) { - GraphicBuffer buffer = atlas.getBuffer(); - if (buffer != null) { - long[] map = atlas.getMap(); - if (map != null) { - GLES20Canvas.initAtlas(buffer, map); - } - // If IAssetAtlas is not the same class as the IBinder - // we are using a remote service and we can safely - // destroy the graphic buffer - if (atlas.getClass() != binder.getClass()) { - buffer.destroy(); - } - } - } - } catch (RemoteException e) { - Log.w(LOG_TAG, "Could not acquire atlas", e); - } - } - - boolean canDraw() { - return mGl != null && mCanvas != null && mGlCanvas != null; - } - - int onPreDraw(Rect dirty) { - return mGlCanvas.onPreDraw(dirty); - } - - void onPostDraw() { - mGlCanvas.onPostDraw(); - } - - void drawProfileData(View.AttachInfo attachInfo) { - if (mDebugDataProvider != null) { - final GraphDataProvider provider = mDebugDataProvider; - initProfileDrawData(attachInfo, provider); - - final int height = provider.getVerticalUnitSize(); - final int margin = provider.getHorizontaUnitMargin(); - final int width = provider.getHorizontalUnitSize(); - - int x = 0; - int count = 0; - int current = 0; - - final float[] data = provider.getData(); - final int elementCount = provider.getElementCount(); - final int graphType = provider.getGraphType(); - - int totalCount = provider.getFrameCount() * elementCount; - if (graphType == GraphDataProvider.GRAPH_TYPE_LINES) { - totalCount -= elementCount; - } - - for (int i = 0; i < totalCount; i += elementCount) { - if (data[i] < 0.0f) break; - - int index = count * 4; - if (i == provider.getCurrentFrame() * elementCount) current = index; - - x += margin; - int x2 = x + width; - - int y2 = mHeight; - int y1 = (int) (y2 - data[i] * height); - - switch (graphType) { - case GraphDataProvider.GRAPH_TYPE_BARS: { - for (int j = 0; j < elementCount; j++) { - //noinspection MismatchedReadAndWriteOfArray - final float[] r = mProfileShapes[j]; - r[index] = x; - r[index + 1] = y1; - r[index + 2] = x2; - r[index + 3] = y2; - - y2 = y1; - if (j < elementCount - 1) { - y1 = (int) (y2 - data[i + j + 1] * height); - } - } - } break; - case GraphDataProvider.GRAPH_TYPE_LINES: { - for (int j = 0; j < elementCount; j++) { - //noinspection MismatchedReadAndWriteOfArray - final float[] r = mProfileShapes[j]; - r[index] = (x + x2) * 0.5f; - r[index + 1] = index == 0 ? y1 : r[index - 1]; - r[index + 2] = r[index] + width; - r[index + 3] = y1; - - y2 = y1; - if (j < elementCount - 1) { - y1 = (int) (y2 - data[i + j + 1] * height); - } - } - } break; - } - - - x += width; - count++; - } - - x += margin; - - drawGraph(graphType, count); - drawCurrentFrame(graphType, current); - drawThreshold(x, height); - } - } - - private void drawGraph(int graphType, int count) { - for (int i = 0; i < mProfileShapes.length; i++) { - mDebugDataProvider.setupGraphPaint(mProfilePaint, i); - switch (graphType) { - case GraphDataProvider.GRAPH_TYPE_BARS: - mGlCanvas.drawRects(mProfileShapes[i], count * 4, mProfilePaint); - break; - case GraphDataProvider.GRAPH_TYPE_LINES: - mGlCanvas.drawLines(mProfileShapes[i], 0, count * 4, mProfilePaint); - break; - } - } - } - - private void drawCurrentFrame(int graphType, int index) { - if (index >= 0) { - mDebugDataProvider.setupCurrentFramePaint(mProfilePaint); - switch (graphType) { - case GraphDataProvider.GRAPH_TYPE_BARS: - mGlCanvas.drawRect(mProfileShapes[2][index], mProfileShapes[2][index + 1], - mProfileShapes[2][index + 2], mProfileShapes[0][index + 3], - mProfilePaint); - break; - case GraphDataProvider.GRAPH_TYPE_LINES: - mGlCanvas.drawLine(mProfileShapes[2][index], mProfileShapes[2][index + 1], - mProfileShapes[2][index], mHeight, mProfilePaint); - break; - } - } - } - - private void drawThreshold(int x, int height) { - float threshold = mDebugDataProvider.getThreshold(); - if (threshold > 0.0f) { - mDebugDataProvider.setupThresholdPaint(mProfilePaint); - int y = (int) (mHeight - threshold * height); - mGlCanvas.drawLine(0.0f, y, x, y, mProfilePaint); - } - } - - private void initProfileDrawData(View.AttachInfo attachInfo, GraphDataProvider provider) { - if (mProfileShapes == null) { - final int elementCount = provider.getElementCount(); - final int frameCount = provider.getFrameCount(); - - mProfileShapes = new float[elementCount][]; - for (int i = 0; i < elementCount; i++) { - mProfileShapes[i] = new float[frameCount * 4]; - } - - mProfilePaint = new Paint(); - } - - mProfilePaint.reset(); - if (provider.getGraphType() == GraphDataProvider.GRAPH_TYPE_LINES) { - mProfilePaint.setAntiAlias(true); - } - - if (mDisplayMetrics == null) { - mDisplayMetrics = new DisplayMetrics(); - } - - attachInfo.mDisplay.getMetrics(mDisplayMetrics); - provider.prepare(mDisplayMetrics); - } - - @Override - void destroy(boolean full) { - try { - if (full && mCanvas != null) { - mCanvas = null; - } - - if (!isEnabled() || mDestroyed) { - setEnabled(false); - return; - } - - destroySurface(); - setEnabled(false); - - mDestroyed = true; - mGl = null; - } finally { - if (full && mGlCanvas != null) { - mGlCanvas = null; - } - } - } - - @Override - void pushLayerUpdate(HardwareLayer layer) { - mGlCanvas.pushLayerUpdate(layer); - } - - @Override - void flushLayerUpdates() { - if (validate()) { - flushLayerChanges(); - mGlCanvas.flushLayerUpdates(); - } - } - - @Override - HardwareLayer createTextureLayer() { - validate(); - return HardwareLayer.createTextureLayer(this); - } - - @Override - public HardwareLayer createDisplayListLayer(int width, int height) { - validate(); - 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()); - } - - @Override - void onLayerDestroyed(HardwareLayer layer) { - if (mGlCanvas != null) { - mGlCanvas.cancelLayerUpdate(layer); - } - if (hasContext()) { - long backingLayer = layer.detachBackingLayer(); - nDestroyLayer(backingLayer); - } - mAttachedLayers.remove(layer); - } - - @Override - public SurfaceTexture createSurfaceTexture(HardwareLayer layer) { - return layer.createSurfaceTexture(); - } - - @Override - boolean copyLayerInto(HardwareLayer layer, Bitmap bitmap) { - if (!validate()) { - throw new IllegalStateException("Could not acquire hardware rendering context"); - } - layer.flushChanges(); - return GLES20Canvas.nCopyLayer(layer.getLayer(), bitmap.mNativeBitmap); - } - - @Override - boolean safelyRun(Runnable action) { - boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR; - - if (needsContext) { - GLRendererEglContext managedContext = - (GLRendererEglContext) sEglContextStorage.get(); - if (managedContext == null) return false; - usePbufferSurface(managedContext.getContext()); - } - - try { - action.run(); - } finally { - if (needsContext) { - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - } - - return true; - } - - @Override - void invokeFunctor(long functor, boolean waitForCompletion) { - boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR; - boolean hasContext = !needsContext; - - if (needsContext) { - GLRendererEglContext managedContext = - (GLRendererEglContext) sEglContextStorage.get(); - if (managedContext != null) { - usePbufferSurface(managedContext.getContext()); - hasContext = true; - } - } - - try { - nInvokeFunctor(functor, hasContext); - } finally { - if (needsContext) { - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - } - } - - private static native void nInvokeFunctor(long functor, boolean hasContext); - - @Override - void destroyHardwareResources(final View view) { - if (view != null) { - safelyRun(new Runnable() { - @Override - public void run() { - if (mCanvas != null) { - mCanvas.clearLayerUpdates(); - } - destroyResources(view); - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); - } - }); - } - } - - private static void destroyResources(View view) { - view.destroyHardwareResources(); - - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - - int count = group.getChildCount(); - for (int i = 0; i < count; i++) { - destroyResources(group.getChildAt(i)); - } - } - } - - static void startTrimMemory(int level) { - if (sEgl == null || sEglConfig == null) return; - - GLRendererEglContext managedContext = - (GLRendererEglContext) sEglContextStorage.get(); - // We do not have OpenGL objects - if (managedContext == null) { - return; - } else { - usePbufferSurface(managedContext.getContext()); - } - - if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL); - } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE); - } - } - - static void endTrimMemory() { - if (sEgl != null && sEglDisplay != null) { - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - } - - private static void usePbufferSurface(EGLContext eglContext) { - synchronized (sPbufferLock) { - // Create a temporary 1x1 pbuffer so we have a context - // to clear our OpenGL objects - if (sPbuffer == null) { - sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] { - EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE - }); - } - } - sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext); - } - - GLRenderer(boolean translucent) { - mTranslucent = translucent; - - loadSystemProperties(); - } - - @Override - void setOpaque(boolean opaque) { - // Not supported - } - - @Override - boolean loadSystemProperties() { - boolean value; - boolean changed = false; - - String profiling = SystemProperties.get(PROFILE_PROPERTY); - int graphType = search(VISUALIZERS, profiling); - value = graphType >= 0; - - if (graphType != mProfileVisualizerType) { - changed = true; - mProfileVisualizerType = graphType; - - mProfileShapes = null; - mProfilePaint = null; - - if (value) { - mDebugDataProvider = new GraphDataProvider(graphType); - } else { - mDebugDataProvider = null; - } - } - - // If on-screen profiling is not enabled, we need to check whether - // console profiling only is enabled - if (!value) { - value = Boolean.parseBoolean(profiling); - } - - if (value != mProfileEnabled) { - changed = true; - mProfileEnabled = value; - - if (mProfileEnabled) { - Log.d(LOG_TAG, "Profiling hardware renderer"); - - int maxProfileFrames = SystemProperties.getInt(PROFILE_MAXFRAMES_PROPERTY, - PROFILE_MAX_FRAMES); - mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT]; - for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { - mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; - } - - mProfileLock = new ReentrantLock(); - } else { - mProfileData = null; - mProfileLock = null; - mProfileVisualizerType = -1; - } - - mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT; - } - - value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false); - if (value != mDebugDirtyRegions) { - changed = true; - mDebugDirtyRegions = value; - - if (mDebugDirtyRegions) { - Log.d(LOG_TAG, "Debugging dirty regions"); - } - } - - String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY); - int debugOverdraw = search(OVERDRAW, overdraw); - if (debugOverdraw != mDebugOverdraw) { - changed = true; - mDebugOverdraw = debugOverdraw; - } - - if (loadProperties()) { - changed = true; - } - - return changed; - } - - 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; - } - - @Override - void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) { - if (mProfileEnabled) { - pw.printf("\n\tDraw\tProcess\tExecute\n"); - - mProfileLock.lock(); - try { - for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { - if (mProfileData[i] < 0) { - break; - } - pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1], - mProfileData[i + 2]); - mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; - } - mProfileCurrentFrame = mProfileData.length; - } finally { - mProfileLock.unlock(); - } - } - } - - /** - * Indicates whether this renderer instance can track and update dirty regions. - */ - boolean hasDirtyRegions() { - return mDirtyRegionsEnabled; - } - - /** - * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)} - * is invoked and the requested flag is turned off. The error code is - * also logged as a warning. - */ - void checkEglErrors() { - if (isEnabled()) { - checkEglErrorsForced(); - } - } - - private void checkEglErrorsForced() { - int error = sEgl.eglGetError(); - if (error != EGL_SUCCESS) { - // something bad has happened revert to - // normal rendering. - Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error)); - fallback(error != EGL11.EGL_CONTEXT_LOST); - } - } - - private void fallback(boolean fallback) { - destroy(true); - if (fallback) { - // we'll try again if it was context lost - setRequested(false); - Log.w(LOG_TAG, "Mountain View, we've had a problem here. " - + "Switching back to software rendering."); - } - } - - @Override - boolean initialize(Surface surface) throws OutOfResourcesException { - if (isRequested() && !isEnabled()) { - boolean contextCreated = initializeEgl(); - mGl = createEglSurface(surface); - mDestroyed = false; - - if (mGl != null) { - int err = sEgl.eglGetError(); - if (err != EGL_SUCCESS) { - destroy(true); - setRequested(false); - } else { - if (mCanvas == null) { - mCanvas = createCanvas(); - } - setEnabled(true); - - if (contextCreated) { - initAtlas(); - } - } - - return mCanvas != null; - } - } - return false; - } - - @Override - void updateSurface(Surface surface) throws OutOfResourcesException { - if (isRequested() && isEnabled()) { - createEglSurface(surface); - } - } - - @Override - void pauseSurface(Surface surface) { - // No-op - } - - boolean initializeEgl() { - synchronized (sEglLock) { - if (sEgl == null && sEglConfig == null) { - sEgl = (EGL10) EGLContext.getEGL(); - - // Get to the default display. - sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY); - - if (sEglDisplay == EGL_NO_DISPLAY) { - throw new RuntimeException("eglGetDisplay failed " - + GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - - // We can now initialize EGL for that display - int[] version = new int[2]; - if (!sEgl.eglInitialize(sEglDisplay, version)) { - throw new RuntimeException("eglInitialize failed " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - - checkEglErrorsForced(); - - sEglConfig = loadEglConfig(); - } - } - - ManagedEGLContext managedContext = sEglContextStorage.get(); - mEglContext = managedContext != null ? managedContext.getContext() : null; - mEglThread = Thread.currentThread(); - - if (mEglContext == null) { - mEglContext = createContext(sEgl, sEglDisplay, sEglConfig); - sEglContextStorage.set(createManagedContext(mEglContext)); - return true; - } - - return false; - } - - private EGLConfig loadEglConfig() { - EGLConfig eglConfig = chooseEglConfig(); - if (eglConfig == null) { - // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without - if (sDirtyRegions) { - sDirtyRegions = false; - eglConfig = chooseEglConfig(); - if (eglConfig == null) { - throw new RuntimeException("eglConfig not initialized"); - } - } else { - throw new RuntimeException("eglConfig not initialized"); - } - } - return eglConfig; - } - - private EGLConfig chooseEglConfig() { - EGLConfig[] configs = new EGLConfig[1]; - int[] configsCount = new int[1]; - int[] configSpec = getConfig(sDirtyRegions); - - // Debug - final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, ""); - if ("all".equalsIgnoreCase(debug)) { - sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount); - - EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]]; - sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs, - configsCount[0], configsCount); - - for (EGLConfig config : debugConfigs) { - printConfig(config); - } - } - - if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) { - throw new IllegalArgumentException("eglChooseConfig failed " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - } else if (configsCount[0] > 0) { - if ("choice".equalsIgnoreCase(debug)) { - printConfig(configs[0]); - } - return configs[0]; - } - - return null; - } - - private static void printConfig(EGLConfig config) { - int[] value = new int[1]; - - Log.d(LOG_TAG, "EGL configuration " + config + ":"); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value); - Log.d(LOG_TAG, " RED_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value); - Log.d(LOG_TAG, " GREEN_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value); - Log.d(LOG_TAG, " BLUE_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value); - Log.d(LOG_TAG, " ALPHA_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value); - Log.d(LOG_TAG, " DEPTH_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value); - Log.d(LOG_TAG, " STENCIL_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLE_BUFFERS, value); - Log.d(LOG_TAG, " SAMPLE_BUFFERS = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLES, value); - Log.d(LOG_TAG, " SAMPLES = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value); - Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0])); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_CONFIG_CAVEAT, value); - Log.d(LOG_TAG, " CONFIG_CAVEAT = 0x" + Integer.toHexString(value[0])); - } - - GL createEglSurface(Surface surface) throws OutOfResourcesException { - // Check preconditions. - if (sEgl == null) { - throw new RuntimeException("egl not initialized"); - } - if (sEglDisplay == null) { - throw new RuntimeException("eglDisplay not initialized"); - } - if (sEglConfig == null) { - throw new RuntimeException("eglConfig not initialized"); - } - if (Thread.currentThread() != mEglThread) { - throw new IllegalStateException("HardwareRenderer cannot be used " - + "from multiple threads"); - } - - // In case we need to destroy an existing surface - destroySurface(); - - // Create an EGL surface we can render into. - if (!createSurface(surface)) { - return null; - } - - initCaches(); - - return mEglContext.getGL(); - } - - private void enableDirtyRegions() { - // If mDirtyRegions is set, this means we have an EGL configuration - // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set - if (sDirtyRegions) { - if (!(mDirtyRegionsEnabled = preserveBackBuffer())) { - Log.w(LOG_TAG, "Backbuffer cannot be preserved"); - } - } else if (sDirtyRegionsRequested) { - // If mDirtyRegions is not set, our EGL configuration does not - // have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default - // swap behavior might be EGL_BUFFER_PRESERVED, which means we - // want to set mDirtyRegions. We try to do this only if dirty - // regions were initially requested as part of the device - // configuration (see RENDER_DIRTY_REGIONS) - mDirtyRegionsEnabled = isBackBufferPreserved(); - } - } - - EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { - final int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, GL_VERSION, EGL_NONE }; - - EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, - attribs); - if (context == null || context == EGL_NO_CONTEXT) { - //noinspection ConstantConditions - throw new IllegalStateException( - "Could not create an EGL context. eglCreateContext failed with error: " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - - return context; - } - - void destroySurface() { - if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) { - if (mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) { - sEgl.eglMakeCurrent(sEglDisplay, - EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - sEgl.eglDestroySurface(sEglDisplay, mEglSurface); - mEglSurface = null; - } - } - - @Override - void invalidate(Surface surface) { - // Cancels any existing buffer to ensure we'll get a buffer - // of the right size before we call eglSwapBuffers - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - - if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) { - sEgl.eglDestroySurface(sEglDisplay, mEglSurface); - mEglSurface = null; - setEnabled(false); - } - - if (surface.isValid()) { - if (!createSurface(surface)) { - return; - } - - mUpdateDirtyRegions = true; - - if (mCanvas != null) { - setEnabled(true); - } - } - } - - private boolean createSurface(Surface surface) { - mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, surface, null); - - if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { - int error = sEgl.eglGetError(); - if (error == EGL_BAD_NATIVE_WINDOW) { - Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); - return false; - } - throw new RuntimeException("createWindowSurface failed " - + GLUtils.getEGLErrorString(error)); - } - - if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { - throw new IllegalStateException("eglMakeCurrent failed " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - - enableDirtyRegions(); - - return true; - } - - boolean validate() { - return checkRenderContext() != SURFACE_STATE_ERROR; - } - - @Override - void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius) { - if (validate()) { - mCanvas.setViewport(width, height); - mCanvas.initializeLight(lightX, lightY, lightZ, lightRadius); - mWidth = width; - mHeight = height; - } - } - - @Override - int getWidth() { - return mWidth; - } - - @Override - int getHeight() { - return mHeight; - } - - @Override - void setName(String name) { - mName = name; - } - - @Override - void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, - Rect dirty) { - if (canDraw()) { - if (!hasDirtyRegions()) { - dirty = null; - } - attachInfo.mIgnoreDirtyState = true; - attachInfo.mDrawingTime = SystemClock.uptimeMillis(); - - view.mPrivateFlags |= View.PFLAG_DRAWN; - - // We are already on the correct thread - final int surfaceState = checkRenderContextUnsafe(); - if (surfaceState != SURFACE_STATE_ERROR) { - HardwareCanvas canvas = mCanvas; - - if (mProfileEnabled) { - mProfileLock.lock(); - } - - dirty = beginFrame(canvas, dirty, surfaceState); - - RenderNode displayList = buildDisplayList(view, canvas); - - flushLayerChanges(); - - // buildDisplayList() calls into user code which can cause - // an eglMakeCurrent to happen with a different surface/context. - // We must therefore check again here. - if (checkRenderContextUnsafe() == SURFACE_STATE_ERROR) { - return; - } - - int saveCount = 0; - int status = RenderNode.STATUS_DONE; - - long start = getSystemTime(); - try { - status = prepareFrame(dirty); - - saveCount = canvas.save(); - callbacks.onHardwarePreDraw(canvas); - - if (displayList != null) { - status |= drawDisplayList(canvas, displayList, status); - } else { - // Shouldn't reach here - view.draw(canvas); - } - } catch (Exception e) { - Log.e(LOG_TAG, "An error has occurred while drawing:", e); - } finally { - callbacks.onHardwarePostDraw(canvas); - canvas.restoreToCount(saveCount); - view.mRecreateDisplayList = false; - - mDrawDelta = getSystemTime() - start; - - if (mDrawDelta > 0) { - mFrameCount++; - - debugDirtyRegions(dirty, canvas); - drawProfileData(attachInfo); - } - } - - onPostDraw(); - - swapBuffers(status); - - if (mProfileEnabled) { - mProfileLock.unlock(); - } - - attachInfo.mIgnoreDirtyState = false; - } - } - } - - private void flushLayerChanges() { - // Loop through and apply any pending layer changes - for (int i = 0; i < mAttachedLayers.size(); i++) { - HardwareLayer layer = mAttachedLayers.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--; - } - } - } - - @Override - void fence() { - // Everything is immediate, so this is a no-op - } - - private RenderNode buildDisplayList(View view, HardwareCanvas canvas) { - view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) - == View.PFLAG_INVALIDATED; - view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; - - long buildDisplayListStartTime = startBuildDisplayListProfiling(); - canvas.clearLayerUpdates(); - - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList"); - RenderNode renderNode = view.getDisplayList(); - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - - endBuildDisplayListProfiling(buildDisplayListStartTime); - - return renderNode; - } - - private Rect beginFrame(HardwareCanvas canvas, Rect dirty, int surfaceState) { - // We had to change the current surface and/or context, redraw everything - if (surfaceState == SURFACE_STATE_UPDATED) { - dirty = null; - beginFrame(null); - } else { - int[] size = mSurfaceSize; - beginFrame(size); - - if (size[1] != mHeight || size[0] != mWidth) { - mWidth = size[0]; - mHeight = size[1]; - - canvas.setViewport(mWidth, mHeight); - - dirty = null; - } - } - - if (mDebugDataProvider != null) dirty = null; - - return dirty; - } - - private long startBuildDisplayListProfiling() { - if (mProfileEnabled) { - mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT; - if (mProfileCurrentFrame >= mProfileData.length) { - mProfileCurrentFrame = 0; - } - - return System.nanoTime(); - } - return 0; - } - - private void endBuildDisplayListProfiling(long getDisplayListStartTime) { - if (mProfileEnabled) { - long now = System.nanoTime(); - float total = (now - getDisplayListStartTime) * 0.000001f; - //noinspection PointlessArithmeticExpression - mProfileData[mProfileCurrentFrame] = total; - } - } - - private int prepareFrame(Rect dirty) { - int status; - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame"); - try { - status = onPreDraw(dirty); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - } - return status; - } - - private int drawDisplayList(HardwareCanvas canvas, RenderNode displayList, - int status) { - - long drawDisplayListStartTime = 0; - if (mProfileEnabled) { - drawDisplayListStartTime = System.nanoTime(); - } - - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList"); - nPrepareTree(displayList.getNativeDisplayList()); - try { - status |= canvas.drawDisplayList(displayList, mRedrawClip, - RenderNode.FLAG_CLIP_CHILDREN); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - } - - if (mProfileEnabled) { - long now = System.nanoTime(); - float total = (now - drawDisplayListStartTime) * 0.000001f; - mProfileData[mProfileCurrentFrame + 1] = total; - } - - return status; - } - - private void swapBuffers(int status) { - if ((status & RenderNode.STATUS_DREW) == RenderNode.STATUS_DREW) { - long eglSwapBuffersStartTime = 0; - if (mProfileEnabled) { - eglSwapBuffersStartTime = System.nanoTime(); - } - - sEgl.eglSwapBuffers(sEglDisplay, mEglSurface); - - if (mProfileEnabled) { - long now = System.nanoTime(); - float total = (now - eglSwapBuffersStartTime) * 0.000001f; - mProfileData[mProfileCurrentFrame + 2] = total; - } - - checkEglErrors(); - } - } - - private void debugDirtyRegions(Rect dirty, HardwareCanvas canvas) { - if (mDebugDirtyRegions) { - if (mDebugPaint == null) { - mDebugPaint = new Paint(); - mDebugPaint.setColor(0x7fff0000); - } - - if (dirty != null && (mFrameCount & 1) == 0) { - canvas.drawRect(dirty, mDebugPaint); - } - } - } - - /** - * Ensures the current EGL context and surface are the ones we expect. - * This method throws an IllegalStateException if invoked from a thread - * that did not initialize EGL. - * - * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current, - * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or - * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one - * - * @see #checkRenderContextUnsafe() - */ - int checkRenderContext() { - if (mEglThread != Thread.currentThread()) { - throw new IllegalStateException("Hardware acceleration can only be used with a " + - "single UI thread.\nOriginal thread: " + mEglThread + "\n" + - "Current thread: " + Thread.currentThread()); - } - - return checkRenderContextUnsafe(); - } - - /** - * Ensures the current EGL context and surface are the ones we expect. - * This method does not check the current thread. - * - * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current, - * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or - * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one - * - * @see #checkRenderContext() - */ - private int checkRenderContextUnsafe() { - if (!mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW)) || - !mEglContext.equals(sEgl.eglGetCurrentContext())) { - if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { - Log.e(LOG_TAG, "eglMakeCurrent failed " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - fallback(true); - return SURFACE_STATE_ERROR; - } else { - if (mUpdateDirtyRegions) { - enableDirtyRegions(); - mUpdateDirtyRegions = false; - } - return SURFACE_STATE_UPDATED; - } - } - return SURFACE_STATE_SUCCESS; - } - - private static int dpToPx(int dp, float density) { - return (int) (dp * density + 0.5f); - } - - static native boolean loadProperties(); - - static native void setupShadersDiskCache(String cacheFile); - - /** - * Notifies EGL that the frame is about to be rendered. - * @param size - */ - static native void beginFrame(int[] size); - - /** - * Returns the current system time according to the renderer. - * This method is used for debugging only and should not be used - * as a clock. - */ - static native long getSystemTime(); - - /** - * Preserves the back buffer of the current surface after a buffer swap. - * Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current - * surface to EGL_BUFFER_PRESERVED. Calling this method requires an EGL - * config that supports EGL_SWAP_BEHAVIOR_PRESERVED_BIT. - * - * @return True if the swap behavior was successfully changed, - * false otherwise. - */ - static native boolean preserveBackBuffer(); - - /** - * Indicates whether the current surface preserves its back buffer - * after a buffer swap. - * - * @return True, if the surface's EGL_SWAP_BEHAVIOR is EGL_BUFFER_PRESERVED, - * false otherwise - */ - static native boolean isBackBufferPreserved(); - - static native void nDestroyLayer(long layerPtr); - - private static native void nPrepareTree(long displayListPtr); - - 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; - private int mHorizontalUnit; - private int mHorizontalMargin; - private int mThresholdStroke; - - public GraphDataProvider(int graphType) { - mGraphType = graphType; - } - - void prepare(DisplayMetrics metrics) { - final float density = metrics.density; - - mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density); - mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density); - mHorizontalMargin = dpToPx(PROFILE_DRAW_MARGIN, density); - mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density); - } - - int getGraphType() { - return mGraphType; - } - - int getVerticalUnitSize() { - return mVerticalUnit; - } - - int getHorizontalUnitSize() { - return mHorizontalUnit; - } - - int getHorizontaUnitMargin() { - return mHorizontalMargin; - } - - float[] getData() { - return mProfileData; - } - - float getThreshold() { - return 16; - } - - int getFrameCount() { - return mProfileData.length / PROFILE_FRAME_DATA_COUNT; - } - - int getElementCount() { - return PROFILE_FRAME_DATA_COUNT; - } - - int getCurrentFrame() { - return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT; - } - - void setupGraphPaint(Paint paint, int elementIndex) { - paint.setColor(PROFILE_DRAW_COLORS[elementIndex]); - if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); - } - - void setupThresholdPaint(Paint paint) { - paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR); - paint.setStrokeWidth(mThresholdStroke); - } - - 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/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java index 9568760..b8e7d8c 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -110,48 +110,6 @@ public abstract class HardwareCanvas extends Canvas { return RenderNode.STATUS_DONE; } - /** - * Indicates that the specified layer must be updated as soon as possible. - * - * @param layer The layer to update - * - * @see #clearLayerUpdates() - * - * @hide - */ - abstract void pushLayerUpdate(HardwareLayer layer); - - /** - * Cancels a queued layer update. If the specified layer was not - * queued for update, this method has no effect. - * - * @param layer The layer whose update to cancel - * - * @see #pushLayerUpdate(HardwareLayer) - * @see #clearLayerUpdates() - * - * @hide - */ - abstract void cancelLayerUpdate(HardwareLayer layer); - - /** - * Immediately executes all enqueued layer updates. - * - * @see #pushLayerUpdate(HardwareLayer) - * - * @hide - */ - abstract void flushLayerUpdates(); - - /** - * Removes all enqueued layer updates. - * - * @see #pushLayerUpdate(HardwareLayer) - * - * @hide - */ - abstract void clearLayerUpdates(); - public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, CanvasProperty<Float> radius, CanvasProperty<Paint> paint); } diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java index 4d78733..6acb134 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,41 +167,25 @@ final class HardwareLayer { surface.detachFromGLContext(); // SurfaceTexture owns the texture name and detachFromGLContext // should have deleted it - nOnTextureDestroyed(mFinalizer.mDeferredUpdater); + nOnTextureDestroyed(mFinalizer.get()); } }); } - /** - * This exists to minimize impact into the current HardwareLayer paths as - * some of the specifics of how to handle error cases in the fully - * deferred model will work - */ - @Deprecated - public void flushChanges() { - if (HardwareRenderer.sUseRenderThread) { - // Not supported, don't try. - return; - } - - boolean success = nFlushChanges(mFinalizer.mDeferredUpdater); - 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,48 +193,20 @@ 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; } - /** - * This should only be used by HardwareRenderer! Do not call directly - */ - static HardwareLayer createTextureLayer(HardwareRenderer renderer) { - return new HardwareLayer(renderer, nCreateTextureLayer(), LAYER_TYPE_TEXTURE); - } - static HardwareLayer adoptTextureLayer(HardwareRenderer renderer, long layer) { return new HardwareLayer(renderer, layer, LAYER_TYPE_TEXTURE); } - /** - * This should only be used by HardwareRenderer! Do not call directly - */ - static HardwareLayer createDisplayListLayer(HardwareRenderer renderer, - int width, int height) { - return new HardwareLayer(renderer, nCreateRenderLayer(width, height), LAYER_TYPE_DISPLAY_LIST); - } - static HardwareLayer adoptDisplayListLayer(HardwareRenderer renderer, long layer) { return new HardwareLayer(renderer, layer, LAYER_TYPE_DISPLAY_LIST); } - /** This also creates the underlying layer */ - private static native long nCreateTextureLayer(); - 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 +221,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 60f8ee3..d67c974 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -171,9 +171,6 @@ public abstract class HardwareRenderer { */ public static boolean sSystemRendererDisabled = false; - /** @hide */ - public static boolean sUseRenderThread = true; - private boolean mEnabled; private boolean mRequested = true; @@ -309,7 +306,7 @@ public abstract class HardwareRenderer { * @hide */ public static void setupDiskCache(File cacheDir) { - GLRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath()); + ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath()); } /** @@ -323,12 +320,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. */ @@ -475,11 +466,7 @@ public abstract class HardwareRenderer { static HardwareRenderer create(boolean translucent) { HardwareRenderer renderer = null; if (GLES20Canvas.isAvailable()) { - if (sUseRenderThread) { - renderer = new ThreadedRenderer(translucent); - } else { - renderer = new GLRenderer(translucent); - } + renderer = new ThreadedRenderer(translucent); } return renderer; } @@ -506,7 +493,7 @@ public abstract class HardwareRenderer { * see {@link android.content.ComponentCallbacks} */ static void startTrimMemory(int level) { - GLRenderer.startTrimMemory(level); + ThreadedRenderer.startTrimMemory(level); } /** @@ -514,7 +501,7 @@ public abstract class HardwareRenderer { * cleanup special resources used by the memory trimming process. */ static void endTrimMemory() { - GLRenderer.endTrimMemory(); + ThreadedRenderer.endTrimMemory(); } /** diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java index e918119..4979059 100644 --- a/core/java/android/view/RenderNodeAnimator.java +++ b/core/java/android/view/RenderNodeAnimator.java @@ -219,6 +219,15 @@ public final class RenderNodeAnimator extends Animator { return mTarget; } + /** + * WARNING: May only be called once!!! + * TODO: Fix above -_- + */ + public void setStartValue(float startValue) { + checkMutable(); + nSetStartValue(mNativePtr.get(), startValue); + } + @Override public void setStartDelay(long startDelay) { checkMutable(); @@ -282,11 +291,12 @@ public final class RenderNodeAnimator extends Animator { } private static native long nCreateAnimator(WeakReference<RenderNodeAnimator> weakThis, - int property, float deltaValue); + int property, float finalValue); private static native long nCreateCanvasPropertyFloatAnimator(WeakReference<RenderNodeAnimator> weakThis, - long canvasProperty, float deltaValue); + long canvasProperty, float finalValue); private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis, - long canvasProperty, int paintField, float deltaValue); + long canvasProperty, int paintField, float finalValue); + private static native void nSetStartValue(long nativePtr, float startValue); private static native void nSetDuration(long nativePtr, long duration); private static native long nGetDuration(long nativePtr); private static native void nSetStartDelay(long nativePtr, long startDelay); diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index cac23a8..9b3ef7f 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -294,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 @@ -309,7 +304,7 @@ public class ThreadedRenderer extends HardwareRenderer { @Override void onLayerDestroyed(HardwareLayer layer) { - nDestroyLayer(mNativeProxy, layer.getDeferredLayerUpdater()); + nCancelLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater()); } @Override @@ -336,6 +331,14 @@ public class ThreadedRenderer extends HardwareRenderer { } } + static void startTrimMemory(int level) { + // TODO + } + + static void endTrimMemory() { + // TODO + } + private static class AtlasInitializer { static AtlasInitializer sInstance = new AtlasInitializer(); @@ -372,6 +375,8 @@ public class ThreadedRenderer extends HardwareRenderer { } } + static native void setupShadersDiskCache(String cacheFile); + private static native void nSetAtlas(GraphicBuffer buffer, long[] map); private static native long nCreateRootRenderNode(); @@ -398,7 +403,8 @@ 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); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ce266d7..b500e46 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -13593,12 +13593,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - // The layer is not valid if the underlying GPU resources cannot be allocated - mHardwareLayer.flushChanges(); - if (!mHardwareLayer.isValid()) { - return null; - } - mHardwareLayer.setLayerPaint(mLayerPaint); RenderNode displayList = mHardwareLayer.startRecording(); updateDisplayListIfDirty(displayList, true); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ac25b57..f3d1e3c 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -716,17 +716,6 @@ public final class ViewRootImpl implements ViewParent, if (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled && forceHwAccelerated)) { - if (!HardwareRenderer.sUseRenderThread) { - // TODO: Delete - // Don't enable hardware acceleration when we're not on the main thread - if (!HardwareRenderer.sSystemRendererDisabled && - Looper.getMainLooper() != Looper.myLooper()) { - Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware " - + "acceleration outside of the main thread, aborting"); - return; - } - } - if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.destroy(true); } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index d45d686..2b4677c 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -1199,6 +1199,9 @@ public interface WindowManagerPolicy { /** * 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 fadeoutDuration); + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration); } 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 5033bee..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 @@ -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/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl index 03d3b22..77f0dec 100644 --- a/core/java/com/android/internal/app/IMediaContainerService.aidl +++ b/core/java/com/android/internal/app/IMediaContainerService.aidl @@ -25,16 +25,18 @@ import android.content.res.ObbInfo; interface IMediaContainerService { String copyResourceToContainer(in Uri packageURI, String containerId, String key, String resFileName, String publicResFileName, boolean isExternal, - boolean isForwardLocked); + boolean isForwardLocked, in String abiOverride); int copyResource(in Uri packageURI, in ContainerEncryptionParams encryptionParams, in ParcelFileDescriptor outStream); - PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold); + PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold, + in String abiOverride); boolean checkInternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in long threshold); - boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked); + boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in String abiOverride); ObbInfo getObbInfo(in String filename); long calculateDirectorySize(in String directory); /** Return file system stats: [0] is total bytes, [1] is available bytes */ long[] getFileSystemStats(in String path); void clearDirectory(in String directory); - long calculateInstalledSize(in String packagePath, boolean isForwardLocked); + long calculateInstalledSize(in String packagePath, boolean isForwardLocked, + in String abiOverride); } 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 5c7a4e6..c0b5b97 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -588,7 +588,7 @@ public class WindowDecorActionBar extends ActionBar implements return; } - final FragmentTransaction trans = ((View) mDecorToolbar).isInEditMode() ? null : + final FragmentTransaction trans = mDecorToolbar.getViewGroup().isInEditMode() ? null : mActivity.getFragmentManager().beginTransaction().disallowAddToBackStack(); if (mSelectedTab == tab) { @@ -847,7 +847,7 @@ public class WindowDecorActionBar extends ActionBar implements mDecorToolbar.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE); if (mTabScrollView != null && !mDecorToolbar.hasEmbeddedTabs() && - isCollapsed((View) mDecorToolbar)) { + isCollapsed(mDecorToolbar.getViewGroup())) { mTabScrollView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); } } @@ -959,7 +959,7 @@ public class WindowDecorActionBar extends ActionBar implements // Clear out the context mode views after the animation finishes mContextView.closeMode(); - ((View) mDecorToolbar).sendAccessibilityEvent( + mDecorToolbar.getViewGroup().sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll); diff --git a/core/java/com/android/internal/backup/BackupConstants.java b/core/java/com/android/internal/backup/BackupConstants.java deleted file mode 100644 index 4c276b7..0000000 --- a/core/java/com/android/internal/backup/BackupConstants.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2009 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.backup; - -/** - * Constants used internally between the backup manager and its transports - */ -public class BackupConstants { - public static final int TRANSPORT_OK = 0; - public static final int TRANSPORT_ERROR = 1; - public static final int TRANSPORT_NOT_INITIALIZED = 2; - public static final int AGENT_ERROR = 3; - public static final int AGENT_UNKNOWN = 4; -} diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index f2b29ef..7292116 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -104,7 +104,7 @@ public class LocalTransport extends BackupTransport { public int initializeDevice() { if (DEBUG) Log.v(TAG, "wiping all data"); deleteContents(mCurrentSetDir); - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) { @@ -166,7 +166,7 @@ public class LocalTransport extends BackupTransport { entity.write(buf, 0, dataSize); } catch (IOException e) { Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath()); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } finally { entity.close(); } @@ -174,11 +174,11 @@ public class LocalTransport extends BackupTransport { entityFile.delete(); } } - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } catch (IOException e) { // oops, something went wrong. abort the operation and return error. Log.v(TAG, "Exception reading backup input:", e); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } } @@ -208,12 +208,12 @@ public class LocalTransport extends BackupTransport { } packageDir.delete(); } - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } public int finishBackup() { if (DEBUG) Log.v(TAG, "finishBackup()"); - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } // Restore handling @@ -249,7 +249,7 @@ public class LocalTransport extends BackupTransport { mRestorePackage = -1; mRestoreToken = token; mRestoreDataDir = new File(mDataDir, Long.toString(token)); - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } public String nextRestorePackage() { @@ -281,7 +281,7 @@ public class LocalTransport extends BackupTransport { ArrayList<DecodedFilename> blobs = contentsByKey(packageDir); if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error Log.e(TAG, "No keys for package: " + packageDir); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } // We expect at least some data if the directory exists in the first place @@ -302,10 +302,10 @@ public class LocalTransport extends BackupTransport { in.close(); } } - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } catch (IOException e) { Log.e(TAG, "Unable to read backup records", e); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } } diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index ba419f9..dab3aff 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -20,6 +20,7 @@ import android.content.pm.PackageManager; import android.util.Slog; import java.io.File; +import java.io.IOException; /** * Native libraries helper. @@ -141,4 +142,18 @@ public class NativeLibraryHelper { return deletedFiles; } + + // We don't care about the other return values for now. + private static final int BITCODE_PRESENT = 1; + + public static boolean hasRenderscriptBitcode(ApkHandle handle) throws IOException { + final int returnVal = hasRenderscriptBitcode(handle.apkHandle); + if (returnVal < 0) { + throw new IOException("Error scanning APK, code: " + returnVal); + } + + return (returnVal == BITCODE_PRESENT); + } + + private static native int hasRenderscriptBitcode(long apkHandle); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index df96488..7dbde69 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -34,6 +34,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.TreeMap; /** @@ -117,6 +118,24 @@ public class InputMethodSubtypeSwitchingController { + " mIsSystemLanguage=" + mIsSystemLanguage + "}"; } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof ImeSubtypeListItem) { + final ImeSubtypeListItem that = (ImeSubtypeListItem)o; + if (!Objects.equals(this.mImi, that.mImi)) { + return false; + } + if (this.mSubtypeId != that.mSubtypeId) { + return false; + } + return true; + } + return false; + } } private static class InputMethodAndSubtypeList { @@ -276,7 +295,7 @@ public class InputMethodSubtypeSwitchingController { private final List<ImeSubtypeListItem> mImeSubtypeList; private final int[] mUsageHistoryOfSubtypeListItemIndex; - public DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) { + private DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) { mImeSubtypeList = imeSubtypeListItems; mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()]; final int N = mImeSubtypeList.size(); @@ -347,15 +366,53 @@ public class InputMethodSubtypeSwitchingController { @VisibleForTesting public static class ControllerImpl { - // TODO: Switch to DynamicRotationList for smarter rotation. - private final StaticRotationList mSwitchingAwareSubtypeList; - private final StaticRotationList mSwitchingUnawareSubtypeList; - - public ControllerImpl(final List<ImeSubtypeListItem> sortedItems) { - mSwitchingAwareSubtypeList = new StaticRotationList(filterImeSubtypeList(sortedItems, - true /* supportsSwitchingToNextInputMethod */)); - mSwitchingUnawareSubtypeList = new StaticRotationList(filterImeSubtypeList(sortedItems, - false /* supportsSwitchingToNextInputMethod */)); + private final DynamicRotationList mSwitchingAwareRotationList; + private final StaticRotationList mSwitchingUnawareRotationList; + + public static ControllerImpl createFrom(final ControllerImpl currentInstance, + final List<ImeSubtypeListItem> sortedEnabledItems) { + DynamicRotationList switchingAwareRotationList = null; + { + final List<ImeSubtypeListItem> switchingAwareImeSubtypes = + filterImeSubtypeList(sortedEnabledItems, + true /* supportsSwitchingToNextInputMethod */); + if (currentInstance != null && + currentInstance.mSwitchingAwareRotationList != null && + Objects.equals(currentInstance.mSwitchingAwareRotationList.mImeSubtypeList, + switchingAwareImeSubtypes)) { + // Can reuse the current instance. + switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList; + } + if (switchingAwareRotationList == null) { + switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes); + } + } + + StaticRotationList switchingUnawareRotationList = null; + { + final List<ImeSubtypeListItem> switchingUnawareImeSubtypes = filterImeSubtypeList( + sortedEnabledItems, false /* supportsSwitchingToNextInputMethod */); + if (currentInstance != null && + currentInstance.mSwitchingUnawareRotationList != null && + Objects.equals( + currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList, + switchingUnawareImeSubtypes)) { + // Can reuse the current instance. + switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList; + } + if (switchingUnawareRotationList == null) { + switchingUnawareRotationList = + new StaticRotationList(switchingUnawareImeSubtypes); + } + } + + return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList); + } + + private ControllerImpl(final DynamicRotationList switchingAwareRotationList, + final StaticRotationList switchingUnawareRotationList) { + mSwitchingAwareRotationList = switchingAwareRotationList; + mSwitchingUnawareRotationList = switchingUnawareRotationList; } public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, @@ -364,10 +421,10 @@ public class InputMethodSubtypeSwitchingController { return null; } if (imi.supportsSwitchingToNextInputMethod()) { - return mSwitchingAwareSubtypeList.getNextInputMethodLocked(onlyCurrentIme, imi, + return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, subtype); } else { - return mSwitchingUnawareSubtypeList.getNextInputMethodLocked(onlyCurrentIme, imi, + return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, subtype); } } @@ -376,10 +433,9 @@ public class InputMethodSubtypeSwitchingController { if (imi == null) { return; } - // TODO: Enable the following code when DynamicRotationList is enabled. - // if (imi.supportsSwitchingToNextInputMethod()) { - // mSwitchingAwareSubtypeList.onUserAction(imi, subtype); - // } + if (imi.supportsSwitchingToNextInputMethod()) { + mSwitchingAwareRotationList.onUserAction(imi, subtype); + } } private static List<ImeSubtypeListItem> filterImeSubtypeList( @@ -424,7 +480,8 @@ public class InputMethodSubtypeSwitchingController { public void resetCircularListLocked(Context context) { mSubtypeList = new InputMethodAndSubtypeList(context, mSettings); - mController = new ControllerImpl(mSubtypeList.getSortedInputMethodAndSubtypeList()); + mController = ControllerImpl.createFrom(mController, + mSubtypeList.getSortedInputMethodAndSubtypeList()); } public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, 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 f22800c..a5421f5 100644 --- a/core/java/com/android/internal/policy/IKeyguardService.aidl +++ b/core/java/com/android/internal/policy/IKeyguardService.aidl @@ -60,6 +60,9 @@ interface IKeyguardService { /** * 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 fadeoutDuration); + oneway void startKeyguardExitAnimation(long startTime, long fadeoutDuration); } diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index a56fa36..d66ef83 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -169,6 +169,15 @@ public class ArrayUtils return false; } + public static boolean contains(long[] array, long value) { + for (long element : array) { + if (element == value) { + return true; + } + } + return false; + } + public static long total(long[] array) { long total = 0; for (long value : array) { @@ -229,6 +238,14 @@ public class ArrayUtils return array; } + /** + * Appends a new value to a copy of the array and returns the copy. If + * the value is already present, the original array is returned + * @param cur The original array, or null to represent an empty array. + * @param val The value to add. + * @return A new array that contains all of the values of the original array + * with the new value added, or the original array. + */ public static int[] appendInt(int[] cur, int val) { if (cur == null) { return new int[] { val }; @@ -264,4 +281,48 @@ public class ArrayUtils } return cur; } + + /** + * Appends a new value to a copy of the array and returns the copy. If + * the value is already present, the original array is returned + * @param cur The original array, or null to represent an empty array. + * @param val The value to add. + * @return A new array that contains all of the values of the original array + * with the new value added, or the original array. + */ + public static long[] appendLong(long[] cur, long val) { + if (cur == null) { + return new long[] { val }; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + return cur; + } + } + long[] ret = new long[N + 1]; + System.arraycopy(cur, 0, ret, 0, N); + ret[N] = val; + return ret; + } + + public static long[] removeLong(long[] cur, long val) { + if (cur == null) { + return null; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + long[] ret = new long[N - 1]; + if (i > 0) { + System.arraycopy(cur, 0, ret, 0, i); + } + if (i < (N - 1)) { + System.arraycopy(cur, i + 1, ret, i, N - i - 1); + } + return ret; + } + } + return cur; + } } 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/Protocol.java b/core/java/com/android/internal/util/Protocol.java index 81e67d8..af966b1 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -57,9 +57,9 @@ public class Protocol { public static final int BASE_DNS_PINGER = 0x00050000; public static final int BASE_NSD_MANAGER = 0x00060000; public static final int BASE_NETWORK_STATE_TRACKER = 0x00070000; - public static final int BASE_CONNECTIVITY_SERVICE = 0x00080000; + public static final int BASE_CONNECTIVITY_MANAGER = 0x00080000; public static final int BASE_NETWORK_AGENT = 0x00081000; public static final int BASE_NETWORK_MONITOR = 0x00082000; - public static final int BASE_CONNECTIVITY_MANAGER = 0x00083000; + public static final int BASE_NETWORK_FACTORY = 0x00083000; //TODO: define all used protocols } 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/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.mk b/core/jni/Android.mk index f446c3a..a1cd7f7 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -57,7 +57,6 @@ LOCAL_SRC_FILES:= \ android_view_KeyEvent.cpp \ android_view_KeyCharacterMap.cpp \ android_view_GraphicBuffer.cpp \ - android_view_GLRenderer.cpp \ android_view_GLES20Canvas.cpp \ android_view_HardwareLayer.cpp \ android_view_ThreadedRenderer.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index e069876..e0c5e96 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -130,7 +130,6 @@ extern int register_android_view_RenderNode(JNIEnv* env); extern int register_android_view_RenderNodeAnimator(JNIEnv* env); extern int register_android_view_GraphicBuffer(JNIEnv* env); extern int register_android_view_GLES20Canvas(JNIEnv* env); -extern int register_android_view_GLRenderer(JNIEnv* env); extern int register_android_view_HardwareLayer(JNIEnv* env); extern int register_android_view_ThreadedRenderer(JNIEnv* env); extern int register_android_view_Surface(JNIEnv* env); @@ -1214,7 +1213,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_view_RenderNodeAnimator), REG_JNI(register_android_view_GraphicBuffer), REG_JNI(register_android_view_GLES20Canvas), - REG_JNI(register_android_view_GLRenderer), REG_JNI(register_android_view_HardwareLayer), REG_JNI(register_android_view_ThreadedRenderer), REG_JNI(register_android_view_Surface), diff --git a/core/jni/android/graphics/Camera.cpp b/core/jni/android/graphics/Camera.cpp index ef57e3d..d17f46c 100644 --- a/core/jni/android/graphics/Camera.cpp +++ b/core/jni/android/graphics/Camera.cpp @@ -3,6 +3,8 @@ #include "SkCamera.h" +#include "GraphicsJNI.h" + static jfieldID gNativeInstanceFieldID; static void Camera_constructor(JNIEnv* env, jobject obj) { @@ -93,7 +95,7 @@ static void Camera_getMatrix(JNIEnv* env, jobject obj, jlong matrixHandle) { } static void Camera_applyToCanvas(JNIEnv* env, jobject obj, jlong canvasHandle) { - SkCanvas* native_canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* native_canvas = GraphicsJNI::getNativeCanvas(canvasHandle); jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); v->applyToCanvas((SkCanvas*)native_canvas); diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index f7acbd7..5fca582 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -42,22 +42,26 @@ #include <utils/Log.h> -static uint32_t get_thread_msec() { -#if defined(HAVE_POSIX_CLOCKS) - struct timespec tm; +namespace android { - clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tm); +// Holds an SkCanvas reference plus additional native data. +class NativeCanvasWrapper { +public: + NativeCanvasWrapper(SkCanvas* canvas) + : mCanvas(canvas) { } - return tm.tv_sec * 1000LL + tm.tv_nsec / 1000000; -#else - struct timeval tv; + SkCanvas* getCanvas() const { + return mCanvas.get(); + } - gettimeofday(&tv, NULL); - return tv.tv_sec * 1000LL + tv.tv_usec / 1000; -#endif -} + void setCanvas(SkCanvas* canvas) { + SkASSERT(canvas); + mCanvas.reset(canvas); + } -namespace android { +private: + SkAutoTUnref<SkCanvas> mCanvas; +}; class ClipCopier : public SkCanvas::ClipVisitor { public: @@ -86,27 +90,30 @@ static jboolean hasNonEmptyClip(const SkCanvas& canvas) { class SkCanvasGlue { public: - static void finalizer(JNIEnv* env, jobject clazz, jlong canvasHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); - canvas->unref(); + // Get the SkCanvas for a given native handle. + static inline SkCanvas* getNativeCanvas(jlong nativeHandle) { + SkASSERT(nativeHandle); + NativeCanvasWrapper* wrapper = reinterpret_cast<NativeCanvasWrapper*>(nativeHandle); + SkCanvas* canvas = wrapper->getCanvas(); + SkASSERT(canvas); + + return canvas; } - static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + // Construct an SkCanvas from the bitmap. + static SkCanvas* createCanvas(SkBitmap* bitmap) { if (bitmap) { - return reinterpret_cast<jlong>(new SkCanvas(*bitmap)); - } else { - // Create an empty bitmap device to prevent callers from crashing - // if they attempt to draw into this canvas. - SkBitmap emptyBitmap; - return reinterpret_cast<jlong>(new SkCanvas(emptyBitmap)); + return SkNEW_ARGS(SkCanvas, (*bitmap)); } + + // Create an empty bitmap device to prevent callers from crashing + // if they attempt to draw into this canvas. + SkBitmap emptyBitmap; + return new SkCanvas(emptyBitmap); } - static void copyCanvasState(JNIEnv* env, jobject clazz, - jlong srcCanvasHandle, jlong dstCanvasHandle) { - SkCanvas* srcCanvas = reinterpret_cast<SkCanvas*>(srcCanvasHandle); - SkCanvas* dstCanvas = reinterpret_cast<SkCanvas*>(dstCanvasHandle); + // Copy the canvas matrix & clip state. + static void copyCanvasState(SkCanvas* srcCanvas, SkCanvas* dstCanvas) { if (srcCanvas && dstCanvas) { dstCanvas->setMatrix(srcCanvas->getTotalMatrix()); if (NULL != srcCanvas->getDevice() && NULL != dstCanvas->getDevice()) { @@ -116,6 +123,42 @@ public: } } + // Native JNI handlers + static void finalizer(JNIEnv* env, jobject clazz, jlong nativeHandle) { + NativeCanvasWrapper* wrapper = reinterpret_cast<NativeCanvasWrapper*>(nativeHandle); + delete wrapper; + } + + // Native wrapper constructor used by Canvas(Bitmap) + static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) { + // No check - 0 is a valid bitmapHandle. + SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + SkCanvas* canvas = createCanvas(bitmap); + + return reinterpret_cast<jlong>(new NativeCanvasWrapper(canvas)); + } + + // Native wrapper constructor used by Canvas(native_canvas) + static jlong initCanvas(JNIEnv* env, jobject, jlong canvasHandle) { + SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + return reinterpret_cast<jlong>(new NativeCanvasWrapper(canvas)); + } + + // Set the given bitmap as the new draw target (wrapped in a new SkCanvas), + // optionally copying canvas matrix & clip state. + static void setBitmap(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, + jboolean copyState) { + NativeCanvasWrapper* wrapper = reinterpret_cast<NativeCanvasWrapper*>(canvasHandle); + SkCanvas* newCanvas = createCanvas(reinterpret_cast<SkBitmap*>(bitmapHandle)); + NPE_CHECK_RETURN_VOID(env, newCanvas); + + if (copyState == JNI_TRUE) { + copyCanvasState(wrapper->getCanvas(), newCanvas); + } + + // setCanvas() unrefs the old canvas. + wrapper->setCanvas(newCanvas); + } static void freeCaches(JNIEnv* env, jobject) { // these are called in no particular order @@ -163,7 +206,7 @@ public: static jint saveLayer(JNIEnv* env, jobject, jlong canvasHandle, jobject bounds, jlong paintHandle, jint flags) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect* bounds_ = NULL; SkRect storage; @@ -177,7 +220,7 @@ public: static jint saveLayer4F(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t, jfloat r, jfloat b, jlong paintHandle, jint flags) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect bounds; bounds.set(l, t, r, b); @@ -188,7 +231,7 @@ public: static jint saveLayerAlpha(JNIEnv* env, jobject, jlong canvasHandle, jobject bounds, jint alpha, jint flags) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect* bounds_ = NULL; SkRect storage; if (bounds != NULL) { @@ -203,7 +246,7 @@ public: static jint saveLayerAlpha4F(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t, jfloat r, jfloat b, jint alpha, jint flags) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect bounds; bounds.set(l, t, r, b); int result = canvas->saveLayerAlpha(&bounds, alpha, @@ -259,14 +302,14 @@ public: static void concat(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); canvas->concat(*matrix); } static void setMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); if (NULL == matrix) { canvas->resetMatrix(); @@ -318,8 +361,8 @@ public: static jboolean clipRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jint op) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect rect; - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); rect.set(left, top, right, bottom); canvas->clipRect(rect, static_cast<SkRegion::Op>(op)); return hasNonEmptyClip(*canvas); @@ -327,7 +370,7 @@ public: static jboolean clipPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle, jint op) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->clipPath(*reinterpret_cast<SkPath*>(pathHandle), static_cast<SkRegion::Op>(op)); return hasNonEmptyClip(*canvas); @@ -335,7 +378,7 @@ public: static jboolean clipRegion(JNIEnv* env, jobject, jlong canvasHandle, jlong deviceRgnHandle, jint op) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRegion* deviceRgn = reinterpret_cast<SkRegion*>(deviceRgnHandle); canvas->clipRegion(*deviceRgn, static_cast<SkRegion::Op>(op)); return hasNonEmptyClip(*canvas); @@ -343,13 +386,13 @@ public: static void setDrawFilter(JNIEnv* env, jobject, jlong canvasHandle, jlong filterHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle)); } static jboolean quickReject__RectF(JNIEnv* env, jobject, jlong canvasHandle, jobject rect) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect rect_; GraphicsJNI::jrectf_to_rect(env, rect, &rect_); bool result = canvas->quickReject(rect_); @@ -358,7 +401,7 @@ public: static jboolean quickReject__Path(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); bool result = canvas->quickReject(*reinterpret_cast<SkPath*>(pathHandle)); return result ? JNI_TRUE : JNI_FALSE; } @@ -366,7 +409,7 @@ public: static jboolean quickReject__FFFF(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, jfloat right, jfloat bottom) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect r; r.set(left, top, right, bottom); bool result = canvas->quickReject(r); @@ -375,32 +418,32 @@ public: static void drawRGB(JNIEnv* env, jobject, jlong canvasHandle, jint r, jint g, jint b) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->drawARGB(0xFF, r, g, b); } static void drawARGB(JNIEnv* env, jobject, jlong canvasHandle, jint a, jint r, jint g, jint b) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->drawARGB(a, r, g, b); } static void drawColor__I(JNIEnv* env, jobject, jlong canvasHandle, jint color) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->drawColor(color); } static void drawColor__II(JNIEnv* env, jobject, jlong canvasHandle, jint color, jint modeHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPorterDuff::Mode mode = static_cast<SkPorterDuff::Mode>(modeHandle); canvas->drawColor(color, SkPorterDuff::ToXfermodeMode(mode)); } static void drawPaint(JNIEnv* env, jobject, jlong canvasHandle, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawPaint(*paint); } @@ -461,14 +504,14 @@ public: static void drawLine__FFFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jfloat startX, jfloat startY, jfloat stopX, jfloat stopY, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawLine(startX, startY, stopX, stopY, *paint); } static void drawRect__RectFPaint(JNIEnv* env, jobject, jlong canvasHandle, jobject rect, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect rect_; GraphicsJNI::jrectf_to_rect(env, rect, &rect_); @@ -478,14 +521,14 @@ public: static void drawRect__FFFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawRectCoords(left, top, right, bottom, *paint); } static void drawOval(JNIEnv* env, jobject, jlong canvasHandle, jobject joval, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect oval; GraphicsJNI::jrectf_to_rect(env, joval, &oval); @@ -494,7 +537,7 @@ public: static void drawCircle(JNIEnv* env, jobject, jlong canvasHandle, jfloat cx, jfloat cy, jfloat radius, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawCircle(cx, cy, radius, *paint); } @@ -502,7 +545,7 @@ public: static void drawArc(JNIEnv* env, jobject, jlong canvasHandle, jobject joval, jfloat startAngle, jfloat sweepAngle, jboolean useCenter, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect oval; GraphicsJNI::jrectf_to_rect(env, joval, &oval); @@ -512,7 +555,7 @@ public: static void drawRoundRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat rx, jfloat ry, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); canvas->drawRoundRect(rect, rx, ry, *paint); @@ -520,7 +563,7 @@ public: static void drawPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPath* path = reinterpret_cast<SkPath*>(pathHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawPath(*path, *paint); @@ -531,7 +574,7 @@ public: jfloat left, jfloat top, jlong paintHandle, jint canvasDensity, jint screenDensity, jint bitmapDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -591,7 +634,7 @@ public: jlong bitmapHandle, jobject srcIRect, jobject dstRectF, jlong paintHandle, jint screenDensity, jint bitmapDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect dst; @@ -604,7 +647,7 @@ public: jlong bitmapHandle, jobject srcIRect, jobject dstRect, jlong paintHandle, jint screenDensity, jint bitmapDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect dst; @@ -616,9 +659,8 @@ public: static void drawBitmapArray(JNIEnv* env, jobject, jlong canvasHandle, jintArray jcolors, jint offset, jint stride, jfloat x, jfloat y, jint width, jint height, - jboolean hasAlpha, jlong paintHandle) - { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + jboolean hasAlpha, jlong paintHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkBitmap bitmap; bitmap.setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config : @@ -638,7 +680,7 @@ public: static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, jlong matrixHandle, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -649,7 +691,7 @@ public: jlong bitmapHandle, jint meshWidth, jint meshHeight, jfloatArray jverts, jint vertIndex, jintArray jcolors, jint colorIndex, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -759,7 +801,7 @@ public: jintArray jcolors, jint colorIndex, jshortArray jindices, jint indexIndex, jint indexCount, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkCanvas::VertexMode mode = static_cast<SkCanvas::VertexMode>(modeHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -799,7 +841,7 @@ public: jcharArray text, jint index, jint count, jfloat x, jfloat y, jint flags, jlong paintHandle, jlong typefaceHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); jchar* textArray = env->GetCharArrayElements(text, NULL); @@ -812,7 +854,7 @@ public: jint start, jint end, jfloat x, jfloat y, jint flags, jlong paintHandle, jlong typefaceHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); const jchar* textArray = env->GetStringChars(text, NULL); @@ -939,10 +981,10 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l } static void drawTextRun___CIIIIFFIPaintTypeface( - JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, - jint count, jint contextIndex, jint contextCount, - jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, + jint count, jint contextIndex, jint contextCount, + jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); @@ -953,10 +995,10 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l } static void drawTextRun__StringIIIIFFIPaintTypeface( - JNIEnv* env, jobject obj, jlong canvasHandle, jstring text, jint start, - jint end, jint contextStart, jint contextEnd, - jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + JNIEnv* env, jobject obj, jlong canvasHandle, jstring text, jint start, + jint end, jint contextStart, jint contextEnd, + jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); @@ -971,7 +1013,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static void drawPosText___CII_FPaint(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, jint count, jfloatArray pos, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); jchar* textArray = text ? env->GetCharArrayElements(text, NULL) : NULL; jsize textCount = text ? env->GetArrayLength(text) : NULL; @@ -1002,7 +1044,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l jlong canvasHandle, jstring text, jfloatArray pos, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); const void* text_ = text ? env->GetStringChars(text, NULL) : NULL; int byteLength = text ? env->GetStringLength(text) : 0; @@ -1032,7 +1074,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, jint count, jlong pathHandle, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPath* path = reinterpret_cast<SkPath*>(pathHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -1045,7 +1087,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jstring text, jlong pathHandle, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPath* path = reinterpret_cast<SkPath*>(pathHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); const jchar* text_ = env->GetStringChars(text, NULL); @@ -1084,7 +1126,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static jboolean getClipBounds(JNIEnv* env, jobject, jlong canvasHandle, jobject bounds) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect r; SkIRect ir; bool result = getHardClipBounds(canvas, &r); @@ -1100,7 +1142,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static void getCTM(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); *matrix = canvas->getTotalMatrix(); } @@ -1108,8 +1150,9 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static JNINativeMethod gCanvasMethods[] = { {"finalizer", "(J)V", (void*) SkCanvasGlue::finalizer}, - {"initRaster","(J)J", (void*) SkCanvasGlue::initRaster}, - {"copyNativeCanvasState","(JJ)V", (void*) SkCanvasGlue::copyCanvasState}, + {"initRaster", "(J)J", (void*) SkCanvasGlue::initRaster}, + {"initCanvas", "(J)J", (void*) SkCanvasGlue::initCanvas}, + {"native_setBitmap", "(JJZ)V", (void*) SkCanvasGlue::setBitmap}, {"isOpaque","()Z", (void*) SkCanvasGlue::isOpaque}, {"getWidth","()I", (void*) SkCanvasGlue::getWidth}, {"getHeight","()I", (void*) SkCanvasGlue::getHeight}, @@ -1224,4 +1267,11 @@ int register_android_graphics_Canvas(JNIEnv* env) { return result; } +} // namespace android + +// GraphicsJNI helper for external clients. +// We keep the implementation here to avoid exposing NativeCanvasWrapper +// externally. +SkCanvas* GraphicsJNI::getNativeCanvas(jlong nativeHandle) { + return android::SkCanvasGlue::getNativeCanvas(nativeHandle); } diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index dce185d..64ad223 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -320,7 +320,7 @@ SkCanvas* GraphicsJNI::getNativeCanvas(JNIEnv* env, jobject canvas) { SkASSERT(canvas); SkASSERT(env->IsInstanceOf(canvas, gCanvas_class)); jlong canvasHandle = env->GetLongField(canvas, gCanvas_nativeInstanceID); - SkCanvas* c = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* c = getNativeCanvas(canvasHandle); SkASSERT(c); return c; } @@ -698,7 +698,7 @@ int register_android_graphics_Graphics(JNIEnv* env) "nativeInt", "I"); gCanvas_class = make_globalref(env, "android/graphics/Canvas"); - gCanvas_nativeInstanceID = getFieldIDCheck(env, gCanvas_class, "mNativeCanvas", "J"); + gCanvas_nativeInstanceID = getFieldIDCheck(env, gCanvas_class, "mNativeCanvasWrapper", "J"); gPaint_class = make_globalref(env, "android/graphics/Paint"); gPaint_nativeInstanceID = getFieldIDCheck(env, gPaint_class, "mNativePaint", "J"); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index db7b6d9..73dd11b 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -45,6 +45,7 @@ public: static SkPoint* jpointf_to_point(JNIEnv*, jobject jpointf, SkPoint* point); static void point_to_jpointf(const SkPoint& point, JNIEnv*, jobject jpointf); + static SkCanvas* getNativeCanvas(jlong nativeHandle); static SkCanvas* getNativeCanvas(JNIEnv*, jobject canvas); static SkPaint* getNativePaint(JNIEnv*, jobject paint); static android::TypefaceImpl* getNativeTypeface(JNIEnv*, jobject paint); 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/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp index 855d267..ab5bdb0 100644 --- a/core/jni/android/graphics/NinePatch.cpp +++ b/core/jni/android/graphics/NinePatch.cpp @@ -119,7 +119,7 @@ public: static void drawF(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRectF, jlong bitmapHandle, jlong chunkHandle, jlong paintHandle, jint destDensity, jint srcDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle); const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -138,7 +138,7 @@ public: static void drawI(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRect, jlong bitmapHandle, jlong chunkHandle, jlong paintHandle, jint destDensity, jint srcDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle); const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp index bac8ef7..a8a3dae 100644 --- a/core/jni/android/graphics/Picture.cpp +++ b/core/jni/android/graphics/Picture.cpp @@ -56,7 +56,7 @@ public: static void draw(JNIEnv* env, jobject, jlong canvasHandle, jlong pictureHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle); SkPicture* picture = reinterpret_cast<SkPicture*>(pictureHandle); SkASSERT(canvas); SkASSERT(picture); 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..ecdfeb2 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -91,53 +91,9 @@ static struct { } gRectClassInfo; // ---------------------------------------------------------------------------- -// Caching -// ---------------------------------------------------------------------------- - -static void android_view_GLES20Canvas_flushCaches(JNIEnv* env, jobject clazz, - jint mode) { - if (Caches::hasInstance()) { - Caches::getInstance().flush(static_cast<Caches::FlushMode>(mode)); - } -} - -static jboolean android_view_GLES20Canvas_initCaches(JNIEnv* env, jobject clazz) { - if (Caches::hasInstance()) { - return Caches::getInstance().init() ? JNI_TRUE : JNI_FALSE; - } - return JNI_FALSE; -} - -static void android_view_GLES20Canvas_terminateCaches(JNIEnv* env, jobject clazz) { - if (Caches::hasInstance()) { - Caches::getInstance().terminate(); - } -} - -// ---------------------------------------------------------------------------- -// Caching -// ---------------------------------------------------------------------------- - -static void android_view_GLES20Canvas_initAtlas(JNIEnv* env, jobject clazz, - jobject graphicBuffer, jlongArray atlasMapArray, jint count) { - - sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer); - jlong* jAtlasMap = env->GetLongArrayElements(atlasMapArray, NULL); - Caches::getInstance().assetAtlas.init(buffer, jAtlasMap, count); - env->ReleaseLongArrayElements(atlasMapArray, jAtlasMap, 0); -} - -// ---------------------------------------------------------------------------- // Constructors // ---------------------------------------------------------------------------- -static jlong android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject clazz) { - RENDERER_LOGD("Create OpenGLRenderer"); - OpenGLRenderer* renderer = new OpenGLRenderer(); - renderer->initProperties(); - return reinterpret_cast<jlong>(renderer); -} - static void android_view_GLES20Canvas_destroyRenderer(JNIEnv* env, jobject clazz, jlong rendererPtr) { OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); @@ -174,10 +130,6 @@ static void android_view_GLES20Canvas_finish(JNIEnv* env, jobject clazz, renderer->finish(); } -static jint android_view_GLES20Canvas_getStencilSize(JNIEnv* env, jobject clazz) { - return Stencil::getStencilSize(); -} - static void android_view_GLES20Canvas_setProperty(JNIEnv* env, jobject clazz, jstring name, jstring value) { if (!Caches::hasInstance()) { @@ -373,7 +325,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 +339,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 +382,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, @@ -577,15 +529,6 @@ static void android_view_GLES20Canvas_drawRegionAsRects(JNIEnv* env, jobject cla } } -static void android_view_GLES20Canvas_drawRects(JNIEnv* env, jobject clazz, - jlong rendererPtr, jfloatArray rects, jint count, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - jfloat* storage = env->GetFloatArrayElements(rects, NULL); - SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); - renderer->drawRects(storage, count, paint); - env->ReleaseFloatArrayElements(rects, storage, 0); -} - static void android_view_GLES20Canvas_drawPoints(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloatArray points, jint offset, jint count, jlong paintPtr) { OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); @@ -924,39 +867,6 @@ static void android_view_GLES20Canvas_drawLayer(JNIEnv* env, jobject clazz, renderer->drawLayer(layer, x, y); } -static jboolean android_view_GLES20Canvas_copyLayer(JNIEnv* env, jobject clazz, - jlong layerPtr, jlong bitmapPtr) { - Layer* layer = reinterpret_cast<Layer*>(layerPtr); - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapPtr); - return LayerRenderer::copyLayer(layer, bitmap); -} - -static void android_view_GLES20Canvas_pushLayerUpdate(JNIEnv* env, jobject clazz, - jlong rendererPtr, jlong layerPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - Layer* layer = reinterpret_cast<Layer*>(layerPtr); - renderer->pushLayerUpdate(layer); -} - -static void android_view_GLES20Canvas_cancelLayerUpdate(JNIEnv* env, jobject clazz, - jlong rendererPtr, jlong layerPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - Layer* layer = reinterpret_cast<Layer*>(layerPtr); - renderer->cancelLayerUpdate(layer); -} - -static void android_view_GLES20Canvas_clearLayerUpdates(JNIEnv* env, jobject clazz, - jlong rendererPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - renderer->clearLayerUpdates(); -} - -static void android_view_GLES20Canvas_flushLayerUpdates(JNIEnv* env, jobject clazz, - jlong rendererPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - renderer->flushLayerUpdates(); -} - #endif // USE_OPENGL_RENDERER // ---------------------------------------------------------------------------- @@ -1001,14 +911,7 @@ static JNINativeMethod gMethods[] = { { "nIsAvailable", "()Z", (void*) android_view_GLES20Canvas_isAvailable }, #ifdef USE_OPENGL_RENDERER - { "nFlushCaches", "(I)V", (void*) android_view_GLES20Canvas_flushCaches }, - { "nInitCaches", "()Z", (void*) android_view_GLES20Canvas_initCaches }, - { "nTerminateCaches", "()V", (void*) android_view_GLES20Canvas_terminateCaches }, - { "nInitAtlas", "(Landroid/view/GraphicBuffer;[JI)V", - (void*) android_view_GLES20Canvas_initAtlas }, - - { "nCreateRenderer", "()J", (void*) android_view_GLES20Canvas_createRenderer }, { "nDestroyRenderer", "(J)V", (void*) android_view_GLES20Canvas_destroyRenderer }, { "nSetViewport", "(JII)V", (void*) android_view_GLES20Canvas_setViewport }, { "nPrepare", "(JZ)I", (void*) android_view_GLES20Canvas_prepare }, @@ -1017,9 +920,6 @@ static JNINativeMethod gMethods[] = { { "nSetProperty", "(Ljava/lang/String;Ljava/lang/String;)V", (void*) android_view_GLES20Canvas_setProperty }, - - { "nGetStencilSize", "()I", (void*) android_view_GLES20Canvas_getStencilSize }, - { "nCallDrawGLFunction", "(JJ)I", (void*) android_view_GLES20Canvas_callDrawGLFunction }, { "nSave", "(JI)I", (void*) android_view_GLES20Canvas_save }, @@ -1059,7 +959,6 @@ static JNINativeMethod gMethods[] = { { "nDrawColor", "(JII)V", (void*) android_view_GLES20Canvas_drawColor }, { "nDrawRect", "(JFFFFJ)V", (void*) android_view_GLES20Canvas_drawRect }, { "nDrawRects", "(JJJ)V", (void*) android_view_GLES20Canvas_drawRegionAsRects }, - { "nDrawRects", "(J[FIJ)V", (void*) android_view_GLES20Canvas_drawRects }, { "nDrawRoundRect", "(JFFFFFFJ)V", (void*) android_view_GLES20Canvas_drawRoundRect }, { "nDrawCircle", "(JFFFJ)V", (void*) android_view_GLES20Canvas_drawCircle }, { "nDrawCircle", "(JJJJJ)V", (void*) android_view_GLES20Canvas_drawCircleProps }, @@ -1099,11 +998,6 @@ static JNINativeMethod gMethods[] = { { "nCreateDisplayListRenderer", "()J", (void*) android_view_GLES20Canvas_createDisplayListRenderer }, { "nDrawLayer", "(JJFF)V", (void*) android_view_GLES20Canvas_drawLayer }, - { "nCopyLayer", "(JJ)Z", (void*) android_view_GLES20Canvas_copyLayer }, - { "nClearLayerUpdates", "(J)V", (void*) android_view_GLES20Canvas_clearLayerUpdates }, - { "nFlushLayerUpdates", "(J)V", (void*) android_view_GLES20Canvas_flushLayerUpdates }, - { "nPushLayerUpdate", "(JJ)V", (void*) android_view_GLES20Canvas_pushLayerUpdate }, - { "nCancelLayerUpdate", "(JJ)V", (void*) android_view_GLES20Canvas_cancelLayerUpdate }, { "nGetMaximumTextureWidth", "()I", (void*) android_view_GLES20Canvas_getMaxTextureWidth }, { "nGetMaximumTextureHeight", "()I", (void*) android_view_GLES20Canvas_getMaxTextureHeight }, diff --git a/core/jni/android_view_GLRenderer.cpp b/core/jni/android_view_GLRenderer.cpp deleted file mode 100644 index d0269a3..0000000 --- a/core/jni/android_view_GLRenderer.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "GLRenderer" - -#include "jni.h" -#include <nativehelper/JNIHelp.h> -#include <android_runtime/AndroidRuntime.h> - -#include <EGL/egl.h> -#include <EGL/eglext.h> -#include <EGL/egl_cache.h> - -#include <utils/Timers.h> - -#include <private/hwui/DrawGlInfo.h> - -#include <Caches.h> -#include <Extensions.h> -#include <LayerRenderer.h> -#include <RenderNode.h> - -#ifdef USE_OPENGL_RENDERER - EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface); -#endif - -namespace android { - -/** - * Note: OpenGLRenderer JNI layer is generated and compiled only on supported - * devices. This means all the logic must be compiled only when the - * preprocessor variable USE_OPENGL_RENDERER is defined. - */ -#ifdef USE_OPENGL_RENDERER - -// ---------------------------------------------------------------------------- -// Defines -// ---------------------------------------------------------------------------- - -// Debug -#define DEBUG_RENDERER 0 - -// Debug -#if DEBUG_RENDERER - #define RENDERER_LOGD(...) ALOGD(__VA_ARGS__) -#else - #define RENDERER_LOGD(...) -#endif - -// ---------------------------------------------------------------------------- -// Surface and display management -// ---------------------------------------------------------------------------- - -static jboolean android_view_GLRenderer_preserveBackBuffer(JNIEnv* env, jobject clazz) { - EGLDisplay display = eglGetCurrentDisplay(); - EGLSurface surface = eglGetCurrentSurface(EGL_DRAW); - - eglGetError(); - eglSurfaceAttrib(display, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED); - - EGLint error = eglGetError(); - if (error != EGL_SUCCESS) { - RENDERER_LOGD("Could not enable buffer preserved swap behavior (%x)", error); - } - - return error == EGL_SUCCESS; -} - -static jboolean android_view_GLRenderer_isBackBufferPreserved(JNIEnv* env, jobject clazz) { - EGLDisplay display = eglGetCurrentDisplay(); - EGLSurface surface = eglGetCurrentSurface(EGL_DRAW); - EGLint value; - - eglGetError(); - eglQuerySurface(display, surface, EGL_SWAP_BEHAVIOR, &value); - - EGLint error = eglGetError(); - if (error != EGL_SUCCESS) { - RENDERER_LOGD("Could not query buffer preserved swap behavior (%x)", error); - } - - return error == EGL_SUCCESS && value == EGL_BUFFER_PRESERVED; -} - -// ---------------------------------------------------------------------------- -// Tracing and debugging -// ---------------------------------------------------------------------------- - -static bool android_view_GLRenderer_loadProperties(JNIEnv* env, jobject clazz) { - if (uirenderer::Caches::hasInstance()) { - return uirenderer::Caches::getInstance().initProperties(); - } - return false; -} - -static void android_view_GLRenderer_beginFrame(JNIEnv* env, jobject clazz, - jintArray size) { - - EGLDisplay display = eglGetCurrentDisplay(); - EGLSurface surface = eglGetCurrentSurface(EGL_DRAW); - - if (size) { - EGLint value; - jint* storage = env->GetIntArrayElements(size, NULL); - - eglQuerySurface(display, surface, EGL_WIDTH, &value); - storage[0] = value; - - eglQuerySurface(display, surface, EGL_HEIGHT, &value); - storage[1] = value; - - env->ReleaseIntArrayElements(size, storage, 0); - } - - eglBeginFrame(display, surface); -} - -static jlong android_view_GLRenderer_getSystemTime(JNIEnv* env, jobject clazz) { - if (uirenderer::Extensions::getInstance().hasNvSystemTime()) { - return eglGetSystemTimeNV(); - } - return systemTime(SYSTEM_TIME_MONOTONIC); -} - -static void android_view_GLRenderer_destroyLayer(JNIEnv* env, jobject clazz, - jlong layerPtr) { - using namespace android::uirenderer; - Layer* layer = reinterpret_cast<Layer*>(layerPtr); - LayerRenderer::destroyLayer(layer); -} - -static void android_view_GLRenderer_prepareTree(JNIEnv* env, jobject clazz, - jlong renderNodePtr) { - using namespace android::uirenderer; - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - TreeInfo ignoredInfo; - renderNode->prepareTree(ignoredInfo); -} - -static void android_view_GLRenderer_invokeFunctor(JNIEnv* env, jobject clazz, - jlong functorPtr, jboolean hasContext) { - using namespace android::uirenderer; - Functor* functor = reinterpret_cast<Functor*>(functorPtr); - DrawGlInfo::Mode mode = hasContext ? DrawGlInfo::kModeProcess : DrawGlInfo::kModeProcessNoContext; - (*functor)(mode, NULL); -} - -#endif // USE_OPENGL_RENDERER - -// ---------------------------------------------------------------------------- -// Shaders -// ---------------------------------------------------------------------------- - -static void android_view_GLRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz, - jstring diskCachePath) { - - const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL); - egl_cache_t::get()->setCacheFilename(cacheArray); - env->ReleaseStringUTFChars(diskCachePath, cacheArray); -} - -// ---------------------------------------------------------------------------- -// JNI Glue -// ---------------------------------------------------------------------------- - -const char* const kClassPathName = "android/view/GLRenderer"; - -static JNINativeMethod gMethods[] = { -#ifdef USE_OPENGL_RENDERER - { "isBackBufferPreserved", "()Z", (void*) android_view_GLRenderer_isBackBufferPreserved }, - { "preserveBackBuffer", "()Z", (void*) android_view_GLRenderer_preserveBackBuffer }, - { "loadProperties", "()Z", (void*) android_view_GLRenderer_loadProperties }, - - { "beginFrame", "([I)V", (void*) android_view_GLRenderer_beginFrame }, - - { "getSystemTime", "()J", (void*) android_view_GLRenderer_getSystemTime }, - { "nDestroyLayer", "(J)V", (void*) android_view_GLRenderer_destroyLayer }, - { "nPrepareTree", "(J)V", (void*) android_view_GLRenderer_prepareTree }, - { "nInvokeFunctor", "(JZ)V", (void*) android_view_GLRenderer_invokeFunctor }, -#endif - - { "setupShadersDiskCache", "(Ljava/lang/String;)V", - (void*) android_view_GLRenderer_setupShadersDiskCache }, -}; - -int register_android_view_GLRenderer(JNIEnv* env) { - return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); -} - -}; diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp index 8a426ac..0210bd9 100644 --- a/core/jni/android_view_GraphicBuffer.cpp +++ b/core/jni/android_view_GraphicBuffer.cpp @@ -21,6 +21,7 @@ #include "android_os_Parcel.h" #include "android_view_GraphicBuffer.h" +#include "android/graphics/GraphicsJNI.h" #include <android_runtime/AndroidRuntime.h> @@ -75,7 +76,7 @@ static struct { static struct { jfieldID mSurfaceFormat; - jmethodID safeCanvasSwap; + jmethodID setNativeBitmap; } gCanvasClassInfo; #define GET_INT(object, field) \ @@ -197,12 +198,11 @@ static jboolean android_view_GraphicBuffer_lockCanvas(JNIEnv* env, jobject, } SET_INT(canvas, gCanvasClassInfo.mSurfaceFormat, buffer->getPixelFormat()); - - SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap)); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(&bitmap)); SkRect clipRect; clipRect.set(rect.left, rect.top, rect.right, rect.bottom); + SkCanvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas); nativeCanvas->clipRect(clipRect); if (dirtyRect) { @@ -218,8 +218,7 @@ static jboolean android_view_GraphicBuffer_unlockCanvasAndPost(JNIEnv* env, jobj GraphicBufferWrapper* wrapper = reinterpret_cast<GraphicBufferWrapper*>(wrapperHandle); - SkCanvas* nativeCanvas = SkNEW(SkCanvas); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, (jlong)0); if (wrapper) { status_t status = wrapper->buffer->unlock(); @@ -319,7 +318,7 @@ int register_android_view_GraphicBuffer(JNIEnv* env) { FIND_CLASS(clazz, "android/graphics/Canvas"); GET_FIELD_ID(gCanvasClassInfo.mSurfaceFormat, clazz, "mSurfaceFormat", "I"); - GET_METHOD_ID(gCanvasClassInfo.safeCanvasSwap, clazz, "safeCanvasSwap", "(JZ)V"); + GET_METHOD_ID(gCanvasClassInfo.setNativeBitmap, clazz, "setNativeBitmap", "(J)V"); return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); } diff --git a/core/jni/android_view_HardwareLayer.cpp b/core/jni/android_view_HardwareLayer.cpp index b2f17de..879836d 100644 --- a/core/jni/android_view_HardwareLayer.cpp +++ b/core/jni/android_view_HardwareLayer.cpp @@ -43,41 +43,12 @@ using namespace uirenderer; #ifdef USE_OPENGL_RENDERER -static jlong android_view_HardwareLayer_createTextureLayer(JNIEnv* env, jobject clazz) { - Layer* layer = LayerRenderer::createTextureLayer(); - if (!layer) return 0; - - return reinterpret_cast<jlong>( new DeferredLayerUpdater(layer) ); -} - -static jlong android_view_HardwareLayer_createRenderLayer(JNIEnv* env, jobject clazz, - jint width, jint height) { - 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) ); -} - static void android_view_HardwareLayer_onTextureDestroyed(JNIEnv* env, jobject clazz, jlong layerUpdaterPtr) { DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); 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); @@ -154,11 +125,7 @@ const char* const kClassPathName = "android/view/HardwareLayer"; static JNINativeMethod gMethods[] = { #ifdef USE_OPENGL_RENDERER - { "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_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp index e19ce36..d689864 100644 --- a/core/jni/android_view_RenderNodeAnimator.cpp +++ b/core/jni/android_view_RenderNodeAnimator.cpp @@ -116,6 +116,11 @@ static jlong createCanvasPropertyPaintAnimator(JNIEnv* env, jobject clazz, return reinterpret_cast<jlong>( animator ); } +static void setStartValue(JNIEnv* env, jobject clazz, jlong animatorPtr, jfloat startValue) { + BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); + animator->setStartValue(startValue); +} + static void setDuration(JNIEnv* env, jobject clazz, jlong animatorPtr, jlong duration) { LOG_ALWAYS_FATAL_IF(duration < 0, "Duration cannot be negative"); BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); @@ -157,6 +162,7 @@ static JNINativeMethod gMethods[] = { { "nCreateAnimator", "(Ljava/lang/ref/WeakReference;IF)J", (void*) createAnimator }, { "nCreateCanvasPropertyFloatAnimator", "(Ljava/lang/ref/WeakReference;JF)J", (void*) createCanvasPropertyFloatAnimator }, { "nCreateCanvasPropertyPaintAnimator", "(Ljava/lang/ref/WeakReference;JIF)J", (void*) createCanvasPropertyPaintAnimator }, + { "nSetStartValue", "(JF)V", (void*) setStartValue }, { "nSetDuration", "(JJ)V", (void*) setDuration }, { "nGetDuration", "(J)J", (void*) getDuration }, { "nSetStartDelay", "(JJ)V", (void*) setStartDelay }, diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 6c9d060..11f87cc 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -70,7 +70,7 @@ static struct { static struct { jfieldID mSurfaceFormat; - jmethodID safeCanvasSwap; + jmethodID setNativeBitmap; } gCanvasClassInfo; // ---------------------------------------------------------------------------- @@ -232,10 +232,11 @@ static jlong nativeLockCanvas(JNIEnv* env, jclass clazz, bitmap.setPixels(NULL); } - SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap)); - env->CallVoidMethod(canvasObj, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + env->CallVoidMethod(canvasObj, gCanvasClassInfo.setNativeBitmap, + reinterpret_cast<jlong>(&bitmap)); if (dirtyRectPtr) { + SkCanvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj); nativeCanvas->clipRect( SkRect::Make(reinterpret_cast<const SkIRect&>(dirtyRect)) ); } @@ -262,8 +263,7 @@ static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz, } // detach the canvas from the surface - SkCanvas* nativeCanvas = SkNEW(SkCanvas); - env->CallVoidMethod(canvasObj, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + env->CallVoidMethod(canvasObj, gCanvasClassInfo.setNativeBitmap, (jlong)0); // unlock surface status_t err = surface->unlockAndPost(); @@ -375,7 +375,7 @@ int register_android_view_Surface(JNIEnv* env) clazz = env->FindClass("android/graphics/Canvas"); gCanvasClassInfo.mSurfaceFormat = env->GetFieldID(clazz, "mSurfaceFormat", "I"); - gCanvasClassInfo.safeCanvasSwap = env->GetMethodID(clazz, "safeCanvasSwap", "(JZ)V"); + gCanvasClassInfo.setNativeBitmap = env->GetMethodID(clazz, "setNativeBitmap", "(J)V"); clazz = env->FindClass("android/graphics/Rect"); gRectClassInfo.left = env->GetFieldID(clazz, "left", "I"); diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp index 9258543..c1ab515 100644 --- a/core/jni/android_view_TextureView.cpp +++ b/core/jni/android_view_TextureView.cpp @@ -29,6 +29,8 @@ #include <SkCanvas.h> #include <SkImage.h> +#include "android/graphics/GraphicsJNI.h" + namespace android { // ---------------------------------------------------------------------------- @@ -45,7 +47,7 @@ static struct { static struct { jfieldID mSurfaceFormat; - jmethodID safeCanvasSwap; + jmethodID setNativeBitmap; } gCanvasClassInfo; static struct { @@ -159,12 +161,11 @@ static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, } SET_INT(canvas, gCanvasClassInfo.mSurfaceFormat, buffer.format); - - SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap)); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(&bitmap)); SkRect clipRect; clipRect.set(rect.left, rect.top, rect.right, rect.bottom); + SkCanvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas); nativeCanvas->clipRect(clipRect); if (dirtyRect) { @@ -178,8 +179,7 @@ static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, static void android_view_TextureView_unlockCanvasAndPost(JNIEnv* env, jobject, jlong nativeWindow, jobject canvas) { - SkCanvas* nativeCanvas = SkNEW(SkCanvas); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, (jlong)0); if (nativeWindow) { sp<ANativeWindow> window((ANativeWindow*) nativeWindow); @@ -228,7 +228,7 @@ int register_android_view_TextureView(JNIEnv* env) { FIND_CLASS(clazz, "android/graphics/Canvas"); GET_FIELD_ID(gCanvasClassInfo.mSurfaceFormat, clazz, "mSurfaceFormat", "I"); - GET_METHOD_ID(gCanvasClassInfo.safeCanvasSwap, clazz, "safeCanvasSwap", "(JZ)V"); + GET_METHOD_ID(gCanvasClassInfo.setNativeBitmap, clazz, "setNativeBitmap", "(J)V"); FIND_CLASS(clazz, "android/view/TextureView"); GET_FIELD_ID(gTextureViewClassInfo.nativeWindow, clazz, "mNativeWindow", "J"); diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index bd016fd..a550649 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -22,6 +22,10 @@ #include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <EGL/egl_cache.h> + #include <utils/StrongPointer.h> #include <android_runtime/android_view_Surface.h> #include <system/window.h> @@ -287,11 +291,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->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->destroyLayer(layer); + proxy->cancelLayerUpdate(layer); } static void android_view_ThreadedRenderer_flushCaches(JNIEnv* env, jobject clazz, @@ -322,6 +333,18 @@ static void android_view_ThreadedRenderer_dumpProfileInfo(JNIEnv* env, jobject c #endif // ---------------------------------------------------------------------------- +// Shaders +// ---------------------------------------------------------------------------- + +static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz, + jstring diskCachePath) { + + const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL); + egl_cache_t::get()->setCacheFilename(cacheArray); + env->ReleaseStringUTFChars(diskCachePath, cacheArray); +} + +// ---------------------------------------------------------------------------- // JNI Glue // ---------------------------------------------------------------------------- @@ -347,12 +370,15 @@ static JNINativeMethod gMethods[] = { { "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 + { "setupShadersDiskCache", "(Ljava/lang/String;)V", + (void*) android_view_ThreadedRenderer_setupShadersDiskCache }, }; int register_android_view_ThreadedRenderer(JNIEnv* env) { diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index 230658f..e55e4ea 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -46,6 +46,9 @@ #define LIB_SUFFIX ".so" #define LIB_SUFFIX_LEN (sizeof(LIB_SUFFIX) - 1) +#define RS_BITCODE_SUFFIX ".bc" +#define RS_BITCODE_SUFFIX_LEN (sizeof(RS_BITCODE_SUFFIX) -1) + #define GDBSERVER "gdbserver" #define GDBSERVER_LEN (sizeof(GDBSERVER) - 1) @@ -486,6 +489,42 @@ com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, j return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch); } +enum bitcode_scan_result_t { + APK_SCAN_ERROR = -1, + NO_BITCODE_PRESENT = 0, + BITCODE_PRESENT = 1, +}; + +static jint +com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode(JNIEnv *env, jclass clazz, + jlong apkHandle) { + ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); + void* cookie = NULL; + if (!zipFile->startIteration(&cookie)) { + return APK_SCAN_ERROR; + } + + char fileName[PATH_MAX]; + ZipEntryRO next = NULL; + while ((next = zipFile->nextEntry(cookie)) != NULL) { + if (zipFile->getEntryFileName(next, fileName, sizeof(fileName))) { + continue; + } + + const size_t fileNameLen = strlen(fileName); + const char* lastSlash = strrchr(fileName, '/'); + const char* baseName = (lastSlash == NULL) ? fileName : fileName + 1; + if (!strncmp(fileName + fileNameLen - RS_BITCODE_SUFFIX_LEN, RS_BITCODE_SUFFIX, + RS_BITCODE_SUFFIX_LEN) && isFilenameSafe(baseName)) { + zipFile->endIteration(cookie); + return BITCODE_PRESENT; + } + } + + zipFile->endIteration(cookie); + return NO_BITCODE_PRESENT; +} + static jlong com_android_internal_content_NativeLibraryHelper_openApk(JNIEnv *env, jclass, jstring apkPath) { @@ -517,6 +556,8 @@ static JNINativeMethod gMethods[] = { {"nativeFindSupportedAbi", "(J[Ljava/lang/String;)I", (void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi}, + {"hasRenderscriptBitcode", "(J)I", + (void *)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode}, }; 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 4a956d7..7e212be 100644 --- a/core/res/res/anim/lock_screen_behind_enter.xml +++ b/core/res/res/anim/lock_screen_behind_enter.xml @@ -18,10 +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="1.0" android:toAlpha="1.0" + android:fromAlpha="0.0" android:toAlpha="1.0" android:fillEnabled="true" android:fillBefore="true" - android:interpolator="@interpolator/decelerate_quint" - android:duration="0"/> + 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 f7a6a65..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_mediumAnimTime" - android:duration="@android:integer/config_shortAnimTime"/> -</set> diff --git a/core/res/res/anim/voice_activity_open_enter.xml b/core/res/res/anim/voice_activity_open_enter.xml index 57fba2a..ce7a4f9 100644 --- a/core/res/res/anim/voice_activity_open_enter.xml +++ b/core/res/res/anim/voice_activity_open_enter.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* //device/apps/common/res/anim/fade_in.xml ** ** Copyright 2007, The Android Open Source Project ** diff --git a/core/res/res/anim/voice_layer_enter.xml b/core/res/res/anim/voice_layer_enter.xml index 57fba2a..ce7a4f9 100644 --- a/core/res/res/anim/voice_layer_enter.xml +++ b/core/res/res/anim/voice_layer_enter.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* //device/apps/common/res/anim/fade_in.xml ** ** Copyright 2007, The Android Open Source Project ** 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-hdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-hdpi/ic_lock_bugreport_alpha.png Binary files differdeleted file mode 100644 index ba5bd01..0000000 --- a/core/res/res/drawable-hdpi/ic_lock_bugreport_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-mdpi/ic_lock_bugreport_alpha.png Binary files differdeleted file mode 100644 index 4e2612d..0000000 --- a/core/res/res/drawable-mdpi/ic_lock_bugreport_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-xhdpi/ic_lock_bugreport_alpha.png Binary files differdeleted file mode 100644 index e6ca1ea..0000000 --- a/core/res/res/drawable-xhdpi/ic_lock_bugreport_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-xxhdpi/ic_lock_bugreport_alpha.png Binary files differdeleted file mode 100644 index d6018dd..0000000 --- a/core/res/res/drawable-xxhdpi/ic_lock_bugreport_alpha.png +++ /dev/null diff --git a/core/res/res/drawable/ic_lock_bugreport.xml b/core/res/res/drawable/ic_lock_bugreport.xml index a3f82ce..b93a09a 100644 --- a/core/res/res/drawable/ic_lock_bugreport.xml +++ b/core/res/res/drawable/ic_lock_bugreport.xml @@ -1,19 +1,28 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- +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 + 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 + 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. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_lock_bugreport_alpha" - android:tint="?attr/colorControlNormal" /> + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> + + <path + android:fill="?attr/colorControlNormal" + android:pathData="M20.0,8.0l-2.8,0.0c-0.5,-0.8 -1.1,-1.5 -1.8,-2.0L17.0,4.4L15.6,3.0l-2.2,2.2C13.0,5.1 12.5,5.0 12.0,5.0s-1.0,0.1 -1.4,0.2L8.4,3.0L7.0,4.4L8.6,6.0C7.9,6.5 7.3,7.2 6.8,8.0L4.0,8.0l0.0,2.0l2.1,0.0C6.0,10.3 6.0,10.7 6.0,11.0l0.0,1.0L4.0,12.0l0.0,2.0l2.0,0.0l0.0,1.0c0.0,0.3 0.0,0.7 0.1,1.0L4.0,16.0l0.0,2.0l2.8,0.0c1.0,1.8 3.0,3.0 5.2,3.0s4.2,-1.2 5.2,-3.0L20.0,18.0l0.0,-2.0l-2.1,0.0c0.1,-0.3 0.1,-0.7 0.1,-1.0l0.0,-1.0l2.0,0.0l0.0,-2.0l-2.0,0.0l0.0,-1.0c0.0,-0.3 0.0,-0.7 -0.1,-1.0L20.0,10.0L20.0,8.0zM14.0,16.0l-4.0,0.0l0.0,-2.0l4.0,0.0L14.0,16.0zM14.0,12.0l-4.0,0.0l0.0,-2.0l4.0,0.0L14.0,12.0z"/> +</vector> 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 3bf777c..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> 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/strings.xml b/core/res/res/values/strings.xml index 6ae35e1..9ff67b4 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -419,6 +419,8 @@ current device state, to send as an e-mail message. It will take a little time from starting the bug report until it is ready to be sent; please be patient.</string> + <!-- Format for build summary info [CHAR LIMIT=NONE] --> + <string name="bugreport_status" translatable="false">%s (%s)</string> <!-- label for item that enables silent mode in phone options dialog --> <string name="global_action_toggle_silent_mode">Silent mode</string> 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 6943533..108334f 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"/> @@ -522,7 +518,10 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.ExpandableListView.White"/> <style name="Widget.Quantum.Gallery" parent="Widget.Gallery"/> <style name="Widget.Quantum.GestureOverlayView" parent="Widget.GestureOverlayView"/> - <style name="Widget.Quantum.GridView" parent="Widget.GridView"/> + + <style name="Widget.Quantum.GridView" parent="Widget.GridView"> + <item name="android:listSelector">?attr/selectableItemBackground</item> + </style> <style name="Widget.Quantum.CalendarView" parent="Widget.CalendarView"> <item name="selectedWeekBackgroundColor">#330099FF</item> @@ -586,7 +585,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 +625,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 +652,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 +711,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> @@ -809,7 +808,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> @@ -879,12 +878,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"/> @@ -894,7 +888,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..8b1ca31 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1388,6 +1388,7 @@ <java-symbol type="string" name="android_upgrading_title" /> <java-symbol type="string" name="bugreport_title" /> <java-symbol type="string" name="bugreport_message" /> + <java-symbol type="string" name="bugreport_status" /> <java-symbol type="string" name="faceunlock_multiple_failures" /> <java-symbol type="string" name="global_action_power_off" /> <java-symbol type="string" name="global_actions_airplane_mode_off_status" /> @@ -1679,7 +1680,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/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java index 5dc9ef8..433d4d2 100644 --- a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java @@ -16,6 +16,9 @@ package com.android.internal.util; +import android.test.MoreAsserts; + +import java.util.Arrays; import junit.framework.TestCase; /** @@ -77,4 +80,79 @@ public class ArrayUtilsTest extends TestCase { assertFalse(ArrayUtils.containsAll(new Object[] { }, new Object[] { null })); assertFalse(ArrayUtils.containsAll(new Object[] { A }, new Object[] { null })); } + + public void testContainsInt() throws Exception { + assertTrue(ArrayUtils.contains(new int[] { 1, 2, 3 }, 1)); + assertTrue(ArrayUtils.contains(new int[] { 1, 2, 3 }, 2)); + assertTrue(ArrayUtils.contains(new int[] { 1, 2, 3 }, 3)); + + assertFalse(ArrayUtils.contains(new int[] { 1, 2, 3 }, 0)); + assertFalse(ArrayUtils.contains(new int[] { 1, 2, 3 }, 4)); + assertFalse(ArrayUtils.contains(new int[] { }, 2)); + } + + public void testAppendInt() throws Exception { + MoreAsserts.assertEquals(new int[] { 1 }, + ArrayUtils.appendInt(null, 1)); + MoreAsserts.assertEquals(new int[] { 1 }, + ArrayUtils.appendInt(new int[] { }, 1)); + MoreAsserts.assertEquals(new int[] { 1, 2 }, + ArrayUtils.appendInt(new int[] { 1 }, 2)); + MoreAsserts.assertEquals(new int[] { 1, 2 }, + ArrayUtils.appendInt(new int[] { 1, 2 }, 1)); + } + + public void testRemoveInt() throws Exception { + assertNull(ArrayUtils.removeInt(null, 1)); + MoreAsserts.assertEquals(new int[] { }, + ArrayUtils.removeInt(new int[] { }, 1)); + MoreAsserts.assertEquals(new int[] { 1, 2, 3, }, + ArrayUtils.removeInt(new int[] { 1, 2, 3}, 4)); + MoreAsserts.assertEquals(new int[] { 2, 3, }, + ArrayUtils.removeInt(new int[] { 1, 2, 3}, 1)); + MoreAsserts.assertEquals(new int[] { 1, 3, }, + ArrayUtils.removeInt(new int[] { 1, 2, 3}, 2)); + MoreAsserts.assertEquals(new int[] { 1, 2, }, + ArrayUtils.removeInt(new int[] { 1, 2, 3}, 3)); + MoreAsserts.assertEquals(new int[] { 2, 3, 1 }, + ArrayUtils.removeInt(new int[] { 1, 2, 3, 1 }, 1)); + } + + public void testContainsLong() throws Exception { + assertTrue(ArrayUtils.contains(new long[] { 1, 2, 3 }, 1)); + assertTrue(ArrayUtils.contains(new long[] { 1, 2, 3 }, 2)); + assertTrue(ArrayUtils.contains(new long[] { 1, 2, 3 }, 3)); + + assertFalse(ArrayUtils.contains(new long[] { 1, 2, 3 }, 0)); + assertFalse(ArrayUtils.contains(new long[] { 1, 2, 3 }, 4)); + assertFalse(ArrayUtils.contains(new long[] { }, 2)); + } + + public void testAppendLong() throws Exception { + MoreAsserts.assertEquals(new long[] { 1 }, + ArrayUtils.appendLong(null, 1)); + MoreAsserts.assertEquals(new long[] { 1 }, + ArrayUtils.appendLong(new long[] { }, 1)); + MoreAsserts.assertEquals(new long[] { 1, 2 }, + ArrayUtils.appendLong(new long[] { 1 }, 2)); + MoreAsserts.assertEquals(new long[] { 1, 2 }, + ArrayUtils.appendLong(new long[] { 1, 2 }, 1)); + } + + public void testRemoveLong() throws Exception { + assertNull(ArrayUtils.removeLong(null, 1)); + MoreAsserts.assertEquals(new long[] { }, + ArrayUtils.removeLong(new long[] { }, 1)); + MoreAsserts.assertEquals(new long[] { 1, 2, 3, }, + ArrayUtils.removeLong(new long[] { 1, 2, 3}, 4)); + MoreAsserts.assertEquals(new long[] { 2, 3, }, + ArrayUtils.removeLong(new long[] { 1, 2, 3}, 1)); + MoreAsserts.assertEquals(new long[] { 1, 3, }, + ArrayUtils.removeLong(new long[] { 1, 2, 3}, 2)); + MoreAsserts.assertEquals(new long[] { 1, 2, }, + ArrayUtils.removeLong(new long[] { 1, 2, 3}, 3)); + MoreAsserts.assertEquals(new long[] { 2, 3, 1 }, + ArrayUtils.removeLong(new long[] { 1, 2, 3, 1 }, 1)); + } + } diff --git a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java index c0c20e2..ca68e93 100644 --- a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java +++ b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java @@ -172,7 +172,8 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); - final ControllerImpl controller = new ControllerImpl(enabledItems); + final ControllerImpl controller = ControllerImpl.createFrom( + null /* currentInstance */, enabledItems); // switching-aware loop assertRotationOrder(controller, false /* onlyCurrentIme */, @@ -214,9 +215,8 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe disabledSubtypeUnawareIme, null); } - // This test is disabled until DynamicRotationList is enabled. @SmallTest - public void DISABLED_testControllerImplWithUserAction() throws Exception { + public void testControllerImplWithUserAction() throws Exception { final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); @@ -226,7 +226,8 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); - final ControllerImpl controller = new ControllerImpl(enabledItems); + final ControllerImpl controller = ControllerImpl.createFrom( + null /* currentInstance */, enabledItems); // === switching-aware loop === assertRotationOrder(controller, false /* onlyCurrentIme */, @@ -272,5 +273,26 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe subtypeUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, switchUnawareJapaneseIme_ja_JP, null); + + // Rotation order should be preserved when created with the same subtype list. + final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes(); + final ControllerImpl newController = ControllerImpl.createFrom(controller, + sameEnabledItems); + assertRotationOrder(newController, false /* onlyCurrentIme */, + japaneseIme_ja_JP, latinIme_fr, latinIme_en_US); + assertRotationOrder(newController, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + + // Rotation order should be initialized when created with a different subtype list. + final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList( + latinIme_en_US, latinIme_fr, switchingUnawarelatinIme_en_UK, + switchUnawareJapaneseIme_ja_JP); + final ControllerImpl anotherController = ControllerImpl.createFrom(controller, + differentEnabledItems); + assertRotationOrder(anotherController, false /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr); + assertRotationOrder(anotherController, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchUnawareJapaneseIme_ja_JP); } } diff --git a/data/fonts/Roboto-Black.ttf b/data/fonts/Roboto-Black.ttf Binary files differindex 2cdbe43..cb905bc 100644 --- a/data/fonts/Roboto-Black.ttf +++ b/data/fonts/Roboto-Black.ttf diff --git a/data/fonts/Roboto-Bold.ttf b/data/fonts/Roboto-Bold.ttf Binary files differindex 15c9b4e..68822ca 100644 --- a/data/fonts/Roboto-Bold.ttf +++ b/data/fonts/Roboto-Bold.ttf diff --git a/data/fonts/Roboto-BoldItalic.ttf b/data/fonts/Roboto-BoldItalic.ttf Binary files differindex a0abf30..aebf8eb 100644 --- a/data/fonts/Roboto-BoldItalic.ttf +++ b/data/fonts/Roboto-BoldItalic.ttf diff --git a/data/fonts/Roboto-Italic.ttf b/data/fonts/Roboto-Italic.ttf Binary files differindex 67b5394..2041cbc 100644 --- a/data/fonts/Roboto-Italic.ttf +++ b/data/fonts/Roboto-Italic.ttf diff --git a/data/fonts/Roboto-Light.ttf b/data/fonts/Roboto-Light.ttf Binary files differindex d9fb64a..aa45340 100644 --- a/data/fonts/Roboto-Light.ttf +++ b/data/fonts/Roboto-Light.ttf diff --git a/data/fonts/Roboto-LightItalic.ttf b/data/fonts/Roboto-LightItalic.ttf Binary files differindex 1fd1d31..a85444f 100644 --- a/data/fonts/Roboto-LightItalic.ttf +++ b/data/fonts/Roboto-LightItalic.ttf diff --git a/data/fonts/Roboto-Medium.ttf b/data/fonts/Roboto-Medium.ttf Binary files differindex c63c115..a3c1a1f 100644 --- a/data/fonts/Roboto-Medium.ttf +++ b/data/fonts/Roboto-Medium.ttf diff --git a/data/fonts/Roboto-MediumItalic.ttf b/data/fonts/Roboto-MediumItalic.ttf Binary files differindex cd7c835..a30aa0c 100644 --- a/data/fonts/Roboto-MediumItalic.ttf +++ b/data/fonts/Roboto-MediumItalic.ttf diff --git a/data/fonts/Roboto-Regular.ttf b/data/fonts/Roboto-Regular.ttf Binary files differindex 9cb4a5a..0e58508 100644 --- a/data/fonts/Roboto-Regular.ttf +++ b/data/fonts/Roboto-Regular.ttf diff --git a/data/fonts/Roboto-Thin.ttf b/data/fonts/Roboto-Thin.ttf Binary files differindex f02f100..8779333 100644 --- a/data/fonts/Roboto-Thin.ttf +++ b/data/fonts/Roboto-Thin.ttf diff --git a/data/fonts/Roboto-ThinItalic.ttf b/data/fonts/Roboto-ThinItalic.ttf Binary files differindex 12a2ce0..b79cb26 100644 --- a/data/fonts/Roboto-ThinItalic.ttf +++ b/data/fonts/Roboto-ThinItalic.ttf diff --git a/data/fonts/RobotoCondensed-Bold.ttf b/data/fonts/RobotoCondensed-Bold.ttf Binary files differindex 1079af6..3e06c7c 100644 --- a/data/fonts/RobotoCondensed-Bold.ttf +++ b/data/fonts/RobotoCondensed-Bold.ttf diff --git a/data/fonts/RobotoCondensed-BoldItalic.ttf b/data/fonts/RobotoCondensed-BoldItalic.ttf Binary files differindex e7f13c2..aaf9fe0 100644 --- a/data/fonts/RobotoCondensed-BoldItalic.ttf +++ b/data/fonts/RobotoCondensed-BoldItalic.ttf diff --git a/data/fonts/RobotoCondensed-Italic.ttf b/data/fonts/RobotoCondensed-Italic.ttf Binary files differindex 7fa0448..d2b611f 100644 --- a/data/fonts/RobotoCondensed-Italic.ttf +++ b/data/fonts/RobotoCondensed-Italic.ttf diff --git a/data/fonts/RobotoCondensed-Light.ttf b/data/fonts/RobotoCondensed-Light.ttf Binary files differindex 96b75dd..d4eb198 100644 --- a/data/fonts/RobotoCondensed-Light.ttf +++ b/data/fonts/RobotoCondensed-Light.ttf diff --git a/data/fonts/RobotoCondensed-LightItalic.ttf b/data/fonts/RobotoCondensed-LightItalic.ttf Binary files differindex 7a2c164..a08f3f4 100644 --- a/data/fonts/RobotoCondensed-LightItalic.ttf +++ b/data/fonts/RobotoCondensed-LightItalic.ttf diff --git a/data/fonts/RobotoCondensed-Regular.ttf b/data/fonts/RobotoCondensed-Regular.ttf Binary files differindex 734cc40..b9fc49c 100644 --- a/data/fonts/RobotoCondensed-Regular.ttf +++ b/data/fonts/RobotoCondensed-Regular.ttf 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/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/docs/html/tools/device.jd b/docs/html/tools/device.jd index e9caa44..e748b12 100644 --- a/docs/html/tools/device.jd +++ b/docs/html/tools/device.jd @@ -280,6 +280,10 @@ above.</p> <td><code>0fce</code></td> </tr> <tr> + <td>Sony Mobile Communications</td> + <td><code>0fce</code></td> + </tr> + <tr> <td>Teleepoch</td> <td><code>2340</code></td> </tr> diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java index a40085b..57e0f27 100644 --- a/graphics/java/android/graphics/Camera.java +++ b/graphics/java/android/graphics/Camera.java @@ -154,7 +154,7 @@ public class Camera { getMatrix(mMatrix); canvas.concat(mMatrix); } else { - nativeApplyToCanvas(canvas.getNativeCanvas()); + nativeApplyToCanvas(canvas.getNativeCanvasWrapper()); } } diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 5e4e42b..bd868f2 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -39,11 +39,11 @@ import javax.microedition.khronos.opengles.GL; public class Canvas { // assigned in constructors or setBitmap, freed in finalizer - private long mNativeCanvas; + private long mNativeCanvasWrapper; /** @hide */ - public long getNativeCanvas() { - return mNativeCanvas; + public long getNativeCanvasWrapper() { + return mNativeCanvasWrapper; } // may be null @@ -88,10 +88,10 @@ public class Canvas { private final CanvasFinalizer mFinalizer; private static final class CanvasFinalizer { - private long mNativeCanvas; + private long mNativeCanvasWrapper; public CanvasFinalizer(long nativeCanvas) { - mNativeCanvas = nativeCanvas; + mNativeCanvasWrapper = nativeCanvas; } @Override @@ -104,9 +104,9 @@ public class Canvas { } public void dispose() { - if (mNativeCanvas != 0) { - finalizer(mNativeCanvas); - mNativeCanvas = 0; + if (mNativeCanvasWrapper != 0) { + finalizer(mNativeCanvasWrapper); + mNativeCanvasWrapper = 0; } } } @@ -120,8 +120,8 @@ public class Canvas { public Canvas() { if (!isHardwareAccelerated()) { // 0 means no native bitmap - mNativeCanvas = initRaster(0); - mFinalizer = new CanvasFinalizer(mNativeCanvas); + mNativeCanvasWrapper = initRaster(0); + mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper); } else { mFinalizer = null; } @@ -141,8 +141,8 @@ public class Canvas { throw new IllegalStateException("Immutable bitmap passed to Canvas constructor"); } throwIfCannotDraw(bitmap); - mNativeCanvas = initRaster(bitmap.ni()); - mFinalizer = new CanvasFinalizer(mNativeCanvas); + mNativeCanvasWrapper = initRaster(bitmap.ni()); + mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper); mBitmap = bitmap; mDensity = bitmap.mDensity; } @@ -152,26 +152,12 @@ public class Canvas { if (nativeCanvas == 0) { throw new IllegalStateException(); } - mNativeCanvas = nativeCanvas; - mFinalizer = new CanvasFinalizer(mNativeCanvas); + mNativeCanvasWrapper = initCanvas(nativeCanvas); + mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper); mDensity = Bitmap.getDefaultDensity(); } /** - * Replace existing canvas while ensuring that the swap has occurred before - * the previous native canvas is unreferenced. - */ - private void safeCanvasSwap(long nativeCanvas, boolean copyState) { - final long oldCanvas = mNativeCanvas; - mNativeCanvas = nativeCanvas; - mFinalizer.mNativeCanvas = nativeCanvas; - if (copyState) { - copyNativeCanvasState(oldCanvas, mNativeCanvas); - } - finalizer(oldCanvas); - } - - /** * Returns null. * * @deprecated This method is not supported and should not be invoked. @@ -212,7 +198,7 @@ public class Canvas { } if (bitmap == null) { - safeCanvasSwap(initRaster(0), false); + native_setBitmap(mNativeCanvasWrapper, 0, false); mDensity = Bitmap.DENSITY_NONE; } else { if (!bitmap.isMutable()) { @@ -220,7 +206,7 @@ public class Canvas { } throwIfCannotDraw(bitmap); - safeCanvasSwap(initRaster(bitmap.ni()), true); + native_setBitmap(mNativeCanvasWrapper, bitmap.ni(), true); mDensity = bitmap.mDensity; } @@ -228,6 +214,13 @@ public class Canvas { } /** + * setBitmap() variant for native callers with a raw bitmap handle. + */ + private void setNativeBitmap(long bitmapHandle) { + native_setBitmap(mNativeCanvasWrapper, bitmapHandle, false); + } + + /** * Set the viewport dimensions if this canvas is GL based. If it is not, * this method is ignored and no exception is thrown. * @@ -382,7 +375,7 @@ public class Canvas { * @return value to pass to restoreToCount() to balance this save() */ public int saveLayer(RectF bounds, Paint paint, int saveFlags) { - return native_saveLayer(mNativeCanvas, bounds, + return native_saveLayer(mNativeCanvasWrapper, bounds, paint != null ? paint.mNativePaint : 0, saveFlags); } @@ -399,7 +392,7 @@ public class Canvas { */ public int saveLayer(float left, float top, float right, float bottom, Paint paint, int saveFlags) { - return native_saveLayer(mNativeCanvas, left, top, right, bottom, + return native_saveLayer(mNativeCanvasWrapper, left, top, right, bottom, paint != null ? paint.mNativePaint : 0, saveFlags); } @@ -429,7 +422,7 @@ public class Canvas { */ public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) { alpha = Math.min(255, Math.max(0, alpha)); - return native_saveLayerAlpha(mNativeCanvas, bounds, alpha, saveFlags); + return native_saveLayerAlpha(mNativeCanvasWrapper, bounds, alpha, saveFlags); } /** @@ -444,7 +437,7 @@ public class Canvas { */ public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, int saveFlags) { - return native_saveLayerAlpha(mNativeCanvas, left, top, right, bottom, + return native_saveLayerAlpha(mNativeCanvasWrapper, left, top, right, bottom, alpha, saveFlags); } @@ -548,7 +541,7 @@ public class Canvas { * @param matrix The matrix to preconcatenate with the current matrix */ public void concat(Matrix matrix) { - if (matrix != null) native_concat(mNativeCanvas, matrix.native_instance); + if (matrix != null) native_concat(mNativeCanvasWrapper, matrix.native_instance); } /** @@ -565,7 +558,7 @@ public class Canvas { * @see #concat(Matrix) */ public void setMatrix(Matrix matrix) { - native_setMatrix(mNativeCanvas, + native_setMatrix(mNativeCanvasWrapper, matrix == null ? 0 : matrix.native_instance); } @@ -575,7 +568,7 @@ public class Canvas { */ @Deprecated public void getMatrix(Matrix ctm) { - native_getCTM(mNativeCanvas, ctm.native_instance); + native_getCTM(mNativeCanvasWrapper, ctm.native_instance); } /** @@ -598,7 +591,7 @@ public class Canvas { * @return true if the resulting clip is non-empty */ public boolean clipRect(RectF rect, Region.Op op) { - return native_clipRect(mNativeCanvas, rect.left, rect.top, rect.right, rect.bottom, + return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt); } @@ -611,7 +604,7 @@ public class Canvas { * @return true if the resulting clip is non-empty */ public boolean clipRect(Rect rect, Region.Op op) { - return native_clipRect(mNativeCanvas, rect.left, rect.top, rect.right, rect.bottom, + return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt); } @@ -649,7 +642,7 @@ public class Canvas { * @return true if the resulting clip is non-empty */ public boolean clipRect(float left, float top, float right, float bottom, Region.Op op) { - return native_clipRect(mNativeCanvas, left, top, right, bottom, op.nativeInt); + return native_clipRect(mNativeCanvasWrapper, left, top, right, bottom, op.nativeInt); } /** @@ -690,7 +683,7 @@ public class Canvas { * @return true if the resulting is non-empty */ public boolean clipPath(Path path, Region.Op op) { - return native_clipPath(mNativeCanvas, path.ni(), op.nativeInt); + return native_clipPath(mNativeCanvasWrapper, path.ni(), op.nativeInt); } /** @@ -718,7 +711,7 @@ public class Canvas { * current matrix. Use {@link #clipRect(Rect)} as an alternative. */ public boolean clipRegion(Region region, Region.Op op) { - return native_clipRegion(mNativeCanvas, region.ni(), op.nativeInt); + return native_clipRegion(mNativeCanvasWrapper, region.ni(), op.nativeInt); } /** @@ -748,7 +741,7 @@ public class Canvas { nativeFilter = filter.mNativeInt; } mDrawFilter = filter; - nativeSetDrawFilter(mNativeCanvas, nativeFilter); + nativeSetDrawFilter(mNativeCanvasWrapper, nativeFilter); } public enum EdgeType { @@ -787,7 +780,7 @@ public class Canvas { * does not intersect with the canvas' clip */ public boolean quickReject(RectF rect, EdgeType type) { - return native_quickReject(mNativeCanvas, rect); + return native_quickReject(mNativeCanvasWrapper, rect); } /** @@ -806,7 +799,7 @@ public class Canvas { * does not intersect with the canvas' clip */ public boolean quickReject(Path path, EdgeType type) { - return native_quickReject(mNativeCanvas, path.ni()); + return native_quickReject(mNativeCanvasWrapper, path.ni()); } /** @@ -831,7 +824,7 @@ public class Canvas { */ public boolean quickReject(float left, float top, float right, float bottom, EdgeType type) { - return native_quickReject(mNativeCanvas, left, top, right, bottom); + return native_quickReject(mNativeCanvasWrapper, left, top, right, bottom); } /** @@ -845,7 +838,7 @@ public class Canvas { * @return true if the current clip is non-empty. */ public boolean getClipBounds(Rect bounds) { - return native_getClipBounds(mNativeCanvas, bounds); + return native_getClipBounds(mNativeCanvasWrapper, bounds); } /** @@ -868,7 +861,7 @@ public class Canvas { * @param b blue component (0..255) of the color to draw onto the canvas */ public void drawRGB(int r, int g, int b) { - native_drawRGB(mNativeCanvas, r, g, b); + native_drawRGB(mNativeCanvasWrapper, r, g, b); } /** @@ -881,7 +874,7 @@ public class Canvas { * @param b blue component (0..255) of the color to draw onto the canvas */ public void drawARGB(int a, int r, int g, int b) { - native_drawARGB(mNativeCanvas, a, r, g, b); + native_drawARGB(mNativeCanvasWrapper, a, r, g, b); } /** @@ -891,7 +884,7 @@ public class Canvas { * @param color the color to draw onto the canvas */ public void drawColor(int color) { - native_drawColor(mNativeCanvas, color); + native_drawColor(mNativeCanvasWrapper, color); } /** @@ -902,7 +895,7 @@ public class Canvas { * @param mode the porter-duff mode to apply to the color */ public void drawColor(int color, PorterDuff.Mode mode) { - native_drawColor(mNativeCanvas, color, mode.nativeInt); + native_drawColor(mNativeCanvasWrapper, color, mode.nativeInt); } /** @@ -913,7 +906,7 @@ public class Canvas { * @param paint The paint used to draw onto the canvas */ public void drawPaint(Paint paint) { - native_drawPaint(mNativeCanvas, paint.mNativePaint); + native_drawPaint(mNativeCanvasWrapper, paint.mNativePaint); } /** @@ -959,7 +952,7 @@ public class Canvas { * @param paint The paint used to draw the line */ public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { - native_drawLine(mNativeCanvas, startX, startY, stopX, stopY, paint.mNativePaint); + native_drawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.mNativePaint); } /** @@ -991,7 +984,7 @@ public class Canvas { * @param paint The paint used to draw the rect */ public void drawRect(RectF rect, Paint paint) { - native_drawRect(mNativeCanvas, rect, paint.mNativePaint); + native_drawRect(mNativeCanvasWrapper, rect, paint.mNativePaint); } /** @@ -1017,7 +1010,7 @@ public class Canvas { * @param paint The paint used to draw the rect */ public void drawRect(float left, float top, float right, float bottom, Paint paint) { - native_drawRect(mNativeCanvas, left, top, right, bottom, paint.mNativePaint); + native_drawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.mNativePaint); } /** @@ -1030,7 +1023,7 @@ public class Canvas { if (oval == null) { throw new NullPointerException(); } - native_drawOval(mNativeCanvas, oval, paint.mNativePaint); + native_drawOval(mNativeCanvasWrapper, oval, paint.mNativePaint); } /** @@ -1044,7 +1037,7 @@ public class Canvas { * @param paint The paint used to draw the circle */ public void drawCircle(float cx, float cy, float radius, Paint paint) { - native_drawCircle(mNativeCanvas, cx, cy, radius, paint.mNativePaint); + native_drawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.mNativePaint); } /** @@ -1075,7 +1068,7 @@ public class Canvas { if (oval == null) { throw new NullPointerException(); } - native_drawArc(mNativeCanvas, oval, startAngle, sweepAngle, + native_drawArc(mNativeCanvasWrapper, oval, startAngle, sweepAngle, useCenter, paint.mNativePaint); } @@ -1102,7 +1095,7 @@ public class Canvas { */ public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint) { - native_drawRoundRect(mNativeCanvas, left, top, right, bottom, rx, ry, paint.mNativePaint); + native_drawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, paint.mNativePaint); } /** @@ -1113,7 +1106,7 @@ public class Canvas { * @param paint The paint used to draw the path */ public void drawPath(Path path, Paint paint) { - native_drawPath(mNativeCanvas, path.ni(), paint.mNativePaint); + native_drawPath(mNativeCanvasWrapper, path.ni(), paint.mNativePaint); } /** @@ -1177,7 +1170,7 @@ public class Canvas { */ public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { throwIfCannotDraw(bitmap); - native_drawBitmap(mNativeCanvas, bitmap.ni(), left, top, + native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), left, top, paint != null ? paint.mNativePaint : 0, mDensity, mScreenDensity, bitmap.mDensity); } @@ -1208,7 +1201,7 @@ public class Canvas { throw new NullPointerException(); } throwIfCannotDraw(bitmap); - native_drawBitmap(mNativeCanvas, bitmap.ni(), src, dst, + native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), src, dst, paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity); } @@ -1239,7 +1232,7 @@ public class Canvas { throw new NullPointerException(); } throwIfCannotDraw(bitmap); - native_drawBitmap(mNativeCanvas, bitmap.ni(), src, dst, + native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), src, dst, paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity); } @@ -1291,7 +1284,7 @@ public class Canvas { return; } // punch down to native for the actual draw - native_drawBitmap(mNativeCanvas, colors, offset, stride, x, y, width, height, hasAlpha, + native_drawBitmap(mNativeCanvasWrapper, colors, offset, stride, x, y, width, height, hasAlpha, paint != null ? paint.mNativePaint : 0); } @@ -1319,7 +1312,7 @@ public class Canvas { * @param paint May be null. The paint used to draw the bitmap */ public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { - nativeDrawBitmapMatrix(mNativeCanvas, bitmap.ni(), matrix.ni(), + nativeDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.ni(), matrix.ni(), paint != null ? paint.mNativePaint : 0); } @@ -1373,7 +1366,7 @@ public class Canvas { // no mul by 2, since we need only 1 color per vertex checkRange(colors.length, colorOffset, count); } - nativeDrawBitmapMesh(mNativeCanvas, bitmap.ni(), meshWidth, meshHeight, + nativeDrawBitmapMesh(mNativeCanvasWrapper, bitmap.ni(), meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint != null ? paint.mNativePaint : 0); } @@ -1436,7 +1429,7 @@ public class Canvas { if (indices != null) { checkRange(indices.length, indexOffset, indexCount); } - nativeDrawVertices(mNativeCanvas, mode.nativeInt, vertexCount, verts, + nativeDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts, vertOffset, texs, texOffset, colors, colorOffset, indices, indexOffset, indexCount, paint.mNativePaint); } @@ -1455,7 +1448,7 @@ public class Canvas { (text.length - index - count)) < 0) { throw new IndexOutOfBoundsException(); } - native_drawText(mNativeCanvas, text, index, count, x, y, paint.mBidiFlags, + native_drawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } @@ -1469,7 +1462,7 @@ public class Canvas { * @param paint The paint used for the text (e.g. color, size, style) */ public void drawText(String text, float x, float y, Paint paint) { - native_drawText(mNativeCanvas, text, 0, text.length(), x, y, paint.mBidiFlags, + native_drawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } @@ -1488,7 +1481,7 @@ public class Canvas { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } - native_drawText(mNativeCanvas, text, start, end, x, y, paint.mBidiFlags, + native_drawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } @@ -1508,7 +1501,7 @@ public class Canvas { public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { - native_drawText(mNativeCanvas, text.toString(), start, end, x, y, + native_drawText(mNativeCanvasWrapper, text.toString(), start, end, x, y, paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } else if (text instanceof GraphicsOperations) { ((GraphicsOperations) text).drawText(this, start, end, x, y, @@ -1516,7 +1509,7 @@ public class Canvas { } else { char[] buf = TemporaryBuffer.obtain(end - start); TextUtils.getChars(text, start, end, buf, 0); - native_drawText(mNativeCanvas, buf, 0, end - start, x, y, + native_drawText(mNativeCanvasWrapper, buf, 0, end - start, x, y, paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); TemporaryBuffer.recycle(buf); } @@ -1559,7 +1552,7 @@ public class Canvas { throw new IllegalArgumentException("unknown dir: " + dir); } - native_drawTextRun(mNativeCanvas, text, index, count, + native_drawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount, x, y, dir, paint.mNativePaint, paint.mNativeTypeface); } @@ -1597,7 +1590,7 @@ public class Canvas { if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { - native_drawTextRun(mNativeCanvas, text.toString(), start, end, + native_drawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart, contextEnd, x, y, flags, paint.mNativePaint, paint.mNativeTypeface); } else if (text instanceof GraphicsOperations) { ((GraphicsOperations) text).drawTextRun(this, start, end, @@ -1607,7 +1600,7 @@ public class Canvas { int len = end - start; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); - native_drawTextRun(mNativeCanvas, buf, start - contextStart, len, + native_drawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len, 0, contextLen, x, y, flags, paint.mNativePaint, paint.mNativeTypeface); TemporaryBuffer.recycle(buf); } @@ -1632,7 +1625,7 @@ public class Canvas { if (index < 0 || index + count > text.length || count*2 > pos.length) { throw new IndexOutOfBoundsException(); } - native_drawPosText(mNativeCanvas, text, index, count, pos, + native_drawPosText(mNativeCanvasWrapper, text, index, count, pos, paint.mNativePaint); } @@ -1652,7 +1645,7 @@ public class Canvas { if (text.length()*2 > pos.length) { throw new ArrayIndexOutOfBoundsException(); } - native_drawPosText(mNativeCanvas, text, pos, paint.mNativePaint); + native_drawPosText(mNativeCanvasWrapper, text, pos, paint.mNativePaint); } /** @@ -1673,7 +1666,7 @@ public class Canvas { if (index < 0 || index + count > text.length) { throw new ArrayIndexOutOfBoundsException(); } - native_drawTextOnPath(mNativeCanvas, text, index, count, + native_drawTextOnPath(mNativeCanvasWrapper, text, index, count, path.ni(), hOffset, vOffset, paint.mBidiFlags, paint.mNativePaint); } @@ -1693,7 +1686,7 @@ public class Canvas { */ public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) { if (text.length() > 0) { - native_drawTextOnPath(mNativeCanvas, text, path.ni(), hOffset, vOffset, + native_drawTextOnPath(mNativeCanvasWrapper, text, path.ni(), hOffset, vOffset, paint.mBidiFlags, paint.mNativePaint); } } @@ -1767,8 +1760,10 @@ public class Canvas { public static native void freeTextLayoutCaches(); private static native long initRaster(long nativeBitmapOrZero); - private static native void copyNativeCanvasState(long nativeSrcCanvas, - long nativeDstCanvas); + private static native long initCanvas(long canvasHandle); + private static native void native_setBitmap(long canvasHandle, + long bitmapHandle, + boolean copyState); private static native int native_saveLayer(long nativeCanvas, RectF bounds, long nativePaint, diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index 6ff5f4f..befac92 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -164,12 +164,12 @@ public class NinePatch { } void drawSoftware(Canvas canvas, RectF location, Paint paint) { - nativeDraw(canvas.getNativeCanvas(), location, mBitmap.ni(), mNativeChunk, + nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap.ni(), mNativeChunk, paint != null ? paint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity); } void drawSoftware(Canvas canvas, Rect location, Paint paint) { - nativeDraw(canvas.getNativeCanvas(), location, mBitmap.ni(), mNativeChunk, + nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap.ni(), mNativeChunk, paint != null ? paint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity); } diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index a16c099..de458af 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -107,7 +107,7 @@ public class Picture { if (mRecordingCanvas != null) { endRecording(); } - nativeDraw(canvas.getNativeCanvas(), mNativePicture); + nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture); } /** diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index f82acc3..1512da5 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -612,7 +612,9 @@ public class GradientDrawable extends Drawable { case LINE: { RectF r = mRect; float y = r.centerY(); - canvas.drawLine(r.left, y, r.right, y, mStrokePaint); + if (haveStroke) { + canvas.drawLine(r.left, y, r.right, y, mStrokePaint); + } break; } case RING: @@ -1153,6 +1155,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,12 +1430,10 @@ 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; - public int mAngle; + public int mAngle = 0; public Orientation mOrientation; public ColorStateList mColorStateList; public ColorStateList mStrokeColorStateList; @@ -1437,11 +1441,12 @@ public class GradientDrawable extends Drawable { public int[] mTempColors; // no need to copy public float[] mTempPositions; // no need to copy public float[] mPositions; - public int mStrokeWidth = -1; // if >= 0 use stroking. - public float mStrokeDashWidth; - public float mStrokeDashGap; - public float mRadius; // use this if mRadiusArray is null - public float[] mRadiusArray; + public int mStrokeWidth = -1; // if >= 0 use stroking. + public float mStrokeDashWidth = 0.0f; + public float mStrokeDashGap = 0.0f; + public float mRadius = 0.0f; // use this if mRadiusArray is null + public float[] mRadiusArray = null; + public Rect mPadding = null; public int mWidth = -1; public int mHeight = -1; public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO; @@ -1491,7 +1496,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 442f327..e5c8898 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -71,8 +71,6 @@ ifeq ($(USE_OPENGL_RENDERER),true) $(LOCAL_PATH)/../../include/utils \ external/skia/src/core - include external/stlport/libstlport.mk - LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES LOCAL_CFLAGS += -Wno-unused-parameter LOCAL_MODULE_CLASS := SHARED_LIBRARIES @@ -80,16 +78,15 @@ ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_MODULE := libhwui LOCAL_MODULE_TAGS := optional + include external/stlport/libstlport.mk + ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT)) LOCAL_CFLAGS += -DANDROID_ENABLE_RENDERSCRIPT - LOCAL_SHARED_LIBRARIES += libRS libRScpp libstlport + LOCAL_SHARED_LIBRARIES += libRS libRScpp LOCAL_C_INCLUDES += \ $(intermediates) \ frameworks/rs/cpp \ - frameworks/rs \ - external/stlport/stlport \ - bionic/ \ - bionic/libstdc++/include + frameworks/rs endif ifndef HWUI_COMPILE_SYMBOLS diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp index b80f7e9..eff3011 100644 --- a/libs/hwui/Animator.cpp +++ b/libs/hwui/Animator.cpp @@ -63,7 +63,6 @@ void BaseRenderNodeAnimator::setStartValue(float value) { void BaseRenderNodeAnimator::setupStartValueIfNecessary(RenderNode* target, TreeInfo& info) { if (mPlayState == NEEDS_START) { setStartValue(getValue(target)); - mPlayState = PENDING; } } @@ -154,7 +153,8 @@ RenderPropertyAnimator::RenderPropertyAnimator(RenderProperty property, float fi } void RenderPropertyAnimator::onAttached(RenderNode* target) { - if (target->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) { + if (mPlayState == NEEDS_START + && target->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) { setStartValue((target->stagingProperties().*mPropertyAccess->getter)()); } (target->mutateStagingProperties().*mPropertyAccess->setter)(finalValue()); diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h index 7741617..a0c7c55 100644 --- a/libs/hwui/Animator.h +++ b/libs/hwui/Animator.h @@ -41,6 +41,7 @@ protected: class BaseRenderNodeAnimator : public VirtualLightRefBase { PREVENT_COPY_AND_ASSIGN(BaseRenderNodeAnimator); public: + ANDROID_API void setStartValue(float value); ANDROID_API void setInterpolator(Interpolator* interpolator); ANDROID_API void setDuration(nsecs_t durationInMs); ANDROID_API nsecs_t duration() { return mDuration; } @@ -64,11 +65,9 @@ protected: BaseRenderNodeAnimator(float finalValue); virtual ~BaseRenderNodeAnimator(); - void setStartValue(float value); virtual float getValue(RenderNode* target) const = 0; virtual void setValue(RenderNode* target, float value) = 0; -private: void callOnFinishedListener(TreeInfo& info); enum PlayState { 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..9212b9d 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 { @@ -788,7 +788,7 @@ public: } virtual void output(int level, uint32_t logFlags) const { - OP_LOG("Draw bitmap %p src="RECT_STRING", dst="RECT_STRING, + OP_LOG("Draw bitmap %p src=" RECT_STRING ", dst=" RECT_STRING, mBitmap, RECT_ARGS(mSrc), RECT_ARGS(mLocalBounds)); } @@ -978,7 +978,7 @@ public: } virtual void output(int level, uint32_t logFlags) const { - OP_LOG("Draw patch "RECT_STRING, RECT_ARGS(mLocalBounds)); + OP_LOG("Draw patch " RECT_STRING, RECT_ARGS(mLocalBounds)); } virtual const char* name() { return "DrawPatch"; } @@ -1060,7 +1060,7 @@ public: } virtual void output(int level, uint32_t logFlags) const { - OP_LOG("Draw Rect "RECT_STRING, RECT_ARGS(mLocalBounds)); + OP_LOG("Draw Rect " RECT_STRING, RECT_ARGS(mLocalBounds)); } virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, @@ -1111,7 +1111,7 @@ public: } virtual void output(int level, uint32_t logFlags) const { - OP_LOG("Draw RoundRect "RECT_STRING", rx %f, ry %f", RECT_ARGS(mLocalBounds), mRx, mRy); + OP_LOG("Draw RoundRect " RECT_STRING ", rx %f, ry %f", RECT_ARGS(mLocalBounds), mRx, mRy); } virtual const char* name() { return "DrawRoundRect"; } @@ -1175,7 +1175,7 @@ public: } virtual void output(int level, uint32_t logFlags) const { - OP_LOG("Draw Oval "RECT_STRING, RECT_ARGS(mLocalBounds)); + OP_LOG("Draw Oval " RECT_STRING, RECT_ARGS(mLocalBounds)); } virtual const char* name() { return "DrawOval"; } @@ -1195,7 +1195,7 @@ public: } virtual void output(int level, uint32_t logFlags) const { - OP_LOG("Draw Arc "RECT_STRING", start %f, sweep %f, useCenter %d", + OP_LOG("Draw Arc " RECT_STRING ", start %f, sweep %f, useCenter %d", RECT_ARGS(mLocalBounds), mStartAngle, mSweepAngle, mUseCenter); } @@ -1232,7 +1232,7 @@ public: } virtual void output(int level, uint32_t logFlags) const { - OP_LOG("Draw Path %p in "RECT_STRING, mPath, RECT_ARGS(mLocalBounds)); + OP_LOG("Draw Path %p in " RECT_STRING, mPath, RECT_ARGS(mLocalBounds)); } virtual const char* name() { return "DrawPath"; } 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/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/RenderNode.cpp b/libs/hwui/RenderNode.cpp index d964efc..baf372a 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -223,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/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 f91e90e..9ebee1d 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -427,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()); } } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index a04269b..00c5bf0 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -55,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(); @@ -83,9 +84,6 @@ public: private: friend class RegisterFrameCallbackTask; - void processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters, TreeInfo& info); - void prepareTree(TreeInfo& info); - void setSurface(ANativeWindow* window); void swapBuffers(); void requireSurface(); diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index 7ea358f..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" @@ -47,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; } } } @@ -132,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 30c8880..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,8 +57,8 @@ 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); void setDensity(float density) { mDensity = density; } @@ -83,13 +84,9 @@ private: 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 77c0aa7..0901963 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -229,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) { @@ -242,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() { @@ -257,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); @@ -280,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) { diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index c8d42ec..944ff9c 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -80,7 +80,8 @@ 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); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 47a8ce5..84d4ab6 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() @@ -635,7 +644,12 @@ public class AudioManager { if (mUseMasterVolume) { service.adjustMasterVolume(direction, flags, mContext.getOpPackageName()); } else { - service.adjustVolume(direction, flags, mContext.getOpPackageName()); + if (USE_SESSIONS) { + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext); + helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags); + } else { + service.adjustVolume(direction, flags, mContext.getOpPackageName()); + } } } catch (RemoteException e) { Log.e(TAG, "Dead object in adjustVolume", e); @@ -665,8 +679,13 @@ public class AudioManager { if (mUseMasterVolume) { service.adjustMasterVolume(direction, flags, mContext.getOpPackageName()); } else { - service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, - mContext.getOpPackageName()); + if (USE_SESSIONS) { + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext); + helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags); + } else { + service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, + mContext.getOpPackageName()); + } } } catch (RemoteException e) { Log.e(TAG, "Dead object in adjustSuggestedStreamVolume", e); @@ -3007,7 +3026,7 @@ public class AudioManager { * @hide */ public int listAudioPorts(ArrayList<AudioPort> ports) { - return ERROR_INVALID_OPERATION; + return updateAudioPortCache(ports, null); } /** @@ -3016,7 +3035,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 +3074,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 +3089,7 @@ public class AudioManager { * @hide */ public int releaseAudioPatch(AudioPatch patch) { - return ERROR_INVALID_OPERATION; + return AudioSystem.releaseAudioPatch(patch); } /** @@ -3069,7 +3098,7 @@ public class AudioManager { * @hide */ public int listAudioPatches(ArrayList<AudioPatch> patches) { - return ERROR_INVALID_OPERATION; + return updateAudioPortCache(null, patches); } /** @@ -3078,7 +3107,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 +3142,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..782ecd8 --- /dev/null +++ b/media/java/android/media/AudioPortEventHandler.java @@ -0,0 +1,172 @@ +/* + * 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) { + looper = Looper.getMainLooper(); + } + + if (looper != null) { + 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; + } + } + }; + } else { + mHandler = null; + } + + 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/ISession.aidl b/media/java/android/media/session/ISession.aidl index c4233c3..1cfc5bc 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/java/android/media/session/ISession.aidl @@ -47,4 +47,8 @@ interface ISession { void setMetadata(in MediaMetadata metadata); void setPlaybackState(in PlaybackState state); void setRatingType(int type); + + // These commands relate to volume handling + void configureVolumeHandling(int type, int arg1, int arg2); + void setCurrentVolume(int currentVolume); }
\ No newline at end of file diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl index 103c3f1..0316d1f 100644 --- a/media/java/android/media/session/ISessionCallback.aidl +++ b/media/java/android/media/session/ISessionCallback.aidl @@ -45,4 +45,8 @@ oneway interface ISessionCallback { void onRewind(); void onSeekTo(long pos); void onRate(in Rating rating); + + // These callbacks are for volume handling + void onAdjustVolumeBy(int delta); + void onSetVolumeTo(int value); }
\ No newline at end of file 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/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index 38b9293..6d9888f 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -29,4 +29,5 @@ interface ISessionManager { ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId); List<IBinder> getSessions(in ComponentName compName, int userId); void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock); + void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags); }
\ No newline at end of file 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..7972639 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,15 +124,21 @@ 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; + /** + * The session uses local playback. Used for configuring volume handling + * with the system. + * + * @hide + */ + public static final int VOLUME_TYPE_LOCAL = 1; - private static final String KEY_COMMAND = "command"; - private static final String KEY_EXTRAS = "extras"; - private static final String KEY_CALLBACK = "callback"; + /** + * The session uses remote playback. Used for configuring volume handling + * with the system. + * + * @hide + */ + public static final int VOLUME_TYPE_REMOTE = 2; private final Object mLock = new Object(); @@ -139,13 +146,16 @@ 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 RemoteVolumeProvider mVolumeProvider; private boolean mActive = false;; @@ -162,11 +172,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 +204,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 +222,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 +246,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,25 +254,36 @@ public final class MediaSession { * * @param stream The {@link AudioManager} stream this session is playing on. */ - public void useLocalPlayback(int stream) { - // TODO + public void setPlaybackToLocal(int stream) { + try { + mBinder.configureVolumeHandling(VOLUME_TYPE_LOCAL, stream, 0); + } catch (RemoteException e) { + Log.wtf(TAG, "Failure in setPlaybackToLocal.", e); + } } /** * 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!"); } - // TODO + mVolumeProvider = volumeProvider; + + try { + mBinder.configureVolumeHandling(VOLUME_TYPE_REMOTE, volumeProvider.getVolumeControl(), + volumeProvider.getMaxVolume()); + } catch (RemoteException e) { + Log.wtf(TAG, "Failure in setPlaybackToRemote.", e); + } } /** @@ -312,7 +323,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 +443,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 +609,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 +622,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 +630,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 +638,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 +647,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 +656,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 +684,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 +695,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 +741,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 +878,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 +886,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 +894,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 +902,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 +910,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 +918,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 +926,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 +934,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 +942,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); } } @@ -758,12 +966,38 @@ public final class MediaSession { } + /* + * (non-Javadoc) + * @see android.media.session.ISessionCallback#onAdjustVolumeBy(int) + */ + @Override + public void onAdjustVolumeBy(int delta) throws RemoteException { + // TODO(epastern): Auto-generated method stub + + } + + /* + * (non-Javadoc) + * @see android.media.session.ISessionCallback#onSetVolumeTo(int) + */ + @Override + public void onSetVolumeTo(int value) throws RemoteException { + // TODO(epastern): Auto-generated method stub + + } + } - 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 +1010,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 +1049,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..099f601 100644 --- a/media/java/android/media/session/MediaSessionLegacyHelper.java +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -76,13 +76,20 @@ public class MediaSessionLegacyHelper { } } - public void addRccListener(PendingIntent pi, TransportPerformer.Listener listener) { + public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) { + mSessionManager.dispatchAdjustVolumeBy(suggestedStream, delta, flags); + if (DEBUG) { + Log.d(TAG, "dispatched volume adjustment"); + } + } + + 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 +99,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 +117,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 +148,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 +163,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 +208,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 +233,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 +279,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..9e8b0d3 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()); @@ -165,4 +166,22 @@ public final class MediaSessionManager { Log.e(TAG, "Failed to send key event.", e); } } + + /** + * Dispatch an adjust volume request to the system. It will be routed to the + * most relevant stream/session. + * + * @param suggestedStream The stream to fall back to if there isn't a + * relevant stream + * @param delta The amount to adjust the volume by. + * @param flags Any flags to include with the volume change. + * @hide + */ + public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags) { + try { + mService.dispatchAdjustVolumeBy(suggestedStream, delta, flags); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send adjust volume.", e); + } + } } 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/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp index d781336..19b54a6 100644 --- a/media/jni/android_mtp_MtpDatabase.cpp +++ b/media/jni/android_mtp_MtpDatabase.cpp @@ -433,16 +433,14 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle, case MTP_TYPE_STR: { jstring stringValue = (jstring)env->GetObjectArrayElement(stringValuesArray, 0); + const char* str = (stringValue ? env->GetStringUTFChars(stringValue, NULL) : NULL); if (stringValue) { - const char* str = env->GetStringUTFChars(stringValue, NULL); - if (str == NULL) { - return MTP_RESPONSE_GENERAL_ERROR; - } packet.putString(str); env->ReleaseStringUTFChars(stringValue, str); } else { packet.putEmptyString(); } + env->DeleteLocalRef(stringValue); break; } default: @@ -515,7 +513,7 @@ MtpResponseCode MyMtpDatabase::setObjectPropertyValue(MtpObjectHandle handle, break; } default: - ALOGE("unsupported type in getObjectPropertyValue\n"); + ALOGE("unsupported type in setObjectPropertyValue\n"); return MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT; } 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 afc2931..ef06d2c 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java @@ -18,6 +18,7 @@ package com.android.mediaframeworktest.unit; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; +import android.util.Pair; import android.util.Range; import android.util.Rational; import android.util.SizeF; @@ -790,6 +791,22 @@ public class CameraMetadataTest extends junit.framework.TestCase { } @SmallTest + public void testReadWritePair() { + // float x 2 + checkKeyMarshal("android.lens.focusRange", + new TypeReference<Pair<Float, Float>>() {{ }}, + Pair.create(1.0f / 2.0f, 1.0f / 3.0f), + toByteArray(1.0f / 2.0f, 1.0f / 3.0f)); + + // byte, int (fake from TYPE_BYTE) + // This takes advantage of the TYPE_BYTE -> int marshaler designed for enums. + checkKeyMarshal("android.flash.mode", + new TypeReference<Pair<Byte, Integer>>() {{ }}, + Pair.create((byte)123, 22), + toByteArray((byte)123, (byte)22)); + } + + @SmallTest public void testReadWriteRange() { // int32 x 2 checkKeyMarshal("android.control.aeTargetFpsRange", diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index 36c1d5c..ec87c6e 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -88,7 +88,7 @@ public class DefaultContainerService extends IntentService { private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { /** * Creates a new container and copies resource there. - * @param paackageURI the uri of resource to be copied. Can be either + * @param packageURI the uri of resource to be copied. Can be either * a content uri or a file uri * @param cid the id of the secure container that should * be used for creating a secure container into which the resource @@ -101,13 +101,13 @@ public class DefaultContainerService extends IntentService { */ public String copyResourceToContainer(final Uri packageURI, final String cid, final String key, final String resFileName, final String publicResFileName, - boolean isExternal, boolean isForwardLocked) { + boolean isExternal, boolean isForwardLocked, String abiOverride) { if (packageURI == null || cid == null) { return null; } return copyResourceInner(packageURI, cid, key, resFileName, publicResFileName, - isExternal, isForwardLocked); + isExternal, isForwardLocked, abiOverride); } /** @@ -153,13 +153,12 @@ public class DefaultContainerService extends IntentService { /** * Determine the recommended install location for package * specified by file uri location. - * @param fileUri the uri of resource to be copied. Should be a - * file uri + * * @return Returns PackageInfoLite object containing * the package info and recommended app location. */ public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags, - long threshold) { + long threshold, String abiOverride) { PackageInfoLite ret = new PackageInfoLite(); if (packagePath == null) { @@ -191,7 +190,7 @@ public class DefaultContainerService extends IntentService { ret.verifiers = pkg.verifiers; ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, - packagePath, flags, threshold); + packagePath, flags, threshold, abiOverride); return ret; } @@ -208,11 +207,11 @@ public class DefaultContainerService extends IntentService { } @Override - public boolean checkExternalFreeStorage(Uri packageUri, boolean isForwardLocked) - throws RemoteException { + public boolean checkExternalFreeStorage(Uri packageUri, boolean isForwardLocked, + String abiOverride) throws RemoteException { final File apkFile = new File(packageUri.getPath()); try { - return isUnderExternalThreshold(apkFile, isForwardLocked); + return isUnderExternalThreshold(apkFile, isForwardLocked, abiOverride); } catch (IOException e) { return true; } @@ -265,11 +264,11 @@ public class DefaultContainerService extends IntentService { } @Override - public long calculateInstalledSize(String packagePath, boolean isForwardLocked) - throws RemoteException { + public long calculateInstalledSize(String packagePath, boolean isForwardLocked, + String abiOverride) throws RemoteException { final File packageFile = new File(packagePath); try { - return calculateContainerSize(packageFile, isForwardLocked) * 1024 * 1024; + return calculateContainerSize(packageFile, isForwardLocked, abiOverride) * 1024 * 1024; } catch (IOException e) { /* * Okay, something failed, so let's just estimate it to be 2x @@ -328,7 +327,8 @@ public class DefaultContainerService extends IntentService { } private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName, - String publicResFileName, boolean isExternal, boolean isForwardLocked) { + String publicResFileName, boolean isExternal, boolean isForwardLocked, + String abiOverride) { if (isExternal) { // Make sure the sdcard is mounted. @@ -343,7 +343,22 @@ public class DefaultContainerService extends IntentService { String codePath = packageURI.getPath(); File codeFile = new File(codePath); NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(codePath); - final int abi = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_ABIS); + String[] abiList = Build.SUPPORTED_ABIS; + if (abiOverride != null) { + abiList = new String[] { abiOverride }; + } else { + try { + if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && + NativeLibraryHelper.hasRenderscriptBitcode(handle)) { + abiList = Build.SUPPORTED_32_BIT_ABIS; + } + } catch (IOException ioe) { + Slog.w(TAG, "Problem determining ABI for: " + codeFile.getPath()); + return null; + } + } + + final int abi = NativeLibraryHelper.findSupportedAbi(handle, abiList); // Calculate size of container needed to hold base APK. final int sizeMb; @@ -414,7 +429,7 @@ public class DefaultContainerService extends IntentService { int ret = PackageManager.INSTALL_SUCCEEDED; if (abi >= 0) { ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, - sharedLibraryDir, Build.SUPPORTED_ABIS[abi]); + sharedLibraryDir, abiList[abi]); } else if (abi != PackageManager.NO_NATIVE_LIBRARIES) { ret = abi; } @@ -672,7 +687,7 @@ public class DefaultContainerService extends IntentService { private static final int PREFER_EXTERNAL = 2; private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags, - long threshold) { + long threshold, String abiOverride) { int prefer; boolean checkBoth = false; @@ -741,7 +756,7 @@ public class DefaultContainerService extends IntentService { boolean fitsOnSd = false; if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) { try { - fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked); + fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked, abiOverride); } catch (IOException e) { return PackageHelper.RECOMMEND_FAILED_INVALID_URI; } @@ -812,13 +827,13 @@ public class DefaultContainerService extends IntentService { * @return true if file fits * @throws IOException when file does not exist */ - private boolean isUnderExternalThreshold(File apkFile, boolean isForwardLocked) + private boolean isUnderExternalThreshold(File apkFile, boolean isForwardLocked, String abiOverride) throws IOException { if (Environment.isExternalStorageEmulated()) { return false; } - final int sizeMb = calculateContainerSize(apkFile, isForwardLocked); + final int sizeMb = calculateContainerSize(apkFile, isForwardLocked, abiOverride); final int availSdMb; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { @@ -832,9 +847,11 @@ public class DefaultContainerService extends IntentService { return availSdMb > sizeMb; } - private int calculateContainerSize(File apkFile, boolean forwardLocked) throws IOException { + private int calculateContainerSize(File apkFile, boolean forwardLocked, + String abiOverride) throws IOException { NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(apkFile); - final int abi = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_ABIS); + final int abi = NativeLibraryHelper.findSupportedAbi(handle, + (abiOverride != null) ? new String[] { abiOverride } : Build.SUPPORTED_ABIS); try { return calculateContainerSize(handle, apkFile, abi, forwardLocked); diff --git a/packages/Keyguard/Android.mk b/packages/Keyguard/Android.mk index 1be44f9..96ed2e7 100644 --- a/packages/Keyguard/Android.mk +++ b/packages/Keyguard/Android.mk @@ -16,8 +16,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-subdir-Iaidl-files) \ - $(call all-proto-files-under,src) +LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-subdir-Iaidl-files) LOCAL_MODULE := Keyguard @@ -27,9 +26,6 @@ LOCAL_PRIVILEGED_MODULE := true LOCAL_PROGUARD_FLAG_FILES := proguard.flags -LOCAL_PROTOC_OPTIMIZE_TYPE := nano -LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors - LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res include $(BUILD_STATIC_JAVA_LIBRARY) 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/Keyguard/src/com/android/keyguard/analytics/KeyguardAnalytics.java b/packages/Keyguard/src/com/android/keyguard/analytics/KeyguardAnalytics.java deleted file mode 100644 index 20af2f1..0000000 --- a/packages/Keyguard/src/com/android/keyguard/analytics/KeyguardAnalytics.java +++ /dev/null @@ -1,278 +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 com.android.keyguard.analytics; - -import com.google.protobuf.nano.CodedOutputByteBufferNano; -import com.google.protobuf.nano.MessageNano; - -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.os.AsyncTask; -import android.util.Log; -import android.view.MotionEvent; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * Tracks sessions, touch and sensor events in Keyguard. - * - * A session starts when the user is presented with the Keyguard and ends when the Keyguard is no - * longer visible to the user. - */ -public class KeyguardAnalytics implements SensorEventListener { - - private static final boolean DEBUG = false; - private static final String TAG = "KeyguardAnalytics"; - private static final long TIMEOUT_MILLIS = 11000; // 11 seconds. - - private static final int[] SENSORS = new int[] { - Sensor.TYPE_ACCELEROMETER, - Sensor.TYPE_GYROSCOPE, - Sensor.TYPE_PROXIMITY, - Sensor.TYPE_LIGHT, - Sensor.TYPE_ROTATION_VECTOR, - }; - - private Session mCurrentSession = null; - // Err on the side of caution, so logging is not started after a crash even tough the screen - // is off. - private boolean mScreenOn = false; - private boolean mHidden = false; - - private final SensorManager mSensorManager; - private final SessionTypeAdapter mSessionTypeAdapter; - private final File mAnalyticsFile; - - public KeyguardAnalytics(Context context, SessionTypeAdapter sessionTypeAdapter, - File analyticsFile) { - mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - mSessionTypeAdapter = sessionTypeAdapter; - mAnalyticsFile = analyticsFile; - } - - public Callback getCallback() { - return mCallback; - } - - public interface Callback { - public void onShow(); - public void onHide(); - public void onScreenOn(); - public void onScreenOff(); - public boolean onTouchEvent(MotionEvent ev, int width, int height); - public void onSetOccluded(boolean hidden); - } - - public interface SessionTypeAdapter { - public int getSessionType(); - } - - private void sessionEntrypoint() { - if (mCurrentSession == null && mScreenOn && !mHidden) { - onSessionStart(); - } - } - - private void sessionExitpoint(int result) { - if (mCurrentSession != null) { - onSessionEnd(result); - } - } - - private void onSessionStart() { - int type = mSessionTypeAdapter.getSessionType(); - mCurrentSession = new Session(System.currentTimeMillis(), System.nanoTime(), type); - if (type == Session.TYPE_KEYGUARD_SECURE) { - mCurrentSession.setRedactTouchEvents(); - } - for (int sensorType : SENSORS) { - Sensor s = mSensorManager.getDefaultSensor(sensorType); - if (s != null) { - mSensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_GAME); - } - } - if (DEBUG) { - Log.d(TAG, "onSessionStart()"); - } - } - - private void onSessionEnd(int result) { - if (DEBUG) { - Log.d(TAG, String.format("onSessionEnd(success=%d)", result)); - } - mSensorManager.unregisterListener(this); - - Session session = mCurrentSession; - mCurrentSession = null; - - session.end(System.currentTimeMillis(), result); - queueSession(session); - } - - private void queueSession(final Session currentSession) { - if (DEBUG) { - Log.i(TAG, "Saving session."); - } - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - try { - byte[] b = writeDelimitedProto(currentSession.toProto()); - OutputStream os = new FileOutputStream(mAnalyticsFile, true /* append */); - if (DEBUG) { - Log.d(TAG, String.format("Serialized size: %d kB.", b.length / 1024)); - } - try { - os.write(b); - os.flush(); - } finally { - try { - os.close(); - } catch (IOException e) { - Log.e(TAG, "Exception while closing file", e); - } - } - } catch (IOException e) { - Log.e(TAG, "Exception while writing file", e); - } - return null; - } - - private byte[] writeDelimitedProto(MessageNano proto) - throws IOException { - byte[] result = new byte[CodedOutputByteBufferNano.computeMessageSizeNoTag(proto)]; - CodedOutputByteBufferNano ob = CodedOutputByteBufferNano.newInstance(result); - ob.writeMessageNoTag(proto); - ob.checkNoSpaceLeft(); - return result; - } - }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); - } - - @Override - public synchronized void onSensorChanged(SensorEvent event) { - if (false) { - Log.v(TAG, String.format( - "onSensorChanged(name=%s, values[0]=%f)", - event.sensor.getName(), event.values[0])); - } - if (mCurrentSession != null) { - mCurrentSession.addSensorEvent(event, System.nanoTime()); - enforceTimeout(); - } - } - - private void enforceTimeout() { - if (System.currentTimeMillis() - mCurrentSession.getStartTimestampMillis() - > TIMEOUT_MILLIS) { - onSessionEnd(Session.RESULT_UNKNOWN); - if (DEBUG) { - Log.i(TAG, "Analytics timed out."); - } - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - } - - private final Callback mCallback = new Callback() { - @Override - public void onShow() { - if (DEBUG) { - Log.d(TAG, "onShow()"); - } - synchronized (KeyguardAnalytics.this) { - sessionEntrypoint(); - } - } - - @Override - public void onHide() { - if (DEBUG) { - Log.d(TAG, "onHide()"); - } - synchronized (KeyguardAnalytics.this) { - sessionExitpoint(Session.RESULT_SUCCESS); - } - } - - @Override - public void onScreenOn() { - if (DEBUG) { - Log.d(TAG, "onScreenOn()"); - } - synchronized (KeyguardAnalytics.this) { - mScreenOn = true; - sessionEntrypoint(); - } - } - - @Override - public void onScreenOff() { - if (DEBUG) { - Log.d(TAG, "onScreenOff()"); - } - synchronized (KeyguardAnalytics.this) { - mScreenOn = false; - sessionExitpoint(Session.RESULT_FAILURE); - } - } - - @Override - public boolean onTouchEvent(MotionEvent ev, int width, int height) { - if (DEBUG) { - Log.v(TAG, "onTouchEvent(ev.action=" - + MotionEvent.actionToString(ev.getAction()) + ")"); - } - synchronized (KeyguardAnalytics.this) { - if (mCurrentSession != null) { - mCurrentSession.addMotionEvent(ev); - mCurrentSession.setTouchArea(width, height); - enforceTimeout(); - } - } - return true; - } - - @Override - public void onSetOccluded(boolean hidden) { - synchronized (KeyguardAnalytics.this) { - if (hidden != mHidden) { - if (DEBUG) { - Log.d(TAG, "onSetOccluded(" + hidden + ")"); - } - mHidden = hidden; - if (hidden) { - // Could have gone to camera on purpose / by falsing or an app could have - // launched on top of the lockscreen. - sessionExitpoint(Session.RESULT_UNKNOWN); - } else { - sessionEntrypoint(); - } - } - } - } - }; - -} diff --git a/packages/Keyguard/src/com/android/keyguard/analytics/PointerTracker.java b/packages/Keyguard/src/com/android/keyguard/analytics/PointerTracker.java deleted file mode 100644 index e68f751..0000000 --- a/packages/Keyguard/src/com/android/keyguard/analytics/PointerTracker.java +++ /dev/null @@ -1,109 +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 com.android.keyguard.analytics; - -import android.graphics.RectF; -import android.util.FloatMath; -import android.util.SparseArray; -import android.view.MotionEvent; - -import java.util.HashMap; -import java.util.Map; - -import static com.android.keyguard.analytics.KeyguardAnalyticsProtos.Session.TouchEvent.BoundingBox; - -/** - * Takes motion events and tracks the length and bounding box of each pointer gesture as well as - * the bounding box of the whole gesture. - */ -public class PointerTracker { - private SparseArray<Pointer> mPointerInfoMap = new SparseArray<Pointer>(); - private RectF mTotalBoundingBox = new RectF(); - - public void addMotionEvent(MotionEvent ev) { - if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { - float x = ev.getX(); - float y = ev.getY(); - mTotalBoundingBox.set(x, y, x, y); - } - for (int i = 0; i < ev.getPointerCount(); i++) { - int id = ev.getPointerId(i); - Pointer pointer = getPointer(id); - float x = ev.getX(i); - float y = ev.getY(i); - boolean down = ev.getActionMasked() == MotionEvent.ACTION_DOWN - || (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN - && ev.getActionIndex() == i); - pointer.addPoint(x, y, down); - mTotalBoundingBox.union(x, y); - } - } - - public float getPointerLength(int id) { - return getPointer(id).length; - } - - public BoundingBox getBoundingBox() { - return boundingBoxFromRect(mTotalBoundingBox); - } - - public BoundingBox getPointerBoundingBox(int id) { - return boundingBoxFromRect(getPointer(id).boundingBox); - } - - private BoundingBox boundingBoxFromRect(RectF f) { - BoundingBox bb = new BoundingBox(); - bb.setHeight(f.height()); - bb.setWidth(f.width()); - return bb; - } - - private Pointer getPointer(int id) { - Pointer p = mPointerInfoMap.get(id); - if (p == null) { - p = new Pointer(); - mPointerInfoMap.put(id, p); - } - return p; - } - - private static class Pointer { - public float length; - public final RectF boundingBox = new RectF(); - - private float mLastX; - private float mLastY; - - public void addPoint(float x, float y, boolean down) { - float deltaX; - float deltaY; - if (down) { - boundingBox.set(x, y, x, y); - length = 0f; - deltaX = 0; - deltaY = 0; - } else { - deltaX = x - mLastX; - deltaY = y - mLastY; - } - mLastX = x; - mLastY = y; - length += FloatMath.sqrt(deltaX * deltaX + deltaY * deltaY); - boundingBox.union(x, y); - } - } -} diff --git a/packages/Keyguard/src/com/android/keyguard/analytics/Session.java b/packages/Keyguard/src/com/android/keyguard/analytics/Session.java deleted file mode 100644 index 05f9165..0000000 --- a/packages/Keyguard/src/com/android/keyguard/analytics/Session.java +++ /dev/null @@ -1,220 +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 com.android.keyguard.analytics; - -import android.os.Build; -import android.util.Slog; -import android.view.MotionEvent; - -import java.util.ArrayList; - -import static com.android.keyguard.analytics.KeyguardAnalyticsProtos.Session.SensorEvent; -import static com.android.keyguard.analytics.KeyguardAnalyticsProtos.Session.TouchEvent; - -/** - * Records data about one keyguard session. - * - * The recorded data contains start and end of the session, whether it unlocked the device - * successfully, sensor data and touch data. - * - * If the keyguard is secure, the recorded touch data will correlate or contain the user pattern or - * PIN. If this is not desired, the touch coordinates can be redacted before serialization. - */ -public class Session { - - private static final String TAG = "KeyguardAnalytics"; - private static final boolean DEBUG = false; - - /** - * The user has failed to unlock the device in this session. - */ - public static final int RESULT_FAILURE = KeyguardAnalyticsProtos.Session.FAILURE; - /** - * The user has succeeded in unlocking the device in this session. - */ - public static final int RESULT_SUCCESS = KeyguardAnalyticsProtos.Session.SUCCESS; - - /** - * It is unknown how the session with the keyguard ended. - */ - public static final int RESULT_UNKNOWN = KeyguardAnalyticsProtos.Session.UNKNOWN; - - /** - * This session took place on an insecure keyguard. - */ - public static final int TYPE_KEYGUARD_INSECURE - = KeyguardAnalyticsProtos.Session.KEYGUARD_INSECURE; - - /** - * This session took place on an secure keyguard. - */ - public static final int TYPE_KEYGUARD_SECURE - = KeyguardAnalyticsProtos.Session.KEYGUARD_SECURE; - - /** - * This session took place during a fake wake up of the device. - */ - public static final int TYPE_RANDOM_WAKEUP = KeyguardAnalyticsProtos.Session.RANDOM_WAKEUP; - - - private final PointerTracker mPointerTracker = new PointerTracker(); - - private final long mStartTimestampMillis; - private final long mStartSystemTimeNanos; - private final int mType; - - private boolean mRedactTouchEvents; - private ArrayList<TouchEvent> mMotionEvents = new ArrayList<TouchEvent>(200); - private ArrayList<SensorEvent> mSensorEvents = new ArrayList<SensorEvent>(600); - private int mTouchAreaHeight; - private int mTouchAreaWidth; - - private long mEndTimestampMillis; - private int mResult; - private boolean mEnded; - - public Session(long startTimestampMillis, long startSystemTimeNanos, int type) { - mStartTimestampMillis = startTimestampMillis; - mStartSystemTimeNanos = startSystemTimeNanos; - mType = type; - } - - public void end(long endTimestampMillis, int result) { - mEnded = true; - mEndTimestampMillis = endTimestampMillis; - mResult = result; - } - - public void addMotionEvent(MotionEvent motionEvent) { - if (mEnded) { - return; - } - mPointerTracker.addMotionEvent(motionEvent); - mMotionEvents.add(protoFromMotionEvent(motionEvent)); - } - - public void addSensorEvent(android.hardware.SensorEvent eventOrig, long systemTimeNanos) { - if (mEnded) { - return; - } - SensorEvent event = protoFromSensorEvent(eventOrig, systemTimeNanos); - mSensorEvents.add(event); - if (DEBUG) { - Slog.v(TAG, String.format("addSensorEvent(name=%s, values[0]=%f", - event.getType(), event.values[0])); - } - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("Session{"); - sb.append("mType=").append(mType); - sb.append(", mStartTimestampMillis=").append(mStartTimestampMillis); - sb.append(", mStartSystemTimeNanos=").append(mStartSystemTimeNanos); - sb.append(", mEndTimestampMillis=").append(mEndTimestampMillis); - sb.append(", mResult=").append(mResult); - sb.append(", mRedactTouchEvents=").append(mRedactTouchEvents); - sb.append(", mTouchAreaHeight=").append(mTouchAreaHeight); - sb.append(", mTouchAreaWidth=").append(mTouchAreaWidth); - sb.append(", mMotionEvents=[size=").append(mMotionEvents.size()).append("]"); - sb.append(", mSensorEvents=[size=").append(mSensorEvents.size()).append("]"); - sb.append('}'); - return sb.toString(); - } - - public KeyguardAnalyticsProtos.Session toProto() { - KeyguardAnalyticsProtos.Session proto = new KeyguardAnalyticsProtos.Session(); - proto.setStartTimestampMillis(mStartTimestampMillis); - proto.setDurationMillis(mEndTimestampMillis - mStartTimestampMillis); - proto.setBuild(Build.FINGERPRINT); - proto.setResult(mResult); - proto.sensorEvents = mSensorEvents.toArray(proto.sensorEvents); - proto.touchEvents = mMotionEvents.toArray(proto.touchEvents); - proto.setTouchAreaWidth(mTouchAreaWidth); - proto.setTouchAreaHeight(mTouchAreaHeight); - proto.setType(mType); - if (mRedactTouchEvents) { - redactTouchEvents(proto.touchEvents); - } - return proto; - } - - private void redactTouchEvents(TouchEvent[] touchEvents) { - for (int i = 0; i < touchEvents.length; i++) { - TouchEvent t = touchEvents[i]; - for (int j = 0; j < t.pointers.length; j++) { - TouchEvent.Pointer p = t.pointers[j]; - p.clearX(); - p.clearY(); - } - t.setRedacted(true); - } - } - - private SensorEvent protoFromSensorEvent(android.hardware.SensorEvent ev, long sysTimeNanos) { - SensorEvent proto = new SensorEvent(); - proto.setType(ev.sensor.getType()); - proto.setTimeOffsetNanos(sysTimeNanos - mStartSystemTimeNanos); - proto.setTimestamp(ev.timestamp); - proto.values = ev.values.clone(); - return proto; - } - - private TouchEvent protoFromMotionEvent(MotionEvent ev) { - int count = ev.getPointerCount(); - TouchEvent proto = new TouchEvent(); - proto.setTimeOffsetNanos(ev.getEventTimeNano() - mStartSystemTimeNanos); - proto.setAction(ev.getActionMasked()); - proto.setActionIndex(ev.getActionIndex()); - proto.pointers = new TouchEvent.Pointer[count]; - for (int i = 0; i < count; i++) { - TouchEvent.Pointer p = new TouchEvent.Pointer(); - p.setX(ev.getX(i)); - p.setY(ev.getY(i)); - p.setSize(ev.getSize(i)); - p.setPressure(ev.getPressure(i)); - p.setId(ev.getPointerId(i)); - proto.pointers[i] = p; - if ((ev.getActionMasked() == MotionEvent.ACTION_POINTER_UP && ev.getActionIndex() == i) - || ev.getActionMasked() == MotionEvent.ACTION_UP) { - p.boundingBox = mPointerTracker.getPointerBoundingBox(p.getId()); - p.setLength(mPointerTracker.getPointerLength(p.getId())); - } - } - if (ev.getActionMasked() == MotionEvent.ACTION_UP) { - proto.boundingBox = mPointerTracker.getBoundingBox(); - } - return proto; - } - - /** - * Discards the x / y coordinates of the touch events on serialization. Retained are the - * size of the individual and overall bounding boxes and the length of each pointer's gesture. - */ - public void setRedactTouchEvents() { - mRedactTouchEvents = true; - } - - public void setTouchArea(int width, int height) { - mTouchAreaWidth = width; - mTouchAreaHeight = height; - } - - public long getStartTimestampMillis() { - return mStartTimestampMillis; - } -} diff --git a/packages/Keyguard/src/com/android/keyguard/analytics/keyguard_analytics.proto b/packages/Keyguard/src/com/android/keyguard/analytics/keyguard_analytics.proto deleted file mode 100644 index 68b1590..0000000 --- a/packages/Keyguard/src/com/android/keyguard/analytics/keyguard_analytics.proto +++ /dev/null @@ -1,102 +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 - */ - -syntax = "proto2"; - -package keyguard; - -option java_package = "com.android.keyguard.analytics"; -option java_outer_classname = "KeyguardAnalyticsProtos"; - -message Session { - message TouchEvent { - message BoundingBox { - optional float width = 1; - optional float height = 2; - } - - enum Action { - // Keep in sync with MotionEvent. - DOWN = 0; - UP = 1; - MOVE = 2; - CANCEL = 3; - OUTSIDE = 4; - POINTER_DOWN = 5; - POINTER_UP = 6; - } - - message Pointer { - optional float x = 1; - optional float y = 2; - optional float size = 3; - optional float pressure = 4; - optional int32 id = 5; - optional float length = 6; - // Bounding box of the pointer. Only set on UP or POINTER_UP event of this pointer. - optional BoundingBox boundingBox = 7; - } - - optional uint64 timeOffsetNanos = 1; - optional Action action = 2; - optional int32 actionIndex = 3; - repeated Pointer pointers = 4; - /* If true, the the x / y coordinates of the touch events were redacted. Retained are the - size of the individual and overall bounding boxes and the length of each pointer's - gesture. */ - optional bool redacted = 5; - // Bounding box of the whole gesture. Only set on UP event. - optional BoundingBox boundingBox = 6; - } - - message SensorEvent { - enum Type { - ACCELEROMETER = 1; - GYROSCOPE = 4; - LIGHT = 5; - PROXIMITY = 8; - ROTATION_VECTOR = 11; - } - - optional Type type = 1; - optional uint64 timeOffsetNanos = 2; - repeated float values = 3; - optional uint64 timestamp = 4; - } - - enum Result { - FAILURE = 0; - SUCCESS = 1; - UNKNOWN = 2; - } - - enum Type { - KEYGUARD_INSECURE = 0; - KEYGUARD_SECURE = 1; - RANDOM_WAKEUP = 2; - } - - optional uint64 startTimestampMillis = 1; - optional uint64 durationMillis = 2; - optional string build = 3; - optional Result result = 4; - repeated TouchEvent touchEvents = 5; - repeated SensorEvent sensorEvents = 6; - - optional int32 touchAreaWidth = 9; - optional int32 touchAreaHeight = 10; - optional Type type = 11; -} 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_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 88a2c3a..e4038f9 100644 --- a/packages/SystemUI/res/drawable/ic_qs_zen_off.xml +++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.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="#4DFFFFFF" - android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/> + 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 0e933cf..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 51be1a8..477c36b 100644 --- a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml +++ b/packages/SystemUI/res/drawable/ic_vol_zen_off.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="#4DFFFFFF" - android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/> + 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 c8c217d..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 8b104d1..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/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/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/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 e6fa535..757d4ad 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -72,6 +72,8 @@ <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/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 61ed3cf..bfbdcf3 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -196,9 +196,6 @@ <dimen name="qs_dual_tile_height">109dp</dimen> <dimen name="qs_dual_tile_padding">12dp</dimen> - <!-- How far the hidden header peeks from the top of the screen when QS is in detail mode. --> - <dimen name="qs_header_peek_height">8dp</dimen> - <!-- How far the expanded QS panel peeks from the header in collapsed state. --> <dimen name="qs_peek_height">8dp</dimen> @@ -235,12 +232,21 @@ <!-- 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> @@ -299,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> @@ -311,4 +318,7 @@ <!-- Volume panel z depth --> <dimen name="volume_panel_z">3dp</dimen> + + <!-- Move distance for the hint animations on the lockscreen (unlock, phone, camera)--> + <dimen name="hint_move_distance">75dp</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 4c7f3df..b280ab7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -204,9 +204,9 @@ public class KeyguardService extends Service { } @Override - public void startKeyguardExitAnimation(long fadeoutDuration) { + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { checkPermission(); - mKeyguardViewMediator.startKeyguardExitAnimation(fadeoutDuration); + 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 7110d8d..4837a53 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -59,8 +59,6 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.MultiUserAvatarCache; import com.android.keyguard.ViewMediatorCallback; -import com.android.keyguard.analytics.KeyguardAnalytics; -import com.android.keyguard.analytics.Session; import com.android.systemui.SystemUI; import com.android.systemui.statusbar.phone.PhoneStatusBar; import com.android.systemui.statusbar.phone.ScrimController; @@ -70,7 +68,6 @@ import com.android.systemui.statusbar.phone.StatusBarWindowManager; import java.io.File; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; -import static com.android.keyguard.analytics.KeyguardAnalytics.SessionTypeAdapter; /** @@ -117,7 +114,6 @@ import static com.android.keyguard.analytics.KeyguardAnalytics.SessionTypeAdapte public class KeyguardViewMediator extends SystemUI { private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; final static boolean DEBUG = false; - private static final boolean ENABLE_ANALYTICS = Build.IS_DEBUGGABLE; private final static boolean DBG_WAKE = false; private final static String TAG = "KeyguardViewMediator"; @@ -199,8 +195,6 @@ public class KeyguardViewMediator extends SystemUI { private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private KeyguardAnalytics mKeyguardAnalytics; - // these are protected by synchronized (this) /** @@ -469,22 +463,6 @@ public class KeyguardViewMediator extends SystemUI { mViewMediatorCallback, mLockPatternUtils); final ContentResolver cr = mContext.getContentResolver(); - if (ENABLE_ANALYTICS && !LockPatternUtils.isSafeModeEnabled() && - Settings.Secure.getInt(cr, KEYGUARD_ANALYTICS_SETTING, 0) == 1) { - mKeyguardAnalytics = new KeyguardAnalytics(mContext, new SessionTypeAdapter() { - - @Override - public int getSessionType() { - return mLockPatternUtils.isSecure() && !mUpdateMonitor.getUserHasTrust( - mLockPatternUtils.getCurrentUser()) - ? Session.TYPE_KEYGUARD_SECURE - : Session.TYPE_KEYGUARD_INSECURE; - } - }, new File(mContext.getCacheDir(), "keyguard_analytics.bin")); - } else { - mKeyguardAnalytics = null; - } - mScreenOn = mPM.isScreenOn(); mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0); @@ -585,9 +563,6 @@ public class KeyguardViewMediator extends SystemUI { } else { doKeyguardLocked(null); } - if (ENABLE_ANALYTICS && mKeyguardAnalytics != null) { - mKeyguardAnalytics.getCallback().onScreenOff(); - } } KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurndOff(why); } @@ -830,9 +805,6 @@ public class KeyguardViewMediator extends SystemUI { updateActivityLockScreenState(); adjustStatusBarLocked(); } - if (ENABLE_ANALYTICS && mKeyguardAnalytics != null) { - mKeyguardAnalytics.getCallback().onSetOccluded(isOccluded); - } } } @@ -1083,7 +1055,8 @@ public class KeyguardViewMediator extends SystemUI { handleDismiss(); break; case START_KEYGUARD_EXIT_ANIM: - handleStartKeyguardExitAnimation((Long) msg.obj); + StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj; + handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration); break; } } @@ -1227,7 +1200,7 @@ public class KeyguardViewMediator extends SystemUI { } } - private void handleStartKeyguardExitAnimation(long fadeoutDuration) { + private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) { synchronized (KeyguardViewMediator.this) { // only play "unlock" noises if not on a call (since the incall UI @@ -1236,7 +1209,7 @@ public class KeyguardViewMediator extends SystemUI { playSounds(false); } - mStatusBarKeyguardViewManager.hide(); + mStatusBarKeyguardViewManager.hide(startTime, fadeoutDuration); mShowing = false; mKeyguardDonePending = false; updateActivityLockScreenState(); @@ -1346,12 +1319,24 @@ public class KeyguardViewMediator extends SystemUI { return mStatusBarKeyguardViewManager; } - public void startKeyguardExitAnimation(long fadeoutDuration) { - Message msg = mHandler.obtainMessage(START_KEYGUARD_EXIT_ANIM, fadeoutDuration); + 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 6ce0e48..2bf369a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -61,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); @@ -151,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; @@ -204,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); } @@ -231,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) { 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/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index 4db81bf..76e88a5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -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.085f; // 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 diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index 1ae7a0d..6391685 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -52,10 +52,12 @@ public class RecentsConfiguration { 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; @@ -63,12 +65,18 @@ public class RecentsConfiguration { 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; @@ -115,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 = @@ -130,11 +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 = @@ -145,6 +158,13 @@ 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); + + 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); 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 9e6c98e..c10ddd1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java @@ -18,6 +18,10 @@ package com.android.systemui.recents.views; 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; @@ -41,6 +45,8 @@ class TaskBarView extends FrameLayout { Drawable mLightDismissDrawable; Drawable mDarkDismissDrawable; + static Paint sHighlightPaint; + public TaskBarView(Context context) { this(context, null); } @@ -55,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 @@ -68,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) { 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 1fbaf87..053f122 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -645,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); @@ -652,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); 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/BounceInterpolator.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BounceInterpolator.java new file mode 100644 index 0000000..367d326 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BounceInterpolator.java @@ -0,0 +1,43 @@ +/* + * 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.systemui.statusbar.phone; + +import android.view.animation.Interpolator; + +/** + * An implementation of a bouncer interpolator optimized for unlock hinting. + */ +public class BounceInterpolator implements Interpolator { + + private final static float SCALE_FACTOR = 7.5625f; + + @Override + public float getInterpolation(float t) { + if (t < 4f / 11f) { + return SCALE_FACTOR * t * t; + } else if (t < 8f / 11f) { + float t2 = t - 6f / 11f; + return SCALE_FACTOR * t2 * t2 + 3f / 4f; + } else if (t < 10f / 11f) { + float t2 = t - 9f / 11f; + return SCALE_FACTOR * t2 * t2 + 15f / 16f; + } else { + float t2 = t - 21f / 22f; + return SCALE_FACTOR * t2 * t2 + 63f / 64f; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 994b329..97aa993 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.os.RemoteException; import android.os.UserHandle; import android.provider.MediaStore; @@ -32,6 +33,10 @@ import android.view.View; import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import android.widget.ImageView; + +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.R; /** @@ -43,6 +48,11 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView"; + private static final Intent SECURE_CAMERA_INTENT = + new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE) + .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + private static final Intent INSECURE_CAMERA_INTENT = + new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL); private ImageView mCameraImageView; @@ -51,6 +61,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private ActivityStarter mActivityStarter; private UnlockMethodCache mUnlockMethodCache; + private LockPatternUtils mLockPatternUtils; public KeyguardBottomAreaView(Context context) { super(context); @@ -72,10 +83,11 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override protected void onFinishInflate() { super.onFinishInflate(); + mLockPatternUtils = new LockPatternUtils(mContext); mCameraImageView = (ImageView) findViewById(R.id.camera_button); mPhoneImageView = (ImageView) findViewById(R.id.phone_button); mLockIcon = (ImageView) findViewById(R.id.lock_icon); - watchForDevicePolicyChanges(); + watchForCameraPolicyChanges(); watchForAccessibilityChanges(); updateCameraVisibility(); updatePhoneVisibility(); @@ -88,8 +100,19 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mActivityStarter = activityStarter; } + private Intent getCameraIntent() { + KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); + boolean currentUserHasTrust = updateMonitor.getUserHasTrust( + mLockPatternUtils.getCurrentUser()); + return mLockPatternUtils.isSecure() && !currentUserHasTrust + ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT; + } + private void updateCameraVisibility() { - boolean visible = !isCameraDisabledByDpm(); + ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(), + PackageManager.MATCH_DEFAULT_ONLY, + mLockPatternUtils.getCurrentUser()); + boolean visible = !isCameraDisabledByDpm() && resolved != null; mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE); } @@ -122,19 +145,12 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL return false; } - private void watchForDevicePolicyChanges() { + private void watchForCameraPolicyChanges() { final IntentFilter filter = new IntentFilter(); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); - getContext().registerReceiver(new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - post(new Runnable() { - @Override - public void run() { - updateCameraVisibility(); - } - }); - } - }, filter); + getContext().registerReceiverAsUser(mDevicePolicyReceiver, + UserHandle.ALL, filter, null, null); + KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback); } private void watchForAccessibilityChanges() { @@ -171,9 +187,12 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } public void launchCamera() { - mContext.startActivityAsUser( - new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE), - UserHandle.CURRENT); + Intent intent = getCameraIntent(); + if (intent == SECURE_CAMERA_INTENT) { + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } else { + mActivityStarter.startActivity(intent); + } } public void launchPhone() { @@ -186,6 +205,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL super.onVisibilityChanged(changedView, visibility); if (changedView == this && visibility == VISIBLE) { updateTrust(); + updateCameraVisibility(); } } @@ -214,5 +234,25 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override public void onMethodSecureChanged(boolean methodSecure) { updateTrust(); + updateCameraVisibility(); } + + private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + post(new Runnable() { + @Override + public void run() { + updateCameraVisibility(); + } + }); + } + }; + + private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = + new KeyguardUpdateMonitorCallback() { + @Override + public void onUserSwitchComplete(int userId) { + updateCameraVisibility(); + } + }; } 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 dce5a30..dfd5a88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -34,7 +34,6 @@ import android.view.animation.Interpolator; import android.widget.LinearLayout; import com.android.systemui.R; -import com.android.systemui.qs.QSPanel; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.GestureRecorder; @@ -46,13 +45,15 @@ 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; private StatusBarHeaderView mHeader; private View mQsContainer; - private QSPanel mQsPanel; + private View mQsPanel; private View mKeyguardStatusView; private ObservableScrollView mScrollView; private View mStackScrollerContainer; @@ -83,14 +84,14 @@ public class NotificationPanelView extends PanelView implements private int mQsMaxExpansionHeight; private int mMinStackHeight; private int mQsPeekHeight; - private int mQsHeaderPeekHeight; - private boolean mQsShowingDetail; private float mNotificationTranslation; private int mStackScrollerIntrinsicPadding; + private boolean mStackScrollerOverscrolling; private boolean mQsExpansionEnabled = true; private ValueAnimator mQsExpansionAnimator; private FlingAnimationUtils mFlingAnimationUtils; private int mStatusBarMinHeight; + private Interpolator mFastOutSlowInInterpolator; private ObjectAnimator mClockAnimator; private int mClockAnimationTarget = -1; @@ -133,13 +134,13 @@ public class NotificationPanelView extends PanelView implements mKeyguardStatusView = findViewById(R.id.keyguard_status_view); mStackScrollerContainer = findViewById(R.id.notification_container_parent); mQsContainer = findViewById(R.id.quick_settings_container); - mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel); - mQsPanel.setCallback(mQsPanelCallback); + mQsPanel = findViewById(R.id.quick_settings_panel); mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view); mScrollView.setListener(this); 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); @@ -158,7 +159,6 @@ public class NotificationPanelView extends PanelView implements mStatusBarMinHeight = getResources().getDimensionPixelSize( com.android.internal.R.dimen.status_bar_height); mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height); - mQsHeaderPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_header_peek_height); mClockPositionAlgorithm.loadDimens(getResources()); } @@ -174,7 +174,9 @@ public class NotificationPanelView extends PanelView implements setQsStackScrollerPadding(mQsMaxExpansionHeight); } } else { - setQsExpansion(mQsMinExpansionHeight); + if (!mStackScrollerOverscrolling) { + setQsExpansion(mQsMinExpansionHeight); + } positionClockAndNotifications(); mNotificationStackScroller.setStackHeight(getExpandedHeight()); } @@ -187,7 +189,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 { @@ -491,6 +496,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); } @@ -518,47 +533,20 @@ 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); mScrollView.setTouchEnabled(mQsExpanded); - if (mQsShowingDetail) { - if (mQsFullyExpanded) { - setQsHeaderPeeking(true); - } - } else { - setQsHeaderPeeking(false); - } - } - - private void setQsHeaderPeeking(boolean peeking) { - final boolean stackIsPeeking = mStackScrollerContainer.getTranslationY() != 0; - final boolean headerIsPeeking = mHeader.getTranslationY() != 0; - final int ty = mQsHeaderPeekHeight - mHeader.getExpandedHeight(); - if (peeking) { - if (!headerIsPeeking) { - mHeader.animate().translationY(ty); - } - if (!stackIsPeeking) { - mStackScrollerContainer.animate().translationY(ty); - } - } else { - if (headerIsPeeking) { - mHeader.animate().translationY(0); - } - if (stackIsPeeking) { - mStackScrollerContainer.animate().translationY(0); - } - } } private void setQsExpansion(float height) { height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); mQsFullyExpanded = height == mQsMaxExpansionHeight; - if (height > mQsMinExpansionHeight && !mQsExpanded) { + if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) { setQsExpanded(true); } else if (height <= mQsMinExpansionHeight && mQsExpanded) { setQsExpanded(false); @@ -566,7 +554,9 @@ public class NotificationPanelView extends PanelView implements mQsExpansionHeight = height; mHeader.setExpansion(height - mQsPeekHeight); setQsTranslation(height); - setQsStackScrollerPadding(height); + if (!mStackScrollerOverscrolling) { + setQsStackScrollerPadding(height); + } mStatusBar.userActivity(); } @@ -650,16 +640,10 @@ public class NotificationPanelView extends PanelView implements if (!mQsExpansionEnabled) { return false; } - final float ty = mHeader.getTranslationY(); boolean onHeader = x >= mHeader.getLeft() && x <= mHeader.getRight() - && y >= mHeader.getTop() + ty && y <= mHeader.getBottom() + ty; + && y >= mHeader.getTop() && y <= mHeader.getBottom(); if (mQsExpanded) { - if (mQsShowingDetail && onHeader) { - // bring back the header, crudely - setQsHeaderPeeking(false); - mQsPanel.setExpanded(false); - } - return !mQsShowingDetail && onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0); + return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0); } else { return onHeader; } @@ -743,8 +727,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); } @@ -823,12 +810,4 @@ public class NotificationPanelView extends PanelView implements public View getRightIcon() { return mKeyguardBottomArea.getCameraImageView(); } - - private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() { - @Override - public void onShowingDetail(boolean showingDetail) { - mQsShowingDetail = showingDetail; - updateQsState(); - } - }; } 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..4686933 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -27,10 +27,13 @@ import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.ViewConfiguration; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; import android.widget.FrameLayout; import com.android.systemui.R; import com.android.systemui.statusbar.FlingAnimationUtils; +import com.android.systemui.statusbar.StatusBarState; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -44,13 +47,16 @@ public abstract class PanelView extends FrameLayout { Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); } + protected PhoneStatusBar mStatusBar; private float mPeekHeight; + private float mHintDistance; private float mInitialOffsetOnTouch; private float mExpandedFraction = 0; private float mExpandedHeight = 0; private boolean mJustPeeked; private boolean mClosing; private boolean mTracking; + private boolean mTouchSlopExceeded; private int mTrackingPointer; protected int mTouchSlop; @@ -66,6 +72,9 @@ public abstract class PanelView extends FrameLayout { private float mInitialTouchY; private float mInitialTouchX; + private Interpolator mLinearOutSlowInInterpolator; + private Interpolator mBounceInterpolator; + protected void onExpandingFinished() { mBar.onExpandingFinished(); } @@ -89,6 +98,9 @@ public abstract class PanelView extends FrameLayout { public PanelView(Context context, AttributeSet attrs) { super(context, attrs); mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f); + mLinearOutSlowInInterpolator = + AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in); + mBounceInterpolator = new BounceInterpolator(); } protected void loadDimens() { @@ -98,6 +110,7 @@ public abstract class PanelView extends FrameLayout { final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledTouchSlop(); + mHintDistance = res.getDimension(R.dimen.hint_move_distance); } private void trackMovement(MotionEvent event) { @@ -138,6 +151,7 @@ public abstract class PanelView extends FrameLayout { mInitialTouchY = y; mInitialTouchX = x; mInitialOffsetOnTouch = mExpandedHeight; + mTouchSlopExceeded = false; if (mVelocityTracker == null) { initVelocityTracker(); } @@ -170,16 +184,18 @@ public abstract class PanelView extends FrameLayout { case MotionEvent.ACTION_MOVE: float h = y - mInitialTouchY; - if (waitForTouchSlop && !mTracking && Math.abs(h) > mTouchSlop - && Math.abs(h) > Math.abs(x - mInitialTouchX)) { - mInitialOffsetOnTouch = mExpandedHeight; - mInitialTouchX = x; - mInitialTouchY = y; - if (mHeightAnimator != null) { - mHeightAnimator.cancel(); // end any outstanding animations + if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)) { + mTouchSlopExceeded = true; + if (waitForTouchSlop && !mTracking) { + mInitialOffsetOnTouch = mExpandedHeight; + mInitialTouchX = x; + mInitialTouchY = y; + if (mHeightAnimator != null) { + mHeightAnimator.cancel(); // end any outstanding animations + } + onTrackingStarted(); + h = 0; } - onTrackingStarted(); - h = 0; } final float newHeight = h + mInitialOffsetOnTouch; if (newHeight > mPeekHeight) { @@ -200,8 +216,15 @@ public abstract class PanelView extends FrameLayout { case MotionEvent.ACTION_CANCEL: mTrackingPointer = -1; trackMovement(event); - boolean expand = flingWithCurrentVelocity(); - onTrackingStopped(expand); + if (mTracking && mTouchSlopExceeded) { + float vel = getCurrentVelocity(); + boolean expand = flingExpands(vel); + onTrackingStopped(expand); + fling(vel, expand); + } else { + boolean expands = onEmptySpaceClick(); + onTrackingStopped(expands); + } if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; @@ -262,6 +285,7 @@ public abstract class PanelView extends FrameLayout { } mInitialTouchY = y; mInitialTouchX = x; + mTouchSlopExceeded = false; initVelocityTracker(); trackMovement(event); break; @@ -285,6 +309,7 @@ public abstract class PanelView extends FrameLayout { mInitialTouchY = y; mInitialTouchX = x; mTracking = true; + mTouchSlopExceeded = true; onTrackingStarted(); return true; } @@ -323,18 +348,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,9 +364,10 @@ 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); + ValueAnimator animator = createHeightAnimator(target); if (expand) { mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight()); } else { @@ -356,12 +379,6 @@ public abstract class PanelView extends FrameLayout { animator.setDuration((long) (animator.getDuration() / 1.75f)); } } - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - setExpandedHeight((Float) animation.getAnimatedValue()); - } - }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -430,7 +447,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,16 +459,14 @@ 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) { mOverExpansion = overExpansion; } - protected void onHeightUpdated(float expandedHeight) { - requestLayout(); - } + protected abstract void onHeightUpdated(float expandedHeight); /** * This returns the maximum height of the panel. Children should override this if their @@ -526,6 +541,101 @@ public abstract class PanelView extends FrameLayout { } } + protected void startUnlockHintAnimation() { + + // We don't need to hint the user if an animation is already running or the user is changing + // the expansion. + if (mHeightAnimator != null || mTracking) { + return; + } + cancelPeek(); + onExpandingStarted(); + startUnlockHintAnimationPhase1(); + mStatusBar.onUnlockHintStarted(); + } + + /** + * Phase 1: Move everything upwards. + */ + private void startUnlockHintAnimationPhase1() { + float target = Math.max(0, getMaxPanelHeight() - mHintDistance); + ValueAnimator animator = createHeightAnimator(target); + animator.setDuration(250); + animator.setInterpolator(mLinearOutSlowInInterpolator); + animator.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled; + + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mCancelled) { + mHeightAnimator = null; + onExpandingFinished(); + mStatusBar.onUnlockHintFinished(); + } else { + startUnlockHintAnimationPhase2(); + } + } + }); + animator.start(); + mHeightAnimator = animator; + } + + /** + * Phase 2: Bounce down. + */ + private void startUnlockHintAnimationPhase2() { + ValueAnimator animator = createHeightAnimator(getMaxPanelHeight()); + animator.setDuration(450); + animator.setInterpolator(mBounceInterpolator); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mHeightAnimator = null; + onExpandingFinished(); + mStatusBar.onUnlockHintFinished(); + } + }); + animator.start(); + mHeightAnimator = animator; + } + + private ValueAnimator createHeightAnimator(float targetHeight) { + ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setExpandedHeight((Float) animation.getAnimatedValue()); + } + }); + return animator; + } + + /** + * Gets called when the user performs a click anywhere in the empty area of the panel. + * + * @return whether the panel will be expanded after the action performed by this method + */ + private boolean onEmptySpaceClick() { + switch (mStatusBar.getBarState()) { + case StatusBarState.KEYGUARD: + startUnlockHintAnimation(); + return true; + case StatusBarState.SHADE_LOCKED: + // TODO: Go to Keyguard again. + return true; + case StatusBarState.SHADE: + collapse(); + return false; + default: + return true; + } + } + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s" + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s" 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..b1216e6 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 { @@ -2940,15 +2957,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } public void onTrackingStarted() { - if (mState == StatusBarState.KEYGUARD) { - mKeyguardIndicationTextView.switchIndication(R.string.keyguard_unlock); - } + } + + public void onUnlockHintStarted() { + mKeyguardIndicationTextView.switchIndication(R.string.keyguard_unlock); + } + + public void onUnlockHintFinished() { + mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase); } public void onTrackingStopped(boolean expand) { - if (mState == StatusBarState.KEYGUARD) { - mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase); - } if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { if (!expand && !mUnlockMethodCache.isMethodInsecure()) { showBouncer(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index 1344703..7c87580 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -95,7 +95,6 @@ public class QSTileHost implements QSTile.Host { mTiles.add(new LocationTile(this)); mTiles.add(new CastTile(this)); mTiles.add(new HotspotTile(this)); - mTiles.add(new BugreportTile(this)); mUserTracker = new CurrentUserTracker(mContext) { @Override 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/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index f41ab3a..2edd7d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -715,6 +715,9 @@ public class StackStateAnimator { public void animateOverScrollToAmount(float targetAmount, final boolean onTop) { final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop); + if (targetAmount == startOverScrollAmount) { + return; + } cancelOverScrollAnimators(onTop); ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount, targetAmount); 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 cbfc641..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; @@ -42,12 +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.widget.FrameLayout; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; @@ -81,6 +86,7 @@ public class VolumePanel extends Handler { 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; @@ -105,6 +111,7 @@ 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; @@ -220,6 +227,7 @@ public class VolumePanel extends Handler { ViewGroup group; ImageView icon; SeekBar seekbarView; + View seekbarContainer; int iconRes; int iconMuteRes; } @@ -273,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(); @@ -284,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) { @@ -318,15 +327,7 @@ public class VolumePanel extends Handler { | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | LayoutParams.FLAG_HARDWARE_ACCELERATED); mDialog.setCanceledOnTouchOutside(true); - // temporary workaround for no window shadows - final FrameLayout layout = new FrameLayout(mContext); - final int z = res.getDimensionPixelSize(com.android.systemui.R.dimen.volume_panel_z); - layout.setPadding(z, z, z, z); - final View v = LayoutInflater.from(mContext).inflate(layoutId, layout, false); - v.setBackgroundResource(com.android.systemui.R.drawable.qs_panel_background); - v.setElevation(z); - layout.addView(v); - mDialog.setContentView(layout); + mDialog.setContentView(com.android.systemui.R.layout.volume_dialog); mDialog.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { @@ -336,6 +337,7 @@ 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); @@ -350,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); @@ -460,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; @@ -470,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(); @@ -493,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) { @@ -562,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 @@ -576,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) { @@ -654,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(); } /** @@ -790,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()) { @@ -822,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() { @@ -1173,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..0c16b78 100644 --- a/policy/src/com/android/internal/policy/impl/GlobalActions.java +++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java @@ -35,6 +35,7 @@ import android.database.ContentObserver; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.ConnectivityManager; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -51,6 +52,7 @@ import android.service.dreams.IDreamManager; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.TypedValue; @@ -185,7 +187,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 +265,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 +303,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,34 +317,38 @@ 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() { - return new SinglePressAction(com.android.internal.R.drawable.stat_sys_adb, - R.string.global_action_bug_report) { + return new SinglePressAction(com.android.internal.R.drawable.ic_lock_bugreport, + R.string.bugreport_title) { public void onPress() { AlertDialog.Builder builder = new AlertDialog.Builder(mContext); @@ -367,10 +378,6 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac dialog.show(); } - public boolean onLongPress() { - return false; - } - public boolean showDuringKeyguard() { return true; } @@ -378,6 +385,14 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac public boolean showBeforeProvisioning() { return false; } + + @Override + public String getStatus() { + return mContext.getString( + com.android.internal.R.string.bugreport_status, + Build.VERSION.RELEASE, + Build.ID); + } }; } @@ -393,11 +408,6 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac } @Override - public boolean onLongPress() { - return false; - } - - @Override public boolean showDuringKeyguard() { return true; } @@ -583,8 +593,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 +609,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. */ @@ -635,12 +650,12 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac return true; } - abstract public void onPress(); - - public boolean onLongPress() { - return false; + public String getStatus() { + return null; } + abstract public void onPress(); + public View create( Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { View v = inflater.inflate(R.layout.global_actions_item, parent, false); @@ -648,7 +663,13 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac ImageView icon = (ImageView) v.findViewById(R.id.icon); TextView messageView = (TextView) v.findViewById(R.id.message); - v.findViewById(R.id.status).setVisibility(View.GONE); + TextView statusView = (TextView) v.findViewById(R.id.status); + final String status = getStatus(); + if (!TextUtils.isEmpty(status)) { + statusView.setText(status); + } else { + statusView.setVisibility(View.GONE); + } if (mIcon != null) { icon.setImageDrawable(mIcon); icon.setScaleType(ScaleType.CENTER_CROP); @@ -769,10 +790,6 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac changeStateFromPress(nowOn); } - public boolean onLongPress() { - return false; - } - public boolean isEnabled() { return !mState.inTransition(); } @@ -862,10 +879,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 fb041fa..0d39586 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -1959,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() { @@ -3393,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; @@ -4613,14 +4617,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - public void startKeyguardExitAnimation(final long fadeoutDuration) { + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { if (mKeyguardDelegate != null) { - mHandler.post(new Runnable() { - @Override - public void run() { - mKeyguardDelegate.startKeyguardExitAnimation(fadeoutDuration); - } - }); + mKeyguardDelegate.startKeyguardExitAnimation(startTime, fadeoutDuration); } } @@ -5678,6 +5677,7 @@ 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); 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 faf7020..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,9 +274,9 @@ public class KeyguardServiceDelegate { mKeyguardState.currentUser = newUserId; } - public void startKeyguardExitAnimation(long fadeoutDuration) { + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { if (mKeyguardService != null) { - mKeyguardService.startKeyguardExitAnimation(fadeoutDuration); + mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration); } } 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 f236ce7..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,9 +190,9 @@ public class KeyguardServiceWrapper implements IKeyguardService { } } - public void startKeyguardExitAnimation(long fadeoutDuration) { + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { try { - mService.startKeyguardExitAnimation(fadeoutDuration); + mService.startKeyguardExitAnimation(startTime, fadeoutDuration); } catch (RemoteException e) { Slog.w(TAG , "Remote Exception", e); } 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/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 0082b1e..14c15a7 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -26,6 +26,7 @@ import android.app.PendingIntent; import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; +import android.app.backup.BackupTransport; import android.app.backup.FullBackup; import android.app.backup.RestoreSet; import android.app.backup.IBackupManager; @@ -82,7 +83,6 @@ import android.util.Slog; import android.util.SparseArray; import android.util.StringBuilderPrinter; -import com.android.internal.backup.BackupConstants; import com.android.internal.backup.IBackupTransport; import com.android.internal.backup.IObbBackupService; import com.android.server.AppWidgetBackupBridge; @@ -2098,7 +2098,7 @@ public class BackupManagerService extends IBackupManager.Stub { } mAgentBinder = null; - mStatus = BackupConstants.TRANSPORT_OK; + mStatus = BackupTransport.TRANSPORT_OK; // Sanity check: if the queue is empty we have no work to do. if (mOriginalQueue.isEmpty()) { @@ -2121,14 +2121,14 @@ public class BackupManagerService extends IBackupManager.Stub { EventLog.writeEvent(EventLogTags.BACKUP_START, transportName); // If we haven't stored package manager metadata yet, we must init the transport. - if (mStatus == BackupConstants.TRANSPORT_OK && pmState.length() <= 0) { + if (mStatus == BackupTransport.TRANSPORT_OK && pmState.length() <= 0) { Slog.i(TAG, "Initializing (wiping) backup state and transport storage"); addBackupTrace("initializing transport " + transportName); resetBackupState(mStateDir); // Just to make sure. mStatus = mTransport.initializeDevice(); addBackupTrace("transport.initializeDevice() == " + mStatus); - if (mStatus == BackupConstants.TRANSPORT_OK) { + if (mStatus == BackupTransport.TRANSPORT_OK) { EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE); } else { EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)"); @@ -2141,7 +2141,7 @@ public class BackupManagerService extends IBackupManager.Stub { // directly and use a synthetic BackupRequest. We always run this pass // because it's cheap and this way we guarantee that we don't get out of // step even if we're selecting among various transports at run time. - if (mStatus == BackupConstants.TRANSPORT_OK) { + if (mStatus == BackupTransport.TRANSPORT_OK) { PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent( mPackageManager, allAgentPackages()); mStatus = invokeAgentForBackup(PACKAGE_MANAGER_SENTINEL, @@ -2149,7 +2149,7 @@ public class BackupManagerService extends IBackupManager.Stub { addBackupTrace("PMBA invoke: " + mStatus); } - if (mStatus == BackupConstants.TRANSPORT_NOT_INITIALIZED) { + if (mStatus == BackupTransport.TRANSPORT_NOT_INITIALIZED) { // The backend reports that our dataset has been wiped. Note this in // the event log; the no-success code below will reset the backup // state as well. @@ -2158,13 +2158,13 @@ public class BackupManagerService extends IBackupManager.Stub { } catch (Exception e) { Slog.e(TAG, "Error in backup thread", e); addBackupTrace("Exception in backup thread: " + e); - mStatus = BackupConstants.TRANSPORT_ERROR; + mStatus = BackupTransport.TRANSPORT_ERROR; } finally { // If we've succeeded so far, invokeAgentForBackup() will have run the PM // metadata and its completion/timeout callback will continue the state // machine chain. If it failed that won't happen; we handle that now. addBackupTrace("exiting prelim: " + mStatus); - if (mStatus != BackupConstants.TRANSPORT_OK) { + if (mStatus != BackupTransport.TRANSPORT_OK) { // if things went wrong at this point, we need to // restage everything and try again later. resetBackupState(mStateDir); // Just to make sure. @@ -2176,7 +2176,7 @@ public class BackupManagerService extends IBackupManager.Stub { // Transport has been initialized and the PM metadata submitted successfully // if that was warranted. Now we process the single next thing in the queue. void invokeNextAgent() { - mStatus = BackupConstants.TRANSPORT_OK; + mStatus = BackupTransport.TRANSPORT_OK; addBackupTrace("invoke q=" + mQueue.size()); // Sanity check that we have work to do. If not, skip to the end where @@ -2236,39 +2236,39 @@ public class BackupManagerService extends IBackupManager.Stub { // done here as long as we're successful so far. } else { // Timeout waiting for the agent - mStatus = BackupConstants.AGENT_ERROR; + mStatus = BackupTransport.AGENT_ERROR; } } catch (SecurityException ex) { // Try for the next one. Slog.d(TAG, "error in bind/backup", ex); - mStatus = BackupConstants.AGENT_ERROR; + mStatus = BackupTransport.AGENT_ERROR; addBackupTrace("agent SE"); } } catch (NameNotFoundException e) { Slog.d(TAG, "Package does not exist; skipping"); addBackupTrace("no such package"); - mStatus = BackupConstants.AGENT_UNKNOWN; + mStatus = BackupTransport.AGENT_UNKNOWN; } finally { mWakelock.setWorkSource(null); // If there was an agent error, no timeout/completion handling will occur. // That means we need to direct to the next state ourselves. - if (mStatus != BackupConstants.TRANSPORT_OK) { + if (mStatus != BackupTransport.TRANSPORT_OK) { BackupState nextState = BackupState.RUNNING_QUEUE; mAgentBinder = null; // An agent-level failure means we reenqueue this one agent for // a later retry, but otherwise proceed normally. - if (mStatus == BackupConstants.AGENT_ERROR) { + if (mStatus == BackupTransport.AGENT_ERROR) { if (MORE_DEBUG) Slog.i(TAG, "Agent failure for " + request.packageName + " - restaging"); dataChangedImpl(request.packageName); - mStatus = BackupConstants.TRANSPORT_OK; + mStatus = BackupTransport.TRANSPORT_OK; if (mQueue.isEmpty()) nextState = BackupState.FINAL; - } else if (mStatus == BackupConstants.AGENT_UNKNOWN) { + } else if (mStatus == BackupTransport.AGENT_UNKNOWN) { // Failed lookup of the app, so we couldn't bring up an agent, but // we're otherwise fine. Just drop it and go on to the next as usual. - mStatus = BackupConstants.TRANSPORT_OK; + mStatus = BackupTransport.TRANSPORT_OK; } else { // Transport-level failure means we reenqueue everything revertAndEndBackup(); @@ -2297,7 +2297,7 @@ public class BackupManagerService extends IBackupManager.Stub { // If everything actually went through and this is the first time we've // done a backup, we can now record what the current backup dataset token // is. - if ((mCurrentToken == 0) && (mStatus == BackupConstants.TRANSPORT_OK)) { + if ((mCurrentToken == 0) && (mStatus == BackupTransport.TRANSPORT_OK)) { addBackupTrace("success; recording token"); try { mCurrentToken = mTransport.getCurrentRestoreSet(); @@ -2314,7 +2314,7 @@ public class BackupManagerService extends IBackupManager.Stub { // state machine sequence and the wakelock is refcounted. synchronized (mQueueLock) { mBackupRunning = false; - if (mStatus == BackupConstants.TRANSPORT_NOT_INITIALIZED) { + if (mStatus == BackupTransport.TRANSPORT_NOT_INITIALIZED) { // Make sure we back up everything and perform the one-time init clearMetadata(); if (DEBUG) Slog.d(TAG, "Server requires init; rerunning"); @@ -2395,7 +2395,7 @@ public class BackupManagerService extends IBackupManager.Stub { EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName, e.toString()); agentErrorCleanup(); - return BackupConstants.AGENT_ERROR; + return BackupTransport.AGENT_ERROR; } // At this point the agent is off and running. The next thing to happen will @@ -2403,7 +2403,7 @@ public class BackupManagerService extends IBackupManager.Stub { // for transport, or a timeout. Either way the next phase will happen in // response to the TimeoutHandler interface callbacks. addBackupTrace("invoke success"); - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } public void failAgent(IBackupAgent agent, String message) { @@ -2484,11 +2484,11 @@ public class BackupManagerService extends IBackupManager.Stub { addBackupTrace("operation complete"); ParcelFileDescriptor backupData = null; - mStatus = BackupConstants.TRANSPORT_OK; + mStatus = BackupTransport.TRANSPORT_OK; try { int size = (int) mBackupDataName.length(); if (size > 0) { - if (mStatus == BackupConstants.TRANSPORT_OK) { + if (mStatus == BackupTransport.TRANSPORT_OK) { backupData = ParcelFileDescriptor.open(mBackupDataName, ParcelFileDescriptor.MODE_READ_ONLY); addBackupTrace("sending data to transport"); @@ -2501,7 +2501,7 @@ public class BackupManagerService extends IBackupManager.Stub { // renaming *all* the output state files (see below) until that happens. addBackupTrace("data delivered: " + mStatus); - if (mStatus == BackupConstants.TRANSPORT_OK) { + if (mStatus == BackupTransport.TRANSPORT_OK) { addBackupTrace("finishing op on transport"); mStatus = mTransport.finishBackup(); addBackupTrace("finished: " + mStatus); @@ -2514,7 +2514,7 @@ public class BackupManagerService extends IBackupManager.Stub { // After successful transport, delete the now-stale data // and juggle the files so that next time we supply the agent // with the new state file it just created. - if (mStatus == BackupConstants.TRANSPORT_OK) { + if (mStatus == BackupTransport.TRANSPORT_OK) { mBackupDataName.delete(); mNewStateName.renameTo(mSavedStateName); EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size); @@ -2525,7 +2525,7 @@ public class BackupManagerService extends IBackupManager.Stub { } catch (Exception e) { Slog.e(TAG, "Transport error backing up " + pkgName, e); EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName); - mStatus = BackupConstants.TRANSPORT_ERROR; + mStatus = BackupTransport.TRANSPORT_ERROR; } finally { try { if (backupData != null) backupData.close(); } catch (IOException e) {} } @@ -2533,7 +2533,7 @@ public class BackupManagerService extends IBackupManager.Stub { // If we encountered an error here it's a transport-level failure. That // means we need to halt everything and reschedule everything for next time. final BackupState nextState; - if (mStatus != BackupConstants.TRANSPORT_OK) { + if (mStatus != BackupTransport.TRANSPORT_OK) { revertAndEndBackup(); nextState = BackupState.FINAL; } else { @@ -4847,7 +4847,7 @@ public class BackupManagerService extends IBackupManager.Stub { mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT); // Assume error until we successfully init everything - mStatus = BackupConstants.TRANSPORT_ERROR; + mStatus = BackupTransport.TRANSPORT_ERROR; try { // TODO: Log this before getAvailableRestoreSets, somehow @@ -4902,7 +4902,7 @@ public class BackupManagerService extends IBackupManager.Stub { return; } - mStatus = BackupConstants.TRANSPORT_OK; + mStatus = BackupTransport.TRANSPORT_OK; executeNextState(RestoreState.DOWNLOAD_DATA); } @@ -4917,7 +4917,7 @@ public class BackupManagerService extends IBackupManager.Stub { try { mStatus = mTransport.startRestore(mToken, mRestorePackages.toArray(new PackageInfo[0])); - if (mStatus != BackupConstants.TRANSPORT_OK) { + if (mStatus != BackupTransport.TRANSPORT_OK) { Slog.e(TAG, "Error starting restore operation"); EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); executeNextState(RestoreState.FINAL); @@ -4926,7 +4926,7 @@ public class BackupManagerService extends IBackupManager.Stub { } catch (RemoteException e) { Slog.e(TAG, "Error communicating with transport for restore"); EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); - mStatus = BackupConstants.TRANSPORT_ERROR; + mStatus = BackupTransport.TRANSPORT_ERROR; executeNextState(RestoreState.FINAL); return; } @@ -4941,14 +4941,14 @@ public class BackupManagerService extends IBackupManager.Stub { if (packageName == null) { Slog.e(TAG, "Error getting first restore package"); EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); - mStatus = BackupConstants.TRANSPORT_ERROR; + mStatus = BackupTransport.TRANSPORT_ERROR; executeNextState(RestoreState.FINAL); return; } else if (packageName.equals("")) { Slog.i(TAG, "No restore data available"); int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime); EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, 0, millis); - mStatus = BackupConstants.TRANSPORT_OK; + mStatus = BackupTransport.TRANSPORT_OK; executeNextState(RestoreState.FINAL); return; } else if (!packageName.equals(PACKAGE_MANAGER_SENTINEL)) { @@ -4979,7 +4979,7 @@ public class BackupManagerService extends IBackupManager.Stub { Slog.e(TAG, "No restore metadata available, so not restoring settings"); EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, PACKAGE_MANAGER_SENTINEL, "Package manager restore metadata missing"); - mStatus = BackupConstants.TRANSPORT_ERROR; + mStatus = BackupTransport.TRANSPORT_ERROR; mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this); executeNextState(RestoreState.FINAL); return; @@ -4987,7 +4987,7 @@ public class BackupManagerService extends IBackupManager.Stub { } catch (RemoteException e) { Slog.e(TAG, "Error communicating with transport for restore"); EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); - mStatus = BackupConstants.TRANSPORT_ERROR; + mStatus = BackupTransport.TRANSPORT_ERROR; mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this); executeNextState(RestoreState.FINAL); return; @@ -5118,7 +5118,7 @@ public class BackupManagerService extends IBackupManager.Stub { } } catch (RemoteException e) { Slog.e(TAG, "Unable to fetch restore data from transport"); - mStatus = BackupConstants.TRANSPORT_ERROR; + mStatus = BackupTransport.TRANSPORT_ERROR; executeNextState(RestoreState.FINAL); } } @@ -5206,7 +5206,7 @@ public class BackupManagerService extends IBackupManager.Stub { Slog.e(TAG, "SElinux restorecon failed for " + downloadFile); } - if (mTransport.getRestoreData(stage) != BackupConstants.TRANSPORT_OK) { + if (mTransport.getRestoreData(stage) != BackupTransport.TRANSPORT_OK) { // Transport-level failure, so we wind everything up and // terminate the restore operation. Slog.e(TAG, "Error getting restore data for " + packageName); @@ -5450,12 +5450,12 @@ public class BackupManagerService extends IBackupManager.Stub { long startRealtime = SystemClock.elapsedRealtime(); int status = transport.initializeDevice(); - if (status == BackupConstants.TRANSPORT_OK) { + if (status == BackupTransport.TRANSPORT_OK) { status = transport.finishBackup(); } // Okay, the wipe really happened. Clean up our local bookkeeping. - if (status == BackupConstants.TRANSPORT_OK) { + if (status == BackupTransport.TRANSPORT_OK) { Slog.i(TAG, "Device init successful"); int millis = (int) (SystemClock.elapsedRealtime() - startRealtime); EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index d7a19ad..b2b4217 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -38,7 +38,6 @@ import static android.net.ConnectivityManager.TYPE_WIMAX; import static android.net.ConnectivityManager.TYPE_PROXY; import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.ConnectivityManager.isNetworkTypeValid; -import static android.net.ConnectivityServiceProtocol.NetworkFactoryProtocol; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; @@ -80,6 +79,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkConfig; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; +import android.net.NetworkFactory; import android.net.NetworkQuotaInfo; import android.net.NetworkRequest; import android.net.NetworkState; @@ -258,17 +258,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private NetworkStateTracker mNetTrackers[]; - /** - * Holds references to all NetworkAgentInfos claiming to support the legacy - * NetworkType. We used to have a static set of of NetworkStateTrackers - * for each network type. This is the new model. - * Supports synchronous inspection of state. - * These are built out at startup such that an unsupported network - * doesn't get an ArrayList instance, making this a tristate: - * unsupported, supported but not active and active. - */ - private ArrayList<NetworkAgentInfo> mNetworkAgentInfoForType[]; - /* Handles captive portal check on a network */ private CaptivePortalTracker mCaptivePortalTracker; @@ -516,6 +505,118 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final int UID_UNUSED = -1; + /** + * Implements support for the legacy "one network per network type" model. + * + * We used to have a static array of NetworkStateTrackers, one for each + * network type, but that doesn't work any more now that we can have, + * for example, more that one wifi network. This class stores all the + * NetworkAgentInfo objects that support a given type, but the legacy + * API will only see the first one. + * + * It serves two main purposes: + * + * 1. Provide information about "the network for a given type" (since this + * API only supports one). + * 2. Send legacy connectivity change broadcasts. Broadcasts are sent if + * the first network for a given type changes, or if the default network + * changes. + */ + private class LegacyTypeTracker { + /** + * Array of lists, one per legacy network type (e.g., TYPE_MOBILE_MMS). + * Each list holds references to all NetworkAgentInfos that are used to + * satisfy requests for that network type. + * + * This array is built out at startup such that an unsupported network + * doesn't get an ArrayList instance, making this a tristate: + * unsupported, supported but not active and active. + * + * The actual lists are populated when we scan the network types that + * are supported on this device. + */ + private ArrayList<NetworkAgentInfo> mTypeLists[]; + + public LegacyTypeTracker() { + mTypeLists = (ArrayList<NetworkAgentInfo>[]) + new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1]; + } + + public void addSupportedType(int type) { + if (mTypeLists[type] != null) { + throw new IllegalStateException( + "legacy list for type " + type + "already initialized"); + } + mTypeLists[type] = new ArrayList<NetworkAgentInfo>(); + } + + private boolean isDefaultNetwork(NetworkAgentInfo nai) { + return mNetworkForRequestId.get(mDefaultRequest.requestId) == nai; + } + + public boolean isTypeSupported(int type) { + return isNetworkTypeValid(type) && mTypeLists[type] != null; + } + + public NetworkAgentInfo getNetworkForType(int type) { + if (isTypeSupported(type) && !mTypeLists[type].isEmpty()) { + return mTypeLists[type].get(0); + } else { + return null; + } + } + + public void add(int type, NetworkAgentInfo nai) { + if (!isTypeSupported(type)) { + return; // Invalid network type. + } + if (VDBG) log("Adding agent " + nai + " for legacy network type " + type); + + ArrayList<NetworkAgentInfo> list = mTypeLists[type]; + if (list.contains(nai)) { + loge("Attempting to register duplicate agent for type " + type + ": " + nai); + return; + } + + if (list.isEmpty() || isDefaultNetwork(nai)) { + if (VDBG) log("Sending connected broadcast for type " + type + + "isDefaultNetwork=" + isDefaultNetwork(nai)); + sendLegacyNetworkBroadcast(nai, true, type); + } + list.add(nai); + } + + public void remove(NetworkAgentInfo nai) { + if (VDBG) log("Removing agent " + nai); + for (int type = 0; type < mTypeLists.length; type++) { + ArrayList<NetworkAgentInfo> list = mTypeLists[type]; + if (list == null || list.isEmpty()) { + continue; + } + + boolean wasFirstNetwork = false; + if (list.get(0).equals(nai)) { + // This network was the first in the list. Send broadcast. + wasFirstNetwork = true; + } + list.remove(nai); + + if (wasFirstNetwork || isDefaultNetwork(nai)) { + if (VDBG) log("Sending disconnected broadcast for type " + type + + "isDefaultNetwork=" + isDefaultNetwork(nai)); + sendLegacyNetworkBroadcast(nai, false, type); + } + + if (!list.isEmpty() && wasFirstNetwork) { + if (VDBG) log("Other network available for type " + type + + ", sending connected broadcast"); + sendLegacyNetworkBroadcast(list.get(0), false, type); + } + } + } + } + private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(); + public ConnectivityService(Context context, INetworkManagementService netd, INetworkStatsService statsService, INetworkPolicyManager policyManager) { // Currently, omitting a NetworkFactory will create one internally @@ -531,7 +632,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addNetworkCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); netCap.addNetworkCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); - mDefaultRequest = new NetworkRequest(netCap, true, nextNetworkRequestId()); + mDefaultRequest = new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId()); NetworkRequestInfo nri = new NetworkRequestInfo(null, mDefaultRequest, new Binder(), NetworkRequestInfo.REQUEST); mNetworkRequests.put(mDefaultRequest, nri); @@ -587,9 +688,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetTransitionWakeLockTimeout = mContext.getResources().getInteger( com.android.internal.R.integer.config_networkTransitionTimeout); - mNetworkAgentInfoForType = (ArrayList<NetworkAgentInfo>[]) - new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1]; - mNetTrackers = new NetworkStateTracker[ ConnectivityManager.MAX_NETWORK_TYPE+1]; mCurrentLinkProperties = new LinkProperties[ConnectivityManager.MAX_NETWORK_TYPE+1]; @@ -644,7 +742,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { "radio " + n.radio + " in network type " + n.type); continue; } - mNetworkAgentInfoForType[n.type] = new ArrayList<NetworkAgentInfo>(); + mLegacyTypeTracker.addSupportedType(n.type); mNetConfigs[n.type] = n; mNetworksDefined++; @@ -2843,7 +2941,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - private int getRestoreDefaultNetworkDelay(int networkType) { + @Override + public int getRestoreDefaultNetworkDelay(int networkType) { String restoreDefaultNetworkDelayStr = SystemProperties.get( NETWORK_RESTORE_DELAY_PROP_NAME); if(restoreDefaultNetworkDelayStr != null && @@ -2994,6 +3093,16 @@ public class ConnectivityService extends IConnectivityManager.Stub { updateNetworkInfo(nai, info); break; } + case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: { + NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); + if (nai == null) { + loge("EVENT_NETWORK_SCORE_CHANGED from unknown NetworkAgent"); + break; + } + Integer score = (Integer) msg.obj; + updateNetworkScore(nai, score); + break; + } case NetworkMonitor.EVENT_NETWORK_VALIDATED: { NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj; handleConnectionValidated(nai); @@ -3098,7 +3207,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { for (NetworkRequestInfo nri : mNetworkRequests.values()) { if (nri.isRequest == false) continue; NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId); - ac.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, + ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, (nai != null ? nai.currentScore : 0), 0, nri.request); } } else { @@ -3114,11 +3223,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else { loge("Error connecting NetworkAgent"); NetworkAgentInfo nai = mNetworkAgentInfos.remove(msg.replyTo); - try { - mNetworkAgentInfoForType[nai.networkInfo.getType()].remove(nai); - } catch (NullPointerException e) {} if (nai != null) { mNetworkForNetId.remove(nai.network.netId); + mLegacyTypeTracker.remove(nai); } } } @@ -3137,14 +3244,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { } catch (Exception e) { loge("Exception removing network: " + e); } + // TODO - if we move the logic to the network agent (have them disconnect + // because they lost all their requests or because their score isn't good) + // then they would disconnect organically, report their new state and then + // disconnect the channel. + if (nai.networkInfo.isConnected()) { + nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, + null, null); + } notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST); nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED); mNetworkAgentInfos.remove(msg.replyTo); updateClat(null, nai.linkProperties, nai); - try { - mNetworkAgentInfoForType[nai.networkInfo.getType()].remove(nai); - } catch (NullPointerException e) {} - + mLegacyTypeTracker.remove(nai); mNetworkForNetId.remove(nai.network.netId); // Since we've lost the network, go through all the requests that // it was satisfying and see if any other factory can satisfy them. @@ -3154,7 +3266,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId); if (VDBG) { log(" checking request " + request + ", currentNetwork = " + - currentNetwork != null ? currentNetwork.name() : "null"); + (currentNetwork != null ? currentNetwork.name() : "null")); } if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) { mNetworkForRequestId.remove(request.requestId); @@ -3203,7 +3315,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { } if (bestNetwork != null) { if (VDBG) log("using " + bestNetwork.name()); - bestNetwork.networkRequests.put(nri.request.requestId, nri.request); + bestNetwork.addRequest(nri.request); + int legacyType = nri.request.legacyType; + if (legacyType != TYPE_NONE) { + mLegacyTypeTracker.add(legacyType, bestNetwork); + } notifyNetworkCallback(bestNetwork, nri); score = bestNetwork.currentScore; } @@ -3211,7 +3327,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (msg.what == EVENT_REGISTER_NETWORK_REQUEST) { if (DBG) log("sending new NetworkRequest to factories"); for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { - nfi.asyncChannel.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, score, 0, nri.request); + nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, + 0, nri.request); } } } @@ -3233,7 +3350,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (nri.isRequest) { for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { - nfi.asyncChannel.sendMessage(NetworkFactoryProtocol.CMD_CANCEL_REQUEST, nri.request); + nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST, + nri.request); } if (affectedNetwork != null) { @@ -5279,7 +5397,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities, - Messenger messenger, int timeoutSec, IBinder binder) { + Messenger messenger, int timeoutSec, IBinder binder, int legacyType) { if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) == false) { enforceConnectivityInternalPermission(); @@ -5291,7 +5409,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { throw new IllegalArgumentException("Bad timeout specified"); } NetworkRequest networkRequest = new NetworkRequest(new NetworkCapabilities( - networkCapabilities), false, nextNetworkRequestId()); + networkCapabilities), legacyType, nextNetworkRequestId()); if (DBG) log("requestNetwork for " + networkRequest); NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder, NetworkRequestInfo.REQUEST); @@ -5317,7 +5435,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { enforceAccessPermission(); NetworkRequest networkRequest = new NetworkRequest(new NetworkCapabilities( - networkCapabilities), false, nextNetworkRequestId()); + networkCapabilities), TYPE_NONE, nextNetworkRequestId()); if (DBG) log("listenForNetwork for " + networkRequest); NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder, NetworkRequestInfo.LISTEN); @@ -5392,18 +5510,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), nextNetId(), new NetworkInfo(networkInfo), new LinkProperties(linkProperties), new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler); - + if (VDBG) log("registerNetworkAgent " + nai); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai)); } private void handleRegisterNetworkAgent(NetworkAgentInfo na) { if (VDBG) log("Got NetworkAgent Messenger"); mNetworkAgentInfos.put(na.messenger, na); - try { - mNetworkAgentInfoForType[na.networkInfo.getType()].add(na); - } catch (NullPointerException e) { - loge("registered NetworkAgent for unsupported type: " + na); - } mNetworkForNetId.put(na.network.netId, na); na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger); NetworkInfo networkInfo = na.networkInfo; @@ -5439,7 +5552,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mClat.stopClat(); } // If the link requires clat to be running, then start the daemon now. - if (newLp != null && na.networkInfo.isConnected()) { + if (na.networkInfo.isConnected()) { mClat.startClat(na); } else { mClat.stopClat(); @@ -5555,7 +5668,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, int score) { if (VDBG) log("sending new Min Network Score(" + score + "): " + networkRequest.toString()); for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { - nfi.asyncChannel.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, score, 0, networkRequest); + nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, 0, + networkRequest); } } @@ -5658,7 +5772,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (VDBG) log(" accepting network in place of null"); } mNetworkForRequestId.put(nri.request.requestId, newNetwork); - newNetwork.networkRequests.put(nri.request.requestId, nri.request); + newNetwork.addRequest(nri.request); + int legacyType = nri.request.legacyType; + if (legacyType != TYPE_NONE) { + mLegacyTypeTracker.add(legacyType, newNetwork); + } keep = true; // TODO - this could get expensive if we have alot of requests for this // network. Think about if there is a way to reduce this. Push @@ -5672,6 +5790,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else { setDefaultDnsSystemProperties(new ArrayList<InetAddress>()); } + mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork); } } } @@ -5792,6 +5911,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + private void updateNetworkScore(NetworkAgentInfo nai, Integer scoreInteger) { + int score = scoreInteger.intValue(); + // TODO + } + // notify only this one new request of the current state protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) { int notifyType = ConnectivityManager.CALLBACK_AVAILABLE; @@ -5801,93 +5925,88 @@ public class ConnectivityService extends IConnectivityManager.Stub { // } else if (nai.networkMonitor.isEvaluating()) { // notifyType = NetworkCallbacks.callCallbackForRequest(request, nai, notifyType); // } - if (nri.request.needsBroadcasts) { - // TODO -// sendNetworkBroadcast(nai, notifyType); - } callCallbackForRequest(nri, nai, notifyType); } + private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, boolean connected, int type) { + if (connected) { + NetworkInfo info = new NetworkInfo(nai.networkInfo); + info.setType(type); + sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay()); + } else { + NetworkInfo info = new NetworkInfo(nai.networkInfo); + info.setType(type); + Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); + if (info.isFailover()) { + intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); + nai.networkInfo.setFailover(false); + } + if (info.getReason() != null) { + intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason()); + } + if (info.getExtraInfo() != null) { + intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); + } + NetworkAgentInfo newDefaultAgent = null; + if (nai.networkRequests.get(mDefaultRequest.requestId) != null) { + newDefaultAgent = mNetworkForRequestId.get(mDefaultRequest.requestId); + if (newDefaultAgent != null) { + intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, + newDefaultAgent.networkInfo); + } else { + intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); + } + } + intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, + mDefaultInetConditionPublished); + final Intent immediateIntent = new Intent(intent); + immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE); + sendStickyBroadcast(immediateIntent); + sendStickyBroadcastDelayed(intent, getConnectivityChangeDelay()); + if (newDefaultAgent != null) { + sendConnectedBroadcastDelayed(newDefaultAgent.networkInfo, + getConnectivityChangeDelay()); + } + } + } + protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType) { if (VDBG) log("notifyType " + notifyType + " for " + networkAgent.name()); - boolean needsBroadcasts = false; for (int i = 0; i < networkAgent.networkRequests.size(); i++) { NetworkRequest nr = networkAgent.networkRequests.valueAt(i); NetworkRequestInfo nri = mNetworkRequests.get(nr); if (VDBG) log(" sending notification for " + nr); - if (nr.needsBroadcasts) needsBroadcasts = true; callCallbackForRequest(nri, networkAgent, notifyType); } - if (needsBroadcasts) { - if (notifyType == ConnectivityManager.CALLBACK_AVAILABLE) { - sendConnectedBroadcastDelayed(networkAgent.networkInfo, - getConnectivityChangeDelay()); - } else if (notifyType == ConnectivityManager.CALLBACK_LOST) { - NetworkInfo info = new NetworkInfo(networkAgent.networkInfo); - Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); - intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); - intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); - if (info.isFailover()) { - intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); - networkAgent.networkInfo.setFailover(false); - } - if (info.getReason() != null) { - intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason()); - } - if (info.getExtraInfo() != null) { - intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); - } - NetworkAgentInfo newDefaultAgent = null; - if (networkAgent.networkRequests.get(mDefaultRequest.requestId) != null) { - newDefaultAgent = mNetworkForRequestId.get(mDefaultRequest.requestId); - if (newDefaultAgent != null) { - intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, - newDefaultAgent.networkInfo); - } else { - intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); - } - } - intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, - mDefaultInetConditionPublished); - final Intent immediateIntent = new Intent(intent); - immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE); - sendStickyBroadcast(immediateIntent); - sendStickyBroadcastDelayed(intent, getConnectivityChangeDelay()); - if (newDefaultAgent != null) { - sendConnectedBroadcastDelayed(newDefaultAgent.networkInfo, - getConnectivityChangeDelay()); - } - } - } } private LinkProperties getLinkPropertiesForTypeInternal(int networkType) { - ArrayList<NetworkAgentInfo> list = mNetworkAgentInfoForType[networkType]; - if (list == null) return null; - try { - return new LinkProperties(list.get(0).linkProperties); - } catch (IndexOutOfBoundsException e) { - return new LinkProperties(); - } + NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); + return (nai != null) ? + new LinkProperties(nai.linkProperties) : + new LinkProperties(); } private NetworkInfo getNetworkInfoForType(int networkType) { - ArrayList<NetworkAgentInfo> list = mNetworkAgentInfoForType[networkType]; - if (list == null) return null; - try { - return new NetworkInfo(list.get(0).networkInfo); - } catch (IndexOutOfBoundsException e) { - return new NetworkInfo(networkType, 0, "Unknown", ""); + if (!mLegacyTypeTracker.isTypeSupported(networkType)) + return null; + + NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); + if (nai != null) { + NetworkInfo result = new NetworkInfo(nai.networkInfo); + result.setType(networkType); + return result; + } else { + return new NetworkInfo(networkType, 0, "Unknown", ""); } } private NetworkCapabilities getNetworkCapabilitiesForType(int networkType) { - ArrayList<NetworkAgentInfo> list = mNetworkAgentInfoForType[networkType]; - if (list == null) return null; - try { - return new NetworkCapabilities(list.get(0).networkCapabilities); - } catch (IndexOutOfBoundsException e) { - return new NetworkCapabilities(); - } + NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); + return (nai != null) ? + new NetworkCapabilities(nai.networkCapabilities) : + new NetworkCapabilities(); } } 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/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 eebb503..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) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 61ba6e0..aede797 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2978,6 +2978,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } buf.append("}"); + if (requiredAbi != null) { + buf.append(" abi="); + buf.append(requiredAbi); + } Slog.i(TAG, buf.toString()); app.setPid(startResult.pid); app.usingWrapper = startResult.usingWrapper; diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 16ad153..ba12374 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -2537,7 +2537,9 @@ final class ActivityStack { + " who=" + r.resultWho + " req=" + r.requestCode + " res=" + resultCode + " data=" + resultData); if (resultTo.userId != r.userId) { - resultData.prepareToLeaveUser(r.userId); + if (resultData != null) { + resultData.prepareToLeaveUser(r.userId); + } } if (r.info.applicationInfo.uid > 0) { mService.grantUriPermissionFromIntentLocked(r.info.applicationInfo.uid, diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 8102591..b03c247 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -45,7 +45,6 @@ public class NetworkAgentInfo { public int currentScore; public final NetworkMonitor networkMonitor; - // The list of NetworkRequests being satisfied by this Network. public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>(); public final ArrayList<NetworkRequest> networkLingered = new ArrayList<NetworkRequest>(); @@ -66,6 +65,10 @@ public class NetworkAgentInfo { networkMonitor = new NetworkMonitor(context, handler, this); } + public void addRequest(NetworkRequest networkRequest) { + networkRequests.put(networkRequest.requestId, networkRequest); + } + public String toString() { return "NetworkAgentInfo{ ni{" + networkInfo + "} network{" + network + "} lp{" + diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index d0b716d..5141d16 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -505,7 +505,7 @@ final class HdmiCecController { // Reply <Feature Abort> to initiator (source) for all requests. HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand( sourceAddress, message.getSource(), message.getOpcode(), - HdmiCecMessageBuilder.ABORT_REFUSED); + HdmiConstants.ABORT_REFUSED); sendCommand(cecMessage, null); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java index 9a76734..6c2be34 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -26,15 +26,6 @@ import java.util.Arrays; * A helper class to build {@link HdmiCecMessage} from various cec commands. */ public class HdmiCecMessageBuilder { - // TODO: move these values to HdmiCec.java once make it internal constant class. - // CEC's ABORT reason values. - static final int ABORT_UNRECOGNIZED_MODE = 0; - static final int ABORT_NOT_IN_CORRECT_MODE = 1; - static final int ABORT_CANNOT_PROVIDE_SOURCE = 2; - static final int ABORT_INVALID_OPERAND = 3; - static final int ABORT_REFUSED = 4; - static final int ABORT_UNABLE_TO_DETERMINE = 5; - private static final int OSD_NAME_MAX_LENGTH = 13; private HdmiCecMessageBuilder() {} @@ -290,6 +281,64 @@ public class HdmiCecMessageBuilder { } /** + * Build <System Audio Mode Request> command. + * + * @param src source address of command + * @param avr destination address of command, it should be AVR + * @param avrPhysicalAddress physical address of AVR + * @param enableSystemAudio whether to enable System Audio Mode or not + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildSystemAudioModeRequest(int src, int avr, int avrPhysicalAddress, + boolean enableSystemAudio) { + if (enableSystemAudio) { + return buildCommand(src, avr, HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST, + physicalAddressToParam(avrPhysicalAddress)); + } else { + return buildCommand(src, avr, HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST); + } + } + + /** + * Build <Give Audio Status> command. + * + * @param src source address of command + * @param dest destination address of command + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildGiveAudioStatus(int src, int dest) { + return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_AUDIO_STATUS); + } + + /** + * Build <User Control Pressed> command. + * + * @param src source address of command + * @param dest destination address of command + * @param uiCommand keycode that user pressed + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildUserControlPressed(int src, int dest, int uiCommand) { + byte[] params = new byte[] { + (byte) uiCommand + }; + return buildCommand(src, dest, HdmiCec.MESSAGE_USER_CONTROL_PRESSED, params); + } + + /** + * Build <User Control Released> command. + * + * @param src source address of command + * @param dest destination address of command + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildUserControlReleased(int src, int dest) { + return buildCommand(src, dest, HdmiCec.MESSAGE_USER_CONTROL_RELEASED); + } + + /***** Please ADD new buildXXX() methods above. ******/ + + /** * Build a {@link HdmiCecMessage} without extra parameter. * * @param src source address of command diff --git a/services/core/java/com/android/server/hdmi/HdmiConstants.java b/services/core/java/com/android/server/hdmi/HdmiConstants.java new file mode 100644 index 0000000..a83d1ed --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HdmiConstants.java @@ -0,0 +1,47 @@ +/* + * 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.hdmi; + +/** + * Defines constants related to HDMI-CEC protocol internal implementation. + * If a constant will be used in the public api, it should be located in + * {@link android.hardware.hdmi.HdmiCec}. + */ +final class HdmiConstants { + + // Constants related to operands of HDMI CEC commands. + // Refer to CEC Table 29 in HDMI Spec v1.4b. + // [Abort Reason] + static final int ABORT_UNRECOGNIZED_MODE = 0; + static final int ABORT_NOT_IN_CORRECT_MODE = 1; + static final int ABORT_CANNOT_PROVIDE_SOURCE = 2; + static final int ABORT_INVALID_OPERAND = 3; + static final int ABORT_REFUSED = 4; + static final int ABORT_UNABLE_TO_DETERMINE = 5; + + // [Audio Status] + static final int SYSTEM_AUDIO_STATUS_OFF = 0; + static final int SYSTEM_AUDIO_STATUS_ON = 1; + + // Constants related to UI Command Codes. + // Refer to CEC Table 30 in HDMI Spec v1.4b. + static final int UI_COMMAND_MUTE = 0x43; + static final int UI_COMMAND_MUTE_FUNCTION = 0x65; + static final int UI_COMMAND_RESTORE_VOLUME_FUNCTION = 0x66; + + private HdmiConstants() { /* cannot be instantiated */ } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index d775733..0f3fc21 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -118,8 +118,11 @@ public final class HdmiControlService extends SystemService { // TODO: it may need to hold lock if it's accessed from others. private boolean mArcStatusEnabled = false; + // Whether SystemAudioMode is "On" or not. + private boolean mSystemAudioMode; + // Handler running on service thread. It's used to run a task in service thread. - private Handler mHandler = new Handler(); + private final Handler mHandler = new Handler(); public HdmiControlService(Context context) { super(context); @@ -158,6 +161,9 @@ public final class HdmiControlService extends SystemService { } publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); + + // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and + // start to monitor the preference value and invoke SystemAudioActionFromTv if needed. } /** @@ -211,35 +217,43 @@ public final class HdmiControlService extends SystemService { * @param action {@link FeatureAction} to remove */ void removeAction(final FeatureAction action) { - runOnServiceThread(new Runnable() { - @Override - public void run() { - mActions.remove(action); - } - }); + assertRunOnServiceThread(); + mActions.remove(action); } // Remove all actions matched with the given Class type. private <T extends FeatureAction> void removeAction(final Class<T> clazz) { - runOnServiceThread(new Runnable() { - @Override - public void run() { - Iterator<FeatureAction> iter = mActions.iterator(); - while (iter.hasNext()) { - FeatureAction action = iter.next(); - if (action.getClass().equals(clazz)) { - action.clear(); - mActions.remove(action); - } - } + removeActionExcept(clazz, null); + } + + // Remove all actions matched with the given Class type besides |exception|. + <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, + final FeatureAction exception) { + assertRunOnServiceThread(); + Iterator<FeatureAction> iter = mActions.iterator(); + while (iter.hasNext()) { + FeatureAction action = iter.next(); + if (action != exception && action.getClass().equals(clazz)) { + action.clear(); + mActions.remove(action); } - }); + } } private void runOnServiceThread(Runnable runnable) { mHandler.post(runnable); } + void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { + mHandler.postAtFrontOfQueue(runnable); + } + + private void assertRunOnServiceThread() { + if (Looper.myLooper() != mHandler.getLooper()) { + throw new IllegalStateException("Should run on service thread."); + } + } + /** * Change ARC status into the given {@code enabled} status. * @@ -306,8 +320,12 @@ public final class HdmiControlService extends SystemService { 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. + case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE: + handleSetSystemAudioMode(message); + return true; + case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: + handleSystemAudioModeStatus(message); + return true; default: return dispatchMessageToAction(message); } @@ -413,7 +431,7 @@ public final class HdmiControlService extends SystemService { sendCecCommand( HdmiCecMessageBuilder.buildFeatureAbortCommand(message.getDestination(), message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE, - HdmiCecMessageBuilder.ABORT_UNRECOGNIZED_MODE)); + HdmiConstants.ABORT_UNRECOGNIZED_MODE)); return; } @@ -438,6 +456,33 @@ public final class HdmiControlService extends SystemService { return false; } + private void handleSetSystemAudioMode(HdmiCecMessage message) { + if (dispatchMessageToAction(message) || !isMessageForSystemAudio(message)) { + return; + } + SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, + message.getDestination(), message.getSource(), + HdmiUtils.parseCommandParamSystemAudioStatus(message)); + addAndStartAction(action); + } + + private void handleSystemAudioModeStatus(HdmiCecMessage message) { + if (!isMessageForSystemAudio(message)) { + return; + } + setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); + } + + private boolean isMessageForSystemAudio(HdmiCecMessage message) { + if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM + || message.getDestination() != HdmiCec.ADDR_TV + || getAvrDeviceInfo() == null) { + Slog.w(TAG, "Skip abnormal CecMessage: " + message); + return false; + } + return true; + } + // Record class that monitors the event of the caller of being killed. Used to clean up // the listener list and record list accordingly. private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { @@ -627,4 +672,32 @@ public final class HdmiControlService extends SystemService { Slog.e(TAG, "Invoking callback failed:" + e); } } + + HdmiCecDeviceInfo getAvrDeviceInfo() { + return mCecController.getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM); + } + + void setSystemAudioMode(boolean newMode) { + assertRunOnServiceThread(); + if (newMode != mSystemAudioMode) { + // TODO: Need to set the preference for SystemAudioMode. + // TODO: Need to handle the notification of changing the mode and + // to identify the notification should be handled in the service or TvSettings. + mSystemAudioMode = newMode; + } + } + + boolean getSystemAudioMode() { + assertRunOnServiceThread(); + return mSystemAudioMode; + } + + void setAudioStatus(boolean mute, int volume) { + // TODO: Hook up with AudioManager. + } + + boolean isInPresetInstallationMode() { + // TODO: Implement this. + return false; + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java new file mode 100644 index 0000000..ef128ed1 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HdmiUtils.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.server.hdmi; + +import android.hardware.hdmi.HdmiCec; +import android.hardware.hdmi.HdmiCecMessage; +import android.util.Slog; + +/** + * Various utilities to handle HDMI CEC messages. + */ +final class HdmiUtils { + + private HdmiUtils() { /* cannot be instantiated */ } + + /** + * Verify if the given address is for the given device type. If not it will throw + * {@link IllegalArgumentException}. + * + * @param logicalAddress the logical address to verify + * @param deviceType the device type to check + * @throw IllegalArgumentException + */ + static void verifyAddressType(int logicalAddress, int deviceType) { + int actualDeviceType = HdmiCec.getTypeFromAddress(logicalAddress); + if (actualDeviceType != deviceType) { + throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType + + ", Actual:" + actualDeviceType); + } + } + + /** + * Check if the given CEC message come from the given address. + * + * @param cmd the CEC message to check + * @param expectedAddress the expected source address of the given message + * @param tag the tag of caller module (for log message) + * @return true if the CEC message comes from the given address + */ + static boolean checkCommandSource(HdmiCecMessage cmd, int expectedAddress, String tag) { + int src = cmd.getSource(); + if (src != expectedAddress) { + Slog.w(tag, "Invalid source [Expected:" + expectedAddress + ", Actual:" + src + "]"); + return false; + } + return true; + } + + /** + * Parse the parameter block of CEC message as [System Audio Status]. + * + * @param cmd the CEC message to parse + * @return true if the given parameter has [ON] value + */ + static boolean parseCommandParamSystemAudioStatus(HdmiCecMessage cmd) { + // TODO: Handle the exception when the length is wrong. + return cmd.getParams().length > 0 + && cmd.getParams()[0] == HdmiConstants.SYSTEM_AUDIO_STATUS_ON; + } +} diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java index 05614a4..08ca306 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java @@ -44,28 +44,15 @@ abstract class RequestArcAction extends FeatureAction { */ RequestArcAction(HdmiControlService service, int sourceAddress, int avrAddress) { super(service, sourceAddress); - verifyAddressType(sourceAddress, HdmiCec.DEVICE_TV); - verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM); + HdmiUtils.verifyAddressType(sourceAddress, HdmiCec.DEVICE_TV); + HdmiUtils.verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM); mAvrAddress = avrAddress; } - private static void verifyAddressType(int logicalAddress, int deviceType) { - int actualDeviceType = HdmiCec.getTypeFromAddress(logicalAddress); - if (actualDeviceType != deviceType) { - throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType - + ", Actual:" + actualDeviceType); - } - } - @Override boolean processCommand(HdmiCecMessage cmd) { - if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE) { - return false; - } - - int src = cmd.getSource(); - if (src != mAvrAddress) { - Slog.w(TAG, "Invalid source [Expected:" + mAvrAddress + ", Actual:" + src + "]"); + if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE + || !HdmiUtils.checkCommandSource(cmd, mAvrAddress, TAG)) { return false; } int opcode = cmd.getOpcode(); diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java index e3525d8..d53d88d 100644 --- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java +++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java @@ -46,21 +46,12 @@ final class SetArcTransmissionStateAction extends FeatureAction { SetArcTransmissionStateAction(HdmiControlService service, int sourceAddress, int avrAddress, boolean enabled) { super(service, sourceAddress); - verifyAddressType(sourceAddress, HdmiCec.DEVICE_TV); - verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM); + HdmiUtils.verifyAddressType(sourceAddress, HdmiCec.DEVICE_TV); + HdmiUtils.verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM); mAvrAddress = avrAddress; mEnabled = enabled; } - // TODO: extract it as separate utility class. - private static void verifyAddressType(int logicalAddress, int deviceType) { - int actualDeviceType = HdmiCec.getTypeFromAddress(logicalAddress); - if (actualDeviceType != deviceType) { - throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType - + ", Actual:" + actualDeviceType); - } - } - @Override boolean start() { if (mEnabled) { diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java new file mode 100644 index 0000000..dde3342 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java @@ -0,0 +1,192 @@ +/* + * 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.hdmi; + +import android.hardware.hdmi.HdmiCec; +import android.hardware.hdmi.HdmiCecMessage; + +/** + * Base feature action class for SystemAudioActionFromTv and SystemAudioActionFromAvr. + */ +abstract class SystemAudioAction extends FeatureAction { + private static final String TAG = "SystemAudioAction"; + + // State in which waits for <SetSystemAudioMode>. + private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 1; + + // State in which waits for <ReportAudioStatus>. + private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 2; + + private static final int MAX_SEND_RETRY_COUNT = 2; + + private static final int ON_TIMEOUT_MS = 5000; + private static final int OFF_TIMEOUT_MS = TIMEOUT_MS; + + // Logical address of AV Receiver. + protected final int mAvrLogicalAddress; + + // The target audio status of the action, whether to enable the system audio mode or not. + protected boolean mTargetAudioStatus; + + private int mSendRetryCount = 0; + + /** + * Constructor + * + * @param service {@link HdmiControlService} instance + * @param sourceAddress logical address of source device (TV or STB). + * @param avrAddress logical address of AVR device + * @param targetStatus Whether to enable the system audio mode or not + * @throw IllegalArugmentException if device type of sourceAddress and avrAddress is invalid + */ + SystemAudioAction(HdmiControlService service, int sourceAddress, int avrAddress, + boolean targetStatus) { + super(service, sourceAddress); + HdmiUtils.verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM); + mAvrLogicalAddress = avrAddress; + mTargetAudioStatus = targetStatus; + } + + protected void sendSystemAudioModeRequest() { + int avrPhysicalAddress = mService.getAvrDeviceInfo().getPhysicalAddress(); + HdmiCecMessage command = HdmiCecMessageBuilder.buildSystemAudioModeRequest(mSourceAddress, + mAvrLogicalAddress, avrPhysicalAddress, mTargetAudioStatus); + sendCommand(command, new HdmiControlService.SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + if (error == HdmiControlService.SEND_RESULT_SUCCESS) { + mState = STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE; + addTimer(mState, mTargetAudioStatus ? ON_TIMEOUT_MS : OFF_TIMEOUT_MS); + } else { + setSystemAudioMode(false); + finish(); + } + } + }); + } + + private void handleSendSystemAudioModeRequestTimeout() { + if (!mTargetAudioStatus // Don't retry for Off case. + || mSendRetryCount++ >= MAX_SEND_RETRY_COUNT) { + setSystemAudioMode(false); + finish(); + return; + } + sendSystemAudioModeRequest(); + } + + protected void setSystemAudioMode(boolean mode) { + mService.setSystemAudioMode(mode); + } + + protected void sendGiveAudioStatus() { + HdmiCecMessage command = HdmiCecMessageBuilder.buildGiveAudioStatus(mSourceAddress, + mAvrLogicalAddress); + sendCommand(command, new HdmiControlService.SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + if (error == HdmiControlService.SEND_RESULT_SUCCESS) { + mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS; + addTimer(mState, TIMEOUT_MS); + } else { + handleSendGiveAudioStatusFailure(); + } + } + }); + } + + private void handleSendGiveAudioStatusFailure() { + // TODO: Notify the failure status. + + int uiCommand = mService.getSystemAudioMode() + ? HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION // SystemAudioMode: ON + : HdmiConstants.UI_COMMAND_MUTE_FUNCTION; // SystemAudioMode: OFF + sendUserControlPressedAndReleased(uiCommand); + finish(); + } + + private void sendUserControlPressedAndReleased(int uiCommand) { + sendCommand(HdmiCecMessageBuilder.buildUserControlPressed( + mSourceAddress, mAvrLogicalAddress, uiCommand)); + sendCommand(HdmiCecMessageBuilder.buildUserControlReleased( + mSourceAddress, mAvrLogicalAddress)); + } + + @Override + final boolean processCommand(HdmiCecMessage cmd) { + switch (mState) { + case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE: + // TODO: Handle <FeatureAbort> of <SystemAudioModeRequest> + if (cmd.getOpcode() != HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE + || !HdmiUtils.checkCommandSource(cmd, mAvrLogicalAddress, TAG)) { + return false; + } + boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd); + if (receivedStatus == mTargetAudioStatus) { + setSystemAudioMode(receivedStatus); + sendGiveAudioStatus(); + } else { + // Unexpected response, consider the request is newly initiated by AVR. + // To return 'false' will initiate new SystemAudioActionFromAvr by the control + // service. + finish(); + return false; + } + return true; + + case STATE_WAIT_FOR_REPORT_AUDIO_STATUS: + // TODO: Handle <FeatureAbort> of <GiveAudioStatus> + if (cmd.getOpcode() != HdmiCec.MESSAGE_REPORT_AUDIO_STATUS + || !HdmiUtils.checkCommandSource(cmd, mAvrLogicalAddress, TAG)) { + return false; + } + byte[] params = cmd.getParams(); + if (params.length > 0) { + boolean mute = (params[0] & 0x80) == 0x80; + int volume = params[0] & 0x7F; + mService.setAudioStatus(mute, volume); + if (mTargetAudioStatus && mute || !mTargetAudioStatus && !mute) { + // Toggle AVR's mute status to match with the system audio status. + sendUserControlPressedAndReleased(HdmiConstants.UI_COMMAND_MUTE); + } + } + finish(); + return true; + } + return false; + } + + protected void removeSystemAudioActionInProgress() { + mService.removeActionExcept(SystemAudioActionFromTv.class, this); + mService.removeActionExcept(SystemAudioActionFromAvr.class, this); + } + + @Override + final void handleTimerEvent(int state) { + if (mState != state) { + return; + } + switch (mState) { + case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE: + handleSendSystemAudioModeRequestTimeout(); + return; + case STATE_WAIT_FOR_REPORT_AUDIO_STATUS: + handleSendGiveAudioStatusFailure(); + return; + } + } +} diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java new file mode 100644 index 0000000..c5eb44b --- /dev/null +++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java @@ -0,0 +1,69 @@ +/* + * 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.hdmi; + +import android.hardware.hdmi.HdmiCec; + +/** + * Feature action that handles System Audio initiated by AVR devices. + */ +final class SystemAudioActionFromAvr extends SystemAudioAction { + /** + * Constructor + * + * @param service {@link HdmiControlService} instance + * @param tvAddress logical address of TV device + * @param avrAddress logical address of AVR device + * @param targetStatus Whether to enable the system audio mode or not + * @throw IllegalArugmentException if device type of tvAddress and avrAddress is invalid + */ + SystemAudioActionFromAvr(HdmiControlService service, int tvAddress, int avrAddress, + boolean targetStatus) { + super(service, tvAddress, avrAddress, targetStatus); + HdmiUtils.verifyAddressType(tvAddress, HdmiCec.DEVICE_TV); + } + + @Override + boolean start() { + removeSystemAudioActionInProgress(); + handleSystemAudioActionFromAvr(); + return true; + } + + private void handleSystemAudioActionFromAvr() { + if (mTargetAudioStatus == mService.getSystemAudioMode()) { + finish(); + return; + } + if (mService.isInPresetInstallationMode()) { + sendCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand( + mSourceAddress, mAvrLogicalAddress, + HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE, HdmiConstants.ABORT_REFUSED)); + mTargetAudioStatus = false; + sendSystemAudioModeRequest(); + return; + } + // TODO: Stop the action for System Audio Mode initialization if it is running. + if (mTargetAudioStatus) { + setSystemAudioMode(true); + sendGiveAudioStatus(); + } else { + setSystemAudioMode(false); + finish(); + } + } +} diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java new file mode 100644 index 0000000..9994de6 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java @@ -0,0 +1,50 @@ +/* + * 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.hdmi; + +import android.hardware.hdmi.HdmiCec; + + +/** + * Feature action that handles System Audio initiated by TV devices. + */ +final class SystemAudioActionFromTv extends SystemAudioAction { + /** + * Constructor + * + * @param service {@link HdmiControlService} instance + * @param tvAddress logical address of TV device + * @param avrAddress logical address of AVR device + * @param targetStatus Whether to enable the system audio mode or not + * @throw IllegalArugmentException if device type of tvAddress is invalid + */ + SystemAudioActionFromTv(HdmiControlService service, int tvAddress, int avrAddress, + boolean targetStatus) { + super(service, tvAddress, avrAddress, targetStatus); + HdmiUtils.verifyAddressType(tvAddress, HdmiCec.DEVICE_TV); + } + + @Override + boolean start() { + // TODO: Check HDMI-CEC is enabled. + // TODO: Move to the waiting state if currently a routing change is in progress. + + removeSystemAudioActionInProgress(); + sendSystemAudioModeRequest(); + return true; + } +} diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 030e3ed..737ffda 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -25,6 +25,7 @@ import android.media.session.ISessionControllerCallback; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.media.session.MediaController; +import android.media.session.RemoteVolumeProvider; import android.media.session.RouteCommand; import android.media.session.RouteInfo; import android.media.session.RouteOptions; @@ -33,6 +34,7 @@ import android.media.session.MediaSession; import android.media.session.MediaSessionInfo; import android.media.session.RouteInterface; import android.media.session.PlaybackState; +import android.media.AudioManager; import android.media.MediaMetadata; import android.media.Rating; import android.os.Bundle; @@ -66,13 +68,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 @@ -112,6 +114,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private long mLastActiveTime; // End TransportPerformer fields + // Volume handling fields + private int mPlaybackType = MediaSession.VOLUME_TYPE_LOCAL; + private int mAudioStream = AudioManager.STREAM_MUSIC; + private int mVolumeControlType = RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE; + private int mMaxVolume = 0; + private int mCurrentVolume = 0; + // End volume handling fields + private boolean mIsActive = false; private boolean mDestroyed = false; @@ -248,6 +258,27 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } /** + * Send a volume adjustment to the session owner. + * + * @param delta The amount to adjust the volume by. + */ + public void adjustVolumeBy(int delta) { + if (mVolumeControlType == RemoteVolumeProvider.VOLUME_CONTROL_FIXED) { + // Nothing to do, the volume cannot be changed + return; + } + mSessionCb.adjustVolumeBy(delta); + } + + public void setVolumeTo(int value) { + if (mVolumeControlType != RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE) { + // Nothing to do. The volume can't be set directly. + return; + } + mSessionCb.setVolumeTo(value); + } + + /** * Set the connection to use for the selected route and notify the app it is * now connected. * @@ -294,14 +325,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { * Check if the session is currently performing playback. This will also * return true if the session was recently paused. * + * @param includeRecentlyActive True if playback that was recently paused + * should count, false if it shouldn't. * @return True if the session is performing playback, false otherwise. */ - public boolean isPlaybackActive() { + public boolean isPlaybackActive(boolean includeRecentlyActive) { int state = mPlaybackState == null ? 0 : mPlaybackState.getState(); if (isActiveState(state)) { return true; } - if (state == mPlaybackState.PLAYSTATE_PAUSED) { + if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) { long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime; if (inactiveTime < ACTIVE_BUFFER) { return true; @@ -311,6 +344,54 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } /** + * Get the type of playback, either local or remote. + * + * @return The current type of playback. + */ + public int getPlaybackType() { + return mPlaybackType; + } + + /** + * Get the local audio stream being used. Only valid if playback type is + * local. + * + * @return The audio stream the session is using. + */ + public int getAudioStream() { + return mAudioStream; + } + + /** + * Get the type of volume control. Only valid if playback type is remote. + * + * @return The volume control type being used. + */ + public int getVolumeControl() { + return mVolumeControlType; + } + + /** + * Get the max volume that can be set. Only valid if playback type is + * remote. + * + * @return The max volume that can be set. + */ + public int getMaxVolume() { + return mMaxVolume; + } + + /** + * Get the current volume for this session. Only valid if playback type is + * remote. + * + * @return The current volume of the remote playback. + */ + public int getCurrentVolume() { + return mCurrentVolume; + } + + /** * @return True if this session is currently connected to a route. */ public boolean isConnected() { @@ -509,12 +590,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 +603,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 +669,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; @@ -640,6 +721,40 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { mRequests.add(request); } } + + @Override + public void setCurrentVolume(int volume) { + mCurrentVolume = volume; + } + + @Override + public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException { + switch(type) { + case MediaSession.VOLUME_TYPE_LOCAL: + mPlaybackType = type; + int audioStream = arg1; + if (isValidStream(audioStream)) { + mAudioStream = audioStream; + } else { + Log.e(TAG, "Cannot set stream to " + audioStream + ". Using music stream"); + mAudioStream = AudioManager.STREAM_MUSIC; + } + break; + case MediaSession.VOLUME_TYPE_REMOTE: + mPlaybackType = type; + mVolumeControlType = arg1; + mMaxVolume = arg2; + break; + default: + throw new IllegalArgumentException("Volume handling type " + type + + " not recognized."); + } + } + + private boolean isValidStream(int stream) { + return stream >= AudioManager.STREAM_VOICE_CALL + && stream <= AudioManager.STREAM_NOTIFICATION; + } } class SessionCb { @@ -649,14 +764,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) { @@ -778,6 +895,22 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { Slog.e(TAG, "Remote failure in rate.", e); } } + + public void adjustVolumeBy(int delta) { + try { + mCb.onAdjustVolumeBy(delta); + } catch (RemoteException e) { + Slog.e(TAG, "Remote failure in adjustVolumeBy.", e); + } + } + + public void setVolumeTo(int value) { + try { + mCb.onSetVolumeTo(value); + } catch (RemoteException e) { + Slog.e(TAG, "Remote failure in adjustVolumeBy.", e); + } + } } class ControllerStub extends ISessionController.Stub { @@ -788,8 +921,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/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 9d85167..87665e1 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -26,6 +26,8 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.media.AudioManager; +import android.media.IAudioService; import android.media.routeprovider.RouteRequest; import android.media.session.ISession; import android.media.session.ISessionCallback; @@ -40,6 +42,7 @@ import android.os.IBinder; import android.os.PowerManager; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; import android.speech.RecognizerIntent; @@ -79,6 +82,7 @@ public class MediaSessionService extends SystemService implements Monitor { private final PowerManager.WakeLock mMediaEventWakeLock; private KeyguardManager mKeyguardManager; + private IAudioService mAudioService; private MediaSessionRecord mPrioritySession; private int mCurrentUserId = -1; @@ -105,6 +109,12 @@ public class MediaSessionService extends SystemService implements Monitor { updateUser(); mKeyguardManager = (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE); + mAudioService = getAudioService(); + } + + private IAudioService getAudioService() { + IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); + return IAudioService.Stub.asInterface(b); } /** @@ -703,6 +713,23 @@ public class MediaSessionService extends SystemService implements Monitor { } @Override + public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags) + throws RemoteException { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + MediaSessionRecord session = mPriorityStack + .getDefaultVolumeSession(mCurrentUserId); + dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -737,6 +764,49 @@ public class MediaSessionService extends SystemService implements Monitor { } } + private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags, + MediaSessionRecord session) { + int direction = 0; + int steps = delta; + if (delta > 0) { + direction = 1; + } else if (delta < 0) { + direction = -1; + steps = -delta; + } + if (DEBUG) { + String sessionInfo = session == null ? null : session.getSessionInfo().toString(); + Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags + + ", suggestedStream=" + suggestedStream); + + } + if (session == null) { + for (int i = 0; i < steps; i++) { + try { + mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, + flags, getContext().getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error adjusting default volume.", e); + } + } + } else { + if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_LOCAL) { + for (int i = 0; i < steps; i++) { + try { + mAudioService.adjustSuggestedStreamVolume(direction, + session.getAudioStream(), flags, + getContext().getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error adjusting volume for stream " + + session.getAudioStream(), e); + } + } + } else if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_REMOTE) { + session.adjustVolumeBy(delta); + } + } + } + private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock, MediaSessionRecord session) { if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) { diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java index 7ba9212..803dee2 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>(); @@ -52,6 +52,7 @@ public class MediaSessionStack { private MediaSessionRecord mCachedButtonReceiver; private MediaSessionRecord mCachedDefault; + private MediaSessionRecord mCachedVolumeDefault; private ArrayList<MediaSessionRecord> mCachedActiveList; private ArrayList<MediaSessionRecord> mCachedTransportControlList; @@ -93,6 +94,9 @@ public class MediaSessionStack { mSessions.remove(record); mSessions.add(0, record); clearCache(); + } else if (newState == PlaybackState.STATE_PAUSED) { + // Just clear the volume cache in this case + mCachedVolumeDefault = null; } } @@ -177,6 +181,25 @@ public class MediaSessionStack { return mCachedButtonReceiver; } + public MediaSessionRecord getDefaultVolumeSession(int userId) { + if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) { + return mGlobalPrioritySession; + } + if (mCachedVolumeDefault != null) { + return mCachedVolumeDefault; + } + ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId); + int size = records.size(); + for (int i = 0; i < size; i++) { + MediaSessionRecord record = records.get(i); + if (record.isPlaybackActive(false)) { + mCachedVolumeDefault = record; + return record; + } + } + return null; + } + public void dump(PrintWriter pw, String prefix) { ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0, UserHandle.USER_ALL); @@ -237,7 +260,7 @@ public class MediaSessionStack { lastLocalIndex++; lastActiveIndex++; lastPublishedIndex++; - } else if (session.isPlaybackActive()) { + } else if (session.isPlaybackActive(true)) { // TODO replace getRoute() == null with real local route check if(session.getRoute() == null) { // Active local sessions get top priority @@ -284,6 +307,7 @@ public class MediaSessionStack { private void clearCache() { mCachedDefault = null; + mCachedVolumeDefault = null; mCachedButtonReceiver = null; mCachedActiveList = null; mCachedTransportControlList = null; 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 a3858d0..4ac2dcc 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -28,8 +28,6 @@ import android.text.TextUtils; import android.util.LruCache; import android.util.Slog; -import com.android.server.notification.NotificationManagerService.NotificationRecord; - import java.util.ArrayList; import java.util.LinkedList; @@ -51,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; 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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 13cc98c..bb93663 100755 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -40,6 +40,7 @@ import com.android.internal.R; import com.android.internal.app.IMediaContainerService; import com.android.internal.app.ResolverActivity; import com.android.internal.content.NativeLibraryHelper; +import com.android.internal.content.NativeLibraryHelper.ApkHandle; import com.android.internal.content.PackageHelper; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; @@ -4148,7 +4149,7 @@ public class PackageManagerService extends IPackageManager.Stub { continue; } PackageParser.Package pkg = scanPackageLI(file, - flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime, null); + flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime, null, null); // Don't mess around with apps in system partition. if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 && mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) { @@ -4215,7 +4216,7 @@ public class PackageManagerService extends IPackageManager.Stub { * Returns null in case of errors and the error code is stored in mLastScanError */ private PackageParser.Package scanPackageLI(File scanFile, - int parseFlags, int scanMode, long currentTime, UserHandle user) { + int parseFlags, int scanMode, long currentTime, UserHandle user, String abiOverride) { mLastScanError = PackageManager.INSTALL_SUCCEEDED; String scanPath = scanFile.getPath(); if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanPath); @@ -4283,7 +4284,7 @@ public class PackageManagerService extends IPackageManager.Stub { mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; return null; } else { - // The current app on the system partion is better than + // The current app on the system partition is better than // what we have updated to on the data partition; switch // back to the system partition version. // At this point, its safely assumed that package installation for @@ -4402,7 +4403,7 @@ public class PackageManagerService extends IPackageManager.Stub { setApplicationInfoPaths(pkg, codePath, resPath); // Note that we invoke the following method only if we are about to unpack an application PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanMode - | SCAN_UPDATE_SIGNATURE, currentTime, user); + | SCAN_UPDATE_SIGNATURE, currentTime, user, abiOverride); /* * If the system app should be overridden by a previously installed @@ -4945,7 +4946,7 @@ public class PackageManagerService extends IPackageManager.Stub { } private PackageParser.Package scanPackageLI(PackageParser.Package pkg, - int parseFlags, int scanMode, long currentTime, UserHandle user) { + int parseFlags, int scanMode, long currentTime, UserHandle user, String abiOverride) { File scanFile = new File(pkg.mScanPath); if (scanFile == null || pkg.applicationInfo.sourceDir == null || pkg.applicationInfo.publicSourceDir == null) { @@ -5395,7 +5396,22 @@ public class PackageManagerService extends IPackageManager.Stub { * only for non-system apps and system app upgrades. */ if (pkg.applicationInfo.nativeLibraryDir != null) { + final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(scanFile); try { + // Enable gross and lame hacks for apps that are built with old + // SDK tools. We must scan their APKs for renderscript bitcode and + // not launch them if it's present. Don't bother checking on devices + // that don't have 64 bit support. + String[] abiList = Build.SUPPORTED_ABIS; + boolean hasLegacyRenderscriptBitcode = false; + if (abiOverride != null) { + abiList = new String[] { abiOverride }; + } else if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && + NativeLibraryHelper.hasRenderscriptBitcode(handle)) { + abiList = Build.SUPPORTED_32_BIT_ABIS; + hasLegacyRenderscriptBitcode = true; + } + File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); final String dataPathString = dataPath.getCanonicalPath(); @@ -5411,21 +5427,26 @@ public class PackageManagerService extends IPackageManager.Stub { Log.i(TAG, "removed obsolete native libraries for system package " + path); } - - setInternalAppAbi(pkg, pkgSetting); + if (abiOverride != null || hasLegacyRenderscriptBitcode) { + pkg.applicationInfo.cpuAbi = abiList[0]; + pkgSetting.cpuAbiString = abiList[0]; + } else { + setInternalAppAbi(pkg, pkgSetting); + } } else { if (!isForwardLocked(pkg) && !isExternal(pkg)) { /* - * Update native library dir if it starts with - * /data/data - */ + * Update native library dir if it starts with + * /data/data + */ if (nativeLibraryDir.getPath().startsWith(dataPathString)) { setInternalAppNativeLibraryPath(pkg, pkgSetting); nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); } try { - int copyRet = copyNativeLibrariesForInternalApp(scanFile, nativeLibraryDir); + int copyRet = copyNativeLibrariesForInternalApp(handle, + nativeLibraryDir, abiList); if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { Slog.e(TAG, "Unable to copy native libraries"); mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; @@ -5435,7 +5456,9 @@ public class PackageManagerService extends IPackageManager.Stub { // We've successfully copied native libraries across, so we make a // note of what ABI we're using if (copyRet != PackageManager.NO_NATIVE_LIBRARIES) { - pkg.applicationInfo.cpuAbi = Build.SUPPORTED_ABIS[copyRet]; + pkg.applicationInfo.cpuAbi = abiList[copyRet]; + } else if (abiOverride != null || hasLegacyRenderscriptBitcode) { + pkg.applicationInfo.cpuAbi = abiList[0]; } else { pkg.applicationInfo.cpuAbi = null; } @@ -5452,20 +5475,22 @@ public class PackageManagerService extends IPackageManager.Stub { // to clean this up but we'll need to change the interface between this service // and IMediaContainerService (but doing so will spread this logic out, rather // than centralizing it). - final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(scanFile); - final int abi = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_ABIS); + final int abi = NativeLibraryHelper.findSupportedAbi(handle, abiList); if (abi >= 0) { - pkg.applicationInfo.cpuAbi = Build.SUPPORTED_ABIS[abi]; + pkg.applicationInfo.cpuAbi = abiList[abi]; } else if (abi == PackageManager.NO_NATIVE_LIBRARIES) { // Note that (non upgraded) system apps will not have any native // libraries bundled in their APK, but we're guaranteed not to be // such an app at this point. - pkg.applicationInfo.cpuAbi = null; + if (abiOverride != null || hasLegacyRenderscriptBitcode) { + pkg.applicationInfo.cpuAbi = abiList[0]; + } else { + pkg.applicationInfo.cpuAbi = null; + } } else { mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; return null; } - handle.close(); } if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path); @@ -5482,8 +5507,12 @@ public class PackageManagerService extends IPackageManager.Stub { } } } + + pkgSetting.cpuAbiString = pkg.applicationInfo.cpuAbi; } catch (IOException ioe) { Slog.e(TAG, "Unable to get canonical file " + ioe.toString()); + } finally { + handle.close(); } } pkg.mScanPath = path; @@ -6175,8 +6204,8 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private static int copyNativeLibrariesForInternalApp(File scanFile, final File nativeLibraryDir) - throws IOException { + private static int copyNativeLibrariesForInternalApp(ApkHandle handle, + final File nativeLibraryDir, String[] abiList) throws IOException { if (!nativeLibraryDir.isDirectory()) { nativeLibraryDir.delete(); @@ -6198,21 +6227,16 @@ public class PackageManagerService extends IPackageManager.Stub { * If this is an internal application or our nativeLibraryPath points to * the app-lib directory, unpack the libraries if necessary. */ - final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(scanFile); - try { - int abi = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_ABIS); - if (abi >= 0) { - int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, - nativeLibraryDir, Build.SUPPORTED_ABIS[abi]); - if (copyRet != PackageManager.INSTALL_SUCCEEDED) { - return copyRet; - } + int abi = NativeLibraryHelper.findSupportedAbi(handle, abiList); + if (abi >= 0) { + int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, + nativeLibraryDir, Build.SUPPORTED_ABIS[abi]); + if (copyRet != PackageManager.INSTALL_SUCCEEDED) { + return copyRet; } - - return abi; - } finally { - handle.close(); } + + return abi; } private void killApplication(String pkgName, int appId, String reason) { @@ -7536,7 +7560,7 @@ public class PackageManagerService extends IPackageManager.Stub { } p = scanPackageLI(fullPath, flags, SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME, - System.currentTimeMillis(), UserHandle.ALL); + System.currentTimeMillis(), UserHandle.ALL, null); if (p != null) { /* * TODO this seems dangerous as the package may have @@ -7657,6 +7681,16 @@ public class PackageManagerService extends IPackageManager.Stub { if (observer == null && observer2 == null) { throw new IllegalArgumentException("No install observer supplied"); } + installPackageWithVerificationEncryptionAndAbiOverrideEtc(packageURI, observer, observer2, + flags, installerPackageName, verificationParams, encryptionParams, null); + } + + @Override + public void installPackageWithVerificationEncryptionAndAbiOverrideEtc(Uri packageURI, + IPackageInstallObserver observer, IPackageInstallObserver2 observer2, + int flags, String installerPackageName, + VerificationParams verificationParams, ContainerEncryptionParams encryptionParams, + String packageAbiOverride) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null); @@ -7696,7 +7730,8 @@ public class PackageManagerService extends IPackageManager.Stub { final Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(packageURI, observer, observer2, filteredFlags, - installerPackageName, verificationParams, encryptionParams, user); + installerPackageName, verificationParams, encryptionParams, user, + packageAbiOverride); mHandler.sendMessage(msg); } @@ -8405,11 +8440,14 @@ public class PackageManagerService extends IPackageManager.Stub { private int mRet; private File mTempPackage; final ContainerEncryptionParams encryptionParams; + final String packageAbiOverride; + final String packageInstructionSetOverride; InstallParams(Uri packageURI, IPackageInstallObserver observer, IPackageInstallObserver2 observer2, int flags, String installerPackageName, VerificationParams verificationParams, - ContainerEncryptionParams encryptionParams, UserHandle user) { + ContainerEncryptionParams encryptionParams, UserHandle user, + String packageAbiOverride) { super(user); this.mPackageURI = packageURI; this.flags = flags; @@ -8418,6 +8456,9 @@ public class PackageManagerService extends IPackageManager.Stub { this.installerPackageName = installerPackageName; this.verificationParams = verificationParams; this.encryptionParams = encryptionParams; + this.packageAbiOverride = packageAbiOverride; + this.packageInstructionSetOverride = (packageAbiOverride == null) ? + packageAbiOverride : VMRuntime.getInstructionSet(packageAbiOverride); } @Override @@ -8563,7 +8604,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Remote call to find out default install location final String packageFilePath = packageFile.getAbsolutePath(); pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags, - lowThreshold); + lowThreshold, packageAbiOverride); /* * If we have too little free space, try to free cache @@ -8572,10 +8613,10 @@ public class PackageManagerService extends IPackageManager.Stub { if (pkgLite.recommendedInstallLocation == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) { final long size = mContainerService.calculateInstalledSize( - packageFilePath, isForwardLocked()); + packageFilePath, isForwardLocked(), packageAbiOverride); if (mInstaller.freeCache(size + lowThreshold) >= 0) { pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, - flags, lowThreshold); + flags, lowThreshold, packageAbiOverride); } /* * The cache free must have deleted the file we @@ -8995,11 +9036,12 @@ public class PackageManagerService extends IPackageManager.Stub { final ManifestDigest manifestDigest; final UserHandle user; final String instructionSet; + final String abiOverride; InstallArgs(Uri packageURI, IPackageInstallObserver observer, IPackageInstallObserver2 observer2, int flags, String installerPackageName, ManifestDigest manifestDigest, - UserHandle user, String instructionSet) { + UserHandle user, String instructionSet, String abiOverride) { this.packageURI = packageURI; this.flags = flags; this.observer = observer; @@ -9008,6 +9050,7 @@ public class PackageManagerService extends IPackageManager.Stub { this.manifestDigest = manifestDigest; this.user = user; this.instructionSet = instructionSet; + this.abiOverride = abiOverride; } abstract void createCopyFile(); @@ -9063,12 +9106,13 @@ public class PackageManagerService extends IPackageManager.Stub { FileInstallArgs(InstallParams params) { super(params.getPackageUri(), params.observer, params.observer2, params.flags, params.installerPackageName, params.getManifestDigest(), - params.getUser(), null /* instruction set */); + params.getUser(), params.packageInstructionSetOverride, + params.packageAbiOverride); } FileInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath, String instructionSet) { - super(null, null, null, 0, null, null, null, instructionSet); + super(null, null, null, 0, null, null, null, instructionSet, null); File codeFile = new File(fullCodePath); installDir = codeFile.getParentFile(); codeFileName = fullCodePath; @@ -9077,7 +9121,7 @@ public class PackageManagerService extends IPackageManager.Stub { } FileInstallArgs(Uri packageURI, String pkgName, String dataDir, String instructionSet) { - super(packageURI, null, null, 0, null, null, null, instructionSet); + super(packageURI, null, null, 0, null, null, null, instructionSet, null); installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir; String apkName = getNextCodePath(null, pkgName, ".apk"); codeFileName = new File(installDir, apkName + ".apk").getPath(); @@ -9181,14 +9225,26 @@ public class PackageManagerService extends IPackageManager.Stub { NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile); nativeLibraryFile.delete(); } + + final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(codeFile); + String[] abiList = (abiOverride != null) ? + new String[] { abiOverride } : Build.SUPPORTED_ABIS; try { - int copyRet = copyNativeLibrariesForInternalApp(codeFile, nativeLibraryFile); + if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && + abiOverride == null && + NativeLibraryHelper.hasRenderscriptBitcode(handle)) { + abiList = Build.SUPPORTED_32_BIT_ABIS; + } + + int copyRet = copyNativeLibrariesForInternalApp(handle, nativeLibraryFile, abiList); if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { return copyRet; } } catch (IOException e) { Slog.e(TAG, "Copying native libraries failed", e); ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + } finally { + handle.close(); } return ret; @@ -9403,14 +9459,15 @@ public class PackageManagerService extends IPackageManager.Stub { AsecInstallArgs(InstallParams params) { super(params.getPackageUri(), params.observer, params.observer2, params.flags, params.installerPackageName, params.getManifestDigest(), - params.getUser(), null /* instruction set */); + params.getUser(), params.packageInstructionSetOverride, + params.packageAbiOverride); } AsecInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath, String instructionSet, boolean isExternal, boolean isForwardLocked) { super(null, null, null, (isExternal ? PackageManager.INSTALL_EXTERNAL : 0) | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0), - null, null, null, instructionSet); + null, null, null, instructionSet, null); // Extract cid from fullCodePath int eidx = fullCodePath.lastIndexOf("/"); String subStr1 = fullCodePath.substring(0, eidx); @@ -9422,7 +9479,7 @@ public class PackageManagerService extends IPackageManager.Stub { AsecInstallArgs(String cid, String instructionSet, boolean isForwardLocked) { super(null, null, null, (isAsecExternal(cid) ? PackageManager.INSTALL_EXTERNAL : 0) | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0), - null, null, null, instructionSet); + null, null, null, instructionSet, null); this.cid = cid; setCachePath(PackageHelper.getSdDir(cid)); } @@ -9431,7 +9488,7 @@ public class PackageManagerService extends IPackageManager.Stub { boolean isExternal, boolean isForwardLocked) { super(packageURI, null, null, (isExternal ? PackageManager.INSTALL_EXTERNAL : 0) | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0), - null, null, null, instructionSet); + null, null, null, instructionSet, null); this.cid = cid; } @@ -9443,7 +9500,7 @@ public class PackageManagerService extends IPackageManager.Stub { try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - return imcs.checkExternalFreeStorage(packageURI, isFwdLocked()); + return imcs.checkExternalFreeStorage(packageURI, isFwdLocked(), abiOverride); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -9469,7 +9526,8 @@ public class PackageManagerService extends IPackageManager.Stub { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); newCachePath = imcs.copyResourceToContainer(packageURI, cid, getEncryptKey(), - RES_FILE_NAME, PUBLIC_RES_FILE_NAME, isExternal(), isFwdLocked()); + RES_FILE_NAME, PUBLIC_RES_FILE_NAME, isExternal(), isFwdLocked(), + abiOverride); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -9777,7 +9835,7 @@ public class PackageManagerService extends IPackageManager.Stub { */ private void installNewPackageLI(PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user, - String installerPackageName, PackageInstalledInfo res) { + String installerPackageName, PackageInstalledInfo res, String abiOverride) { // Remember this for later, in case we need to rollback this install String pkgName = pkg.packageName; @@ -9805,7 +9863,7 @@ public class PackageManagerService extends IPackageManager.Stub { } mLastScanError = PackageManager.INSTALL_SUCCEEDED; PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode, - System.currentTimeMillis(), user); + System.currentTimeMillis(), user, abiOverride); if (newPackage == null) { Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath); if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) { @@ -9832,7 +9890,7 @@ public class PackageManagerService extends IPackageManager.Stub { private void replacePackageLI(PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user, - String installerPackageName, PackageInstalledInfo res) { + String installerPackageName, PackageInstalledInfo res, String abiOverride) { PackageParser.Package oldPackage; String pkgName = pkg.packageName; @@ -9861,17 +9919,19 @@ public class PackageManagerService extends IPackageManager.Stub { boolean sysPkg = (isSystemApp(oldPackage)); if (sysPkg) { replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode, - user, allUsers, perUserInstalled, installerPackageName, res); + user, allUsers, perUserInstalled, installerPackageName, res, + abiOverride); } else { replaceNonSystemPackageLI(oldPackage, pkg, parseFlags, scanMode, - user, allUsers, perUserInstalled, installerPackageName, res); + user, allUsers, perUserInstalled, installerPackageName, res, + abiOverride); } } private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage, PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user, int[] allUsers, boolean[] perUserInstalled, - String installerPackageName, PackageInstalledInfo res) { + String installerPackageName, PackageInstalledInfo res, String abiOverride) { PackageParser.Package newPackage = null; String pkgName = deletedPackage.packageName; boolean deletedPkg = true; @@ -9896,7 +9956,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Successfully deleted the old package. Now proceed with re-installation mLastScanError = PackageManager.INSTALL_SUCCEEDED; newPackage = scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_TIME, - System.currentTimeMillis(), user); + System.currentTimeMillis(), user, abiOverride); if (newPackage == null) { Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath); if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) { @@ -9925,7 +9985,7 @@ public class PackageManagerService extends IPackageManager.Stub { } // Since we failed to install the new package we need to restore the old // package that we deleted. - if(deletedPkg) { + if (deletedPkg) { if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, reinstalling: " + deletedPackage); File restoreFile = new File(deletedPackage.mPath); // Parse old package @@ -9936,7 +9996,7 @@ public class PackageManagerService extends IPackageManager.Stub { int oldScanMode = (oldOnSd ? 0 : SCAN_MONITOR) | SCAN_UPDATE_SIGNATURE | SCAN_UPDATE_TIME; if (scanPackageLI(restoreFile, oldParseFlags, oldScanMode, - origUpdateTime, null) == null) { + origUpdateTime, null, null) == null) { Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade"); return; } @@ -9956,7 +10016,7 @@ public class PackageManagerService extends IPackageManager.Stub { private void replaceSystemPackageLI(PackageParser.Package deletedPackage, PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user, int[] allUsers, boolean[] perUserInstalled, - String installerPackageName, PackageInstalledInfo res) { + String installerPackageName, PackageInstalledInfo res, String abiOverride) { if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg + ", old=" + deletedPackage); PackageParser.Package newPackage = null; @@ -10010,7 +10070,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Successfully disabled the old package. Now proceed with re-installation res.returnCode = mLastScanError = PackageManager.INSTALL_SUCCEEDED; pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; - newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0, user); + newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0, user, abiOverride); if (newPackage == null) { Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath); if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) { @@ -10044,7 +10104,7 @@ public class PackageManagerService extends IPackageManager.Stub { removeInstalledPackageLI(newPackage, true); } // Add back the old system package - scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0, user); + scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0, user, null); // Restore the old system information in Settings synchronized(mPackages) { if (updatedSettings) { @@ -10278,10 +10338,10 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.nativeLibraryDir = args.getNativeLibraryPath(); if (replace) { replacePackageLI(pkg, parseFlags, scanMode, args.user, - installerPackageName, res); + installerPackageName, res, args.abiOverride); } else { installNewPackageLI(pkg, parseFlags, scanMode | SCAN_DELETE_DATA_ON_FAILURES, args.user, - installerPackageName, res); + installerPackageName, res, args.abiOverride); } synchronized (mPackages) { final PackageSetting ps = mSettings.mPackages.get(pkgName); @@ -10698,7 +10758,7 @@ public class PackageManagerService extends IPackageManager.Stub { parseFlags |= PackageParser.PARSE_IS_PRIVILEGED; } PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath, - parseFlags, SCAN_MONITOR | SCAN_NO_PATHS, 0, null); + parseFlags, SCAN_MONITOR | SCAN_NO_PATHS, 0, null, null); if (newPkg == null) { Slog.w(TAG, "Failed to restore system package:" + newPs.name @@ -12511,7 +12571,7 @@ public class PackageManagerService extends IPackageManager.Stub { doGc = true; synchronized (mInstallLock) { final PackageParser.Package pkg = scanPackageLI(new File(codePath), parseFlags, - 0, 0, null); + 0, 0, null, null); // Scan the package if (pkg != null) { /* 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/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/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 81db8b3..a354c45 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -174,14 +174,14 @@ public class TaskStack { stackNdx = 0; } else { stackNdx = mTasks.size(); - final int currentUserId = mService.mCurrentUserId; - if (task.mUserId != currentUserId) { + if (!mService.isCurrentProfileLocked(task.mUserId)) { // Place the task below all current user tasks. while (--stackNdx >= 0) { - if (currentUserId != mTasks.get(stackNdx).mUserId) { + if (!mService.isCurrentProfileLocked(mTasks.get(stackNdx).mUserId)) { break; } } + // Put it above first non-current user task. ++stackNdx; } } @@ -352,7 +352,7 @@ public class TaskStack { int top = mTasks.size(); for (int taskNdx = 0; taskNdx < top; ++taskNdx) { Task task = mTasks.get(taskNdx); - if (task.mUserId == userId) { + if (mService.isCurrentProfileLocked(task.mUserId)) { mTasks.remove(taskNdx); mTasks.add(task); --top; diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 6fdd535..008d2fc 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -284,7 +284,7 @@ public class WindowAnimator { } else if (mKeyguardGoingAway && !nowAnimating) { // Timeout!! Slog.e(TAG, "Timeout waiting for animation to startup"); - mPolicy.startKeyguardExitAnimation(0); + mPolicy.startKeyguardExitAnimation(0, 0); mKeyguardGoingAway = false; } if (win.isReadyForDisplay()) { @@ -392,7 +392,9 @@ public class WindowAnimator { winAnimator.mAnimationIsEntrance = true; if (startKeyguardExit) { // Do one time only. - mPolicy.startKeyguardExitAnimation(a.getStartOffset()); + mPolicy.startKeyguardExitAnimation(mCurrentTime + a.getStartOffset(), + a.getDuration()); + mKeyguardGoingAway = false; startKeyguardExit = false; } } diff --git a/services/core/jni/com_android_server_AssetAtlasService.cpp b/services/core/jni/com_android_server_AssetAtlasService.cpp index 163225e..9a5079d 100644 --- a/services/core/jni/com_android_server_AssetAtlasService.cpp +++ b/services/core/jni/com_android_server_AssetAtlasService.cpp @@ -18,6 +18,7 @@ #include "jni.h" #include "JNIHelp.h" +#include "android/graphics/GraphicsJNI.h" #include <android_view_GraphicBuffer.h> #include <cutils/log.h> @@ -46,7 +47,7 @@ namespace android { // ---------------------------------------------------------------------------- static struct { - jmethodID safeCanvasSwap; + jmethodID setNativeBitmap; } gCanvasClassInfo; #define INVOKEV(object, method, ...) \ @@ -63,9 +64,7 @@ static jlong com_android_server_AssetAtlasService_acquireCanvas(JNIEnv* env, job bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height); bitmap->allocPixels(); bitmap->eraseColor(0); - - SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (*bitmap)); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(bitmap)); return reinterpret_cast<jlong>(bitmap); } @@ -74,8 +73,7 @@ static void com_android_server_AssetAtlasService_releaseCanvas(JNIEnv* env, jobj jobject canvas, jlong bitmapHandle) { SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - SkCanvas* nativeCanvas = SkNEW(SkCanvas); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, (jlong)0); delete bitmap; } @@ -244,7 +242,7 @@ int register_android_server_AssetAtlasService(JNIEnv* env) { jclass clazz; FIND_CLASS(clazz, "android/graphics/Canvas"); - GET_METHOD_ID(gCanvasClassInfo.safeCanvasSwap, clazz, "safeCanvasSwap", "(JZ)V"); + GET_METHOD_ID(gCanvasClassInfo.setNativeBitmap, clazz, "setNativeBitmap", "(J)V"); return jniRegisterNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); } 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 c0f60c7..164fe05 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; @@ -126,7 +127,7 @@ public final class SystemServer { private static final String WIFI_SERVICE_CLASS = "com.android.server.wifi.WifiService"; private static final String WIFI_PASSPOINT_SERVICE_CLASS = - "com.android.server.wifi.passpoint.PasspointService"; + "com.android.server.wifi.passpoint.WifiPasspointService"; private static final String WIFI_P2P_SERVICE_CLASS = "com.android.server.wifi.p2p.WifiP2pService"; private static final String HDMI_CEC_SERVICE_CLASS = @@ -643,15 +644,15 @@ public final class SystemServer { } try { - mSystemServiceManager.startService(WIFI_SERVICE_CLASS); + mSystemServiceManager.startService(WIFI_PASSPOINT_SERVICE_CLASS); } catch (Throwable e) { - reportWtf("starting Wi-Fi Service", e); + reportWtf("starting Wi-Fi PasspointService", e); } try { - mSystemServiceManager.startService(WIFI_PASSPOINT_SERVICE_CLASS); + mSystemServiceManager.startService(WIFI_SERVICE_CLASS); } catch (Throwable e) { - reportWtf("starting Wi-Fi PasspointService", e); + reportWtf("starting Wi-Fi Service", e); } try { @@ -941,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); @@ -991,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); } 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/telecomm/java/android/telecomm/CallService.java b/telecomm/java/android/telecomm/CallService.java index 51f10c1..d452172 100644 --- a/telecomm/java/android/telecomm/CallService.java +++ b/telecomm/java/android/telecomm/CallService.java @@ -27,6 +27,8 @@ import com.android.internal.os.SomeArgs; import com.android.internal.telecomm.ICallService; import com.android.internal.telecomm.ICallServiceAdapter; +import java.util.List; + /** * Base implementation of CallService which can be used to provide calls for the system * in-call UI. CallService is a one-way service from the framework's CallsManager to any app @@ -59,6 +61,8 @@ public abstract class CallService extends Service { private static final int MSG_ON_AUDIO_STATE_CHANGED = 11; private static final int MSG_PLAY_DTMF_TONE = 12; private static final int MSG_STOP_DTMF_TONE = 13; + private static final int MSG_ADD_TO_CONFERENCE = 14; + private static final int MSG_SPLIT_FROM_CONFERENCE = 15; /** * Default Handler used to consolidate binder method calls onto a single thread. @@ -123,6 +127,29 @@ public abstract class CallService extends Service { case MSG_STOP_DTMF_TONE: stopDtmfTone((String) msg.obj); break; + case MSG_ADD_TO_CONFERENCE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + @SuppressWarnings("unchecked") + List<String> callIds = (List<String>) args.arg2; + String conferenceCallId = (String) args.arg1; + addToConference(conferenceCallId, callIds); + } finally { + args.recycle(); + } + break; + } + case MSG_SPLIT_FROM_CONFERENCE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String conferenceCallId = (String) args.arg1; + String callId = (String) args.arg2; + splitFromConference(conferenceCallId, callId); + } finally { + args.recycle(); + } + break; + } default: break; } @@ -204,6 +231,22 @@ public abstract class CallService extends Service { args.arg2 = audioState; mMessageHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, args).sendToTarget(); } + + @Override + public void addToConference(String conferenceCallId, List<String> callsToConference) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = conferenceCallId; + args.arg2 = callsToConference; + mMessageHandler.obtainMessage(MSG_ADD_TO_CONFERENCE, args).sendToTarget(); + } + + @Override + public void splitFromConference(String conferenceCallId, String callId) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = conferenceCallId; + args.arg2 = callId; + mMessageHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, args).sendToTarget(); + } } /** @@ -359,4 +402,24 @@ public abstract class CallService extends Service { * @param audioState The new {@link CallAudioState}. */ public abstract void onAudioStateChanged(String activeCallId, CallAudioState audioState); + + /** + * Adds the specified calls to the specified conference call. + * + * @param conferenceCallId The unique ID of the conference call onto which the specified calls + * should be added. + * @param callIds The calls to add to the conference call. + * @hide + */ + public abstract void addToConference(String conferenceCallId, List<String> callIds); + + /** + * Removes the specified call from the specified conference call. This is a no-op if the call + * is not already part of the conference call. + * + * @param conferenceCallId The conference call. + * @param callId The call to remove from the conference call + * @hide + */ + public abstract void splitFromConference(String conferenceCallId, String callId); } diff --git a/telecomm/java/android/telecomm/CallServiceAdapter.java b/telecomm/java/android/telecomm/CallServiceAdapter.java index d5bb989..7396808 100644 --- a/telecomm/java/android/telecomm/CallServiceAdapter.java +++ b/telecomm/java/android/telecomm/CallServiceAdapter.java @@ -20,6 +20,8 @@ import android.os.RemoteException; import com.android.internal.telecomm.ICallServiceAdapter; +import java.util.List; + /** * Provides methods for ICallService implementations to interact with the system phone app. * TODO(santoscordon): Need final public-facing comments in this file. @@ -156,5 +158,59 @@ 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) { + } + } + + /** + * Indicates that the specified call can conference with any of the specified list of calls. + * + * @param callId The unique ID of the call. + * @param conferenceCapableCallIds The unique IDs of the calls which can be conferenced. + * @hide + */ + public void setCanConferenceWith(String callId, List<String> conferenceCapableCallIds) { + try { + mAdapter.setCanConferenceWith(callId, conferenceCapableCallIds); + } catch (RemoteException ignored) { + } + } + + /** + * Indicates whether or not the specified call is currently conferenced into the specified + * conference call. + * + * @param conferenceCallId The unique ID of the conference call. + * @param callId The unique ID of the call being conferenced. + * @hide + */ + public void setIsConferenced(String conferenceCallId, String callId, boolean isConferenced) { + try { + mAdapter.setIsConferenced(conferenceCallId, callId, isConferenced); + } catch (RemoteException ignored) { + } + } + /** + * Indicates that the call no longer exists. Can be used with either a call or a conference + * call. + * + * @param callId The unique ID of the call. + * @hide + */ + public void removeCall(String callId) { + try { + mAdapter.removeCall(callId); + } catch (RemoteException ignored) { + } + } } 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..aeb1c33 100644 --- a/telecomm/java/android/telecomm/ConnectionService.java +++ b/telecomm/java/android/telecomm/ConnectionService.java @@ -20,6 +20,8 @@ import android.net.Uri; import android.os.Bundle; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; import java.util.Map; /** @@ -89,6 +91,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 @@ -241,6 +250,39 @@ public abstract class ConnectionService extends CallService { findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState); } + /** @hide */ + @Override + public final void addToConference(String conferenceCallId, List<String> callIds) { + Log.d(this, "addToConference %s, %s", conferenceCallId, callIds); + + List<Connection> connections = new LinkedList<>(); + for (String id : callIds) { + Connection connection = findConnectionForAction(id, "addToConference"); + if (connection == NULL_CONNECTION) { + Log.w(this, "Connection missing in conference request %s.", id); + return; + } + connections.add(connection); + } + + // TODO(santoscordon): Find an existing conference call or create a new one. Then call + // conferenceWith on it. + } + + /** @hide */ + @Override + public final void splitFromConference(String conferenceCallId, String callId) { + Log.d(this, "splitFromConference(%s, %s)", conferenceCallId, callId); + + Connection connection = findConnectionForAction(callId, "splitFromConference"); + if (connection == NULL_CONNECTION) { + Log.w(this, "Connection missing in conference request %s.", callId); + return; + } + + // TODO(santoscordon): Find existing conference call and invoke split(connection). + } + /** * Find a set of Subscriptions matching a given handle (e.g. phone number). * @@ -335,4 +377,4 @@ public abstract class ConnectionService extends CallService { Log.w(this, "%s - Cannot find Connection %s", action, callId); return NULL_CONNECTION; } -}
\ No newline at end of file +} diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java index e41d3f6..6838ede 100644 --- a/telecomm/java/android/telecomm/InCallAdapter.java +++ b/telecomm/java/android/telecomm/InCallAdapter.java @@ -196,4 +196,32 @@ public final class InCallAdapter { } catch (RemoteException e) { } } + + /** + * Instructs Telecomm to conference the specified calls together. + * + * @param callId The unique ID of the call. + * @param callIdToConference The unique ID of the call to conference with. + * @hide + */ + void conferenceWith(String callId, String callIdToConference) { + try { + mAdapter.conferenceWith(callId, callIdToConference); + } catch (RemoteException ignored) { + } + } + + /** + * Instructs Telecomm to split the specified call from any conference call with which it may be + * connected. + * + * @param callId The unique ID of the call. + * @hide + */ + void splitFromConference(String callId) { + try { + mAdapter.splitFromConference(callId); + } catch (RemoteException ignored) { + } + } } diff --git a/telecomm/java/android/telecomm/InCallCall.java b/telecomm/java/android/telecomm/InCallCall.java index c3b2ae7..346d207 100644 --- a/telecomm/java/android/telecomm/InCallCall.java +++ b/telecomm/java/android/telecomm/InCallCall.java @@ -17,13 +17,13 @@ package android.telecomm; import android.net.Uri; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.telephony.DisconnectCause; -import java.util.Date; -import java.util.UUID; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Information about a call that is used between InCallService and Telecomm. @@ -38,8 +38,12 @@ public final class InCallCall implements Parcelable { private final GatewayInfo mGatewayInfo; private final CallServiceDescriptor mCurrentCallServiceDescriptor; private final CallServiceDescriptor mHandoffCallServiceDescriptor; + private final List<String> mConferenceCapableCallIds; + private final String mParentCallId; + private final List<String> mChildCallIds; /** @hide */ + @SuppressWarnings("unchecked") public InCallCall( String id, CallState state, @@ -50,6 +54,25 @@ public final class InCallCall implements Parcelable { GatewayInfo gatewayInfo, CallServiceDescriptor descriptor, CallServiceDescriptor handoffDescriptor) { + this(id, state, disconnectCause, capabilities, connectTimeMillis, handle, gatewayInfo, + descriptor, handoffDescriptor, Collections.EMPTY_LIST, null, + Collections.EMPTY_LIST); + } + + /** @hide */ + public InCallCall( + String id, + CallState state, + int disconnectCause, + int capabilities, + long connectTimeMillis, + Uri handle, + GatewayInfo gatewayInfo, + CallServiceDescriptor descriptor, + CallServiceDescriptor handoffDescriptor, + List<String> conferenceCapableCallIds, + String parentCallId, + List<String> childCallIds) { mId = id; mState = state; mDisconnectCause = disconnectCause; @@ -59,6 +82,9 @@ public final class InCallCall implements Parcelable { mGatewayInfo = gatewayInfo; mCurrentCallServiceDescriptor = descriptor; mHandoffCallServiceDescriptor = handoffDescriptor; + mConferenceCapableCallIds = conferenceCapableCallIds; + mParentCallId = parentCallId; + mChildCallIds = childCallIds; } /** The unique ID of the call. */ @@ -112,6 +138,31 @@ public final class InCallCall implements Parcelable { return mHandoffCallServiceDescriptor; } + /** + * The calls with which this call can conference. + * @hide + */ + public List<String> getConferenceCapableCallIds() { + return mConferenceCapableCallIds; + } + + /** + * The conference call to which this call is conferenced. Null if not conferenced. + * @hide + */ + public String getParentCallId() { + return mParentCallId; + } + + /** + * The child call-IDs if this call is a conference call. Returns an empty list if this is not + * a conference call or if the conference call contains no children. + * @hide + */ + public List<String> getChildCallIds() { + return mChildCallIds; + } + /** Responsible for creating InCallCall objects for deserialized Parcels. */ public static final Parcelable.Creator<InCallCall> CREATOR = new Parcelable.Creator<InCallCall> () { @@ -127,8 +178,14 @@ public final class InCallCall implements Parcelable { GatewayInfo gatewayInfo = source.readParcelable(classLoader); CallServiceDescriptor descriptor = source.readParcelable(classLoader); CallServiceDescriptor handoffDescriptor = source.readParcelable(classLoader); + List<String> conferenceCapableCallIds = new ArrayList<>(); + source.readList(conferenceCapableCallIds, classLoader); + String parentCallId = source.readString(); + List<String> childCallIds = new ArrayList<>(); + source.readList(childCallIds, classLoader); return new InCallCall(id, state, disconnectCause, capabilities, connectTimeMillis, - handle, gatewayInfo, descriptor, handoffDescriptor); + handle, gatewayInfo, descriptor, handoffDescriptor, conferenceCapableCallIds, + parentCallId, childCallIds); } @Override @@ -155,5 +212,8 @@ public final class InCallCall implements Parcelable { destination.writeParcelable(mGatewayInfo, 0); destination.writeParcelable(mCurrentCallServiceDescriptor, 0); destination.writeParcelable(mHandoffCallServiceDescriptor, 0); + destination.writeList(mConferenceCapableCallIds); + destination.writeString(mParentCallId); + destination.writeList(mChildCallIds); } } 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/ICallService.aidl b/telecomm/java/com/android/internal/telecomm/ICallService.aidl index cc0641c..771a3ae 100644 --- a/telecomm/java/com/android/internal/telecomm/ICallService.aidl +++ b/telecomm/java/com/android/internal/telecomm/ICallService.aidl @@ -55,4 +55,8 @@ oneway interface ICallService { void playDtmfTone(String callId, char digit); void stopDtmfTone(String callId); + + void addToConference(String conferenceCallId, in List<String> callIds); + + void splitFromConference(String conferenceCallId, String callId); } diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl index dfdaa75..a92b176 100644 --- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl +++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl @@ -43,4 +43,12 @@ oneway interface ICallServiceAdapter { void setDisconnected(String callId, int disconnectCause, String disconnectMessage); void setOnHold(String callId); + + void setRequestingRingback(String callId, boolean ringing); + + void setCanConferenceWith(String callId, in List<String> conferenceCapableCallIds); + + void setIsConferenced(String conferenceCallId, String callId, boolean isConferenced); + + void removeCall(String callId); } diff --git a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl index 512e898..6a27217 100644 --- a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl +++ b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl @@ -47,4 +47,8 @@ oneway interface IInCallAdapter { void postDialContinue(String callId); void handoffCall(String callId); + + void conferenceWith(String callId, String callIdToConference); + + void splitFromConference(String callId); } 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/MoreAsserts.java b/test-runner/src/android/test/MoreAsserts.java index fb0faba..3364895 100644 --- a/test-runner/src/android/test/MoreAsserts.java +++ b/test-runner/src/android/test/MoreAsserts.java @@ -128,6 +128,33 @@ public final class MoreAsserts { } /** + * @hide Asserts that array {@code actual} is the same size and every element equals + * those in array {@code expected}. On failure, message indicates first + * specific element mismatch. + */ + public static void assertEquals( + String message, long[] expected, long[] actual) { + if (expected.length != actual.length) { + failWrongLength(message, expected.length, actual.length); + } + for (int i = 0; i < expected.length; i++) { + if (expected[i] != actual[i]) { + failWrongElement(message, i, expected[i], actual[i]); + } + } + } + + /** + * @hide Asserts that array {@code actual} is the same size and every element equals + * those in array {@code expected}. On failure, message indicates first + * specific element mismatch. + */ + public static void assertEquals(long[] expected, long[] actual) { + assertEquals(null, expected, actual); + } + + + /** * Asserts that array {@code actual} is the same size and every element equals * those in array {@code expected}. On failure, message indicates first * specific element mismatch. 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/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java index 5b0aa66..a81e063 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java @@ -118,19 +118,25 @@ public class CirclePropActivity extends Activity { mRadius, mToggle ? 250.0f : 150.0f)); mRunningAnimations.add(new RenderNodeAnimator( - mPaint, RenderNodeAnimator.PAINT_ALPHA, - mToggle ? 64.0f : 255.0f)); - - mRunningAnimations.add(new RenderNodeAnimator( mPaint, RenderNodeAnimator.PAINT_STROKE_WIDTH, mToggle ? 5.0f : 60.0f)); - TimeInterpolator interp = new OvershootInterpolator(3.0f); + mRunningAnimations.add(new RenderNodeAnimator( + mPaint, RenderNodeAnimator.PAINT_ALPHA, 64.0f)); + + // Will be "chained" to run after the above + mRunningAnimations.add(new RenderNodeAnimator( + mPaint, RenderNodeAnimator.PAINT_ALPHA, 255.0f)); + for (int i = 0; i < mRunningAnimations.size(); i++) { RenderNodeAnimator anim = mRunningAnimations.get(i); - anim.setInterpolator(interp); anim.setDuration(1000); anim.setTarget(this); + if (i == (mRunningAnimations.size() - 1)) { + // "chain" test + anim.setStartValue(64.0f); + anim.setStartDelay(anim.getDuration()); + } anim.start(); } 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..d6f8118 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 TransportCallback()); + 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 TransportCallback 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/RenderThreadTest/src/com/example/renderthread/MainActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java index c7715ad..fc5426c 100644 --- a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java +++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java @@ -43,7 +43,6 @@ public class MainActivity extends Activity implements OnItemClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - HardwareRenderer.sUseRenderThread = true; setContentView(R.layout.activity_main); ListView lv = (ListView) findViewById(android.R.id.list); lv.setDrawSelectorOnTop(true); 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/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index e9daffd..e35bc06 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -71,7 +71,7 @@ public final class Canvas_Delegate { * Returns the native delegate associated to a given {@link Canvas} object. */ public static Canvas_Delegate getDelegate(Canvas canvas) { - return sManager.getDelegate(canvas.getNativeCanvas()); + return sManager.getDelegate(canvas.getNativeCanvasWrapper()); } /** @@ -102,7 +102,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static boolean isOpaque(Canvas thisCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas()); + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper()); if (canvasDelegate == null) { return false; } @@ -113,7 +113,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static int getWidth(Canvas thisCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas()); + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper()); if (canvasDelegate == null) { return 0; } @@ -124,7 +124,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static int getHeight(Canvas thisCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas()); + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper()); if (canvasDelegate == null) { return 0; } @@ -135,7 +135,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static void translate(Canvas thisCanvas, float dx, float dy) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas()); + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper()); if (canvasDelegate == null) { return; } @@ -146,7 +146,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static void rotate(Canvas thisCanvas, float degrees) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas()); + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper()); if (canvasDelegate == null) { return; } @@ -157,7 +157,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static void scale(Canvas thisCanvas, float sx, float sy) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas()); + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper()); if (canvasDelegate == null) { return; } @@ -168,7 +168,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static void skew(Canvas thisCanvas, float kx, float ky) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas()); + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper()); if (canvasDelegate == null) { return; } @@ -204,7 +204,7 @@ public final class Canvas_Delegate { /*package*/ static boolean clipRect(Canvas thisCanvas, float left, float top, float right, float bottom) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas()); + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper()); if (canvasDelegate == null) { return false; } @@ -227,7 +227,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static int save(Canvas thisCanvas, int saveFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas()); + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper()); if (canvasDelegate == null) { return 0; } @@ -238,7 +238,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static void restore(Canvas thisCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas()); + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper()); if (canvasDelegate == null) { return; } @@ -249,7 +249,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static int getSaveCount(Canvas thisCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas()); + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper()); if (canvasDelegate == null) { return 0; } @@ -260,7 +260,7 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static void restoreToCount(Canvas thisCanvas, int saveCount) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas()); + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper()); if (canvasDelegate == null) { return; } @@ -287,7 +287,7 @@ public final class Canvas_Delegate { /*package*/ static void drawLines(Canvas thisCanvas, final float[] pts, final int offset, final int count, Paint paint) { - draw(thisCanvas.getNativeCanvas(), paint.mNativePaint, false /*compositeOnly*/, + draw(thisCanvas.getNativeCanvasWrapper(), paint.mNativePaint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { @Override public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { 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..99151c3 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) { @@ -131,8 +144,7 @@ public class ScanResult implements Parcelable { distanceCm = source.distanceCm; distanceSdCm = source.distanceSdCm; seen = source.seen; - if (source.passpoint != null) - passpoint = new WifiPasspointInfo(source.passpoint); + passpoint = source.passpoint; } } @@ -166,8 +178,7 @@ public class ScanResult implements Parcelable { sb.append(", distanceSd: ").append((distanceSdCm != UNSPECIFIED ? distanceSdCm : "?")). append("(cm)"); - if (passpoint != null) - sb.append(", passpoint: [").append(passpoint.toString()).append("]"); + sb.append(", passpoint: ").append(passpoint != null ? "yes" : "no"); return sb.toString(); } @@ -199,6 +210,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 +244,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 +262,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 963117c..5dfc318 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -324,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 */ @@ -441,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 @@ -453,11 +478,29 @@ 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; + + /** + * peer WifiConfiguration this WifiConfiguration was added for + * @hide + */ + public String peerWifiConfiguration; + + /** * @hide * Indicate that a WifiConfiguration is temporary and should not be saved * nor considered by AutoJoin. @@ -513,6 +556,7 @@ public class WifiConfiguration implements Parcelable { enterpriseConfig = new WifiEnterpriseConfig(); autoJoinStatus = AUTO_JOIN_ENABLED; selfAdded = false; + didSelfAdd = false; ephemeral = false; mIpConfiguration = new IpConfiguration(); } @@ -650,6 +694,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(); } @@ -883,6 +931,7 @@ 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; @@ -933,6 +982,11 @@ public class WifiConfiguration implements Parcelable { } lastFailure = source.lastFailure; + didSelfAdd = source.didSelfAdd; + lastConnectUid = source.lastConnectUid; + lastUpdateUid = source.lastUpdateUid; + creatorUid = source.creatorUid; + peerWifiConfiguration = source.peerWifiConfiguration; } } @@ -972,6 +1026,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) { @@ -1017,6 +1075,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 f809abf..54ac71e 100644 --- a/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java +++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java @@ -20,10 +20,13 @@ 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. @@ -32,64 +35,76 @@ import java.util.Map; 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 @@ -114,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, @@ -205,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; @@ -240,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(); @@ -266,6 +199,7 @@ public class WifiPasspointCredential implements Parcelable { mHomeOIList = credinfo.homeSP.homeOIList.values(); mFriendlyName = credinfo.homeSP.FriendlyName; + mCheckAaaServerCertStatus = credinfo.credential.CheckAAAServerCertStatus; } /** @hide */ @@ -284,8 +218,8 @@ public class WifiPasspointCredential implements Parcelable { } /** @hide */ - public String getWifiSPFQDN() { - return mWifiSPFQDN; + public String getWifiSpFqdn() { + return mWifiSpFqdn; } /** @hide */ @@ -294,7 +228,7 @@ public class WifiPasspointCredential implements Parcelable { } /** @hide */ - public String getEapMethodStr() { + public String getType() { return mType; } @@ -384,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; } @@ -405,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 */ @@ -430,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 */ @@ -465,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; } @@ -495,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 */ @@ -578,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(); } @@ -654,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} */ @@ -674,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; } @@ -699,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/WifiPasspointInfo.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointInfo.java index 99bea2f..5ef1bf9 100644 --- a/wifi/java/android/net/wifi/passpoint/WifiPasspointInfo.java +++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointInfo.java @@ -88,68 +88,174 @@ public class WifiPasspointInfo implements Parcelable { CONNECTION_CAPABILITY | OSU_PROVIDER; - /** TODO doc */ - public String bssid; - /** TODO doc */ - public String venueName; + public static class WanMetrics { + public static final int STATUS_RESERVED = 0; + public static final int STATUS_UP = 1; + public static final int STATUS_DOWN = 2; + public static final int STATUS_TEST = 3; + + public int wanInfo; + public long downlinkSpeed; + public long uplinkSpeed; + public int downlinkLoad; + public int uplinkLoad; + public int lmd; + + public int getLinkStatus() { + return wanInfo & 0x3; + } - /** TODO doc */ - public String networkAuthType; + public boolean getSymmetricLink() { + return (wanInfo & (1 << 2)) != 0; + } - /** TODO doc */ - public String roamingConsortium; + public boolean getAtCapacity() { + return (wanInfo & (1 << 3)) != 0; + } - /** TODO doc */ - public String ipAddrTypeAvaibility; + @Override + public String toString() { + return wanInfo + "," + downlinkSpeed + "," + uplinkSpeed + "," + + downlinkLoad + "," + uplinkLoad + "," + lmd; + } + } - /** TODO doc */ - public String naiRealm; + public static class IpProtoPort { + public static final int STATUS_CLOSED = 0; + public static final int STATUS_OPEN = 1; + public static final int STATUS_UNKNOWN = 2; - /** TODO doc */ - public String cellularNetwork; + public int proto; + public int port; + public int status; - /** TODO doc */ - public String domainName; + @Override + public String toString() { + return proto + "," + port + "," + status; + } + } - /** TODO doc */ - public String operatorFriendlyName; + public static class NetworkAuthType { + public static final int TYPE_TERMS_AND_CONDITION = 0; + public static final int TYPE_ONLINE_ENROLLMENT = 1; + public static final int TYPE_HTTP_REDIRECTION = 2; + public static final int TYPE_DNS_REDIRECTION = 3; - /** TODO doc */ - public String wanMetrics; + public int type; + public String redirectUrl; - /** TODO doc */ - public String connectionCapability; + @Override + public String toString() { + return type + "," + redirectUrl; + } + } - /** TODO doc */ - public List<WifiPasspointOsuProvider> osuProviderList; + public static class IpAddressType { + public static final int IPV6_NOT_AVAILABLE = 0; + public static final int IPV6_AVAILABLE = 1; + public static final int IPV6_UNKNOWN = 2; + + public static final int IPV4_NOT_AVAILABLE = 0; + public static final int IPV4_PUBLIC = 1; + public static final int IPV4_PORT_RESTRICTED = 2; + public static final int IPV4_SINGLE_NAT = 3; + public static final int IPV4_DOUBLE_NAT = 4; + public static final int IPV4_PORT_RESTRICTED_SINGLE_NAT = 5; + public static final int IPV4_PORT_RESTRICTED_DOUBLE_NAT = 6; + public static final int IPV4_PORT_UNKNOWN = 7; + + private static final int NULL_VALUE = -1; + + public int availability; + + public int getIpv6Availability() { + return availability & 0x3; + } + + public int getIpv4Availability() { + return (availability & 0xFF) >> 2; + } + + @Override + public String toString() { + return getIpv6Availability() + "," + getIpv4Availability(); + } + } + + public static class NaiRealm { + public static final int ENCODING_RFC4282 = 0; + public static final int ENCODING_UTF8 = 1; + + public int encoding; + public String realm; - /** default constructor @hide */ - public WifiPasspointInfo() { - // osuProviderList = new ArrayList<OsuProvider>(); + @Override + public String toString() { + return encoding + "," + realm; + } } - /** copy constructor @hide */ - public WifiPasspointInfo(WifiPasspointInfo source) { - // TODO - bssid = source.bssid; - venueName = source.venueName; - networkAuthType = source.networkAuthType; - roamingConsortium = source.roamingConsortium; - ipAddrTypeAvaibility = source.ipAddrTypeAvaibility; - naiRealm = source.naiRealm; - cellularNetwork = source.cellularNetwork; - domainName = source.domainName; - operatorFriendlyName = source.operatorFriendlyName; - wanMetrics = source.wanMetrics; - connectionCapability = source.connectionCapability; - if (source.osuProviderList != null) { - osuProviderList = new ArrayList<WifiPasspointOsuProvider>(); - for (WifiPasspointOsuProvider osu : source.osuProviderList) - osuProviderList.add(new WifiPasspointOsuProvider(osu)); + public static class CellularNetwork { + public byte[] rawData; + + public int getMnc() { + // TODO + return 0; + } + + public int getMcc() { + // TODO + return 0; + } + + @Override + public String toString() { + if (rawData == null) return null; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < rawData.length; i++) + sb.append(String.format("%02X", rawData[i])); + return sb.toString(); } + } + /** BSSID */ + public String bssid; + + /** venue name */ + public String venueName; + + /** list of network authentication types */ + public List<NetworkAuthType> networkAuthType; + + /** list of roaming consortium OIs */ + public List<String> roamingConsortium; + + /** IP address availability */ + public IpAddressType ipAddrTypeAvailability; + + /** NAI realm */ + public List<NaiRealm> naiRealm; + + /** 3GPP cellular network */ + public CellularNetwork cellularNetwork; + + /** fully qualified domain name (FQDN) */ + public List<String> domainName; + + /** HS 2.0 operator friendly name */ + public String operatorFriendlyName; + + /** HS 2.0 wan metrics */ + public WanMetrics wanMetrics; + + /** HS 2.0 list of IP proto port */ + public List<IpProtoPort> connectionCapability; + + /** HS 2.0 list of OSU providers */ + public List<WifiPasspointOsuProvider> osuProviderList; + /** * Convert mask to ANQP subtypes, for supplicant command use. * @@ -193,46 +299,149 @@ public class WifiPasspointInfo implements Parcelable { @Override public String toString() { StringBuffer sb = new StringBuffer(); + sb.append("BSSID: ").append(bssid); + if (venueName != null) - sb.append(" venueName: ").append(venueName); - if (networkAuthType != null) - sb.append(" networkAuthType: ").append(networkAuthType); - if (roamingConsortium != null) - sb.append(" roamingConsortium: ").append(roamingConsortium); - if (ipAddrTypeAvaibility != null) - sb.append(" ipAddrTypeAvaibility: ").append(ipAddrTypeAvaibility); - if (naiRealm != null) - sb.append(" naiRealm: ").append(naiRealm); + sb.append(" venueName: ").append(venueName.replace("\n", "\\n")); + + if (networkAuthType != null) { + sb.append(" networkAuthType: "); + for (NetworkAuthType auth : networkAuthType) + sb.append("(").append(auth.toString()).append(")"); + } + + if (roamingConsortium != null) { + sb.append(" roamingConsortium: "); + for (String oi : roamingConsortium) + sb.append("(").append(oi).append(")"); + } + + if (ipAddrTypeAvailability != null) { + sb.append(" ipAddrTypeAvaibility: ").append("(") + .append(ipAddrTypeAvailability.toString()).append(")"); + } + + if (naiRealm != null) { + sb.append(" naiRealm: "); + for (NaiRealm realm : naiRealm) + sb.append("(").append(realm.toString()).append(")"); + } + if (cellularNetwork != null) - sb.append(" cellularNetwork: ").append(cellularNetwork); - if (domainName != null) - sb.append(" domainName: ").append(domainName); + sb.append(" cellularNetwork: ").append("(") + .append(cellularNetwork.toString()).append(")"); + + if (domainName != null) { + sb.append(" domainName: "); + for (String fqdn : domainName) + sb.append("(").append(fqdn).append(")"); + } + if (operatorFriendlyName != null) - sb.append(" operatorFriendlyName: ").append(operatorFriendlyName); + sb.append(" operatorFriendlyName: ").append("(") + .append(operatorFriendlyName).append(")"); + if (wanMetrics != null) - sb.append(" wanMetrics: ").append(wanMetrics); - if (connectionCapability != null) - sb.append(" connectionCapability: ").append(connectionCapability); - if (osuProviderList != null) - sb.append(" osuProviderList: (size=" + osuProviderList.size() + ")"); + sb.append(" wanMetrics: ").append("(") + .append(wanMetrics.toString()).append(")"); + + if (connectionCapability != null) { + sb.append(" connectionCapability: "); + for (IpProtoPort ip : connectionCapability) + sb.append("(").append(ip.toString()).append(")"); + } + + if (osuProviderList != null) { + sb.append(" osuProviderList: "); + for (WifiPasspointOsuProvider osu : osuProviderList) + sb.append("(").append(osu.toString()).append(")"); + } + return sb.toString(); } /** Implement the Parcelable interface {@hide} */ @Override public void writeToParcel(Parcel out, int flags) { - out.writeValue(bssid); - out.writeValue(venueName); - out.writeValue(networkAuthType); - out.writeValue(roamingConsortium); - out.writeValue(ipAddrTypeAvaibility); - out.writeValue(naiRealm); - out.writeValue(cellularNetwork); - out.writeValue(domainName); - out.writeValue(operatorFriendlyName); - out.writeValue(wanMetrics); - out.writeValue(connectionCapability); + out.writeString(bssid); + out.writeString(venueName); + + if (networkAuthType == null) { + out.writeInt(0); + } else { + out.writeInt(networkAuthType.size()); + for (NetworkAuthType auth : networkAuthType) { + out.writeInt(auth.type); + out.writeString(auth.redirectUrl); + } + } + + if (roamingConsortium == null) { + out.writeInt(0); + } else { + out.writeInt(roamingConsortium.size()); + for (String oi : roamingConsortium) + out.writeString(oi); + } + + if (ipAddrTypeAvailability == null) { + out.writeInt(IpAddressType.NULL_VALUE); + } else { + out.writeInt(ipAddrTypeAvailability.availability); + } + + if (naiRealm == null) { + out.writeInt(0); + } else { + out.writeInt(naiRealm.size()); + for (NaiRealm realm : naiRealm) { + out.writeInt(realm.encoding); + out.writeString(realm.realm); + } + } + + if (cellularNetwork == null) { + out.writeInt(0); + } else { + out.writeInt(cellularNetwork.rawData.length); + out.writeByteArray(cellularNetwork.rawData); + } + + + if (domainName == null) { + out.writeInt(0); + } else { + out.writeInt(domainName.size()); + for (String fqdn : domainName) + out.writeString(fqdn); + } + + out.writeString(operatorFriendlyName); + + if (wanMetrics == null) { + out.writeInt(0); + } else { + out.writeInt(1); + out.writeInt(wanMetrics.wanInfo); + out.writeLong(wanMetrics.downlinkSpeed); + out.writeLong(wanMetrics.uplinkSpeed); + out.writeInt(wanMetrics.downlinkLoad); + out.writeInt(wanMetrics.uplinkLoad); + out.writeInt(wanMetrics.lmd); + } + + if (connectionCapability == null) { + out.writeInt(0); + } else { + out.writeInt(connectionCapability.size()); + for (IpProtoPort ip : connectionCapability) { + out.writeInt(ip.proto); + out.writeInt(ip.port); + out.writeInt(ip.status); + } + } + if (osuProviderList == null) { out.writeInt(0); } else { @@ -254,18 +463,86 @@ public class WifiPasspointInfo implements Parcelable { @Override public WifiPasspointInfo createFromParcel(Parcel in) { WifiPasspointInfo p = new WifiPasspointInfo(); - p.bssid = (String) in.readValue(String.class.getClassLoader()); - p.venueName = (String) in.readValue(String.class.getClassLoader()); - p.networkAuthType = (String) in.readValue(String.class.getClassLoader()); - p.roamingConsortium = (String) in.readValue(String.class.getClassLoader()); - p.ipAddrTypeAvaibility = (String) in.readValue(String.class.getClassLoader()); - p.naiRealm = (String) in.readValue(String.class.getClassLoader()); - p.cellularNetwork = (String) in.readValue(String.class.getClassLoader()); - p.domainName = (String) in.readValue(String.class.getClassLoader()); - p.operatorFriendlyName = (String) in.readValue(String.class.getClassLoader()); - p.wanMetrics = (String) in.readValue(String.class.getClassLoader()); - p.connectionCapability = (String) in.readValue(String.class.getClassLoader()); - int n = in.readInt(); + int n; + + p.bssid = in.readString(); + p.venueName = in.readString(); + + n = in.readInt(); + if (n > 0) { + p.networkAuthType = new ArrayList<NetworkAuthType>(); + for (int i = 0; i < n; i++) { + NetworkAuthType auth = new NetworkAuthType(); + auth.type = in.readInt(); + auth.redirectUrl = in.readString(); + p.networkAuthType.add(auth); + } + } + + n = in.readInt(); + if (n > 0) { + p.roamingConsortium = new ArrayList<String>(); + for (int i = 0; i < n; i++) + p.roamingConsortium.add(in.readString()); + } + + n = in.readInt(); + if (n != IpAddressType.NULL_VALUE) { + p.ipAddrTypeAvailability = new IpAddressType(); + p.ipAddrTypeAvailability.availability = n; + } + + n = in.readInt(); + if (n > 0) { + p.naiRealm = new ArrayList<NaiRealm>(); + for (int i = 0; i < n; i++) { + NaiRealm realm = new NaiRealm(); + realm.encoding = in.readInt(); + realm.realm = in.readString(); + p.naiRealm.add(realm); + } + } + + n = in.readInt(); + if (n > 0) { + p.cellularNetwork = new CellularNetwork(); + p.cellularNetwork.rawData = new byte[n]; + in.readByteArray(p.cellularNetwork.rawData); + } + + n = in.readInt(); + if (n > 0) { + p.domainName = new ArrayList<String>(); + for (int i = 0; i < n; i++) + p.domainName.add(in.readString()); + } + + p.operatorFriendlyName = in.readString(); + + n = in.readInt(); + if (n > 0) { + p.wanMetrics = new WanMetrics(); + p.wanMetrics.wanInfo = in.readInt(); + p.wanMetrics.downlinkSpeed = in.readLong(); + p.wanMetrics.uplinkSpeed = in.readLong(); + p.wanMetrics.downlinkLoad = in.readInt(); + p.wanMetrics.uplinkLoad = in.readInt(); + p.wanMetrics.lmd = in.readInt(); + } + + n = in.readInt(); + if (n > 0) { + p.connectionCapability = new ArrayList<IpProtoPort>(); + for (int i = 0; i < n; i++) { + IpProtoPort ip = new IpProtoPort(); + ip.proto = in.readInt(); + ip.port = in.readInt(); + ip.status = in.readInt(); + p.connectionCapability.add(ip); + } + } + + n = in.readInt(); if (n > 0) { p.osuProviderList = new ArrayList<WifiPasspointOsuProvider>(); for (int i = 0; i < n; i++) { @@ -274,6 +551,7 @@ public class WifiPasspointInfo implements Parcelable { p.osuProviderList.add(osu); } } + return p; } diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointOsuProvider.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointOsuProvider.java index 18a8f1e..f40dc4f 100644 --- a/wifi/java/android/net/wifi/passpoint/WifiPasspointOsuProvider.java +++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointOsuProvider.java @@ -94,10 +94,10 @@ public class WifiPasspointOsuProvider implements Parcelable { sb.append(" serverUri: ").append(serverUri); sb.append(" osuMethod: ").append(osuMethod); if (iconFileName != null) { - sb.append(" icon: [").append(iconWidth).append("x") + sb.append(" icon: <").append(iconWidth).append("x") .append(iconHeight).append(" ") .append(iconType).append(" ") - .append(iconFileName); + .append(iconFileName).append(">"); } if (osuNai != null) sb.append(" osuNai: ").append(osuNai); @@ -113,16 +113,16 @@ public class WifiPasspointOsuProvider implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeValue(ssid); - out.writeValue(friendlyName); - out.writeValue(serverUri); + out.writeString(ssid); + out.writeString(friendlyName); + out.writeString(serverUri); out.writeInt(osuMethod); out.writeInt(iconWidth); out.writeInt(iconHeight); - out.writeValue(iconType); - out.writeValue(iconFileName); - out.writeValue(osuNai); - out.writeValue(osuService); + out.writeString(iconType); + out.writeString(iconFileName); + out.writeString(osuNai); + out.writeString(osuService); // TODO: icon image? } @@ -131,16 +131,16 @@ public class WifiPasspointOsuProvider implements Parcelable { @Override public WifiPasspointOsuProvider createFromParcel(Parcel in) { WifiPasspointOsuProvider osu = new WifiPasspointOsuProvider(); - osu.ssid = (String) in.readValue(String.class.getClassLoader()); - osu.friendlyName = (String) in.readValue(String.class.getClassLoader()); - osu.serverUri = (String) in.readValue(String.class.getClassLoader()); + osu.ssid = in.readString(); + osu.friendlyName = in.readString(); + osu.serverUri = in.readString(); osu.osuMethod = in.readInt(); osu.iconWidth = in.readInt(); osu.iconHeight = in.readInt(); - osu.iconType = (String) in.readValue(String.class.getClassLoader()); - osu.iconFileName = (String) in.readValue(String.class.getClassLoader()); - osu.osuNai = (String) in.readValue(String.class.getClassLoader()); - osu.osuService = (String) in.readValue(String.class.getClassLoader()); + osu.iconType = in.readString(); + osu.iconFileName = in.readString(); + osu.osuNai = in.readString(); + osu.osuService = in.readString(); return osu; } 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; |
